Translating and Rotating Objects with specklepy

Hi everybody,

after the conference I have been playing around with shifting and rotating objects of my speckle stream.

I designed a small house in Revit and then pushed it to Speckle. I then shifted my wall by shifting the baseLine. After I received my stream back in revit my wall shifted which is great.

However, the displayValue is only changed when I commit the model from Revit again. So after my specklepy changes (since I did only change my baseLine) these changes are not represented in the viewer.

Any ideas of how I can generate a new displayValue after modifying my baseLine or basePoint?

(Can I make use of three.js maybe?)

This is by a large expected behaviour.

All our connectors generate display value mesh for web view and fallback for onward apps - if this workflow you describe is what you want to do (I’d love to know what you’re cooking - I’d recommend Trimesh for Python.

It doesn’t have full featured mesh manipulation methods but does perform translations very well.

Thanks Jonathon. I am just playing around to explore. I will let you know if I find a use case.

It makes sense that it us expected behavior.
Trimesh sounds great.

Trimesh will only take triangulated meshes, and Speckle supports ngon-faced meshes, so there may be some work to make the transforms.

to transform a Trimesh it is then simply t_mesh.apply_transform(trimesh_transform)

Once the Trimesh exists, you can safely use it to recreate the resultant SpeckleMesh.

e.g. code to achieve this end… CAVEAT I haven’t fully tested this.

def speckle_mesh_to_trimesh(input_mesh: SpeckleMesh) -> trimesh.Trimesh:
    vertices = np.array(input_mesh.vertices).reshape((-1, 3))
    faces = []

    i = 0
    while i < len(input_mesh.faces):
        face_vertex_count = input_mesh.faces[i]
        i += 1  # Skip the vertex count

        face_vertex_indices = input_mesh.faces[i: i + face_vertex_count]

        face_vertices = [
            Vector.from_list(vertices[idx].tolist()) for idx in face_vertex_indices
        ]

        if face_vertex_count == 3:
            faces.append(face_vertex_indices)
        else:
            triangulated = triangulate_face(face_vertices)
            faces.extend(
                [[face_vertex_indices[idx] for idx in tri] for tri in triangulated]
            )

        i += face_vertex_count

    t_mesh = trimesh.Trimesh(vertices=vertices, faces=np.array(faces))

    return t_mesh

The helper methods we have used in our connectors that use ear-clipping to triangulate as roughly as follows.

def triangulate_face(vertices: List[Vector]) -> List[List[int]]:
    triangles = []
    indices = list(range(len(vertices)))
    normal = calculate_polygon_normal(vertices)

    # The ear clipping algorithm is used for triangulation.
    while len(indices) > 2:
        for i in range(len(indices)):
            prev, curr, nxt = (
                indices[i - 1],
                indices[i],
                indices[(i + 1) % len(indices)],
            )
            if is_convex(vertices[prev], vertices[curr], vertices[nxt], normal):
                triangles.append([prev, curr, nxt])
                del indices[i]
                break

    return triangles

def calculate_polygon_normal(vertices: List[Vector]) -> Vector:
    normal = Vector.from_list([0.0, 0.0, 0.0])
    num_vertices = len(vertices)
    for i in range(num_vertices):
        curr, nxt = vertices[i], vertices[(i + 1) % num_vertices]
        # Cross product components are accumulated to find the normal.
        normal.x += (curr.y - nxt.y) * (curr.z + nxt.z)
        normal.y += (curr.z - nxt.z) * (curr.x + nxt.x)
        normal.z += (curr.x - nxt.x) * (curr.y + nxt.y)

    # Normalize the calculated normal vector.
    length = np.sqrt(normal.x**2 + normal.y**2 + normal.z**2)
    normal.x, normal.y, normal.z = (
        normal.x / length,
        normal.y / length,
        normal.z / length,
    )
    return normal

def is_convex(a: Vector, b: Vector, c: Vector, normal: Vector) -> bool:
    ab = Vector.from_list([b.x - a.x, b.y - a.y, b.z - a.z])
    bc = Vector.from_list([c.x - b.x, c.y - b.y, c.z - b.z])
    cross = Vector.from_list(
        [
            ab.y * bc.z - ab.z * bc.y,
            ab.z * bc.x - ab.x * bc.z,
            ab.x * bc.y - ab.y * bc.x,
        ]
    )

    # Dot product to compare with the face normal
    return cross.x * normal.x + cross.y * normal.y + cross.z * normal.z > 0

And the reciprocal method:

def trimesh_to_speckle_mesh(input_mesh: trimesh.Trimesh) -> SpeckleMesh:
    # Flatten the vertices array to a 1D array as expected by Speckle
    vertices = input_mesh.vertices.reshape(-1).tolist()

    # Initialize an empty list for SpeckleMesh faces
    faces = []

    for face in input_mesh.faces:
        # For each face in Trimesh, prepend the vertex count (3 for triangles)
        faces.extend([3, *face])

    # Create a SpeckleMesh with the vertices and faces
    speckle_mesh = SpeckleMesh(vertices=vertices, faces=faces)

    return speckle_mesh
1 Like

3 posts were split to a new topic: Translating Geometry and Mapping Speckle Objects in Python