Skip to content
Draft
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
21 changes: 21 additions & 0 deletions flow360/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@
PointArray2D,
Slice,
)
from flow360.component.simulation.outputs.output_render_types import (
RenderCameraConfig,
RenderLightingConfig,
RenderEnvironmentConfig,
RenderMaterialConfig,
AmbientLight,
DirectionalLight,
OrthographicProjection,
PerspectiveProjection,
StaticCamera,
AnimatedCamera,
Keyframe,
SolidBackground,
SkyboxBackground,
SkyboxTexture,
PBRMaterial,
FieldMaterial,
ColorKey,
Transform
)
from flow360.component.simulation.outputs.outputs import (
AeroAcousticOutput,
IsosurfaceOutput,
Expand All @@ -126,6 +146,7 @@
TimeAverageVolumeOutput,
UserDefinedField,
VolumeOutput,
RenderOutput
)
from flow360.component.simulation.primitives import (
Box,
Expand Down
134 changes: 134 additions & 0 deletions flow360/component/simulation/outputs/output_render_types.py
Original file line number Diff line number Diff line change
@@ -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])
60 changes: 59 additions & 1 deletion flow360/component/simulation/outputs/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
Point,
PointArray,
PointArray2D,
Slice,
Slice
)
from flow360.component.simulation.outputs.output_render_types import (
RenderCameraConfig,
RenderLightingConfig,
RenderEnvironmentConfig,
RenderMaterialConfig,
Transform,
)
from flow360.component.simulation.outputs.output_fields import (
AllFieldNames,
Expand Down Expand Up @@ -463,6 +470,56 @@ 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`."
)
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 "
":ref:`universal output variables<UniversalVariablesV2>` and :class:`UserDefinedField`."
)
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)


class ProbeOutput(_OutputBase):
"""
:class:`ProbeOutput` class for setting output data probed at monitor points.
Expand Down Expand Up @@ -999,6 +1056,7 @@ class StreamlineOutput(Flow360BaseModel):
TimeAverageSurfaceProbeOutput,
AeroAcousticOutput,
StreamlineOutput,
RenderOutput
],
pd.Field(discriminator="output_type"),
]
Expand Down
75 changes: 68 additions & 7 deletions flow360/component/simulation/translator/solver_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand Down Expand Up @@ -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": []}
Expand All @@ -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)
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -438,6 +447,52 @@ 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)
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,
"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,
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),
"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


def translate_surface_slice_output(
output_params: list,
output_class: Union[SurfaceSliceOutput],
Expand Down Expand Up @@ -636,6 +691,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 = {}
Expand Down
Loading