diff --git a/flow360/component/simulation/design_intent.py b/flow360/component/simulation/design_intent.py new file mode 100644 index 000000000..5d3da290b --- /dev/null +++ b/flow360/component/simulation/design_intent.py @@ -0,0 +1,82 @@ +"""Data models for CAD design intent (feature tree) metadata.""" + +from typing import Dict, List, Optional + +import pydantic as pd + +from flow360.component.simulation.framework.base_model import Flow360BaseModel + + +class CADFeature(Flow360BaseModel): + """A single CAD feature (e.g., fillet, chamfer, extrusion) from the CAD feature tree.""" + + id: str = pd.Field(description="Deterministic UUID identifying this feature.") + name: str = pd.Field(description="Feature name as reported by the CAD kernel.") + cad_type: str = pd.Field(description="CAD feature type string (e.g., 'Fillet', 'Extrude').") + parent_id: str = pd.Field( + "", description="ID of the parent feature, or empty string for root features." + ) + produced_entity_ids: List[str] = pd.Field( + [], + description="IDs of geometry entities (faces/edges) produced as the primary output of this feature.", + ) + reference_entity_ids: List[str] = pd.Field( + [], + description="IDs of geometry entities used as construction or positioning references.", + ) + support_entity_ids: List[str] = pd.Field( + [], + description="IDs of geometry entities used as support (e.g., faces selected for a fillet).", + ) + properties: Dict[str, str] = pd.Field( + {}, + description="Key-value pairs of feature parameters (e.g., radius, depth).", + ) + child_feature_ids: List[str] = pd.Field( + [], + description="IDs of child features in the feature tree.", + ) + + def get_all_entity_ids(self) -> List[str]: + """Return the union of all entity IDs referenced by this feature.""" + return list( + dict.fromkeys( + self.produced_entity_ids + self.reference_entity_ids + self.support_entity_ids + ) + ) + + +class CADDesignIntent(Flow360BaseModel): + """Top-level container for CAD design intent extracted from the feature tree.""" + + version: str = pd.Field("0.1.0", frozen=True) + features: List[CADFeature] = pd.Field( + [], description="Flat list of all CAD features extracted from the model." + ) + + def get_feature_by_id(self, feature_id: str) -> Optional[CADFeature]: + """Return the feature with the given ID, or None if not found.""" + for feature in self.features: + if feature.id == feature_id: + return feature + return None + + def get_features_by_type(self, cad_type: str) -> List[CADFeature]: + """Return all features whose cad_type matches (case-insensitive).""" + lower = cad_type.lower() + return [f for f in self.features if f.cad_type.lower() == lower] + + def get_producing_features(self, entity_id: str) -> List[CADFeature]: + """Return all features that produced the given entity ID.""" + return [f for f in self.features if entity_id in f.produced_entity_ids] + + def get_sibling_features(self, feature_id: str) -> List[CADFeature]: + """Return features that share the same parent as the given feature (excluding itself).""" + feature = self.get_feature_by_id(feature_id) + if feature is None: + return [] + return [ + f + for f in self.features + if f.parent_id == feature.parent_id and f.id != feature_id + ] diff --git a/flow360/component/simulation/entity_info.py b/flow360/component/simulation/entity_info.py index d90345cc0..39b073aa2 100644 --- a/flow360/component/simulation/entity_info.py +++ b/flow360/component/simulation/entity_info.py @@ -6,6 +6,7 @@ import pydantic as pd +from flow360.component.simulation.design_intent import CADDesignIntent from flow360.component.simulation.framework.base_model import Flow360BaseModel from flow360.component.simulation.framework.entity_registry import ( EntityRegistry, @@ -178,6 +179,12 @@ class GeometryEntityInfo(EntityInfoModel): description="The default value based on uploaded geometry for geometry_accuracy.", ) + design_intent: Optional[CADDesignIntent] = pd.Field( + None, + description="CAD design intent (feature tree) extracted from a native CAD file. " + "Only populated when the geometry was imported from a format that exposes the feature tree.", + ) + @property def all_face_ids(self) -> list[str]: """