Data traversal is the systematic process of navigating Speckle’s object graphs to extract data, find specific objects, and preserve hierarchical context. Understanding traversal is fundamental to working with Speckle data effectively.
Prerequisites: This guide assumes familiarity with the Base class and data types. If you’re new to Speckle objects, start with those concepts first.
Speckle objects form hierarchical graphs - tree-like structures where objects contain other objects, arrays of objects, and references to objects stored elsewhere. Unlike simple flat lists, these graphs require strategic navigation to:
Extract all objects of a specific type (e.g., all walls, all meshes)
Preserve parent-child relationships and organizational context
Filter data based on properties or structure
Build analysis pipelines that understand object hierarchies
Key Concepts:
Graph Structure: Objects are connected through properties (parent → child relationships)
Traversal: The process of visiting every object in a graph systematically
Filtering: Selecting only objects that match specific criteria during traversal
Flattening: Converting a hierarchical graph into a flat list of objects
Copy
from specklepy.api import operationsfrom specklepy.objects import Base# Receive an object graphobj = operations.receive(object_id, remote_transport=transport)# Navigate the graphfor name in obj.get_member_names(): value = getattr(obj, name) print(f"{name}: {type(value).__name__}")
Speckle data comes in different structural complexities depending on the source and use case. Understanding these patterns helps you choose the right traversal strategy.
For v3 Data: The most effective strategies are Property-Filtered, displayValue-Based, and elements[] Hierarchy traversal. Type-filtered traversal using speckle_type is primarily useful for geometry primitives (Mesh, Point, Line) but not for BIM objects, which are now generic DataObject instances with semantics in their properties.
When to use: Objects are organized in a logical hierarchy using elements[] arrays.
The elements property is Speckle’s standardized convention for organizing hierarchical data. See The elements Convention for a detailed explanation of this pattern.
Strategy 5: Type-Filtered Traversal (Limited Use in v3)
When to use: Looking for geometry primitives (Mesh, Point, Line, etc.).How it works:
Traverse recursively
Check each object’s speckle_type property
Collect only objects matching the target type
Continue traversing to find all instances
Best for:
Finding geometry objects (Mesh, Line, Point, Arc, Circle, etc.)
Pure geometry workflows
Legacy v2 objects with typed BIM classes
Limited Use in v3 BIM Data: Most BIM objects from connectors are now DataObject instances with speckle_type = "Objects.Data.DataObject". Filtering by this type returns ALL BIM objects without differentiation. For BIM data, use Property-Filtered Traversal (Strategy 1) instead to filter by category, family, type, etc.
Working with BIM data from connectors?├─ YES → Use Property-Filtered Traversal (Strategy 1)│ Filter by: category, family, type, level, material, etc.└─ NO → Continue...Need only viewer-visible objects?├─ YES → Use displayValue-Based Traversal (Strategy 2)│ Gets atomic, selectable objects└─ NO → Continue...Following organizational hierarchy (Collections, Groups)?├─ YES → Use elements[] Hierarchy Traversal (Strategy 3)│ Faster, follows intentional structure└─ NO → Continue...Looking for geometry primitives (Mesh, Point, Line)?├─ YES → Use type-filtered traversal (Pattern 1)└─ NO → Continue...Are you looking for v3 BIM objects (walls, columns, etc.)?├─ YES → Use property-filtered traversal (Pattern 2)│ or displayValue filtering (Pattern 3b)└─ NO → Continue...Is data organized in elements[] arrays?├─ YES → Use elements[] traversal (Pattern 3a)└─ NO → Use full recursive traversal
When to use: You know the exact structure and property names.Advantages: Fastest, most efficient, no unnecessary traversal.
Copy
from specklepy.api import operationsobj = operations.receive(object_id, remote_transport=transport)# Direct access (if you know the structure)if hasattr(obj, "name"): print(f"Name: {obj.name}")if hasattr(obj, "measurements"): for measurement in obj.measurements: print(f"Point: ({measurement.x}, {measurement.y}, {measurement.z})")
from specklepy.objects import Basedef print_members(obj, indent=0): """Print all members of an object.""" prefix = " " * indent if isinstance(obj, Base): print(f"{prefix}{obj.speckle_type}") for name in obj.get_member_names(): if name.startswith("_"): continue value = getattr(obj, name, None) print(f"{prefix} {name}:") print_members(value, indent + 2) elif isinstance(obj, list): print(f"{prefix}[List: {len(obj)} items]") if obj: print_members(obj[0], indent + 1) # Show first item elif isinstance(obj, dict): print(f"{prefix}{{Dict: {len(obj)} keys}}") for key, value in list(obj.items())[:3]: # Show first 3 print(f"{prefix} {key}:") print_members(value, indent + 2) else: # Primitive value value_str = str(obj)[:50] # Limit length print(f"{prefix}{value_str}")# Use itprint_members(obj)
from specklepy.objects import Basedef find_all(root, predicate): """Find all objects matching a predicate function.""" results = [] def traverse(obj): if isinstance(obj, Base): # Check if this object matches if predicate(obj): results.append(obj) # Traverse children for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) elif isinstance(obj, dict): for value in obj.values(): traverse(value) traverse(root) return results# Use it: find all meshesfrom specklepy.objects.geometry import Meshmeshes = find_all(obj, lambda x: isinstance(x, Mesh))print(f"Found {len(meshes)} meshes")# Find all walls by checking properties (v3 pattern)from specklepy.objects.data_objects import DataObjectwalls = find_all(obj, lambda x: isinstance(x, DataObject) and x.properties.get("category") == "Walls")print(f"Found {len(walls)} walls")
Pattern 1: Find by Property ⭐ Primary Pattern for v3
Purpose: Search for objects based on their properties (the standard v3 pattern).When to use:
Working with BIM data from any connector (Revit, Rhino, ArchiCAD, etc.)
Need to find objects by category, family, type, level, material
Filtering by quantities, parameters, or metadata
This is the primary pattern for modern Speckle data
How it works:
Recursively traverse all objects
Check if object has properties dictionary
Filter based on property values
Collect matches
Purpose: Find objects based on their properties dictionary values (v3 BIM pattern).When to use:
Working with v3 BIM data (DataObject)
Need to find objects by category (“Walls”, “Columns”, “Floors”)
Filtering by BIM properties (loadBearing, fireRating, family, type)
Need to find objects with specific quantities or metadata
Advantages:
Works with v3 object model
Can filter on any property value
Can combine multiple property conditions
Reflects how BIM data is actually organized
How it works:
Traverse all objects
Check if object is a DataObject
Access the properties dictionary
Filter based on property values
Collect matches
Copy
from specklepy.objects import Basedef find_by_property(root, property_name, property_value=None): """Find objects with a specific property (optionally with value).""" results = [] def traverse(obj): if isinstance(obj, Base): # Check if property exists if hasattr(obj, property_name): value = getattr(obj, property_name) # If checking value too if property_value is None or value == property_value: results.append(obj) # Check properties dictionary if hasattr(obj, "properties") and isinstance(obj.properties, dict): if property_name in obj.properties: value = obj.properties[property_name] if property_value is None or value == property_value: results.append(obj) # Traverse children for name in obj.get_member_names(): if not name.startswith("_"): child = getattr(obj, name, None) traverse(child) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return results# Use itsteel_elements = find_by_property(obj, "Material", "Steel")exterior_walls = find_by_property(obj, "Function", "Exterior")all_with_volume = find_by_property(obj, "volume") # Any object with volumeprint(f"Steel elements: {len(steel_elements)}")print(f"Exterior walls: {len(exterior_walls)}")print(f"Objects with volume: {len(all_with_volume)}")
Purpose: Search for geometry primitives by speckle_type.When to use:
Looking for geometry objects (Mesh, Point, Line, Arc, Circle, etc.)
Pure geometry workflows without BIM semantics
Need to find all instances of a specific geometry class
When NOT to use:
❌ BIM data from connectors (use Pattern 1 - Find by Property instead)
❌ Need to differentiate walls from columns (use properties)
❌ Any data where semantics are in the properties dictionary
Limited Use for BIM Data: In v3, BIM objects are DataObject instances with speckle_type = "Objects.Data.DataObject". All walls, columns, beams, etc. have the same type. To find specific BIM elements, use Pattern 1 (Find by Property) to filter by category, family, type, etc.
Copy
from specklepy.objects import Basedef find_by_type(root, target_type): """Find all objects with a specific speckle_type (for geometry objects).""" results = [] def traverse(obj): if isinstance(obj, Base): if obj.speckle_type == target_type: results.append(obj) for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) elif isinstance(obj, dict): for value in obj.values(): traverse(value) traverse(root) return results# Good use cases - geometry primitivespoints = find_by_type(obj, "Objects.Geometry.Point")meshes = find_by_type(obj, "Objects.Geometry.Mesh")lines = find_by_type(obj, "Objects.Geometry.Line")print(f"Found {len(points)} points, {len(meshes)} meshes, {len(lines)} lines")# ❌ Won't work for BIM data:# data_objects = find_by_type(obj, "Objects.Data.DataObject") # # Returns ALL BIM objects - walls, columns, beams, everything!# # Use Pattern 1 (find_by_property) instead
from specklepy.objects import Basefrom specklepy.objects.geometry import Meshfrom specklepy.objects.data_objects import DataObjectdef flatten_by_category(root, category_name): """Build a flat list of BIM objects by category (v3 pattern).""" results = [] def traverse(obj): if isinstance(obj, Base): # Check properties for category (v3 pattern) if hasattr(obj, "properties") and isinstance(obj.properties, dict): if obj.properties.get("category") == category_name: results.append(obj) # Continue traversing even after match for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) elif isinstance(obj, dict): for value in obj.values(): traverse(value) traverse(root) return results# For geometry primitives, use isinstance checksdef flatten_geometry(root, geometry_class): """Build a flat list of geometry objects by class.""" results = [] def traverse(obj): if isinstance(obj, geometry_class): results.append(obj) elif isinstance(obj, Base): for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return results# Use itfrom specklepy.objects.geometry import Mesh, Pointmeshes = flatten_geometry(obj, Mesh)points = flatten_geometry(obj, Point)
def flatten_by_properties(root, **filters): """Build a flat list of DataObjects by category (for v3 BIM objects).""" results = [] def traverse(obj): if isinstance(obj, DataObject): obj_category = obj.properties.get("category", "") if obj_category == category: results.append(obj) if isinstance(obj, Base): for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return results# Use it for geometryall_meshes = flatten_by_type(obj, "Objects.Geometry.Mesh")print(f"Flattened to {len(all_meshes)} meshes")# Use it for BIM objects (v3 pattern)all_walls = flatten_data_objects_by_category(obj, "Walls")all_columns = flatten_data_objects_by_category(obj, "Columns")print(f"Found {len(all_walls)} walls, {len(all_columns)} columns")
Purpose: Navigate object hierarchies by following the organizational structure defined by elements[] arrays.When to use:
Data is organized in Collections or Groups
Objects have a logical hierarchy (Building → Level → Room → Elements)
Want to respect the intentional organization
Need faster traversal by ignoring non-structural properties
How it works:
Check if object has elements property (list)
Recursively traverse only through elements arrays
Optionally filter during traversal
Ignore other properties like displayValue, geometry, etc.
Key concept: Many Speckle objects use elements[] to define parent-child relationships:
Collection objects have elements containing child objects
Group objects organize related objects in elements
Level hierarchies nest objects in elements
Advantages:
Faster than full traversal
Follows logical organization
Respects data structure intent
Avoids traversing geometry details
Copy
from specklepy.objects import Basefrom specklepy.objects.data_objects import DataObjectdef flatten_via_elements(root, filter_func=None): """ Flatten object hierarchy by following elements[] arrays. Collections and groups typically have an elements property containing child objects. """ results = [] def traverse(obj): if isinstance(obj, Base): # Check if this object matches filter if filter_func is None or filter_func(obj): results.append(obj) # Recursively traverse elements array if hasattr(obj, "elements") and isinstance(obj.elements, list): for element in obj.elements: traverse(element) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return results# Use it - get all objects in elements hierarchyall_objects = flatten_via_elements(obj)print(f"Total objects in elements tree: {len(all_objects)}")# Filter by type while traversingwalls = flatten_via_elements( obj, lambda o: isinstance(o, DataObject) and o.properties.get("category") == "Walls")print(f"Walls in elements tree: {len(walls)}")# Filter by propertyload_bearing = flatten_via_elements( obj, lambda o: isinstance(o, DataObject) and o.properties.get("loadBearing") == True)print(f"Load bearing elements: {len(load_bearing)}")
Purpose: Find only atomic, viewer-selectable objects by filtering for displayValue presence.When to use:
Need to count actual BIM elements (not containers or groups)
Want objects that appear as selectable items in the viewer
Building element lists for UI selection
Extracting objects that have visual representation
Need to match what users see in the 3D viewer
Key concept: Not all objects in the graph are “real” elements:
Containers (Collections, Groups) organize but aren’t selectable
Proxies (LevelProxy, ColorProxy) reference but aren’t rendered
Atomic objects have displayValue and ARE selectable in the viewer
Why displayValue matters: Objects with a displayValue property represent atomic, selectable items in the Speckle viewer. Each object with displayValue can be clicked, selected, and queried independently in the 3D view. The displayValue typically contains a list of Mesh objects that define the visual representation.
How it works:
Traverse the entire graph
Check if object has displayValue property
Verify displayValue is not None
Collect these objects
Optionally filter further by properties
Result: A list of objects that exactly matches what users can select in the viewer.
Copy
from specklepy.objects import Basefrom specklepy.objects.data_objects import DataObjectdef find_displayable_objects(root): """ Find all objects with displayValue property. These are atomic objects that can be clicked/selected in the Speckle viewer. """ displayable = [] def traverse(obj): if isinstance(obj, Base): # Check if this object has displayValue if hasattr(obj, "displayValue") and obj.displayValue is not None: displayable.append(obj) # Continue traversing to find nested displayable objects if hasattr(obj, "elements") and isinstance(obj.elements, list): for element in obj.elements: traverse(element) # Also traverse other properties for name in obj.get_member_names(): if not name.startswith("_") and name != "displayValue": value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return displayable# Find all atomic objectsatomic_objects = find_displayable_objects(obj)print(f"Found {len(atomic_objects)} displayable objects")# These are the objects that appear as selectable items in the viewerfor atomic_obj in atomic_objects[:5]: # First 5 if isinstance(atomic_obj, DataObject): category = atomic_obj.properties.get("category", "Unknown") print(f" {atomic_obj.name} ({category})") print(f" Display meshes: {len(atomic_obj.displayValue)}")
Purpose: Efficiently find atomic BIM objects by combining structural traversal with displayValue filtering.When to use:
Large BIM models where full traversal is slow
Data is organized in elements[] hierarchy
Only want viewer-selectable objects
Need to skip containers and organizational objects
Advantages:
Faster than full traversal (follows elements[] only)
More precise than elements[] alone (filters to atomic objects)
Gets you exactly what users see in the viewer
Skips geometry details and nested references
Key Optimization: Objects with displayValue are typically leaf nodes - they don’t have children with displayValues. This means you can stop traversing deeper once you find a displayValue, making traversal much more efficient.Strategy:
Traverse through elements[] arrays only (ignore other properties)
At each object, check for displayValue
Collect objects that have displayValue and STOP traversing deeper
Skip containers and organizational objects automatically
Copy
def find_displayable_in_elements(root): """ Traverse elements[] hierarchy and collect objects with displayValue. This gives you the atomic BIM objects that are viewer-selectable. """ displayable = [] def traverse(obj): if isinstance(obj, Base): # If it has displayValue, it's an atomic object (leaf node) if hasattr(obj, "displayValue") and obj.displayValue is not None: displayable.append(obj) return # STOP - leaf nodes rarely have children with displayValue # Container node - continue down elements hierarchy if hasattr(obj, "elements") and isinstance(obj.elements, list): for element in obj.elements: traverse(element) traverse(root) return displayable# Get all viewer-selectable BIM objectsselectable = find_displayable_in_elements(obj)print(f"Selectable objects: {len(selectable)}")# Analyze by categoryfrom collections import defaultdictby_category = defaultdict(int)for obj in selectable: if isinstance(obj, DataObject): category = obj.properties.get("category", "Other") by_category[category] += 1print("\nBy category:")for category, count in sorted(by_category.items()): print(f" {category}: {count}")
Purpose: Organize flat lists of objects into groups based on shared property values.When to use:
Need to analyze objects by category, type, or level
Building summaries (“count by type”)
Preparing data for reports or charts
Want to process each group separately
Need to find relationships between objects
Common groupings:
By BIM category (Walls, Columns, Floors)
By level/storey (“Level 1”, “Level 2”)
By family or type
By material
By any property value
How it works:
First, flatten objects (Pattern 3)
Iterate through flat list
Extract grouping property from each object
Build dictionary with property value as key
Append objects to appropriate group
Result: Dictionary where keys are property values and values are lists of objects.
Copy
from collections import defaultdictfrom specklepy.objects import Basedef group_by_property(root, property_name, type_filter=None): """Group objects by a property value.""" groups = defaultdict(list) def traverse(obj): if isinstance(obj, Base): # Apply type filter if specified if type_filter and type_filter not in obj.speckle_type: # Still traverse children for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) return # Try direct property if hasattr(obj, property_name): key = getattr(obj, property_name) groups[key].append(obj) # Try properties dict elif hasattr(obj, "properties") and isinstance(obj.properties, dict): if property_name in obj.properties: key = obj.properties[property_name] groups[key].append(obj) # Traverse children for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return dict(groups)# Use itby_material = group_by_property(obj, "Material")for material, elements in by_material.items(): print(f"{material}: {len(elements)} elements")by_level = group_by_property(obj, "level", type_filter="Revit")for level, elements in by_level.items(): print(f"{level}: {len(elements)} elements")
import pandas as pdfrom specklepy.objects import Basedef extract_to_dataframe(root, type_filter=None, properties=None): """Extract objects to pandas DataFrame.""" rows = [] def traverse(obj): if isinstance(obj, Base): # Apply type filter if type_filter and type_filter not in obj.speckle_type: # Still traverse children for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) return # Build row row = { "id": obj.id, "type": obj.speckle_type, } # Add specified properties if properties: for prop in properties: # Direct property if hasattr(obj, prop): row[prop] = getattr(obj, prop) # Properties dict elif hasattr(obj, "properties") and isinstance(obj.properties, dict): row[prop] = obj.properties.get(prop) else: row[prop] = None else: # Include all direct properties (not nested) for name in obj.get_typed_member_names(): value = getattr(obj, name, None) if not isinstance(value, (Base, list, dict)): row[name] = value # Include properties dict if hasattr(obj, "properties") and isinstance(obj.properties, dict): for key, value in obj.properties.items(): if not isinstance(value, (Base, list, dict)): row[key] = value rows.append(row) # Traverse children for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) return pd.DataFrame(rows)# Use it - filter by DataObject with specific categorydf = extract_to_dataframe(obj, type_filter="Objects.Data.DataObject")# Then filter by propertieswalls_df = df[df["properties"].apply(lambda p: p.get("category") == "Walls" if isinstance(p, dict) else False)]print(walls_df.head())# With specific propertiesdf = extract_to_dataframe( obj, type_filter="Wall", properties=["family", "type", "Volume", "Area", "Material"])print(df[["family", "type", "Volume", "Area"]].head())
from collections import Counterfrom specklepy.objects import Basedef count_by_type(root): """Count objects by speckle_type.""" counts = Counter() def traverse(obj): if isinstance(obj, Base): counts[obj.speckle_type] += 1 for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) elif isinstance(obj, dict): for value in obj.values(): traverse(value) traverse(root) return counts# Use itcounts = count_by_type(obj)print("Object counts:")for speckle_type, count in counts.most_common(): print(f" {speckle_type}: {count}")# Summary by categorygeometry_count = sum(count for type_name, count in counts.items() if "Geometry" in type_name)revit_count = sum(count for type_name, count in counts.items() if "Revit" in type_name)print(f"\nGeometry objects: {geometry_count}")print(f"Revit objects: {revit_count}")
from specklepy.objects import Basedef safe_traverse(obj, callback, max_depth=10, _depth=0): """Safely traverse unknown structures with depth limit.""" if _depth > max_depth: return try: if isinstance(obj, Base): # Call callback with object callback(obj, _depth) # Traverse members safely for name in obj.get_member_names(): if name.startswith("_"): continue try: value = getattr(obj, name, None) safe_traverse(value, callback, max_depth, _depth + 1) except Exception as e: print(f"Warning: Error accessing {name}: {e}") elif isinstance(obj, list): for i, item in enumerate(obj): try: safe_traverse(item, callback, max_depth, _depth + 1) except Exception as e: print(f"Warning: Error at list index {i}: {e}") elif isinstance(obj, dict): for key, value in obj.items(): try: safe_traverse(value, callback, max_depth, _depth + 1) except Exception as e: print(f"Warning: Error at dict key {key}: {e}") except Exception as e: print(f"Warning: Traversal error at depth {_depth}: {e}")# Use itfound_objects = []def collect_meshes(obj, depth): if "Mesh" in obj.speckle_type: found_objects.append(obj)safe_traverse(obj, collect_meshes)print(f"Safely found {len(found_objects)} meshes")
from specklepy.objects import Baseclass CachedTraverser: """Traverser with result caching.""" def __init__(self): self.cache = {} def find_by_type(self, root, speckle_type): """Find objects with caching.""" cache_key = (id(root), speckle_type) if cache_key in self.cache: return self.cache[cache_key] results = [] def traverse(obj): if isinstance(obj, Base): if obj.speckle_type == speckle_type: results.append(obj) for name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: traverse(item) traverse(root) self.cache[cache_key] = results return results# Use ittraverser = CachedTraverser()# First call - does traversalmeshes = traverser.find_by_type(obj, "Objects.Geometry.Mesh")# Second call - uses cachemeshes_again = traverser.find_by_type(obj, "Objects.Geometry.Mesh") # Fast!
from specklepy.objects import Basedef find_first(root, predicate): """Find first object matching predicate (stops early).""" result = [None] # Use list to allow modification in nested function def traverse(obj): if result[0] is not None: return # Already found if isinstance(obj, Base): if predicate(obj): result[0] = obj return for name in obj.get_member_names(): if result[0] is not None: return if not name.startswith("_"): value = getattr(obj, name, None) traverse(value) elif isinstance(obj, list): for item in obj: if result[0] is not None: return traverse(item) traverse(root) return result[0]# Use itfirst_wall = find_first(obj, lambda x: "Wall" in x.speckle_type)if first_wall: print(f"Found wall: {first_wall.speckle_type}")
# Goodif hasattr(obj, "displayValue"): mesh = obj.displayValue# Bad - can crashmesh = obj.displayValue
Skip private members
Don’t traverse _ prefixed properties:
Copy
# Goodfor name in obj.get_member_names(): if not name.startswith("_"): value = getattr(obj, name)# Bad - includes internalsfor name in dir(obj): value = getattr(obj, name)
Handle both lists and single values
Properties can be lists or single objects:
Copy
# Gooddisplay = obj.displayValuemeshes = display if isinstance(display, list) else [display]# Bad - assumes always listfor mesh in obj.displayValue: # Crashes if not list pass
Use type filters to reduce traversal
Limit traversal scope when possible:
Copy
# Good - focused property search (v3 pattern)walls = find_by_property(obj, "category", "Walls")# Less good - searches everything then filtersall_objects = find_all(obj, lambda x: True)walls = [o for o in all_objects if hasattr(o, "properties") and o.properties.get("category") == "Walls"]