Kukan Kogei -空間工芸-

Notes about my 3d printing artcrafts

Calabi Yau and Hanson's Manifold in Blender (with add-ons)

f:id:asahidari:20200613140449p:plain

OverView

I wrote about creating Calabi Yau Manifold with python in my previous post1 . After that, I found an article2 about another version of this manifold having torus-knot-like shape.

In my previous entry, I made the original manifold with python in Jupyter Notebook environment, but in this entry, I'll make this manifold using 3d modeling software Blender with its add-ons (like sverchok3 and animation nodes4), which have node editors including scripting nodes.

Difference from previous Calabi Yau Manifold

Exponents of z1 and z2 may not always equal

When I wrote my previous entry, In the equations of Calabi Yau Manifold, exponents of z1 and z2 are equal ( both has a same value, n). But for this manifold, exponents of z1 (n1) and z2 (n2) are not always equal. As a result, the range of k1 and k2 may also be different.

 z_1^{n1}+z_2^{n2}=1
 \phi_1=\frac{2\pi\mathrm{k}_1}{\mathrm{n1}}\qquad (0\leq \mathrm{k}_1 < \mathrm{n1}) \qquad \phi_2=\frac{2\pi\mathrm{k}_2}{\mathrm{n2}}\qquad (0\leq \mathrm{k}_2 < \mathrm{n2})

Using sinh/cosh instead of sin/cos for z1 and z2 parameter

Compare with the previous one, the Calabi Yau and Hanson's Manifold uses sinh and cosh instead of sin and cos for z1 and z2.

 \begin{eqnarray} \left\{ \begin{array}{1} z_1=\mathrm{e}^{\mathrm{i}\phi_1}[cosh(x+\mathrm{i}y)]^\left(2/n\right) \\
z_2=\mathrm{e}^{\mathrm{i}\phi_2}[sinh(x+\mathrm{i}y)]^\left(2/n\right) \end{array} \right. \end{eqnarray}

Making the manifold with Blender add-ons

Considering these differences, I'll implement the manifold with Blender add-ons. Both add-ons (sverchok and animation nodes) have script nodes, so I'll write some scripts and use them in each environments.

Making the manifold with sverchok

To make the surface, I wrote a script and load it with sverchok's 'Script Node Lite'. The code is here:

"""
in n1_dimension s d=2 n=2
in n2_dimension s d=2 n=2
in a_radian s d=0.4 n=2
in x_dim s d=20 n=2
in y_dim s d=20 n=2
out verts v
out edges s
out faces s
"""


import cmath
import numpy as np
from math import sin, cos, sinh, cosh, pi
from mathutils import Vector


def calcZ1(x, y, k, n):
    return cmath.exp(1j*(2*cmath.pi*k/n)) * (cmath.cosh(x+y*1j))**(2/n)

def calcZ2(x, y, k, n):
    return cmath.exp(1j*(2*cmath.pi*k/n)) * (1 / 1j) * (cmath.sinh(x+y*1j))**(2/n)

def calcZ1Real(x, y, k, n):
    return (calcZ1(x, y, k, n)).real

def calcZ2Real(x, y, k, n):
    return (calcZ2(x, y, k, n)).real

def calcZ(x, y, k1_, k2_, n1_, n2_, a_):
    z1 = calcZ1(x, y, k1, n1_)
    z2 = calcZ2(x, y, k2, n2_)
    return z1.imag * cos(a_) + z2.imag*sin(a_)

# x = np.linspace(0, pi/2, x_dim)
x = np.linspace(-1, 1, x_dim)
y = np.linspace(0, pi/2, y_dim)
x, y = np.meshgrid(x, y)

verts = [[]]
edges = [[]]
edge_set = []
faces = [[]]
face_set = []

n1 = n1_dimension
n2 = n2_dimension
for i in range(n1*n2):
    edge_set.append(set())
    face_set.append(set())

count = 0
for k1 in range(n1):
    for k2 in range(n2):
        # calc X, Y, Z values
        X = np.frompyfunc(calcZ1Real, 4, 1)(x, y, k1, n1).astype('float32')
        Y = np.frompyfunc(calcZ2Real, 4, 1)(x, y, k2, n2).astype('float32')
        Z = np.frompyfunc(calcZ, 7, 1)(x, y, k1, k2, n1, n2, a_radian).astype('float32')

        X_ = X.flatten()
        Y_ = Y.flatten()
        Z_ = Z.flatten()
        
        v = []
        for x1, y1, z1 in zip(X_, Y_, Z_):
            v.append(((float(x1), float(y1), float(z1))))
        verts[0].extend(v)
            
        for i in range(x_dim * y_dim):
            y_index = i / y_dim
            x_index = i % y_dim
            j = i + count * x_dim * y_dim
            if (y_index < y_dim - 1) and (x_index < x_dim - 1):
                edge_set[count].add(tuple(sorted([j, j+y_dim])))
                edge_set[count].add(tuple(sorted([j+y_dim, j+y_dim+1])))
                edge_set[count].add(tuple(sorted([j+y_dim+1, j+1])))
                edge_set[count].add(tuple(sorted([j+1, j])))
                face_set[count].add(tuple(([j, j+y_dim, j+y_dim+1, j+1])))
        
        count += 1

for i in range(n1*n2):
    edges[0].extend(list(edge_set[i]))
    faces[0].extend(list(face_set[i]))

This code also has been uploaded in my gist: https://gist.github.com/asahidari/16da5b0d35f0197dba2a9e13aa1af29a

Here is the output of this script.

f:id:asahidari:20200613144211g:plain

Making the manifold with animation nodes

Animation nodes also have 'Script Node'. Some data types are different from sverchok, but the code is very similar.

"""
in n1_dimension s d=2 n=2
in n2_dimension s d=2 n=2
in a_radian s d=0.4 n=2
in x_dim s d=20 n=2
in y_dim s d=20 n=2
out verts v
out edges s
out faces s
"""


import cmath
import numpy as np
from math import sin, cos, sinh, cosh, pi
from mathutils import Vector


def calcZ1(x, y, k, n):
    return cmath.exp(1j*(2*cmath.pi*k/n)) * (cmath.cosh(x+y*1j))**(2/n)

def calcZ2(x, y, k, n):
    return cmath.exp(1j*(2*cmath.pi*k/n)) * (1 / 1j) * (cmath.sinh(x+y*1j))**(2/n)

def calcZ1Real(x, y, k, n):
    return (calcZ1(x, y, k, n)).real

def calcZ2Real(x, y, k, n):
    return (calcZ2(x, y, k, n)).real

def calcZ(x, y, k1_, k2_, n1_, n2_, a_):
    z1 = calcZ1(x, y, k1, n1_)
    z2 = calcZ2(x, y, k2, n2_)
    return z1.imag * cos(a_) + z2.imag*sin(a_)

# x = np.linspace(0, pi/2, x_dim)
x = np.linspace(-1, 1, x_dim)
y = np.linspace(0, pi/2, y_dim)
x, y = np.meshgrid(x, y)

verts = [[]]
edges = [[]]
edge_set = []
faces = [[]]
face_set = []

n1 = n1_dimension
n2 = n2_dimension
for i in range(n1*n2):
    edge_set.append(set())
    face_set.append(set())

count = 0
for k1 in range(n1):
    for k2 in range(n2):
        # calc X, Y, Z values
        X = np.frompyfunc(calcZ1Real, 4, 1)(x, y, k1, n1).astype('float32')
        Y = np.frompyfunc(calcZ2Real, 4, 1)(x, y, k2, n2).astype('float32')
        Z = np.frompyfunc(calcZ, 7, 1)(x, y, k1, k2, n1, n2, a_radian).astype('float32')

        X_ = X.flatten()
        Y_ = Y.flatten()
        Z_ = Z.flatten()
        
        v = []
        for x1, y1, z1 in zip(X_, Y_, Z_):
            v.append(((float(x1), float(y1), float(z1))))
        verts[0].extend(v)
            
        for i in range(x_dim * y_dim):
            y_index = i / y_dim
            x_index = i % y_dim
            j = i + count * x_dim * y_dim
            if (y_index < y_dim - 1) and (x_index < x_dim - 1):
                edge_set[count].add(tuple(sorted([j, j+y_dim])))
                edge_set[count].add(tuple(sorted([j+y_dim, j+y_dim+1])))
                edge_set[count].add(tuple(sorted([j+y_dim+1, j+1])))
                edge_set[count].add(tuple(sorted([j+1, j])))
                face_set[count].add(tuple(([j, j+y_dim, j+y_dim+1, j+1])))
        
        count += 1

for i in range(n1*n2):
    edges[0].extend(list(edge_set[i]))
    faces[0].extend(list(face_set[i]))

This code is also uploaded to my gist: https://gist.github.com/asahidari/48a09b76000dca35f89eb8613f158169

Following image is how to connect nodes. f:id:asahidari:20200613150751p:plain

The output image is here. f:id:asahidari:20200613150835g:plain

Environments

  • Blender 2.8.3
  • Sverchok add-on 0.6.0.0
  • Animation nodes add-on 2.7.1