Skip to content

Commit c2c67a6

Browse files
Andrzej KrupkaAndrzej Krupka
authored andcommitted
Added basic data models for render output
1 parent 83f0f31 commit c2c67a6

File tree

6 files changed

+204
-13
lines changed

6 files changed

+204
-13
lines changed

flow360/component/simulation/outputs/output_entities.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
"""Output for simulation."""
22

33
from abc import ABCMeta
4-
from typing import Literal, Union
4+
from typing import Literal, Union, Optional
55

66
import pydantic as pd
77

88
from flow360.component.simulation.framework.base_model import Flow360BaseModel
99
from flow360.component.simulation.framework.entity_base import EntityBase, generate_uuid
1010
from flow360.component.simulation.outputs.output_fields import IsoSurfaceFieldNames
11-
from flow360.component.simulation.unit_system import LengthType
12-
from flow360.component.types import Axis
11+
from flow360.component.simulation.unit_system import LengthType, AngleType
12+
from flow360.component.types import Axis, Vector, Color
1313

1414

1515
class _OutputItemBase(Flow360BaseModel):
@@ -178,3 +178,63 @@ class PointArray2D(_PointEntityBase):
178178
v_axis_vector: LengthType.Axis = pd.Field(description="The scaled v-axis of the parallelogram.")
179179
u_number_of_points: int = pd.Field(ge=2, description="The number of points along the u axis.")
180180
v_number_of_points: int = pd.Field(ge=2, description="The number of points along the v axis.")
181+
182+
183+
# Linear interpolation between keyframes
184+
class KeyframeCamera(Flow360BaseModel):
185+
pass
186+
187+
188+
# Follow a cubic spline curve
189+
class SplineCamera(Flow360BaseModel):
190+
pass
191+
192+
193+
# Orbit a point (start and end points specified in spherical coordinates)
194+
class OrbitCamera(Flow360BaseModel):
195+
pass
196+
197+
198+
# Static camera setup in Cartesian coordinates
199+
class StaticCamera(Flow360BaseModel):
200+
position: LengthType.Point = pd.Field(description="Position of the camera in the scene")
201+
target: LengthType.Point = pd.Field(description="Target point of the camera")
202+
up: Optional[Vector] = pd.Field((0, 1, 0), description="Up vector, if not specified assume Y+")
203+
204+
205+
# Ortho projection, parallel lines stay parallel
206+
class OrthographicProjection(Flow360BaseModel):
207+
width: LengthType = pd.Field()
208+
near: LengthType = pd.Field()
209+
far: LengthType = pd.Field()
210+
211+
212+
# Perspective projection
213+
class PerspectiveProjection(Flow360BaseModel):
214+
fov: AngleType = pd.Field()
215+
near: LengthType = pd.Field()
216+
far: LengthType = pd.Field()
217+
218+
219+
# Only basic static camera with ortho projection supported currently
220+
class RenderCameraConfig(Flow360BaseModel):
221+
view: StaticCamera = pd.Field()
222+
projection: OrthographicProjection = pd.Field()
223+
224+
225+
# Ambient light, added by default to all pixels in the scene, simulates global illumination
226+
class AmbientLight(Flow360BaseModel):
227+
intensity: float = pd.Field()
228+
color: Color = pd.Field()
229+
230+
231+
# Ambient light, added by default to all pixels in the scene, simulates global illumination
232+
class DirectionalLight(Flow360BaseModel):
233+
intensity: float = pd.Field()
234+
color: Color = pd.Field()
235+
direction: Vector = pd.Field()
236+
237+
238+
class RenderLightingConfig(Flow360BaseModel):
239+
ambient: AmbientLight = pd.Field()
240+
directional: DirectionalLight = pd.Field()

flow360/component/simulation/outputs/outputs.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Point,
2020
PointArray,
2121
PointArray2D,
22-
Slice,
22+
Slice, RenderCameraConfig, RenderLightingConfig,
2323
)
2424
from flow360.component.simulation.outputs.output_fields import (
2525
AllFieldNames,
@@ -463,6 +463,51 @@ def ensure_surface_existence(cls, value):
463463
return check_deleted_surface_in_entity_list(value)
464464

465465

466+
class RenderOutput(_AnimationSettings):
467+
"""
468+
469+
:class:`RenderOutput` class for backend rendered output settings.
470+
471+
Example
472+
-------
473+
474+
Define the :class:`RenderOutput` of :code:`qcriterion` on two isosurfaces:
475+
476+
>>> fl.RenderOutput(
477+
... isosurfaces=[
478+
... fl.Isosurface(
479+
... name="Isosurface_T_0.1",
480+
... iso_value=0.1,
481+
... field="T",
482+
... ),
483+
... fl.Isosurface(
484+
... name="Isosurface_p_0.5",
485+
... iso_value=0.5,
486+
... field="p",
487+
... ),
488+
... ],
489+
... output_field="qcriterion",
490+
... )
491+
492+
====
493+
"""
494+
495+
name: Optional[str] = pd.Field(
496+
"Render output", description="Name of the `IsosurfaceOutput`."
497+
)
498+
entities: UniqueItemList[Isosurface] = pd.Field(
499+
alias="isosurfaces",
500+
description="List of :class:`~flow360.Isosurface` entities.",
501+
)
502+
output_fields: UniqueItemList[Union[CommonFieldNames, str]] = pd.Field(
503+
description="List of output variables. Including "
504+
":ref:`universal output variables<UniversalVariablesV2>` and :class:`UserDefinedField`."
505+
)
506+
camera: RenderCameraConfig = pd.Field(description="Camera settings")
507+
lighting: RenderLightingConfig = pd.Field(description="Lighting settings")
508+
output_type: Literal["RenderOutput"] = pd.Field("RenderOutput", frozen=True)
509+
510+
466511
class ProbeOutput(_OutputBase):
467512
"""
468513
:class:`ProbeOutput` class for setting output data probed at monitor points.
@@ -999,6 +1044,7 @@ class StreamlineOutput(Flow360BaseModel):
9991044
TimeAverageSurfaceProbeOutput,
10001045
AeroAcousticOutput,
10011046
StreamlineOutput,
1047+
RenderOutput
10021048
],
10031049
pd.Field(discriminator="output_type"),
10041050
]

flow360/component/simulation/translator/solver_translator.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
TimeAverageSurfaceProbeOutput,
6969
TimeAverageVolumeOutput,
7070
UserDefinedField,
71-
VolumeOutput,
71+
VolumeOutput, RenderOutput,
7272
)
7373
from flow360.component.simulation.primitives import Box, SurfacePair
7474
from flow360.component.simulation.simulation_params import SimulationParams
@@ -82,7 +82,7 @@
8282
remove_units_in_dict,
8383
replace_dict_key,
8484
translate_setting_and_apply_to_all_entities,
85-
update_dict_recursively,
85+
update_dict_recursively, get_all_entries_of_type,
8686
)
8787
from flow360.component.simulation.unit_system import LengthType
8888
from flow360.component.simulation.utils import (
@@ -149,7 +149,7 @@ def init_average_output(
149149
return base
150150

151151

152-
def init_output_base(obj_list, class_type: Type, has_average_capability: bool, is_average: bool):
152+
def init_output_base(obj_list, class_type: Type, has_average_capability: bool, is_average: bool, no_format=False):
153153
"""Initialize the common output attribute."""
154154

155155
base = {"outputFields": []}
@@ -158,10 +158,11 @@ def init_output_base(obj_list, class_type: Type, has_average_capability: bool, i
158158
class_type,
159159
"output_format",
160160
)
161-
assert output_format is not None
162-
if output_format == "both":
163-
output_format = "paraview,tecplot"
164-
base["outputFormat"] = output_format
161+
if not no_format:
162+
assert output_format is not None
163+
if output_format == "both":
164+
output_format = "paraview,tecplot"
165+
base["outputFormat"] = output_format
165166

166167
if is_average:
167168
base = init_average_output(base, obj_list, class_type)
@@ -291,6 +292,14 @@ def inject_isosurface_info(entity: Isosurface):
291292
}
292293

293294

295+
def inject_render_info(entity: Isosurface):
296+
"""inject entity info"""
297+
return {
298+
"surfaceField": entity.field,
299+
"surfaceFieldMagnitude": entity.iso_value,
300+
}
301+
302+
294303
def inject_probe_info(entity: EntityList):
295304
"""inject entity info"""
296305

@@ -438,6 +447,35 @@ def translate_isosurface_output(
438447
return translated_output
439448

440449

450+
def translate_render_output(output_params: list, injection_function):
451+
"""Translate render output settings."""
452+
453+
renders = get_all_entries_of_type(output_params, RenderOutput)
454+
455+
translated_outputs = []
456+
457+
for render in renders:
458+
camera = render.camera.model_dump(exclude_none=True, exclude_unset=True, by_alias=True)
459+
lighting = render.lighting.model_dump(exclude_none=True, exclude_unset=True, by_alias=True)
460+
461+
translated_output = {
462+
"animationFrequency": render.frequency,
463+
"animationFrequencyOffset": render.frequency_offset,
464+
"isoSurfaces": translate_setting_and_apply_to_all_entities(
465+
[render],
466+
RenderOutput,
467+
translation_func=translate_output_fields,
468+
to_list=False,
469+
entity_injection_func=injection_function,
470+
),
471+
"camera": remove_units_in_dict(camera),
472+
"lighting": remove_units_in_dict(lighting)
473+
}
474+
translated_outputs.append(translated_output)
475+
476+
return translated_outputs
477+
478+
441479
def translate_surface_slice_output(
442480
output_params: list,
443481
output_class: Union[SurfaceSliceOutput],
@@ -636,6 +674,12 @@ def translate_output(input_params: SimulationParams, translated: dict):
636674
outputs, inject_isosurface_info
637675
)
638676

677+
##:: Step4: Get translated["renderOutput"]
678+
if has_instance_in_list(outputs, RenderOutput):
679+
translated["renderOutput"] = translate_render_output(
680+
outputs, inject_isosurface_info
681+
)
682+
639683
##:: Step5: Get translated["monitorOutput"]
640684
probe_output = {}
641685
probe_output_average = {}

flow360/component/simulation/translator/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import functools
66
import json
7+
import pydantic as pd
78
from collections import OrderedDict
89
from typing import Union
910

@@ -163,6 +164,17 @@ def getattr_by_path(obj, path: Union[str, list], *args):
163164
return obj
164165

165166

167+
def get_all_entries_of_type(obj_list: list, class_type: type[pd.BaseModel]):
168+
entries = []
169+
170+
if obj_list is not None:
171+
for obj in obj_list:
172+
if is_exact_instance(obj, class_type):
173+
entries.append(obj)
174+
175+
return entries
176+
177+
166178
def get_global_setting_from_first_instance(
167179
obj_list: list,
168180
class_type,

flow360/component/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
List2D = List[List[float]]
1515
# we use tuple for fixed length lists, beacause List is a mutable, variable length structure
1616
Coordinate = Tuple[float, float, float]
17-
17+
Color = Tuple[int, int, int]
1818

1919
class Vector(Coordinate):
2020
""":class: Vector

tests/simulation/translator/test_solver_translator.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@
4040
LiquidOperatingCondition,
4141
ThermalState,
4242
)
43-
from flow360.component.simulation.outputs.output_entities import Slice
43+
from flow360.component.simulation.outputs.output_entities import Slice, Isosurface, RenderCameraConfig, \
44+
OrthographicProjection, StaticCamera, RenderLightingConfig, AmbientLight, DirectionalLight
4445
from flow360.component.simulation.outputs.outputs import (
4546
SliceOutput,
4647
SurfaceOutput,
4748
UserDefinedField,
4849
VolumeOutput,
50+
RenderOutput,
4951
)
5052
from flow360.component.simulation.primitives import ReferenceGeometry, Surface
5153
from flow360.component.simulation.simulation_params import SimulationParams
@@ -119,6 +121,7 @@ def get_om6Wing_tutorial_param():
119121
my_wall = Surface(name="1")
120122
my_symmetry_plane = Surface(name="2")
121123
my_freestream = Surface(name="3")
124+
my_isosurface = Isosurface(name="iso", field="Mach", iso_value=0.5)
122125
with SI_unit_system:
123126
param = SimulationParams(
124127
reference_geometry=ReferenceGeometry(
@@ -182,6 +185,32 @@ def get_om6Wing_tutorial_param():
182185
output_format="paraview",
183186
output_fields=["Cp"],
184187
),
188+
RenderOutput(
189+
entities=[my_isosurface],
190+
output_fields=["qcriterion"],
191+
camera=RenderCameraConfig(
192+
view=StaticCamera(
193+
position=(20, 20, 20),
194+
target=(0, 0, 0)
195+
),
196+
projection=OrthographicProjection(
197+
width=30,
198+
near=0.01,
199+
far=100
200+
)
201+
),
202+
lighting=RenderLightingConfig(
203+
ambient=AmbientLight(
204+
intensity=0.4,
205+
color=(255, 255, 255)
206+
),
207+
directional=DirectionalLight(
208+
intensity=1.5,
209+
color=(255, 255, 255),
210+
direction=(-1.0, -1.0, -1.0)
211+
)
212+
)
213+
)
185214
],
186215
)
187216
return param

0 commit comments

Comments
 (0)