[Closed] Scripting controllers – constrain circle to bounding box?
I know this can be done, but I’m utterly stuck at the minute.
I’m building a rigging tool helper for our pipeline, so that animations can be abstracted from the actual bones and saved/loaded instead onto a set of joystick controllers.
I’ve built a function that builds a joystick, but I have NO IDEA how to make the controller circle stay within the the rectangular bounding box. The bounding box is the parent of the circle, so I’m starting with the assumption that I need to use
in coordsys parent
to report the position of the circle, and clamp it if it goes outside the boundingbox.
So, I’ve got a circle, “cc”, and a bounding box “cb”. I’ve got variables for the size of the joystick bounding box (jsl and jsl for length and width), so I assume that I’ll need to query the x,y position of the circle in relation to the box.
But…I’m stuck.
Any pointers?
Hi Rick, try using the “float_limit()” controller. You can also wire the limits of the controller to the width and height of the box.
i.e. If the box have 10 units height, the up limit of the circle will be the “height/2”
hope it helps
See ya
Hi Rick!
Fabiomussarela is a good starting point.
Otherwise you will have to make a number of calculations based on the distance that the
circle control has moved from the center of the parent, taking into account the size of the both the circle control and it’s parent.
It can be done, but setting up can be tricky.
Shane
Cheers for the fast reply! I’m at home at the minute, but I’ll give that a shot when I get in tomorrow. After clearing bugs, obviously.
Still at home, having a wee look at the online docs – float_limit does allow for limits, but can I lock it to x and y? How to I specify a position lock?
Taken from Autodesk:
<float_limit>.lower_limit Float default: -10000.0
For a horizontal controller, the position would range from -jsw to +jsw (by default on my joystick, that’s -0.1 to +0.1), so I’d use
<float_limit>.lower_limit Float default: -jsw
and do the same for an upper of course:
<float_limit>.upper_limit Float default: jsw
Since my joystick controller allows me to create vertical, horizontal AND square controllers, I’m wondering if this will work for the square controller?
Perhaps for square it doesn’t matter, since x and y are the same size anyway.
Not having max in front of me is annoying…
In the end, I gave up on the DependsOn and FloatLimit, and ripped off another script that uses IK.
So firstly, credit for the constraints goes to :
– Rigging Tools 0.45
– Alden Filion / pDe
– alden@aldenfilion.com
And now the rest of the joystick creation script. We work on a small scale, where 1 unit = 1 metre, hence the multiplier on line 1 that scales everything down by a factor of 10.
I’ve tried to abstract it to a function as much as possible, as I’ll like be calling the function a dozen times to build a complete rig with no UI. The UI is there solely to test the function.
-- Generic Joystick creation script with test UI.
-- Rick Stirling
-- November 2007
-- A universal scaler for different sized joysticks
global sizemulti = 0.1
-- Function that builds a joystick
fn createJoystick jsn jstyle jpos =
(
-- setup our default sizes
jsl = 1 * sizemulti as float -- bounding box length
jsw = 1 * sizemulti as float -- bounding box width
jscor = 0.1 * sizemulti as float -- rounded corners
jscir = 0.1 * sizemulti as float -- and the circle size
jscap = 0.2 * sizemulti as float -- caption text size
jstxsp = jsl - 0.3 * sizemulti as float -- caption text offset
-- The size of the bounding box will depend on the style chosen, so adjust variables now
if jstyle == 2 then
(
-- Vertical style stick
jsw = 0.2 as float
jscor = 0.1 as float
)
else if jstyle == 3 then
(
-- Horizontal style stick
jsl = 0.2 as float
jscor = 0.1 as float
)
-- create bounding box for the joystick, keep it selected
jsbox = Rectangle length:jsl width:jsw cornerRadius:jscor position:jpos name: ("JSB_" + jsn) wirecolor:(color 250 230 100) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) isselected:on
cb = jsbox -- punt this to a temp variable to select it later if I need to. cb means current box
-- add the caption, parent it to the bounding box
jscaption = Text text: jsn size: jscap wirecolor:(color 20 20 255) name: ("JSCaption_" + jsn) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
jscaption.parent = jsbox
in coordsys parent jscaption.position = [0,jstxsp,0]
-- and create the circle controller, parent it to the bounding box
jscircle = Circle radius: (jscir) name: ("JS_Circle_" + jsn) wirecolor:(color 20 20 255) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
jscircle.parent = jsbox
cc = jscircle -- punt this to a temp variable to select it later. cc means current circle.
in coordsys parent jscircle.position = [0,0,0]
--in coordsys parent jscircle.position = [jsl/2,jsw/2,0]
-- now that we have the controller bits built, we need to limit the controller
-- circle to the bounding box edges.
-- can't figure it our with dependsOn, so I'll use IK
-- NIK code from Alden Filion / pDe
NIK = iksys.ikchain cb cc "IKHISolver"
NIK.name = jsn + "_IKChain"
hide NIK
cb.transform.controller.RotXActive = off
cb.transform.controller.RotYActive = off
cb.transform.controller.RotZActive = off
cc.transform.controller.PosXActive = on
cc.transform.controller.PosYActive = on
cc.transform.controller.PosXLimited = on
cc.transform.controller.PosXLLimited = on
cc.transform.controller.PosXULimited = on
cc.transform.controller.PosYLimited = on
cc.transform.controller.PosYLLimited = on
cc.transform.controller.PosYULimited = on
-- set up the limits for square style controller, modifiy if needed
cc.transform.controller.PosXLLimit = -(jsw/2)
cc.transform.controller.PosXULimit = (jsw/2)
cc.transform.controller.PosYLLimit = -(jsl/2)
cc.transform.controller.PosYULimit = (jsl/2)
if (jstyle == 2) then
(
-- Vertical style, no X movement
cc.transform.controller.PosXLLimit = 0
cc.transform.controller.PosXULimit = 0
)
if (jstyle == 3) then
(
-- Horizontal style, no Y movement
cc.transform.controller.PosYLLimit = 0
cc.transform.controller.PosYULimit = 0
)
select cb -- Select the parent of the joystick
)
rollout crig "Control Rig builder" width:163 height:175
(
-- UI here
group "Create New Joystick"
(
Edittext jsn "Joystick Name: "
RadioButtons jstype labels:#("Square", "Vertical", "Horizontal")
Button crjstick "Create Joystick"
)
on crjstick pressed do
(
-- call the joystick creation function with the UI data
jsname = jsn.text as string
jstyle = jstype.state as integer
jpos = [1,0,2] -- the position on screen. This can overridden later, perhaps by mouse click.
createJoystick jsname jstyle jpos
)
) -- rollout end
createDialog crig 250 400
Back to float_limit() – I really wanted to get a handle on this, so I’ve ben chipping away, and I nailed it. It’s a much more elegant solution, so thank you for suggesting it.
The actual constraining fr x and y is now done with float limits and wire parameters, thusly:
xfl=float_limit()
jscircle.pos.controller.x_position.controller=xfl
paramWire.connect jsbox.baseObject[#width] xfl.limits[#upper_limit] ((conjsw/2) as string)
paramWire.connect jsbox.baseObject[#width] xfl.limits[#lower_limit] ((-conjsw/2) as string)
yfl=float_limit()
jscircle.pos.controller.y_position.controller=yfl
paramWire.connect jsbox.baseObject[#length] yfl.limits[#upper_limit] ((conjsl/2) as string)
paramWire.connect jsbox.baseObject[#length] yfl.limits[#lower_limit] ((-conjsl/2) as string)
I’ve tidied the script in other areas too – the function doen’t select the joystick anymore, it returns it instead, so I can call the function as a constructor instead. I killed unwanted variables, and I think it’s really nice – but I’ll take any feedback.
The paraWire drove me mad for while until I figured out I had to convert my float to a string.
Feel free to use/edit/steal.
-- Generic Joystick creation script with test UI.
-- Rick Stirling
-- November 2007
-- A universal scaler for different sized joysticks
global sizemulti = 1
-- Function that builds a joystick
fn createJoystick jsn jstyle jpos =
(
-- setup our default sizes
local jsl = conjsl = 0.1 * sizemulti as float -- bounding box length
local jsw = conjsw = 0.1 * sizemulti as float -- bounding box width
local jscor = 0.01 * sizemulti as float -- rounded corners
local jscir = 0.01 * sizemulti as float -- and the circle size
local jscap = 0.02 * sizemulti as float -- caption text size
local jstxsp = jsl - 0.03 * sizemulti as float -- caption text offset
-- The size of the bounding box will depend on the style chosen, so adjust variables now
if jstyle == 2 then
(
-- Vertical style stick
jsw = 0.02 as float
jscor = 0.01 as float
conjsw=0
)
else if jstyle == 3 then
(
-- Horizontal style stick
jsl = 0.02 as float
jscor = 0.01 as float
conjsl=0
)
-- create bounding box for the joystick, keep it selected
jsbox = Rectangle length:jsl width:jsw cornerRadius:jscor position:jpos name: ("JSB_" + jsn) wirecolor:(color 250 230 100) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) isselected:on
-- add the caption, parent it to the bounding box
jscaption = Text text: jsn size: jscap wirecolor:(color 20 20 255) name: ("JSCaption_" + jsn) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
jscaption.parent = jsbox
in coordsys parent jscaption.position = [0,jstxsp,0]
-- and create the circle controller, parent it to the bounding box
jscircle = Circle radius: (jscir) name: ("JS_Circle_" + jsn) wirecolor:(color 20 20 255) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
jscircle.parent = jsbox
in coordsys parent jscircle.position = [0,0,0]
-- now that we have the controller bits built, we need to limit the controller
-- circle to the bounding box edges.
-- use float limits based on cgtalk ideas
xfl=float_limit()
jscircle.pos.controller.x_position.controller=xfl
paramWire.connect jsbox.baseObject[#width] xfl.limits[#upper_limit] ((conjsw/2) as string)
paramWire.connect jsbox.baseObject[#width] xfl.limits[#lower_limit] ((-conjsw/2) as string)
yfl=float_limit()
jscircle.pos.controller.y_position.controller=yfl
paramWire.connect jsbox.baseObject[#length] yfl.limits[#upper_limit] ((conjsl/2) as string)
paramWire.connect jsbox.baseObject[#length] yfl.limits[#lower_limit] ((-conjsl/2) as string)
return jsbox
)
rollout crig "Control Rig builder" width:163 height:175
(
-- UI here
group "Create New Joystick"
(
Edittext jsn "Joystick Name: "
RadioButtons jstype labels:#("Square", "Vertical", "Horizontal")
Button crjstick "Create Joystick"
)
on crjstick pressed do
(
-- call the joystick creation function with the UI data
jsname = jsn.text as string
jstyle = jstype.state as integer
jpos = [1,0,2] -- the position on screen. This can overridden later, perhaps by mouse click.
seljoy = createJoystick jsname jstyle jpos
)
) -- rollout end
createDialog crig 250 400
Something else useful – a normalise function for the joystick. Since I create them at a small scale, and normally you’ll want them to be in the range -1 to +1, this function takes a controller as input, queries the parent bounding box, and return a multiplier for X and Y
– normalise joystick
– get the size of the boundingbox, create a normalise multiplier
fn joystick_normalise stickcontrol =
(
nmx = 1/($.parent.width)*2
nmy = 1/($.parent.length)*2
xyra = [nmx, nmy]
return xyra
)
I’ve figured out part two, so I thought I may as well share – linking controllers to bones, and controllers to controllers.
My only currnet sticking point is that I can’t seem to link a controller to two other controllers that control bones – so if I have a left eye controller and a right eye controller, trying to make a parent controller for those two ails.
Anyway, functions plus examples
-- a function to wire joystick controllers to bones
fn wire_control_bone thecontrol caxis nrmlzr thebone baxis anglelimit =
(
-- multiply normaliser with angle limit
theamount = nrmlzr * anglelimit
-- Get the current base rotations of the bone.
tr = in coordsys world thebone.transform.rotation as eulerangles
rx = tr.x
ry = tr.y
rz = tr.z
angleoffset = rx -- pick one to initialise the variable
-- which axis is the controller changing on?
-- 1 = x axis, 2= y axis
if caxis == 1 then
(
pwcontrol = thecontrol.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
)
else if caxis == 2 then
(
pwcontrol = thecontrol.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
)
-- which axis is the bone to rotate on?
-- 1 = x axis, 2= y axis, 3 = z axis
if baxis == 1 then
(
pwtarget = thebone.rotation.controller[#X_Rotation]
angleoffset = rx
)
else if baxis == 2 then
(
pwtarget = thebone.rotation.controller[#Y_Rotation]
angleoffset = ry
)
else if baxis == 3 then
(
pwtarget = thebone.rotation.controller[#Z_Rotation]
angleoffset = rz
)
-- setup the rotation expression
controlexp = "(Limited_Controller__Bezier_Float * degtorad " + (theamount as string) + ")"
-- add the rotational offset
controlexp = controlexp + " + degtorad " + (angleoffset as string)
-- do the connecting
paramWire.connect pwcontrol pwtarget controlexp
)
-- allow a controller to control other controllers
fn wire_control_control Cinput ciaxis ctarget ctaxis theamount=
(
-- which axis is the controller changing on?
-- 1 = x axis, 2= y axis
if ciaxis == 1 then
(
pwcontrol = Cinput.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
)
else if ciaxis == 2 then
(
pwcontrol = Cinput.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
)
-- which axis is the target changing on?
-- 1 = x axis, 2= y axis
if ctaxis == 1 then
(
pwtarget = ctarget.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
)
else if ctaxis == 2 then
(
pwtarget = ctarget.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
)
-- setup the rotation expression
controlexp = "(Limited_Controller__Bezier_Float * " + (theamount as string) + ")"
-- do the connecting
paramWire.connect pwcontrol pwtarget controlexp
)
-- 1 = x axis, 2= y axis, 3 = z axis
x = 1
y = 2
z = 3
-- eyes l/r to eye controller left, multiply by 1
--wire_control_control $'JS_Circle_Eyes' x $'JS_Circle_Left_Eye' x 1
--wire_control_control $'JS_Circle_Eyes' y $'JS_Circle_Left_Eye' y 1
--wire_control_control $'JS_Circle_Eyes' x $'JS_Circle_Right Eye' x 1
--wire_control_control $'JS_Circle_Eyes' y $'JS_Circle_Right Eye' y 1
-- left eye L/R
wire_control_bone $'JS_Circle_Left_Eye' x 20 $'FB_L_Eye' z 45
-- left eye U/D
wire_control_bone $'JS_Circle_Left_Eye' y 20 $'FB_L_Eye' y -45
-- right eye L/R
wire_control_bone $'JS_Circle_Right Eye' x 20 $FB_R_Eye z 45
-- right eye U/D
wire_control_bone $'JS_Circle_Right Eye' y 20 $'FB_R_Eye' y -45