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 D1.2 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 unrollstrip_mesh = strip_meshes[8]# first the quad mesh must be triangulatedtrimesh = strip_mesh.copy()trimesh.quads_to_triangles()
Find the root face and vertex as the starting point for unrolling; it is the very corner of the mesh.
# d2 ---------------------------------------------------------------------------# get root facefor fkey in trimesh.faces(): nbrs = trimesh.face_neighbors(fkey)iflen(nbrs)==1: root = fkeybreak root =None# get corner of root facefor key in trimesh.face_vertices(root):if trimesh.vertex_degree(key)==2: corner = keybreak corner =None
# ==============================================================================# Visualize Rhino# ==============================================================================# color the root corner in redvertexcolor ={}vertexcolor[corner]= (255,0,0)# color the root face in greenfacecolor ={}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 edgeu = cornerv = trimesh.face_vertex_descendant(root, u)# define origin frame for transformationorigin = 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 transformationframe_to = Frame.worldXY()# define transformationT = Transformation.from_frame_to_frame(frame, frame_to)
# ==============================================================================# Visualize Rhino# ==============================================================================# color the u vertex in red# and the v vertex in bluevertexcolor ={}vertexcolor[u]= (255,0,0)vertexcolor[v]= (0,0,255)# color the root face in greenfacecolor ={}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 attributesflatmesh = trimesh.copy()# transform root face by vertex coordinatesfor key in trimesh.face_vertices(root): xyz = trimesh.vertex_coordinates(key) point =Point(*xyz) point.transform(T) flatmesh.vertex_attributes(key, 'xyz', point)
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 = rootvisited =set([root])# check if halfedge has a neighbor face that is not yet visited, otherwise go to next halfedge of face to visitfor u, v in trimesh.face_halfedges(tovisit): nbr = trimesh.halfedge[v][u]if nbr isNone:continueif 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 greenvertexcolor ={}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 = nbrvisited.add(nbr)# check if halfedge has a neighbor face that is not yet visited, otherwise go to next halfedge of face to visitfor u, v in trimesh.face_halfedges(tovisit): nbr = trimesh.halfedge[v][u]if nbr isNone:continueif 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 visitedwhile 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 visitfor u, v in trimesh.face_halfedges(fkey): nbr = trimesh.halfedge[v][u]if nbr isNone:continueif 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)
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# ==============================================================================...defmap_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']= zreturn flatquadmeshdefunroll(strip_mesh):"""Unroll a mesh strip onto the xy-plane. """ ... #d1 to d7 flatquadmesh =map_quad_to_flattrimesh(strip_mesh, flatmesh)return flatquadmesh