Skip to main content

What You’ll Learn

By the end of this guide, you’ll understand:
  • ✅ How to recursively traverse nested object structures
  • ✅ How to filter objects by property values while traversing
  • ✅ How to extract geometry from BIM objects
  • ✅ How to handle hierarchical collections and preserve context

Prerequisites

Before starting this guide, you should:
This guide builds on traversal fundamentals from the Core Concepts. We focus on practical patterns for finding and extracting data from real-world Speckle projects.

How do I traverse nested Speckle data?

Speckle data is often deeply nested - buildings contain levels, levels contain rooms, rooms contain elements. You need to visit every object in the tree to process or analyze them.
Terminology Note: When we use terms like “levels”, “rooms”, or reference property names in examples, these are for illustrative purposes. Real BIM data from connectors (Revit, Rhino, etc.) uses proxy structures - for example, Revit levels are represented as LevelProxy objects in dedicated collections, not as direct hierarchy. See the Proxification guide and BIM Data Patterns for actual BIM data structures.
Basic manual traversal - here’s the fundamental pattern:
SDK’s built-in traversal utility ⭐ - SpecklePy provides GraphTraversal for robust traversal:
The SDK provides three key components for traversal:
  1. GraphTraversal - The main traversal engine
    • Creates the traversal instance: GraphTraversal([rules])
    • Executes traversal: .traverse(root) returns an iterator
    • Pass [] for default behavior (traverse everything)
  2. TraversalContext - Information about each visited object
    • .current - The current Base object being visited
    • .member_name - Property name from parent (e.g., “elements”, “displayValue”)
    • .parent - Parent TraversalContext (or None if root)
  3. TraversalRule - Optional rules to control behavior
    • _conditions - When does this rule apply? (list of predicates)
    • _members_to_traverse - What properties to traverse? (function returning list)
    • _should_return_to_output - Include objects in results? (boolean)
In the examples below, look for comments showing where each component is used.:
Understanding the traversal flow:
Why use GraphTraversal?:
  • ✅ Handles all edge cases (dicts, lists, nested Base objects)
  • ✅ Provides context (parent object, property name)
  • ✅ Supports custom rules for filtering during traversal
  • ✅ Memory efficient (uses iterators, not lists)
  • ✅ Battle-tested in production SDK code
The traversal pattern has three parts:
  1. Base case: Check if object is a Base instance
  2. Process: Do something with the current object
  3. Recurse: Visit all child objects via get_member_names()
Why get_member_names()? It returns all property names on the object, already filters out private members (_) and methods, and works with both typed and dynamic properties.
Collecting objects during traversal using GraphTraversal: Instead of just visiting objects, you often want to collect them into a list. Here are two approaches - collecting all at once or processing as you go:
Understanding TraversalContext: Each context provides information about where you are in the object tree - the current object, what property it came from, and its parent:
Using TraversalRule to control traversal: Rules give you fine-grained control over what gets traversed and returned. They’re useful when you want to skip certain properties or limit results during traversal:
Simpler approach - filter after traversal: If you just need to skip certain objects, filtering after traversal is often clearer than writing a custom rule:
Comprehensive example - all three components together:
Output example:
Don’t process the same property twice! When handling elements arrays specifically, make sure you don’t also process them in the general get_member_names() loop:

How do I find specific objects?

You need to find all objects matching certain criteria - for example, all walls, all objects on a specific level, or all elements with a particular property value. Filter while traversing by checking conditions and collecting matches:
About “category” property: When we reference properties["category"] in examples, this demonstrates the pattern. Real BIM data may organize categories differently - Revit data, for instance, uses both properties["category"] on individual objects AND category-based proxy collections. See BIM Data Patterns for production patterns.
Multiple filter criteria: When you need to match objects on several properties at once (e.g., walls that are also concrete), pass multiple key-value pairs to check all conditions:
Using custom filter functions: For complex filtering logic (numeric comparisons, nested properties, combined conditions), pass a custom function that returns True for objects you want to keep:
The filtering pattern: (1) Traverse the entire tree recursively, (2) Check each object against your criteria, (3) Collect matching objects in a results list, (4) Return the accumulated results. This pattern works for any filtering criteria - category, property values, object types, etc.

How do I extract geometry from BIM objects?

BIM objects from connectors (Revit, Rhino, etc.) contain geometry in the displayValue property. You need to extract these meshes for visualization or analysis. Check for displayValue and collect geometry objects:
Extracting geometry with metadata: Often you need to know which object each mesh came from. Use TraversalContext to track source objects and their properties:
Handling different geometry types: Not all geometry is meshes - you might also encounter points, lines, and polylines. Check for all geometry types you’re interested in:
Don’t recurse into displayValue! The displayValue property often contains the same geometry as the object, leading to duplicates:

How do I work with hierarchical collections?

BIM data often has hierarchical structures: Building → Levels → Rooms → Elements. You need to process these hierarchies while maintaining context about where each object came from.
Real BIM structures use proxies! When working with actual connector data (Revit, Rhino, ArchiCAD), “Levels” aren’t nested hierarchies - they’re represented as LevelProxy collections that reference objects by ID. The examples here show conceptual hierarchies for learning. For production code with real BIM data, see: - Proxification guide - Understanding proxy structures - BIM Data Patterns - Working with real connector data
Track hierarchy levels during traversal:
Output:
Collecting objects by level: To analyze your data by depth in the tree (e.g., root objects vs. deeply nested objects), organize objects by their hierarchy level:
Flattening vs. preserving hierarchy: Choose between flattening (all objects in one list) or preserving structure (nested dictionaries). Flatten when you just need to process objects; preserve when hierarchy matters:

Practical Examples

Example 1: Find All Walls on a Specific Level

Example 2: Extract Geometry by Category

Example 3: Build a Category Summary Report

Learn More

Core Concepts: Guides: Next Steps:

Next Steps

Now that you can find and extract data, you’re ready to:
  1. Optimize for performance - Build indexes for large datasets
  2. Handle complexity - Work with detached objects and references
  3. Extract Revit parameters - Access nested BIM metadata efficiently
Continue to Advanced: Performance and Complex Patterns
Last modified on July 18, 2026