From c2c67a699d65f07f6401842eff444a5ab06387f6 Mon Sep 17 00:00:00 2001 From: Andrzej Krupka Date: Thu, 19 Jun 2025 12:05:43 -0400 Subject: [PATCH 1/3] Added basic data models for render output --- .../simulation/outputs/output_entities.py | 66 ++++++++++++++++++- .../component/simulation/outputs/outputs.py | 48 +++++++++++++- .../translator/solver_translator.py | 58 ++++++++++++++-- .../component/simulation/translator/utils.py | 12 ++++ flow360/component/types.py | 2 +- .../translator/test_solver_translator.py | 31 ++++++++- 6 files changed, 204 insertions(+), 13 deletions(-) diff --git a/flow360/component/simulation/outputs/output_entities.py b/flow360/component/simulation/outputs/output_entities.py index 4a7ea7488..19d28295c 100644 --- a/flow360/component/simulation/outputs/output_entities.py +++ b/flow360/component/simulation/outputs/output_entities.py @@ -1,15 +1,15 @@ """Output for simulation.""" from abc import ABCMeta -from typing import Literal, Union +from typing import Literal, Union, Optional import pydantic as pd from flow360.component.simulation.framework.base_model import Flow360BaseModel from flow360.component.simulation.framework.entity_base import EntityBase, generate_uuid from flow360.component.simulation.outputs.output_fields import IsoSurfaceFieldNames -from flow360.component.simulation.unit_system import LengthType -from flow360.component.types import Axis +from flow360.component.simulation.unit_system import LengthType, AngleType +from flow360.component.types import Axis, Vector, Color class _OutputItemBase(Flow360BaseModel): @@ -178,3 +178,63 @@ class PointArray2D(_PointEntityBase): v_axis_vector: LengthType.Axis = pd.Field(description="The scaled v-axis of the parallelogram.") u_number_of_points: int = pd.Field(ge=2, description="The number of points along the u axis.") v_number_of_points: int = pd.Field(ge=2, description="The number of points along the v axis.") + + +# Linear interpolation between keyframes +class KeyframeCamera(Flow360BaseModel): + pass + + +# Follow a cubic spline curve +class SplineCamera(Flow360BaseModel): + pass + + +# Orbit a point (start and end points specified in spherical coordinates) +class OrbitCamera(Flow360BaseModel): + pass + + +# Static camera setup in Cartesian coordinates +class StaticCamera(Flow360BaseModel): + position: LengthType.Point = pd.Field(description="Position of the camera in the scene") + target: LengthType.Point = pd.Field(description="Target point of the camera") + up: Optional[Vector] = pd.Field((0, 1, 0), description="Up vector, if not specified assume Y+") + + +# Ortho projection, parallel lines stay parallel +class OrthographicProjection(Flow360BaseModel): + width: LengthType = pd.Field() + near: LengthType = pd.Field() + far: LengthType = pd.Field() + + +# Perspective projection +class PerspectiveProjection(Flow360BaseModel): + fov: AngleType = pd.Field() + near: LengthType = pd.Field() + far: LengthType = pd.Field() + + +# Only basic static camera with ortho projection supported currently +class RenderCameraConfig(Flow360BaseModel): + view: StaticCamera = pd.Field() + projection: OrthographicProjection = pd.Field() + + +# Ambient light, added by default to all pixels in the scene, simulates global illumination +class AmbientLight(Flow360BaseModel): + intensity: float = pd.Field() + color: Color = pd.Field() + + +# Ambient light, added by default to all pixels in the scene, simulates global illumination +class DirectionalLight(Flow360BaseModel): + intensity: float = pd.Field() + color: Color = pd.Field() + direction: Vector = pd.Field() + + +class RenderLightingConfig(Flow360BaseModel): + ambient: AmbientLight = pd.Field() + directional: DirectionalLight = pd.Field() diff --git a/flow360/component/simulation/outputs/outputs.py b/flow360/component/simulation/outputs/outputs.py index 1fb58828d..5b6e8ba67 100644 --- a/flow360/component/simulation/outputs/outputs.py +++ b/flow360/component/simulation/outputs/outputs.py @@ -19,7 +19,7 @@ Point, PointArray, PointArray2D, - Slice, + Slice, RenderCameraConfig, RenderLightingConfig, ) from flow360.component.simulation.outputs.output_fields import ( AllFieldNames, @@ -463,6 +463,51 @@ def ensure_surface_existence(cls, value): return check_deleted_surface_in_entity_list(value) +class RenderOutput(_AnimationSettings): + """ + + :class:`RenderOutput` class for backend rendered output settings. + + Example + ------- + + Define the :class:`RenderOutput` of :code:`qcriterion` on two isosurfaces: + + >>> fl.RenderOutput( + ... isosurfaces=[ + ... fl.Isosurface( + ... name="Isosurface_T_0.1", + ... iso_value=0.1, + ... field="T", + ... ), + ... fl.Isosurface( + ... name="Isosurface_p_0.5", + ... iso_value=0.5, + ... field="p", + ... ), + ... ], + ... output_field="qcriterion", + ... ) + + ==== + """ + + name: Optional[str] = pd.Field( + "Render output", description="Name of the `IsosurfaceOutput`." + ) + entities: UniqueItemList[Isosurface] = pd.Field( + alias="isosurfaces", + description="List of :class:`~flow360.Isosurface` entities.", + ) + output_fields: UniqueItemList[Union[CommonFieldNames, str]] = pd.Field( + description="List of output variables. Including " + ":ref:`universal output variables` and :class:`UserDefinedField`." + ) + camera: RenderCameraConfig = pd.Field(description="Camera settings") + lighting: RenderLightingConfig = pd.Field(description="Lighting settings") + output_type: Literal["RenderOutput"] = pd.Field("RenderOutput", frozen=True) + + class ProbeOutput(_OutputBase): """ :class:`ProbeOutput` class for setting output data probed at monitor points. @@ -999,6 +1044,7 @@ class StreamlineOutput(Flow360BaseModel): TimeAverageSurfaceProbeOutput, AeroAcousticOutput, StreamlineOutput, + RenderOutput ], pd.Field(discriminator="output_type"), ] diff --git a/flow360/component/simulation/translator/solver_translator.py b/flow360/component/simulation/translator/solver_translator.py index 53f71e5be..0653c8519 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -68,7 +68,7 @@ TimeAverageSurfaceProbeOutput, TimeAverageVolumeOutput, UserDefinedField, - VolumeOutput, + VolumeOutput, RenderOutput, ) from flow360.component.simulation.primitives import Box, SurfacePair from flow360.component.simulation.simulation_params import SimulationParams @@ -82,7 +82,7 @@ remove_units_in_dict, replace_dict_key, translate_setting_and_apply_to_all_entities, - update_dict_recursively, + update_dict_recursively, get_all_entries_of_type, ) from flow360.component.simulation.unit_system import LengthType from flow360.component.simulation.utils import ( @@ -149,7 +149,7 @@ def init_average_output( return base -def init_output_base(obj_list, class_type: Type, has_average_capability: bool, is_average: bool): +def init_output_base(obj_list, class_type: Type, has_average_capability: bool, is_average: bool, no_format=False): """Initialize the common output attribute.""" base = {"outputFields": []} @@ -158,10 +158,11 @@ def init_output_base(obj_list, class_type: Type, has_average_capability: bool, i class_type, "output_format", ) - assert output_format is not None - if output_format == "both": - output_format = "paraview,tecplot" - base["outputFormat"] = output_format + if not no_format: + assert output_format is not None + if output_format == "both": + output_format = "paraview,tecplot" + base["outputFormat"] = output_format if is_average: base = init_average_output(base, obj_list, class_type) @@ -291,6 +292,14 @@ def inject_isosurface_info(entity: Isosurface): } +def inject_render_info(entity: Isosurface): + """inject entity info""" + return { + "surfaceField": entity.field, + "surfaceFieldMagnitude": entity.iso_value, + } + + def inject_probe_info(entity: EntityList): """inject entity info""" @@ -438,6 +447,35 @@ def translate_isosurface_output( return translated_output +def translate_render_output(output_params: list, injection_function): + """Translate render output settings.""" + + renders = get_all_entries_of_type(output_params, RenderOutput) + + translated_outputs = [] + + for render in renders: + camera = render.camera.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) + lighting = render.lighting.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) + + translated_output = { + "animationFrequency": render.frequency, + "animationFrequencyOffset": render.frequency_offset, + "isoSurfaces": translate_setting_and_apply_to_all_entities( + [render], + RenderOutput, + translation_func=translate_output_fields, + to_list=False, + entity_injection_func=injection_function, + ), + "camera": remove_units_in_dict(camera), + "lighting": remove_units_in_dict(lighting) + } + translated_outputs.append(translated_output) + + return translated_outputs + + def translate_surface_slice_output( output_params: list, output_class: Union[SurfaceSliceOutput], @@ -636,6 +674,12 @@ def translate_output(input_params: SimulationParams, translated: dict): outputs, inject_isosurface_info ) + ##:: Step4: Get translated["renderOutput"] + if has_instance_in_list(outputs, RenderOutput): + translated["renderOutput"] = translate_render_output( + outputs, inject_isosurface_info + ) + ##:: Step5: Get translated["monitorOutput"] probe_output = {} probe_output_average = {} diff --git a/flow360/component/simulation/translator/utils.py b/flow360/component/simulation/translator/utils.py index cce76f33c..7e12f19c3 100644 --- a/flow360/component/simulation/translator/utils.py +++ b/flow360/component/simulation/translator/utils.py @@ -4,6 +4,7 @@ import functools import json +import pydantic as pd from collections import OrderedDict from typing import Union @@ -163,6 +164,17 @@ def getattr_by_path(obj, path: Union[str, list], *args): return obj +def get_all_entries_of_type(obj_list: list, class_type: type[pd.BaseModel]): + entries = [] + + if obj_list is not None: + for obj in obj_list: + if is_exact_instance(obj, class_type): + entries.append(obj) + + return entries + + def get_global_setting_from_first_instance( obj_list: list, class_type, diff --git a/flow360/component/types.py b/flow360/component/types.py index 914b52b13..3823f7a2f 100644 --- a/flow360/component/types.py +++ b/flow360/component/types.py @@ -14,7 +14,7 @@ List2D = List[List[float]] # we use tuple for fixed length lists, beacause List is a mutable, variable length structure Coordinate = Tuple[float, float, float] - +Color = Tuple[int, int, int] class Vector(Coordinate): """:class: Vector diff --git a/tests/simulation/translator/test_solver_translator.py b/tests/simulation/translator/test_solver_translator.py index a99ee9254..85edb6bed 100644 --- a/tests/simulation/translator/test_solver_translator.py +++ b/tests/simulation/translator/test_solver_translator.py @@ -40,12 +40,14 @@ LiquidOperatingCondition, ThermalState, ) -from flow360.component.simulation.outputs.output_entities import Slice +from flow360.component.simulation.outputs.output_entities import Slice, Isosurface, RenderCameraConfig, \ + OrthographicProjection, StaticCamera, RenderLightingConfig, AmbientLight, DirectionalLight from flow360.component.simulation.outputs.outputs import ( SliceOutput, SurfaceOutput, UserDefinedField, VolumeOutput, + RenderOutput, ) from flow360.component.simulation.primitives import ReferenceGeometry, Surface from flow360.component.simulation.simulation_params import SimulationParams @@ -119,6 +121,7 @@ def get_om6Wing_tutorial_param(): my_wall = Surface(name="1") my_symmetry_plane = Surface(name="2") my_freestream = Surface(name="3") + my_isosurface = Isosurface(name="iso", field="Mach", iso_value=0.5) with SI_unit_system: param = SimulationParams( reference_geometry=ReferenceGeometry( @@ -182,6 +185,32 @@ def get_om6Wing_tutorial_param(): output_format="paraview", output_fields=["Cp"], ), + RenderOutput( + entities=[my_isosurface], + output_fields=["qcriterion"], + camera=RenderCameraConfig( + view=StaticCamera( + position=(20, 20, 20), + target=(0, 0, 0) + ), + projection=OrthographicProjection( + width=30, + near=0.01, + far=100 + ) + ), + lighting=RenderLightingConfig( + ambient=AmbientLight( + intensity=0.4, + color=(255, 255, 255) + ), + directional=DirectionalLight( + intensity=1.5, + color=(255, 255, 255), + direction=(-1.0, -1.0, -1.0) + ) + ) + ) ], ) return param From 7b04946dcf5487d9b4a9a2ad4d3fe526eb1d6beb Mon Sep 17 00:00:00 2001 From: Andrzej Krupka Date: Fri, 20 Jun 2025 16:47:29 +0000 Subject: [PATCH 2/3] Update module imports --- flow360/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flow360/__init__.py b/flow360/__init__.py index 3ade4723e..44dd862a8 100644 --- a/flow360/__init__.py +++ b/flow360/__init__.py @@ -107,6 +107,16 @@ PointArray, PointArray2D, Slice, + RenderCameraConfig, + RenderLightingConfig, + AmbientLight, + DirectionalLight, + OrthographicProjection, + PerspectiveProjection, + StaticCamera, + OrbitCamera, + SplineCamera, + KeyframeCamera ) from flow360.component.simulation.outputs.outputs import ( AeroAcousticOutput, @@ -126,6 +136,7 @@ TimeAverageVolumeOutput, UserDefinedField, VolumeOutput, + RenderOutput ) from flow360.component.simulation.primitives import ( Box, From 3dd8a5f01f0be0e926d523d03e705d143e04c3f4 Mon Sep 17 00:00:00 2001 From: Andrzej Krupka Date: Tue, 24 Jun 2025 17:22:05 -0400 Subject: [PATCH 3/3] Add additional config options to render output --- flow360/__init__.py | 16 ++- .../simulation/outputs/output_entities.py | 66 +-------- .../simulation/outputs/output_render_types.py | 134 ++++++++++++++++++ .../component/simulation/outputs/outputs.py | 20 ++- .../translator/solver_translator.py | 19 ++- .../component/simulation/translator/utils.py | 23 +-- 6 files changed, 197 insertions(+), 81 deletions(-) create mode 100644 flow360/component/simulation/outputs/output_render_types.py diff --git a/flow360/__init__.py b/flow360/__init__.py index 44dd862a8..8855397ca 100644 --- a/flow360/__init__.py +++ b/flow360/__init__.py @@ -107,16 +107,26 @@ PointArray, PointArray2D, Slice, +) +from flow360.component.simulation.outputs.output_render_types import ( RenderCameraConfig, RenderLightingConfig, + RenderEnvironmentConfig, + RenderMaterialConfig, AmbientLight, DirectionalLight, OrthographicProjection, PerspectiveProjection, StaticCamera, - OrbitCamera, - SplineCamera, - KeyframeCamera + AnimatedCamera, + Keyframe, + SolidBackground, + SkyboxBackground, + SkyboxTexture, + PBRMaterial, + FieldMaterial, + ColorKey, + Transform ) from flow360.component.simulation.outputs.outputs import ( AeroAcousticOutput, diff --git a/flow360/component/simulation/outputs/output_entities.py b/flow360/component/simulation/outputs/output_entities.py index 19d28295c..4a7ea7488 100644 --- a/flow360/component/simulation/outputs/output_entities.py +++ b/flow360/component/simulation/outputs/output_entities.py @@ -1,15 +1,15 @@ """Output for simulation.""" from abc import ABCMeta -from typing import Literal, Union, Optional +from typing import Literal, Union import pydantic as pd from flow360.component.simulation.framework.base_model import Flow360BaseModel from flow360.component.simulation.framework.entity_base import EntityBase, generate_uuid from flow360.component.simulation.outputs.output_fields import IsoSurfaceFieldNames -from flow360.component.simulation.unit_system import LengthType, AngleType -from flow360.component.types import Axis, Vector, Color +from flow360.component.simulation.unit_system import LengthType +from flow360.component.types import Axis class _OutputItemBase(Flow360BaseModel): @@ -178,63 +178,3 @@ class PointArray2D(_PointEntityBase): v_axis_vector: LengthType.Axis = pd.Field(description="The scaled v-axis of the parallelogram.") u_number_of_points: int = pd.Field(ge=2, description="The number of points along the u axis.") v_number_of_points: int = pd.Field(ge=2, description="The number of points along the v axis.") - - -# Linear interpolation between keyframes -class KeyframeCamera(Flow360BaseModel): - pass - - -# Follow a cubic spline curve -class SplineCamera(Flow360BaseModel): - pass - - -# Orbit a point (start and end points specified in spherical coordinates) -class OrbitCamera(Flow360BaseModel): - pass - - -# Static camera setup in Cartesian coordinates -class StaticCamera(Flow360BaseModel): - position: LengthType.Point = pd.Field(description="Position of the camera in the scene") - target: LengthType.Point = pd.Field(description="Target point of the camera") - up: Optional[Vector] = pd.Field((0, 1, 0), description="Up vector, if not specified assume Y+") - - -# Ortho projection, parallel lines stay parallel -class OrthographicProjection(Flow360BaseModel): - width: LengthType = pd.Field() - near: LengthType = pd.Field() - far: LengthType = pd.Field() - - -# Perspective projection -class PerspectiveProjection(Flow360BaseModel): - fov: AngleType = pd.Field() - near: LengthType = pd.Field() - far: LengthType = pd.Field() - - -# Only basic static camera with ortho projection supported currently -class RenderCameraConfig(Flow360BaseModel): - view: StaticCamera = pd.Field() - projection: OrthographicProjection = pd.Field() - - -# Ambient light, added by default to all pixels in the scene, simulates global illumination -class AmbientLight(Flow360BaseModel): - intensity: float = pd.Field() - color: Color = pd.Field() - - -# Ambient light, added by default to all pixels in the scene, simulates global illumination -class DirectionalLight(Flow360BaseModel): - intensity: float = pd.Field() - color: Color = pd.Field() - direction: Vector = pd.Field() - - -class RenderLightingConfig(Flow360BaseModel): - ambient: AmbientLight = pd.Field() - directional: DirectionalLight = pd.Field() diff --git a/flow360/component/simulation/outputs/output_render_types.py b/flow360/component/simulation/outputs/output_render_types.py new file mode 100644 index 000000000..769ca569b --- /dev/null +++ b/flow360/component/simulation/outputs/output_render_types.py @@ -0,0 +1,134 @@ +import abc +from enum import Enum +from typing import Optional, List, Union, Dict + +from flow360.component.simulation.framework.base_model import Flow360BaseModel +from flow360.component.simulation.unit_system import LengthType, AngleType + +import pydantic as pd + +from flow360.component.types import Vector, Color + + +class StaticCamera(Flow360BaseModel): + position: LengthType.Point = pd.Field(description="Position of the camera in the scene") + target: LengthType.Point = pd.Field(description="Target point of the camera") + up: Optional[Vector] = pd.Field(default=(0, 1, 0), description="Up vector, if not specified assume Y+") + + +class Keyframe(Flow360BaseModel): + time: pd.confloat(ge=0) = pd.Field(0) + view: StaticCamera = pd.Field() + + +class AnimatedCamera(Flow360BaseModel): + keyframes: List[Keyframe] = pd.Field([]) + + +AllCameraTypes = Union[StaticCamera, AnimatedCamera] + + +class OrthographicProjection(Flow360BaseModel): + type: str = pd.Field(default="orthographic", frozen=True) + width: LengthType = pd.Field() + near: LengthType = pd.Field() + far: LengthType = pd.Field() + + +class PerspectiveProjection(Flow360BaseModel): + type: str = pd.Field(default="perspective", frozen=True) + fov: AngleType = pd.Field() + near: LengthType = pd.Field() + far: LengthType = pd.Field() + + +class RenderCameraConfig(Flow360BaseModel): + view: AllCameraTypes = pd.Field() + projection: Union[OrthographicProjection, PerspectiveProjection] = pd.Field() + + +class AmbientLight(Flow360BaseModel): + intensity: float = pd.Field() + color: Color = pd.Field() + + +class DirectionalLight(Flow360BaseModel): + intensity: float = pd.Field() + color: Color = pd.Field() + direction: Vector = pd.Field() + + +class RenderLightingConfig(Flow360BaseModel): + directional: DirectionalLight = pd.Field() + ambient: Optional[AmbientLight] = pd.Field(None) + + +class RenderBackgroundBase(Flow360BaseModel, metaclass=abc.ABCMeta): + type: str = pd.Field(default="", frozen=True) + + +class SolidBackground(RenderBackgroundBase): + type: str = pd.Field(default="solid", frozen=True) + color: Color = pd.Field() + + +class SkyboxTexture(str, Enum): + SKY = "sky" + + +class SkyboxBackground(RenderBackgroundBase): + type: str = pd.Field(default="skybox", frozen=True) + texture: SkyboxTexture = pd.Field(SkyboxTexture.SKY) + + +AllBackgroundTypes = Union[ + SolidBackground, + SkyboxBackground +] + + +class RenderEnvironmentConfig(Flow360BaseModel): + background: AllBackgroundTypes = pd.Field() + + +class RenderMaterialBase(Flow360BaseModel, metaclass=abc.ABCMeta): + type: str = pd.Field(default="", frozen=True) + + +class PBRMaterial(RenderMaterialBase): + color: Color = pd.Field(default=[255, 255, 255]) + alpha: float = pd.Field(default=1) + roughness: float = pd.Field(default=0.5) + f0: Vector = pd.Field(default=(0.03, 0.03, 0.03)) + type: str = pd.Field(default="pbr", frozen=True) + + +class ColorKey(Flow360BaseModel): + color: Color = pd.Field(default=[255, 255, 255]) + value: pd.confloat(ge=0, le=1) = pd.Field(default=0.5) + + +class FieldMaterial(RenderMaterialBase): + alpha: float = pd.Field(default=1) + output_field: str = pd.Field(default="") + min: float = pd.Field(default=0) + max: float = pd.Field(default=0) + colormap: List[ColorKey] = pd.Field() + type: str = pd.Field(default="field", frozen=True) + + +AllMaterialTypes = Union[ + PBRMaterial, + FieldMaterial +] + + +class RenderMaterialConfig(Flow360BaseModel): + materials: List[AllMaterialTypes] = pd.Field([]) + mappings: Dict[str, int] = pd.Field({}) + + +class Transform(Flow360BaseModel): + translation: LengthType.Point = pd.Field(default=[0, 0, 0]) + rotation: AngleType.Vector = pd.Field(default=[0, 0, 0]) + scale: Vector = pd.Field(default=[1, 1, 1]) \ No newline at end of file diff --git a/flow360/component/simulation/outputs/outputs.py b/flow360/component/simulation/outputs/outputs.py index 5b6e8ba67..3aaafbe18 100644 --- a/flow360/component/simulation/outputs/outputs.py +++ b/flow360/component/simulation/outputs/outputs.py @@ -19,7 +19,14 @@ Point, PointArray, PointArray2D, - Slice, RenderCameraConfig, RenderLightingConfig, + Slice +) +from flow360.component.simulation.outputs.output_render_types import ( + RenderCameraConfig, + RenderLightingConfig, + RenderEnvironmentConfig, + RenderMaterialConfig, + Transform, ) from flow360.component.simulation.outputs.output_fields import ( AllFieldNames, @@ -495,9 +502,11 @@ class RenderOutput(_AnimationSettings): name: Optional[str] = pd.Field( "Render output", description="Name of the `IsosurfaceOutput`." ) - entities: UniqueItemList[Isosurface] = pd.Field( - alias="isosurfaces", - description="List of :class:`~flow360.Isosurface` entities.", + isosurfaces: Optional[UniqueItemList[Isosurface]] = pd.Field(None, + description="List of :class:`~flow360.Isosurface` entities." + ) + surfaces: Optional[EntityList[Surface]] = pd.Field(None, + description="List of of :class:`~flow360.Surface` entities." ) output_fields: UniqueItemList[Union[CommonFieldNames, str]] = pd.Field( description="List of output variables. Including " @@ -505,6 +514,9 @@ class RenderOutput(_AnimationSettings): ) camera: RenderCameraConfig = pd.Field(description="Camera settings") lighting: RenderLightingConfig = pd.Field(description="Lighting settings") + environment: RenderEnvironmentConfig = pd.Field(description="Environment settings") + materials: RenderMaterialConfig = pd.Field(description="Material settings") + transform: Optional[Transform] = pd.Field(None, description="Optional model transform to apply to all entities") output_type: Literal["RenderOutput"] = pd.Field("RenderOutput", frozen=True) diff --git a/flow360/component/simulation/translator/solver_translator.py b/flow360/component/simulation/translator/solver_translator.py index 0653c8519..4e357bc9f 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -457,6 +457,8 @@ def translate_render_output(output_params: list, injection_function): for render in renders: camera = render.camera.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) lighting = render.lighting.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) + environment = render.environment.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) + materials = render.materials.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) translated_output = { "animationFrequency": render.frequency, @@ -467,10 +469,25 @@ def translate_render_output(output_params: list, injection_function): translation_func=translate_output_fields, to_list=False, entity_injection_func=injection_function, + entity_list_attribute_name="isosurfaces" + ), + "surfaces": translate_setting_and_apply_to_all_entities( + [render], + RenderOutput, + translation_func=translate_output_fields, + to_list=False, + entity_list_attribute_name="surfaces" ), "camera": remove_units_in_dict(camera), - "lighting": remove_units_in_dict(lighting) + "lighting": remove_units_in_dict(lighting), + "environment": remove_units_in_dict(environment), + "materials": remove_units_in_dict(materials), } + + if render.transform: + transform = render.transform.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) + translated_output["transform"] = remove_units_in_dict(transform) + translated_outputs.append(translated_output) return translated_outputs diff --git a/flow360/component/simulation/translator/utils.py b/flow360/component/simulation/translator/utils.py index 7e12f19c3..75a77c301 100644 --- a/flow360/component/simulation/translator/utils.py +++ b/flow360/component/simulation/translator/utils.py @@ -229,6 +229,7 @@ def translate_setting_and_apply_to_all_entities( lump_list_of_entities=False, use_instance_name_as_key=False, use_sub_item_as_key=False, + entity_list_attribute_name="entities", **kwargs, ): """ @@ -308,26 +309,28 @@ def translate_setting_and_apply_to_all_entities( if k.startswith(entity_injection_prefix) } + + # pylint: disable=too-many-nested-blocks for obj in obj_list: if class_type and is_exact_instance(obj, class_type): - list_of_entities = [] - if "entities" in obj.__class__.model_fields: - if obj.entities is None or ( - "stored_entities" in obj.entities.__class__.model_fields - and obj.entities.stored_entities is None + if entity_list_attribute_name in obj.__class__.model_fields: + entities = getattr(obj, entity_list_attribute_name) + if entities is None or ( + "stored_entities" in entities.__class__.model_fields + and entities.stored_entities is None ): # unique item list does not allow None "items" for now. continue - if isinstance(obj.entities, EntityList): + if isinstance(entities, EntityList): list_of_entities = ( - obj.entities.stored_entities + entities.stored_entities if lump_list_of_entities is False - else [obj.entities] + else [entities] ) - elif isinstance(obj.entities, UniqueItemList): + elif isinstance(entities, UniqueItemList): list_of_entities = ( - obj.entities.items if lump_list_of_entities is False else [obj.entities] + entities.items if lump_list_of_entities is False else [entities] ) elif "entity_pairs" in obj.__class__.model_fields: # Note: This is only used in Periodic BC and lump_list_of_entities is not relavant