-
Notifications
You must be signed in to change notification settings - Fork 9
Two level structure for geometry referencing snappy #1465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: snappy-main
Are you sure you want to change the base?
Changes from all commits
680d946
9958816
100a35c
49d5f7d
ea7bf0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -236,6 +236,11 @@ class Geometry(AssetBase): | |
_entity_info_class = GeometryEntityInfo | ||
_cloud_resource_type_name = "Geometry" | ||
|
||
# pylint: disable=redefined-builtin | ||
def __init__(self, id: Union[str, None]): | ||
super().__init__(id) | ||
self.snappy_body_registry = None | ||
|
||
@property | ||
def face_group_tag(self): | ||
"getter for face_group_tag" | ||
|
@@ -263,6 +268,16 @@ def body_group_tag(self): | |
def body_group_tag(self, new_value: str): | ||
raise SyntaxError("Cannot set body_group_tag, use group_bodies_by_tag() instead.") | ||
|
||
@property | ||
def snappy_bodies(self): | ||
"""Getter for the snappy registry.""" | ||
if hasattr(self, "snappy_body_registry") is False or self.snappy_body_registry is None: | ||
raise Flow360ValueError( | ||
"The faces in geometry are not grouped for snappy." | ||
"Please use `group_faces_for_snappy` function to group them first." | ||
) | ||
return self.snappy_body_registry | ||
|
||
def get_dynamic_default_settings(self, simulation_dict: dict): | ||
"""Get the default geometry settings from the simulation dict""" | ||
|
||
|
@@ -412,10 +427,25 @@ def group_bodies_by_tag(self, tag_name: str) -> None: | |
"body", tag_name, self.internal_registry | ||
) | ||
|
||
def group_faces_for_snappy(self) -> None: | ||
""" | ||
Group faces according to body::region convention for snappyHexMesh. | ||
""" | ||
# pylint: disable=protected-access,no-member | ||
self.internal_registry = self._entity_info._group_entity_by_tag( | ||
"face", "faceId", self.internal_registry | ||
) | ||
# pylint: disable=protected-access | ||
self.snappy_body_registry = self._entity_info._group_faces_by_snappy_format() | ||
|
||
def reset_face_grouping(self) -> None: | ||
"""Reset the face grouping""" | ||
# pylint: disable=protected-access,no-member | ||
self.internal_registry = self._entity_info._reset_grouping("face", self.internal_registry) | ||
if hasattr(self, "snappy_body_registry") is True and self.snappy_body_registry: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here? |
||
self.snappy_body_registry = self.snappy_body._reset_grouping( | ||
"face", self.snappy_body_registry | ||
) | ||
|
||
def reset_edge_grouping(self) -> None: | ||
"""Reset the edge grouping""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,10 @@ | |
import pydantic as pd | ||
|
||
from flow360.component.simulation.framework.base_model import Flow360BaseModel | ||
from flow360.component.simulation.framework.entity_registry import EntityRegistry | ||
from flow360.component.simulation.framework.entity_registry import ( | ||
EntityRegistry, | ||
SnappyBodyRegistry, | ||
) | ||
from flow360.component.simulation.outputs.output_entities import ( | ||
Point, | ||
PointArray, | ||
|
@@ -23,6 +26,7 @@ | |
GeometryBodyGroup, | ||
GhostCircularPlane, | ||
GhostSphere, | ||
SnappyBody, | ||
Surface, | ||
) | ||
from flow360.component.simulation.unit_system import LengthType | ||
|
@@ -40,6 +44,8 @@ | |
pd.Field(discriminator="private_attribute_entity_type_name"), | ||
] | ||
|
||
GROUPED_SNAPPY = "Grouped with snappy name formatting." | ||
|
||
|
||
class EntityInfoModel(Flow360BaseModel, metaclass=ABCMeta): | ||
"""Base model for asset entity info JSON""" | ||
|
@@ -142,7 +148,7 @@ class GeometryEntityInfo(EntityInfoModel): | |
|
||
def group_in_registry( | ||
self, | ||
entity_type_name: Literal["face", "edge", "body"], | ||
entity_type_name: Literal["face", "edge", "body", "snappy_body"], | ||
attribute_name: str, | ||
registry: EntityRegistry, | ||
) -> EntityRegistry: | ||
|
@@ -155,15 +161,31 @@ def group_in_registry( | |
known_frozen_hashes = registry.fast_register(item, known_frozen_hashes) | ||
return registry | ||
|
||
def _get_snappy_bodies(self) -> List[SnappyBody]: | ||
|
||
snappy_body_mapping = {} | ||
for patch in self.grouped_faces[self.face_attribute_names.index("faceId")]: | ||
name_components = patch.name.split("::") | ||
body_name = name_components[0] | ||
if body_name not in snappy_body_mapping: | ||
snappy_body_mapping[body_name] = [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use default dict for initialization. |
||
if patch not in snappy_body_mapping[body_name]: | ||
snappy_body_mapping[body_name].append(patch) | ||
|
||
return [ | ||
SnappyBody(name=snappy_body, surfaces=body_entities) | ||
for snappy_body, body_entities in snappy_body_mapping.items() | ||
] | ||
|
||
def _get_list_of_entities( | ||
self, | ||
attribute_name: Union[str, None] = None, | ||
entity_type_name: Literal["face", "edge", "body"] = None, | ||
) -> Union[List[Surface], List[Edge], List[GeometryBodyGroup]]: | ||
entity_type_name: Literal["face", "edge", "body", "snappy_body"] = None, | ||
) -> Union[List[Surface], List[Edge], List[GeometryBodyGroup], List[SnappyBody]]: | ||
# Validations | ||
if entity_type_name is None: | ||
raise ValueError("Entity type name is required.") | ||
if entity_type_name not in ["face", "edge", "body"]: | ||
if entity_type_name not in ["face", "edge", "body", "snappy_body"]: | ||
raise ValueError( | ||
f"Invalid entity type name, expected 'body, 'face' or 'edge' but got {entity_type_name}." | ||
) | ||
|
@@ -175,10 +197,12 @@ def _get_list_of_entities( | |
entity_attribute_names = self.edge_attribute_names | ||
entity_full_list = self.grouped_edges | ||
specified_attribute_name = self.edge_group_tag | ||
else: | ||
elif entity_type_name == "body": | ||
entity_attribute_names = self.body_attribute_names | ||
entity_full_list = self.grouped_bodies | ||
specified_attribute_name = self.body_group_tag | ||
else: | ||
return self._get_snappy_bodies() | ||
|
||
# Use the supplied one if not None | ||
if attribute_name is not None: | ||
|
@@ -188,6 +212,8 @@ def _get_list_of_entities( | |
if specified_attribute_name in entity_attribute_names: | ||
# pylint: disable=no-member, unsubscriptable-object | ||
return entity_full_list[entity_attribute_names.index(specified_attribute_name)] | ||
if specified_attribute_name == GROUPED_SNAPPY: | ||
return self._get_snappy_bodies() | ||
|
||
raise ValueError( | ||
f"The given attribute_name `{attribute_name}` is not found" | ||
|
@@ -199,6 +225,11 @@ def get_boundaries(self, attribute_name: str = None) -> list[Surface]: | |
Get the full list of boundaries. | ||
If attribute_name is supplied then ignore stored face_group_tag and use supplied one. | ||
""" | ||
if self.face_group_tag == GROUPED_SNAPPY and attribute_name is None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to add this if the faces are already grouped with faceId? Wouldn't the previous method give correct list of boundaries? |
||
all_boundaries = [] | ||
for body in self._get_list_of_entities(attribute_name, "snappy_body"): | ||
all_boundaries += body.surfaces | ||
return all_boundaries | ||
return self._get_list_of_entities(attribute_name, "face") | ||
|
||
def update_persistent_entities(self, *, asset_entity_registry: EntityRegistry) -> None: | ||
|
@@ -348,11 +379,36 @@ def _group_entity_by_tag( | |
|
||
return registry | ||
|
||
def _group_faces_by_snappy_format(self): | ||
registry = SnappyBodyRegistry() | ||
|
||
existing_face_tag = None | ||
if self.face_group_tag is not None: | ||
existing_face_tag = self.face_group_tag | ||
|
||
if existing_face_tag: | ||
if existing_face_tag != GROUPED_SNAPPY: | ||
log.info( | ||
f"Regrouping face entities using snappy name formatting (previous `{GROUPED_SNAPPY}`)." | ||
) | ||
registry = self._reset_grouping(entity_type_name="face", registry=registry) | ||
|
||
registry = self.group_in_registry( | ||
"snappy_body", attribute_name=GROUPED_SNAPPY, registry=registry | ||
) | ||
|
||
with model_attribute_unlock(self, "face_group_tag"): | ||
self.face_group_tag = GROUPED_SNAPPY | ||
|
||
return registry | ||
|
||
@pd.validate_call | ||
def _reset_grouping( | ||
self, entity_type_name: Literal["face", "edge", "body"], registry: EntityRegistry | ||
) -> EntityRegistry: | ||
if entity_type_name == "face": | ||
registry.clear(Surface) | ||
registry.clear(SnappyBody) | ||
with model_attribute_unlock(self, "face_group_tag"): | ||
self.face_group_tag = None | ||
elif entity_type_name == "edge": | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,37 @@ | |
from flow360.component.simulation.framework.base_model import Flow360BaseModel | ||
from flow360.component.simulation.framework.entity_base import EntityBase | ||
from flow360.component.utils import _naming_pattern_handler | ||
from flow360.exceptions import Flow360ValueError | ||
|
||
|
||
class DoubleIndexableList(list): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The addition of this DoubleIndexableList seems redundant? See my other comment. |
||
""" | ||
An extension of a list that allows accessing elements inside it through a string key. | ||
""" | ||
|
||
def __getitem__(self, key: Union[str, slice, int]): | ||
if isinstance(key, str): | ||
returned_items = [] | ||
for item in self: | ||
try: | ||
item_ret_value = item[key] | ||
except KeyError: | ||
item_ret_value = [] | ||
except Exception as e: | ||
raise ValueError( | ||
f"Trying to access something in {item} through string indexing, which is not allowed." | ||
) from e | ||
if isinstance(item_ret_value, list): | ||
returned_items += item_ret_value | ||
else: | ||
returned_items.append(item_ret_value) | ||
if not returned_items: | ||
raise ValueError( | ||
"No entity found in registry for parent entities: " | ||
+ f"{', '.join([f'{entity.name}' for entity in self])} with given name/naming pattern: '{key}'." | ||
) | ||
return returned_items | ||
return super().__getitem__(key) | ||
|
||
|
||
class EntityRegistryBucket: | ||
|
@@ -227,3 +258,48 @@ def find_by_asset_id(self, *, entity_id: str, entity_class: type[EntityBase]): | |
def is_empty(self): | ||
"""Return True if the registry is empty, False otherwise.""" | ||
return not self.internal_registry | ||
|
||
|
||
class SnappyBodyRegistry(EntityRegistry): | ||
""" | ||
Extension of Entityregistry to be used with SnappyBody, allows double indexing. | ||
""" | ||
|
||
def find_by_naming_pattern( | ||
self, pattern: str, enforce_output_as_list: bool = True, error_when_no_match: bool = False | ||
) -> list[EntityBase]: | ||
""" | ||
Finds all registered entities whose names match a given pattern. | ||
|
||
Parameters: | ||
pattern (str): A naming pattern, which can include '*' as a wildcard. | ||
|
||
Returns: | ||
List[EntityBase]: A list of entities whose names match the pattern. | ||
""" | ||
matched_entities = DoubleIndexableList() | ||
regex = _naming_pattern_handler(pattern=pattern) | ||
# pylint: disable=no-member | ||
for entity_list in self.internal_registry.values(): | ||
matched_entities.extend(filter(lambda x: regex.match(x.name), entity_list)) | ||
|
||
if not matched_entities and error_when_no_match is True: | ||
raise ValueError( | ||
f"No entity found in registry with given name/naming pattern: '{pattern}'." | ||
) | ||
if enforce_output_as_list is False and len(matched_entities) == 1: | ||
return matched_entities[0] | ||
|
||
return matched_entities | ||
|
||
def __getitem__(self, key): | ||
""" | ||
Get the entity by name. | ||
`key` is the name of the entity or the naming pattern if wildcard is used. | ||
""" | ||
if isinstance(key, str) is False: | ||
raise Flow360ValueError(f"Entity naming pattern: {key} is not a string.") | ||
|
||
return self.find_by_naming_pattern( | ||
key, enforce_output_as_list=False, error_when_no_match=True | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ | |
get_validation_info, | ||
) | ||
from flow360.component.types import Axis | ||
from flow360.component.utils import _naming_pattern_handler | ||
|
||
BOUNDARY_FULL_NAME_WHEN_NOT_FOUND = "This boundary does not exist!!!" | ||
|
||
|
@@ -716,14 +717,33 @@ def __str__(self): | |
return ",".join(sorted([self.pair[0].name, self.pair[1].name])) | ||
|
||
|
||
class SnappyBody(Flow360BaseModel): | ||
class SnappyBody(EntityBase): | ||
""" | ||
Represents a group of faces forming a body for snappyHexMesh. | ||
Bodies and their regions are defined in the ASCII STL file by using the solid -> endsolid" | ||
keywords with a body::region naming scheme. | ||
""" | ||
|
||
body_name: str = pd.Field() | ||
private_attribute_registry_bucket_name: Literal["SurfaceGroupedEntityType"] = pd.Field( | ||
"SurfaceGroupedEntityType", frozen=True | ||
) | ||
private_attribute_entity_type_name: Literal["SnappyBody"] = pd.Field("SnappyBody", frozen=True) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please populate private_attribute_id too here. |
||
surfaces: List[Surface] = pd.Field() | ||
|
||
def __getitem__(self, key: str): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we still need to get my_geo["tunne*::patch*"] Why do user want to instead do: my_geo.snappy_body["tunne*"]["patch*"] which is longer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had a discussion with @piotrkluba and this is just a syntax candy for snappy user. |
||
if len(self.surfaces) == 1 and ("::" not in self.surfaces[0].name): | ||
regex = _naming_pattern_handler(pattern=key) | ||
else: | ||
regex = _naming_pattern_handler(pattern=f"{self.name}::{key}") | ||
|
||
matched_surfaces = [entity for entity in self.surfaces if regex.match(entity.name)] | ||
if not matched_surfaces: | ||
print(key) | ||
raise KeyError( | ||
f"No entity found in registry for parent entity: {self.name} with given name/naming pattern: '{key}'." | ||
) | ||
return matched_surfaces | ||
|
||
|
||
@final | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you changed the constructor to always populate self.snappy_body_registry as None, the first statement will overshadowed?