Profile script

Here is a script I have been working on to sweep a profile along a path. It currently has some problems/limitations but is basically in working order. It will probably be a while until I have time to develop it further.


#####################################################
#   PROFILE.py - (c) Neil McAllister April 2003     #
#---------------------------------------------------#
#    To use select first the path and then the      #
#    profile to be swept along the path, then       #
#    run script with ALT-P.                         #
#                                                   #
#    The meshes used have certain restrictions:     #
#    - they must be planar (i.e. flat)              #
#    - they must have no faces - only edges         #
#    - they must make a single path (open or closed)#
#      with no branches                             #
#                                                   #
#    Currently the profile is assumed to be on the  #
#    XY plane.                                      #
#                                                   #
#####################################################

print "--------------------------------------"
print " Profile script - (c) Neil McAllister"
print "--------------------------------------"

import Blender
from Blender import *
from Blender.Draw import *
from math import *

#####################################################
# Define abort function                             #
#####################################################

def abort():
	print "ABORTING!"
	raise Exception, "Error in geometry!"

#####################################################
# Main procedure                                    #
#####################################################

if len(Object.GetSelected())!=2:
	print "Script requires exactly 2 meshes selected"

pathobj=Object.GetSelected()[0]
profileobj=Object.GetSelected()[1]

print "Path Object: "+pathobj.name
print "Profile Object: "+profileobj.name
print
######################################################
print "Checking path object..."

path=NMesh.GetRawFromObject(pathobj.name)

pathverts=[]
pathfaces=[]

#####################################################
print "-> Calculating normal...",

x0,y0,z0=path.verts[0].co
x1,y1,z1=path.verts[1].co
x2,y2,z2=path.verts[2].co

pathNx=(y1-y0)*(z2-z0)-(z1-z0)*(y2-y0)
pathNy=(z1-z0)*(x2-x0)-(x1-x0)*(z2-z0)
pathNz=(x1-x0)*(y2-y0)-(y1-y0)*(x2-x0)

Nl = sqrt(pathNx*pathNx+pathNy*pathNy+pathNz*pathNz)

pathNx /= Nl
pathNy /= Nl
pathNz /= Nl

Nx=pathNx
Ny=pathNy
Nz=pathNz

print "DONE"
#####################################################
print "-> Checking if path is planar...",

planar=1

for a in range(2,len(path.verts)):
	Vx,Vy,Vz=path.verts[a].co

	xnv=abs((y1-y0)*(Vz-z0)-(z1-z0)*(Vy-y0))
	ynv=abs((z1-z0)*(Vx-x0)-(x1-x0)*(Vz-z0))
	znv=abs((x1-x0)*(Vy-y0)-(y1-y0)*(Vx-x0))

	lnv = sqrt(xnv*xnv+ynv*ynv+znv*znv)
	if lnv>0:
		xnv /=lnv
		ynv /= lnv
		znv /= lnv

		eps=.000001
		if (abs(xnv-pathNx)>=eps)&(abs(ynv-pathNy)>=eps)&(abs(znv-pathNz)>=eps):
			planar=0

if planar==0:
	print "Mesh non-planar"
	abort()
else:
	print "OK"
#####################################################
print "-> Checking faces...",

onesided=1

for f in path.faces:
	if len(f.v)>2:
		onesided=0

if onesided==0:
	print "Multi-sided faces found"
	abort()
else:
	print "OK"

print "Path OK"
print
#####################################################
print "Building path data..."
print "-> Building face and vertex list...",
	
for f in path.faces:
	indices=[0,0]
	for a in [0,1]:
		v=f.v[a]
		exists=0
		for b in range(0,len(pathverts)):
			v2=pathverts[b]
			if (v[0]==v2[0])&(v[1]==v2[1])&(v[2]==v2[2]):
				exists=1
				indices[a]=b
		if exists==0:
			pathverts.append([v[0],v[1],v[2]])
			indices[a]=len(pathverts)-1
	pathfaces.append(indices)

print "DONE"
#####################################################
print "-> Building chain...",

pathchain=[]

f=pathfaces.pop(0)
pathchain.append(f[0])
pathchain.append(f[1])

while len(pathfaces)>0:
	for a in range(0,len(pathfaces)):
		f=pathfaces[a]
		if f[0]==pathchain[0]:
			pathchain.insert(0,f[1])
			pathfaces.pop(a)
			break
		elif f[0]==pathchain[len(pathchain)-1]:
			pathchain.append(f[1])
			pathfaces.pop(a)
			break
		elif f[1]==pathchain[0]:
			pathchain.insert(0,f[0])
			pathfaces.pop(a)
			break
		elif f[1]==pathchain[len(pathchain)-1]:
			pathchain.append(f[0])
			pathfaces.pop(a)
			break

pathcyclical=0
if pathchain[0]==pathchain[len(pathchain)-1]:
	pathcyclical=1
	pathchain.pop()

print "DONE"
print "Path data completed"
print
###########################################################################################################
print "Checking profile object..."

profile=NMesh.GetRawFromObject(profileobj.name)

profileverts=[]
profilefaces=[]

#####################################################
print "-> Calculating normal...",

x0,y0,z0=profile.verts[0].co
x1,y1,z1=profile.verts[1].co
x2,y2,z2=profile.verts[2].co

profileNx=(y1-y0)*(z2-z0)-(z1-z0)*(y2-y0)
profileNy=(z1-z0)*(x2-x0)-(x1-x0)*(z2-z0)
profileNz=(x1-x0)*(y2-y0)-(y1-y0)*(x2-x0)

Nl = sqrt(profileNx*profileNx+profileNy*profileNy+profileNz*profileNz)

profileNx /= Nl
profileNy /= Nl
profileNz /= Nl

print "DONE"
#####################################################
print "-> Checking if profile is planar...",

planar=1

for a in range(2,len(profile.verts)):
	Vx,Vy,Vz=profile.verts[a].co

	xnv=abs((y1-y0)*(Vz-z0)-(z1-z0)*(Vy-y0))
	ynv=abs((z1-z0)*(Vx-x0)-(x1-x0)*(Vz-z0))
	znv=abs((x1-x0)*(Vy-y0)-(y1-y0)*(Vx-x0))

	lnv = sqrt(xnv*xnv+ynv*ynv+znv*znv)
	
	xnv /= lnv
	ynv /= lnv
	znv /= lnv

	eps=.000001
	if (abs(xnv-profileNx)>=eps)&(abs(ynv-profileNy)>=eps)&(abs(znv-profileNz)>=eps):
		planar=0

if planar==0:
	print "Mesh non-planar"
	abort()
else:
	print "OK"
#####################################################
print "-> Checking faces...",

onesided=1

for f in profile.faces:
	if len(f.v)>2:
		onesided=0

if onesided==0:
	print "Multi-sided faces found"
	abort()
else:
	print "OK"

print "Profile OK"
print
#####################################################
print "Building profile data..."
print "-> Building face and vertex list...",
	
for f in profile.faces:
	indices=[0,0]
	for a in [0,1]:
		v=f.v[a]
		exists=0
		for b in range(0,len(profileverts)):
			v2=profileverts[b]
			if (v[0]==v2[0])&(v[1]==v2[1])&(v[2]==v2[2]):
				exists=1
				indices[a]=b
		if exists==0:
			profileverts.append([v[0],v[1],v[2]])
			indices[a]=len(profileverts)-1
	profilefaces.append(indices)

print "DONE"
#####################################################
print "-> Building chain...",

profilechain=[]

f=profilefaces.pop(0)
profilechain.append(f[0])
profilechain.append(f[1])

while len(profilefaces)>0:
	for a in range(0,len(profilefaces)):
		f=profilefaces[a]
		if f[0]==profilechain[0]:
			profilechain.insert(0,f[1])
			profilefaces.pop(a)
			break
		elif f[0]==profilechain[len(profilechain)-1]:
			profilechain.append(f[1])
			profilefaces.pop(a)
			break
		elif f[1]==profilechain[0]:
			profilechain.insert(0,f[0])
			profilefaces.pop(a)
			break
		elif f[1]==profilechain[len(profilechain)-1]:
			profilechain.append(f[0])
			profilefaces.pop(a)
			break

profilecyclical=0
if profilechain[0]==profilechain[len(profilechain)-1]:
	profilecyclical=1
	profilechain.pop()

print "DONE"
print "Profile data completed"
print
#####################################################
print "Building mesh..."

newMesh=NMesh.GetRaw()

print "-> Creating vertices...",

for a in range(0,len(pathchain)):
	va=pathverts[pathchain[a]]

	if (a==len(pathchain)-1)&(pathcyclical==0):
		vc=pathverts[pathchain[a-1]]

		Vx=va[0]-vc[0]
		Vy=va[1]-vc[1]
		Vz=va[2]-vc[2]

		Px=Ny*Vz-Nz*Vy
		Py=Nz*Vx-Nx*Vz
		Pz=Nx*Vy-Ny*Vx
	
		Pl=sqrt(Px*Px+Py*Py+Pz*Pz)

		Px /= Pl
		Py /= Pl

		Pz /= Pl
	elif (a==0)&(pathcyclical==0):
		vb=pathverts[pathchain[a+1]]

		Vx=vb[0]-va[0]
		Vy=vb[1]-va[1]
		Vz=vb[2]-va[2]

		Px=Ny*Vz-Nz*Vy
		Py=Nz*Vx-Nx*Vz
		Pz=Nx*Vy-Ny*Vx
	
		Pl=sqrt(Px*Px+Py*Py+Pz*Pz)

		Px /= Pl
		Py /= Pl
		Pz /= Pl
	else:
		if a==len(pathchain)-1:
			vb=pathverts[pathchain[0]]
		else:
			vb=pathverts[pathchain[a+1]]		
		if a==0:
			vc=pathverts[pathchain[len(pathchain)-1]]		
		else:
			vc=pathverts[pathchain[a-1]]		
			
		Vx=vb[0]-va[0]
		Vy=vb[1]-va[1]
		Vz=vb[2]-va[2]

		Px1=Ny*Vz-Nz*Vy
		Py1=Nz*Vx-Nx*Vz
		Pz1=Nx*Vy-Ny*Vx
	
		Pl=sqrt(Px1*Px1+Py1*Py1+Pz1*Pz1)

		Px1 /= Pl
		Py1 /= Pl
		Pz1 /= Pl
	
		Vx=va[0]-vc[0]
		Vy=va[1]-vc[1]
		Vz=va[2]-vc[2]

		Px2=Ny*Vz-Nz*Vy
		Py2=Nz*Vx-Nx*Vz
		Pz2=Nx*Vy-Ny*Vx
	
		Pl=sqrt(Px2*Px2+Py2*Py2+Pz2*Pz2)

		Px2 /= Pl
		Py2 /= Pl
		Pz2 /= Pl
		
		Px=Px1+Px2
		Py=Py1+Py2
		Pz=Pz1+Pz2

		theta=acos(Px1*Px2+Py1*Py2+Pz1*Pz2)		

		Pl=sqrt(Px*Px+Py*Py+Pz*Pz)*cos(theta/2)

		Px /= Pl
		Py /= Pl
		Pz /= Pl
	
	
	for b in range(0,len(profilechain)):
		vp=profileverts[profilechain[b]]
		newMesh.verts.append(NMesh.Vert(va[0]+Px*vp[0]+Nx*vp[1], va[1]+Py*vp[0]+Ny*vp[1], va[2]+Pz*vp[0]+Nz*vp[1]))

points=len(profilechain)
print "DONE"
#####################################################
print "Creating faces...",

for a in range(0,len(newMesh.verts)-points,points):
	for b in range(0,points-1):
		f=NMesh.Face()
		f.v.append(newMesh.verts[a+b])
		f.v.append(newMesh.verts[a+b+1])
		f.v.append(newMesh.verts[a+b+1+points])
		f.v.append(newMesh.verts[a+b+points])
		newMesh.faces.append(f)
	if profilecyclical:
		f=NMesh.Face()
		f.v.append(newMesh.verts[a+points-1])
		f.v.append(newMesh.verts[a])
		f.v.append(newMesh.verts[a+points])
		f.v.append(newMesh.verts[a+points*2-1])
		newMesh.faces.append(f)

if pathcyclical==1:
	a+=points
	for b in range(0,points-1):
		f=NMesh.Face()
		f.v.append(newMesh.verts[a+b])
		f.v.append(newMesh.verts[a+b+1])
		f.v.append(newMesh.verts[b+1])
		f.v.append(newMesh.verts[b])
		newMesh.faces.append(f)
	if profilecyclical:
		f=NMesh.Face()
		f.v.append(newMesh.verts[a+points-1])
		f.v.append(newMesh.verts[a])
		f.v.append(newMesh.verts[0])
		f.v.append(newMesh.verts[points-1])
		newMesh.faces.append(f)

print "DONE"
print "Mesh Completed"
print
#####################################################
NMesh.PutRaw(newMesh,"Test",1)
Redraw()
print "Finished!"

Some problems are it doesn’t copy the location, rotation and scale from the path object so the mesh may land in a strange place. It is also difficult to control the direction of the profile - this is something I need to look into. Another problem is it doesn’t mitre the corners properly when the edges get too short and multiple segments overlap. Despite all the I think it could be of some use.

Neil.