Great that you are making progress and learning some of the idiosyncrasies of Speckle.
Aside from mesh definition, Speckle also distinguishes itself from other 3D environments in several ways, particularly in how it handles both Transforms, Instances and its underlying data structure. While other 3D environments might support transforms at every node, Speckle employs a different strategy that prioritizes data interoperability, efficiency, and collaboration.
Directed Acyclic Graph (DAG) Structure
Speckle uses a directed acyclic graph (DAG) to manage its objects. This structure ensures that each object points to other objects in a hierarchical manner, with no cycles. The benefits of a DAG include:
• Efficiency: By avoiding cyclic dependencies, Speckle ensures efficient data storage and retrieval.
• Non-redundancy: Objects can be reused across the graph without duplication, reducing data redundancy.
• Clarity: The hierarchical nature of a DAG makes it easier to understand the relationships between different objects.
Definitions and Instances
In Speckle, reusable components are managed through Definition and Instance objects:
• `Definition`: Acts as a template for reusable components, such as windows or doors, that appear multiple times in your model.
• `Instance`: Represents each occurrence of a defined component, including a transformation matrix to specify its position, rotation, and scale.
This approach contrasts with other 3D environments where transformations might be applied at every node, potentially leading to complex and less efficient hierarchies.
Objects, Custom Objects and Display Values
For arguments sake let’s say all Objects in Speckle can include a displayValue
property, which is a list of meshes* representing the visual aspect of the object. This allows for flexible and detailed representation of components without the need for transformations at every level.
Collections and Hierarchical Organization
Speckle uses Collection
objects to group related elements, which can include other collections, custom objects, and in turn be geometric definitions.
This hierarchical organization supports:
• Modular Design: By grouping related components, Speckle encourages a modular approach to model design.
• Reusability: Components defined once can be reused across different parts of the model, maintaining consistency and reducing duplication.
Example: Building Model with Reusable Assemblies
To illustrate how Speckle’s approach works in practice, let’s consider creating a building model with reusable window instances:
1. Define the Mesh for a Window: Create the geometry for a window using a Mesh object.
2. Create a Window Definition: Define a reusable window component using a Definition.
3. Create Window Instances: Instantiate the window with different transformations for placement in the building.
4. Define Custom Objects (e.g., Walls): Create custom objects that include window instances in their displayValue.
5. Group Elements Using Collections: Use a Collection to group all elements of the building assembly.
6. Send Data to Speckle: Serialize and send the organized data to Speckle for visualization and collaboration.
SpeckleMesh Specification
As you have been discovering a SpeckleMesh stores vertices and faces in a flat list format:
• Vertices: A flat list of `x`, `y`, `z` coordinates. The length should be a multiple of 3.
• Faces: A flat list of integers representing polygon faces. The first integer indicates the number of vertices (cardinality) of the face.
Code Example
Here’s how you might implement this in Speckle:
from specklepy.objects.geometry import Mesh
from specklepy.objects.other import Definition, Instance, Transform, Collection
from specklepy.objects.base import Base
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account
from specklepy.api.operations import operations
from specklepy.transports.server import ServerTransport
import numpy as np
# Initialize the client and transport
client = SpeckleClient(host="https://speckle.xyz")
account = get_default_account()
client.authenticate_with_account(account)
# Example vertices and faces for a window mesh
window_vertices = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0]
window_faces = [3, 0, 1, 2, 4, 3, 4, 5, 6]
# Create the mesh
window_mesh = Mesh(vertices=window_vertices, faces=window_faces)
# Create a reusable window definition
window_definition = Definition(name="Window", geometry=[window_mesh])
# Function to create a column-major transformation matrix
def create_transform_matrix(translation, rotation=np.eye(3), scale=[1, 1, 1]):
transform_matrix = np.identity(4)
transform_matrix[:3, :3] = np.diag(scale) @ rotation
transform_matrix[:3, 3] = translation
return transform_matrix.flatten().tolist() # Flatten for row-major order in Speckle
# Transformations for window instances
window_transform1 = Transform(
value=create_transform_matrix([10, 0, 0])
)
window_transform2 = Transform(
value=create_transform_matrix([20, 0, 0])
)
# Create window instances
window_instance1 = Instance(
definition=window_definition,
transform=window_transform1.matrix
)
window_instance2 = Instance(
definition=window_definition,
transform=window_transform2.matrix
)
# Create custom objects with window instances
class CustomObject(Base):
def __init__(self, name, display_meshes):
super().__init__()
self.name = name
self.displayValue = display_meshes
wall_with_windows = CustomObject(name="Wall with Windows", display_meshes=[window_instance1, window_instance2])
# Group the instances into a collection
building_collection = Collection(
name="Building Assembly",
elements=[wall_with_windows]
)
# Create a definition for the building assembly
building_definition = Definition(
name="Building Assembly Definition",
geometry=[building_collection]
)
# Create transformation matrices for building instances
building_transform1 = Transform(
translation=[0, 0, 0],
rotation=np.eye(3).flatten().tolist(),
scale=[1, 1, 1]
)
building_transform2 = Transform(
translation=[0, 50, 0],
rotation=np.eye(3).flatten().tolist(),
scale=[1, 1, 1]
)
# Create instances of the building assembly
building_instance1 = Instance(
definition=building_definition,
transform=building_transform1.matrix
)
building_instance2 = Instance(
definition=building_definition,
transform=building_transform2.matrix
)
# Group all building instances into a final collection
final_collection = Collection(
name="Building Model",
elements=[building_instance1, building_instance2]
)
# Send the final collection to Speckle
new_stream_id = client.stream.create(name="Building Model with Reusable Assembly")
new_stream = client.stream.get(id=new_stream_id)
transport = ServerTransport(client=client, stream_id=new_stream_id)
hash_final_collection = operations.send(base=final_collection, transports=[transport])
commit_id_final_collection = client.commit.create(stream_id=new_stream_id, object_id=hash_final_collection, message="Building model with reusable assembly instances")
Conclusion
Speckle’s approach, with its DAG structure and use of Definition and Instance objects, offers a streamlined and efficient way to manage complex assemblies. While it differs from other 3D environments that may support transforms at every node, Speckle’s method prioritises data integrity, reusability, and clarity, making it a powerful tool for collaboration and data management in AEC and other industries.