When working with BIM models in Speckle, you’ll encounter objects whose geometry is represented through displayValue properties. This guide shows you how to extract this geometry, including how to handle instance definitions that require resolving applicationId references.What you’ll learn:
How to extract meshes from displayValue properties
How to build an applicationId index for reference lookups
How to resolve InstanceDefinition references to get actual geometry
After receiving a model version with operations.receive(), objects have a displayValue property containing visualization geometry. This can be:
Direct meshes - The geometry is right there
Instance references - References to InstanceDefinition objects by applicationId
The second case requires building an applicationId index to resolve the references.
# Case 1: Direct meshobj.displayValue = [mesh1, mesh2, ...] # Meshes are directly accessible# Case 2: Instance referencesobj.displayValue = [ { "applicationId": "instance_def_123", # ... other instance properties # Need to look this up in instanceDefinitionProxies }]# InstanceDefinition itself has no 'value' property# It has 'objects' which are applicationIds that need resolution
from specklepy.objects.geometry import Meshdef extract_direct_meshes(obj): """ Extract meshes that are directly in displayValue. Returns: List of Mesh objects """ if not hasattr(obj, "displayValue"): return [] display = obj.displayValue meshes = [] # Normalize to list display_list = [display] if not isinstance(display, list) else display for item in display_list: # Check if it's a Mesh object if isinstance(item, Mesh): meshes.append(item) return meshes# Usagefor obj in objects_with_display: meshes = extract_direct_meshes(obj) for mesh in meshes: if hasattr(mesh, "vertices"): print(f"Mesh: {len(mesh.vertices) // 3} vertices")
To resolve instance references, build an index mapping applicationIds to objects:
from specklepy.objects.graph_traversal.traversal import GraphTraversaldef build_applicationid_index(root): """ Build a dictionary mapping applicationId to objects. Why this is needed: InstanceDefinition objects reference other objects by applicationId, not by direct embedding. Returns: Dictionary: {applicationId: object} """ index = {} traversal = GraphTraversal([]) for context in traversal.traverse(root): obj = context.current # Index any object with an applicationId if hasattr(obj, "applicationId") and obj.applicationId: index[obj.applicationId] = obj return index# Build the index onceapp_id_index = build_applicationid_index(version)print(f"Indexed {len(app_id_index)} objects")# Now you can quickly look up any objectsome_obj = app_id_index.get("some-application-id")if some_obj: print(f"Found: {getattr(some_obj, 'name', 'unnamed')}")
Why build an index? Without it, you’d traverse the entire tree for each lookup. With an index, lookups are instant (O(1) vs O(n)).
Instance definitions enable geometry reuse. The definition contains references to geometry objects (by applicationId), and each instance includes a transformation matrix.
# Root has instanceDefinitionProxiesversion.instanceDefinitionProxies = [ { "id": "door_def_123", "name": "Door Type A", "objects": [ "mesh_app_id_1", "mesh_app_id_2" ] # NOTE: No 'value' property like LevelProxy or ColorProxy! # 'objects' are applicationIds that need to be resolved }]# Objects reference the definitionobj.displayValue = [ { "applicationId": "door_def_123", # References the definition "transform": [...] # 4x4 transformation matrix }]
Extract instance definitions and resolve their geometry references:
def extract_instance_definitions(root, app_id_index): """ Extract instance definitions and resolve their geometry. Args: root: Model version object app_id_index: applicationId index from build_applicationid_index() Returns: Dictionary: {applicationId: definition_info} """ if not hasattr(root, "instanceDefinitionProxies"): return {} definitions = {} for proxy in root.instanceDefinitionProxies: # Index by applicationId (not id) - instances reference by applicationId def_app_id = getattr(proxy, "applicationId", None) if def_app_id: # Resolve geometry references using the index geometry = [] if hasattr(proxy, "objects"): for app_id in proxy.objects: obj = app_id_index.get(app_id) if obj: geometry.append(obj) definitions[def_app_id] = { "name": getattr(proxy, "name", "unnamed"), "geometry": geometry, "proxy": proxy } return definitions# Usagedefinitions = extract_instance_definitions(version_root_object, app_id_index)for def_app_id, def_info in definitions.items(): print(f"{def_info['name']}: {len(def_info['geometry'])} objects")# Args:# root: Version object# app_id_index: applicationId index# definitions: Instance definitions from extract_instance_definitions()# Returns:# List of dictionaries with mesh and metadata
Now you can resolve instance references when extracting display values:
from specklepy.objects.geometry import Meshdef extract_all_display_meshes(root, app_id_index, definitions): """ Extract all meshes from display values, resolving instance references. Args: root: Version object app_id_index: applicationId index definitions: Instance definitions from extract_instance_definitions() Returns: List of dictionaries with mesh and metadata """ results = [] traversal = GraphTraversal([]) for context in traversal.traverse(root): obj = context.current if not hasattr(obj, "displayValue"): continue display = obj.displayValue display_list = [display] if not isinstance(display, list) else display for item in display_list: # Case 1: Direct mesh if isinstance(item, Mesh): results.append({ "mesh": item, "source": obj, "is_instance": False }) # Case 2: Instance reference (by definitionId which is applicationId) elif hasattr(item, "definitionId"): def_app_id = item.definitionId if def_app_id in definitions: # Get the geometry from the definition def_info = definitions[def_app_id] for geom in def_info["geometry"]: if isinstance(geom, Mesh): results.append({ "mesh": geom, "source": obj, "is_instance": True, "definition": def_info, "transform": getattr(item, "transform", None) }) return results
Instance references include transformation matrices that position, rotate, and scale the geometry:
def get_transformed_instances(root, app_id_index): """ Extract instance meshes with their transformations. Note: This function identifies which meshes need transformation. Actual transformation math is shown for reference but may require additional libraries (numpy) for production use. Returns: List of dictionaries with mesh and transform info """ definitions = extract_instance_definitions(root, app_id_index) instances = [] traversal = GraphTraversal([]) for context in traversal.traverse(root): obj = context.current if not hasattr(obj, "displayValue"): continue display = obj.displayValue display_list = [display] if not isinstance(display, list) else display for item in display_list: # Look for instance references with transforms if hasattr(item, "applicationId") and hasattr(item, "transform"): def_id = item.applicationId transform = item.transform if def_id in definitions: def_info = definitions[def_id] for geom in def_info["geometry"]: instances.append({ "geometry": geom, "definition_name": def_info["name"], "transform": transform, # 4x4 matrix as flat list "source_object": obj }) return instances# Usageinstances = get_transformed_instances(version, app_id_index)print(f"Found {len(instances)} instance uses")
Transform structure: The transform is a 4x4 transformation matrix stored as a flat list of 16 numbers (column-major order). This represents position, rotation, and scale.
Building an applicationId index traverses the entire object tree. Do this once at the start:
# ✅ Good: Build once, use many timesapp_id_index = build_applicationid_index(version)definitions = extract_instance_definitions(version, app_id_index)# Then use the index for all lookupsfor app_id in some_list_of_ids: obj = app_id_index.get(app_id)# ❌ Bad: Don't rebuild in loops
Always use .get() when looking up by applicationId:
# ❌ This can crashobj = app_id_index[app_id] # KeyError if not found# ✅ This is safeobj = app_id_index.get(app_id)if obj: # Process the object print(getattr(obj, "name", "unnamed"))
Why can't I find geometry in an instance definition?
Problem: Instance definition’s objects list has applicationIds but you can’t find the geometry.Why this happens: The geometry objects might not have been indexed, or the applicationIds are incorrect.Solution: Verify your index contains the referenced objects:
def debug_instance_definition(proxy, app_id_index): """Debug helper to see what's in an instance definition.""" print(f"Definition: {getattr(proxy, 'name', 'unnamed')}") print(f" ID: {getattr(proxy, 'id', 'none')}") print(f" References {len(proxy.objects)} objects:") for app_id in proxy.objects: obj = app_id_index.get(app_id) if obj: obj_type = getattr(obj, "speckle_type", "unknown") print(f" ✓ {app_id}: {obj_type}") else: print(f" ✗ {app_id}: NOT FOUND IN INDEX")# Use it to debugfor proxy in version.instanceDefinitionProxies: debug_instance_definition(proxy, app_id_index)
How do I handle mixed display values (meshes + instances)?
Problem: Some objects have direct meshes, others have instance references in the same displayValue list.Why this happens: Complex objects can combine directly modeled geometry with instanced components.Solution: Check the type of each item in displayValue:
from specklepy.objects.geometry import Meshdef extract_mixed_display(obj, definitions): """Handle displayValue with both meshes and instances.""" if not hasattr(obj, "displayValue"): return [] meshes = [] display_list = [obj.displayValue] if not isinstance(obj.displayValue, list) else obj.displayValue for item in display_list: # Type 1: Direct mesh if isinstance(item, Mesh): meshes.append(item) # Type 2: Instance reference elif hasattr(item, "applicationId"): def_id = item.applicationId if def_id in definitions: # Get meshes from the definition for geom in definitions[def_id]["geometry"]: if isinstance(geom, Mesh): meshes.append(geom) return meshes
Why are there no instance definitions in my version?
Problem:version.instanceDefinitionProxies doesn’t exist or is empty.Why this happens: Not all models use instancing—depends on the source application
and how the model was created.Solution: Always check before accessing, and handle both cases:
# Check if instances are usedif hasattr(version, "instanceDefinitionProxies") and version.instanceDefinitionProxies: print(f"Found {len(version.instanceDefinitionProxies)} instance definitions") definitions = extract_instance_definitions(version, app_id_index)else: print("No instance definitions - all geometry is direct") definitions = {}# Your code works with or without instancesfor obj in objects_with_display: # This handles both cases meshes = extract_direct_meshes(obj) # Gets direct meshes # If needed, also check for instances using definitions dict
How do I know if an object uses instances?
Problem: Can’t tell if displayValue contains meshes or instance references.Why this happens: Both are in the same displayValue property.Solution: Check for the presence of applicationId:
def is_instance_reference(item): """Check if displayValue item is an instance reference.""" # Instance references have applicationId # Direct meshes are Mesh objects from specklepy.objects.geometry import Mesh if isinstance(item, Mesh): return False if hasattr(item, "applicationId"): return True return False# Use itfor item in obj.displayValue: if is_instance_reference(item): print(f"Instance: {item.applicationId}") else: print("Direct mesh")