Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "AE_SeamlessRotationSmartMaker"

-- **************************************************
-- General information about this script
-- **************************************************

AE_SeamlessRotationSmartMaker = {}

function AE_SeamlessRotationSmartMaker:Name()
	return 'Seamlessly rotating smartbone on/off'
end

function AE_SeamlessRotationSmartMaker:Version()
	return '1.0'
end

function AE_SeamlessRotationSmartMaker:UILabel()
	return 'Seamlessly rotating smartbone on/off'
end

function AE_SeamlessRotationSmartMaker:Creator()
	return 'Alexandra Evseeva'
end

function AE_SeamlessRotationSmartMaker:Description()
	return 'Create or discard a bone system to make smartbone joints rotaing seamlessly'
end


-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function AE_SeamlessRotationSmartMaker:IsRelevant(moho)
	return true
end

function AE_SeamlessRotationSmartMaker:IsEnabled(moho)
	return true
end

-- **************************************************
-- The guts of this script
-- **************************************************

function AE_SeamlessRotationSmartMaker:ReparentPoints(moho, layer, sourceBone, targetBone)
	if moho:LayerAsGroup(layer) then
		local group = moho:LayerAsGroup(layer)
		for i=0, group:CountLayers()-1 do
			self:ReparentPoints(moho, group:Layer(i), sourceBone, targetBone)
		end
	elseif moho:LayerAsVector(layer) then
		local skel = moho:Skeleton()
		local sourceID = skel:BoneID(sourceBone)
		local targetID = skel:BoneID(targetBone)
		local mesh = moho:LayerAsVector(layer):Mesh()
		for p = 0, mesh:CountPoints()-1 do
			local point = mesh:Point(p)
			if point.fParent == sourceID then point.fParent = targetID end
		end
	end
end

function AE_SeamlessRotationSmartMaker:MoveAngleAnimation(moho, sourceBone, targetBone)
	--copy animation from source to target in any of sources's smart actions (removing previously existing in target)
	local actionNames = {sourceBone:Name(), (sourceBone:Name().." 2")}
	for i, actionName in pairs(actionNames) do
		if moho.layer:HasAction(actionName) then
			local oldSmartRotation = sourceBone.fAnimAngle:ActionByName(actionName)
			if not oldSmartRotation then return string.format("Something went wrong: %s is not animated in its own action %s", sourceBone:Name(), actionName) end
			oldSmartRotation = moho:ChannelAsAnimVal(oldSmartRotation)
			if not targetBone.fAnimAngle:ActionByName(actionName) then
				targetBone.fAnimAngle:ActivateAction(actionName)
			end		
			local newSmartRotation = moho:ChannelAsAnimVal(targetBone.fAnimAngle:ActionByName(actionName))
			newSmartRotation:Clear()
			for k=0, oldSmartRotation:CountKeys()-1 do
				newSmartRotation:SetValue(oldSmartRotation:GetKeyWhen(k), oldSmartRotation:GetValueByID(k))
				local interp = MOHO.InterpSetting:new_local()
				oldSmartRotation:GetKeyInterpByID(k, interp)
				newSmartRotation:SetKeyInterp(oldSmartRotation:GetKeyWhen(k), interp)
			end
			oldSmartRotation:Clear()
		end
	end
	--rename actions
	for i, actionName in pairs(actionNames) do
		local newName = targetBone:Name() .. string.sub(actionName, #sourceBone:Name()+1)
		moho.layer:RenameAction(actionName, newName)
	end
	--rebind angle-dependent bones 
	local skel = moho:Skeleton()
	local sourceID = skel:BoneID(sourceBone)
	local targetID = skel:BoneID(targetBone)
	for b=0, skel:CountBones()-1 do
		local nextBone = skel:Bone(b)
		if nextBone.fAngleControlParent == sourceID then nextBone.fAngleControlParent = targetID end
	end
	--[[
	--if arm has points bind to it, lets rebind them
	self:ReparentPoints(moho, moho.layer, sourceBone, targetBone)
	--]]
	return nil
end

function AE_SeamlessRotationSmartMaker:CreateSeamlessSystem(moho, armBone)
	local skel = moho:Skeleton()
	local baseName = armBone:Name()
	armBone.fConstraints = false
	--find or create target bone
	local targetName = baseName .. "_smartTarget"
	local targetBone = skel:BoneByName(targetName)
	if not targetBone then
		targetBone = skel:AddBone(0)
		targetBone:SetName(targetName)
		targetBone.fAnimParent:SetValue(0, skel:BoneID(armBone))
		local tipVector = LM.Vector2:new_local()
		tipVector:Set(armBone.fLength, 0)
		targetBone.fAnimPos:SetValue(0, tipVector)
		targetBone.fLength = 0.0001
		targetBone.fShy = true
		targetBone.fHidden = true
	end
	--find or create joint bone
	local jointName = baseName .. "_smartElbow"
	local jointBone = skel:BoneByName(jointName)
	if not jointBone then
		jointBone = skel:AddBone(0)
		jointBone:SetName(jointName)
		jointBone.fAnimParent:SetValue(0, armBone.fAnimParent:GetValue(0))
		jointBone.fAnimPos:SetValue(0, armBone.fAnimPos:GetValue(0))
		jointBone.fAnimAngle:SetValue(0, 0)
		jointBone.fLength = 0
		jointBone.fPosControlParent = skel:BoneID(armBone)
		jointBone.fMaxConstraint = 0
		jointBone.fMinConstraint = 0
		jointBone.fShy = true	
		jointBone.fHidden = true
	end	
	--find or create smartBone
	local smartName = baseName .. "_smart"
	local smartBone = skel:BoneByName(smartName)
	if not smartBone then
		smartBone = skel:AddBone(0)
		smartBone:SetName(smartName)
		smartBone.fAnimParent:SetValue(0, skel:BoneID(jointBone))
		--[[]]
		--if no points to rebind
		smartBone.fLength = 0.0001
		--
		--[[
		--if arm has points bind to it, lets rebind them
		smartBone.fLength = armBone.fLength
		smartBone.fMaxAutoScaling = 1000
		smartBone.fSquashStretchScaling = armBone.fSquashStretchScaling
		--]]
		local zeroVector = LM.Vector2:new_local()
		smartBone.fAnimPos:SetValue(0, zeroVector)
		smartBone.fAnimAngle:SetValue(0, armBone.fAnimAngle:GetValue(0))
		smartBone.fTargetBone:SetValue(0, skel:BoneID(targetBone))
		smartBone.fShy = true
		smartBone.fHidden = true
	end
	--create smartBone animation for any of arm's smart actions (removing previously existing)
	return self:MoveAngleAnimation(moho, armBone, smartBone)
end

function AE_SeamlessRotationSmartMaker:DiscardSeamlessSystem(moho, armBone, smartBone)
	local skel = moho:Skeleton()
	local baseName = armBone:Name()
	local smartName = baseName .. "_smart"
	--if smartBone provided, test if this is the bone user wanted. Then rename it and all its friends.
	if smartBone then
		local userReturn = LM.GUI.Alert(LM.GUI.ALERT_INFO, "There are no "..smartName.." bone in skeleton. But "..smartBone:Name().." seems to be the fake smartBone to turn off. It will be renamed.", "", "", "OK", "Cancel")
		if userReturn > 0 then return end		
		--find joint and target bones and rename them first
		local targetBone = skel:Bone(smartBone.fTargetBone:GetValue(0))
		if targetBone then 
			targetBone:SetName(baseName .. "_smartTarget")
		else
			return LM.GUI.Alert(LM.GUI.ALERT_WARNING, smartBone:Name().." has no target. Aborting.")
		end 
		local jointBone = skel:Bone(smartBone.fAnimParent:GetValue(0))
		if jointBone then 
			jointBone:SetName(baseName .. "_smartElbow")
		end

	else
		smartBone = skel:BoneByName(smartName)
	end
	--create animation for armBone in smartBone actions
	local errorText = self:MoveAngleAnimation(moho, smartBone, armBone)
	if not errorText then 
		smartBone:SetName(smartName)
	else 
		return LM.GUI.Alert(LM.GUI.ALERT_WARNING, errorText)
	end
end

function AE_SeamlessRotationSmartMaker:Run(moho)
	moho.document:SetDirty()
	moho.document:PrepUndo(nil)
	
	--test if selected bone has smart actions and call create or discard the system respecively
	local skel = moho:Skeleton()
	if not skel then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Please select a bone layer") end
	
	local selBone = skel:Bone(skel:SelectedBoneID())
	if not selBone then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Please select any bone") end
	
	local selName = selBone:Name()
	if moho.layer:HasAction(selName) then return self:CreateSeamlessSystem(moho, selBone) end 
	
	local smartName = selName .. "_smart"
	if moho.layer:HasAction(smartName) then return self:DiscardSeamlessSystem(moho, selBone) end 
	
	if skel:BoneByName(smartName) then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, string.format("Something went wrong: You have %s bone, but no action for it", smartName)) end
	
	local armParentID = selBone.fAnimParent:GetValue(moho.frame)
	for b=0, skel:CountBones()-1 do
		if b ~= skel:SelectedBoneID() and skel:Bone(b).fAnimParent:GetValue(moho.frame) == armParentID then
			for bb =0, skel:CountBones()-1 do
				if skel:Bone(bb).fAnimParent:GetValue(moho.frame) == b then
					local targetID = skel:Bone(bb).fTargetBone:GetValue(moho.frame)
					if targetID >= 0 and skel:Bone(targetID).fAnimParent:GetValue(moho.frame) == skel:SelectedBoneID() then
						local smartBone = skel:Bone(bb)
						if moho.layer:HasAction(smartBone:Name()) then
							return self:DiscardSeamlessSystem(moho, selBone, smartBone)				
						end	
					end
				end
			end
		end
		local targetID = skel:Bone(b).fTargetBone:GetValue(moho.frame)
		if targetID >= 0 and skel:Bone(targetID).fAnimParent:GetValue(moho.frame) == skel:SelectedBoneID() then
			local smartBone = skel:Bone(b)
			if moho.layer:HasAction(smartBone:Name()) then
				return self:DiscardSeamlessSystem(moho, selBone, smartBone)				
			end
		end
	end
	
	return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Something went wrong: did not find any action related to selected bone, neither seamless, nor common")
	
end

Seamless rotation maker
Listed

Script type: Button/Menu

Uploaded: Aug 26 2022, 07:35

Script Version: 1.0

Create bone system to seamlessly rotate bone 360+ with correct use of smartbone actions associated with it.
Select bone whish have to rotate 360+ (forearm with elbow smart actions, for example) and use the script.

Installation Options:

This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 1163