[Closed] New script: Work with spline based objects outside of sub-object mode
I developed this script while working on another, somewhat related one. I was getting frustrated with constantly having to jump in and out of sub-object while working with splines, and with the (in my opinion) twitchy and difficult-to-control default bezier handles. A more detailed description of the script is included in the header.
Try it, you’ll like it!
------------------------------------------------------------------------------------------------------------
-- Super Splines --
-- SuperSplines.mcr --
-- Version 1.0 --
-- by Kevin Mackey (kmackey2001@hotmail.com) --
-- --
-- The purpose of this script is to make accessible bezier handles that can be used outside the context --
-- of shape sub-selections. Use the script on any (currently only single-spline) selected editable --
-- spline or line object to activate the supersplines helper set. Make your desired changes to the --
-- shape using the helpers. Select any of the superspline helpers and use the script again to make the --
-- helper set disappear; the editable spline or line object will remain in the shape that you left it. --
-- I find that this script works best mapped to a keyboard shortcut, for quick toggling. --
-- --
-- "Super Splines" is just a (mildly) clever name; this script is not related to the mathematical --
-- concept of the same name. --
------------------------------------------------------------------------------------------------------------
-- NOTES: --
-- - Currently this script only works reliably for editable splines and lines with scale values of 100% --
-- - Deleting Superspline helpers may have undesirable results --
-- - Earlier versions of this script occasionally crashed Max; I believe this has been fixed --
-- - Please report any bugs, issues, or suggestions to the e-mail address listed above --
-- - More features will be made available in future versions --
------------------------------------------------------------------------------------------------------------
macroScript SuperSpline category:"Versatile Tools" buttonText:"SuperSpline"
(
global objSet
---------------------------------------
-- Structure "objSet" (i.e. object set)
-- theObject: the object
-- ctrl: the control helper
-- pointSet: the Spline_IK_Control points
-- vecSet: the in/out helper points
-- lineSet: the "handle" lines
-- connectSet: the connecting lines
struct objSet (theObject, theCopy, ctrl, pointSet, vecSet, lineSet, connectSet, sideSet, lengthSet, angleSet, matrixSet, xyzSet)
--------------------------------------------------------------------------------------------------------------------------------
theSel = #(); for s in selection do append theSel s
newSel = #()
max modify mode
---------------------------------------------------------------------------
-- Function to find the angle between lines (special thanks to prettyPixel)
fn lineLineAngle pA pB pC pD =
(
local vAB = pB - pA
local vCD = pD - pC
local angle = acos (dot (normalize vAB) (normalize vCD))
if angle < 90.0 then angle else (180.0 - angle)
)
-------------------------------------------------
--------------------------------------------
-- Function to create a matrix from 3 points
fn threePointMatrix pA pB pC =
(
xVector = normalize (pB - pA)
yVector = normalize (pC - pA)
zVector = normalize (cross yVector xVector)
yVector = normalize (cross zVector xVector)
matrix3 xVector yVector zVector pA
)
------------------------------------
-----------------------------------------------------------------
-- Function to add SuperSpline controllers to the selected shapes
fn convertToSuperSplines =
(
for s in theSel where classOf s == splineShape or classof s == line do -- for each selected editable spline/line
(
animateVertex s #all
rotS = s.rotation; s.rotation = (quat 0 0 0 1) -- store the shape's rotation
-- Create a control helper
obj = execute (s.name + " = objSet theObject:$" + s.name)
ctrl = point pos:s.pos wireColor:(s.wireColor) constantScreenSize:true size:10 box:false cross:true name:(s.name + "_CTRL")
obj.ctrl = ctrl
-- Set up spline IK control with a helper point for every knot
splineIK = spline_IK_Control()
addModifier s splineIK
splineIK.createHelper (numKnots s)
splineIK.noLinking()
obj.pointSet = s.modifiers[#Spline_IK_Control].helper_list
obj.vecSet = #() -- allocated for in/out helper points
obj.lineSet = #() -- allocated for "handle" lines
obj.connectSet = #() -- allocated for connecting lines
obj.sideSet = #() -- allocated for connecting line lengths
obj.lengthSet = #() -- allocated for "handle" line lengths
obj.angleSet = #() -- allocated for "handle" line angles
obj.matrixSet = #() -- allocated for connecting line matrices
obj.xyzSet = #() -- allocated for xyz coordinates of in/out helper points according to connecting line matrices
for i = 1 to (numKnots s) do
(
setKnotType s 1 i #bezierCorner
-- Adjust the name and appearance of the Spline_IK_Control point
obj.pointSet[i].name = s.name + "_helper_" + i as string
obj.pointSet[i].wireColor = color 255 0 0
obj.pointSet[i].constantScreenSize = true
obj.pointSet[i].size = 3
obj.pointSet[i].parent = ctrl
-----------------------------------------------------------------------------------------
-- Create in/out helper points, link to the Spline_IK_Control point, and append to struct
inVec = point pos:(getInVec s 1 i) wireColor:(color 0 255 0) constantScreenSize:true size:3 box:true cross:false name:(s.name + "_helper_" + i as string + "_in") transform:ctrl.transform
outVec = point pos:(getOutVec s 1 i) wireColor:(color 0 255 0) constantScreenSize:true size:3 box:true cross:false name:(s.name + "_helper_" + i as string + "_out") transform:ctrl.transform
inVec.rotation.controller = rotation_script(); inVec.parent = obj.pointSet[i]
outVec.rotation.controller = rotation_script(); outVec.parent = obj.pointSet[i]
append obj.vecSet #(inVec, outVec)
----------------------------------
--------------------------------------------------------------------------------------------------
-- Create in/out "handle" lines, link to the Spline_IK_Control point, append to struct, and freeze
inLine = splineShape pos:obj.pointSet[i].pos wireColor:(color 255 255 0) name:(s.name + "_handle_" + i as string + "_in")
addNewSpline inLine
addKnot inLine 1 #corner #line obj.pointSet[i].pos; addKnot inLine 1 #corner #line inVec.pos
updateShape inLine; animateVertex inLine #all
outLine = splineShape pos:obj.pointSet[i].pos wireColor:(color 255 255 0) name:(s.name + "_handle_" + i as string + "_out")
addNewSpline outLine
addKnot outLine 1 #corner #line obj.pointSet[i].pos; addKnot outLine 1 #corner #line outVec.pos
updateShape outLine; animateVertex outLine #all
inLine.parent = obj.pointSet[i]; inLine.showFrozenInGray = false; inLine.isFrozen = true
outLine.parent = obj.pointSet[i]; outLine.showFrozenInGray = false; outLine.isFrozen = true
append obj.lineSet #(inLine, outLine)
-------------------------------------
)
for i = 1 to (numKnots s) do
(
theClass = (getsubanimnames s[#modified_object])[2] -- determines if the shape is an editable spline or a line
hn = obj.pointSet[i].name -- i.e. "helper name"
kp = ("$" + s.name + ".Spline_1___Vertex_" + i as string) -- i.e. "knot point" (adjust this to accommodate more than one spline in a shape)
if i == 1 then theLast = numKnots s else theLast = i - 1 -- previous knot in sequence
if i == numKnots s then theNext = 1 else theNext = i + 1 -- next knot in sequence
---------------------------------------------------------------------------------------------------------------
-- Use wire parameters to connect the shape's bezier controllers and "handle" lines to the in/out helper points
inCtrl = obj.vecSet[i][1].transform.controller[#Position]
inBez = s[#Modified_Object][theClass][#Master][(i * 3) - 2]
inLineEnd = obj.lineSet[i][1][#Object__Editable_Spline][#Master][5]
paramWire.connect inCtrl inBez ("in coordsys $" + hn + " Position + " + kp)
paramWire.connect inCtrl inLineEnd ("Position")
outCtrl = obj.vecSet[i][2].transform.controller[#Position]
outBez = s[#Modified_Object][theClass][#Master][i * 3]
outLineEnd = obj.lineSet[i][2][#Object__Editable_Spline][#Master][5]
paramWire.connect outCtrl outBez ("in coordsys $" + hn + " Position + " + kp)
paramWire.connect outCtrl outLineEnd ("Position")
-------------------------------------------------
)
ctrl.rotation = rotS -- match the shape's original rotation
s.showFrozenInGray = false; s.isFrozen = true
deselect s; append newSel obj.ctrl
)
) -- end function (convertToSuperSplines)
-------------------------------------------------------------------------------
-- Function to delete all SuperSpline objects and associated script controllers
fn convertFromSuperSplines =
(
for s in theSel where classOf s == point do
(
try
(
theBaseName = (filterstring s.name "_")[1]
for o in objects where matchpattern o.name pattern:(theBaseName + "*") == true and (filterString o.name "_")[2] == "copy" do delete o -- delete copy
-------------------------
-- For the original shape
for o in objects where matchpattern o.name pattern:(theBaseName) == true do
(
obj = execute o.name
transS = obj.ctrl.transform -- store the position/rotation of the control helper
theClass = (getsubanimnames o[#modified_object])[2] -- determines if the shape is an editable spline or a line
-- Remove all wire parameters, delete the spline_IK_Control modifiers, and unfreeze
for p in (getPropNames o) where matchpattern (p as string) pattern:("Spline*") == true do o[#modified_object][theClass][#Master][p].controller = Bezier_Point3()
deletemodifier o o.modifiers[#Spline_IK_Control]
o.transform = transS -- return the shape to its original position/rotation
-- Set all knots and bezier control points to the desired positions (adjust this to accommodate more than one spline in a shape)
for k = 1 to numKnots o do
(
setInVec o 1 k obj.vecSet[k][1].pos
setKnotPoint o 1 k obj.pointSet[k].pos
setoutVec o 1 k obj.vecSet[k][2].pos
)
o.isFrozen = false; append newSel o
)
-------------------------------------
-- Remove all wire parameter controls from the set
for o in objects where matchpattern o.name pattern:(theBaseName + "*") == true and (filterString o.name "_")[2] == "handle" do
(
for p in (getPropNames o) where matchpattern (p as string) pattern:("Spline*") == true do o[#Object__Editable_Spline][#Master][p].controller = Bezier_Point3()
)
-- Select and delete all point and "handle" lines
for o in objects where matchpattern o.name pattern:(theBaseName + "*") == true and (filterString o.name "_")[2] != undefined do selectMore o
) catch (format "Redundant selection: %
" s.name)
)
delete selection
) -- end function (convertFromSuperSplines)
-------------------------------------------
convertToSuperSplines() -- add SuperSpline controllers to the selected shapes
convertFromSuperSplines() -- delete all SuperSpline objects and associated script controllers
for s = 1 to newSel.count do selectMore newSel[s]
callbacks.addScript #filePreOpen "callbacks.removeScripts()" -- remove callbacks before opening another file
----------------------------------------------------
-- Delete SuperSpline sets before the scene is saved
setArray = #() -- save set names to a temporary array
fn cleanupSets =
(
callbacks.removeScripts #filePostSave; callbacks.addScript #filePostSave "restoreSets()"
for o in objects do deselect o
for o in objects where (filterString o.name "_")[2] == "CTRL" do (append setArray (filterString o.name "_")[1]; selectMore o)
theSel = #(); for s in selection do append theSel s
--format "setArray (preSave): %
" setArray
callbacks.removeScripts #viewportChange
convertFromSuperSplines()
delete selection; theSel = #()
)
callbacks.addScript #filePreSave "cleanupSets()"
------------------------------------------------
----------------------------------------------------------
-- Restore SuperSpline sets after the scene has been saved
fn restoreSets =
(
--format "setArray (postSave): %
" setArray
for n in setArray do (obj = execute n; selectMore obj.theObject)
theSel = #(); for s in selection do append theSel s
convertToSuperSplines()
)
callbacks.addScript #filePostSave "callbacks.removeScripts #filePreSave"
------------------------------------------------------------------------
that’s pretty cool. have you thought about using scripted manipulators, instead of helper objects?
Probably eventually, once I have learned a bit more. There are still some other issues with the script that I want to get fixed up first. I’m going to be very busy for the next few months, so I don’t know when I can get around to making any adjustments. Feel free to make any changes that you think would improve the script and post them here!
I just noticed there was a problem with the initial definition of the objSet structure, so I fixed it. (Thanks again Martijn!)