In this post [link] I talk about how Mikoto assigns vertices to a bone using the geometry of the triangle created in Metasequoia for bdef meshes without using anchors.
To help visualise which vertices will be bound to a bone I wrote a script that shows the zone of influence of each bone.
The Metasequoia Python API doesn’t have routines to create Primitive objects so rather than create an object in code this script requires a Primitive to be created in Metasequoia which it can copy.
The Primitive object must be named capsule and have its properties set as shown in the screenshot below.
Note my tabs have been converted to one space indentation in the code below.
'''
Metasequoia Python script to show Mikoto bdef default bone influence
Only tested in version 4.2.2 trial
Before running the script:
1) an object named with "bone:" as the prefix and containing triangles must exist
2) an object named "capsule" with a material assigned to all faces must exist
3) create the "capsule" object using the Primitive in the right hand column and bottom row
4) the "capsule" object must be vertical
5) set the properties for the "capsule" object as:
X = 0, Y = 0, Z = 0
Segment Size
U 20 1
V 1 4
R 4 1
6) the material for the "capsule" object should have an alpha set to 0.5 to make it see through
7) the "capsule" object may be hidden and/or locked
After the script has finished a new object should have been created containing child objects
for each triangle in the "bone:" object showing the corresponding Mikoto bone's zone of influence
'''
# import Python's math module so its trigonometry functions and "pi" constant can be used
import math
# import Python's sys module so its "version_info" constant can be used
import sys
# global variables -> all functions can use them
doc = MQSystem.getDocument()
bone_obj = None
caps_obj = None
influence_object = None
# Metasequoia 4 -> can use print() or MQSystem.println() in scripts to print to output window
# Metasequoia 3 -> can only use MQSystem.println() in scripts to print to output window
def mqprint(message):
try:
print(message)
except:
MQSystem.println(str(message))
return
def ScalarTimesVec(scalar, vec):
outvec = MQSystem.newPoint(scalar*vec.x, scalar*vec.y, scalar*vec.z)
return outvec
def VecLength(vec):
return (vec.x*vec.x+vec.y*vec.y+vec.z*vec.z)**0.5
def RadToDegrees(angle):
return angle*180.0/math.pi
def RotPtAboutAxis(point, axis_pt, axis, angle):
'''
axis_pt is point on the axis
axis must be unit vector
angle in radians
source: https://sites.google.com/site/glennmurray/Home/rotation-matrices-and-formulas
'''
x, y, z = point.x, point.y, point.z
a, b, c = axis_pt.x, axis_pt.y, axis_pt.z
u, v, w = axis.x, axis.y, axis.z
theta = angle
t = 1 - math.cos(theta)
ct = math.cos(theta)
st = math.sin(theta)
x1 = (a*(v*v+w*w)-u*(b*v+c*w-u*x-v*y-w*z))*t+x*ct+(-c*v+b*w-w*y+v*z)*st
y1 = (b*(u*u+w*w)-v*(a*u+c*w-u*x-v*y-w*z))*t+y*ct+( c*u-a*w+w*x-u*z)*st
z1 = (c*(u*u+v*v)-w*(a*u+b*v-u*x-v*y-w*z))*t+z*ct+(-b*u+a*v-v*x+u*y)*st
return MQSystem.newPoint(x1,y1,z1)
def getCapsuleMat():
'''This function returns material index used for first face in "capsule" object'''
return caps_obj.face[0].material
def getData(f, obj):
'''This function returns length of short side, length of long side, direction
of long side and middle of long side of a "bone:" object triangle'''
result = {} # empty dictionary to store results
if f.numVertex == 3:
a = obj.vertex[f.index[0]]
b = obj.vertex[f.index[1]]
c = obj.vertex[f.index[2]]
vectors = [] # empty list to store vectors
vectors.append(b.pos - a.pos) # a -> b
vectors.append(c.pos - b.pos) # b -> c
vectors.append(c.pos - a.pos) # a -> c
#mqprint(vectors)
sides = [] # empty list to store lengths of sides
for v in vectors:
sides.append(VecLength(v))
i = 0
for s in sides:
if s == max(sides):
h_index = i # longest side
elif s == min(sides):
s_index = i # shortest side
else:
l_index = i # long side
i += 1
# find root vertex opposite longest side
if h_index == 0:
root = c.pos
elif h_index == 1:
root = a.pos
else:
root = b.pos
# find tip vertex opposite shortest side
if s_index == 0:
tip = c.pos
elif s_index == 1:
tip = a.pos
else:
tip = b.pos
direction = tip - root
#mqprint(direction)
centre = root + ScalarTimesVec(0.5, (direction))
# make direction a unit vector
direction.normalize()
result["direction"] = direction
result["long"] = sides[l_index]
result["short"] = sides[s_index]
result["centre"] = centre
return result
else:
return result
def drawCapsule(parent_obj, radius, length, centre, direction):
'''This function creates a copy of the "capsule" object, scales it according to the short side
of a "bone:" object triangle and aligns it with the long side of a "bone:" object triangle'''
#mqprint("R %f, L %f, C %s, D %s"%(radius, length, str(centre), str(direction)))
obj = MQSystem.newObject()
R_size = 1.0 # "capsule" object R size
ratio = radius/R_size # scale radius to short side length
svector = MQSystem.newPoint(ratio,ratio,ratio)
rvector = MQSystem.newAngle()
tvector = MQSystem.newPoint()
SRTmatrix = MQSystem.newMatrix()
SRTmatrix.setTransform(svector, rvector, tvector)
# scale the capsule in all axes
for v in caps_obj.vertex:
obj.addVertex(SRTmatrix.mult(v.pos))
for f in caps_obj.face:
obj.addFace(list(f.index))
# move vertices of the capsule in vertical (Y) axis
for v in obj.vertex:
if v.pos.y > 0:
v.pos.y -= (1*ratio - length/2.0)
else:
v.pos.y += (1*ratio - length/2.0)
capsule_vec = MQSystem.newPoint(0.0,1.0,0.0) # capsule object axis unit vector
rot_axis = capsule_vec.crossProduct(direction) # axis perpendicular to the two vectors
rot_axis.normalize()
#mqprint(rot_axis)
cosTheta = capsule_vec.dotProduct(direction) # cosine of angle between the two unit vectors
if cosTheta > 1.0:
cosTheta = 1.0
if cosTheta < -1.0:
cosTheta = -1.0
rot_angle = math.acos(cosTheta) # rot_angle in radians
#mqprint(RadToDegrees(rot_angle)) # always positive
axis_pt = MQSystem.newPoint()
for v in obj.vertex:
v.setPos(RotPtAboutAxis(v.pos, axis_pt, rot_axis, rot_angle))
svector = MQSystem.newPoint(1.0, 1.0, 1.0)
rvector = MQSystem.newAngle()
tvector = MQSystem.newPoint(centre.x, centre.y, centre.z)
# translate capsule centre to middle of long side
SRTmatrix.setTransform(svector, rvector, tvector)
for v in obj.vertex:
v.pos = SRTmatrix.mult(v.pos)
# assign capsule material to created object
mat = getCapsuleMat()
for f in obj.face:
f.material = mat
if not (doc.material[mat].alpha < 1.0):
doc.material[mat].alpha = 0.5
# add object to document
idx = doc.addObject(obj, parent_obj)
return
def main():
'''
Write the script as a function so can stop the script by using a "return"
statement if necessary to exit the function.
It is not required to name this function "main".
'''
if doc.numObject == 0:
mqprint("No objects")
return
# make this function use the global variables and not create local variables with the same
# names because these names appear on the left hand side of an assignment (=) statement
global caps_obj
global bone_obj
global influence_object
for ob in doc.object:
if ob == None:
continue
if ob.name.startswith("bone:"):
bone_obj = ob
if ob.name.lower() == "capsule":
caps_obj = ob
if bone_obj == None:
mqprint('No "bone:" object found')
return
if caps_obj == None:
mqprint('No "capsule" object found')
return
if bone_obj.numFace == 0:
mqprint('No faces in "bone:" object')
return
count = 0
for f in bone_obj.face:
if f.numVertex == 3:
count += 1
if count == 0:
mqprint('No triangles in "bone:" object')
return
if caps_obj.numFace == 0:
mqprint('No faces in "capsule" object')
return
obj = MQSystem.newObject()
idx = doc.addObject(obj)
influence_object = doc.object[idx]
# make the object the current object
doc.currentObjectIndex = idx
for f in bone_obj.face:
if f.numVertex != 3:
continue
data = getData(f, bone_obj)
if data:
drawCapsule(influence_object, data["short"], data["long"], data["centre"], data["direction"])
else:
mqprint("No data for face %d"%(f.id))
return
'''
If this script is executed (Run) its __name__ variable is the string "__main__" and the
statements in the following "if" block execute but if this script is imported into
another script its __name__ variable is not "__main__" so the "if" block statements
will not be executed
'''
if __name__ == "__main__":
# clear the Metasequoia Script Editor output window each time before the script is run
MQSystem.clearLog()
# print Python major.minor.revision version
# Metasequoia 3 uses Python 2.*, Metasequoia 4 uses Python 3.*
mqprint("Python version is %d.%d.%d"%(sys.version_info[0], sys.version_info[1], sys.version_info[2]))
# call the function "main()" to execute its code
main()
# let user know script has finished
mqprint("Script finished")
Below is a screenshot showing the skeleton in Metasequoia before running the script.
Below is a screenshot after running the script showing the zone of influence for the triangles of the skeleton.
No comments:
Post a Comment