[Closed] Replace bone object in skin modifier maxscript
Hi,
I have a problem where I have a CAT rig that’s already skinned and I want to replace it with a “mesh copy” of the CAT rig, that have baked key-frames on it, if it only was one simple mesh that were skinned I would use something like the Skin Utilities in max, but because its multiple objects that are skinned and each is kind of poly heavy and I will need to do this multiple times, a script is probably the only solution. I wrote two other scripts to do the baking key-frames part but I would appreciate if someone would like help to write the bone/skin replace script because it looks a bit complex to get working (or just help to point me in the right direction).
I recorded a little video maybe showing a bit better what the problem is.
Video(1440p):
Thanks.
wow! i really like your art.
i want to help you with the script.
but unfortunately the task is not clear for me.
cat…, skin…, baked animation…
try to clarify the task a bit better.
Thanks,
I created a simplified scene where you can test the script in, its fundamentally the same as the scene shown in the original video.
Downlod scene here: [3ds max 2013 file] (might not work in newer versions then 3ds max 2013)
“New bone” mesh with baked animation on it:
#IMG1
The CAT rig that’s currently the objects that’s being used as bones for the skinned objects:
#IMG2
The skinned objects:
#IMG3
Skin modifier list:
#IMG4
Here is a start of the script:
if Replace_Bone != undefined then
(
DestroyDialog Replace_Bone
)
rollout Replace_Bone "Replace Bone"
(
global array_skin_objects = #()
global array_new_bone_objects = #()
Button btn_select_skin "Add selected skin objects" tooltip:"rightclick to clear"
Button btn_select_new_bone "Add selected objects to new bones" tooltip:"rightclick to clear"
Button btn_run_bone_script "RUN script"
on btn_select_skin pressed do
(
if $ != undefined then
(
array_skin_objects = (selection as array)
Replace_Bone.btn_select_skin.text = "Right click to clear"
print "added skin objects"
)
)
on btn_select_skin rightclick do
(
array_skin_objects = #()
Replace_Bone.btn_select_skin.text = "Add selected skin objects"
print "skin objects cleared"
)
on btn_select_new_bone pressed do
(
if $ != undefined then
(
array_new_bone_objects = (selection as array)
Replace_Bone.btn_select_new_bone.text = "Right click to clear"
print "added new bone objects "
)
)
on btn_select_new_bone rightclick do
(
array_new_bone_objects = #()
Replace_Bone.btn_select_new_bone.text = "Add selected objects to new bones"
print "bone objects cleared"
)
on btn_run_bone_script pressed do
(
if (((array_skin_objects as string) != "#()") and (array_new_bone_objects as string) != "#()" )then
(
for i = 1 to array_skin_objects.count do
(
if (ClassOf array_skin_objects[i].modifiers[1]) == skin then
(
-- select the bones in the "name" list of that modifier and replace them with the objects that have the same name in the "array_new_bone_objects"
)
)
print "run skript"
)
else
(
print"add both skin and bone objects before pressing run"
)
)
)
createdialog Replace_Bone width:200 height:100
So to try explaining what I need: I have objects that already have a skin modifier on it (img3) and its using the CAT rig objects (img2) as bone objects, I want to replace those bone objects in that skin modifier (img4) with the mesh objects that have the baked animation on it (img1).
#Video2
//youtu.be/6w3aWPpOV3k
Hey! Why do you need to replace it with meshes that have the animation baked into it? What are you using it for? you could simple cache the geometry if this is not being used for games.
Hi, basically its just cause my main CAT rig gets corrupted or gets super unstable when trying to open it in a newer version of 3ds max, and I need to use a newer version of 3ds max for my main scene cause of some plugin stuff, so my plan was to do the CAT animations in 3ds max 2013 and then replace the cat rig with baked “mesh bones” then import the skinned objects and the baked “mesh bones” in 3ds max 2014,
but yeah in hindsight it probably would have been better to use like the point cache modifier and made a script that first saves the point cache of the selected objects and then it removes the old skin modifier. I might end up having to do this, unless I can get the bone replace script to work, because It would also be nice to just have the bone replace script for future stuff.
Any script that works would be good. (;
This is the current code I have written, its probably not a nice way of doing it and its probably much slower than it needs to be, just cause my lack of knowledge when it come to the maxscript language, but it works, at least the replace the bones part, it don’t transfers the skin weights correctly, and that’s the part I knew that I would have the most problems with.
So how would I make that work?
if Replace_Bone != undefined then
(
DestroyDialog Replace_Bone
)
rollout Replace_Bone "Replace Bone"
(
global array_skin_objects = #()
global array_new_bone_objects = #()
global ary_old_bones_temp = #()
Button btn_select_skin "Add selected skin objects" tooltip:"rightclick to clear"
Button btn_select_new_bone "Add selected objects to new bones" tooltip:"rightclick to clear"
Button btn_run_bone_script "RUN script"
on btn_select_skin pressed do
(
if $ != undefined then
(
array_skin_objects = (selection as array)
Replace_Bone.btn_select_skin.text = "Right click to clear"
print "added skin objects"
)
)
on btn_select_skin rightclick do
(
array_skin_objects = #()
Replace_Bone.btn_select_skin.text = "Add selected skin objects"
print "skin objects cleared"
)
on btn_select_new_bone pressed do
(
if $ != undefined then
(
array_new_bone_objects = (selection as array)
Replace_Bone.btn_select_new_bone.text = "Right click to clear"
print "added new bone objects "
)
)
on btn_select_new_bone rightclick do
(
array_new_bone_objects = #()
Replace_Bone.btn_select_new_bone.text = "Add selected objects to new bones"
print "bone objects cleared"
)
on btn_run_bone_script pressed do
(
if (((array_skin_objects as string) != "#()") and (array_new_bone_objects as string) != "#()" )then
(
for k = 1 to array_skin_objects.count do
(
if (ClassOf array_skin_objects[k].modifiers[1]) == skin then
(
select array_skin_objects[k]
max modify mode
modPanel.setCurrentObject array_skin_objects[k].modifiers[1]
ary_old_bones_temp = #()
for i = 1 to (skinOps.getNumberBones $.skin) do
(
select (getnodebyname (skinOps.getBoneName $.skin i 0))
ary_old_bones_temp = ary_old_bones_temp + (selection as array)
select array_skin_objects[k]
)
for j = 1 to ary_old_bones_temp.count do
(
for i = 1 to array_new_bone_objects.count do
(
if ary_old_bones_temp[j].name == array_new_bone_objects[i].name then
(
skinOps.removebone $.skin j
skinOps.addbone $.skin array_new_bone_objects[i] -1
)
)
)
)
)
print "run skript"
)
else
(
print"add both skin and bone objects before pressing run"
)
)
)
createdialog Replace_Bone width:200 height:100
will a function that takes three parameters – skin modifier, old bone, and new bone – be good?
like:
fn xchangeSkinBone sk source target = (...)
because skinops works only with skin modifier that is current modpanel object the function might look as:
fn xchangeSkinBone source target = if iskindof (sk = modpanel.getcurrentobject()) Skin do
(
...
)
fn collectSkinBones sk =
(
for b in (refs.dependson sk) where isvalidnode b collect b
)
fn xchangeSkinBone sk source target remove:off add:on =
(
local result = off
if sk == modpanel.getcurrentobject() and iskindof sk Skin do
(
bones = collectSkinBones sk
if (sid = finditem bones source) > 0 do
(
if finditem bones target == 0 and add do
(
skinops.addbone sk target 1
bones = collectSkinBones sk
)
if (tid = finditem bones target) > 0 do
(
verts = #{1..skinops.getnumbervertices sk}
skinops.selectvertices sk verts
skinops.bakeselectedverts sk
for v in verts do
(
bb = #()
ww = #()
for k=1 to skinops.getvertexweightcount sk v do
(
append bb (skinops.getvertexweightboneid sk v k)
append ww (skinops.getvertexweight sk v k)
)
if (k = finditem bb sid) > 0 do
(
bb[k] = tid
skinops.replacevertexweights sk v tid 1.0
skinops.setvertexweights sk v bb ww
result = on
)
)
if remove do skinops.removebone sk tid
)
)
)
result
)
/******** using
xchangeSkinBone <skin modifier> [remove:] [add:]
***********/
sorry, this might seem super obvious to you, but how do I actually use the pretty code you have written?
you have to call the function:
xchangeSkinBone sk source target remove:on add:on
where:
sk is skin modifier ($.skin for example)
source is old bone to replace from ($old)
target is a new bone to replace to ($new)
remove is an optional if want to remove old bone from skin
add is an optional if you want to add new bone to skin
Thanks, but I’m still probably messing it up, here is how I implemented it, but it goes horrible wrong.
fn collectSkinBones sk =
(
for b in (refs.dependson sk) where isvalidnode b collect b
)
fn xchangeSkinBone sk source target remove:off add:on =
(
local result = off
if sk == modpanel.getcurrentobject() and iskindof sk Skin do
(
bones = collectSkinBones sk
if (sid = finditem bones source) > 0 do
(
if finditem bones target == 0 and add do
(
skinops.addbone sk target 1
bones = collectSkinBones sk
)
if (tid = finditem bones target) > 0 do
(
verts = #{1..skinops.getnumbervertices sk}
skinops.selectvertices sk verts
skinops.bakeselectedverts sk
for v in verts do
(
bb = #()
ww = #()
for k=1 to skinops.getvertexweightcount sk v do
(
append bb (skinops.getvertexweightboneid sk v k)
append ww (skinops.getvertexweight sk v k)
)
if (k = finditem bb sid) > 0 do
(
bb[k] = tid
skinops.replacevertexweights sk v tid 1.0
skinops.setvertexweights sk v bb ww
result = on
)
)
if remove do skinops.removebone sk tid
)
)
)
result
)
if Replace_Bone != undefined then
(
DestroyDialog Replace_Bone
)
rollout Replace_Bone "Replace Bone"
(
global array_skin_objects = #()
global array_new_bone_objects = #()
global array_old_bone_objects = #()
Button btn_select_skin "Add selected skin objects" tooltip:"rightclick to clear"
Button btn_select_new_bone "Add selected objects to new bones" tooltip:"rightclick to clear"
Button btn_select_current_bone "Add selected objects to current bones" tooltip:"rightclick to clear"
Button btn_run_bone_script "RUN script"
on btn_select_skin pressed do
(
if $ != undefined then
(
array_skin_objects = (selection as array)
Replace_Bone.btn_select_skin.text = "Right click to clear"
print "added skin objects"
)
)
on btn_select_skin rightclick do
(
array_skin_objects = #()
Replace_Bone.btn_select_skin.text = "Add selected skin objects"
print "skin objects cleared"
)
on btn_select_new_bone pressed do
(
if $ != undefined then
(
array_new_bone_objects = (selection as array)
Replace_Bone.btn_select_new_bone.text = "Right click to clear"
print "added new bone objects "
)
)
on btn_select_new_bone rightclick do
(
array_new_bone_objects = #()
Replace_Bone.btn_select_new_bone.text = "Add selected objects to new bones"
print "new bone objects cleared"
)
on btn_select_current_bone pressed do
(
array_old_bone_objects = (selection as array)
Replace_Bone.btn_select_current_bone.text = "Right click to clear"
print "added new bone objects "
)
on btn_select_current_bone rightclick do
(
array_old_bone_objects = #()
Replace_Bone.btn_select_current_bone.text = "Add selected objects to current bones"
print "current bone objects cleared"
)
on btn_run_bone_script pressed do
(
for i = 1 to array_skin_objects.count do
(
select array_skin_objects[i]
max modify mode
modPanel.setCurrentObject array_skin_objects[i].modifiers[1]
for j = 1 to array_old_bone_objects.count do
(
for k = 1 to array_new_bone_objects.count do
(
if array_old_bone_objects[j].name == array_new_bone_objects[k].name then
(
global sk = array_skin_objects[i].skin
global source = array_old_bone_objects[j]
global target = array_new_bone_objects[k]
xchangeSkinBone sk source target remove:on add:on
)
)
)
)
)
)
createdialog Replace_Bone width:200 height:110
here is with simple UI:
global XChangeSkinDialogPos = unsupplied
try (destroydialog XChangeSkinDialog) catch()
rollout XChangeSkinDialog "X-Change Skin Bones" width:200
(
button xchange_bt "X-Change" width:190 align:#right offset:[8,0]
label xchange_lb "nodes xchanged:" align:#left across:2
label xchange_nm "0" align:#left offset:[0,0]
fn collectSkinBones sk =
(
for b in (refs.dependson sk) where isvalidnode b collect b
)
fn findSkinBoneID sk bone bones: =
(
if bones == unsupplied do bones = collectSkinBones sk
finditem bones bone
)
fn getBoneID sk bone =
(
local id = 0
for k=1 to skinops.getnumberbones sk while id == 0 do
(
if bone.name == skinops.getbonename sk k 1 do id = k
)
id
)
fn xchangeSkinBone sk source target remove:off add:on =
(
local result = off
if sk == modpanel.getcurrentobject() and iskindof sk Skin do
(
bones = collectSkinBones sk
if (sid = finditem bones source) > 0 do
(
if finditem bones target == 0 and add do
(
skinops.addbone sk target 1
bones = collectSkinBones sk
sid = finditem bones source
)
if (tid = finditem bones target) > 0 do
(
verts = #{1..skinops.getnumbervertices sk}
skinops.selectvertices sk verts
skinops.bakeselectedverts sk
for v in verts do
(
bb = #()
ww = #()
for k=1 to skinops.getvertexweightcount sk v do
(
append bb (skinops.getvertexweightboneid sk v k)
append ww (skinops.getvertexweight sk v k)
)
if (k = finditem bb sid) > 0 do
(
bb[k] = tid
skinops.replacevertexweights sk v tid 1.0
skinops.setvertexweights sk v bb ww
result = on
)
)
if remove do skinops.removebone sk sid
)
)
)
result
)
fn getTargetPair node =
(
getnodebyname (node.name + "_new")
)
on xchange_bt pressed do undo "X-Change" on
(
count = 0
sk = modpanel.getcurrentobject()
if not iskindof sk Skin then
(
messagebox "Skin modifier has to be open in Modifiers Panel"
)
else
(
bones = collectSkinBones sk
for source in bones where (target = getTargetPair source) != undefined do
(
if (act = xchangeSkinBone sk source target remove:on add:on) do count += 1
)
)
xchange_nm.text = count as string
ok
)
on XChangeSkinDialog close do
(
XChangeSkinDialogPos = getdialogpos XChangeSkinDialog
)
)
createdialog XChangeSkinDialog pos:XChangeSkinDialogPos
run the code.
select skin (it has to be open in modifiers panel)
all old bones have to have a pair of new bone.
the name of an old bone has to be different by suffix from its target:
old bone name – “theBone1”
new bone name – “theBone1_new”
if i find a pair i will x-change them
I still cant get it to work, is it because of the naming convention I’m using?
This is what I’m doing:
[video]
it’s hard to debug by using a video only. make a simple scene that shows the problem. it might help.
I already have made a simple scene, I posted the download link to it in my second post in this thread, that’s the scene I’m using in the video, I thought you used it while writing your code, but I can post it again if you missed it.
Download here:
http://www.patan77.com/download/Skin_CAT_bone_replace_scriptScene_.zip