Notifications
Clear all

[Closed] Create a chain of bones from a spline

Something I put together to works some bugs out of a bigger project I’m working on.
Any feedback is, of course, appreciated!

fn getAxis p1 p2 p3 = -- Get an axis using three points
 (
 	x1 = normalize (p2 - p1)
 	x2 = normalize (p3 - p2)
 	normalize (cross x1 x2)
 )
 
 fn getKnotPoints obj s:1 k: = -- Get the positions of an array of knots along a spline
 (
 	case (classOf k) of
 	(
 		array:						try (for i in k collect (getKnotPoint obj s i)) catch()
 		integer:					#(getKnotPoint obj s k)
 		unsuppliedClass:	for i = 1 to (numKnots obj s) collect (getKnotPoint obj s i)
 	)
 ) -- end getKnotPoints
 
 fn x90_Up	o =
 (
 	in coordsys local o.rotation.x_rotation += 90
 	for c in o.children do in coordsys o rotate c (eulerAngles -90 0 0)
 )
 
 fn x90_Down o =
 (
 	in coordsys local o.rotation.x_rotation -= 90
 	for c in o.children do in coordsys o rotate c (eulerAngles 90 0 0)
 )
 
 fn makeTipBone b =
 (
 	local tip			= boneSys.createBone b.transform.translation (b.transform.translation - 6) b.dir
 	tip.name			= b.name + "_Tip"
 	tip.parent		= b
 	tip.transform = b.transform
 
 	tip.backFin = tip.frontFin = tip.sideFins = false								-- switch off all fins
 	tip.height = tip.length = tip.width = aMin #(b.width, b.height)	-- make tip equilateral
 
 	in coordsys local move tip [b.length, 0, 0]											-- position tip
 	tip
 )
 
 fn boneChainFromLine lin names base: fWid: fWP:true side: type: wid: WP:true  =
 (
 	format "type: %
" type
 	local segCount	= (numKnots lin 1) - 1
 	local LP				= getKnotPoints lin
 	local boneChain	= #()
 
 	segLengths = getSegLengths lin 1
 	segLengths = for s = ((segLengths.count / 2) + 1) to (segLengths.count - 1) collect segLengths[s]
 
 	segS = aMin segLengths
 	segL = aMax segLengths
 
 	case of 
 	(
 		(type == #head or type == #spine):
 		(
 			for i = 1 to segCount do
 			(
 				local p1 = LP[i], p2 = LP[i + 1]
 
 				local a	= cross [0, 1, 0] (p2 - p1)
 				local b	= boneSys.createBone p1 p2 a
 
 				if names[i] != undefined then
 					b.name = names[i]
 				else
 					b.name = "Bone_" + lin.name + "_" + (i as string)
 
 				append boneChain b
 				if i > 1 do b.parent = boneChain[i - 1]
 			)
 		)
 
 		(type == #leg):
 		(
 			for i = 1 to 5 do -- create thigh and calf
 			(
 				local p1 = LP[i], p2 = LP[i + 1]
 
 				local a = -- bone alignment
 				(
 					case i of
 					(
 						1: getAxis LP[3] LP[2] LP[1]
 						2: getAxis LP[1] LP[2] LP[3]
 						default: cross [0,0,1] (p2 - p1)
 					)
 				) -- end a
 
 				b = boneSys.createBone p1 p2 a
 				append boneChain b
 
 				if names[i] != undefined then
 					b.name = names[i]
 				else
 					b.name = "Bone_" + lin.name + "_" + (i as string)
 
 				if i == 1 then
 				(
 					if base != unsupplied do
 						b.parent = base
 				)
 				else
 				(
 					b.parent = boneChain[i - 1]
 
 					while (in coordsys parent b.rotation.x_Rotation > 45) do
 						x90_Down b
 
 					while (in coordsys parent b.rotation.x_Rotation < -45) do
 						x90_Up b
 				)
 			) -- end i loop
 		) -- end leg
 
 		(type == #arm):
 		(
 			for i = 1 to 3 do
 			(
 				local p1 = LP[i], p2 = LP[i + 1]
 
 				local a = -- bone alignment
 				(
 					case i of
 					(
 						1:
 						(
 							if base != undefined then
 								cross (p2 - p1) base.transform[3]
 							else
 								cross [0, 1, 0] (p2 - p1)
 						)
 
 						2: getAxis LP[4] LP[3] LP[2] 
 						3: getAxis LP[2] LP[3] LP[4] 
 					) -- end case (i)
 				) -- end a
 
 				local b = boneSys.createBone p1 p2 a
 				append boneChain b
 
 				if names[i] != undefined then
 					b.name = names[i]
 				else
 					b.name = "Bone_" + lin.name + "_" + (i as string)
 
 				if i == 1 then
 				(
 					if base != undefined do
 						b.parent = base
 				)
 				else
 				(
 					b.parent = boneChain[i - 1]
 
 					while (in coordsys parent b.rotation.x_Rotation > 45) do
 						x90_Down b
 
 					while (in coordsys parent b.rotation.x_Rotation < -45) do
 						x90_Up b
 				)
 			) -- end i loop
 
 			boneChain[boneChain.count].boneScaleType = #none
 
 			s	= for f = 1 to (numSplines lin) collect (getKnotPoints lin s:f)	-- knuckles
 			m	=	s[ceil (s.count as float / 2) + 1][1]	-- middle finger knuckle point
 			w	= ((LP[4] * 3) + m) / 4									-- middle finger metacarpal start point
 
 			n = "Bone_" + (filterString lin.name "_")[1] + "_Finger" + side + "_"
 
 			for f = 2 to (numSplines lin) do
 			(
 				local fingerBoneChain = #()
 				local FP = getKnotPoints lin s:f
 
 				if f > 2 do -- create metacarpals
 				(
 					local p1	= ((s[f][1] - m) / 2) + w, p2	= s[f][1]
 					local a		= getAxis FP[2] FP[3] FP[1]
 					local b		= boneSys.createBone p1 p2 a
 					append fingerBoneChain b
 
 					b.parent = boneChain[boneChain.count] -- parent metacarpals to forearm
 				)
 
 				for i = 2 to (FP.count) do -- create phalanges
 				(
 					local p1	= FP[i - 1], p2 = FP[i]
 					local a		= getAxis FP[2] FP[3] FP[1]
 					local b		= boneSys.createBone p1 p2 a
 
 					-- Set parent bones
 					if fingerBoneChain.count > 0 then
 						b.parent = fingerBoneChain[fingerBoneChain.count]
 					else
 						b.parent = boneChain[boneChain.count]
 
 					append fingerBoneChain b
 				)
 
 				fingerSegS = aMin (for i in fingerBoneChain collect i.length)
 				fingerSegL = aMax (for i in fingerBoneChain collect i.length)
 
 				for i = 1 to fingerBoneChain.count do
 				(
 					b = fingerBoneChain[i]
 
 					b.name =
 					(
 						if f > 2 then
 							n + (f - 1) as string + "_" + (i - 1) as string
 						else 
 							"Bone_" + (filterString lin.name "_")[1] + "_Thumb" + side + "_" + i as string
 					)
 
 					b.height = b.width = -- set height and width of bone
 					(
 						case fWP of
 						(
 							0: fWid
 							1: b.length * fWid / 100
 							2: fingerSegS * fWid / 100
 							3: fingerSegL * fWid / 100
 						)
 					)
 
 					b.frontFin = b.sideFins = false
 					b.backFin			= true
 					b.backFinSize = b.width / 2
 				)
 			) -- end f loop
 		) -- end arm
 
 		default:
 		(
 			for i = 1 to segCount do
 			(
 				local p1 = LP[i], p2 = LP[i + 1] -- start and end points
 
 				local a = -- bone alignment
 				(
 					if LP.count > 2 then
 					(
 						if i == 1 then
 							getAxis LP[3] LP[2] LP[1] 
 						else
 							getAxis LP[i - 1] LP[i] LP[i + 1] 
 					)
 					else
 						normalize (p1 - p2)
 				)
 
 				local b = boneSys.createBone p1 p2 a
 				append boneChain b
 
 				if names[i] != undefined then
 					b.name = names[i]
 				else
 					b.name = "Bone_" + lin.name + "_" + (i as string)
 
 				if i == 1 then
 				(
 					if base != undefined do
 						b.parent = base
 				)
 				else
 				(
 					b.parent = boneChain[i - 1]
 
 					while (in coordsys parent b.rotation.x_Rotation > 45) do
 						x90_Down b
 
 					while (in coordsys parent b.rotation.x_Rotation < -45) do
 						x90_Up b
 				)
 			) -- end i loop
 		) -- end default
 	) -- end case
 
 	for i = 1 to boneChain.count do
 	(
 		b = boneChain[i]
 
 		case of
 		(
 			(type == #head and i == boneChain.count):	b.width = 0.6 * (b.height = b.length)
 			(type == #leg and i > 2):	b.height = 0.25 * (b.width = 0.4 * boneChain[boneChain.count].length)
 
 			default:
 			(
 				b.height = b.width = -- set height and width of bone
 				(
 					case WP of
 					(
 						0: wid
 						1: b.length * wid / 100
 						2: segS * wid / 100
 						3: segL * wid / 100
 					) -- end case (WP)
 				)
 			)
 		) -- end case
 
 		b.frontFin = b.sideFins = false
 		b.backFin			= true
 		b.backFinSize = b.height / 2
 	) -- end i loop
 
 	if not (type == #head or type == #spine) do makeTipBone boneChain[boneChain.count]
 	boneChain
 ) -- end boneChainFromLine
 
 
 try (destroyDialog RO_BoneChainFromLine) catch()
 rollout RO_BoneChainFromLine "Bone Chain From Line"
 (
 	local multiSplines = false
 
 	fn filterLine obj =
 	(
 		(classOf obj == Line) and
 		(
 			case RO_BoneChainFromLine.rad_Type.state of
 			(
 				1: numSplines obj == 1 and numKnots obj 1 > 2	-- head and neck
 				2: numSplines obj == 1 and numKnots obj 1 > 1	-- spine
 				3: numKnots obj 1 == 6												-- leg
 				4: numSplines obj > 1 and numKnots obj 1 == 4	-- arm
 				default: numSplines obj == 1
 			)
 		)
 	) -- end filterLine
 
 	pickButton		pck_Line "Select Line" filter:filterLine autoDisplay:true width:270
 	label					lbl_Type "Chain type:" pos:[15,35]
 	radioButtons	rad_Type labels:#("head and neck", "spine", "leg", "arm", "default") columns:1 default:5	pos:[15,55]		
 
 	radioButtons	rad_Side labels:#("left", "right") enabled: false pos:[15,140]
 
 	label					lbl_Width "Bone Width:"													pos:[164,35]
 	editText			edt_Width width:36 text:"10"										pos:[224,35]
 	checkButton		chk_Percent "%" width:20 height:18 checked:true	pos:[265,35] 
 	radioButtons	rad_Width labels:#("per segment", "of shortest segment", "of longest segment") columns:1 default:3 pos:[164,55]
 
 	label					lbl_FingerWidth "Finger Width:"													pos:[164,110] enabled:false
 	editText			edt_FingerWidth width:36 text:"20"											pos:[230,110] enabled:false
 	checkButton		chk_FingerPercent "%" width:20 height:18 checked:true		pos:[271,110] enabled:false
 	radioButtons	rad_FingerWidth labels:#("per segment", "of shortest segment", "of longest segment") columns:1 default:3 pos:[164,130] enabled:false
 
 	label					lbl_CharacterName "Character Name:"	pos:[15,165]
 	editText			edt_CharacterName width:278					pos:[10,180]
 
 	label					lbl_BoneNames "Bone Names:"	pos:[15,205]
 	editText			edt_BoneNames width:278			pos:[10,220]
 
 	label					lbl_Info "Please select a line"
 	button				btn_CreateChain "Create Chain" enabled:false
 	-----------------
 
 	local side
 
 	fn setNames =
 	(
 		edt_BoneNames.text	= ""
 		prefix							= "Bone_" + edt_CharacterName.text
 		segCount						= (numKnots pck_Line.object 1) - 1
 
 		case rad_Type.state of
 		(
 			1:
 			(
 				for i = 1 to (segCount - 1) do
 					edt_BoneNames.text = edt_BoneNames.text + prefix + "_Neck" + (i as string) + ", "
 
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Head"
 			)
 
 			2:
 			(
 				for i = 1 to segCount do
 				(
 					edt_BoneNames.text = edt_BoneNames.text + prefix + "_Spine" + (i as string)
 					if i < segCount do edt_BoneNames.text = edt_BoneNames.text + ", "
 				)
 			)
 
 			3:
 			(
 				side = if rad_Side.state == 1 then "L" else "R"
 
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Thigh" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Calf" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Instep" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Toe" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Heel" + side
 			)
 
 			4:
 			(
 				side = if rad_Side.state == 1 then "L" else "R"
 
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Clavicle" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_UpperArm" + side + ", "
 				edt_BoneNames.text = edt_BoneNames.text + prefix + "_ForeArm" + side
 			)
 
 			default:
 			(
 				for i = 1 to segCount do
 				(
 					edt_BoneNames.text = edt_BoneNames.text + prefix + (i as string)
 					if i < segCount do edt_BoneNames.text = edt_BoneNames.text + ", "
 				)
 			)
 		) -- end case (rad_Type.state)
 	) -- end setNames
 	-----------------
 
 	on pck_Line picked obj do
 	(
 		if edt_CharacterName.text == "" do edt_CharacterName.text = (filterString obj.name "_")[1]
 		setNames()
 		lbl_Info.text = "Ready"
 		btn_CreateChain.enabled	= true
 	) -- end handler (pck_Line)
 
 	on rad_Type changed state do
 	(
 		format "changed state
"
 		rad_Side.enabled = if (state == 3) or (state == 4) then true else false
 
 		-- Enable/disable finger related controls
 		for c in #(lbl_FingerWidth, edt_FingerWidth, chk_FingerPercent, rad_FingerWidth) do
 			c.enabled = if (state == 4) then true else false
 
 		if pck_Line.object != undefined do
 		(
 			--local valid = true
 			obj = pck_Line.object
 			lbl_Info.text = "Ready"
 
 			case state of
 			(
 				3: if numKnots obj != 6 do lbl_Info.text = "First spline in shape must have 6 vertices"
 				4:
 				(
 					if numKnots obj 1 != 4 do
 						lbl_Info.text = "First spline in shape needs 4 vertices"
 					if numSplines obj < 3 do
 						lbl_Info.text = "Shape needs 3 or more splines"
 				)
 				default: if numSplines obj != 1 do lbl_Info.text	= "Shape must have 1 spline"
 			) -- end case (state)
 
 			if lbl_Info.text == "Ready" then
 			(
 				setNames()
 				btn_CreateChain.enabled	= true
 			)
 			else
 			(
 				btn_CreateChain.enabled	= false
 				edt_BoneNames.text = ""
 			)
 		)
 	) -- end handler (rad_Type)
 
 	on rad_Side changed state do
 	(
 		if pck_Line.object != undefined and lbl_Info.text == "Ready" then
 			setNames()
 		else
 			edt_BoneNames.text = ""
 	)
 
 	on chk_Percent changed state do
 		rad_Width.enabled = state
 
 	on btn_CreateChain pressed do with undo on
 	(
 		names = filterString edt_BoneNames.text ", "
 
 		chainType	=
 		(
 			case rad_Type.state of
 			(
 				1: #head
 				2: #spine
 				3: #leg
 				4: #arm
 				5: #default
 			)
 		)
 
 		wp	= if (not chk_Percent.state) then 0 else rad_Width.state -- percent based width type
 		fwp	= if (not chk_FingerPercent.state) then 0 else rad_FingerWidth.state
 
 		boneChainFromLine pck_Line.object names base:pck_Line.object.parent fWid:(edt_FingerWidth.text as float) fwp:fWP side:side type:chainType wid:(edt_Width.text as float) wp:wp 
 	) -- end handler (btn_CreateChain)
 )
 createDialog RO_BoneChainFromLine width:300
30 Replies

could you give a pipeline example where your method might be useful?
btw. i’m very experienced rigger. could it be possible that i’ve missed anything?

Well, it’s in a pretty basic state as seen here. I created this primarily to quickly generate rigs to test out some of my other scripts with. It’s just something to automate the process a bit. Take a character model, throw in a few splines with vertices at the joints, and you can generate a complete skeleton of max bones for the character in a matter of seconds.

More importantly, by specifying the particular types of chains you want to create, this script can be used a base to attach any custom code that you want to each of the limbs. IK chains, control objects, rotation limits, custom attributes, etc. can all be dropped into the various definitions, and will then be automatically incorporated when creating a chain of that type.

good. but spline doesn’t have a normal. it has tangent. yes. but it’s not enough to define bones orientation. is just set of points (as joints) not an easier way to create a bone chain?

In the case of the spline and neck, it IS just using the spline as a set of points. The script is assuming that most people are modeling their characters in a default position of facing forward, and any crookedness in the spine is just that – a crooked spine – rather than an indication of any twisting taking place.

If you /do/ want to try and use the positioning of the points to determine orientation, you can always use the “default” setting instead.


EDIT: okay wow, I feel silly. Somehow I not only read spline as spine but I actually typed OUT “spline” when I meant “spine”! I probably should have taken that nap I was thinking about, haha.

But anyway, the normals are determined by a combination of points along the spline and the type of chain that is specified by the user. For example, knees and elbows move on a single axis, so the normals of the upper and lower arms and leg bones are determined according to that axis. The fingers operate on a similar principle, and the bones of the feet use the ground plane to determine their starting normals.

The spine and head, as I stated before, assume a state of facing forward as their starting position, and the “default” spline uses the starting point of each bone as well as the points directly before and after that point to determine the normal.

Added some code for building a basic autoclav arm.

fn getAxis p1 p2 p3 = -- Get an axis using three points
   (
   	x1 = normalize (p2 - p1)
   	x2 = normalize (p3 - p2)
   	normalize (cross x1 x2)
   )
   
   fn getKnotPoints obj s:1 k: = -- Get the positions of an array of knots along a spline
   (
   	case (classOf k) of
   	(
   		array:						try (for i in k collect (getKnotPoint obj s i)) catch()
   		integer:					#(getKnotPoint obj s k)
   		unsuppliedClass:	for i = 1 to (numKnots obj s) collect (getKnotPoint obj s i)
   	)
   ) -- end getKnotPoints
   
   fn x90_Up	o =
   (
   	in coordsys local o.rotation.x_rotation += 90
   	for c in o.children do in coordsys o rotate c (eulerAngles -90 0 0)
   )
   
   fn x90_Down o =
   (
   	in coordsys local o.rotation.x_rotation -= 90
   	for c in o.children do in coordsys o rotate c (eulerAngles 90 0 0)
   )
   
   fn makeTipBone b =
   (
   	local tip			= boneSys.createBone b.transform.translation (b.transform.translation - 6) b.dir
   	tip.wireColor	= b.wireColor
   	tip.name			= b.name + "_Tip"
   	tip.parent		= b
   	tip.transform = b.transform
   
   	tip.backFin = tip.frontFin = tip.sideFins = false								-- switch off all fins
   	tip.height = tip.length = tip.width = aMin #(b.width, b.height)	-- make tip equilateral
   
   	in coordsys local move tip [b.length, 0, 0]											-- position tip
   	tip
   )
   
   fn boneChainFromLine lin names base: fWid: fWP:true IKChain: side: type: wid: WP:true  =
   (
   	local segCount	= (numKnots lin 1) - 1
   	local LP				= getKnotPoints lin
   	local boneChain	= #()
   
   	local segLengths	= getSegLengths lin 1
   	segLengths				= for s = ((segLengths.count / 2) + 1) to (segLengths.count - 1) collect segLengths[s]
   
   	local chainLength	= 0
   	for s in segLengths do chainLength += s
   
   	local segS = aMin segLengths
   	local segL = aMax segLengths
   
   	local hw = -- height and width
   	(
   		case WP of
   		(
   			0: fWid
   			1: b.length * fWid / 100
   			2: segS * fWid / 100
   			3: segL * fWid / 100
   			4: chainLength * fWid / 100
   		)
   	) -- end hw
   
   	case of 
   	(
   		(type == #head or type == #spine):
   		(
   			for i = 1 to segCount do
   			(
   				local p1 = LP[i], p2 = LP[i + 1] -- start and end points
   
   				local a	= cross [0, 1, 0] (p2 - p1)
   				local b	= boneSys.createBone p1 p2 a
   
   				if names[i] != undefined then
   					b.name = names[i]
   				else
   					b.name = "Bone_" + lin.name + "_" + (i as string)
   
   				append boneChain b
   
   				if i == 1 then
   					if base != unsupplied do b.parent = base
   				else
   					b.parent = boneChain[i - 1]
   			) -- end i loop
   		)
   
   		(type == #leg):
   		(
   			for i = 1 to 5 do -- create thigh and calf
   			(
   				local p1 = LP[i], p2 = LP[i + 1] -- start and end points
   
   				local a = -- bone alignment
   				(
   					case i of
   					(
   						1: getAxis LP[3] LP[2] LP[1]
   						2: getAxis LP[1] LP[2] LP[3]
   						default: cross [0,0,1] (p2 - p1)
   					)
   				) -- end a
   
   				b = boneSys.createBone p1 p2 a
   				append boneChain b
   
   				if names[i] != undefined then
   					b.name = names[i]
   				else
   					b.name = "Bone_" + lin.name + "_" + (i as string)
   
   				if i == 1 then
   					if base != unsupplied do b.parent = base
   				else
   					b.parent = boneChain[i - 1]
   			) -- end i loop
   		) -- end leg
   
   		(type == #arm):
   		(
   			for i = 1 to 3 do
   			(
   				local p1 = LP[i], p2 = LP[i + 1] -- start and end points
   
   				local a = -- bone alignment
   				(
   					case i of
   					(
   						1:
   						(
   							if base != undefined then
   								cross (p2 - p1) base.transform[3]
   							else
   								cross [0, 1, 0] (p2 - p1)
   						)
   
   						2: getAxis LP[4] LP[3] LP[2] 
   						3: getAxis LP[2] LP[3] LP[4] 
   					) -- end case (i)
   				) -- end a
   
   				local b = boneSys.createBone p1 p2 a
   				append boneChain b
   
   				if names[i] != undefined then
   					b.name = names[i]
   				else
   					b.name = "Bone_" + lin.name + "_" + (i as string)
   
   				if i == 1 then
   				(
   					if base != undefined do
   						b.parent = base
   				)
   				else
   				(
   					b.parent = boneChain[i - 1]
   
   					while (in coordsys parent b.rotation.x_Rotation > 45) do
   						x90_Down b
   
   					while (in coordsys parent b.rotation.x_Rotation < -45) do
   						x90_Up b
   				)
   			) -- end i loop
   
   			boneChain[boneChain.count].boneScaleType = #none
   
   			s	= for f = 1 to (numSplines lin) collect (getKnotPoints lin s:f)	-- knuckles
   			m	=	s[ceil (s.count as float / 2) + 1][1]	-- middle finger knuckle point
   			w	= ((LP[4] * 3) + m) / 4									-- middle finger metacarpal start point
   			n = "Bone_" + (filterString lin.name "_")[1] + "_Finger" + side + "_"
   
   
   			-- Create finger bones
   			----------------------
   			for f = 2 to (numSplines lin) do
   			(
   				local fingerBoneChain = #()
   				local FP = getKnotPoints lin s:f
   
   				if f > 2 do -- create metacarpals
   				(
   					local p1	= ((s[f][1] - m) / 2) + w, p2	= s[f][1]
   					local a		= getAxis FP[2] FP[3] FP[1]
   					local b		= boneSys.createBone p1 p2 a
   					append fingerBoneChain b
   
   					b.parent = boneChain[boneChain.count] -- parent metacarpals to forearm
   				) -- end if (f > 2)
   
   				for i = 2 to (FP.count) do -- create phalanges
   				(
   					local p1	= FP[i - 1], p2 = FP[i]
   					local a		= getAxis FP[2] FP[3] FP[1]
   					local b		= boneSys.createBone p1 p2 a
   
   					-- Set parent bones
   					if fingerBoneChain.count > 0 then
   						b.parent = fingerBoneChain[fingerBoneChain.count]
   					else
   						b.parent = boneChain[boneChain.count]
   
   					append fingerBoneChain b
   				) -- end i loop
   
   				local fingerChainLength = 0
   				for i in fingerBoneChain do fingerChainLength += i.length
   
   				local fingerSegS = aMin (for i in fingerBoneChain collect i.length)
   				local fingerSegL = aMax (for i in fingerBoneChain collect i.length)
   
   				for i = 1 to fingerBoneChain.count do
   				(
   					b = fingerBoneChain[i]
   
   					b.name =
   					(
   						if f > 2 then
   							n + (f - 1) as string + "_" + (i - 1) as string
   						else 
   							"Bone_" + (filterString lin.name "_")[1] + "_Thumb" + side + "_" + i as string
   					) -- end b.name
   
   					b.height = b.width = -- set height and width of bone
   					(
   						case fWP of
   						(
   							0: fWid
   							1: b.length * fWid / 100
   							2: fingerSegS * fWid / 100
   							3: fingerSegL * fWid / 100
   							4: fingerChainLength * fWid / 100
   						)
   					)
   
   					b.frontFin = b.sideFins = false
   					b.backFin			= true
   					b.backFinSize = b.width / 2
   				)
   			) -- end f loop
   			----------------------
   
   
   			-- AutoClav arm setup
   			---------------------
   			if IKChain do
   			(
   				autoClavChain = #()
   
   				for i = 1 to 3 do
   				(
   					b								= boneChain[i]
   					a								= copy b
   					a.boneEnable		= true
   					a.boneScaleType = #none
   					a.name					= b.name + "_A"
   					a.taper					= 90
   					a.wireColor			= (color 255 0 0)
   					append autoClavChain a
   
   					-- Bone and fin size
   					a.width = a.height = (hw / 2)
   					a.frontFin		= a.sideFins = false
   					a.backFin			= true
   					a.backFinSize	= a.height / 8
   
   					a.parent =
   					(
   						if i == 1 then
   							boneChain[1].parent
   						else
   							autoClavChain[i - 1]
   					) -- end a.parent
   				) -- end i loop
   
   				makeTipBone autoClavChain[autoClavChain.count]
   
   				aim	= point name:(boneChain[1].name + "_Aim") transform:boneChain[1].transform
   				tgt = point name:(boneChain[1].name + "_Target") transform:boneChain[2].transform parent:autoClavChain[2]
   
   				aim.rotation.controller = lookAt_Constraint lookAt_Vector_Length:0 pickUpNode:autoClavChain[1] relative:true upNode_World:false 
   				aim.rotation.controller.appendTarget tgt 100
   
   				aim.parent = base
   
   				for p in #(aim, tgt) do
   				(
   					p.size				= hw
   					p.wireColor		= color 14 255 2
   					p[4][2].value = false
   					p[4][4].value = false
   					p[4][5].value = true
   				)
   
   				in coordSys local move tgt [boneChain[2].length / 2, 0, 0]
   				boneChain[1].parent = aim
   			) -- end if (IKChain)
   			---------------------
   
   
   		) -- end arm
   
   		default:
   		(
   			for i = 1 to segCount do
   			(
   				local p1 = LP[i], p2 = LP[i + 1] -- start and end points
   
   				local a = -- bone alignment
   				(
   					if LP.count > 2 then
   					(
   						if i == 1 then
   							getAxis LP[3] LP[2] LP[1] 
   						else
   							getAxis LP[i - 1] LP[i] LP[i + 1] 
   					)
   					else
   						normalize (p1 - p2)
   				) -- end a
   
   				local b = boneSys.createBone p1 p2 a
   				append boneChain b
   
   				if names[i] != undefined then
   					b.name = names[i]
   				else
   					b.name = "Bone_" + lin.name + "_" + (i as string)
   
   				if i == 1 then
   				(
   					if base != undefined do
   						b.parent = base
   				)
   				else
   				(
   					b.parent = boneChain[i - 1]
   
   					while (in coordsys parent b.rotation.x_Rotation > 45) do
   						x90_Down b
   
   					while (in coordsys parent b.rotation.x_Rotation < -45) do
   						x90_Up b
   				)
   			) -- end i loop
   		) -- end default
   	) -- end case
   
   	for i = 1 to boneChain.count do
   	(
   		b = boneChain[i]
   
   		case of
   		(
   			(type == #head and i == boneChain.count):	b.width = 0.6 * (b.height = b.length)
   			(type == #leg and i > 2):	b.height = 0.25 * (b.width = 0.4 * boneChain[boneChain.count].length)
   
   			default: b.height = b.width = hw -- set height and width of bone
   		) -- end case
   
   		-- Bone fins
   		b.frontFin = b.sideFins = false
   		b.backFin = true
   		if type == #head and i == boneChain.count then
   			b.backFinSize = b.height / 8
   		else
   			b.backFinSize = b.height / 4
   
   		b.taper = -- bone taper
   		(
   			case of
   			(
   				(type == #head and i == boneChain.count): 30
   				(type == #leg and i == 2):	70
   				(type == #leg and i > 2):		30
   				(type == #arm and i == 3):	70
   				default: 50
   			)
   		) -- end b.taper
   	) -- end i loop
   
   	if not (type == #head or type == #spine) do makeTipBone boneChain[boneChain.count]
   
   	if IKChain == true do
   	(
   		case type of
   		(
   			#arm:
   			(
   				IKHI											= IKSys.ikChain boneChain[2] boneChain[3].children[boneChain[3].children.count] "IKHISolver"
   				IKHI.controller.dispGoal	= true
   				IKHI.name									= ("IK_" + lin.name)
   
   				IKHI_A											= IKSys.ikChain autoClavChain[2] autoClavChain[3].children[1] "IKHISolver"
   				IKHI_A.controller.dispGoal	= false
   				IKHI_A.name									= ("IK_" + lin.name + "_A")
   
   				p = point pos:IKHI.pos size:hw wireColor:(color 14 255 2)
   				p[4][2].value = false
   				p[4][4].value = false
   				p[4][5].value = true
   
   				for c in #(IKHI, IKHI_A) do
   				(
   					c.controller.dispSolver = false
   					c.controller.goalSize		= c.controller.endJoint.length
   			
   					c.pos.controller = position_Constraint()
   					c.pos.controller.appendTarget p 100
   				)
   			)
   		) -- end case (type)
   	) -- end if (IKChain == true)
   
   
   
   	boneChain
   ) -- end boneChainFromLine
   
   
   try (destroyDialog RO_BoneChainFromLine) catch()
   rollout RO_BoneChainFromLine "Bone Chain From Line"
   (
   	local multiSplines = false
   
   	fn filterLine obj =
   	(
   		(classOf obj == Line) and
   		(
   			case RO_BoneChainFromLine.rad_Type.state of
   			(
   				1: numSplines obj == 1 and numKnots obj 1 > 2	-- head and neck
   				2: numSplines obj == 1 and numKnots obj 1 > 1	-- spine
   				3: numKnots obj 1 == 6												-- leg
   				4: numSplines obj > 1 and numKnots obj 1 == 4	-- arm
   				default: numSplines obj == 1
   			)
   		)
   	) -- end filterLine
   
   
   	-- Rollout controls
   	-------------------
   	pickButton		pck_Line "Select Line" filter:filterLine autoDisplay:true width:270
   	label					lbl_Type "Chain type:" pos:[15,35]
   	radioButtons	rad_Type labels:#("head and neck", "spine", "leg", "arm", "default") columns:1 default:5	pos:[15,55]		
   
   	radioButtons	rad_Side labels:#("left", "right") enabled: false pos:[15,140]
   
   	checkButton		chk_IK "IK" enabled:false pos:[15,165]
   
   	label					lbl_Width "Bone Width:"													pos:[164,35]
   	editText			edt_Width width:36 text:"10"										pos:[224,35]
   	checkButton		chk_Percent "%" width:20 height:18 checked:true	pos:[265,35] 
   	radioButtons	rad_Width labels:#("per segment", "of shortest segment", "of longest segment", "of chain length") columns:1 default:3 pos:[164,55]
   
   	label					lbl_FingerWidth "Finger Width:"													pos:[164,125] enabled:false
   	editText			edt_FingerWidth width:36 text:"20"											pos:[230,125] enabled:false
   	checkButton		chk_FingerPercent "%" width:20 height:18 checked:true		pos:[271,125] enabled:false
   	radioButtons	rad_FingerWidth labels:#("per segment", "of shortest segment", "of longest segment", "of chain length") columns:1 default:3 pos:[164,145] enabled:false
   
   	label					lbl_CharacterName "Character Name:"	pos:[15,195]
   	editText			edt_CharacterName width:278					pos:[10,210]
   
   	label					lbl_BoneNames "Bone Names:"	pos:[15,230]
   	editText			edt_BoneNames width:278			pos:[10,245]
   
   	label					lbl_Info "Please select a line"
   	button				btn_CreateChain "Create Chain" enabled:false
   	-------------------
   
   
   	local side
   
   	fn setNames =
   	(
   		edt_BoneNames.text	= ""
   		prefix							= "Bone_" + edt_CharacterName.text
   		segCount						= (numKnots pck_Line.object 1) - 1
   
   		case rad_Type.state of
   		(
   			1: -- Generate names for neck and head bones
   			(
   				for i = 1 to (segCount - 1) do
   					edt_BoneNames.text = edt_BoneNames.text + prefix + "_Neck" + (i as string) + ", "
   
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Head"
   			)
   
   			2: -- Generate names for spine bones
   			(
   				for i = 1 to segCount do
   				(
   					edt_BoneNames.text = edt_BoneNames.text + prefix + "_Spine" + (i as string)
   					if i < segCount do edt_BoneNames.text = edt_BoneNames.text + ", "
   				)
   			)
   
   			3: -- Generate names for leg chain bones
   			(
   				side = if rad_Side.state == 1 then "L" else "R"
   
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Thigh" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Calf" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Instep" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Toe" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Heel" + side
   			)
   
   			4: -- Generate names for arm bone chains
   			(
   				side = if rad_Side.state == 1 then "L" else "R"
   
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_Clavicle" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_UpperArm" + side + ", "
   				edt_BoneNames.text = edt_BoneNames.text + prefix + "_ForeArm" + side
   			)
   
   			default:
   			(
   				for i = 1 to segCount do
   				(
   					edt_BoneNames.text = edt_BoneNames.text + prefix + (i as string)
   					if i < segCount do edt_BoneNames.text = edt_BoneNames.text + ", "
   				)
   			)
   		) -- end case (rad_Type.state)
   	) -- end setNames
   	-----------------
   
   	on pck_Line picked obj do
   	(
   		if edt_CharacterName.text == "" do edt_CharacterName.text = (filterString obj.name "_")[1]
   		setNames()
   		lbl_Info.text = "Ready"
   		btn_CreateChain.enabled	= true
   	) -- end handler (pck_Line)
   
   	on rad_Type changed state do
   	(
   		rad_Side.enabled = if (state == 3) or (state == 4) then true else false
   
   		-- Enable/disable finger related controls
   		for c in #(lbl_FingerWidth, edt_FingerWidth, chk_FingerPercent, rad_FingerWidth) do
   			c.enabled = if (state == 4) then true else false
   
   		chk_IK.enabled = if (state == 3 or state == 4) then true else false
   
   		if pck_Line.object != undefined do
   		(
   			obj = pck_Line.object
   			lbl_Info.text = "Ready"
   
   			case state of
   			(
   				3: if numKnots obj != 6 do lbl_Info.text = "First spline in shape must have 6 vertices"
   				4:
   				(
   					if numKnots obj 1 != 4 do
   						lbl_Info.text = "First spline in shape needs 4 vertices"
   					if numSplines obj < 3 do
   						lbl_Info.text = "Shape needs 3 or more splines"
   				)
   				default: if numSplines obj != 1 do lbl_Info.text	= "Shape must have 1 spline"
   			) -- end case (state)
   
   			if lbl_Info.text == "Ready" then
   			(
   				setNames()
   				btn_CreateChain.enabled	= true
   			)
   			else
   			(
   				btn_CreateChain.enabled	= false
   				edt_BoneNames.text = ""
   			)
   		)
   	) -- end handler (rad_Type)
   
   	on rad_Side changed state do
   	(
   		if pck_Line.object != undefined and lbl_Info.text == "Ready" then
   			setNames()
   		else
   			edt_BoneNames.text = ""
   	)
   
   	on chk_Percent changed state do
   		rad_Width.enabled = state
   
   	on btn_CreateChain pressed do with undo on
   	(
   		names = filterString edt_BoneNames.text ", "
   
   		chainType	=
   		(
   			case rad_Type.state of
   			(
   				1: #head
   				2: #spine
   				3: #leg
   				4: #arm
   				5: #default
   			)
   		) -- end chainType
   
   		wp	= if (not chk_Percent.state) then 0 else rad_Width.state -- percent based width type
   		fwp	= if (not chk_FingerPercent.state) then 0 else rad_FingerWidth.state
   
   		boneChainFromLine pck_Line.object names base:pck_Line.object.parent fWid:(edt_FingerWidth.text as float) fwp:fWP [color=Lime]IKChain:chk_IK.checked[/color] side:side type:chainType wid:(edt_Width.text as float) wp:wp 
   	) -- end handler (btn_CreateChain)
   )
   createDialog RO_BoneChainFromLine width:300

Next I’ll do the same for a reverse foot rig.

reverse foot… back to 00th… do you not know that the reverse foot is not in trend anymore?

interesting… who will be the first told us what a ‘reverse foot’ the biggest problem is?

i can give you a clue. the right answer contains only one word…

Er… rather than a clue, why not just tell me?

Page 1 / 3