forked from bartvdbraak/blender
98b2e98c79
Added a 2D Line intersection function Added a function to BPyMesh that gets the mesh space vertex location of a Faces UV Pixel.
585 lines
15 KiB
Python
585 lines
15 KiB
Python
import Blender
|
|
from BPyMesh_redux import redux # seperated because of its size.
|
|
|
|
|
|
def meshWeight2Dict(me):
|
|
''' Takes a mesh and return its group names and a list of dicts, one dict per vertex.
|
|
using the group as a key and a float value for the weight.
|
|
These 2 lists can be modified and then used with dict2MeshWeight to apply the changes.
|
|
'''
|
|
|
|
vWeightDict= [dict() for i in xrange(len(me.verts))] # Sync with vertlist.
|
|
|
|
# Clear the vert group.
|
|
groupNames= me.getVertGroupNames()
|
|
|
|
for group in groupNames:
|
|
for index, weight in me.getVertsFromGroup(group, 1): # (i,w) tuples.
|
|
vWeightDict[index][group]= weight
|
|
|
|
# removed this because me may be copying teh vertex groups.
|
|
#for group in groupNames:
|
|
# me.removeVertGroup(group)
|
|
|
|
return groupNames, vWeightDict
|
|
|
|
|
|
def dict2MeshWeight(me, groupNames, vWeightDict):
|
|
''' Takes a list of groups and a list of vertex Weight dicts as created by meshWeight2Dict
|
|
and applys it to the mesh.'''
|
|
|
|
if len(vWeightDict) != len(me.verts):
|
|
raise 'Error, Lists Differ in size, do not modify your mesh.verts before updating the weights'
|
|
|
|
# Clear the vert group.
|
|
currentGroupNames= me.getVertGroupNames()
|
|
for group in currentGroupNames:
|
|
if group not in groupNames:
|
|
me.removeVertGroup(group) # messes up the active group.
|
|
else:
|
|
me.removeVertsFromGroup(group)
|
|
|
|
# Add clean unused vert groupNames back
|
|
currentGroupNames= me.getVertGroupNames()
|
|
for group in groupNames:
|
|
if group not in currentGroupNames:
|
|
me.addVertGroup(group)
|
|
|
|
add_ = Blender.Mesh.AssignModes.ADD
|
|
|
|
vertList= [None]
|
|
for i, v in enumerate(me.verts):
|
|
vertList[0]= i
|
|
for group, weight in vWeightDict[i].iteritems():
|
|
try:
|
|
me.assignVertsToGroup(group, vertList, min(1, max(0, weight)), add_)
|
|
except:
|
|
pass # vert group is not used anymore.
|
|
|
|
me.update()
|
|
|
|
def dictWeightMerge(dict_weights):
|
|
'''
|
|
Takes dict weight list and merges into 1 weight dict item and returns it
|
|
'''
|
|
|
|
if not dict_weights:
|
|
return {}
|
|
|
|
keys= []
|
|
for weight in dict_weights:
|
|
keys.extend([ (k, 0.0) for k in weight.iterkeys() ])
|
|
|
|
new_wdict = dict(keys)
|
|
|
|
len_dict_weights= len(dict_weights)
|
|
|
|
for weight in dict_weights:
|
|
for group, value in weight.iteritems():
|
|
new_wdict[group] += value/len_dict_weights
|
|
|
|
return new_wdict
|
|
|
|
|
|
FLIPNAMES=[\
|
|
('Left','Right'),\
|
|
('_L','_R'),\
|
|
('-L','-R'),\
|
|
('.L','.R'),\
|
|
]
|
|
|
|
def dictWeightFlipGroups(dict_weight, groupNames, createNewGroups):
|
|
'''
|
|
Returns a weight with flip names
|
|
dict_weight - 1 vert weight.
|
|
groupNames - because we may need to add new group names.
|
|
dict_weight - Weather to make new groups where needed.
|
|
'''
|
|
|
|
def flipName(name):
|
|
for n1,n2 in FLIPNAMES:
|
|
for nA, nB in ( (n1,n2), (n1.lower(),n2.lower()), (n1.upper(),n2.upper()) ):
|
|
if createNewGroups:
|
|
newName= name.replace(nA,nB)
|
|
if newName!=name:
|
|
if newName not in groupNames:
|
|
groupNames.append(newName)
|
|
return newName
|
|
|
|
newName= name.replace(nB,nA)
|
|
if newName!=name:
|
|
if newName not in groupNames:
|
|
groupNames.append(newName)
|
|
return newName
|
|
|
|
else:
|
|
newName= name.replace(nA,nB)
|
|
if newName!=name and newName in groupNames:
|
|
return newName
|
|
|
|
newName= name.replace(nB,nA)
|
|
if newName!=name and newName in groupNames:
|
|
return newName
|
|
|
|
return name
|
|
|
|
if not dict_weight:
|
|
return dict_weight, groupNames
|
|
|
|
|
|
new_wdict = {}
|
|
for group, weight in dict_weight.iteritems():
|
|
flipname= flipName(group)
|
|
new_wdict[flipname]= weight
|
|
|
|
return new_wdict, groupNames
|
|
|
|
|
|
def getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None):
|
|
'''
|
|
ob - the object that you want to get the mesh from
|
|
container_mesh - a Blender.Mesh type mesh that is reused to avoid a new datablock per call to getMeshFromObject
|
|
apply_modifiers - if enabled, subsurf bones etc. will be applied to the returned mesh. disable to get a copy of the mesh.
|
|
vgroup - For mesh objects only, apply the vgroup to the the copied mesh. (slower)
|
|
scn - Scene type. avoids getting the current scene each time getMeshFromObject is called.
|
|
|
|
Returns Mesh or None
|
|
'''
|
|
|
|
if not scn:
|
|
scn= Blender.Scene.GetCurrent()
|
|
if not container_mesh:
|
|
mesh = Blender.Mesh.New()
|
|
else:
|
|
mesh= container_mesh
|
|
mesh.verts= None
|
|
|
|
|
|
type = ob.getType()
|
|
dataname = ob.getData(1)
|
|
tempob= None
|
|
if apply_modifiers or type != 'Mesh':
|
|
try:
|
|
mesh.getFromObject(ob.name)
|
|
except:
|
|
return None
|
|
|
|
else:
|
|
'''
|
|
Dont apply modifiers, copy the mesh.
|
|
So we can transform the data. its easiest just to get a copy of the mesh.
|
|
'''
|
|
tempob= Blender.Object.New('Mesh')
|
|
tempob.shareFrom(ob)
|
|
scn.link(tempob)
|
|
mesh.getFromObject(tempob.name)
|
|
scn.unlink(tempob)
|
|
|
|
if type == 'Mesh':
|
|
if vgroups:
|
|
if tempob==None:
|
|
tempob= Blender.Object.New('Mesh')
|
|
tempob.link(mesh)
|
|
try:
|
|
# Copy the influences if possible.
|
|
groupNames, vWeightDict= meshWeight2Dict(tempMe)
|
|
dict2MeshWeight(mesh, groupNames, vWeightDict)
|
|
except:
|
|
# if the modifier changes the vert count then it messes it up for us.
|
|
pass
|
|
|
|
return mesh
|
|
|
|
|
|
|
|
#============================================================================#
|
|
# Takes a face, and a pixel x/y on the image and returns a worldspace x/y/z #
|
|
# will return none if the pixel is not inside the faces UV #
|
|
#============================================================================#
|
|
def getUvPixelLoc(face, pxLoc, img_size = None, uvArea = None):
|
|
TriangleArea= Blender.Mathutils.TriangleArea
|
|
Vector= Blender.Mathutils.Vector
|
|
|
|
if not img_size:
|
|
w,h = face.image.size
|
|
else:
|
|
w,h= img_size
|
|
|
|
scaled_uvs= [Vector(uv.x*w, uv.y*h) for uv in f.uv]
|
|
|
|
if len(scaled_uvs)==3:
|
|
indicies= ((0,1,2),)
|
|
else:
|
|
indicies= ((0,1,2), (0,2,3))
|
|
|
|
for fidxs in indicies:
|
|
for i1,i2,i3 in fidxs:
|
|
# IS a point inside our triangle?
|
|
# UVArea could be cached?
|
|
uv_area = TriangleArea(scaled_uvs[i1], scaled_uvs[i2], scaled_uvs[i3])
|
|
area0 = TriangleArea(pxLoc, scaled_uvs[i2], scaled_uvs[i3])
|
|
area1 = TriangleArea(pxLoc, scaled_uvs[i1], scaled_uvs[i3])
|
|
area2 = TriangleArea(pxLoc, scaled_uvs[i1], scaled_uvs[i2])
|
|
if area0 + area1 + area2 > uv_area + 1: # 1 px bleed/error margin.
|
|
pass # if were a quad the other side may contain the pixel so keep looking.
|
|
else:
|
|
# We know the point is in the tri
|
|
area0 /= uv_area
|
|
area1 /= uv_area
|
|
area2 /= uv_area
|
|
|
|
# New location
|
|
return Vector(\
|
|
face.v[i1].co[0]*area0 + face.v[i2].co[0]*area1 + face.v[i3].co[0]*area2,\
|
|
face.v[i1].co[1]*area0 + face.v[i2].co[1]*area1 + face.v[i3].co[1]*area2,\
|
|
face.v[i1].co[2]*area0 + face.v[i2].co[2]*area1 + face.v[i3].co[2]*area2\
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
type_tuple= type( (0,) )
|
|
type_list= type( [] )
|
|
def ngon(from_data, indices):
|
|
'''
|
|
takes a polyline of indices (fgon)
|
|
and returns a list of face indicie lists.
|
|
Designed to be used for importers that need indices for an fgon to create from existing verts.
|
|
|
|
from_data is either a mesh, or a list/tuple of vectors.
|
|
'''
|
|
Mesh= Blender.Mesh
|
|
Window= Blender.Window
|
|
Scene= Blender.Scene
|
|
Object= Blender.Object
|
|
|
|
if len(indices) < 4:
|
|
return [indices]
|
|
temp_mesh_name= '~NGON_TEMP~'
|
|
is_editmode= Window.EditMode()
|
|
if is_editmode:
|
|
Window.EditMode(0)
|
|
try:
|
|
temp_mesh = Mesh.Get(temp_mesh_name)
|
|
if temp_mesh.users!=0:
|
|
temp_mesh = Mesh.New(temp_mesh_name)
|
|
except:
|
|
temp_mesh = Mesh.New(temp_mesh_name)
|
|
|
|
if type(from_data) in (type_tuple, type_list):
|
|
# From a list/tuple of vectors
|
|
temp_mesh.verts.extend( [from_data[i] for i in indices] )
|
|
temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
|
|
else:
|
|
# From a mesh
|
|
temp_mesh.verts.extend( [from_data.verts[i].co for i in indices] )
|
|
temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
|
|
|
|
|
|
oldmode = Mesh.Mode()
|
|
Mesh.Mode(Mesh.SelectModes['VERTEX'])
|
|
temp_mesh.sel= True # Select all verst
|
|
|
|
# Must link to scene
|
|
scn= Scene.GetCurrent()
|
|
temp_ob= Object.New('Mesh')
|
|
temp_ob.link(temp_mesh)
|
|
scn.link(temp_ob)
|
|
temp_mesh.fill()
|
|
scn.unlink(temp_ob)
|
|
Mesh.Mode(oldmode)
|
|
|
|
new_indices= [ [v.index for v in f.v] for f in temp_mesh.faces ]
|
|
|
|
if not new_indices: # JUST DO A FAN, Cant Scanfill
|
|
print 'Warning Cannot scanfill!- Fallback on a triangle fan.'
|
|
new_indices = [ [indices[0], indices[i-1], indices[i]] for i in xrange(2, len(indices)) ]
|
|
else:
|
|
# Use real scanfill.
|
|
# See if its flipped the wrong way.
|
|
flip= None
|
|
for fi in new_indices:
|
|
if flip != None:
|
|
break
|
|
for i, vi in enumerate(fi):
|
|
if vi==0 and fi[i-1]==1:
|
|
flip= False
|
|
break
|
|
elif vi==1 and fi[i-1]==0:
|
|
flip= True
|
|
break
|
|
|
|
if not flip:
|
|
for fi in new_indices:
|
|
fi.reverse()
|
|
|
|
if is_editmode:
|
|
Window.EditMode(1)
|
|
|
|
# Save some memory and forget about the verts.
|
|
# since we cant unlink the mesh.
|
|
temp_mesh.verts= None
|
|
|
|
return new_indices
|
|
|
|
|
|
|
|
# EG
|
|
'''
|
|
scn= Scene.GetCurrent()
|
|
me = scn.getActiveObject().getData(mesh=1)
|
|
ind= [v.index for v in me.verts if v.sel] # Get indices
|
|
|
|
indices = ngon(me, ind) # fill the ngon.
|
|
|
|
# Extand the faces to show what the scanfill looked like.
|
|
print len(indices)
|
|
me.faces.extend([[me.verts[ii] for ii in i] for i in indices])
|
|
'''
|
|
|
|
def meshCalcNormals(me, vertNormals=None):
|
|
'''
|
|
takes a mesh and returns very high quality normals 1 normal per vertex.
|
|
The normals should be correct, indipendant of topology
|
|
|
|
vertNormals - a list of vectors at least as long as the number of verts in the mesh
|
|
'''
|
|
Ang= Blender.Mathutils.AngleBetweenVecs
|
|
Vector= Blender.Mathutils.Vector
|
|
SMALL_NUM=0.000001
|
|
# Weight the edge normals by total angle difference
|
|
# EDGE METHOD
|
|
|
|
if not vertNormals:
|
|
vertNormals= [ Vector() for v in xrange(len(me.verts)) ]
|
|
else:
|
|
for v in vertNormals:
|
|
v.zero()
|
|
|
|
edges={}
|
|
for f in me.faces:
|
|
for i in xrange(len(f.v)):
|
|
i1, i2= f.v[i].index, f.v[i-1].index
|
|
if i1<i2:
|
|
i1,i2= i2,i1
|
|
|
|
try:
|
|
edges[i1, i2].append(f.no)
|
|
except:
|
|
edges[i1, i2]= [f.no]
|
|
|
|
# Weight the edge normals by total angle difference
|
|
for fnos in edges.itervalues():
|
|
|
|
len_fnos= len(fnos)
|
|
if len_fnos>1:
|
|
totAngDiff=0
|
|
for j in reversed(xrange(len_fnos)):
|
|
for k in reversed(xrange(j)):
|
|
#print j,k
|
|
try:
|
|
totAngDiff+= (Ang(fnos[j], fnos[k])) # /180 isnt needed, just to keeop the vert small.
|
|
except:
|
|
pass # Zero length face
|
|
|
|
# print totAngDiff
|
|
if totAngDiff > SMALL_NUM:
|
|
'''
|
|
average_no= Vector()
|
|
for no in fnos:
|
|
average_no+=no
|
|
'''
|
|
average_no= reduce(lambda a,b: a+b, fnos, Vector())
|
|
fnos.append(average_no*totAngDiff) # average no * total angle diff
|
|
#else:
|
|
# fnos[0]
|
|
else:
|
|
fnos.append(fnos[0])
|
|
|
|
for ed, v in edges.iteritems():
|
|
vertNormals[ed[0]]+= v[-1]
|
|
vertNormals[ed[1]]+= v[-1]
|
|
for i, v in enumerate(me.verts):
|
|
v.no= vertNormals[i]
|
|
|
|
|
|
|
|
|
|
def pointInsideMesh(ob, pt):
|
|
Intersect = Blender.Mathutils.Intersect # 2 less dict lookups.
|
|
Vector = Blender.Mathutils.Vector
|
|
|
|
def ptInFaceXYBounds(f, pt):
|
|
|
|
co= f.v[0].co
|
|
xmax= xmin= co.x
|
|
ymax= ymin= co.y
|
|
|
|
co= f.v[1].co
|
|
xmax= max(xmax, co.x)
|
|
xmin= min(xmin, co.x)
|
|
ymax= max(ymax, co.y)
|
|
ymin= min(ymin, co.y)
|
|
|
|
co= f.v[2].co
|
|
xmax= max(xmax, co.x)
|
|
xmin= min(xmin, co.x)
|
|
ymax= max(ymax, co.y)
|
|
ymin= min(ymin, co.y)
|
|
|
|
if len(f.v)==4:
|
|
co= f.v[3].co
|
|
xmax= max(xmax, co.x)
|
|
xmin= min(xmin, co.x)
|
|
ymax= max(ymax, co.y)
|
|
ymin= min(ymin, co.y)
|
|
|
|
# Now we have the bounds, see if the point is in it.
|
|
if\
|
|
pt.x < xmin or\
|
|
pt.y < ymin or\
|
|
pt.x > xmax or\
|
|
pt.y > ymax:
|
|
return False # point is outside face bounds
|
|
else:
|
|
return True # point inside.
|
|
#return xmax, ymax, xmin, ymin
|
|
|
|
def faceIntersect(f):
|
|
isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
|
|
if not isect and len(f.v) == 4:
|
|
isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
|
|
|
|
if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point.
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
obImvMat = Blender.Mathutils.Matrix(ob.matrixWorld)
|
|
obImvMat.invert()
|
|
pt.resize4D()
|
|
obSpacePt = pt* obImvMat
|
|
pt.resize3D()
|
|
obSpacePt.resize3D()
|
|
ray = Vector(0,0,-1)
|
|
me= ob.getData(mesh=1)
|
|
|
|
# Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
|
|
return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
|
|
|
|
|
|
# NMesh wrapper
|
|
Vector= Blender.Mathutils.Vector
|
|
class NMesh(object):
|
|
__slots__= 'verts', 'faces', 'edges', 'faceUV', 'materials', 'realmesh'
|
|
def __init__(self, mesh):
|
|
'''
|
|
This is an NMesh wrapper that
|
|
mesh is an Mesh as returned by Blender.Mesh.New()
|
|
This class wraps NMesh like access into Mesh
|
|
|
|
Running NMesh.update() - with this wrapper,
|
|
Will update the realmesh.
|
|
'''
|
|
self.verts= []
|
|
self.faces= []
|
|
self.edges= []
|
|
self.faceUV= False
|
|
self.materials= []
|
|
self.realmesh= mesh
|
|
|
|
def addFace(self, nmf):
|
|
self.faces.append(nmf)
|
|
|
|
def Face(self, v=[]):
|
|
return NMFace(v)
|
|
def Vert(self, x,y,z):
|
|
return NMVert(x,y,z)
|
|
|
|
def hasFaceUV(self, flag):
|
|
if flag:
|
|
self.faceUV= True
|
|
else:
|
|
self.faceUV= False
|
|
|
|
def addMaterial(self, mat):
|
|
self.materials.append(mat)
|
|
|
|
def update(self, recalc_normals=False): # recalc_normals is dummy
|
|
mesh= self.realmesh
|
|
mesh.verts= None # Clears the
|
|
|
|
# Add in any verts from faces we may have not added.
|
|
for nmf in self.faces:
|
|
for nmv in nmf.v:
|
|
if nmv.index==-1:
|
|
nmv.index= len(self.verts)
|
|
self.verts.append(nmv)
|
|
|
|
|
|
mesh.verts.extend([nmv.co for nmv in self.verts])
|
|
for i, nmv in enumerate(self.verts):
|
|
nmv.index= i
|
|
mv= mesh.verts[i]
|
|
mv.sel= nmv.sel
|
|
|
|
good_faces= [nmf for nmf in self.faces if len(nmf.v) in (3,4)]
|
|
#print len(good_faces), 'AAA'
|
|
|
|
|
|
#mesh.faces.extend([nmf.v for nmf in self.faces])
|
|
mesh.faces.extend([[mesh.verts[nmv.index] for nmv in nmf.v] for nmf in good_faces])
|
|
if len(mesh.faces):
|
|
if self.faceUV:
|
|
mesh.faceUV= 1
|
|
|
|
#for i, nmf in enumerate(self.faces):
|
|
for i, nmf in enumerate(good_faces):
|
|
mf= mesh.faces[i]
|
|
if self.faceUV:
|
|
if len(nmf.uv) == len(mf.v):
|
|
mf.uv= [Vector(uv[0], uv[1]) for uv in nmf.uv]
|
|
if len(nmf.col) == len(mf.v):
|
|
for c, i in enumerate(mf.col):
|
|
c.r, c.g, c.b= nmf.col[i].r, nmf.col[i].g, nmf.col[i].b
|
|
if nmf.image:
|
|
mf.image= nmf.image
|
|
|
|
mesh.materials= self.materials[:16]
|
|
|
|
class NMVert(object):
|
|
__slots__= 'co', 'index', 'no', 'sel', 'uvco'
|
|
def __init__(self, x,y,z):
|
|
self.co= Vector(x,y,z)
|
|
self.index= None # set on appending.
|
|
self.no= Vector(0,0,1) # dummy
|
|
self.sel= 0
|
|
self.uvco= None
|
|
class NMFace(object):
|
|
__slots__= 'col', 'flag', 'hide', 'image', 'mat', 'materialIndex', 'mode', 'normal',\
|
|
'sel', 'smooth', 'transp', 'uv', 'v'
|
|
|
|
def __init__(self, v=[]):
|
|
self.col= []
|
|
self.flag= 0
|
|
self.hide= 0
|
|
self.image= None
|
|
self.mat= 0 # materialIndex needs support too.
|
|
self.mode= 0
|
|
self.normal= Vector(0,0,1)
|
|
self.uv= []
|
|
self.sel= 0
|
|
self.smooth= 0
|
|
self.transp= 0
|
|
self.uv= []
|
|
self.v= [] # a list of nmverts.
|
|
|
|
class NMCol(object):
|
|
__slots__ = 'r', 'g', 'b', 'a'
|
|
def __init__(self):
|
|
self.r= 255
|
|
self.g= 255
|
|
self.b= 255
|
|
self.a= 255
|