C2.a-c Cutting Pattern

Split the fabric into separate strips.

Objectives

You will learn how to generate a cutting pattern on the 3D form-found mesh along parallel edges. For this, we will first strip and then split the mesh into separate strip meshes along the cable-net lines.

Procedure

o. Paths

# ==============================================================================
# Paths
# ==============================================================================

HERE = os.path.dirname(__file__)
DATA = os.path.abspath(os.path.join(HERE, '..', 'data'))
FILE_I = os.path.join(DATA, 'cablenet_materialized.json')

a. Flip Face Orientation

In preparation for later, flip the face orientation of the mesh so that its normals point outwards by flipping the cycle direction.

# ==============================================================================
# Mesh
# ==============================================================================

# construct the form-found mesh from stored data contained in the json file
mesh = Mesh.from_json(FILE_I)

# reorient mesh so that normals point upwards by flipping the cycle directions of all faces
mesh.flip_cycles()
# ==============================================================================
# Visualize Rhino
# ==============================================================================

baselayer = "CSD2::C2::MaterializeFabric"

artist = MeshArtist(mesh, layer=baselayer+"::Mesh")
artist.clear_layer()
artist.draw_facenormals(color=(0, 255, 0), scale=0.2)
artist.draw()

The Mesh.flip_cycles function does not care about the directions being unified or not. It just reverses whatever direction it finds. In case you ever have a mesh with nonunified cycle directions, you should check out the Mesh.unify_cycle function, as it is a necessary condition for the data structures to work properly.

b1. Shorter or Longer Boundary

Find the shorter (or longer) boundary to strip off from. This is basically the same code of module B2.e1 with the addition that you can choose if you would like to find the shorter or longer boundary. It is also basically a subpart of the function from module C1.d1.


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

def shorter_longer_boundary(mesh, direction='short'):
    """Return boundary edges based on length, either short or long.
    """
    # select starting corner
    corners = list(mesh.vertices_where({'vertex_degree': 2}))
    corner = corners[0]

    # check in which direction the edge is shorter
    corner_edges = mesh.vertex_neighbors(corner)

    edgeA = (corner, corner_edges[0])
    edgeB = (corner, corner_edges[1])

    loopA = mesh.edge_loop(edgeA)
    loopB = mesh.edge_loop(edgeB)

    if len(loopA) <= len(loopB):
        if direction == 'short':
            start = edgeA
            loop = loopA
        elif direction == 'long':
            start = edgeB
            loop = loopB
    else:
        if direction == 'short':
            start = edgeB
            loop = loopB
        elif direction == 'long':
            start = edgeA
            loop = loopA

    return corner, start, loop
# ==============================================================================
# Shorter Boundary
# ==============================================================================

corner, start, loop = shorter_longer_boundary(mesh, direction='short')

# ==============================================================================
# Visualize Rhino
# ==============================================================================

edgecolor = {}
for edge in loop:
    if edge not in mesh.edges():
        edge = (edge[1], edge[0])
    edgecolor[edge] = (0, 255, 0)

if start not in mesh.edges():
    start = (start[1], start[0])
edgecolor[start] = (255, 0, 0)

vertexcolor = {}
vertexcolor[corner] = (255, 0, 0)

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(mesh, layer=baselayer+"::Mesh")
artist.clear_layer()
artist.draw_vertices(color=vertexcolor)
artist.draw_edges(color=edgecolor)
artist.draw()

b2. Strip Mesh as Face Attributes

Strip off the short boundary along parallel edge strips and assign strip number as faces attribute. Also, store the total number of strips as mesh attribute.

In this step, the data structure is still one entire mesh with different face attributes only.

A set(list) method can be used to remove duplicates in a list.

def face_strips(mesh, loop):
    """Strip the mesh in face attributes.
    """
    # create list of faces for each strip
    strips = []
    for row in loop:
        edge_strip = mesh.edge_strip(row)
        face_strip = [mesh.edge_faces(u, v) for (u, v) in edge_strip]
        face_strip = list(set(flatten(face_strip)))
        face_strip = [i for i in face_strip if i is not None]
        strips.append(face_strip)

    # set attributes for faces with strip index
    for i_strip, strip in enumerate(strips):
        for fkey in strip:
            mesh.face_attribute(fkey, 'strip', i_strip)

    # set attribute mesh on total number of strips
    mesh.attributes['strips'] = len(strips)

    return strips
    

...

# ==============================================================================
# Strip Faces of the Mesh
# ==============================================================================

# shorter boundary from which to strip off
corner, start, loop = shorter_longer_boundary(mesh, direction='short')

# the previous code was wrapped into a function
strip = face_strips(mesh, loop)

The Mesh.edge_strip method returns None if an edge is on the boundary and the thus neighbouring face does not exist. Remove the None elements from the list.

Display the mesh with various colours depending on the face attribute strip number and display the face keys.

# ==============================================================================
# Visualize Rhino
# ==============================================================================

facecolor = {}
for fkey in mesh.faces():
    i_strip = mesh.face_attribute(fkey, 'strip')
    facecolor[fkey] = i_to_rgb(i_strip/mesh.attributes['strips'])

baselayer = "CSD2::C2::MaterializeFabric"
artist = MeshArtist(mesh, layer=baselayer+"::Mesh")
artist.clear_layer()
artist.draw_vertices()
artist.draw_edges()
artist.draw_faces(color=facecolor)

artist.draw_facelabels(text=None, color=facecolor)

c. Split Mesh into Strip Meshes

For each strip create a new mesh that inherits the attributes of the entire mesh. Make sure to keep the vertex and face keys (the edge keys follow the vertex keys). This means that along the inner boundaries the vertices will be doubled with the same index.

Remember the difference in accessing and copying default attributes and custom attributes!

After this step, the meshes are all separate per strip as can be seen by the edge colours or the layer structure.

# ==============================================================================
# Split Mesh into Strip Meshes
# ==============================================================================

# list to contain all new strip meshes
strip_meshes = []

# do so for each strip
for i_strip in range(mesh.attributes['strips']):

    # find all faces that are in the respective strip
    faces_strip = list(mesh.faces_where({'strip': i_strip}))

    # create submesh and inherit default attributes
    strip_mesh = Mesh()
    strip_mesh.attributes.update({'strip': i_strip})
    strip_mesh.update_default_vertex_attributes(mesh.default_vertex_attributes)
    strip_mesh.update_default_edge_attributes(mesh.default_edge_attributes)
    strip_mesh.update_default_face_attributes(mesh.default_face_attributes)

    for fkey in faces_strip:

        # add vertices with copied attributes
        keys = mesh.face_vertices(fkey)
        for key in keys:
            if key not in strip_mesh.vertex:
                attr = mesh.vertex[key].copy()
                strip_mesh.add_vertex(key=key, attr_dict=attr)

        # add face with copied attributes
        attr = mesh.facedata[fkey].copy()
        strip_mesh.add_face(keys, fkey=fkey, attr_dict=attr)

    # copy edge attributes
    for (u, v), attr in strip_mesh.edges(True):
        for name in attr:
            value = mesh.edge_attribute((u, v), name)
            attr[name] = value

    # append the new mesh strip to the list of meshes
    strip_meshes.append(strip_mesh)

Visualise each strip mesh separately with different edge colours for interior or boundary edges and display the strip/mesh number as face labels.

# ==============================================================================
# Visualize Rhino
# ==============================================================================

# visualise each strip mesh separate
for strip_mesh in strip_meshes:

    facecolor = {}
    facetext = {}
    for fkey in strip_mesh.faces():
        i_strip = strip_mesh.attributes['strip']
        facecolor[fkey] = i_to_rgb(i_strip/mesh.attributes['strips'])
        facetext[fkey] = str(i_strip)

    edgecolor = {}
    for (u, v) in strip_mesh.edges():
        if (u, v) in strip_mesh.edges_on_boundary() or (v, u) in strip_mesh.edges_on_boundary():
            edgecolor[(u, v)] = (0, 0, 0)
        else:
            edgecolor[(u, v)] = (125, 125, 125)

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

    artist.draw_facelabels(text=facetext, color=facecolor)

Congratulations, we reached the first part of materialising the fabric by creating the cutting pattern. Move on to unroll these mesh strips!

Last updated