Skip to content
Merged
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
5 changes: 1 addition & 4 deletions flow360/component/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pydantic as pd
import typing_extensions
from flow360_schema.framework.physical_dimensions import Length
from flow360_schema.models.asset_cache import CoordinateSystemStatus, MirrorStatus
from pydantic import PositiveInt

from flow360.cloud.file_cache import get_shared_cloud_file_cache
Expand Down Expand Up @@ -47,10 +48,6 @@
DraftContext,
get_active_draft,
)
from flow360.component.simulation.draft_context.coordinate_system_manager import (
CoordinateSystemStatus,
)
from flow360.component.simulation.draft_context.mirror import MirrorStatus
from flow360.component.simulation.draft_context.obb.tessellation_loader import (
TessellationFileLoader,
)
Expand Down
2 changes: 1 addition & 1 deletion flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Optional, Type, TypeVar, get_args

from flow360_schema.framework.physical_dimensions import Length
from flow360_schema.models.asset_cache import AssetCache
from pydantic import ValidationError

from flow360.component.simulation import services
Expand All @@ -17,7 +18,6 @@
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.framework.entity_base import EntityList
from flow360.component.simulation.framework.entity_registry import EntityRegistry
from flow360.component.simulation.framework.param_utils import AssetCache
from flow360.component.simulation.outputs.outputs import (
SurfaceIntegralOutput,
SurfaceOutput,
Expand Down
8 changes: 3 additions & 5 deletions flow360/component/simulation/draft_context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Union, get_args

from flow360_schema.models.asset_cache import CoordinateSystemStatus, MirrorStatus

from flow360.component.simulation.draft_context.coordinate_system_manager import (
CoordinateSystemManager,
CoordinateSystemStatus,
)
from flow360.component.simulation.draft_context.mirror import (
MirrorManager,
MirrorStatus,
)
from flow360.component.simulation.draft_context.mirror import MirrorManager
from flow360.component.simulation.entity_info import (
DraftEntityTypes,
EntityInfoModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,101 +3,27 @@
from __future__ import annotations

import collections
from typing import Dict, List, Literal, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union

import numpy as np
import pydantic as pd
from flow360_schema.models.asset_cache import (
CoordinateSystemAssignmentGroup,
CoordinateSystemEntityRef,
CoordinateSystemParent,
CoordinateSystemStatus,
)

from flow360.component.simulation.entity_operation import (
CoordinateSystem,
_compose_transformation_matrices,
)
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.framework.entity_base import EntityBase
from flow360.component.simulation.framework.entity_registry import EntityRegistry
from flow360.component.simulation.utils import is_exact_instance
from flow360.exceptions import Flow360RuntimeError
from flow360.log import log


class CoordinateSystemParent(Flow360BaseModel):
"""
Parent relationship for a coordinate system.

This is a lightweight, serializable representation of a coordinate system hierarchy edge
used by `CoordinateSystemStatus`.
"""

type_name: Literal["CoordinateSystemParent"] = pd.Field("CoordinateSystemParent", frozen=True)
coordinate_system_id: str
parent_id: Optional[str] = pd.Field(None)


class CoordinateSystemEntityRef(Flow360BaseModel):
"""
Entity reference used in assignment serialization.

Notes
-----
This stores an `(entity_type, entity_id)` pair instead of a direct entity instance so that
the status can be serialized and later restored against a draft's entity registry.
"""

type_name: Literal["CoordinateSystemEntityRef"] = pd.Field(
"CoordinateSystemEntityRef", frozen=True
)
entity_type: str
entity_id: str


class CoordinateSystemAssignmentGroup(Flow360BaseModel):
"""
Grouped entity assignments for a coordinate system.

A single coordinate system can be assigned to multiple entities. This model groups the
entity references to keep the status payload compact and easy to validate.
"""

type_name: Literal["CoordinateSystemAssignmentGroup"] = pd.Field(
"CoordinateSystemAssignmentGroup", frozen=True
)
coordinate_system_id: str
entities: List[CoordinateSystemEntityRef]


class CoordinateSystemStatus(Flow360BaseModel):
"""
Serializable snapshot for front end/asset cache.

This status is stored in an asset's private cache and restored into a `DraftContext` so
that coordinate system definitions and assignments can persist across sessions.
"""

type_name: Literal["CoordinateSystemStatus"] = pd.Field("CoordinateSystemStatus", frozen=True)
coordinate_systems: List[CoordinateSystem]
parents: List[CoordinateSystemParent]
assignments: List[CoordinateSystemAssignmentGroup]

@pd.model_validator(mode="after")
def _validate_unique_coordinate_system_ids_and_names(self):
"""Validate that all coordinate system IDs and names are unique."""
seen_ids = set()
seen_names = set()
for cs in self.coordinate_systems:
# Check IDs first to match the order of validation in _from_status
if cs.private_attribute_id in seen_ids:
raise ValueError(
f"[Internal] Duplicate coordinate system id '{cs.private_attribute_id}' in status."
)
if cs.name in seen_names:
raise ValueError(
f"[Internal] Duplicate coordinate system name '{cs.name}' in status."
)
seen_ids.add(cs.private_attribute_id)
seen_names.add(cs.name)
return self


class CoordinateSystemManager:
"""
Manage coordinate systems, hierarchy, and entity assignments inside a `DraftContext`.
Expand Down
120 changes: 3 additions & 117 deletions flow360/component/simulation/draft_context/mirror.py
Original file line number Diff line number Diff line change
@@ -1,138 +1,24 @@
"""Mirror plane, mirrored entities and helpers."""

from typing import Dict, List, Literal, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union

import numpy as np
import pydantic as pd
from flow360_schema.framework.physical_dimensions import Length
from flow360_schema.models.asset_cache import MirrorStatus
from flow360_schema.models.entities import MirrorPlane

from flow360.component.simulation.entity_operation import (
_transform_direction,
_transform_point,
)
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.framework.entity_base import EntityBase
from flow360.component.simulation.framework.entity_registry import (
EntityRegistry,
EntityRegistryView,
)
from flow360.component.simulation.framework.entity_utils import generate_uuid
from flow360.component.simulation.primitives import (
GeometryBodyGroup,
MirroredGeometryBodyGroup,
MirroredSurface,
Surface,
)
from flow360.component.simulation.utils import is_exact_instance
from flow360.component.types import Axis
from flow360.exceptions import Flow360RuntimeError
from flow360.log import log


class MirrorPlane(EntityBase):
"""
Define a mirror plane used by `MirrorManager` to create mirrored draft entities.

A `MirrorPlane` is a draft entity representing an infinite plane defined by a center point
and a normal direction. Mirror operations use this plane to derive mirrored entities.

Parameters
----------
name : str
Mirror plane name. Must be unique within the draft.
normal : Axis
Normal direction of the mirror plane.
center : LengthType.Point
Center point of the mirror plane.

Example
-------

>>> import flow360 as fl
>>> plane = fl.MirrorPlane(
... name="MirrorPlane",
... normal=(0, 1, 0),
... center=(0, 0, 0) * fl.u.m,
... )
"""

name: str = pd.Field()
normal: Axis = pd.Field(description="Normal direction of the plane.")
# pylint: disable=no-member
center: Length.Vector3 = pd.Field(description="Center point of the plane.")

private_attribute_entity_type_name: Literal["MirrorPlane"] = pd.Field(
"MirrorPlane", frozen=True
)
private_attribute_id: str = pd.Field(default_factory=generate_uuid, frozen=True)

def _apply_transformation(self, matrix: np.ndarray) -> "MirrorPlane":
"""Apply 3x4 transformation matrix, returning new transformed instance."""
# Transform the center point
center_array = np.asarray(self.center.value)
new_center_array = _transform_point(center_array, matrix)
new_center = type(self.center)(new_center_array, self.center.units)

# Transform and normalize the normal direction
normal_array = np.asarray(self.normal)
transformed_normal = _transform_direction(normal_array, matrix)
new_normal = tuple(transformed_normal / np.linalg.norm(transformed_normal))

return self.model_copy(update={"center": new_center, "normal": new_normal})


# region -----------------------------Internal Model Below-------------------------------------
class MirrorStatus(Flow360BaseModel):
"""
Serializable snapshot of mirror state stored in the asset cache.

Notes
-----
This status stores both:
- User-authored inputs: `mirror_planes`
- Derived draft-only entities: `mirrored_geometry_body_groups` and `mirrored_surfaces`

The derived entities are generated from mirror actions and are registered into the draft's
entity registry when a draft is created/restored.
"""

# Note: We can do similar thing as entityList to support mirroring with EntitySelectors.
mirror_planes: List[MirrorPlane] = pd.Field(description="List of mirror planes to mirror.")
mirrored_geometry_body_groups: List[MirroredGeometryBodyGroup] = pd.Field(
description="List of mirrored geometry body groups."
)
mirrored_surfaces: List[MirroredSurface] = pd.Field(description="List of mirrored surfaces.")

@pd.model_validator(mode="after")
def _validate_unique_mirror_plane_names(self):
"""Validate that all mirror plane names are unique."""
seen_names = set()
for plane in self.mirror_planes:
if plane.name in seen_names:
raise ValueError(
f"Duplicate mirror plane name '{plane.name}' found in mirror status."
)
seen_names.add(plane.name)
return self

def is_empty(self) -> bool:
"""
Return True if no mirror planes or mirrored entities exist in this status.

Returns
-------
bool
True when no mirroring is configured.
"""
return (
not self.mirror_planes
and not self.mirrored_geometry_body_groups
and not self.mirrored_surfaces
)


# endregion -------------------------------------------------------------------------------------

MIRROR_SUFFIX = "_<mirror>"

# region -----------------------------Internal Functions Below-------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion flow360/component/simulation/entity_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# pylint: disable=unused-import
from flow360_schema.framework.entity.entity_operation import ( # noqa: F401
CoordinateSystem,
Transformation,
_build_transformation_matrix,
_compose_transformation_matrices,
_extract_rotation_matrix,
Expand All @@ -16,3 +15,6 @@
_validate_uniform_scale_and_transform_center,
rotation_matrix_from_axis_and_angle,
)
from flow360_schema.framework.entity.legacy_transformation import ( # noqa: F401
Transformation,
)
2 changes: 1 addition & 1 deletion flow360/component/simulation/framework/boundary_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def update_entities_in_model( # pylint: disable=too-many-branches
- Lists/tuples containing entities or models
"""
# pylint: disable=import-outside-toplevel
from flow360.component.simulation.framework.param_utils import AssetCache
from flow360_schema.models.asset_cache import AssetCache

for field in model.__dict__.values():
if isinstance(field, AssetCache):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
)
from flow360_schema.framework.entity.entity_utils import DEFAULT_NOT_MERGED_TYPES
from flow360_schema.framework.validation.context import DeserializationContext
from flow360_schema.models.entities import MirrorPlane

from flow360.component.simulation.draft_context.mirror import MirrorPlane
from flow360.component.simulation.outputs.output_entities import (
Point,
PointArray,
Expand Down
Loading
Loading