@@ -331,6 +331,63 @@ def children(
331331
332332 return NodeCollection (all_children )
333333
334+ def search (
335+ self ,
336+ type : Optional [NodeType ] = None ,
337+ name : Optional [str ] = None ,
338+ colorRGB : Optional [str ] = None ,
339+ material : Optional [str ] = None ,
340+ attributes : Optional [Dict [str , str ]] = None ,
341+ ) -> "CollectionTreeSearch" :
342+ """
343+ Create a deferred search operation across all nodes in the collection.
344+
345+ This method searches the subtrees of all nodes in this collection for nodes
346+ matching the given criteria. It returns a CollectionTreeSearch instance that
347+ captures the search criteria but does not execute until needed.
348+
349+ Supports wildcard matching for name and material using '*' character.
350+ All criteria are ANDed together.
351+
352+ Parameters
353+ ----------
354+ type : Optional[NodeType]
355+ Node type to match (e.g., NodeType.FRMFeature)
356+ name : Optional[str]
357+ Name pattern to match. Supports wildcards:
358+ - "*wing*" matches any name containing "wing"
359+ - "wing*" matches any name starting with "wing"
360+ - "*wing" matches any name ending with "wing"
361+ - "wing" matches exact name "wing"
362+ colorRGB : Optional[str]
363+ RGB color string to match (e.g., "255,0,0" for red)
364+ material : Optional[str]
365+ Material name to match. Supports wildcard matching like name parameter.
366+ attributes : Optional[Dict[str, str]]
367+ Dictionary of attribute key-value pairs to match
368+
369+ Returns
370+ -------
371+ CollectionTreeSearch
372+ A search instance that can be executed later to get matching nodes
373+
374+ Examples
375+ --------
376+ >>> # Search for FRMFeature nodes across multiple nodes
377+ >>> results = collection.search(type=NodeType.FRMFeature, name="Boss-Extrude3")
378+ >>>
379+ >>> # Pass to create_face_group or add methods
380+ >>> wing.add(results)
381+ """
382+ return CollectionTreeSearch (
383+ nodes = self ._nodes ,
384+ type = type ,
385+ name = name ,
386+ colorRGB = colorRGB ,
387+ material = material ,
388+ attributes = attributes ,
389+ )
390+
334391 def __len__ (self ) -> int :
335392 """Return the number of nodes in this collection"""
336393 return len (self ._nodes )
@@ -457,6 +514,101 @@ def __repr__(self):
457514 return f"TreeSearch({ criteria_str } )"
458515
459516
517+ class CollectionTreeSearch :
518+ """
519+ Represents a deferred tree search operation across multiple nodes.
520+
521+ This class is similar to TreeSearch but operates on a collection of nodes
522+ instead of a single node. It captures search criteria and executes the search
523+ across all nodes when requested.
524+ """
525+
526+ def __init__ (
527+ self ,
528+ nodes : List [TreeNode ],
529+ type : Optional [NodeType ] = None ,
530+ name : Optional [str ] = None ,
531+ colorRGB : Optional [str ] = None ,
532+ material : Optional [str ] = None ,
533+ attributes : Optional [Dict [str , str ]] = None ,
534+ ):
535+ """
536+ Initialize a CollectionTreeSearch with search criteria.
537+
538+ Parameters
539+ ----------
540+ nodes : List[TreeNode]
541+ The nodes from which to start the search (searches their subtrees)
542+ type : Optional[NodeType]
543+ Node type to match (e.g., NodeType.FRMFeature)
544+ name : Optional[str]
545+ Name pattern to match. Supports wildcards (e.g., "*wing*")
546+ colorRGB : Optional[str]
547+ RGB color string to match (e.g., "255,0,0")
548+ material : Optional[str]
549+ Material name to match. Supports wildcards.
550+ attributes : Optional[Dict[str, str]]
551+ Dictionary of attribute key-value pairs to match
552+ """
553+ self .nodes = nodes
554+ self .type = type
555+ self .name = name
556+ self .colorRGB = colorRGB
557+ self .material = material
558+ self .attributes = attributes
559+
560+ def execute (self ) -> List [TreeNode ]:
561+ """
562+ Execute the search across all nodes and return matching nodes.
563+
564+ Searches the subtree of each node in the collection and combines
565+ all matching results, avoiding duplicates.
566+
567+ Returns
568+ -------
569+ List[TreeNode]
570+ List of unique nodes matching the search criteria across all subtrees
571+ """
572+ all_matches = []
573+ seen_ids = set ()
574+
575+ for node in self .nodes :
576+ # Create a TreeSearch for this node
577+ tree_search = TreeSearch (
578+ node = node ,
579+ type = self .type ,
580+ name = self .name ,
581+ colorRGB = self .colorRGB ,
582+ material = self .material ,
583+ attributes = self .attributes ,
584+ )
585+
586+ # Execute and collect matches, avoiding duplicates
587+ matches = tree_search .execute ()
588+ for match in matches :
589+ node_id = id (match )
590+ if node_id not in seen_ids :
591+ seen_ids .add (node_id )
592+ all_matches .append (match )
593+
594+ return all_matches
595+
596+ def __repr__ (self ):
597+ criteria = []
598+ if self .type is not None :
599+ criteria .append (f"type={ self .type .value } " )
600+ if self .name is not None :
601+ criteria .append (f"name='{ self .name } '" )
602+ if self .colorRGB is not None :
603+ criteria .append (f"colorRGB='{ self .colorRGB } '" )
604+ if self .material is not None :
605+ criteria .append (f"material='{ self .material } '" )
606+ if self .attributes is not None :
607+ criteria .append (f"attributes={ self .attributes } " )
608+ criteria_str = ", " .join (criteria )
609+ return f"CollectionTreeSearch({ len (self .nodes )} nodes, { criteria_str } )"
610+
611+
460612class GeometryTree :
461613 """Pure tree structure representing Geometry hierarchy"""
462614
0 commit comments