Creating bones using MaxPlus (Python)

Creating bones using MaxPlus (Python)

Anonymous
Not applicable
3,395 Views
12 Replies
Message 1 of 13

Creating bones using MaxPlus (Python)

Anonymous
Not applicable

I can't find a way to create bones using MaxPlus library in 3ds Max. I figure out how to create them using MaxScript (in Python) and using the new pymxs module but it seems that in MaxPlus there is no way to create them. Anyone can help me? Thank you 🙂

0 Likes
3,396 Views
12 Replies
Replies (12)
Message 2 of 13

Anonymous
Not applicable

Hi guys, I have found two 'tricky' ways of creating bones using MaxPlus and accessing its node to modify its attributes.

 

First one is using pymxs module included in 3ds Max 2017:

 

 

# Create bone
rt = pymxs.runtime.BoneSys.createBone(pymxs.runtime.Point3(0,0,0), pymxs.runtime.Point3(0,5,0), pymxs.runtime.Point3(0,0,1))

# Access bone node through its name
node = MaxPlus.INode.GetINodeByName(rt.Name)

# Modify bone node attributes
node.Move(MaxPlus.Point3(0, 0, 5))
node.BaseObject.ParameterBlock.Length.Value =25

 

The problem with this method is its compatiblity. Only compatible with 3ds Max 2017.

 

The second way is using EvalMAXScript from MaxPlus module

 

# Create FPValue
res = MaxPlus.FPValue()

#Create bone and select it using MaxScript
test = MaxPlus.Core.EvalMAXScript("startPos=[0,0,0] \n endPos=[0,1,0] \n bnName=\"hola\" \n b = boneSys.createBone startPos endPos [0,0,1] \n b.length = 5 \n b.name = bnName \n obj=getNodeByName bnName \n select obj\n", res)

# Check if the MaxScript evaluation was correct
if test:
	# Access selected bone node
	node = MaxPlus.SelectionManager.GetNode(0)
	
	# Modify bone node attributes
	node.Move(MaxPlus.Point3(0, 0, 5))
	node.BaseObject.ParameterBlock.Length.Value = 25

This second method is a bit trickier but should work in 3ds Max 2014, 2015 and 2016 also (I only tested it in 3ds Max 2017)

 

If someone discovers if there is a cleaner way to create bones using MaxPlus, please tell me

 

Thank you 🙂

 

0 Likes
Message 3 of 13

har1sf0x
Advocate
Advocate

Hello there.

I just saw your post and even it's been a while i thought that it might be useful to reply. Your approach is very nice. Straight forward and simple.

I on the other hand created a python representation of the maxscript "BoneSys.createBone" without the zAxis prerequisite.

######
#created by har1sf0x
######

import MaxPlus as mp def direction (a , b): if not (type(a) == type(b) == mp.Point3): return None return mp.Point3(b.GetX()-a.GetX() , b.GetY()-a.GetY() , b.GetZ()-a.GetZ()) def dotProd (a , b): if not (type(a) == type(b) == mp.Point3): return None temp1 = a.Normalize() temp2 = b.Normalize() return (temp1.GetX()*temp2.GetX() + temp1.GetY()*temp2.GetY() + temp1.GetZ()*temp2.GetZ()) def crossProd (a , b , norm = True): if not (type(a) == type(b) == mp.Point3): return None if norm: return mp.Point3(a.GetY()*b.GetZ()-a.GetZ()*b.GetY() , a.GetZ()*b.GetX()-a.GetX()*b.GetZ() , a.GetX()*b.GetY()-a.GetY()*b.GetX()).Normalize() else: return mp.Point3(a.GetY()*b.GetZ()-a.GetZ()*b.GetY() , a.GetZ()*b.GetX()-a.GetX()*b.GetZ() , a.GetX()*b.GetY()-a.GetY()*b.GetX()) def createBone (start , end): if not (type(start) == type(end) == mp.Point3): return None if not (dotProd(start,mp.Point3(0,0,1)) == 1.0 or dotProd(start,mp.Point3(0,0,1)) == -1.0): upVec = mp.Point3(0,0,1) else: upVec = mp.Point3(1,0,0) dirVec = direction(start , end) frontVec = dirVec.Normalize() sideVec = crossProd(upVec , frontVec) upVec = crossProd(frontVec , sideVec) tm = mp.Matrix3(frontVec , sideVec , upVec , start) obj = mp.Factory.CreateGeomObject(MaxPlus.ClassIds.BoneGeometry) obj.ParameterBlock.Length.Value = dirVec.GetLength() node = mp.Factory.CreateNode(obj) node.SetWorldTM(tm) return node if __name__ == "__main__" : a = mp.Point3(20,0,0) b = mp.Point3(0,20,10) test = createBone(a,b)

I hope this could be helpful.

 

Happy New Year

 

P.S. if not mistaken there is a big "hole". In the Point3 class you cannot calculate the cross or dot product of 2 vectors while in the IPoint3 class you cannot use floats or normalize the vector while you can calculate the cross or dot product of 2 integer vectors. In both classes you cannot subtract 2 vectors...

 

P.S. attaching a .py is not permitted in a programming forum. security first but lol...

0 Likes
Message 4 of 13

har1sf0x
Advocate
Advocate

@Anonymous_koh_ath wrote:

In both classes you cannot subtract 2 vectors...

 

My bad, you can :D. So the direction function can be deleted and also change the line "dirVec = direction(start , end)" to "dirVec = end - start"

0 Likes
Message 5 of 13

har1sf0x
Advocate
Advocate

Hello there.

 

I also found the cross and dot products (tricky, if anyone finds it in documentation please share the link. i used help() to dig them out).

Dot Product:
MaxPlus.Point3(x1,y1,z1).__mod__(MaxPlus.Point3(x2,y2,z2)) or MaxPlus.Point3(x1,y1,z1)%MaxPlus.Point3(x2,y2,z2): remember to .Normalize() each Point3 if you want a result in range [-1,1]

Cross Product:
MaxPlus.Point3(x1,y1,z1).__xor__(MaxPlus.Point3(x2,y2,z2)) or MaxPlus.Point3(x1,y1,z1)^MaxPlus.Point3(x2,y2,z2): remember to .Normalize() each Point3 and the result in order to get a normalised perpendicular axis

 

Enjoy

0 Likes
Message 6 of 13

Anonymous
Not applicable

Has anyone looked at how to create a chain using this method?

The function creates a single bone, which is good and all, but limited in further application as it stands.

 

I guess that a list of some description, holding the start and end in callable tuples would be a start, but another level of functionality is required to parent the joints.

You would need the end point of the preceeding bone to be the first of the next tuple in the list, so I guess its not too much of a leap to add in a recursive parenting function to resolve the issue.

 

I just stumbled across this page, so will feedback to it once I have had a try.

 

Thanks for the initial code, very helpful.

 

 

0 Likes
Message 7 of 13

har1sf0x
Advocate
Advocate

Hello,

 

  Could you please try this code because i am not in a computer with 3ds max and i cannot check it:

import MaxPlus as mp
def createBone (start , end):
if not (type(start) == type(end) == mp.Point3):
return None
if not (start%mp.Point3(0,0,1) == 1.0) or (start%mp.Point3(0,0,1) == -1.0):
upVec = mp.Point3(0,0,1)
else:
upVec = mp.Point3(1,0,0)
dirVec = end - start
frontVec = dirVec.Normalize()
sideVec = (upVec^frontVec).Normalize()
upVec = (frontVec^sideVec).Normalize()
tm = mp.Matrix3(frontVec , sideVec , upVec , start)
obj = mp.Factory.CreateGeomObject(MaxPlus.ClassIds.BoneGeometry)
obj.ParameterBlock.Length.Value = dirVec.GetLength()
node = mp.Factory.CreateNode(obj)
node.SetWorldTM(tm)
return node

def createBoneChain (posList, index, parent=mp.Core.GetRootNode()):
'''
posList is a list of Point3 positions of the bones you want to create,
!!! INCLUDING !!! the end position of the end bone.
'''
bn = createBone(posList[index], posList[index+1])
bn.SetParent(parent)
if index < len(posList) - 2:
createBoneChain(posList, index + 1, parent=bn)

and please post back any corrections.

 

Enjoy,

har1sf0x

Message 8 of 13

Anonymous
Not applicable

Perhaps I am not using it right, but when I try the following:

 

bnList = [[0,0,0], [0,10,0], [0,20,0], [0,40,0], [0,60,0], [0,62,0]]

createBoneChain (bnList, index = 1, parent=mp.Core.GetRootNode())

 

I get this: 

 

Traceback (most recent call last):

File "U:/public/blah/blah/blah/maxPlus_createBoneChain.py", line 34, in <module>

createBoneChain (bnList, index = 1, parent=mp.Core.GetRootNode())

File "U:/blah/blah/createBoneChain/maxPlus_createBoneChain.py", line 27, in createBoneChain

bn.SetParent(parent)

AttributeError: 'NoneType' object has no attribute 'SetParent'

 

 

So, do I implicitly call each set of positions as a point 3 object?

The Index, is that a number I have to use as I have done?

 

Iam sure that its just down to a simple misuse of the code, so will test again using other methods of input.

 

Thanks again

0 Likes
Message 9 of 13

har1sf0x
Advocate
Advocate

Hello,

 

  You have to provide Point3 values not lists, e.g. bnList = [mp.Point3(0,0,0), mp.Point3(0,10,0), mp.Point3(0,20,0)]. You do not need to provide the parent as mp.Core.GetRootNode(), it is predefined. You can just type createBoneChain(bnList , 0). The code is 0-based so the index must be 0 in order to start at your first entry, e.g. mp.Point(0,0,0).

You can alter the code so as you do not need to specify the index:

def createBoneChain(posList, index = 0, parent = mp.Core.GetRootNode()):

If you change that you must change also the last line, in the if statement:

createBoneChain(posList, index=(index + 1), parent=bn)

Then you will call the function with just: createBoneChain(bnList)

 

Enjoy,

har1sf0x

Message 10 of 13

Anonymous
Not applicable

Hi, I made some changes and attached is the code as it is in the script. I did this a s posting the code can have format issues, so its easier to see in the image where I have possibly gone awry. If irun the code, I get this:

 

Traceback (most recent call last):

File "U:/blah/blah/blah/createBones/maxPlus_createBoneChain/maxPlus_createBoneChain.py", line 34, in <module>

createBoneChain(bnList, 0)

File "U:/blah/blah/blah/createBones/maxPlus_createBoneChain/maxPlus_createBoneChain.py", line 27, in createBoneChain

bn.SetParent(parent)

AttributeError: 'NoneType' object has no attribute 'SetParent'

 

 

Is setParent not a MEL command? Perhaps its been defined in max as the same, but I have not checked yet.boneCreationCode.jpg

0 Likes
Message 11 of 13

Anonymous
Not applicable

I am using Max 2016, so that setParent may be a method/attribute of a later version. I will check

0 Likes
Message 12 of 13

har1sf0x
Advocate
Advocate

Hello,

 

 the problem was the indentation in the else block of the createBone function. I also did a couple of changes in the second if condition and in its else block as well as i added width and height parameters now that i am on a computer with max (2016 as well). So here it is the createBone and createBoneChain definitions:

 

def createBone (start, end, width = 2.0, height = 2.0):
	if not (type(start) == type(end) == mp.Point3):
		return None
	dirVec = end - start
	if not (dirVec.Normalize()%mp.Point3(0,0,1) == 1.0) or (dirVec.Normalize()%mp.Point3(0,0,1) == -1.0):
		upVec = mp.Point3(0,0,1)
	else:
		upVec = mp.Point3(0,1,0)
	
	frontVec = dirVec.Normalize()
	sideVec = (upVec^frontVec).Normalize()
	upVec = (frontVec^sideVec).Normalize()
	tm = mp.Matrix3(frontVec , sideVec , upVec , start)
	obj = mp.Factory.CreateGeomObject(MaxPlus.ClassIds.BoneGeometry)
	obj.ParameterBlock.Length.Value = dirVec.GetLength()
       	obj.ParameterBlock.Width.Value = width
	obj.ParameterBlock.Height.Value = height
	node = mp.Factory.CreateNode(obj)
	node.SetWorldTM(tm)
	return node
def createBoneChain (posList, width = 4.0, height = 4.0, index = 0, parent=mp.Core.GetRootNode()):
	'''
	posList is a list of Point3 positions of the bones you want to create,
	!!! INCLUDING !!! the end position of the end bone.
	'''
	bn = createBone(posList[index], posList[index+1], width, height)
	bn.SetParent(parent)
	if index < len(posList) - 2:
		createBoneChain(posList, width, height, index + 1, bn)

Enjoy,

har1sf0x

 

p.s. i added the python file as .txt

 

Message 13 of 13

Anonymous
Not applicable

Marvelous. Thanks for your input and help with this. I am transferring old rigging scripts and can use this method to update the code.

 

Once done, I will post back with rigging scripts of my own that others can use/discuss/critique.

 

Thanks

0 Likes