Author Topic: Useful scripts for 3ds Max  (Read 43773 times)

0 Members and 1 Guest are viewing this topic.

Offline zookeeper

  • *knock knock* Who's there? Poe. Poe who?
  • 210
Re: Useful scripts for 3ds Max
How about this:
Right. Kinda obvious now that I think of it. :banghead:

 

Offline zookeeper

  • *knock knock* Who's there? Poe. Poe who?
  • 210
Re: Useful scripts for 3ds Max
Added two more little scripts which might be of use, and also (finally) put them all into [ code ] blocks instead of attachments so they don't get lost.

  
Re: Useful scripts for 3ds Max
Here's a few useful ones:


Used to create properties for subobjects (if it's a turret with uvec, fvec it'll copy the values to the destroyed version)
Code: [Select]
macroScript GiveThemProperties category:"Freespace"
(
changeCount = 0

uVecValue = ""
fVecValue = ""

isThisATurret = false
turretDestroyedLocation = -1

-- Go through all the selected objects
for i = 1 to selection.count do
(
test = ""
dummyCount = 0

-- Let's see what we're actually got
if (findString selection[i].name "helper" != undefined) then
continue -- helper object
else if (findString selection[i].name "thruster" != undefined) then
continue -- thruster dummy points
else if (findString selection[i].name "bank" != undefined) then
continue -- missile, gun and glow banks
else if (findString selection[i].name "eye" != undefined) then
continue -- eyepoint
else if (findString selection[i].name "destroyed" != undefined) then
(
-- Possibly a destroyed turret (could be another subsystem)
turretDestroyedLocation = i
continue ;
)
else if (findString selection[i].name "-" != undefined) then
continue -- turret arms, destroyed subsystems
else if (findString selection[i].name "firepoint" != undefined) then
continue -- firepoints
else if (findString selection[i].name "turret" != undefined) then
(
isThisATurret = true
)

-- Retrieve the existing properties, we need to save the uvec,fvec
objectProperties = getUserPropBuffer selection[i]
listofProp = filterString objectProperties "\n\r"

foundUVec = false

-- Save the uvec if theres any
for j = 1 to listofProp.count do
(
if (findstring listofProp[j] "$uvec" != undefined) then
(
foundUVec = true
uVecValue = listofProp[j]
break
)
)

-- Save the fvec if there's any (there should be)
for j = 1 to listofProp.count do
(
if (findstring listofProp[j] "$fvec" != undefined) then
(
fVecValue = listofProp[j]
break
)
)

-- Paste the new data back to the object's property
test = "$special=subsystem\r\n$fov=180\r\n$name=" + selection[i].name

if (foundUVec) then
(
test = test + "\r\n" + uVecValue + "\r\n" + fVecValue
)
changeCount = changeCount + 1

setUserPropBuffer selection[i] test
)

--If this was a turret and had a destroyed version we need to copy the uvec and fvec if they exist
if (isThisATurret) then
(
if (turretDestroyedLocation != -1) then
(
test = uVecValue + "\r\n" + fVecValue
setUserPropBuffer selection[turretDestroyedLocation] test
)
)

if (changeCount == 0) then
(
MessageBox "Nothing useable was selected"
)
)


Remove ALL properties from anything
Code: [Select]
macroScript RemoveProperties category:"Freespace"
(

for i = 1 to selection.count do
(
setUserPropBuffer selection[i] ""
)
)


Numbering turrets (this also renames the turret0X-arm).  Just group select the turet, arm (if any), firepoints, firepoint, helper and destroyed (if exists) click the Renumber Turret and repeat for each additional turret, it'll convert their names to a numbered "turret0X" correctly.  It does not create uvecs or fvecs, use the other scripts for that. 
Code: [Select]
macroScript TurretNumber category:"Freespace"
(
turretNumber = 1

rollout rename_rollout "Turret Renumbering"
(
button ResetCount "Reset Turret Count"
button RenameBase "Renumber Turret"

on ResetCount pressed do
(
turretNumber = 1
)

on RenameBase pressed do
(
print "hello"

changeCount = 0

if (turretNumber < 10) then
newName = "turret0" + turretNumber as String
else
newName = "turret" + turretNumber as String

for loop = 1 to selection.Count do
(
oldname = selection[loop].name
if (findString oldName "turret" != undefined) then
(
if (findString oldName "-arm" != undefined) then
(
selection[loop].Name = newName + "-arm"
changeCount = changeCount + 1
)
else if (findString oldName "-destroyed" != undefined) then
(
selection[loop].Name = newName + "-destroyed"
changeCount = ChangeCount + 1
)
else
(
selection[loop].Name = newName
changeCount = changeCount + 1
)
)
)

if (changeCount == 0) then
(
MessageBox "Nothing useable was selected"
)
else
(
turretNumber = turretNumber + 1
)
)

)--end rollout

createDialog rename_rollout

)


Used to renumber engine thrusters objects (thruster01, thruster02, thruster03.....)  Note this does not create the properties (use the first script above)
Code: [Select]
macroScript EngineNumber category:"Freespace"
(
engineNumber = 1

rollout rename_rollout "Engine Renumbering"
(
button ResetCount "Reset Engine Count"
button RenameBase "Renumber Engine"

on ResetCount pressed do
(
engineNumber = 1
)

on RenameBase pressed do
(
changeCount = 0

if (engineNumber < 10) then
newName = "engine0" + engineNumber as String
else
newName = "engine" + engineNumber as String

for loop = 1 to selection.Count do
(
oldname = selection[loop].name
if (findString oldName "engine" != undefined) then
(
if (findString oldName "-destroyed" == undefined) then
(
selection[loop].Name = newName
changeCount = changeCount + 1
)
else
(
selection[loop].Name = newName + "-destroyed"
changeCount = changeCount + 1
)
)
)

if (changeCount == 0) then
(
MessageBox "Nothing useable was selected"
)
else
(
engineNumber = engineNumber + 1
)
)

)--end rollout

createDialog rename_rollout

)


Same as thrusters but for intakes (if you do have them)
Code: [Select]
macroScript IntakeNumber category:"Freespace"
(
intakeNumber = 1

rollout rename_rollout "Intake Renumbering"
(
button ResetCount "Reset Intake Count"
button RenameBase "Renumber Intake"

on ResetCount pressed do
(
intakeNumber = 1
)

on RenameBase pressed do
(
changeCount = 0

if (intakeNumber < 10) then
newName = "intake0" + intakeNumber as String
else
newName = "intake" + intakeNumber as String

for loop = 1 to selection.Count do
(
oldname = selection[loop].name
if (findString oldName "intake" != undefined) then
(
if (findString oldName "-destroyed" == undefined) then
(
selection[loop].Name = newName
changeCount = changeCount + 1
)
else
(
selection[loop].Name = newName + "-destroyed"
changeCount = changeCount + 1
)
)
)

if (changeCount == 0) then
(
MessageBox "Nothing useable was selected"
)
else
(
intakeNumber = intakeNumber + 1
)
)

)--end rollout

createDialog rename_rollout

)

That's cool and ....disturbing at the same time o_o  - Vasudan Admiral

"Don't play games with me. You just killed someone I like, that is not a safe place to stand. I'm the Doctor. And you're in the biggest library in the universe. Look me up."

"Quick everyone out of the universe now!"

 
Re: Useful scripts for 3ds Max
4 years later!!  :eek2:

Oh you've added a box, modified it and then copied it many times (be it copy or instances doesn't matter).  Then you realize you probably should have named them better, whoops!  Well this script asks for a name then applies it plus padded number to it... AKA Box becomes Grip01, Grip02, Grip03.... Grip10, Grip11...etc
Rename script:
Code: [Select]
macroScript RenameObjects category:"Fast Utils"
(
rollout renameObjs_rollout "Renaming Objects" width:162 height:72
(
label lbl_Name "New Name"
edittext edit_NewName ""
button btnStart "Start"

on btnStart pressed  do
(
if (edit_NewName.text == "") then
(
MessageBox "You need to give it a name first"
return 0
)

if (selection.Count == 0) then
(
MessageBox "You need to select at least one entry first"
return 0
)

for loop = 1 to selection.Count do
(
-- This keeps the number formatting nice and neat
prefex = ""

upper = ceil ((selection.Count as float)/ (10 as float)) * 10
lower = ceil ((loop as float)/ (10 as float)) * 10

while (lower < upper) do
(
prefex = prefex + "0"
lower = lower * 10
)

selection[loop].Name = edit_NewName.Text + prefex + loop as String
)

destroydialog renameObjs_rollout
)
)

createDialog renameObjs_rollout

)

Next script, let's say you have all those boxes now named properly, but let's say you want to change something on them, thats when you realize you made a copy and not a reference/instance, oh  :mad:. Well this fixes that (with some caveats).  Simply select the parent object, then select all of the children and it will replace each of the children with a copy/instance/reference of the parent.  The selected object is then deleted. It will also orientate and scale them correctly, HOWEVER, if you have rotated and/or scaled the object to be replaced then did a xreset, the scale/angle info was lost and it'll simply put a new object in the (0,0,0) orientation and 100% scale.
Code: [Select]
macroScript ReplaceWithClones category:"Fast Utils"
(
rollout ReplaceObjs_rollout "Replace objects with clones" width:162 height:227
(
 
button btnSelectParent "Select as Parent" pos:[34,7] width:93 height:21
 
button btnProcessInstances "Replace Selected" pos:[49,191] width:56 height:21
edittext edtParentName "" pos:[4,36] width:149 height:25 enabled:false
radiobuttons rdo1 "Replace By..." pos:[10,69] width:73 height:62 labels:#("Copy", "Instance", "Reference") default:2
checkbox chk1 "Delete original" pos:[10,150] width:96 height:20 checked:true


on btnSelectParent pressed do
(
if (selection.count != 1) then
(
MessageBox "Missing object for parent"
return 0
)

parent = selection[1]
edtParentName.text = selection[1].Name
)
on btnProcessInstances pressed do
(
if (edtParentName.Text == "") then
(
MessageBox "First need to select a parent object to copy"
return 0
)

for loop = 1 to selection.Count do
(
case rdo1.state of
(
1:
child = copy parent
2:
child = instance parent
3:
child = reference parent
)

child.pos = selection[loop].pos
child.rotation = selection[loop].rotation
child.scale = selection[loop].scale

)

if (chk1.Checked) then
(
for loop = 1 to selection.Count do
(
delete selection[1]
)
)
)
)
createDialog ReplaceObjs_rollout

)

And last....
This isn't really useful for POF as exporting to DAE and PCS can handle it well, but it seems to clear up problems exporting to Unreal 4.  What this script does is save your selected objects first, make copies of those objects, collapse just those, stuff those into a layer "ExportLayer", select all those and let's you export however necessary.  Finally there is a cleanup Finish, which deletes those objects, removes the layer and then reselects the objects you had originally selected, as if nothing had happened.  It's not foolproof and might be buggy, thats why I had the collapsed meshes put into a unique layer, so if things go bad you can simply delete everything from that layer.  Oh and ya don't have a "ExportLayer" in your project.  It can take sometime to actually process, depending on how complicated your model is.  It takes about a half minute to export a 300K model with some 130+ objects.  Couple other notes: if you see a bug in your model after prepping, don't fix it there, it'll just get deleted, nuke that export (hit "finish") fix the problem and try again.  Two: Groups seem to cause some problems, they will appear as big boxes that are empty, just delete them for now, the actual meshes will still be there (they're no longer part of the group upon resetting).
Code: [Select]
macroScript PrepForExport category:"Fast Utils"
(
mySavedArray = #()
rollout ExportPrep_rollout "Prep for Export" width:162 height:68
(
 
button btnSelect "Select object(s)" pos:[34,7] width:93 height:21
 
button btnFinish "Finished exporting" pos:[34,40] width:93 height:21

on btnSelect pressed do
(
if (selection.Count == 0) then
(
MessageBox "You need to select at least one entry first"
return 0
)


myNewArray = #()

layer = LayerManager.newLayer ()
layer.setName "ExportLayer"

for loop = 1 to selection.count do
(
entry = copy selection[loop]
print entry.Name

collapseStack entry
ResetXForm entry
collapseStack entry
layer.addnode entry

append myNewArray entry

windows.processpostedmessages ()

)

for obj in selection do
(
append mySavedArray obj
windows.processpostedmessages ()
)
max select none
select myNewArray

result = IsolateSelection.EnterIsolateSelectionMode()

MessageBox "You may now export your model"
)
on btnFinish pressed do
(
max select none

layer = LayerManager.GetLayerFromName "ExportLayer"

if (layer == undefined) then
(
return 0
)

layer.nodes &temp

select temp
delete selection

LayerManager.DeleteLayerByName "ExportLayer"

result = IsolateSelection.ExitIsolateSelectionMode ()
select mySavedArray
)
)
createDialog ExportPrep_rollout

)

Whew!
« Last Edit: March 29, 2015, 04:54:23 am by Scooby_Doo »
That's cool and ....disturbing at the same time o_o  - Vasudan Admiral

"Don't play games with me. You just killed someone I like, that is not a safe place to stand. I'm the Doctor. And you're in the biggest library in the universe. Look me up."

"Quick everyone out of the universe now!"

 
Re: Useful scripts for 3ds Max
Two years latter I've got some more for you...

If you have more than one object you want to replace with something else this script makes it easy.  Let's say you have a bunch of the same object but decide you want to replace or modify it. You realize you forget to make references or instances of it. Opps...

First select the parent object, hit "select as parent" then select all the objects you want replaced, then hit "Replace Selected".  You can choose to copy, instance or replace and you can choose to ignore the scale and/or rotation.  And finally you don't have to actually delete the original one.  Couple of caveats: The new objects are stuck in whichever layer is currently active, it may not be the one that the original object is located in (that should be fixable), and if the object(s) to be replaced have been xform resetted, you probably won't get the correct results.
Code: [Select]
macroScript ReplaceWithClones category:"Fast Utils"
(
rollout ReplaceObjs_rollout "Replace objects with clones" width:162 height:227
(
 
button btnSelectParent "Select as Parent" pos:[34,7] width:93 height:21
 
 
button btnProcessInstances "Replace Selected" pos:[3,191] width:145 height:21
edittext edtParentName "" pos:[4,36] width:149 height:25 enabled:false
radiobuttons rdoReplaceBy "Replace By..." pos:[10,69] width:73 height:62 labels:#("Copy", "Instance", "Reference") default:2
checkbox chkDeleteOrg "Delete original" pos:[10,166] width:96 height:20 checked:true
checkbox chkIgnScale "Ignore scale" pos:[10,150] width:103 height:17
checkbox chkIgnRot "Ignore rotation" pos:[9,132] width:103 height:15

global parent

on btnSelectParent pressed do
(
if (selection.count != 1) then
(
MessageBox "Misisng object for parent"
return 0
)

if (selection.Count == 0) then
(
MessageBox "You need to select at least one entry first"
return 0
)

parent = selection[1]
edtParentName.text = selection[1].Name
)
on btnProcessInstances pressed do
(
if (edtParentName.Text == "") then
(
MessageBox "First need to select a parent object to copy"
return 0
)

props = getUserpropBuffer parent

for loop = 1 to selection.Count do
(
case rdoReplaceBy.state of
(
1:
child = copy parent
2:
child = instance parent
3:
child = reference parent
)

if (chkIgnScale.checked == false) then
(
child.scale = selection[loop].scale
)


if (chkIgnRot.checked == false) then
(
child.rotation = selection[loop].rotation
)


child.pos = selection[loop].pos
setuserPropBuffer child props

)

if (chkDeleteOrg.Checked) then
(
for loop = 1 to selection.Count do
(
delete selection[1]
)
)
)
)
createDialog ReplaceObjs_rollout

)
That's cool and ....disturbing at the same time o_o  - Vasudan Admiral

"Don't play games with me. You just killed someone I like, that is not a safe place to stand. I'm the Doctor. And you're in the biggest library in the universe. Look me up."

"Quick everyone out of the universe now!"

 
Re: Useful scripts for 3ds Max
LODDING

This script let's you hide/show lod levels quickly and easily.  You have 4 lod levels to use, simply select the object(s) you want on a specific level and hit the "+" next to the LOD level.  "-" will remove it from that level.  You can then use the show/hide buttons for those objects.  Hint: Objects can be on more than one lod level.

Code: [Select]
macroScript LodDisplay category:"Fast Utils"
(
rollout renameObjs_rollout "LODS" width:344 height:240
(
 

button btnShow0 "Show" pos:[48,66] width:107 height:34
button btnHide0 "Hide" pos:[160,66] width:104 height:32
label lbl4 "LOD 0" pos:[8,80] width:32 height:16
button btnAdd0 "+" pos:[272,66] width:32 height:32
button btnRemove0 "-" pos:[304,66] width:32 height:32
button btnShow1 "Show" pos:[48,104] width:107 height:34
button btnHide1 "Hide" pos:[160,104] width:104 height:32
label lbl7 "LOD 1" pos:[8,118] width:32 height:16
button btnAdd1 "+" pos:[272,104] width:32 height:32
button btnRemove1 "-" pos:[304,104] width:32 height:32
button btnShow2 "Show" pos:[48,144] width:107 height:34
button btnHide2 "Hide" pos:[160,144] width:104 height:32
label lbl8 "LOD 2" pos:[8,158] width:32 height:16
button btnAdd2 "+" pos:[272,144] width:32 height:32
button btnRemove2 "-" pos:[304,144] width:32 height:32
button btnShow3 "Show" pos:[48,184] width:107 height:34
button btnHide3 "Hide" pos:[160,184] width:104 height:32
label lbl9 "LOD 3" pos:[8,198] width:32 height:16
button btnAdd3 "+" pos:[272,184] width:32 height:32
button btnRemove3 "-" pos:[304,184] width:32 height:32

fn ShowChildren parent levelText =
(
for obj in parent do
(
test = getUserPropBuffer obj

if matchpattern test pattern:levelText then
unhide obj
)

for children in parent.children do
(
ShowChildren children levelText
)
)

fn HideChildren parent levelText =
(
for obj in parent do
(
test = getUserPropBuffer obj

if matchpattern test pattern:levelText then
hide obj
)

for children in parent.children do
(
HideChildren children levelText
)
)

on btnShow0 pressed do
(
for obj in rootscene.world.children do
(
ShowChildren obj "*lod=0*"
)

)

on btnHide0 pressed do
(
for obj in rootscene.world.children do
(
HideChildren obj "*lod=0*"
)
)

on btnAdd0 pressed do
(
for loop = 1 to selection.Count do
(
test = getUserPropBuffer selection[loop]
if not(matchpattern test pattern:"*lod=0*") then
(
test = test + "lod=0\r\n"
setUserPropBuffer selection[loop] test
)
)

)
on btnRemove0 pressed do
(
for loop = 1 to selection.Count do
(
buff = (getUserPropBuffer selection[loop]) as stringStream
newb = stringStream ""
while not eof buff do
(
str = readLine buff
if str != "" and not matchpattern str pattern:"*lod=0*" do format "%\r\n" str to:newb
)
setUserPropBuffer selection[loop] (replace_LF_with_CRLF (newb as string))

)
)
on btnShow1 pressed do
(
for obj in rootscene.world.children do
(
ShowChildren obj "*lod=1*"
)
)
on btnHide1 pressed do
(
for obj in rootscene.world.children do
(
HideChildren obj "*lod=1*"
)
)
on btnAdd1 pressed do
(
for loop = 1 to selection.Count do
(
test = getUserPropBuffer selection[loop]
if not(matchpattern test pattern:"*lod=1*") then
(
test = test + "lod=1\r\n"
setUserPropBuffer selection[loop] test
)
)

)
on btnRemove1 pressed do
(
for loop = 1 to selection.Count do
(
buff = (getUserPropBuffer selection[loop]) as stringStream
newb = stringStream ""
while not eof buff do
(
str = readLine buff
if str != "" and not matchpattern str pattern:"*lod=1*" do format "%\r\n" str to:newb
)
setUserPropBuffer selection[loop] (replace_LF_with_CRLF (newb as string))

)
)
on btnShow2 pressed do
(
for obj in rootscene.world.children do
(
ShowChildren obj "*lod=2*"
)
)
on btnHide2 pressed do
(
for obj in rootscene.world.children do
(
HideChildren obj "*lod=2*"
)
)
on btnAdd2 pressed do
(
for loop = 1 to selection.Count do
(
test = getUserPropBuffer selection[loop]
if not(matchpattern test pattern:"*lod=2*") then
(
test = test + "lod=2\r\n"
setUserPropBuffer selection[loop] test
)
)

)
on btnRemove2 pressed do
(
for loop = 1 to selection.Count do
(
buff = (getUserPropBuffer selection[loop]) as stringStream
newb = stringStream ""
while not eof buff do
(
str = readLine buff
if str != "" and not matchpattern str pattern:"*lod=2*" do format "%\r\n" str to:newb
)
setUserPropBuffer selection[loop] (replace_LF_with_CRLF (newb as string))

)
)
on btnShow3 pressed do
(
for obj in rootscene.world.children do
(
ShowChildren obj "*lod=3*"
)
)
on btnHide3 pressed do
(
for obj in rootscene.world.children do
(
HideChildren obj "*lod=3*"
)
)
on btnAdd3 pressed do
(
for loop = 1 to selection.Count do
(
test = getUserPropBuffer selection[loop]
if not(matchpattern test pattern:"*lod=3*") then
(
test = test + "lod=3\r\n"
setUserPropBuffer selection[loop] test
)
)

)
on btnRemove3 pressed do
(
for loop = 1 to selection.Count do
(
buff = (getUserPropBuffer selection[loop]) as stringStream
newb = stringStream ""
while not eof buff do
(
str = readLine buff
if str != "" and not matchpattern str pattern:"*lod=3*" do format "%\r\n" str to:newb
)
setUserPropBuffer selection[loop] (replace_LF_with_CRLF (newb as string))

)
)
)

createDialog renameObjs_rollout

)

How it works: It simply adds a property "lod" to the objects properties.
« Last Edit: January 01, 2018, 10:22:05 pm by Scooby_Doo »
That's cool and ....disturbing at the same time o_o  - Vasudan Admiral

"Don't play games with me. You just killed someone I like, that is not a safe place to stand. I'm the Doctor. And you're in the biggest library in the universe. Look me up."

"Quick everyone out of the universe now!"

 
Re: Useful scripts for 3ds Max
Collapse Clones

Ever get the message in PCS2 that something couldn't be imported because it wasn't xform resetted?  But you don't want to do an xform reset because you'll lose inheritance data.  This comes to the rescue...  Simply select the object(s) and run script.  The original object will be cloned, that clone will be xform reset and the original object(s) will be hidden.  If you make changes to the original object latter, just rerun the script, it'll delete the original clone and make a new one.

Code: [Select]
macroScript CollapseClones category:"Fast Utils"
(
fn AddKey theObject key=
(
buff = getUserPropBuffer theObject
buff = buff + key

setUserPropBuffer theObject buff
)

fn RetrieveID theObj theKey =
(

buff = (getUserPropBuffer theObj) as stringStream
newb = stringStream ""

scankey = "*" + theKey + "*"
while not eof buff do
(
str = readLine buff
if str != "" and matchpattern str pattern:scankey do 
(
filtered = filterString str "="
return filtered[2]
)
)

return ""
)
fn RemoveKey object key =
(
buff = (getUserPropBuffer object) as stringStream
newb = stringStream ""

while not eof buff do
(
str = readLine buff
if str != "" and not matchpattern str pattern:("*" + key + "*") do format "%\r\n" str to:newb
)
setUserPropBuffer object (replace_LF_with_CRLF (newb as string))

)

fn DeleteChildren obj =
(
for loop = 1 to obj.Children.count do
(
DeleteChildren obj.Children[loop]
)

delete obj
)

fn RemoveExistingClones parentID =
(

for child in rootscene.world.children do
(

id = RetrieveID child "slave"

if matchpattern id pattern:parentID then
(
DeleteChildren child
)
)

)

fn CreateClone object uniqueID =
(
--Get the node and all it's children
maxOps.CloneNodes

-- Now create the clone
maxOps.CloneNodes object clonetype:#copy newnodes:&cloneNodes expandHierarchy:true

for loop = 1 to cloneNodes.count do
(
AddKey cloneNodes[loop] ("slave=" +  uniqueID + "\r\n")
--cloneNodes[loop].Name = cloneNodes[loop].Name + "-Cloned"
cloneNodes[loop].Name = (uniquename "cloned")


--First unlink children
tempChildren = #()
for child in cloneNodes[loop].Children do
(
append tempChildren child
child.parent = undefine

)

--Then collapse
collapseStack cloneNodes[loop]
ResetXForm cloneNodes[loop]
collapseStack cloneNodes[loop]

--Then relink
for child in tempChildren do
append cloneNodes[loop].Children child

)

)

fn HideOriginal obj =
(
for loop = 1 to obj.Children.Count do
(
HideOriginal obj.Children[loop]
)

hide obj
)

fn ResetXFormClone object =
(
for obj in object do
objectChildren = #()

for child in obj.children do
append objectChildren child


for child in objectChildren do
child.parent = undefined


--print ("Reseting XForm on " + node.name)
collapseStack obj
ResetXForm obj
collapseStack obj

if objectChildren.count >= 1 then
(
for child in objectChildren do
child.parent = obj
)
)


for loop = 1 to selection.count do
(
checkForSlave = RetrieveId selection[loop] "*slave=*"
if (checkForSlave != "") then
continue

-- Retrieve the existing unique ID, if any
uniqueID = RetrieveID selection[loop] "master"

if (uniqueID != "") then
(
RemoveExistingClones uniqueID
RemoveKey selection[loop] "master"
)

--Create a random parent ID value
randomValue = random 1000 9999
uniqueID = uniquename selection[loop].Name  + randomValue as string


CreateClone selection[loop] uniqueID

--Now add that name to the parents properties
AddKey selection[loop] ("master=" + uniqueID + "\r\n")

HideOriginal selection[loop]
)
)

How it works: it simply creates a random number name that it attaches to the master/slave. 
That's cool and ....disturbing at the same time o_o  - Vasudan Admiral

"Don't play games with me. You just killed someone I like, that is not a safe place to stand. I'm the Doctor. And you're in the biggest library in the universe. Look me up."

"Quick everyone out of the universe now!"