-- ************************************************** -- Provide Moho with the name of this script object -- ************************************************** ScriptName = "AE_MergeSkeletons" -- ************************************************** -- General information about this script -- ************************************************** AE_MergeSkeletons = {} function AE_MergeSkeletons:Name() return "Merge Skeletons" end function AE_MergeSkeletons:Version() return "2.1" end function AE_MergeSkeletons:UILabel() return "Merge Skeletons" end function AE_MergeSkeletons:Creator() return "Alexandra Evseeva" end function AE_MergeSkeletons:Description() return "Merge current skeleton with parent one" end -- ************************************************** -- Recurring values -- ************************************************** -- AE_MergeSkeletons.value1 = false -- ************************************************** -- Is Enabled -- ************************************************** function AE_MergeSkeletons:IsEnabled(moho) return true end -- ************************************************** -- The guts of this script -- ************************************************** function AE_MergeSkeletons:Run(moho) moho.document:PrepUndo(moho.layer) moho.document:SetDirty() local sourceBoneLayer = moho.layer local sourceSkeleton = moho:Skeleton() local targetSkeleton = sourceBoneLayer:ControllingSkeleton() local targetBoneLayer = sourceBoneLayer:Parent() if sourceSkeleton and not targetSkeleton then -- if no parent skeleton, perform unmerge instead of merge moho:SetCurFrame(0) local bones2detach, layers2detach, actions2detach = self:Bones2LayersAndActions(moho) self:UnMergeSkeleton(moho, bones2detach, layers2detach, actions2detach) return end nextBoneLayer = moho:LayerAsBone(targetBoneLayer) while not nextBoneLayer or not nextBoneLayer:Skeleton() == targetSkeleton do targetBoneLayer = targetBoneLayer:Parent() nextBoneLayer = moho:LayerAsBone(targetBoneLayer) end moho:SetSelLayer(targetBoneLayer) local boneMap = {} for boneID=0, sourceSkeleton:CountBones()-1 do local sourceBone = sourceSkeleton:Bone(boneID) local targetBone = targetSkeleton:AddBone(0) boneMap[sourceBone] = targetBone self:CopyBoneProps(sourceBone, targetBone) targetBone:SetName(sourceBone:Name()) end for k, v in pairs(boneMap) do if(k.fParent>-1) then v.fParent = targetSkeleton:BoneID(boneMap[sourceSkeleton:Bone(k.fParent)]) end if(k.fAngleControlParent>-1) then v.fAngleControlParent = targetSkeleton:BoneID(boneMap[sourceSkeleton:Bone(k.fAngleControlParent)]) v.fAngleControlScale = k.fAngleControlScale end if(k.fPosControlParent>-1) then v.fPosControlParent = targetSkeleton:BoneID(boneMap[sourceSkeleton:Bone(k.fPosControlParent)]) v.fPosControlScale = k.fPosControlScale end if(k.fScaleControlParent>-1) then v.fScaleControlParent = targetSkeleton:BoneID(boneMap[sourceSkeleton:Bone(k.fScaleControlParent)]) v.fScaleControlScale = k.fScaleControlScale end if(k.fTargetBone:GetValue(0)>-1) then v.fTargetBone:SetValue(0, targetSkeleton:BoneID(boneMap[sourceSkeleton:Bone(k.fTargetBone:GetValue(0))])) end end local layersMap = {} local oldGroup = moho:LayerAsGroup(sourceBoneLayer) local newGroup = moho:LayerAsGroup(targetBoneLayer) self:StoreBindings(moho, layersMap, oldGroup, sourceSkeleton) while oldGroup:CountLayers()>0 do local childLayer = oldGroup:Layer(0) moho:PlaceLayerInGroup(childLayer, newGroup, false) end local boneIDMap = {} for k, v in pairs(boneMap) do boneIDMap[sourceSkeleton:BoneID(k)] = targetSkeleton:BoneID(v) end self:RestoreBindings(moho, layersMap, boneIDMap) --[[ -- commented out because of falling Moho for sourceBone, targetBone in pairs(boneMap) do for _, boneTrackName in pairs({'fAnimPos', 'fAnimAngle', 'fAnimScale', 'fFlipH', 'fFlipV'}) do sourceTrack = sourceBone[boneTrackName] for a = 0, sourceTrack:CountActions() - 1 do local actionName = sourceTrack:ActionName(a) local sourceActionTrack = sourceTrack:ActionByName(actionName) sourceActionTrack = AE_Utilities:GetDerivedChannel(moho, sourceActionTrack) if not targetBoneLayer:HasAction(actionName) then targetBoneLayer:ActivateAction(actionName) end targetTrack = targetBone[boneTrackName] targetTrack:ActivateAction(actionName) targetActionTrack = targetTrack:ActionByName(actionName) targetActionTrack = AE_Utilities:GetDerivedChannel(moho, targetActionTrack) for f = 1, sourceActionTrack:Duration() do if sourceActionTrack:HasKey(f) then targetActionTrack:SetValue(f, sourceActionTrack:GetValue(f)) local keyProps = MOHO.InterpSetting:new_local() sourceActionTrack:GetKeyInterp(f, interp) targetActionTrack:SetKeyInterp(f, interp) end end end end end --]] for k, v in pairs(boneMap) do local nextActionName = k:Name() if sourceBoneLayer:HasAction(nextActionName) then sourceBoneLayer:ActivateAction(nextActionName) local animAngle = k.fAnimAngle print(nextActionName, " keys: ", animAngle:CountKeys()) targetBoneLayer:ActivateAction(nextActionName) for f=0, animAngle:Duration() do if animAngle:HasKey(f) then v.fAnimAngle:SetValue(f,animAngle:GetValue(f)) end for otherBoneID = 0, sourceSkeleton:CountBones()-1 do local otherBone = sourceSkeleton:Bone(otherBoneID) local otherTargetBone = boneMap[otherBone] if otherBone.fAnimAngle:HasKey(f) then otherTargetBone.fAnimAngle:SetValue(f, otherBone.fAnimAngle:GetValue(f)) end if otherBone.fAnimPos:HasKey(f) then otherTargetBone.fAnimPos:SetValue(f, otherBone.fAnimPos:GetValue(f)) end if otherBone.fAnimScale:HasKey(f) then otherTargetBone.fAnimScale:SetValue(f, otherBone.fAnimScale:GetValue(f)) end if otherBone.fFlipH:HasKey(f) then otherTargetBone.fFlipH:SetValue(f, otherBone.fFlipH:GetValue(f)) end if otherBone.fFlipV:HasKey(f) then otherTargetBone.fFlipV:SetValue(f, otherBone.fFlipV:GetValue(f)) end end end end nextActionName = k:Name() .. " 2" if sourceBoneLayer:HasAction(nextActionName) then sourceBoneLayer:ActivateAction(nextActionName) local animAngle = k.fAnimAngle print(nextActionName, " keys: ", animAngle:CountKeys()) targetBoneLayer:ActivateAction(nextActionName) for f=0, animAngle:Duration() do if animAngle:HasKey(f) then v.fAnimAngle:SetValue(f,animAngle:GetValue(f)) end for otherBoneID = 0, sourceSkeleton:CountBones()-1 do local otherBone = sourceSkeleton:Bone(otherBoneID) local otherTargetBone = boneMap[otherBone] if otherBone.fAnimAngle:HasKey(f) then otherTargetBone.fAnimAngle:SetValue(f, otherBone.fAnimAngle:GetValue(f)) end if otherBone.fAnimPos:HasKey(f) then otherTargetBone.fAnimPos:SetValue(f, otherBone.fAnimPos:GetValue(f)) end if otherBone.fAnimScale:HasKey(f) then otherTargetBone.fAnimScale:SetValue(f, otherBone.fAnimScale:GetValue(f)) end if otherBone.fFlipH:HasKey(f) then otherTargetBone.fFlipH:SetValue(f, otherBone.fFlipH:GetValue(f)) end if otherBone.fFlipV:HasKey(f) then otherTargetBone.fFlipV:SetValue(f, otherBone.fFlipV:GetValue(f)) end end end end end sourceBoneLayer:ActivateAction("") while sourceSkeleton:CountBones()>0 do sourceSkeleton:DeleteBone(0,0) end moho.layer:ActivateAction("") end function AE_MergeSkeletons:CopyBoneProps(src, trg) trg.fLength = src.fLength trg.fAnimPos:SetValue(0, src.fPos) trg.fAnimAngle:SetValue(0, src.fAngle) trg.fAnimScale:SetValue(0, src.fScale) trg:ShowLabel(src:IsLabelShowing()) trg.fShy = src.fShy trg.fStrength = src.fStrength trg.fFixedAngle = src.fFixedAngle trg.fMinConstraint = src.fMinConstraint trg.fMaxConstraint = src.fMaxConstraint trg.fConstraints = src.fConstraints trg.fIgnoredByIK = src.fIgnoredByIK trg.fMaxAutoScaling = src.fMaxAutoScaling end function AE_MergeSkeletons:StoreBindings(moho, storeArray, rootGroup, sourceSkeleton) for childID = 0, rootGroup:CountLayers()-1 do local childLayer = rootGroup:Layer(childID) if childLayer:ControllingSkeleton() == sourceSkeleton then local storeObject = {} storeObject["layer"] = childLayer:LayerParentBone() if childLayer:LayerType() == MOHO.LT_VECTOR then local vertices = {} moho:SetSelLayer(childLayer) local mesh = moho:Mesh() for pointID = 0, mesh:CountPoints()-1 do vertices[pointID] = mesh:Point(pointID).fParent end storeObject["vertices"] = vertices end storeArray[childLayer] = storeObject if childLayer:IsGroupType() then local childGroup = moho:LayerAsGroup(childLayer) self:StoreBindings(moho, storeArray, childGroup, sourceSkeleton) end end end end function AE_MergeSkeletons:StoreBindings2(moho, storeArray, layer, sourceSkeleton) if layer:ControllingSkeleton() ~= sourceSkeleton then return end local storeObject = {} storeObject["layer"] = layer:LayerParentBone() if layer:LayerType() == MOHO.LT_VECTOR then local vertices = {} local mesh = moho:LayerAsVector(layer):Mesh() for pointID = 0, mesh:CountPoints()-1 do vertices[pointID] = mesh:Point(pointID).fParent end storeObject["vertices"] = vertices end storeArray[layer] = storeObject if layer:IsGroupType() then local childGroup = moho:LayerAsGroup(layer) for nch = 0, childGroup:CountLayers() - 1 do self:StoreBindings2(moho, storeArray, childGroup:Layer(nch), sourceSkeleton) end end end function AE_MergeSkeletons:RestoreBindings(moho, storeArray, boneIDMap) for k, v in pairs(storeArray) do if v.layer > -1 then k:SetLayerParentBone(boneIDMap[v.layer]) end if k:LayerType() == MOHO.LT_VECTOR then moho:SetSelLayer(k) local mesh = moho:Mesh() for pointID = 0, mesh:CountPoints()-1 do if v.vertices[pointID] > -1 then local newBone = boneIDMap[v.vertices[pointID]] mesh:Point(pointID).fParent = newBone end end end end end function AE_MergeSkeletons:Bones2LayersAndActions(moho) -- get current skeleton selected bones and collect: -- layers, driven with selected bones -- actions, driving only these layers and nothing else -- additional bones, driving only these layers and nothing else -- additional smartbones, driving only these layers and bones and nothing else --collect selected bones local skel = moho:Skeleton() local bones2detach = {} for b = 0, skel:CountBones() - 1 do if skel:Bone(b).fSelected then table.insert(bones2detach, skel:Bone(b)) end end --collect layers driven with collected bones and bones driving found layers local layers2detach = {} local mainGroup = moho:LayerAsGroup(moho.layer) for nchld = 0, mainGroup:CountLayers() - 1 do local nextChild = mainGroup:Layer(nchld) local doesBelong, additionalBones = self:CheckBoneBelong(moho, skel, bones2detach, nextChild) if doesBelong then table.insert(layers2detach, nextChild) for i, bone in pairs(additionalBones) do if not bone.fSelected then bone.fSelected = true table.insert(bones2detach, bone) end end end end --if collecting new bones results in need to collect new layers then abort for nchld = 0, mainGroup:CountLayers() - 1 do local nextChild = mainGroup:Layer(nchld) local doesBelong, additionalBones = self:CheckBoneBelong(moho, skel, bones2detach, nextChild) if doesBelong then if #additionalBones > 0 then local boneList = "" for j, bone in pairs(additionalBones) do boneList = boneList .. " " .. bone:Name() end local warning = "These bones do not drive some separate layers to detach" local waring2 = "additional bones used are, for example: " LM.GUI.Alert(LM.GUI.ALERT_WARNING, warning, warning2, boneList) return bones2detach, layers2detach, {} end local newLayer = true for i, layer in pairs(layers2detach) do if nextChild == layer then newLayer = false break end end if newLayer then table.insert(layers2detach, nextChild) end end end -- collect actions used only in collected layers local actions2detach = {} for a = 0, moho.layer:CountActions() - 1 do local actionName = moho.layer:ActionName(a) local foundInCollected = false local foundInOthers = false for nchld = 0, mainGroup:CountLayers() - 1 do local layer = mainGroup:Layer(nchld) local isLayerCollected = false for j, colLayer in pairs(layers2detach) do if colLayer == layer then isLayerCollected = true break end end if layer:ActionDuration(actionName) > 0 then if isLayerCollected then foundInCollected = true else foundInOthers = true end end if foundInCollected and foundInOthers then break end end if foundInCollected and not foundInOthers then table.insert(actions2detach, actionName) end end -- collect smartbones driving collected actions (inserting into bones array too) for i, actionName in pairs(actions2detach) do if moho.layer:IsSmartBoneAction(actionName) then local smartBoneName = actionName if string.sub(actionName, -2) == " 2" then smartBoneName = string.sub(actionName, 1, -3) end local smartbone = skel:BoneByName(smartBoneName) if smartbone then local newBone = true for j, bone in pairs(bones2detach) do if bone == smartbone then newBone = false break end end if newBone then table.insert(bones2detach, smartbone) end end end end return bones2detach, layers2detach, actions2detach end function AE_MergeSkeletons:CheckBoneBelong(moho, skel, bones, layer) -- flexi binding without restrictions (to all the bones of parent skeleton) if layer:LayerParentBone() == -2 then return false, {} end -- layer belongs to a single bone if layer:LayerParentBone() > -1 then for i, bone in pairs(bones) do if skel:BoneID(bone) == layer:LayerParentBone() then return true, {} end end return false, {} end -- layer belongs to multiple bones if layer:LayerParentBone() == -3 then local flexiBones = {} local doesBelong = false for b = 0, skel:CountBones() - 1 do if layer:IsIncludedInFlexiBoneSubset(b) then local thisBone = false for i, bone in pairs(bones) do if skel:BoneID(bone) == b then doesBelong = true thisBone = true break end end if not thisBone then table.insert(flexiBones, skel:Bone(b)) end end end if doesBelong then return true, flexiBones end return false, {} end -- layer is vector if moho:LayerAsVector(layer) then local otherBones = {} local doesBelong = false local mesh = moho:LayerAsVector(layer):Mesh() for p = 0, mesh:CountPoints()-1 do local boneID = mesh:Point(p).fParent if boneID > -1 then local thisBone = false for i, bone in pairs(bones) do if skel:BoneID(bone) == boneID then doesBelong = true thisBone = true break end end if not thisBone then table.insert(otherBones, skel:Bone(boneID)) end end end if doesBelong then return true, otherBones end return false, {} end -- layer is group if moho:LayerAsGroup(layer) then local doesBelong = false local otherBones = {} local aGroup = moho:LayerAsGroup(layer) for nch = 0, aGroup:CountLayers() - 1 do local nextChild = aGroup:Layer(nch) if nextChild:ControllingSkeleton() == skel then local nextBelong, nextBones = self:CheckBoneBelong(moho, skel, bones, nextChild) --print("Checking ", nextChild:Name(), "... ", tostring(nextBelong)) if nextBelong then doesBelong = true end for i, bone in pairs(nextBones) do local allwaysCollected = false for j, clbone in pairs(otherBones) do if clbone == bone then allwaysCollected = true break end end if not allwaysCollected then table.insert(otherBones, bone) end end end end if doesBelong then return true, otherBones end return false, {} end return false, {} end function AE_MergeSkeletons:UnMergeSkeleton(moho, bones2detach, layers2detach, actions2detach) -- duplicate current skeleton layer local sourceLayer = moho.layer local sourceSkeleton = moho:LayerAsBone(sourceLayer):Skeleton() local targetLayer = moho:DuplicateLayer(sourceLayer) local targetSkeleton = moho:LayerAsBone(targetLayer):Skeleton() if #layers2detach > 0 then targetLayer:SetName(layers2detach[1]:Name()) end -- delete layers from target local otherLayers = {} local sourceGroup = moho:LayerAsGroup(sourceLayer) local targetGroup = moho:LayerAsGroup(targetLayer) for i = 0, sourceGroup:CountLayers() - 1 do local toCollect = true for j, layer in pairs(layers2detach) do if layer == sourceGroup:Layer(i) then toCollect = false break end end if toCollect then table.insert(otherLayers, targetGroup:Layer(i)) end end for i, layer in pairs(otherLayers) do moho:DeleteLayer(layer) end -- delete bones from target local otherBones = {} local boneMap = {} for b = 0, sourceSkeleton:CountBones() - 1 do local toCollect = true for i, bone in pairs(bones2detach) do if bone == sourceSkeleton:Bone(b) then toCollect = false break end end if toCollect then table.insert(otherBones, targetSkeleton:Bone(b)) else boneMap[targetSkeleton:Bone(b)] = sourceSkeleton:Bone(b) end end for i, bone in pairs(otherBones) do local boneID = targetSkeleton:BoneID(bone) for j = 0, targetGroup:CountLayers() - 1 do targetGroup:Layer(j):DeleteParentBone(boneID) end targetSkeleton:DeleteBone(boneID) end -- delete NOT USED actions from target local otherActions = {} for a = 0, targetLayer:CountActions() - 1 do local actionName = targetLayer:ActionName(a) targetLayer:ActivateAction(actionName) targetLayer:ActivateAction(nil) if targetLayer:ActionDuration(actionName) < 1 then table.insert(otherActions, actionName) end end for i, actionName in pairs(otherActions) do targetLayer:DeleteAction(actionName) end -- copy animations into root target bones from their source copies (for all actions) for b = 0, targetSkeleton:CountBones() - 1 do local nextBone = targetSkeleton:Bone(b) if nextBone.fAnimParent:GetValue(0) == -1 then self:FixBoneAnimation(moho, nextBone, boneMap[nextBone], sourceSkeleton, moho.document:EndFrame() + targetLayer:TotalTimingOffset()) for a = 0, targetLayer:CountActions() - 1 do local actionName = targetLayer:ActionName(a) self:FixBoneAnimation(moho, nextBone, boneMap[nextBone], sourceSkeleton, targetLayer:ActionDuration(actionName), actionName) end end end -- delete layers, bones and actions from old skeleton local topNumber = -1 for i = 0, sourceGroup:CountLayers()-1 do for j, layer in pairs(layers2detach) do if sourceGroup:Layer(i) == layer then topNumber = i + 1 end end end local topLayer = nil if topNumber < sourceGroup:CountLayers() then topLayer = sourceGroup:Layer(topNumber) end for i, layer in pairs(layers2detach) do moho:DeleteLayer(layer) end for i, actionName in pairs(actions2detach) do sourceLayer:DeleteAction(actionName) end for i, bone in pairs(bones2detach) do local boneID = sourceSkeleton:BoneID(bone) for nch = 0, sourceGroup:CountLayers() - 1 do sourceGroup:Layer(nch):DeleteParentBone(boneID) end sourceSkeleton:DeleteBone(boneID) end -- place new into old (and reset new layer transformation) targetLayer.fTranslation:Clear() targetLayer.fScale:Clear() targetLayer.fRotationX:Clear() targetLayer.fRotationY:Clear() targetLayer.fRotationZ:Clear() targetLayer.fShear:Clear() targetLayer.fFlipH:Clear() targetLayer.fFlipV:Clear() local zeroVec = LM.Vector3:new_local() targetLayer.fTranslation:SetValue(0, zeroVec) targetLayer.fShear:SetValue(0, zeroVec) zeroVec:Set(1,1,1) targetLayer.fScale:SetValue(0, zeroVec) targetLayer.fRotationX:SetValue(0,0) targetLayer.fRotationY:SetValue(0,0) targetLayer.fRotationZ:SetValue(0,0) targetLayer.fFlipH:SetValue(0, false) targetLayer.fFlipV:SetValue(0, false) moho:PlaceLayerInGroup(targetLayer, sourceGroup, true) if topLayer then moho:PlaceLayerBehindAnother(targetLayer, topLayer) end moho:SetSelLayer(targetLayer) end function AE_MergeSkeletons:FixBoneAnimation(moho, trgBone, srcBone, srcSkel, duration, actionName) -- iterate every frame with pos or angle key local posChannel = trgBone.fAnimPos local angleChannel = trgBone.fAnimAngle if actionName then posChannel = posChannel:ActionByName(actionName) if posChannel then posChannel = moho:ChannelAsAnimVec2(posChannel) end angleChannel = angleChannel:ActionByName(actionName) if angleChannel then angleChannel = moho:ChannelAsAnimVal(angleChannel) end end for f = 1, duration do if (posChannel and posChannel:HasKey(f)) or (angleChannel and angleChannel:HasKey(f)) then --local theMatrix = AE_Utilities:GetGlobalBoneMatrix(moho, srcSkel, srcBone, f, actionName) --local pos, angle = AE_Utilities:Matrix2transform(theMatrix) local pos, angle = AE_Utilities:GetGlobalBonePRS(moho, srcSkel, srcBone, f, actionName) if posChannel then posChannel:SetValue(f, pos) end if angleChannel then angleChannel:SetValue(f, angle) end end end end
Merge skeletons
Listed
Author: A.Evseeva
View Script
Script type: Button/Menu
Uploaded: Jan 21 2021, 11:37
Last modified: Dec 02 2022, 06:12
Script Version: 2.1
Merge bones from a nested skeleton layer into parent layer skeleton
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: 1584
Merge skeletons
Listed
Author: A.Evseeva
View Script
Script type: Button/Menu
Uploaded: Jan 21 2021, 11:37
Last modified: Dec 02 2022, 06:12
Script Version: 2.1
Merge bones from a nested skeleton layer into parent layer skeleton
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: 1584