When you drag a bone in a FK chain, it will rotate and scale it's parentbone as if it were IK. And rotate the selected bone as if its angle was independent. As you see in the GIF, when the parentbone that's being adjusted has a parentbone with an angle that's not 0 degrees the mouse is offset and the tool is broken.
The tool also works for entire chains if you set 'local singleMode = true' to false and drag a bone in a chain of multiple parents that are not set to ignore IK. But the problem doesn't happen in that case because it uses Mike's IKSolver function.
See code below GIF. The tool is dependant on FO_Utilities. Here's the testfile from the GIF: testfile

Code: Select all
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************
ScriptName = "LK_FakeIK"
-- **************************************************
-- General information about this script
-- **************************************************
LK_FakeIK = {}
function LK_FakeIK:Name()
return "LK_FakeIK"
end
function LK_FakeIK:Version()
return "0.0"
end
function LK_FakeIK:Description()
return "LK_FakeIK"
end
function LK_FakeIK:Creator()
return "Lukas Krepel, Frame Order"
end
function LK_FakeIK:UILabel()
return "LK_FakeIK"
end
function LK_FakeIK:ColorizeIcon()
return true
end
-- **************************************************
-- The guts of this script
-- **************************************************
function LK_FakeIK:IsEnabled(moho)
return true
end
function LK_FakeIK:IsRelevant(moho)
local skel = moho:Skeleton()
if (skel == nil) then
return false
end
return true
end
function LK_FakeIK:DoLayout(moho, layout)
FO_Utilities:Divider(layout, "FakeIK", true)
end
function LK_FakeIK:UpdateWidgets(moho)
-- *
end
function LK_FakeIK:OnMouseDown(moho, mouseEvent)
self.bone = nil
self.startBoneVec = nil
self.mousePickedID = nil
self.angleBones = {}
moho.document:PrepMultiUndo()
moho.document:SetDirty()
self.skel = moho:Skeleton()
-- *
local layer = moho.layer -- * TODO Maybe use rig layer instead?
self.mousePickedID = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, layer, true) -- * Pick exact.
if self.mousePickedID == -1 then
self.mousePickedID = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, layer, false) -- * Don't pick exact.
end
self.bone = self.skel:Bone(self.mousePickedID)
local parentBone = self.skel:Bone(self.bone.fParent)
if parentBone == nil or parentBone.fIgnoredByIK then
return
end
skel:SelectNone()
self.bone.fSelected = true
self.startBoneVec = LM.Vector2:new_local()
self.startBoneVec:Set(parentBone.fLength,0)
self.matrix = LM.Matrix:new_local()
self.matrix:Set(parentBone.fMovedMatrix)
self.matrix:Transform(self.startBoneVec)
while parentBone ~= nil and not parentBone.fIgnoredByIK do
table.insert(self.angleBones, parentBone)
local singleMode = true------TODO
if singleMode then
parentBone = nil
else
parentBone = self.skel:Bone(parentBone.fParent)
end
end
self.startAngle = self.bone.fAngle
for i = 1, #self.angleBones do
local angleBone = self.angleBones[i]
angleBone.fTempAngle = angleBone.fAngle
self.startAngle = self.startAngle + angleBone.fAngle
end
end
function LK_FakeIK:OnMouseMoved(moho, mouseEvent)
local parentBone = self.skel:Bone(self.bone.fParent)
if parentBone == nil or parentBone.fIgnoredByIK then
return
end
if self.bone ~= nil and moho.frame ~= 0 then
bone = self.bone
local offset = mouseEvent.vec - mouseEvent.startVec
local parent = nil
if (bone.fParent >= 0) then
parent = self.skel:Bone(bone.fParent)
end
local fakeTargetVec = LM.Vector2:new_local()
fakeTargetVec = self.startBoneVec + offset
-- * SINGLE BONE:
if #self.angleBones == 1 then
local parentBone = self.angleBones[1]
local parentBonePos = LM.Vector2:new_local()
self.matrix:Transform(parentBonePos)
local distance = FO_Utilities:Distance(moho, parentBonePos, fakeTargetVec)
local newScale = distance/parentBone.fLength
parentBone.fAnimScale:SetValue(moho.frame, newScale)
-- * Calculate angle:
local newAngle = math.atan2(fakeTargetVec.y - parentBonePos.y, fakeTargetVec.x - parentBonePos.x)
-- * Set angle:
local oldAngle = parentBone.fTempAngle
-- * TODO get shortest route from oldAngle to newAngle and adjust newAngle accordingly, so the bones don't spin around.
parentBone.fAnimAngle:SetValue(moho.frame, newAngle)
else
-- * BONE CHAIN:
if parent ~= nil then
self.skel:IKAngleSolver(bone.fParent, fakeTargetVec, 1, false, true) -- * M_Skeleton:IKAngleSolver(boneID, target, iterMultiplier, allowTwoBoneShortcut, allowBoneStretching)
for i = 1, #self.angleBones do
local parentBone = self.angleBones[i]
parentBone.fAnimAngle:SetValue(moho.frame, parentBone.fAngle)
end
end
end
local newAngle = self.startAngle
for i = 1, #self.angleBones do
newAngle = newAngle - self.angleBones[i].fAnimAngle.value
end
bone.fAnimAngle:SetValue(moho.frame, newAngle)
end
MOHO.Redraw()
moho.layer:UpdateCurFrame()
end
function LK_FakeIK:OnMouseUp(moho, mouseEvent)
if moho.frame == 0 then
return
end
FO_Utilities:PaintKeys(moho)
moho:UpdateUI()
end
function LK_FakeIK:YoungestChild(skel, bone) -- * TODO UGLY FUNCTION
for stupid = 0, 10 do
for i = 0, self.skel:CountBones() - 1 do
local b = skel:Bone(i)
local parent = skel:Bone(b.fParent)
if bone == parent then
bone = b
end
end
end
return bone
end