diff --git a/flow360/component/geometry.py b/flow360/component/geometry.py index b692f93fe..22fdfb3d7 100644 --- a/flow360/component/geometry.py +++ b/flow360/component/geometry.py @@ -4,6 +4,7 @@ from __future__ import annotations +import json import os import threading from enum import Enum @@ -19,6 +20,14 @@ ) from flow360.cloud.heartbeat import post_upload_heartbeat from flow360.cloud.rest_api import RestApi + +# Re-exports for face grouping API +from flow360.component.geometry_tree import ( + GeometryTreeNode, + GeometryTreeNodeSet, + TreeBackend, +) +from flow360.component.geometry_tree.face_group import FaceGroup from flow360.component.interfaces import GeometryInterface from flow360.component.resource_base import ( AssetMetaBaseModelV2, @@ -374,7 +383,7 @@ def submit( return Geometry.from_cloud(info.id) -class Geometry(AssetBase): +class Geometry(AssetBase): # pylint: disable=too-many-public-methods """ Geometry component for workbench (simulation V2) """ @@ -387,9 +396,150 @@ class Geometry(AssetBase): # pylint: disable=redefined-builtin def __init__(self, id: Union[str, None]): + self._tree = None # TreeBackend for tree navigation and face grouping + self._face_groups = {} # name -> FaceGroup super().__init__(id) self.snappy_body_registry = None + @classmethod + def from_local_tree( + cls, tree_json_path: str = "geometryHierarchicalMetadata.json" + ) -> "Geometry": + """ + Create a Geometry from a local hierarchical metadata JSON file. + + Loads the tree directly from a local JSON file, without requiring + cloud upload. + + Parameters + ---------- + tree_json_path : str + Path to the hierarchical metadata JSON file. + + Returns + ------- + Geometry + Geometry with tree loaded (supports faces(), create_face_group(), etc.) + """ + geo = cls(id=None) + geo.snappy_body_registry = None + with open(tree_json_path, "r", encoding="utf-8") as f: + tree_data = json.load(f) + geo._tree = TreeBackend() + geo._tree.load_from_json(tree_data) + log.info(f"Geometry loaded from local tree: {len(geo.faces())} faces") + return geo + + # ================================================================ + # Tree Navigation Methods + # ================================================================ + + def root_node(self) -> GeometryTreeNode: + """Get the root node of the geometry tree.""" + if self._tree is None: + raise Flow360ValueError( + "Geometry tree not loaded. Use Geometry(file_path) to load from file." + ) + root_id = self._tree.get_root() + return GeometryTreeNode(self, self._tree, root_id) + + def children(self, **filters) -> GeometryTreeNodeSet: + """Get direct children of the root node.""" + return self.root_node().children(**filters) + + def descendants(self, **filters) -> GeometryTreeNodeSet: + """Get all descendants of the root.""" + return self.root_node().descendants(**filters) + + def faces(self, **filters) -> GeometryTreeNodeSet: + """Get all face nodes in the geometry.""" + return self.root_node().faces(**filters) + + # ================================================================ + # Face Group Management + # ================================================================ + + def create_face_group(self, name: str, selection: GeometryTreeNodeSet) -> FaceGroup: + """ + Create a named face group from a selection. + + Each face can only belong to one group. Faces in the selection + are removed from any previous group they belonged to. + """ + if name in self._face_groups: + raise ValueError(f"Group '{name}' already exists") + + # Extract face node IDs from the selection + face_nodes = selection.faces() + face_node_ids = face_nodes._node_ids # pylint: disable=protected-access + + # Remove these faces from any existing groups (exclusive ownership) + for group in self._face_groups.values(): + group._node_ids -= face_node_ids + + group = FaceGroup(name, face_node_ids) + self._face_groups[name] = group + return group + + def get_face_group(self, name: str) -> FaceGroup: + """Get a face group by name.""" + if name not in self._face_groups: + raise KeyError(f"Group '{name}' not found") + return self._face_groups[name] + + def list_groups(self): + """List all group names.""" + return list(self._face_groups.keys()) + + def clear_groups(self) -> None: + """Remove all face groups.""" + self._face_groups.clear() + + def _build_face_grouping_config(self) -> dict: + """Build versioned face grouping config. + + Returns a dict with structure: + {"version": "1.0", "face_group_mapping": {uuid: group_name, ...}} + """ + face_group_mapping = {} + for group_name, group in self._face_groups.items(): + for node_id in group._node_ids: # pylint: disable=protected-access + face_group_mapping[node_id] = group_name + return {"version": "1.0", "face_group_mapping": face_group_mapping} + + def export_face_grouping_config(self, output_path: str) -> None: + """ + Save face groups to a versioned local JSON file. + + Parameters + ---------- + output_path : str + Path to write the face grouping JSON file. + """ + face_grouping_config = self._build_face_grouping_config() + with open(output_path, "w", encoding="utf-8") as fh: + json.dump(face_grouping_config, fh, indent=4) + mapping = face_grouping_config["face_group_mapping"] + log.info(f"Saved {len(mapping)} face group entries to {output_path}") + + # ================================================================ + # Set Operations + # ================================================================ + + def __sub__(self, other) -> GeometryTreeNodeSet: + """Subtract faces from total geometry (geometry - FaceGroup).""" + all_faces = self.faces() + if isinstance(other, FaceGroup): + other_nodes = GeometryTreeNodeSet( + self, self._tree, other._node_ids + ) # pylint: disable=protected-access + return all_faces - other_nodes + if isinstance(other, GeometryTreeNodeSet): + raise Flow360ValueError( + "Geometry subtraction with GeometryTreeNodeSet is not supported. " + ) + return NotImplemented + @property def face_group_tag(self): "getter for face_group_tag" diff --git a/flow360/component/geometry_tree/__init__.py b/flow360/component/geometry_tree/__init__.py new file mode 100644 index 000000000..04278782b --- /dev/null +++ b/flow360/component/geometry_tree/__init__.py @@ -0,0 +1,13 @@ +""" +geometry_tree - Hierarchical geometry tree for face grouping + +Provides tree navigation and face grouping using a fluent, scope-based API. +""" + +from .face_group import FaceGroup +from .node import GeometryTreeNode +from .node_set import GeometryTreeNodeSet +from .node_type import NodeType +from .tree_backend import TreeBackend + +__all__ = ["TreeBackend", "GeometryTreeNodeSet", "GeometryTreeNode", "NodeType", "FaceGroup"] diff --git a/flow360/component/geometry_tree/face_group.py b/flow360/component/geometry_tree/face_group.py new file mode 100644 index 000000000..2ff4c4060 --- /dev/null +++ b/flow360/component/geometry_tree/face_group.py @@ -0,0 +1,24 @@ +""" +face_group.py - FaceGroup class for named face groups +""" + +from typing import Set + + +class FaceGroup: + """ + Represents a named group of faces. + + Can be used in set operations with Geometry to get remaining faces. + """ + + def __init__(self, name: str, node_ids: Set[str]): + self.name = name + self._node_ids = node_ids.copy() + + def face_count(self) -> int: + """Get number of faces in group.""" + return len(self._node_ids) + + def __repr__(self) -> str: + return f"FaceGroup('{self.name}', {self.face_count()} faces)" diff --git a/flow360/component/geometry_tree/filters.py b/flow360/component/geometry_tree/filters.py new file mode 100644 index 000000000..b5a380795 --- /dev/null +++ b/flow360/component/geometry_tree/filters.py @@ -0,0 +1,84 @@ +""" +filters.py - Filter matching logic + +Provides functions for matching node attributes against filter criteria. +Supports glob patterns (* and ?) for string matching. +""" + +import re +from typing import Any, Dict + +from .node_type import NodeType + + +def glob_to_regex(pattern: str) -> re.Pattern: + """ + Convert glob pattern to regex. + + Supports: + * - matches any number of characters + ? - matches exactly one character + """ + # Escape special regex characters except * and ? + escaped = re.escape(pattern) + # Convert glob wildcards to regex + regex_pattern = escaped.replace(r"\*", ".*").replace(r"\?", ".") + return re.compile(f"^{regex_pattern}$", re.IGNORECASE) + + +def matches_pattern(value: str, pattern: str) -> bool: + """ + Check if a string value matches a glob pattern. + """ + if value is None: + return False + + # If no wildcards, do exact case-insensitive match + if "*" not in pattern and "?" not in pattern: + return value.lower() == pattern.lower() + + regex = glob_to_regex(pattern) + return bool(regex.match(value)) + + +def matches_criteria(node_attrs: Dict[str, Any], criteria: Dict[str, Any]) -> bool: + """ + Check if node attributes match all given criteria. + + System attributes (name, type, colorRGB, etc.) are matched at top level. + Custom attributes should be passed via 'attributes' parameter: + attributes={"groupName": "wing"} + """ + for key, expected_value in criteria.items(): + # Handle custom attributes dict separately + if key == "attributes" and isinstance(expected_value, dict): + node_custom_attrs = node_attrs.get("attributes", {}) + for attr_key, attr_pattern in expected_value.items(): + actual_value = node_custom_attrs.get(attr_key) + if actual_value is None: + return False + if not matches_pattern(str(actual_value), str(attr_pattern)): + return False + continue + + # Type uses exact NodeType comparison + if key == "type": + if node_attrs.get("type") != expected_value: + return False + continue + + # Other system attributes - match via pattern + actual_value = node_attrs.get(key) + + if actual_value is None: + return False + + if not matches_pattern(str(actual_value), str(expected_value)): + return False + + return True + + +def is_face_node(node_attrs: Dict[str, Any]) -> bool: + """Check if a node is a face.""" + return node_attrs.get("type") == NodeType.FACE diff --git a/flow360/component/geometry_tree/node.py b/flow360/component/geometry_tree/node.py new file mode 100644 index 000000000..3d496b756 --- /dev/null +++ b/flow360/component/geometry_tree/node.py @@ -0,0 +1,91 @@ +""" +node.py - GeometryTreeNode class for individual tree nodes + +A GeometryTreeNode represents a single node in the geometry tree with direct attribute access. +""" + +from typing import TYPE_CHECKING, Optional + +from .node_type import NodeType + +if TYPE_CHECKING: + from .node_set import GeometryTreeNodeSet + + +class GeometryTreeNode: + """ + Represents a single node in the geometry tree. + + Provides direct attribute access and navigation from a single node. + """ + + def __init__(self, geometry, tree, node_id: str): + self._geometry = geometry + self._tree = tree + self._node_id = node_id + self._attrs = tree.get_node_attrs(node_id) + + @property + def name(self) -> str: + """Get the node name.""" + return self._attrs.get("name", "") + + @property + def type(self) -> Optional[NodeType]: + """Get the node type (e.g., NodeType.PART, NodeType.FACE).""" + return self._attrs.get("type") + + @property + def color(self) -> str: + """Get the node color (colorRGB value).""" + return self._attrs.get("colorRGB", "") + + def children(self, **filters) -> "GeometryTreeNodeSet": + """Get direct children of this node.""" + from .node_set import ( # pylint: disable=import-outside-toplevel + GeometryTreeNodeSet, + ) + + child_ids = set(self._tree.get_children(self._node_id)) + if filters: + child_ids = self._tree.filter_nodes(child_ids, **filters) + return GeometryTreeNodeSet(self._geometry, self._tree, child_ids) + + def descendants(self, **filters) -> "GeometryTreeNodeSet": + """Get all descendants of this node.""" + from .node_set import ( # pylint: disable=import-outside-toplevel + GeometryTreeNodeSet, + ) + + descendant_ids = self._tree.get_descendants(self._node_id) + if filters: + descendant_ids = self._tree.filter_nodes(descendant_ids, **filters) + return GeometryTreeNodeSet(self._geometry, self._tree, descendant_ids) + + def faces(self, **filters) -> "GeometryTreeNodeSet": + """Get all face nodes under this node.""" + from .node_set import ( # pylint: disable=import-outside-toplevel + GeometryTreeNodeSet, + ) + + node_set = GeometryTreeNodeSet(self._geometry, self._tree, {self._node_id}) + return node_set.faces(**filters) + + def is_face(self) -> bool: + """Check if this node is a face.""" + return self.type == NodeType.FACE + + def __repr__(self) -> str: + info = f"GeometryTreeNode('{self.name}', type='{self.type}'" + if self.color: + info += f", color='{self.color}'" + info += ")" + return info + + def __eq__(self, other) -> bool: + if not isinstance(other, GeometryTreeNode): + return False + return self._node_id == other._node_id + + def __hash__(self) -> int: + return hash(self._node_id) diff --git a/flow360/component/geometry_tree/node_set.py b/flow360/component/geometry_tree/node_set.py new file mode 100644 index 000000000..fd6f13cee --- /dev/null +++ b/flow360/component/geometry_tree/node_set.py @@ -0,0 +1,136 @@ +""" +node_set.py - GeometryTreeNodeSet class for tree navigation + +A GeometryTreeNodeSet represents a set of nodes at the current navigation scope. +Supports method chaining for fluent tree traversal and set operations. +""" + +from typing import Iterator, Set + +from .filters import is_face_node, matches_criteria +from .node import GeometryTreeNode + + +class GeometryTreeNodeSet: + """ + A set of nodes at the current navigation scope. + + Supports fluent navigation through method chaining and set operations. + """ + + def __init__(self, geometry, tree, node_ids: Set[str]): + self._geometry = geometry + self._tree = tree + self._node_ids = node_ids.copy() + + # ================================================================ + # Navigation Methods + # ================================================================ + + def children(self, **filters) -> "GeometryTreeNodeSet": + """Get direct children of all nodes in this set.""" + child_ids = set() + for node_id in self._node_ids: + child_ids.update(self._tree.get_children(node_id)) + if filters: + child_ids = self._tree.filter_nodes(child_ids, **filters) + return GeometryTreeNodeSet(self._geometry, self._tree, child_ids) + + def descendants(self, **filters) -> "GeometryTreeNodeSet": + """Get all descendants of all nodes in this set.""" + descendant_ids = set() + for node_id in self._node_ids: + descendant_ids.update(self._tree.get_descendants(node_id)) + if filters: + descendant_ids = self._tree.filter_nodes(descendant_ids, **filters) + return GeometryTreeNodeSet(self._geometry, self._tree, descendant_ids) + + def faces(self, **filters) -> "GeometryTreeNodeSet": + """Get all face nodes within this node scope.""" + all_nodes = set() + for node_id in self._node_ids: + all_nodes.add(node_id) + all_nodes.update(self._tree.get_descendants(node_id)) + + face_node_ids = set() + for node_id in all_nodes: + attrs = self._tree.get_node_attrs(node_id) + if is_face_node(attrs): + if filters: + if not matches_criteria(attrs, filters): + continue + face_node_ids.add(node_id) + + return GeometryTreeNodeSet(self._geometry, self._tree, face_node_ids) + + # ================================================================ + # Set Operations + # ================================================================ + + def __or__(self, other: "GeometryTreeNodeSet") -> "GeometryTreeNodeSet": + """Union of two GeometryTreeNodeSets.""" + if not isinstance(other, GeometryTreeNodeSet): + return NotImplemented + return GeometryTreeNodeSet(self._geometry, self._tree, self._node_ids | other._node_ids) + + def __and__(self, other: "GeometryTreeNodeSet") -> "GeometryTreeNodeSet": + """Intersection of two GeometryTreeNodeSets.""" + if not isinstance(other, GeometryTreeNodeSet): + return NotImplemented + return GeometryTreeNodeSet(self._geometry, self._tree, self._node_ids & other._node_ids) + + def __sub__(self, other) -> "GeometryTreeNodeSet": + """Difference: supports GeometryTreeNodeSet and FaceGroup.""" + from .face_group import FaceGroup # pylint: disable=import-outside-toplevel + + if isinstance(other, (GeometryTreeNodeSet, FaceGroup)): + return GeometryTreeNodeSet(self._geometry, self._tree, self._node_ids - other._node_ids) + return NotImplemented + + # ================================================================ + # Collection Methods + # ================================================================ + + def is_empty(self) -> bool: + """Check if GeometryTreeNodeSet is empty.""" + return len(self._node_ids) == 0 + + def __len__(self) -> int: + return len(self._node_ids) + + def __iter__(self) -> Iterator[GeometryTreeNode]: + for node_id in self._node_ids: + yield GeometryTreeNode(self._geometry, self._tree, node_id) + + def __contains__(self, item) -> bool: + if isinstance(item, GeometryTreeNode): + return item._node_id in self._node_ids # pylint: disable=protected-access + return item in self._node_ids + + def __bool__(self) -> bool: + return len(self._node_ids) > 0 + + def __eq__(self, other) -> bool: + if not isinstance(other, GeometryTreeNodeSet): + return False + return self._node_ids == other._node_ids + + __hash__ = None + + def __repr__(self) -> str: + if not self._node_ids: + return "GeometryTreeNodeSet(0 nodes)" + + lines = [f"GeometryTreeNodeSet({len(self._node_ids)} nodes):"] + for node_id in sorted(self._node_ids): + attrs = self._tree.get_node_attrs(node_id) + name = attrs.get("name", "") + node_type = attrs.get("type", "") + color = attrs.get("colorRGB", "") + + info = f" - {name} ({node_type})" + if color: + info += f" [color: {color}]" + lines.append(info) + + return "\n".join(lines) diff --git a/flow360/component/geometry_tree/node_type.py b/flow360/component/geometry_tree/node_type.py new file mode 100644 index 000000000..834475b0c --- /dev/null +++ b/flow360/component/geometry_tree/node_type.py @@ -0,0 +1,18 @@ +""" +node_type.py - Enum for geometry tree node types. +""" + +from enum import Enum + + +class NodeType(str, Enum): + """Valid node types in the geometry tree.""" + + MODEL_FILE = "ModelFile" + ASSEMBLY = "Assembly" + PART = "Part" + BODY = "Body" + BODY_COLLECTION = "BodyCollection" + SHELL_COLLECTION = "ShellCollection" + SHELL = "Shell" + FACE = "Face" diff --git a/flow360/component/geometry_tree/tree_backend.py b/flow360/component/geometry_tree/tree_backend.py new file mode 100644 index 000000000..ff01f24e1 --- /dev/null +++ b/flow360/component/geometry_tree/tree_backend.py @@ -0,0 +1,186 @@ +""" +tree_backend.py - Backend for geometry tree storage and querying. +""" + +import json +from collections import deque +from typing import Any, Dict, List, Optional, Set + +from .filters import is_face_node, matches_criteria +from .node_type import NodeType + + +class TreeBackend: + """ + Backend for storing and querying the geometry tree. + + The tree stores elements (ModelFile, Assembly, Part, Face, etc.) + with parent-child relationships and metadata (name, type, colorRGB, material, etc.) + """ + + def __init__(self): + self._nodes: Dict[str, Dict[str, Any]] = {} + self._children: Dict[str, List[str]] = {} + self._parent: Dict[str, Optional[str]] = {} + self.root_id: Optional[str] = None + + def _clear(self): + """Clear all stored data.""" + self._nodes.clear() + self._children.clear() + self._parent.clear() + self.root_id = None + + def load_from_json(self, json_data: dict) -> str: + """ + Load geometry tree from JSON dictionary. + + Args: + json_data: Versioned tree structure {"version": "...", "tree": {...}} + + Returns: + Root node ID + """ + self._clear() + self.root_id = self._add_node_recursive(json_data["tree"], parent_id=None) + return self.root_id + + def load_from_file(self, filepath: str) -> str: + """ + Load geometry tree from JSON file. + + Args: + filepath: Path to JSON file + + Returns: + Root node ID + """ + with open(filepath, "r", encoding="utf-8") as f: + json_data = json.load(f) + return self.load_from_json(json_data) + + def _add_node_recursive(self, node_data: dict, parent_id: Optional[str]) -> str: + """ + Recursively add nodes to the graph. + + Args: + node_data: Node data dictionary + parent_id: Parent node ID (None for root) + + Returns: + Node ID of the added node + """ + attributes = node_data.get("attributes", {}) + node_id = attributes.get("_Flow360UUID") + node_name = node_data.get("name", "") + node_type = node_data.get("type", "") + + if node_id is None: + raise ValueError( + f"Node '{node_name}' (type={node_type}) is missing " f"'_Flow360UUID' attribute." + ) + if node_id in self._nodes: + raise ValueError( + f"Duplicate _Flow360UUID '{node_id}' found on node " + f"'{node_name}' (type={node_type})." + ) + + try: + resolved_type = NodeType(node_type) + except ValueError as exc: + raise ValueError( + f"Node '{node_name}' has unknown type '{node_type}'. " + f"Valid types: {[t.value for t in NodeType]}" + ) from exc + + self._nodes[node_id] = { + "name": node_data.get("name", ""), + "type": resolved_type, + "colorRGB": node_data.get("colorRGB", ""), + "material": node_data.get("material", ""), + "faceCount": node_data.get("faceCount"), + "attributes": attributes, + } + self._children[node_id] = [] + self._parent[node_id] = parent_id + + if parent_id is not None: + self._children[parent_id].append(node_id) + + for child_data in node_data.get("children", []): + self._add_node_recursive(child_data, parent_id=node_id) + + return node_id + + def get_root(self) -> Optional[str]: + """Get root node ID.""" + return self.root_id + + def get_node_attrs(self, node_id: str) -> Dict[str, Any]: + """Get attributes of a node.""" + if node_id not in self._nodes: + return {} + return dict(self._nodes[node_id]) + + def get_children(self, node_id: str) -> List[str]: + """Get direct children of a node.""" + if node_id not in self._nodes: + return [] + return list(self._children[node_id]) + + def get_parent(self, node_id: str) -> Optional[str]: + """Get parent of a node.""" + if node_id not in self._nodes: + return None + return self._parent[node_id] + + def get_descendants(self, node_id: str) -> Set[str]: + """Get all descendants of a node (recursive children).""" + if node_id not in self._nodes: + return set() + result = set() + queue = deque(self._children[node_id]) + while queue: + child_id = queue.popleft() + result.add(child_id) + queue.extend(self._children[child_id]) + return result + + def filter_nodes(self, node_ids: Set[str], **criteria) -> Set[str]: + """ + Filter nodes by criteria. + + Args: + node_ids: Set of node IDs to filter + **criteria: Filter criteria (name, type, colorRGB, etc.) + + Returns: + Set of matching node IDs + """ + if not criteria: + return node_ids + + result = set() + for node_id in node_ids: + attrs = self.get_node_attrs(node_id) + if matches_criteria(attrs, criteria): + result.add(node_id) + return result + + def get_all_faces(self) -> Set[str]: + """Get all face node IDs in the entire tree.""" + if self.root_id is None: + return set() + result = set() + for node_id, attrs in self._nodes.items(): + if is_face_node(attrs): + result.add(node_id) + return result + + def get_all_nodes(self) -> Set[str]: + """Get all node IDs in the tree.""" + return set(self._nodes.keys()) + + def node_count(self) -> int: + """Get total number of nodes.""" + return len(self._nodes) diff --git a/pyproject.toml b/pyproject.toml index a7687f949..5953ce49e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ pylint = "^3.1.0" black = {extras = ["jupyter"], version = "^24.10.0"} wcmatch = "^10.0" + # docs autodoc_pydantic = {version="*", optional = true} jupyter = {version="*", optional = true} diff --git a/tests/data/geometry_tree/airplane_rc_geometry_tree.json b/tests/data/geometry_tree/airplane_rc_geometry_tree.json new file mode 100644 index 000000000..bad5f1c3f --- /dev/null +++ b/tests/data/geometry_tree/airplane_rc_geometry_tree.json @@ -0,0 +1,2063 @@ +{ + "version": "1.0", + "tree": { + "name": "Solid-Body-RC-Plane_v2024_colored", + "type": "ModelFile", + "attributes": { + "_Flow360UUID": "baaceea2-ccd8-5c37-b39c-26afeaf52f71" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "Solid-Body-RC-Plane_v2024_colored", + "type": "Assembly", + "attributes": { + "_Flow360UUID": "f5c24d62-9c7b-5e60-818d-9fc4cf13a950" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "Default", + "type": "Assembly", + "attributes": { + "Component Type": "0", + "IsCPReArchEnabled": "1", + "SW-BOM Part Number(BOM Part Number)": "Solid-Body-RC-Plane_v2024_colored", + "SW-Configuration Name(Configuration Name)": "Default", + "SW-Created Date(Created Date)": "Tuesday, February 25, 2020 10:02:56 PM", + "SW-File Name(File Name)": "Solid-Body-RC-Plane_v2024_colored", + "SW-File Title(File Title)": "Solid-Body-RC-Plane_v2024_colored", + "SW-Folder Name(Folder Name)": "C:\\Users\\feilin\\Downloads\\model-plane-7.snapshot.3\\3D models\\", + "SW-HasDesignTable": "0", + "SW-Last Saved By(Last Saved By)": "FEILIN", + "SW-Last Saved Date(Last Saved Date)": "Tuesday, November 11, 2025 6:51:16 PM", + "SW-Long Date(Long Date)": "Tuesday, November 11, 2025", + "SW-Short Date(Short Date)": "11/11/2025", + "_Flow360UUID": "d1ee4dcd-9c98-593b-9c32-625933a17355" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "", + "type": "Part", + "attributes": { + "_Flow360UUID": "f387fd80-11f3-5436-a713-751605714ef8" + }, + "colorRGB": "255,0,0", + "material": "", + "children": [ + { + "name": "", + "type": "Body", + "attributes": { + "_Flow360UUID": "c59c473d-417c-5533-9a00-117fea2bf739" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "", + "type": "ShellCollection", + "attributes": { + "_Flow360UUID": "f2a91637-c2b0-5c34-8ad8-94d174b743bc" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "", + "type": "Shell", + "attributes": { + "_Flow360UUID": "7c55208d-4444-50aa-b6e7-465a18ec346c" + }, + "colorRGB": "", + "material": "", + "children": [ + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c5cf2be5-f597-59e8-9531-18cadebf2e7e" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8e7f63fe-afd5-501d-967c-0eef96293892" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1f08465a-1508-5e4c-9cea-9a0e5c3166a1" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "0a886da4-f0c3-5493-ae1d-b678f3472c62" + }, + "colorRGB": "0,0,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b0431635-03be-5c08-ab96-b5014b829524" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "727a2460-670d-575a-872f-e8b1d53d3491" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9de070b3-ca14-53ce-b897-da1b8bb1a469" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a58758bd-0c5c-5769-a756-1c5c29edb3e8" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "2e4520e5-19fc-5b9b-9a1d-0a96b0bee36b" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "28284f03-736e-5d1c-b7e7-49e4ad39762e" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "eb6aaf23-f2b4-50d7-a9f1-7dcecf849dc2" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "36301693-22d3-5e1e-aa94-9041db8b44fe" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "3e0980c6-0bac-57e9-a61b-8f91516a88b3" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "df28f869-6c11-51d0-8781-69bfda32717d" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7fbc7468-c45d-5b4e-8854-29c8e2e3ccf9" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5ae40928-4f6c-541e-b8ac-cd288213f7c7" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "21c8ff06-6042-511b-add6-93c08800cb18" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b9ea14e6-2749-557c-8392-3585ce22a6f7" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d28da8ba-c7c0-56d4-ac6e-d53d85c498d3" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6b6953b9-9f79-5064-ad90-2c9d21e7807f" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9165391c-a73d-558e-9864-12cca8bc32df" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "533c432b-79eb-5770-83d2-8bed72c2b16d" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b98234bd-3a59-57c6-849b-5acb61e273d7" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fbaebadd-51e7-590f-b422-08de8404f327" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f816ab56-190a-58d1-8529-9af4c73aa733" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f6f88a54-20f4-5a3b-889e-d8fb19fa8258" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "2fe600d8-6150-532d-b3fb-07361f197501" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b9f78c1b-cffa-5c20-a044-63cb329bf57d" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d562f528-ca68-56a1-88a4-3574d92bc5b3" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "591d6dad-88d7-57fb-abcd-3e9d8a3bd8e1" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ebc9e340-e512-5e17-a42b-5c96da41cb44" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c95f27f1-81bd-5f06-a9fc-41a35a21fed9" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9a3a8861-9b43-5ac9-8faf-e512969bf9c4" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9331d913-b59f-5d32-a7b8-b105a2f52ddc" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "83622df1-cee7-5f69-9cf5-ad71bec3ee70" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fb1fa855-621d-59d9-b202-bf437f0e2c1b" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8379c07f-3804-5fed-8ecf-75b255776d6c" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "17408707-a4e7-5e3e-9f76-ad0d7110d104" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a6528c57-4bb7-5129-9ec8-254d845e1ad4" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "18aaf5b4-20d3-5c27-8ccf-bdd243306e44" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fd7ac117-53b5-5608-bd6c-3ec52402f0f1" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f5eb0553-d810-54e7-a6aa-668b357077ec" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7124b0a6-7b3a-55a6-9d2c-58acef316c54" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ebb059c8-7cdc-5479-8e67-39f2299d51d4" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "cd848336-aa15-5f25-afbf-b2300ac6c84d" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1c01b9a8-dd24-59bd-9e9e-a31438efe538" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8a31636b-3afd-561a-93b1-c56bdcda956d" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "46c0243c-3c0f-5b3b-b868-d4d51187358f" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1a73c815-1573-5e1e-9349-e4e0abb35a42" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "252b5f03-0711-5d73-9952-6e0724e54d1c" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7885ab6f-92be-56fc-b9db-8c99ce591b40" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "00ebe0b1-df08-5b3b-9949-f6695d7f8cfb" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d8ef0b95-b6aa-5fe3-8d39-c0254e84dc6e" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f3ec2b76-daee-5fcf-84fb-53e6e9558313" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e6aeeb25-f7b5-5175-9ab9-6f7d9bfdf8f5" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5651aedd-6212-569a-9fed-1b9bc58aadc4" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e86c28e0-5655-57db-bbec-a50754737091" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5d08cb65-c0b8-5bcf-8e9b-d312e51397b9" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "dd57092d-b29f-52f3-9d94-891a2ca60ca2" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "11b0a322-1280-5717-bff4-db56524a4d15" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "0404de40-aad4-5783-b0c3-02468cd334d0" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "124594ba-7d86-52cf-88d5-e676c48efeaf" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8077331d-0981-55fc-9cc7-7a69497c6305" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b4aa3e19-a8a6-58e8-99c0-bbe627bf0e96" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d9a0d6ed-d4f9-5b70-b56a-78e2750b3c23" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "28abd7f7-d532-52cc-b1d5-f9cdd79f3be8" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "0066324b-3d95-522d-b1b5-61c971eb8019" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "3cd4f3d8-7548-53f2-893a-19b7d2877bf3" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "656318b9-3a21-524d-b950-8054e7f4562a" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ad232c4b-95c9-58a1-9e7e-1ffdb5f94b5b" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6c9fee31-7bdd-588b-96f5-d3e39454cd3e" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5fb9c116-dc87-5139-9336-06760a95d800" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e71fda8b-1238-500e-8817-928045169b8c" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5f2a842a-9626-587e-90f0-58f67ea4ea4c" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "bd37039d-7c23-583e-8302-ae4ee0bfadbf" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8ef07a4d-da78-5721-b553-69d204a0ce1b" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f2df3800-137a-572e-aa0f-ad93c7fedbe9" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "dc6030d5-f90e-507c-834e-d3a19bc43bca" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "49789fe7-eb49-5ae0-b808-174fbbbb3bc9" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e659d1b4-7e90-5e26-a5ee-4b9e0a58c0e0" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ff27e097-ae78-51ac-bf76-1a036dbe56ee" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ab659ab9-5e24-55b5-bb2b-708dd377c00e" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5502fc91-cbdb-5c31-a89e-fa83b831b5e1" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "36e7f18b-032f-5438-8ea6-7aa4e771bd9b" + }, + "colorRGB": "0,255,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fa21fdf9-d689-5bc0-9715-f681c0aaa2cf" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e8165a34-2282-5b0a-bcce-5503727bff11" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b4a372b3-537c-58eb-8893-5c7df3771bbd" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ce87ad5b-e37d-5979-a9fe-58d169d02d51" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "edd45089-cff2-5209-8365-8337281a4aad" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e2840a84-e8c9-5c2f-a5c7-226edcc001ce" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6a473c16-5179-59a2-98d2-b584e5746c1b" + }, + "colorRGB": "0,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c6ef7894-a2e6-5257-b625-ef95936cd3ae" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e14b5db1-af55-55bd-83fa-e096561e7f64" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "2a6bf5e4-01ff-5420-9f3c-ede53b915a97" + }, + "colorRGB": "0,0,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "65428b39-4987-5cff-9a24-dd67753a8e57" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "37140e79-76ef-50c2-be5a-8a3a77408292" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7f824d0d-f265-5152-9dea-d12a30a76ab5" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e26ea3cf-3d72-54bc-bbd0-f87a83450c3d" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f964d527-ed5d-5443-9f91-ab5618426339" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "32691988-8c57-5abf-b01a-9d2c39971d27" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b7d8c83a-d3e5-523f-8295-54aff4d231f9" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4576fab4-99af-588c-95e0-23f5a44c2607" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a7e29b4f-9058-5a0d-812b-612dc93529ec" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7e760475-b89c-532a-9ff5-d3420c425c91" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1b300def-98e8-50ac-aad7-9cc09db525b3" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "97ad12d9-0f91-55ab-8c01-494e73ed066a" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d1095c7e-494c-5966-8dfd-34512233b057" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4a26e1c5-7e7c-5dd9-8382-47894d85b46e" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "85aa9e10-67c1-5a0e-90b2-381175b4ad1b" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "31502906-0c58-5f06-b5ed-4e9589b6023e" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4676f4f1-e17d-5047-a85e-53b326ca4630" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "22c3ea79-8501-5d85-b72c-f7d9d5a5b9a2" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7634ee82-a203-52eb-8e18-668648e9959d" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "0a5383ba-4f88-5d41-8901-5822982c6482" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8064d463-3209-5669-aa48-368a0ac74e04" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a7bad0a0-6a73-5499-9d29-88aba74b30f6" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4efd75be-c616-5a58-8db8-3e33779d193d" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c21d1d27-9eb6-5e79-a20a-abf9a283502b" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "23713f17-968d-5cf3-ba4b-d687dae430fc" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "215dbaa9-1323-5f05-a711-32329201950a" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6b267fbb-d345-5a88-b0ec-465191036d8f" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "614601ed-1ad9-5e60-8028-6fc9b02e8017" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "14fe4e0a-058c-50c4-8ed9-5dba44352843" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "dc5b44b2-4665-571a-b7fe-4fafa55bf652" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "70b5602d-baa0-5b22-90d0-8727ceb281ee" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "62916085-36a3-549d-be4a-905ce4c6a045" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "78111c55-e92e-586b-9d60-e401229dfcfa" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ac908e3d-59e6-548f-bffc-91f0c9d1bb83" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "884a1c07-f228-57ce-8979-98ded408df7b" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4ec2dc2d-f40c-58a9-ad76-1ca2f2056983" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "23424f8c-ef7e-5e0e-8028-6e96b1afb59d" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1240516b-2530-50ae-882f-d6c5921e257b" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d581b236-9525-51de-8722-4c6f286e1ad0" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "617c0b8a-2da3-5440-a8b5-1b7940c172b8" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "3927a43b-6813-50ae-b5e0-0b19e9fa21af" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fbd13004-1726-560a-ab57-2ced73609002" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "0ad182fe-b0b8-5a2e-b6c8-c93632e7244b" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b7c14c79-50c6-507d-849a-1682b3c3a364" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a0356ef1-9f56-5b68-a88d-e15253d526f1" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ab611826-a86a-59af-9a07-9150be9ac104" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c0390191-4c59-5fd8-9b80-781ee49605d6" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a6ff0767-1f49-5800-ae68-0acbff8db632" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "282cfd0e-ddad-53be-9867-db010b4412bc" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c2bed383-fd23-5b49-8a0b-13c6f3b1dee1" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "be49ba8f-d9c5-5e82-bc71-a00535ac21c1" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c21325f4-f566-508f-8e2e-1fcd65141dbe" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "d711c148-1129-5da0-94f6-2997f2e19ba9" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "3c28c442-161b-5a82-bf3d-57fd7a9e6af4" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6550c1d5-dadb-54ab-86ae-91a1acd7a3a8" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f673f1f0-f68e-5cf2-b96d-7bc92d68604b" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "989cfac7-a6ec-52b6-ba64-456358a15327" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1d44da17-e907-5039-b492-04a92e682776" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "edd2c56a-a5c2-591a-91f0-8871e4fcbc21" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "411febfc-4aae-5dd8-89d9-59ba263a3eed" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "f5df84ee-0ecb-50dc-b2f8-04a76970546d" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "af89f60c-7d47-5c40-ab7d-6ad1565727fd" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9f1d8dba-5e73-5979-932c-69096e0af252" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "efd5c020-4df5-5679-849c-b08b07fdd4c5" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "580d721a-aba9-5aaf-9850-19ebb2d770a7" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "2b956b01-1db6-52b4-b8a6-7e8c2dbce9ff" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9d656def-f96a-5917-82ff-d703d689999d" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9c7ce039-2af9-5e5d-93ad-adb8afe24c90" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "80343ff1-ad01-5260-a348-869946f80bd5" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "8ca3046b-4d92-5bec-be46-b036a79280aa" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6e4a925e-1d05-55ad-bb2e-0e43f99be2b6" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ac308a38-70b6-5223-a9e6-d102c5b3d6d5" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "ec7479c8-256e-508b-be3d-458b2e5ba3b2" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "6d92085d-b45a-50a5-b168-68791e591043" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "b1838825-ff45-5194-a5c8-41684d64d460" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "7ad890be-ff24-5017-b149-605bcf36e76e" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1ad3ac2e-954c-5ece-a9ac-d4a5c49f2339" + }, + "colorRGB": "255,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "56b7f7a3-df7b-5cf2-a8db-f9d6a9310bc1" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "057cccd5-0578-5e57-92ce-4e537a89ec78" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1d806bae-4df4-5305-aa43-0b69cbad832c" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "17ffc31e-7dbf-51c0-afc1-2b277425ceaf" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "a31f6754-fe91-5e03-87b3-bfbb85321d47" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "fba8fa4c-f454-5889-80be-f9b97968d7da" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "26f01662-e5d1-5208-aec9-9dbcba4d5201" + }, + "colorRGB": "0,255,0", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e923ffd6-c59f-5416-8f29-ce4ff2485f09" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5c334bd6-f514-5234-b2a4-a3c86e21076d" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "de2228cb-6838-5239-bbd3-fc31dc6721ca" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c939d342-2efc-5c09-ad6b-8e8081cf362e" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "e7f7da11-efba-55cd-be89-2b63f721c8b8" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5f7255f5-7c1c-5e2e-84f2-581cad4c9107" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9891bab2-f85f-50f9-9d07-ba2fc2fcaf34" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "14297c1c-1ff7-5ed5-acfe-324389fc3e19" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "1c22dbdb-2b86-51a1-88dd-19fd1998ae59" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "9fbc525f-5291-5aed-8e28-70b134cb7c7c" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "c56a309e-90c4-5fa1-94db-b03b581ff75e" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "91e75999-e700-5dcc-b737-bde887fb96c2" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "12d8ee11-34fe-5943-9d5f-00c4f90bb503" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "72624a02-83e6-5a42-a3ce-7750b5bc024d" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "5123725f-8a86-50c1-92f6-255f9ca4c3e3" + }, + "colorRGB": "255,0,255", + "material": "", + "children": [] + }, + { + "name": "", + "type": "Face", + "attributes": { + "_Flow360UUID": "4e23fc44-29fe-5469-bbe1-0db9cf9644c9" + }, + "colorRGB": "", + "material": "", + "children": [] + } + ] + } + ] + } + ] + }, + { + "name": "Sketch1", + "type": "BodyCollection", + "attributes": { + "_Flow360UUID": "6e855533-c1ac-5924-85c9-9a6dd5f55e74" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "Sketch2", + "type": "BodyCollection", + "attributes": { + "_Flow360UUID": "fd45105b-6e75-5709-9d21-048e49856a6d" + }, + "colorRGB": "", + "material": "", + "children": [] + }, + { + "name": "Sketch3", + "type": "BodyCollection", + "attributes": { + "_Flow360UUID": "785212e5-861a-5049-b85c-d8f5479f6aa3" + }, + "colorRGB": "", + "material": "", + "children": [] + } + ] + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/geometry_tree/drivaer_geometry_tree.json b/tests/data/geometry_tree/drivaer_geometry_tree.json new file mode 100644 index 000000000..5f98a1e20 --- /dev/null +++ b/tests/data/geometry_tree/drivaer_geometry_tree.json @@ -0,0 +1 @@ +{"version":"1.0","tree":{"name":"ANSA_Assembled_DrivAer_Input","type":"ModelFile","attributes":{"_Flow360UUID":"baaceea2-ccd8-5c37-b39c-26afeaf52f71"},"colorRGB":"","material":"","children":[{"name":"_","type":"Assembly","attributes":{"_Flow360UUID":"f5c24d62-9c7b-5e60-818d-9fc4cf13a950","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"_"},"colorRGB":"","material":"","children":[{"name":"Body","type":"Assembly","attributes":{"_Flow360UUID":"d1ee4dcd-9c98-593b-9c32-625933a17355"},"colorRGB":"","material":"","children":[{"name":"Body","type":"Assembly","attributes":{"_Flow360UUID":"f387fd80-11f3-5436-a713-751605714ef8","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Body"},"colorRGB":"","material":"","children":[{"name":"Body","type":"Part","attributes":{"_Flow360UUID":"c59c473d-417c-5533-9a00-117fea2bf739"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"56a674a7-2589-53e4-ae8f-2543c834109f"},"colorRGB":"206,2,61","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"99fd0802-e443-5f7c-b7aa-ac4df8a1718c"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"de92bb9b-7cc4-51b0-9b91-e275c636ac5b"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"72cc5290-3e49-5c9b-a2b9-f41ee0d960f7"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"c6509209-7541-5835-a658-3e559bc2011d"},"colorRGB":"254,242,217","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"c47db53d-900a-509e-a817-265a0048207a"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"f39a60d9-5355-54a6-b503-f15643436803"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"8fd02b7c-5532-51de-bab4-8d68483ce6e0"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"2fa07f4b-ffe8-5519-ae7f-10210be3c73d"},"colorRGB":"227,152,149","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"58613805-566a-54a0-967c-bdd937fca046"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"1cc0c8c1-0fef-56f7-bbe4-770febc37fc0"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"b3c9c5d9-4261-5862-af78-55cf03eeb144"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"ExhaustSystem_new","type":"Assembly","attributes":{"_Flow360UUID":"c55dd51a-f34d-5fd1-ba41-db962702a9ae"},"colorRGB":"","material":"","children":[{"name":"ExhaustSystem_new","type":"Assembly","attributes":{"_Flow360UUID":"1053d8b8-c5e1-58f2-a650-3ae16a3ffd3e","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"ExhaustSystem_new"},"colorRGB":"","material":"","children":[{"name":"ExhaustSystem_new","type":"Part","attributes":{"_Flow360UUID":"96e92fc8-4fe6-5ef4-a833-545e94a25b20"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"beba7363-1023-5fe5-b8b2-20123e998ebe"},"colorRGB":"146,215,201","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"f4fe11bb-4760-562f-ba47-e35c298361a8"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"9a5af8b9-2fd4-5e50-9878-b526451da061"},"colorRGB":"","material":"","children":[{"name":"AGA SOL","type":"Face","attributes":{"_Flow360UUID":"55cbda9c-0692-5091-a488-e505f5b75bea"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"0139a1ca-9fe2-5599-b580-b28ca2e5331f"},"colorRGB":"195,252,6","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"bdfb1c14-6a17-56b5-baab-9534997124f6"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"4841e301-8d10-5d2f-8410-e6a248a9c6ac"},"colorRGB":"","material":"","children":[{"name":"AGA SOL","type":"Face","attributes":{"_Flow360UUID":"80ebe782-bfc1-57da-bf99-6c0e27c18f35"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"c37b785e-f1b0-5e43-9508-be77706d9539"},"colorRGB":"221,114,246","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"4efbe467-2dd4-5356-9d2d-beb1c41db305"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"ec20c17d-2aab-5635-8495-f6304253e8cf"},"colorRGB":"","material":"","children":[{"name":"Fl_X2_00E4_X0_che.1","type":"Face","attributes":{"_Flow360UUID":"bcfc02dc-4d4b-5f3e-b5d2-8e4fcdbdb417"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"RearEnd_Sedan","type":"Assembly","attributes":{"_Flow360UUID":"827962f9-92be-53b9-a0d3-965092e9d793"},"colorRGB":"","material":"","children":[{"name":"RearEnd_Sedan","type":"Assembly","attributes":{"_Flow360UUID":"cfb75e65-b5e5-5043-b063-3700bf3dcc06","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"RearEnd_Sedan"},"colorRGB":"","material":"","children":[{"name":"RearEnd_Sedan","type":"Part","attributes":{"_Flow360UUID":"56b01064-ee53-53e6-a6c3-1550c92a87b6"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"229ab6a2-ea25-53f3-b68a-52432d724f68"},"colorRGB":"212,121,184","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"9216713e-0256-5d2b-b774-adb824339de2"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"23a0c3d9-da1c-5916-90ad-e607e2136df3"},"colorRGB":"","material":"","children":[{"name":"Trennen.4","type":"Face","attributes":{"_Flow360UUID":"b29a416b-66d1-5ca9-82d9-da243006808f"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"22f373b3-2c86-5074-8378-44620e30e838"},"colorRGB":"132,39,151","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"b115a742-53c1-5c57-b2d7-4262b058847d"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"c1aac814-d7f3-5d2e-851c-9f11b125b7e4"},"colorRGB":"","material":"","children":[{"name":"Trennen.4","type":"Face","attributes":{"_Flow360UUID":"e3faa63e-2cc0-5dbc-9082-eb431936bdf8"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"db80d72b-2cbd-56b7-9007-819578fb8064"},"colorRGB":"168,104,181","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"53930566-db19-51bd-b43e-65a91bff59d4"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"7ca96988-0ef4-57c1-9239-53e4bba3b69d"},"colorRGB":"","material":"","children":[{"name":"Trennen.4","type":"Face","attributes":{"_Flow360UUID":"b09c34aa-87d5-54fe-a7c6-a6db072f586b"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"Wheels_Front","type":"Assembly","attributes":{"_Flow360UUID":"b295e202-384d-5f2a-abf0-dbcf4c19a546"},"colorRGB":"","material":"","children":[{"name":"Wheels_Front","type":"Assembly","attributes":{"_Flow360UUID":"6dd19a3f-3018-5ad3-9d39-77920221bf95","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Wheels_Front"},"colorRGB":"","material":"","children":[{"name":"Wheels_Front","type":"Part","attributes":{"_Flow360UUID":"f4946d16-c2c7-5d1f-bc26-881e2a8e6d3a"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Body","attributes":{"_Flow360UUID":"2395a275-e5fe-5f3d-a0f7-0419ea81fe28"},"colorRGB":"143,213,24","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"5aa4df49-3330-57e5-9888-dbf06ce47ecc"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"a4802168-5aec-5a57-bed4-03cea0b28976"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"e7ad39f6-447c-5f5a-be6a-829529c4b2c0"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"88ebbddd-d507-5abf-90c9-7864864e54fe"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"cd1416f7-6e2d-5355-92a9-45b00427acbf"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"Verbindung.2","type":"Body","attributes":{"_Flow360UUID":"20e08048-41c3-5627-8895-56f190aea589"},"colorRGB":"151,137,81","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"1b01c27c-3215-5b4f-b96a-d0aac222fbe2"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"0cb46596-8dfd-5055-93c6-3aca2a072c00"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"33440e11-bb5e-59fc-847c-8d7b11973876"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"d349b76b-4edf-5384-8e97-68f37e5148df"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"da913141-be92-5768-8a27-7cc76e018698"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"94a1b9fa-a4d2-5b6a-a075-960c71903a70"},"colorRGB":"148,208,201","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"e25665da-3740-5a60-a574-43a10a968e9c"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"9e5ab8c5-da6f-5711-b2ba-b8b66bd8b6f1"},"colorRGB":"","material":"","children":[{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"13891421-4d37-5c4c-8344-b9bc3ccb2d11"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"16497bdb-a5d0-5728-bbc5-f9117e501242"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"3812a812-d0a6-5652-9596-01285f38efb3"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"Wheels_Rear","type":"Assembly","attributes":{"_Flow360UUID":"684fafc9-a56b-5e75-b148-c09af0e0602b"},"colorRGB":"","material":"","children":[{"name":"Wheels_Rear","type":"Assembly","attributes":{"_Flow360UUID":"6b50ddaf-bd1c-5374-a620-cc6a897655a2","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Wheels_Rear"},"colorRGB":"","material":"","children":[{"name":"Wheels_Rear","type":"Part","attributes":{"_Flow360UUID":"e2bc8bc5-ed74-5371-a853-0a2f1ced91e0"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Body","attributes":{"_Flow360UUID":"e2fa216d-993c-5371-9bfb-5c0e59969cd2"},"colorRGB":"235,93,53","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"bbaa8c8e-4025-59fc-9fdd-8eef96d990dc"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"bf751007-5e1e-5e01-831b-14b8ce29afbf"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"de995a4f-acb4-5408-8dfd-fbb9d7fefde1"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"d612cb2c-77b3-5ebd-ad28-cf8127be63c4"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"26077f81-d66c-57de-a056-419b0ca40348"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"Verbindung.2","type":"Body","attributes":{"_Flow360UUID":"3d5171af-e43f-5110-9083-6fe322f284e2"},"colorRGB":"170,210,133","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"4ccffe1a-be90-5f0f-9f6a-a9cca47d0234"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"22acdd8b-df1a-5da8-a099-5b5375496255"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"a27696ac-7702-5903-8725-dff8d3a3c9f1"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"8f90fd9b-6ae4-5026-a993-8212f4fd0bda"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"c63bc471-ade0-5f2c-944c-27c5aa3f5cfd"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"47b2123a-cc41-53f6-94c1-7cd72c3cb08f"},"colorRGB":"220,218,21","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"7ba2b278-5106-574f-ae12-07cd8ab24f22"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"0206c28c-95b8-5608-9565-5e7d791bde4d"},"colorRGB":"","material":"","children":[{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"2189dbbd-c78d-5584-9c07-b231a4061f75"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"83e5bf25-b4b2-5e38-8e38-4efd51e58a72"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.3","type":"Face","attributes":{"_Flow360UUID":"3a3c1c97-6044-53c2-8eeb-7055bc431f91"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"Mirrors","type":"Assembly","attributes":{"_Flow360UUID":"f1840070-d6bb-5b47-abe1-0567bb5f2bc4"},"colorRGB":"","material":"","children":[{"name":"Mirrors","type":"Assembly","attributes":{"_Flow360UUID":"37f588ff-3842-5965-990b-60eb049dabd0","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Mirrors"},"colorRGB":"","material":"","children":[{"name":"Mirrors","type":"Part","attributes":{"_Flow360UUID":"db13e27f-6a28-5423-b324-76da9df4e4fe"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Body","attributes":{"_Flow360UUID":"ab80d280-3179-5015-a954-661896f4b7ca"},"colorRGB":"196,50,55","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"d60638ce-1494-54cf-80d0-1066050b4ab2"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"fddb6ee3-a78a-5f70-a211-1770f6ba8f58"},"colorRGB":"","material":"","children":[{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"54ba964b-2062-568a-a41c-d09ab60f76dc"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"f2b6f577-1081-5c65-ae65-7bc842a1027b"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.1","type":"Face","attributes":{"_Flow360UUID":"ffaaaf02-a000-5e72-956a-e35c4fab59fe"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"Verbindung.2","type":"Body","attributes":{"_Flow360UUID":"31e1fedb-77c7-5775-99cd-5486f2b569e4"},"colorRGB":"185,49,217","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"cf59a07c-ffd9-5ab5-8444-e5c59dde1dcc"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"9fa009b8-cdfe-5c53-83f9-a0f02930cb85"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"2feedd9b-284c-5394-8c5d-c701b3136818"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"2d5a2b9b-5512-593c-927f-7187956db406"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"757b15ad-eb7d-5dd5-99c9-23142df7597a"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"09_engineAndGearbox","type":"Assembly","attributes":{"_Flow360UUID":"d8a39b75-1e41-5dea-b1e0-79a6ff15c51e"},"colorRGB":"","material":"","children":[{"name":"09_engineAndGearbox","type":"Assembly","attributes":{"_Flow360UUID":"f731e0b1-510d-530a-864e-164bb213abb4","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"09_engineAndGearbox"},"colorRGB":"","material":"","children":[{"name":"09_engineAndGearbox","type":"Part","attributes":{"_Flow360UUID":"0da147bf-268a-5052-8c9c-e96f24fed039"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"35cd52ba-1c4a-5212-a386-7d8c6db1b3ef"},"colorRGB":"191,149,92","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"32c5cb91-f91a-523c-8c98-2927c8ace91c"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"2e1aa952-b6c6-51d1-b9cb-74d4bf5acaa4"},"colorRGB":"","material":"","children":[{"name":"Fl_X2_00E4_X0_che.307","type":"Face","attributes":{"_Flow360UUID":"760cf8d0-49c5-5f56-a2d5-764490c18911"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"c4f54b3b-644b-5c0c-ad7b-af82eff6fd9a"},"colorRGB":"214,216,67","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"192a7991-604a-5b5c-8fd8-d1b44e32702b"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"4985358d-423b-5472-9cf8-4bf6b3cc78b2"},"colorRGB":"","material":"","children":[{"name":"Fl_X2_00E4_X0_che.308","type":"Face","attributes":{"_Flow360UUID":"154425d9-fcf8-55aa-933f-a988c43d3d85"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"632deb1f-6de3-5d01-b1ea-8b42d86a9ab4"},"colorRGB":"227,27,203","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"fea83d6b-60ce-5762-8dff-1b999a458980"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"f954be45-dab8-572e-885f-6fb7a7f036de"},"colorRGB":"","material":"","children":[{"name":"Fl_X2_00E4_X0_che.310","type":"Face","attributes":{"_Flow360UUID":"4a9637a3-7f3e-556c-ad15-e154d71aea82"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"21_Chassis","type":"Assembly","attributes":{"_Flow360UUID":"5156b4b7-a694-5ec4-9705-472499ce285c"},"colorRGB":"","material":"","children":[{"name":"21_Chassis","type":"Assembly","attributes":{"_Flow360UUID":"bd09248c-6c31-5f8f-b790-47cba8895182","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"21_Chassis"},"colorRGB":"","material":"","children":[{"name":"21_Chassis","type":"Part","attributes":{"_Flow360UUID":"eceeab23-93f2-505d-8f7d-df260e0c5746"},"colorRGB":"","material":"","children":[{"name":"Wheel Support front 1","type":"Body","attributes":{"_Flow360UUID":"0145c92c-ce74-50b8-9528-555043e3014e"},"colorRGB":"243,138,160","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"0e2b2d77-490f-5f97-a630-fc92fe5acac9"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"0a718a19-f046-5abd-a991-e17531eb5011"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"83044757-41e5-5b97-b059-7bc6323090b2"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Susspension","type":"Face","attributes":{"_Flow360UUID":"6ddea706-d964-580b-86d9-4eb53bc6618b"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Susspension","type":"Face","attributes":{"_Flow360UUID":"665f9492-30f1-5ec3-8d15-f947c09c367a"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"Wheel Support front 2","type":"Body","attributes":{"_Flow360UUID":"8ee7c783-561d-5784-95b4-76e22359fc20"},"colorRGB":"216,57,239","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"e3f5203c-ff54-5a63-aac2-8049995e751e"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"8f060791-22f3-5c67-8c19-7692e41e3c4a"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"1f03c063-c2ab-58dd-8541-d29e98441d5f"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Hubs","type":"Face","attributes":{"_Flow360UUID":"64a17a21-7b15-5be5-b537-0b48806f6f23"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Hubs","type":"Face","attributes":{"_Flow360UUID":"9d36ae29-33b0-5a2a-9f31-59e22a81f747"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"Brake Disc front","type":"Body","attributes":{"_Flow360UUID":"b890b434-1272-5603-b759-016d01dc7a63"},"colorRGB":"189,137,9","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"c8ad14f8-28c2-5c31-a150-2cc5b0736449"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"bc848c37-ebf1-55fe-988b-192e2daacf07"},"colorRGB":"","material":"","children":[{"name":"L300 - Front Discs","type":"Face","attributes":{"_Flow360UUID":"e0219d33-62aa-5228-8b39-508ce187b3b4"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Discs","type":"Face","attributes":{"_Flow360UUID":"29c62942-ef63-53cc-9810-6e6c5aac248a"},"colorRGB":"","material":"","children":[]},{"name":"L300 - Front Discs","type":"Face","attributes":{"_Flow360UUID":"0211d29c-714e-51b1-babf-d62007852757"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"Underbody_new","type":"Assembly","attributes":{"_Flow360UUID":"89fe0345-9c0a-5741-997a-d01b187aa82d"},"colorRGB":"","material":"","children":[{"name":"Underbody_new","type":"Assembly","attributes":{"_Flow360UUID":"ca19665d-8db0-563a-b831-02bb79e8f857","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Underbody_new"},"colorRGB":"","material":"","children":[{"name":"Underbody_new","type":"Part","attributes":{"_Flow360UUID":"b0fd7be3-a385-518a-8003-3fec92e42d35"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Body","attributes":{"_Flow360UUID":"b7c907bf-6a63-5593-b7fe-15251753f3fe"},"colorRGB":"195,238,121","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"ba3d48d7-7977-57b5-bc2b-8ce16e6e8719"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"42404f65-d4c8-58d3-b91e-eb38aa283732"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"d191e092-52c2-5788-b8e0-85299112da36"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"e4c9ece2-36c6-5efd-a072-268c611aab24"},"colorRGB":"213,111,114","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"68a8c01b-f0fd-5a76-aadc-0d53d1526f60"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"de2052d9-0f3c-506b-827b-16516e5d44c9"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"b3377cd2-b3f8-5659-8e74-7ac7c48b0cc4"},"colorRGB":"","material":"","children":[]},{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"1d25cae0-1640-5b78-b9e8-b31eb63c0309"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"dbbf920c-96a5-5038-b87f-fe40c34f7bc3"},"colorRGB":"186,249,21","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"38e1fdc6-52e3-5f95-a004-3f4e61dd1e86"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"4b188efc-596c-569a-b66a-326dc3bc062a"},"colorRGB":"","material":"","children":[{"name":"Verbindung.2","type":"Face","attributes":{"_Flow360UUID":"b577ae21-18fc-5aa2-9771-27ff5d7f5beb"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"22_EngineBaySeals","type":"Assembly","attributes":{"_Flow360UUID":"314d6464-404d-5438-80e2-830d84d7e6a5"},"colorRGB":"","material":"","children":[{"name":"22_EngineBaySeals","type":"Assembly","attributes":{"_Flow360UUID":"a900aad4-57d1-50d2-b628-c036126fea73","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"22_EngineBaySeals"},"colorRGB":"","material":"","children":[{"name":"22_EngineBaySeals","type":"Part","attributes":{"_Flow360UUID":"57a68b3a-1313-5019-ac6a-f917e135d138"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"bb75e2ec-5d12-5474-a720-ef6105cd6893"},"colorRGB":"148,223,118","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"b8261446-4090-5fa8-a906-443dddf9326d"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"9abcb65c-7480-54ce-bea8-a63089c71f78"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"504f0fdc-1a09-507d-8568-13ae1e2c998e"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"ffce65d8-400a-5d8a-a479-b3695738f8f2"},"colorRGB":"162,136,133","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"58faa15b-ee6b-5b4c-8cec-69f96a27f5e7"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"b910c11e-a8d9-52a8-86d5-5b80ad711d8c"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"30164826-8b58-59ea-af7a-0470e090f5fb"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"a162166e-8d93-561c-8b77-0a9e92bc9e30"},"colorRGB":"174,209,241","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"949aede4-ae8a-54a3-bbb5-c362739c14a7"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"679ae65e-9d90-561d-a097-3d33c66fbb45"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"5d7211d6-6826-5228-8633-c2ce4f79b930"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"tyre_plinth","type":"Assembly","attributes":{"_Flow360UUID":"ff5ac0b6-7109-5b50-ac44-bb20e85333f1"},"colorRGB":"","material":"","children":[{"name":"tyre_plinth","type":"Assembly","attributes":{"_Flow360UUID":"9b0f5c89-5ef0-575d-8668-8651b19e0975","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"tyre_plinth"},"colorRGB":"","material":"","children":[{"name":"tyre_plinth","type":"Part","attributes":{"_Flow360UUID":"e1202cde-013b-5597-94ab-215043f79d95"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"758f3b14-fcc6-5657-8cff-f6cd62c841f3"},"colorRGB":"255,255,255","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"123622e6-3a57-5389-ac8f-a06e7715e6cf"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"c1f51b29-6c62-5db7-85bf-76d9b69e6295"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"9cb7de78-34ef-5c6f-a297-d2678ef06052"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"08061604-f78f-5a00-a1b2-160023988c08"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"914b8147-1840-5250-a7c1-aacdd1dc1887"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"b455eebc-0d94-5de1-871c-f73eb93fa09f"},"colorRGB":"255,255,255","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"61ea9a40-571f-5ed0-987e-09ba6fa87b82"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"c1a5464f-453f-51d9-8279-ecb88b7d7942"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"8d8a9109-51e1-51a2-a3af-1bb9c4562b4b"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"4cb73da6-f618-55d4-9b18-92f55b99a223"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"531132b5-e88b-57a2-88f2-63af0eef7d7e"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"1e048cfb-c79f-5039-ace8-2a8afcce6188"},"colorRGB":"255,255,255","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"2492ef1d-7566-5c54-aea3-721284e40985"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"ec5e248e-101a-5646-a23d-1886f1ac4a3c"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"412862d0-2461-507e-9aad-85194db88ed1"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"482ebcb2-bded-5be3-9ba3-ee4d513f039b"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"5e4e26da-c3f4-5d04-aa1f-fe5ed5799146"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"WT_ground_NoSlip","type":"Assembly","attributes":{"_Flow360UUID":"f92e48d1-1b5b-5368-b86d-6224b0fbd146"},"colorRGB":"","material":"","children":[{"name":"WT_ground_NoSlip","type":"Assembly","attributes":{"_Flow360UUID":"1ee4da3e-6709-5aba-a029-ba54a6c8718e","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"WT_ground_NoSlip"},"colorRGB":"","material":"","children":[{"name":"WT_ground_NoSlip","type":"Part","attributes":{"_Flow360UUID":"66ea1c1a-7fd9-59b2-8b37-09b1ac902cb3"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"ed812f2e-c85c-580f-b607-d5f5cb17e579"},"colorRGB":"176,72,234","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"7a21fbe4-6bba-563f-ac42-70896e3a622d"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"db89753a-a661-5b1f-aef9-d56a47c1e835"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"01f9e90a-f993-5327-bc9c-57c07443e61d"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"212ec96e-d981-55c1-9761-3bd9ad59055e"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"Slip_Ground","type":"Assembly","attributes":{"_Flow360UUID":"4a1acbd8-78c5-576e-a87b-83289c19021d"},"colorRGB":"","material":"","children":[{"name":"Slip_Ground","type":"Assembly","attributes":{"_Flow360UUID":"094efb69-ba85-51ed-b844-69edd3dfc8f1","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"Slip_Ground"},"colorRGB":"","material":"","children":[{"name":"Slip_Ground","type":"Part","attributes":{"_Flow360UUID":"b7d3e260-313f-5e43-af0e-9d23efbfb05d"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"e0c2e016-f779-5e1c-b4be-70789828699e"},"colorRGB":"137,42,252","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"1fe8aaa8-e37a-54ae-96cf-d738afc975cc"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"552bebae-e076-5791-81f5-abb1760c3f67"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"f12c6cfd-adfa-57c6-9851-4e0d6fcd0dd5"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"b66930c9-8962-5b96-b416-05884713f2fe"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"8d616bd9-b4ec-56cc-bf60-7db16b7d11a2"},"colorRGB":"178,38,214","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"1ef4718b-f356-5979-be4b-2b95fae05f11"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"77089a82-e2d2-53c7-8bf5-d6bbe75c350f"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"47eebf4b-8221-5555-b9ff-d89f32fe0870"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"633f2f6e-280d-524b-82f7-c64231b5b303"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"WT_Sides","type":"Assembly","attributes":{"_Flow360UUID":"3c197d11-a33f-553e-8361-9913a5a8e402"},"colorRGB":"","material":"","children":[{"name":"WT_Sides","type":"Assembly","attributes":{"_Flow360UUID":"49aa5f46-d59f-59e4-9544-50bd11f41a29","__STEPMakeOrBuy":".NOT_KNOWN.","__STEPProductID":"WT_Sides"},"colorRGB":"","material":"","children":[{"name":"WT_Sides","type":"Part","attributes":{"_Flow360UUID":"853b3490-f97e-5c0b-902f-79aa5b5c91d3"},"colorRGB":"","material":"","children":[{"name":"","type":"Body","attributes":{"_Flow360UUID":"0de0a08a-66bf-5a3e-b5ef-076c45eb04db"},"colorRGB":"176,23,22","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"e952b792-4dca-5712-a40b-56e7d8c5f7bc"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"ab771952-49bb-5c99-8916-fe538dcd9fdc"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"87cce053-8bbc-5420-8340-4922c79a873d"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"ec0f90e3-5f33-5d0b-9b8a-09addbcbc243"},"colorRGB":"128,254,210","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"dd6349ec-9d9e-5867-852e-274fa7df896d"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"f95024f9-9451-558c-a4d7-2ab7d713b4c5"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"c07eb86f-3226-553d-bf71-77ace175ec25"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"2a67b1fc-1462-5b9d-82e8-9fbd82462696"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"25df3ac4-c875-5d22-9ffa-abbb9811e5d6"},"colorRGB":"","material":"","children":[]}]}]}]},{"name":"","type":"Body","attributes":{"_Flow360UUID":"fd56c872-f3ad-51b2-ad38-be1d2ba8a2b9"},"colorRGB":"183,2,21","material":"","children":[{"name":"","type":"ShellCollection","attributes":{"_Flow360UUID":"e778519c-8102-5710-b527-cb7792c84a40"},"colorRGB":"","material":"","children":[{"name":"","type":"Shell","attributes":{"_Flow360UUID":"6d79da66-0202-5686-a87d-46f7bf267c0d"},"colorRGB":"","material":"","children":[{"name":"","type":"Face","attributes":{"_Flow360UUID":"fd82133f-6891-578b-acce-5c0e24f01b1c"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"f1016289-09f9-5d2e-a572-79b730a1c3d5"},"colorRGB":"","material":"","children":[]},{"name":"","type":"Face","attributes":{"_Flow360UUID":"43d33acd-6e87-591b-8711-f35ab22d3674"},"colorRGB":"","material":"","children":[]}]}]}]}]}]}]},{"name":"_","type":"Part","attributes":{"_Flow360UUID":"c57a5034-ceae-5c97-ab92-4191e9f380fb"},"colorRGB":"","material":"","children":[]}]}]}} \ No newline at end of file diff --git a/tests/simulation/asset/test_geometry_tree.py b/tests/simulation/asset/test_geometry_tree.py new file mode 100644 index 000000000..a31de1ee6 --- /dev/null +++ b/tests/simulation/asset/test_geometry_tree.py @@ -0,0 +1,588 @@ +""" +Tests for the geometry tree and face grouping API. +""" + +import json +import os + +import pytest + +from flow360.component.geometry import Geometry +from flow360.component.geometry_tree import NodeType, TreeBackend +from flow360.component.geometry_tree.face_group import FaceGroup +from flow360.component.geometry_tree.filters import matches_pattern +from flow360.exceptions import Flow360ValueError + +TREE_DATA_DIR = os.path.join(os.path.dirname(__file__), "../../data/geometry_tree") +AIRPLANE_JSON_PATH = os.path.join(TREE_DATA_DIR, "airplane_rc_geometry_tree.json") +AIRPLANE_TOTAL_FACES = 194 + +DRIVAER_JSON_PATH = os.path.join(TREE_DATA_DIR, "drivaer_geometry_tree.json") +DRIVAER_TOTAL_FACES = 74 +DRIVAER_TOTAL_NODES = 233 + +AIRPLANE_COLOR_EXPECTED = [ + ("255,0,255", 40), + ("0,0,255", 37), + ("0,255,0", 37), + ("0,255,255", 27), + ("255,255,0", 27), + ("", 24), + ("0,0,0", 2), +] + + +@pytest.fixture +def airplane_json(): + with open(AIRPLANE_JSON_PATH, "r") as f: + return json.load(f) + + +@pytest.fixture +def airplane_tree(airplane_json): + tree = TreeBackend() + tree.load_from_json(airplane_json) + return tree + + +@pytest.fixture +def airplane_geometry(): + return Geometry.from_local_tree(AIRPLANE_JSON_PATH) + + +@pytest.fixture +def drivaer_tree(): + with open(DRIVAER_JSON_PATH, "r") as f: + json_data = json.load(f) + tree = TreeBackend() + tree.load_from_json(json_data) + return tree + + +@pytest.fixture +def drivaer_geometry(): + return Geometry.from_local_tree(DRIVAER_JSON_PATH) + + +# ================================================================ +# TreeBackend tests +# ================================================================ + + +class TestTreeBackend: + def test_load_from_json_versioned(self, airplane_tree): + assert airplane_tree.get_root() is not None + # ModelFile + 2 Assembly + Part + Body + 3 BodyCollection + # + ShellCollection + Shell + 194 Faces = 204 + assert airplane_tree.node_count() == 204 + + def test_load_from_json_requires_version_key(self): + """Unversioned data (no 'tree' key) should raise KeyError.""" + backend = TreeBackend() + raw_tree = {"name": "root", "type": "ModelFile", "children": []} + with pytest.raises(KeyError): + backend.load_from_json(raw_tree) + + def test_load_from_json_missing_uuid(self): + """Nodes without _Flow360UUID should raise ValueError.""" + backend = TreeBackend() + tree_data = { + "version": "1.0", + "tree": {"name": "root", "type": "ModelFile", "children": []}, + } + with pytest.raises(ValueError, match="missing.*_Flow360UUID"): + backend.load_from_json(tree_data) + + def test_load_from_json_duplicate_uuid(self): + """Duplicate _Flow360UUID should raise ValueError.""" + backend = TreeBackend() + tree_data = { + "version": "1.0", + "tree": { + "name": "root", + "type": "ModelFile", + "attributes": {"_Flow360UUID": "same-uuid"}, + "children": [ + { + "name": "child", + "type": "Face", + "attributes": {"_Flow360UUID": "same-uuid"}, + } + ], + }, + } + with pytest.raises(ValueError, match="Duplicate.*_Flow360UUID"): + backend.load_from_json(tree_data) + + def test_load_from_json_unknown_type(self): + """Unknown node type should raise ValueError.""" + backend = TreeBackend() + tree_data = { + "version": "1.0", + "tree": { + "name": "root", + "type": "UnknownType", + "attributes": {"_Flow360UUID": "uuid-1"}, + }, + } + with pytest.raises(ValueError, match="unknown type.*UnknownType"): + backend.load_from_json(tree_data) + + def test_load_from_file(self): + backend = TreeBackend() + root_id = backend.load_from_file(AIRPLANE_JSON_PATH) + assert root_id is not None + assert backend.node_count() == 204 + + def test_root_node_attributes(self, airplane_tree): + root_attrs = airplane_tree.get_node_attrs(airplane_tree.get_root()) + assert root_attrs["name"] == "Solid-Body-RC-Plane_v2024_colored" + assert root_attrs["type"] == NodeType.MODEL_FILE + + def test_get_children(self, airplane_tree): + children = airplane_tree.get_children(airplane_tree.get_root()) + assert len(children) == 1 # single Assembly child + + def test_get_descendants(self, airplane_tree): + descendants = airplane_tree.get_descendants(airplane_tree.get_root()) + assert len(descendants) == 203 # 204 total - 1 root + + def test_get_parent(self, airplane_tree): + root = airplane_tree.get_root() + children = airplane_tree.get_children(root) + assert airplane_tree.get_parent(children[0]) == root + assert airplane_tree.get_parent(root) is None + + def test_get_all_faces(self, airplane_tree): + assert len(airplane_tree.get_all_faces()) == AIRPLANE_TOTAL_FACES + + def test_filter_nodes_by_type(self, airplane_tree): + all_nodes = airplane_tree.get_all_nodes() + assert ( + len(airplane_tree.filter_nodes(all_nodes, type=NodeType.FACE)) == AIRPLANE_TOTAL_FACES + ) + assert len(airplane_tree.filter_nodes(all_nodes, type=NodeType.SHELL)) == 1 + assert len(airplane_tree.filter_nodes(all_nodes, type=NodeType.BODY_COLLECTION)) == 3 + + def test_nonexistent_node(self, airplane_tree): + assert airplane_tree.get_node_attrs("nonexistent") == {} + assert airplane_tree.get_children("nonexistent") == [] + assert airplane_tree.get_parent("nonexistent") is None + assert airplane_tree.get_descendants("nonexistent") == set() + + +# ================================================================ +# Tree navigation tests +# ================================================================ + + +class TestTreeNavigation: + def test_root_node(self, airplane_geometry): + root = airplane_geometry.root_node() + assert root.name == "Solid-Body-RC-Plane_v2024_colored" + assert root.type == NodeType.MODEL_FILE + + def test_children(self, airplane_geometry): + children = airplane_geometry.children() + assert len(children) == 1 + + def test_descendants(self, airplane_geometry): + assert len(airplane_geometry.descendants()) == 203 + + def test_descendants_with_type_filter(self, airplane_geometry): + assert len(airplane_geometry.descendants(type=NodeType.FACE)) == AIRPLANE_TOTAL_FACES + assert len(airplane_geometry.descendants(type=NodeType.SHELL_COLLECTION)) == 1 + assert len(airplane_geometry.descendants(type=NodeType.PART)) == 1 + assert len(airplane_geometry.descendants(type=NodeType.BODY)) == 1 + assert len(airplane_geometry.descendants(type=NodeType.SHELL)) == 1 + assert len(airplane_geometry.descendants(type=NodeType.ASSEMBLY)) == 2 + assert len(airplane_geometry.descendants(type=NodeType.BODY_COLLECTION)) == 3 + + def test_faces_no_filter(self, airplane_geometry): + assert len(airplane_geometry.faces()) == AIRPLANE_TOTAL_FACES + + @pytest.mark.parametrize("color,expected_count", AIRPLANE_COLOR_EXPECTED) + def test_faces_filter_by_color(self, airplane_geometry, color, expected_count): + assert len(airplane_geometry.faces(colorRGB=color)) == expected_count + + def test_faces_filter_no_match(self, airplane_geometry): + assert len(airplane_geometry.faces(colorRGB="999,999,999")) == 0 + + def test_all_color_filters_sum_to_total(self, airplane_geometry): + total = sum( + len(airplane_geometry.faces(colorRGB=color)) for color, _ in AIRPLANE_COLOR_EXPECTED + ) + assert total == AIRPLANE_TOTAL_FACES + + +# ================================================================ +# Node attribute tests +# ================================================================ + + +class TestNodeAttributes: + def test_face_node_properties(self, airplane_geometry): + magenta_faces = airplane_geometry.faces(colorRGB="255,0,255") + node = next(iter(magenta_faces)) + assert node.type == NodeType.FACE + assert node.color == "255,0,255" + assert node.is_face() + + def test_non_face_node(self, airplane_geometry): + node = next(iter(airplane_geometry.descendants(type=NodeType.PART))) + assert node.type == NodeType.PART + assert not node.is_face() + + def test_node_equality(self, airplane_geometry): + faces1 = airplane_geometry.faces(colorRGB="0,0,0") + faces2 = airplane_geometry.faces(colorRGB="0,0,0") + assert len(faces1) == 2 + # Same query yields equal NodeSets + assert faces1 == faces2 + + +# ================================================================ +# Face group management tests +# ================================================================ + + +class TestFaceGroupManagement: + def test_create_face_group(self, airplane_geometry): + group = airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + assert isinstance(group, FaceGroup) + assert group.name == "magenta" + assert group.face_count() == 40 + + def test_list_groups(self, airplane_geometry): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + airplane_geometry.create_face_group( + name="blue", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + assert airplane_geometry.list_groups() == ["magenta", "blue"] + + def test_get_face_group(self, airplane_geometry): + airplane_geometry.create_face_group( + name="green", selection=airplane_geometry.faces(colorRGB="0,255,0") + ) + group = airplane_geometry.get_face_group("green") + assert group.name == "green" + assert group.face_count() == 37 + + def test_get_face_group_not_found(self, airplane_geometry): + with pytest.raises(KeyError): + airplane_geometry.get_face_group("nonexistent") + + def test_duplicate_group_name_raises(self, airplane_geometry): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + with pytest.raises(ValueError, match="already exists"): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + + def test_clear_groups(self, airplane_geometry): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + airplane_geometry.create_face_group( + name="blue", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + airplane_geometry.clear_groups() + assert airplane_geometry.list_groups() == [] + + def test_exclusive_face_ownership(self, airplane_geometry): + """Assigning faces to a new group removes them from the old group.""" + all_group = airplane_geometry.create_face_group( + name="all", selection=airplane_geometry.faces() + ) + assert all_group.face_count() == AIRPLANE_TOTAL_FACES + + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + assert all_group.face_count() == AIRPLANE_TOTAL_FACES - 40 + + airplane_geometry.create_face_group( + name="blue", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + assert all_group.face_count() == AIRPLANE_TOTAL_FACES - 40 - 37 + + @pytest.mark.parametrize("color,expected_count", AIRPLANE_COLOR_EXPECTED) + def test_group_face_count_per_color(self, airplane_geometry, color, expected_count): + name = color if color else "uncolored" + group = airplane_geometry.create_face_group( + name=name, selection=airplane_geometry.faces(colorRGB=color) + ) + assert group.face_count() == expected_count + + def test_group_all_colors(self, airplane_geometry): + for color, _ in AIRPLANE_COLOR_EXPECTED: + name = color if color else "uncolored" + airplane_geometry.create_face_group( + name=name, selection=airplane_geometry.faces(colorRGB=color) + ) + assert len(airplane_geometry.list_groups()) == 7 + + +# ================================================================ +# export_face_grouping_config and _build_face_grouping_config tests +# ================================================================ + + +class TestSaveGroups: + def test_build_face_grouping_config(self, airplane_geometry): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + airplane_geometry.create_face_group( + name="blue", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + + config = airplane_geometry._build_face_grouping_config() + assert config["version"] == "1.0" + mapping = config["face_group_mapping"] + assert len(mapping) == 40 + 37 # 77 UUID entries + assert set(mapping.values()) == {"magenta", "blue"} + + def test_save_and_load(self, airplane_geometry, tmp_path): + airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + airplane_geometry.create_face_group( + name="blue", selection=airplane_geometry.faces(colorRGB="0,0,255") + ) + airplane_geometry.create_face_group( + name="green", selection=airplane_geometry.faces(colorRGB="0,255,0") + ) + + output_path = str(tmp_path / "face_grouping.json") + airplane_geometry.export_face_grouping_config(output_path) + with open(output_path, "r") as f: + data = json.load(f) + + assert data["version"] == "1.0" + mapping = data["face_group_mapping"] + assert len(mapping) == 40 + 37 + 37 # 114 faces across 3 groups + assert set(mapping.values()) == {"magenta", "blue", "green"} + # Keys should be valid UUIDs (contain hyphens) + for key in mapping: + assert "-" in key + + +# ================================================================ +# Set operation tests +# ================================================================ + + +class TestSetOperations: + def test_nodeset_union(self, airplane_geometry): + magenta = airplane_geometry.faces(colorRGB="255,0,255") + blue = airplane_geometry.faces(colorRGB="0,0,255") + assert len(magenta | blue) == 77 # 40 + 37 + + def test_nodeset_intersection(self, airplane_geometry): + all_faces = airplane_geometry.faces() + magenta = airplane_geometry.faces(colorRGB="255,0,255") + assert len(all_faces & magenta) == 40 + + def test_nodeset_intersection_disjoint(self, airplane_geometry): + magenta = airplane_geometry.faces(colorRGB="255,0,255") + blue = airplane_geometry.faces(colorRGB="0,0,255") + assert len(magenta & blue) == 0 + + def test_nodeset_difference(self, airplane_geometry): + all_faces = airplane_geometry.faces() + magenta = airplane_geometry.faces(colorRGB="255,0,255") + assert len(all_faces - magenta) == AIRPLANE_TOTAL_FACES - 40 + + def test_subtract_multiple_colors(self, airplane_geometry): + remaining = ( + airplane_geometry.faces() + - airplane_geometry.faces(colorRGB="255,0,255") + - airplane_geometry.faces(colorRGB="0,0,255") + ) + assert len(remaining) == AIRPLANE_TOTAL_FACES - 40 - 37 + + def test_nodeset_subtract_face_group(self, airplane_geometry): + group = airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + assert len(airplane_geometry.faces() - group) == AIRPLANE_TOTAL_FACES - 40 + + def test_geometry_subtract_face_group(self, airplane_geometry): + group = airplane_geometry.create_face_group( + name="magenta", selection=airplane_geometry.faces(colorRGB="255,0,255") + ) + assert len(airplane_geometry - group) == AIRPLANE_TOTAL_FACES - 40 + + def test_geometry_subtract_nodeset_raises(self, airplane_geometry): + magenta = airplane_geometry.faces(colorRGB="255,0,255") + with pytest.raises(Flow360ValueError): + _ = airplane_geometry - magenta + + def test_nodeset_is_empty(self, airplane_geometry): + empty = airplane_geometry.faces(colorRGB="999,999,999") + assert empty.is_empty() + assert not bool(empty) + + faces = airplane_geometry.faces() + assert not faces.is_empty() + assert bool(faces) + + def test_nodeset_contains(self, airplane_geometry): + faces = airplane_geometry.faces() + node = next(iter(faces)) + assert node in faces + + def test_nodeset_equality(self, airplane_geometry): + faces1 = airplane_geometry.faces(colorRGB="255,0,255") + faces2 = airplane_geometry.faces(colorRGB="255,0,255") + assert faces1 == faces2 + + +# ================================================================ +# DrivAer geometry tests (large model: 9645 faces, 6240 bodies) +# ================================================================ + + +class TestDrivAer: + def test_tree_loading(self, drivaer_tree): + assert drivaer_tree.node_count() == DRIVAER_TOTAL_NODES + assert len(drivaer_tree.get_all_faces()) == DRIVAER_TOTAL_FACES + + def test_root_node(self, drivaer_geometry): + root = drivaer_geometry.root_node() + assert root.name == "ANSA_Assembled_DrivAer_Input" + assert root.type == NodeType.MODEL_FILE + + def test_faces(self, drivaer_geometry): + assert len(drivaer_geometry.faces()) == DRIVAER_TOTAL_FACES + + def test_type_counts(self, drivaer_geometry): + assert len(drivaer_geometry.descendants(type=NodeType.FACE)) == DRIVAER_TOTAL_FACES + assert len(drivaer_geometry.descendants(type=NodeType.BODY)) == 38 + assert len(drivaer_geometry.descendants(type=NodeType.SHELL)) == 38 + assert len(drivaer_geometry.descendants(type=NodeType.SHELL_COLLECTION)) == 38 + assert len(drivaer_geometry.descendants(type=NodeType.PART)) == 15 + assert len(drivaer_geometry.descendants(type=NodeType.ASSEMBLY)) == 29 + + def test_all_faces_uncolored(self, drivaer_geometry): + """DrivAer model has no color assignments.""" + assert len(drivaer_geometry.faces(colorRGB="")) == DRIVAER_TOTAL_FACES + + def test_face_grouping_by_assembly_name(self, drivaer_geometry): + """Group faces by Assembly name and verify counts.""" + expected_faces = { + "09_engineAndGearbox": 3, + "21_Chassis": 9, + "22_EngineBaySeals": 3, + "Body": 3, + "ExhaustSystem_new": 3, + "Mirrors": 6, + "RearEnd_Sedan": 3, + "Slip_Ground": 4, + "Underbody_new": 4, + "WT_Sides": 7, + "WT_ground_NoSlip": 2, + "Wheels_Front": 9, + "Wheels_Rear": 9, + "tyre_plinth": 9, + } + + for name, count in expected_faces.items(): + faces = drivaer_geometry.descendants(type=NodeType.ASSEMBLY, name=name).faces() + group = drivaer_geometry.create_face_group(name=name, selection=faces) + assert group.face_count() == count + + assert len(drivaer_geometry.list_groups()) == 14 + + # Subtracting all groups from geometry should leave 0 faces + remaining = drivaer_geometry + for name in expected_faces: + remaining = remaining - drivaer_geometry.get_face_group(name) + assert len(remaining) == 0 + + def test_face_grouping_by_children_navigation(self, drivaer_geometry): + """Group faces using chained .children() navigation.""" + expected = { + "Body": 3, + "Wheels_Front": 9, + "Wheels_Rear": 9, + "Underbody_new": 4, + "Mirrors": 6, + } + + for name, count in expected.items(): + group = drivaer_geometry.create_face_group( + name=name, + selection=drivaer_geometry.children().children(name=name), + ) + assert group.face_count() == count + + # Subtracting these groups leaves the remaining faces + remaining = drivaer_geometry + for name in expected: + remaining = remaining - drivaer_geometry.get_face_group(name) + assert len(remaining) == DRIVAER_TOTAL_FACES - sum(expected.values()) + + +# ================================================================ +# NodeType enum tests +# ================================================================ + + +class TestNodeType: + def test_str_enum_value(self): + """NodeType values are the strings exported by C++.""" + assert NodeType.MODEL_FILE == "ModelFile" + assert NodeType.FACE == "Face" + assert NodeType.BODY_COLLECTION == "BodyCollection" + + def test_unknown_type_rejected(self): + """Unknown type string raises ValueError on load.""" + backend = TreeBackend() + tree_data = { + "version": "1.0", + "tree": { + "name": "root", + "type": "Bogus", + "attributes": {"_Flow360UUID": "uuid-1"}, + }, + } + with pytest.raises(ValueError, match="unknown type"): + backend.load_from_json(tree_data) + + +# ================================================================ +# Filter pattern matching tests (for non-type attributes) +# ================================================================ + + +class TestFilterPatterns: + def test_exact_match(self): + assert matches_pattern("hello", "hello") + assert matches_pattern("Hello", "hello") # case insensitive + assert not matches_pattern("hello", "world") + + def test_wildcard_star(self): + assert matches_pattern("top_surface", "top*") + assert matches_pattern("top_surface", "*surface") + assert matches_pattern("top_surface", "*_*") + assert not matches_pattern("top_surface", "bottom*") + + def test_wildcard_question(self): + assert matches_pattern("abc", "a?c") + assert not matches_pattern("abbc", "a?c") + + def test_none_value(self): + assert not matches_pattern(None, "anything") + + def test_filter_by_name_glob(self, airplane_geometry): + """Glob filter works for name attribute.""" + result = airplane_geometry.descendants(name="*Default*") + assert len(result) > 0