Kukan Kogei -空間工芸-

Notes about my 3d printing artcrafts

Miura fold in Blender with add-ons

f:id:asahidari:20200627142754p:plain

Overview

When using 3D-CG tools like Rhino + Grasshopper or Houdini, finding articles about 'origami' is relatively easy. In such documents, the Miura fold1 (Miura-ori) is often mentioned.

Currently (June 2020), however, I'v not been able to find such articles using Blender. One article2 is found, but it seems to require precise controls of bones. But now Blender has some useful add-ons to simulate this, so I will take another approach to simulate Miura fold with some node tree add-ons.

Deciding the points of Miura fold unit cell

In some articles about Miura fold that can be found online, I've found an article3 relatively easy to understand how to decide the vertices' coordinates and implement the fold with a few parameters.

In that article, the fold has a unit cell and vertices of the cell can be decided by some angles.

f:id:asahidari:20200626220339p:plain

 \angle AOD = \theta \quad \angle DOI = alpha \quad \angle AOI = \beta \\
A(cos\beta, sin\beta, 0), \quad D(cos\alpha, 0, sin\alpha), \\
B(cos\alpha + cos\beta, sin\beta, sin\alpha),  \quad C(2cos\alpha + cos\beta, sin\beta, 0), \\
E(2cos\alpha, 0, 0) \\
cos\beta = \frac{cos\theta}{cos\alpha}

Writing scripts of Miura fold in Blender

Following the parameterization, I will write some scripts in Blender's node editor add-ons. In this entry, I'll use sverchok4 add-on and animation nodes5.

Writing a script in Sverchok add-on

When using sverchok, you can use 'Script Node Lite' node to load your script.

I wrote a script to implement the Miura fold. In this script, at first, locations of vertices in the unit cell are calculated. Then, copy the vertices along x/y axis and connect them each other to make edges and faces.

"""
in x_dim s d=3 n=2
in y_dim s d=3 n=2
in theta s d=1.0471973 n=2
in alpha s d=0. n=2
out verts v
out edges s
out faces s
"""

from mathutils import Vector
import math

if x_dim < 1: x_dim = 1
if y_dim < 1: y_dim = 1

if math.cos(alpha) < 0.03:
    beta = 0.0
else:
    beta = math.acos(math.cos(theta)/math.cos(alpha))

# vertices of the unit cell
v_base = [
            [math.cos(beta), math.sin(beta), 0],
            [math.cos(alpha) + math.cos(beta), math.sin(beta), math.sin(alpha)],
            [2*math.cos(alpha) + math.cos(beta), math.sin(beta), 0],
            [0,0,0],
            [math.cos(alpha), 0, math.sin(alpha)],
            [2*math.cos(alpha), 0, 0],
            [math.cos(beta), -math.sin(beta), 0],
            [math.cos(alpha) + math.cos(beta), -math.sin(beta), math.sin(alpha)],
            [2*math.cos(alpha) + math.cos(beta), -math.sin(beta), 0]
        ]

# arrange vertices
verts = [[]]
v = []
for i in range(y_dim):
    v1 = []
    v2 = []
    v3 = []
    for j in range(x_dim):
        x_offset = 2*math.cos(alpha)*j
        y_offset = -2*math.sin(beta)*i
        if j == 0:
            if i == 0:
                v1.append(Vector((v_base[0][0], v_base[0][1], v_base[0][2])))
            v2.append(Vector((v_base[3][0], y_offset + v_base[3][1], v_base[3][2])))
            v3.append(Vector((v_base[6][0], y_offset + v_base[6][1], v_base[6][2])))
        if i == 0:
            v1.append(Vector((x_offset + v_base[1][0], y_offset + v_base[1][1], v_base[1][2])))
            v1.append(Vector((x_offset + v_base[2][0], y_offset + v_base[2][1], v_base[2][2])))
            
        v2.append(Vector((x_offset + v_base[4][0], y_offset + v_base[4][1], v_base[4][2])))
        v2.append(Vector((x_offset + v_base[5][0], y_offset + v_base[5][1], v_base[5][2])))
            
        v3.append(Vector((x_offset + v_base[7][0], y_offset + v_base[7][1], v_base[7][2])))
        v3.append(Vector((x_offset + v_base[8][0], y_offset + v_base[8][1], v_base[8][2])))

    if i == 0:
        v.extend(v1)
    v.extend(v2)
    v.extend(v3)

verts[0] = v

# define edges
edges = [[]]
edge_set = set()

y = 3 + 2*(y_dim-1)
x = 3 + 2*(x_dim-1)

index = 0
for i in range(y-1):
    for j in range(x-1):
        index = i * x + j
        edge_set.add(tuple(sorted([index, index+1])))
        edge_set.add(tuple(sorted([index, index+x])))

    # right side
    edge_set.add(tuple(sorted([index + 1, index+x + 1])))

# bottom line
for k in range(x-1):
    index = (y-1) * x + k
    edge_set.add(tuple(sorted([index, index + 1])))

edges[0].extend(list(edge_set))

# define faces
faces = [[]]
face_set = set()
for i in range(y-1):
    for j in range(x-1):
        index = i * x + j
        face_set.add(tuple((index, index+1, index+x+1, index+x)))
faces[0].extend(list(face_set))

You can download this code from my gist. https://gist.github.com/asahidari/db12374b36b92e90619bbe2c8c758f6e

After writing this, import it in 'Script Node Lite' node and simply connect with Viewer node (Viewer Draw Mk3 or Viewer BMesh). f:id:asahidari:20200627013238p:plain

Then you can fold this moving the 'alpha' angle. f:id:asahidari:20200627013255g:plain

Writing a script in Animation nodes add-on

When using Animation nodes add-on, you can use 'Script' node. The script for the node has a few difference from the script for sverchok. Specifically, types of out-parameters are different (In animation nodes, Vector3DList, EdgeIndicesList, PolygonIndicesList are used).

"""
in x_dim s d=3 n=2
in y_dim s d=3 n=2
in theta s d=1.0471973 n=2
in alpha s d=0. n=2
out verts v
out edges s
out faces s
"""

from mathutils import Vector
from animation_nodes.data_structures import Vector3DList, EdgeIndicesList, PolygonIndicesList
# import numpy as np
import math

if x_dim < 1: x_dim = 1
if y_dim < 1: y_dim = 1

if math.cos(alpha) < 0.03:
    beta = 0.0
else:
    beta = math.acos(math.cos(theta)/math.cos(alpha))

# vertices of the unit cell
v_base = [
            [math.cos(beta), math.sin(beta), 0],
            [math.cos(alpha) + math.cos(beta), math.sin(beta), math.sin(alpha)],
            [2*math.cos(alpha) + math.cos(beta), math.sin(beta), 0],
            [0,0,0],
            [math.cos(alpha), 0, math.sin(alpha)],
            [2*math.cos(alpha), 0, 0],
            [math.cos(beta), -math.sin(beta), 0],
            [math.cos(alpha) + math.cos(beta), -math.sin(beta), math.sin(alpha)],
            [2*math.cos(alpha) + math.cos(beta), -math.sin(beta), 0]
        ]

# arrange vertices
verts = Vector3DList()
v = []
for i in range(y_dim):
    v1 = []
    v2 = []
    v3 = []
    for j in range(x_dim):
        x_offset = 2*math.cos(alpha)*j
        y_offset = -2*math.sin(beta)*i
        if j == 0:
            if i == 0:
                v1.append(Vector((v_base[0][0], v_base[0][1], v_base[0][2])))
            v2.append(Vector((v_base[3][0], y_offset + v_base[3][1], v_base[3][2])))
            if i != y_dim - 1:
                v3.append(Vector((v_base[6][0], y_offset + v_base[6][1], v_base[6][2])))
        if i == 0:
            v1.append(Vector((x_offset + v_base[1][0], y_offset + v_base[1][1], v_base[1][2])))
            if j != x_dim - 1:
                v1.append(Vector((x_offset + v_base[2][0], y_offset + v_base[2][1], v_base[2][2])))
            
        v2.append(Vector((x_offset + v_base[4][0], y_offset + v_base[4][1], v_base[4][2])))
        if j != x_dim - 1:
            v2.append(Vector((x_offset + v_base[5][0], y_offset + v_base[5][1], v_base[5][2])))
        
        if i != y_dim - 1:
            v3.append(Vector((x_offset + v_base[7][0], y_offset + v_base[7][1], v_base[7][2])))
            if j != x_dim - 1:
                v3.append(Vector((x_offset + v_base[8][0], y_offset + v_base[8][1], v_base[8][2])))

    if i == 0:
        v.extend(v1)
    v.extend(v2)
    v.extend(v3)

verts.extend(v)

# define edges
edges = EdgeIndicesList()
edge_set = set()

y = 3 + 2*(y_dim-1) - 1
x = 3 + 2*(x_dim-1) - 1

index = 0
for i in range(y-1):
    for j in range(x-1):
        index = i * x + j
        edge_set.add(tuple(sorted([index, index+1])))
        edge_set.add(tuple(sorted([index, index+x])))

    # right side
    edge_set.add(tuple(sorted([index + 1, index+x + 1])))

# bottom line
for k in range(x-1):
    index = (y-1) * x + k
    edge_set.add(tuple(sorted([index, index + 1])))

edges.extend(list(edge_set))

# define faces
faces = PolygonIndicesList()
face_set = set()
for i in range(y-1):
    for j in range(x-1):
        index = i * x + j
        face_set.add(tuple((index, index+1, index+x+1, index+x)))
faces.extend(list(face_set))

This code also can be downloaded from my gist. https://gist.github.com/asahidari/6465ff73f159038bf1c14eb578112928

Using this script with 'Script' node, connect with 'Combine Mesh' node and output the mesh with 'Mesh Object Output' node. f:id:asahidari:20200627013503p:plain

Then shifting the 'alpha' angle generates an animation. f:id:asahidari:20200627013608g:plain

References