TL;DR: The Speckle Collection
class is a structured container for BIM objects. You can:
- Traverse it recursively to inspect structure.
- Extract everything at once (flatten it).
- Use a generator (preferred) to iterate efficiently.
When you use operations.receive()
, you often return a Collection, a special Speckle class that acts like a container for multiple objects. Think of it as a structured list of elements, each of which can be another Collection
or a specific BIM object (like walls, doors, etc.).
Since a Collection
holds nested data, you can handle it in two main ways:
Traversing the Collection (Recursive Approach)
If you want to walk through the structure and inspect each object, including any nested Collections:
def traverse_collection(collection):
"""Recursively traverses a Collection and prints object types"""
print(f"Traversing collection with {len(collection.["elements"])} children")
for element in collection["elements"]:
print(f"Child object type: {type(element)}")
if isinstance(child, Collection): # If nested, keep going
traverse_collection(element)
else:
print(f"Object properties: {element.__dict__}") # Inspect properties
Flattening the Collection (Extract All Objects at Once)
If you’re mainly interested in getting all objects in one list, regardless of hierarchy:
def flatten_collection(collection):
"""Returns a flat list of all objects within a Collection"""
objects = []
def collect_items(coll):
for child in coll["elements"]:
if isinstance(element, Collection):
collect_items(element) # Dive deeper
else:
objects.append(element) # Store object
collect_items(collection)
return objects
Then use this as so:
all_objects = flatten_collection(obj_data)
print(f"Total extracted objects: {len(all_objects)}")
As you are a rusty Pythonista, that should suffice, but my preferred pattern for the flatten function is to work with a Generator
Instead of collecting all objects in a list, we can yield each object on demand:
def yield_collection_items(collection):
"""Generator that lazily iterates through all objects in a Collection"""
for element in collection["elements"]:
if isinstance(element, Collection):
yield from yield_collection_items(element) # Recursively yield deeper items
else:
yield element # Yield the object itself
and then
for obj in yield_collection_items(obj_data):
print(f"Found object: {type(obj)}, Properties: {obj.__dict__}")
Like C#'s LINQ, generators delay execution until needed, but they’re one-time-use. Convert to a list if you need to reuse the results: list(generator)
… Similar to LINQ, this allows you not to store thousands of objects in a list, and if you are looking for something specific, you can exit the process when you find it.
We introduced Collections a while back as a change to how Revit data was stored in Speckle: BREAKING CHANGE: Introducing Collections Class for Predictable Data Hierarchies: Testing the Revit Connector .
A typical Speckle use case is querying Revit data. Since Revit models are stored as Collections
, these patterns make filtering elements like walls, doors, and windows incredibly efficient.
If you need to extract all walls from a Revit model,
def yield_revit_elements(collection, category=None):
"""Generator to yield elements of a specific Revit category."""
for element in collection["elements"]:
if isinstance(element, Collection):
yield from yield_revit_elements(element, category) # Dive into nested collections
elif category is None or getattr(element, "speckle_type", "").endswith(category):
yield element # Yield only objects of the specified category
# Example: Extracting all "Wall" objects
walls = yield_revit_elements(obj_data, category="Revit.Wall")
for wall in walls:
print(f"Wall ID: {wall.id}, Level: {wall.level}")
This stupid-simple example obviously can work with Walls, Doors, Windows, etc. but the pattern stops once you find what you need.
When to Use What?
Use Case |
Best Approach |
Querying specific elements dynamically |
Generator (yield_revit_elements ) |
Processing all elements at once |
Flat list (flatten_revit_elements ) |
Exploring nested structure |
Recursive traversal |