SeamedPipe.py (includes SeamedLine)

Be aware the generated loop is incomplete. Connecting part has triangulated planes, not squared ones. (I do not know exact word for it, sorry. :( )

import math
import bmesh
import numpy as np

'''
SeamedPipe
'''

def deselect():
    for x in bpy.data.objects:
        x.select = False
#    bpy.ops.object.delete()

def genLineVerts(n, objectSize, isCircle, isLooped, axis):
    v = np.array([0.0,0.0,0.0])
    if isCircle:
        for i in range(n):
            phi = float(i)*2.0*math.pi/float(n)
            v[axis-2] = objectSize*math.cos(phi)
            v[axis-1] = objectSize*math.sin(phi)
            v[axis] = 0.0
            x, y, z = v[0], v[1], v[2]
            yield(x,y,z)
    else:
        for z in np.arange(0, objectSize, objectSize/n):
            v[axis] = z
            x, y, z = v[0], v[1], v[2]
            yield(x,y,z)

def genLineEdges(n, hasLoop):
    for i in range(n-1):
        yield(i, i+1)
    if hasLoop:
        yield(n-1, 0)

def getPipeVerts(p, nn, n, pipeWidth, isLooped, offset):
    # Rodrigues' rotation: where q point to move, k normal, phi in radian
    r = lambda q, k, phi: \
        q*math.cos(phi) + \
            np.cross(k,q)*math.sin(phi) +\
            k*np.dot(k,q)*(1.0-math.cos(phi))
    v = np.zeros((nn*n, 3))
    # first circle with center p0 i.e. v[0]
    p0p1 = np.array(p[1]) - np.array(p[0])
    p0p1 /= np.linalg.norm(p0p1)
    gg = np.array([p0p1[0]*p0p1[0], p0p1[1]*p0p1[1], p0p1[2]*p0p1[2]])
    g = gg.argsort()
    print("g", g)
    q0 = np.array([0.0,0.0,0.0])
    q0[g[2]] = p0p1[g[1]]
    q0[g[1]] = -p0p1[g[2]]
    # q0 is a point on the circle at p0
    q0 = pipeWidth*q0/np.linalg.norm(q0)
    q00 = r(q0, p0p1, offset*math.pi/180.0)
    print("p0p1, q00",p0p1,q00)
    v[0] = p[0] + q00
    for i in range(1, n):
        phi = float(i) * 2*math.pi/n
        qq = r(q00,p0p1,phi)
        print("qq",qq)
        v[i] = (p[0]+qq)
    # map eccl0 on each seam
    # let the plane l
    for k in range(1, nn+1):
        i = k%nn
        # if not looped, last circle is vertical to p[nn-1]
        v_p = np.array(p[i]) -np.array(p[i-1])
        v_p_abs = np.linalg.norm(v_p)
        v_p /= v_p_abs
        if ((isLooped is False) and (k<=nn-2)) or \
            ((isLooped is True) and (k<=nn-1)):
            v_n = np.array(p[i+1]) - np.array(p[i])
            v_n /= np.linalg.norm(v_n)
            ln =  v_n + v_p
        elif ((isLooped is False) and (k==nn-1)) or \
            ((isLooped is True) and (k==nn)):
            ln = v_p
        else:
            break
        ln /= np.linalg.norm(ln)
        # pl is the distance p[i-1] to the plane
        pl = np.dot(v_p, ln)
        for j in range(n):
            q_prev = v[n*(i-1) + j]
            # lp is the distance q_prev to the plane
            ql = np.dot(p[i]-q_prev, ln)
            qq_abs = v_p_abs*ql/pl
            v[n*i+j] = v[n*(i-1) + j] + qq_abs*v_p/v_p_abs
    return v

def genPipeFaces(nn, n, isLooped):
    for i in range(nn):
        if isLooped == False and i>nn-2:
            break
        for j in range(n-1):
            k = n*(i%nn) + j
            l = n*((i+1)%nn) + j
            yield(k, l, k+1)
            yield(l, k+1, l+1)
        j = n-1
        k = n*(i%nn) + j
        l = n*((i+1)%nn) + j
        yield(k, l, n*(i%nn)+(j+1)%n)
        yield(l, n*(i%nn)+(j+1)%n, n*((i+1)%nn)+(j+1)%n)

# nn=seams, n=verts per seam
def makeLine(n=12, isCircle=False, objectSize=1.0, isLooped=False, axis=0):
    deselect()
    if isCircle==False and isLooped:
        print("Loop is ignored for a line.")
    verts = list(genLineVerts(n, objectSize, isCircle, isLooped, axis))
    edges = list(genLineEdges(len(verts), (isCircle and isLooped)))
    m = bpy.data.meshes.new('seamedLine_mesh')
    o = bpy.data.objects.new('seamedLine', m)
    bpy.context.scene.objects.link(o)#        m = context.scene.objects.active.data
    bpy.context.scene.objects.active = o
    bpy.context.scene.objects.active.select = True
    #m.from_pydata(verts, edges, [])
    m.from_pydata(verts, edges, [])
    m.update()
    return {'FINISHED'}

# offset is the angle(degree) at each vert
def makePipe(n=12, pipeWidth=0.1, isLooped=False, offset=0.0):
    o0 = bpy.context.scene.objects.active
    nn = len(o0.data.vertices)
    verts = np.zeros((nn,3))
    for i in range(nn):
        verts[i] = o0.data.vertices[i].co
    pipeVerts = getPipeVerts(verts, nn, n, pipeWidth, isLooped, offset)
    print(pipeVerts)
    pipeFaces = list(genPipeFaces(nn, n, isLooped))
    print(pipeFaces)
    #
    m = bpy.data.meshes.new('seamedPipe_mesh')
    o1 = bpy.data.objects.new('seamedPipe', m)
    o1.matrix_basis = o0.matrix_basis
    o1.matrix_local = o0.matrix_local
    o1.matrix_parent_inverse = o0.matrix_parent_inverse
    o1.matrix_world = o0.matrix_world
    o1.location = o0.location
    #
    bpy.context.scene.objects.link(o1)#        m = context.scene.objects.active.data
    bpy.context.scene.objects.active = o1
    bpy.context.scene.objects.active.select = True
    #m.from_pydata(verts, edges, [])
    m.from_pydata(pipeVerts, [], pipeFaces)
    m.update()
    return {'FINISHED'}