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 
				

