Notifications
Clear all

[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"
  ------------------------------------------------------------------------
  
3 Replies

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!)