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'}