Now that you understand the data model, here are practical patterns for traversing and processing Speckle models. These recipes assume you have a Root Collection object.
Recipe 1: Traverse Collections and Objects
The most common pattern: walk the collection hierarchy to find all objects.
def traverse_collections(collection):
"""Recursively traverse collections and yield all objects."""
# Process objects in this collection
if hasattr(collection, 'elements'):
for obj in collection.elements:
yield obj
# Recurse into nested collections
if hasattr(collection, 'elements'):
for item in collection.elements:
if is_collection(item):
yield from traverse_collections(item)
def is_collection(obj):
"""Check if an object is a collection."""
return obj.speckle_type == "Objects.Organization.Collection"
# Usage
root = get_root_collection()
for obj in traverse_collections(root):
if is_data_object(obj):
process_object(obj)
Recipe 2: Find All DataObjects
Filter for DataObjects specifically:
def find_all_data_objects(collection):
"""Find all DataObjects in the model."""
data_objects = []
def traverse(coll):
if hasattr(coll, 'elements'):
for item in coll.elements:
if is_data_object(item):
data_objects.append(item)
elif is_collection(item):
traverse(item)
traverse(collection)
return data_objects
def is_data_object(obj):
"""Check if an object is a DataObject."""
return obj.speckle_type.startswith("Objects.Data.DataObject")
# Usage
root = get_root_collection()
data_objects = find_all_data_objects(root)
Recipe 3: Process Geometry from displayValue
Extract and process geometry from DataObjects:
def process_geometry(obj):
"""Process all geometry in a DataObject's displayValue."""
if not hasattr(obj, 'displayValue') or not obj.displayValue:
return
for geom in obj.displayValue:
geom_type = geom.speckle_type
if "Mesh" in geom_type:
process_mesh(geom)
elif "Line" in geom_type or "Curve" in geom_type:
process_curve(geom)
elif "Point" in geom_type:
process_point(geom)
# ... handle other geometry types
# Usage
for obj in find_all_data_objects(root):
process_geometry(obj)
Recipe 4: Resolve Proxy References
Find which objects use a specific resource:
def find_objects_using_resource(root, proxy_type, resource_name):
"""Find all objects that use a specific resource via proxy."""
# Find the proxy
proxy = find_proxy_by_name(root, proxy_type, resource_name)
if not proxy:
return []
# Get referenced object IDs
referenced_ids = proxy.objects if hasattr(proxy, 'objects') else []
# Find objects by applicationId
objects = []
for obj in traverse_all_objects(root):
if hasattr(obj, 'applicationId') and obj.applicationId in referenced_ids:
objects.append(obj)
return objects
def find_proxy_by_name(root, proxy_type, name):
"""Find a proxy by type and name."""
proxies = get_proxies(root, proxy_type)
for proxy in proxies:
if hasattr(proxy, 'name') and proxy.name == name:
return proxy
return None
def get_proxies(root, proxy_type):
"""Get all proxies of a specific type from root collection."""
if not hasattr(root, 'elements'):
return []
proxies = []
for item in root.elements:
if hasattr(item, 'speckle_type') and proxy_type in item.speckle_type:
proxies.append(item)
return proxies
# Usage: Find all objects using "Concrete" material
concrete_objects = find_objects_using_resource(
root,
"RenderMaterial",
"Concrete"
)
Recipe 5: Find Resources for an Object
Find which resources (materials, groups, etc.) an object uses:
def find_resources_for_object(root, obj):
"""Find all proxies that reference this object."""
if not hasattr(obj, 'applicationId'):
return []
app_id = obj.applicationId
resources = []
# Check all proxies
for proxy in get_all_proxies(root):
if hasattr(proxy, 'objects') and app_id in proxy.objects:
resources.append(proxy)
return resources
def get_all_proxies(root):
"""Get all proxies from root collection."""
proxies = []
if hasattr(root, 'elements'):
for item in root.elements:
if is_proxy(item):
proxies.append(item)
return proxies
def is_proxy(obj):
"""Check if an object is a proxy."""
proxy_types = [
"RenderMaterial",
"Level",
"Group",
"Definition",
"Color"
]
return any(pt in obj.speckle_type for pt in proxy_types)
# Usage
obj = get_object("wall-123")
resources = find_resources_for_object(root, obj)
for resource in resources:
print(f"{resource.speckle_type}: {resource.name}")
Recipe 6: Resolve Instance Definitions
Get geometry for instance objects:
def resolve_instance_geometry(root, instance):
"""Get the geometry for an instance by resolving its definition."""
if not hasattr(instance, 'definitionId'):
return None
# Find the definition proxy
definition_proxy = find_proxy_by_application_id(
root,
"Definition",
instance.definitionId
)
if not definition_proxy:
return None
# Get geometry from definition value
if hasattr(definition_proxy, 'value') and hasattr(definition_proxy.value, 'displayValue'):
return definition_proxy.value.displayValue
return None
def find_proxy_by_application_id(root, proxy_type, app_id):
"""Find a proxy by type and applicationId."""
proxies = get_proxies(root, proxy_type)
for proxy in proxies:
if hasattr(proxy, 'applicationId') and proxy.applicationId == app_id:
return proxy
return None
# Usage
for obj in traverse_all_objects(root):
if "Instance" in obj.speckle_type:
geometry = resolve_instance_geometry(root, obj)
if geometry:
process_geometry_list(geometry)
Recipe 7: Build Object-Resource Map
Create a lookup map for efficient access:
def build_resource_map(root):
"""Build maps for efficient object-resource lookups."""
# Map: object applicationId -> list of resources
object_to_resources = {}
# Map: resource name -> list of object applicationIds
resource_to_objects = {}
# Process all proxies
for proxy in get_all_proxies(root):
resource_name = getattr(proxy, 'name', 'unknown')
resource_type = proxy.speckle_type
if hasattr(proxy, 'objects'):
for app_id in proxy.objects:
# Build object -> resources map
if app_id not in object_to_resources:
object_to_resources[app_id] = []
object_to_resources[app_id].append(proxy)
# Build resource -> objects map
key = f"{resource_type}:{resource_name}"
if key not in resource_to_objects:
resource_to_objects[key] = []
resource_to_objects[key].append(app_id)
return object_to_resources, resource_to_objects
# Usage
obj_to_res, res_to_obj = build_resource_map(root)
# Find resources for an object
wall_resources = obj_to_res.get("wall-123", [])
# Find objects using a resource
concrete_walls = res_to_obj.get("RenderMaterial:Concrete", [])
Recipe 8: Filter by Properties
Find objects matching specific property criteria:
def find_objects_by_property(root, property_path, value):
"""Find objects with a specific property value."""
matches = []
for obj in find_all_data_objects(root):
if not hasattr(obj, 'properties'):
continue
# Handle nested property paths like "Type Parameters.Width"
prop_value = get_nested_property(obj.properties, property_path)
if prop_value == value:
matches.append(obj)
return matches
def get_nested_property(props, path):
"""Get a nested property value using dot notation."""
keys = path.split('.')
current = props
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return None
return current
# Usage: Find all walls with width = 0.2
walls_200mm = find_objects_by_property(
root,
"Type Parameters.Width",
0.2
)
Recipe 9: Process by Collection Path
Process objects organized by their collection hierarchy:
def process_by_collection_path(collection, path=""):
"""Process objects with their collection path context."""
current_path = f"{path}/{collection.name}" if hasattr(collection, 'name') else path
if hasattr(collection, 'elements'):
for item in collection.elements:
if is_data_object(item):
# Process with path context
process_object_with_path(item, current_path)
elif is_collection(item):
process_by_collection_path(item, current_path)
# Usage: Understand object context
process_by_collection_path(root)
# Objects processed with paths like:
# "/Project/Level 1/Walls/Basic Wall"
Gather statistics about a model:
def get_model_statistics(root):
"""Get statistics about a model."""
stats = {
'total_objects': 0,
'data_objects': 0,
'geometry_objects': 0,
'instances': 0,
'collections': 0,
'proxies': 0,
'geometry_types': {},
'object_types': {}
}
def traverse(coll):
if hasattr(coll, 'elements'):
for item in coll.elements:
if is_collection(item):
stats['collections'] += 1
traverse(item)
elif is_data_object(item):
stats['data_objects'] += 1
stats['total_objects'] += 1
# Count object type
obj_type = item.speckle_type
stats['object_types'][obj_type] = stats['object_types'].get(obj_type, 0) + 1
# Count geometry
if hasattr(item, 'displayValue') and item.displayValue:
for geom in item.displayValue:
stats['geometry_objects'] += 1
geom_type = geom.speckle_type
stats['geometry_types'][geom_type] = stats['geometry_types'].get(geom_type, 0) + 1
elif "Instance" in item.speckle_type:
stats['instances'] += 1
stats['total_objects'] += 1
elif is_proxy(item):
stats['proxies'] += 1
traverse(root)
return stats
# Usage
stats = get_model_statistics(root)
print(f"Total objects: {stats['total_objects']}")
print(f"Geometry types: {stats['geometry_types']}")
Common Patterns Summary
- Always check for attributes - Use
hasattr() before accessing properties
- Handle empty arrays -
displayValue and objects may be empty
- Use applicationId for references - Proxies reference by
applicationId, not id
- Recurse collections - Collections can be nested to any depth
- Check speckle_type - Use
speckle_type to identify object types
- Proxies are at root - Always look for proxies in Root Collection
- Geometry in displayValue - All DataObject geometry is in
displayValue
Next Steps
Last modified on November 18, 2025