C2.d-e Unroll Strips

Unroll a mesh strip from 3D to the XY-plane.

Objective

You will learn how to unroll a mesh strip from the 3D-geometry onto the XY-plane without distorting the fabric. We will show the process for one random strip, e.g. strip 8:

Procedure

d1. Triangulate Strip

Continue the script from C2.a-c Cutting Pattern. First, select the strip to unroll (e.g. strip 8). Second, in order to prevent distortion, the quadrangular strip must be triangulated before unrolling. For that, copy the mesh object to keep the quad mesh as well.

# ==============================================================================
# Unroll
# ==============================================================================

# d1 ---------------------------------------------------------------------------
# select one random strip to unroll
strip_mesh = strip_meshes[8]

# first the quad mesh must be triangulated
trimesh = strip_mesh.copy()
trimesh.quads_to_triangles()
# ==============================================================================
# Visualize Rhino
# ==============================================================================

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(trimesh, layer=baselayer+"::{}::Strip{}".format("TriMesh", strip_mesh.attributes['strip']))
artist.draw()

d2. Root Face and Vertex

Find the root face and vertex as the starting point for unrolling; it is the very corner of the mesh.


# d2 ---------------------------------------------------------------------------
# get root face
for fkey in trimesh.faces():
    nbrs = trimesh.face_neighbors(fkey)
    if len(nbrs) == 1:
        root = fkey
        break
    root = None

# get corner of root face
for key in trimesh.face_vertices(root):
    if trimesh.vertex_degree(key) == 2:
        corner = key
        break
    corner = None
# ==============================================================================
# Visualize Rhino
# ==============================================================================

# color the root corner in red
vertexcolor = {}
vertexcolor[corner] = (255, 0, 0)

# color the root face in green
facecolor = {}
facecolor[root] = (0, 255, 0)

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(trimesh, layer=baselayer+"::{}::Strip{}".format("TriMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw_vertices(color=vertexcolor)
artist.draw_edges()
artist.draw_faces(color=facecolor)

d3. Transformation Frames

Define the transformation frames from the root face to the world axes.

# d3 ---------------------------------------------------------------------------
# set starting edge along short side edge
u = corner
v = trimesh.face_vertex_descendant(root, u)

# define origin frame for transformation
origin = trimesh.vertex_coordinates(u)
zaxis = trimesh.face_normal(root, unitized=True)
xaxis = normalize_vector(trimesh.edge_direction(u, v))
yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
frame = Frame(origin, xaxis, yaxis)

# define target frame for transformation
frame_to = Frame.worldXY()

# define transformation
T = Transformation.from_frame_to_frame(frame, frame_to)
# ==============================================================================
# Visualize Rhino
# ==============================================================================

# color the u vertex in red
# and the v vertex in blue
vertexcolor = {}
vertexcolor[u] = (255, 0, 0)
vertexcolor[v] = (0, 0, 255)

# color the root face in green
facecolor = {}
facecolor[root] = (0, 255, 0)


baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(trimesh, layer=baselayer+"::{}::Strip{}".format("TriMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw_vertices(color=vertexcolor)
artist.draw_edges()
artist.draw_faces(color=facecolor)


artist = FrameArtist(frame, layer=baselayer+"::FrameOrigin", scale=0.5)
artist.draw()
artist = FrameArtist(frame_to, layer=baselayer+"::FrameTarget", scale=0.5)
artist.draw()

d4. Face Transformation (by Vertex)

Transform the root face by changing the root vertex coordinates. To keep the original tri mesh, make a copy of the tri mesh to flatten down before.

# d4 ---------------------------------------------------------------------------
# make a copy of the trimesh to flatten down by coordinate attributes
flatmesh = trimesh.copy()

# transform root face by vertex coordinates
for key in trimesh.face_vertices(root):
    xyz = trimesh.vertex_coordinates(key)
    point = Point(*xyz)
    point.transform(T)
    flatmesh.vertex_attributes(key, 'xyz', point)
# ==============================================================================
# Visualize Rhino
# ==============================================================================

...
artist.draw_facenormals(color=(100, 100, 100), scale=0.2)

artist = MeshArtist(flatmesh, layer=baselayer+"::{}::Strip{}".format("FlatMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw_vertices(color=vertexcolor)
artist.draw_edges()
artist.draw_faces(color=facecolor)
...

d5. Transformation of Next Neighboring Face (by Vertex)

Transform the next neighbouring face also by vertex coordinates.

Only the next vertex coordinates must be updated to unroll the next face because the other vertex coordinates of the face are already updated in step d4.

# d5 ---------------------------------------------------------------------------
# for the next neighbor of this face (only):
tovisit = root
visited = set([root])

# check if halfedge has a neighbor face that is not yet visited, otherwise go to next halfedge of face to visit
for u, v in trimesh.face_halfedges(tovisit):
    nbr = trimesh.halfedge[v][u]
    if nbr is None:

        continue
    if nbr in visited:
        continue

    start = v
    end = u

    # define origin frame for transformation
    origin = trimesh.vertex_coordinates(v)
    zaxis = trimesh.face_normal(nbr, unitized=True)
    xaxis = normalize_vector(trimesh.edge_direction(v, u))
    yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
    frame = Frame(origin, xaxis, yaxis)

    # define target frame for transformation
    origin = flatmesh.vertex_coordinates(v)
    zaxis = [0, 0, 1.0]
    xaxis = normalize_vector(flatmesh.edge_direction(v, u))
    yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
    frame_to = Frame(origin, xaxis, yaxis)

    # define transformation
    T = Transformation.from_frame_to_frame(frame, frame_to)

    # transform next face by vertex coordinates
    w = trimesh.face_vertex_ancestor(nbr, v)
    xyz = trimesh.vertex_coordinates(w)
    point = Point(*xyz)
    point.transform(T)
    flatmesh.vertex_attributes(w, 'xyz', point)
# ==============================================================================
# Visualize Rhino
# ==============================================================================

# color the u vertex in red
# and the v vertex in blue
# and the w vertex to transform in green
vertexcolor = {}
vertexcolor[start] = (255, 0, 0)
vertexcolor[end] = (0, 0, 255)
vertexcolor[w] = (0, 255, 0)

...

d6. And of the Next

Continue to the next vertex (hence face) manually. The concept is like climbing up a ladder.

# d6 ---------------------------------------------------------------------------
# for the next neighbor of the last face:

tovisit = nbr
visited.add(nbr)

# check if halfedge has a neighbor face that is not yet visited, otherwise go to next halfedge of face to visit
for u, v in trimesh.face_halfedges(tovisit):
    nbr = trimesh.halfedge[v][u]
    if nbr is None:
        continue
    if nbr in visited:
        continue

    start = v
    end = u

    # define origin frame for transformation
    origin = trimesh.vertex_coordinates(v)
    zaxis = trimesh.face_normal(nbr, unitized=True)
    xaxis = normalize_vector(trimesh.edge_direction(v, u))
    yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
    frame = Frame(origin, xaxis, yaxis)

    # define target frame for transformation
    origin = flatmesh.vertex_coordinates(v)
    zaxis = [0, 0, 1.0]
    xaxis = normalize_vector(flatmesh.edge_direction(v, u))
    yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
    frame_to = Frame(origin, xaxis, yaxis)

    # define transformation
    T = Transformation.from_frame_to_frame(frame, frame_to)

    # transform next face by vertex coordinates
    w = trimesh.face_vertex_ancestor(nbr, v)
    xyz = trimesh.vertex_coordinates(w)
    point = Point(*xyz)
    point.transform(T)
    flatmesh.vertex_attributes(w, 'xyz', point)

It is basically identical to step d5; this is very inelegant, and it does not make sense to copy it for all faces.

d7. Continue for All Faces/Vertices

Thus, we put the procedure into a loop that visits all faces/vertices (and delete the part from step d4). We basically must add a while loop and keep track of the faces visited and to visit.

...

# d7 ---------------------------------------------------------------------------
# repeat as long as there is a face left to visit that has not yet been visited
while tovisit:
    fkey = tovisit
    tovisit = None

    # d6 modified ---------------------------------------------------------------------------

    # check if halfedge has a neighbor face that is not yet visited, otherwise go to next halfedge of face to visit
    for u, v in trimesh.face_halfedges(fkey):
        nbr = trimesh.halfedge[v][u]
        if nbr is None:
            continue
        if nbr in visited:
            continue

        # if new face to visit is found update the new face to visit for the next iteration and add to visited list
        tovisit = nbr
        visited.add(nbr)

        # define origin frame for transformation
        origin = trimesh.vertex_coordinates(v)
        zaxis = trimesh.face_normal(nbr, unitized=True)
        xaxis = normalize_vector(trimesh.edge_direction(v, u))
        yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
        frame = Frame(origin, xaxis, yaxis)

        # define target frame for transformation
        origin = flatmesh.vertex_coordinates(v)
        zaxis = [0, 0, 1.0]
        xaxis = normalize_vector(flatmesh.edge_direction(v, u))
        yaxis = normalize_vector(cross_vectors(zaxis, xaxis))
        frame_to = Frame(origin, xaxis, yaxis)

        # define transformation
        T = Transformation.from_frame_to_frame(frame, frame_to)

        # transform next face by vertex coordinates
        w = trimesh.face_vertex_ancestor(nbr, v)
        x, y, z = trimesh.vertex_coordinates(w)
        point = Point(x, y, z)
        point.transform(T)
        flatmesh.vertex_attributes(w, 'xyz', point)
    
# ==============================================================================
# Visualize Rhino
# ==============================================================================

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(trimesh, layer=baselayer+"::{}::Strip{}".format("TriMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw()

artist = MeshArtist(flatmesh, layer=baselayer+"::{}::Strip{}".format("FlatMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw()

Now, the tri mesh is fully unrolled onto the XY-plane.

e. Map the Quadmesh

As the last step to unroll the mesh strip, map the quad mesh down to the unrolled tri mesh. To clean up, we put all steps from d1 to d7 into a function, that calls the map_quad_to_flattrimesh function inside.

The vertex keys of the quad mesh and the tri mesh are identical!

# ==============================================================================
# Helpers
# ==============================================================================

...

def map_quad_to_flattrimesh(quadmesh, flattrimesh):
    """Map the 3D quad mesh down to the unrolled trimesh.
    """
    flatquadmesh = quadmesh.copy()

    for key, attr in flatquadmesh.vertices(True):
        x, y, z = flattrimesh.vertex_coordinates(key)
        attr['x'] = x
        attr['y'] = y
        attr['z'] = z

    return flatquadmesh

def unroll(strip_mesh):
    """Unroll a mesh strip onto the xy-plane.
    """
    
    ... #d1 to d7
    
    flatquadmesh = map_quad_to_flattrimesh(strip_mesh, flatmesh)

    return flatquadmesh
# ==============================================================================
# Visualize Rhino
# ==============================================================================

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(strip_mesh, layer=baselayer+"::{}::Strip{}".format("QuadMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw()

artist = MeshArtist(strip_flat, layer=baselayer+"::{}::Strip{}".format("FlatQuadMesh", strip_mesh.attributes['strip']))
artist.clear_layer()
artist.draw()

You have accomplished the unrolling objective!

Last updated