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 @@ -124,12 +124,33 @@
PointArray2D,
Slice,
)
from flow360.component.simulation.outputs.output_render_types import (
AmbientLight,
AnimatedCamera,
ColorKey,
DirectionalLight,
FieldMaterial,
Keyframe,
OrthographicProjection,
PBRMaterial,
PerspectiveProjection,
RenderCameraConfig,
RenderEnvironmentConfig,
RenderLightingConfig,
SkyboxBackground,
SkyboxTexture,
SolidBackground,
StaticCamera,
Transform,
)
from flow360.component.simulation.outputs.outputs import (
AeroAcousticOutput,
IsosurfaceOutput,
MovingStatistic,
Observer,
ProbeOutput,
RenderOutputGroup,
RenderOutput,
SliceOutput,
StreamlineOutput,
SurfaceIntegralOutput,
Expand Down
4 changes: 2 additions & 2 deletions flow360/component/simulation/outputs/output_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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.simulation.unit_system import AngleType, LengthType
from flow360.component.simulation.user_code.core.types import (
Expression,
UnytQuantity,
Expand All @@ -21,7 +21,7 @@
solver_variable_to_user_variable,
)
from flow360.component.simulation.user_code.core.utils import is_runtime_expression
from flow360.component.types import Axis
from flow360.component.types import Axis, Color, Vector


class _OutputItemBase(Flow360BaseModel):
Expand Down
251 changes: 251 additions & 0 deletions flow360/component/simulation/outputs/output_render_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import abc
from enum import Enum
from typing import Dict, List, Optional, Union

import pydantic as pd

import flow360.component.simulation.units as u
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.unit_system import AngleType, LengthType
from flow360.component.types import Color, Vector


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()

@classmethod
def orthographic(cls, x=1, y=1, z=1, scale=1):
return RenderCameraConfig(
view=StaticCamera(
position=(x * scale, y * scale, z * scale) * u.m,
target=(0, 0, 0) * u.m
),
projection=OrthographicProjection(
width=scale * u.m,
near=0.01 * u.m,
far=50 * scale * u.m
)
)

@classmethod
def perspective(cls, x=1, y=1, z=1, scale=1):
return RenderCameraConfig(
view=StaticCamera(
position=(x * scale, y * scale, z * scale) * u.m,
target=(0, 0, 0) * u.m
),
projection=PerspectiveProjection(
fov=60 * u.deg,
near=0.01 * u.m,
far=50 * scale * u.m
)
)


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)

@classmethod
def default(cls):
return RenderLightingConfig(
ambient=AmbientLight(
intensity=0.5,
color=(255, 255, 255)
),
directional=DirectionalLight(
intensity=1.5,
color=(255, 255, 255),
direction=(-1.0, -1.0, -1.0)
)
)


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"
GRADIENT = "gradient"


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()

@classmethod
def simple(cls):
return RenderEnvironmentConfig(
background=SolidBackground(
color=(207, 226, 230)
)
)

@classmethod
def sky(cls):
return RenderEnvironmentConfig(
background=SkyboxBackground(
texture=SkyboxTexture.SKY
)
)

@classmethod
def gradient(cls):
return RenderEnvironmentConfig(
background=SkyboxBackground(
texture=SkyboxTexture.GRADIENT
)
)


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)

@classmethod
def metal(cls, shine=0.5, alpha=1.0):
return PBRMaterial(
color=(255, 255, 255),
alpha=alpha,
roughness=1 - shine,
f0=(0.56, 0.56, 0.56)
)

@classmethod
def plastic(cls, shine=0.5, alpha=1.0):
return PBRMaterial(
color=(255, 255, 255),
alpha=alpha,
roughness=1 - shine,
f0=(0.03, 0.03, 0.03)
)


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=1)
colormap: List[ColorKey] = pd.Field()
type: str = pd.Field(default="field", frozen=True)

@classmethod
def greyscale(cls, field, min=0, max=1, alpha=1):
return FieldMaterial(
alpha=alpha,
output_field=field,
min=min,
max=max,
colormap = [
ColorKey(color=(0, 0, 0), value=0),
ColorKey(color=(255, 255, 255), value=1.0)
]
)

@classmethod
def hot_cold(cls, field, min=0, max=1, alpha=1):
return FieldMaterial(
alpha=alpha,
output_field=field,
min=min,
max=max,
colormap = [
ColorKey(color=(0, 0, 255), value=0),
ColorKey(color=(255, 255, 255), value=0.5),
ColorKey(color=(255, 0, 0), value=1.0)
]
)

@classmethod
def rainbow(cls, field, min=0, max=1, alpha=1):
return FieldMaterial(
alpha=alpha,
output_field=field,
min=min,
max=max,
colormap = [
ColorKey(color=(0, 0, 255), value=0),
ColorKey(color=(0, 255, 255), value=0.25),
ColorKey(color=(0, 255, 0), value=0.5),
ColorKey(color=(255, 255, 0), value=0.75),
ColorKey(color=(255, 0, 0), value=1.0)
]
)


AnyMaterial = Union[PBRMaterial, FieldMaterial]


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])
64 changes: 63 additions & 1 deletion flow360/component/simulation/outputs/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

# pylint: disable=too-many-lines
from typing import Annotated, List, Literal, Optional, Union, get_args
from typing import Annotated, Dict, List, Literal, Optional, Union, get_args

import pydantic as pd

Expand All @@ -32,6 +32,13 @@
VolumeFieldNames,
get_field_values,
)
from flow360.component.simulation.outputs.output_render_types import (
AnyMaterial,
RenderCameraConfig,
RenderEnvironmentConfig,
RenderLightingConfig,
Transform,
)
from flow360.component.simulation.primitives import (
GhostCircularPlane,
GhostSphere,
Expand Down Expand Up @@ -665,6 +672,60 @@ def allow_only_simulation_surfaces_or_imported_surfaces(cls, value):
return value


class RenderOutputGroup(Flow360BaseModel):
surfaces: Optional[EntityList[Surface]] = pd.Field(
None, description="List of of :class:`~flow360.Surface` entities."
)
slices: Optional[EntityList[Slice]] = pd.Field(
None, description="List of of :class:`~flow360.Slice` entities."
)
isosurfaces: Optional[UniqueItemList[Isosurface]] = pd.Field(
None, description="List of :class:`~flow360.Isosurface` entities."
)
material: AnyMaterial = pd.Field()


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: str = pd.Field("Render output", description="Name of the `RenderOutput`.")
groups: List[RenderOutputGroup] = pd.Field("Render groups")
output_fields: UniqueItemList[Union[CommonFieldNames, str]] = pd.Field(
[], description="List of output variables."
)
camera: RenderCameraConfig = pd.Field(description="Camera settings", default_factory=RenderCameraConfig.orthographic)
lighting: RenderLightingConfig = pd.Field(description="Lighting settings", default_factory=RenderLightingConfig.default)
environment: RenderEnvironmentConfig = pd.Field(description="Environment settings", default_factory=RenderEnvironmentConfig.simple)
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 @@ -1293,6 +1354,7 @@ class TimeAverageStreamlineOutput(StreamlineOutput):
AeroAcousticOutput,
StreamlineOutput,
TimeAverageStreamlineOutput,
RenderOutput,
],
pd.Field(discriminator="output_type"),
]
Expand Down
Loading