Image
-- **************************************************
-- Intro
-- **************************************************

ScriptName = "SS_VirtualBones"

-- **************************************************
-- Virtual Bones - Create Custom Virtual Bones
-- version:	01.1 AS11/MH12+ #510803/511030/510722/530308
-- by Sam Cogheil (SimplSam)

-- #5211 ignore by layer select
-- #5303 dynam bone size
-- **************************************************

--[[ ***** Licence & Warranty *****

    Copyright 2022 - Sam Cogheil (SimplSam)

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at:

        http://www.apache.org/licenses/LICENSE-2.0

    Conditions require preservation of copyright and license notices.

    You must retain, in the Source form of any Derivative Works that
    You distribute, all copyright, patent, trademark, and attribution
    notices from the Source form of the Work, excluding those notices
    that do not pertain to any part of the Derivative Works.

    You can:
        Use   - use/reuse freely, even commercially
        Adapt - remix, transform, and build upon for any purpose
        Share - redistribute the material in any medium or format

    Adapt / Share under the following terms:
        Attribution - You must give appropriate credit, provide a link to
        the Apache 2.0 license, and indicate if changes were made. You may
        do so in any reasonable manner, but not in any way that suggests
        the licensor endorses you or your use.

    Licensed works, modifications and larger works may be distributed
    under different License terms and without source code.

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    The Developer Sam Cogheil / SimplSam will not be liable for any direct,
    indirect or consequential loss of actual or anticipated - data, revenue,
    profits, business, trade or goodwill that is suffered as a result of the
    use of the software provided.

]]

--[[
	***** SPECIAL THANKS to:
	*    Stan (and team) @ MOHO Scripting -- https://mohoscripting.com
	*    The friendly faces @ Lost Marble Moho forum -- https://www.lostmarble.com/forum
	*****
]]

SS_VirtualBones = {}
SS_VirtualBones._MOHO_Version = -1

function SS_VirtualBones:Name()
	return "Virtual Bones"
end

function SS_VirtualBones:Version()
	return "1.1 #530308"
end

function SS_VirtualBones:Description()
	return "Create Custom Virtual Bones"
end

function SS_VirtualBones:Creator()
	return "Sam Cogheil (SimplSam)"
end

function SS_VirtualBones:UILabel()
	return "Virtual Bones"
end

function SS_VirtualBones:IsEnabled(moho)
	if ((moho.layer:LayerType() ~= MOHO.LT_BONE) and (moho.layer:LayerType() ~= MOHO.LT_SWITCH)) or
       (moho.document:CurrentDocAction() ~= "") then
        return false
    end
end

function SS_VirtualBones:IsRelevant(moho)
    return (moho:Skeleton() ~= nil)
end

function SS_VirtualBones:ColorizeIcon()
    return true
end

-- **************************************************
-- Recurring Values
-- **************************************************

--= [ ] Options etc.
SS_VirtualBones.templateLayer = nil
SS_VirtualBones.templateList  = {}
SS_VirtualBones.templateID    = -1
SS_VirtualBones.templateUUID  = nil

SS_VirtualBones.selectedOnly  = false
SS_VirtualBones.showPinBones  = true
SS_VirtualBones.showTargets   = true
SS_VirtualBones.inheritStyle  = false
SS_VirtualBones.scaleCompens  = true

--= Looky Likey Style
SS_VirtualBones.BONE_COLORS = {}
SS_VirtualBones.BONE_COLORS[00] = "000000"  -- Plain (unused)
SS_VirtualBones.BONE_COLORS[01] = "C80000"  -- Red
SS_VirtualBones.BONE_COLORS[02] = "FCA600"  -- Orange
SS_VirtualBones.BONE_COLORS[03] = "FCFF00"  -- Yellow
SS_VirtualBones.BONE_COLORS[04] = "00C800"  -- Green
SS_VirtualBones.BONE_COLORS[05] = "0000C8"  -- Blue
SS_VirtualBones.BONE_COLORS[06] = "A400F0"  -- Purple
SS_VirtualBones.BONE_COLORS[07] = "B0823F"  -- Tan
SS_VirtualBones.BONE_COLORS[08] = "FF9EB0"  -- Pink
SS_VirtualBones.BONE_COLORS[09] = "00C6B2"  -- Turquoise
SS_VirtualBones.BONE_COLORS[10] = "007074"  -- Cadet Blue
SS_VirtualBones.BONE_COLORS[11] = "EA5C2C"  -- Coral
SS_VirtualBones.BONE_COLOR_MAX  = 11

SS_VirtualBones.BONE_LINE_COLOR  = "69A9A1"  -- Outliner Blue
SS_VirtualBones.BONE_FILL_ALPHA  = 96
SS_VirtualBones.BONE_LINE_ALPHA  = 255
SS_VirtualBones.BONE_LINE_WIDTH  = 1.6

SS_VirtualBones.BONE_WIDE_FACTOR = 0.075
SS_VirtualBones.BONE_PIN_SIZE    = 32 --54
SS_VirtualBones.BONE_TARGET_IN_SIZE  = 7
SS_VirtualBones.BONE_TARGET_OUT_SIZE = 12
SS_VirtualBones.BONE_LENGTH_FACTOR   = .25

--= Other
SS_VirtualBones.VIRTUAL_LAYER_NAME = "_virtual_bones"
SS_VirtualBones.SHOW_DEBUG         = true
SS_VirtualBones.isSelected         = false


-- **************************************************
-- Prefs
-- **************************************************

function SS_VirtualBones:LoadPrefs(prefs)
    self.selectedOnly = prefs:GetBool("SS_VirtualBones.selectedOnly", false)
    self.showPinBones = prefs:GetBool("SS_VirtualBones.showPinBones", true)
    self.showTargets  = prefs:GetBool("SS_VirtualBones.showTargets", true)
    self.inheritStyle = prefs:GetBool("SS_VirtualBones.inheritStyle", false)
    self.scaleCompens = prefs:GetBool("SS_VirtualBones.scaleCompens", true)
    -- self.templateID   = prefs:GetInt("SS_VirtualBones.templateID", -1)
    -- self.templateUUID = prefs:GetString("SS_VirtualBones.templateUUID", nil)
end

function SS_VirtualBones:SavePrefs(prefs)
    prefs:SetBool("SS_VirtualBones.selectedOnly", self.selectedOnly)
    prefs:SetBool("SS_VirtualBones.showPinBones", self.showPinBones)
    prefs:SetBool("SS_VirtualBones.showTargets", self.showTargets)
    prefs:SetBool("SS_VirtualBones.inheritStyle", self.inheritStyle)
    prefs:SetBool("SS_VirtualBones.scaleCompens", self.scaleCompens)
    -- prefs:SetInt("SS_VirtualBones.templateID", self.templateID)
    -- prefs:SetString("SS_VirtualBones.templateUUID", self.templateUUID)
end

function SS_VirtualBones:ResetPrefs()
    self.lastSelectedOnly = false
    self.selectedOnly  = false
    self.recolorBones  = false
    self.showPinBones  = true
    self.showTargets   = true
    self.inheritStyle  = false
    self.scaleCompens  = true
    self.onlyPinBones  = false
    self.templateLayer = nil
    self.templateID    = -1
    self.templateUUID  = nil
end


-- **************************************************
-- SS_VirtualBonesDialog
-- **************************************************

local SS_VirtualBonesDialog = {}
SS_VirtualBonesDialog.MSG_BASE       = MOHO.MSG_BASE + 12024 --#todo Bones bug 13.5
SS_VirtualBonesDialog.RESET          = SS_VirtualBonesDialog.MSG_BASE + 00
SS_VirtualBonesDialog.SELECTED_ONLY  = SS_VirtualBonesDialog.MSG_BASE + 01
SS_VirtualBonesDialog.SHOW_PINS      = SS_VirtualBonesDialog.MSG_BASE + 02
SS_VirtualBonesDialog.SHOW_TARGETS   = SS_VirtualBonesDialog.MSG_BASE + 03
SS_VirtualBonesDialog.INHERIT_STYLE  = SS_VirtualBonesDialog.MSG_BASE + 04
SS_VirtualBonesDialog.SCALE_COMPENS  = SS_VirtualBonesDialog.MSG_BASE + 05

SS_VirtualBonesDialog.TEMPLATE       = SS_VirtualBonesDialog.MSG_BASE + 20
SS_VirtualBonesDialog.TEMPLATE_NONE  = SS_VirtualBonesDialog.TEMPLATE + 01
SS_VirtualBonesDialog.TEMPLATE_DIV_  = SS_VirtualBonesDialog.TEMPLATE + 02
SS_VirtualBonesDialog.TEMPLATE_MAX_  = SS_VirtualBonesDialog.TEMPLATE + 99


function SS_VirtualBonesDialog:BuildTemplateMenu(menu)
    menu:AddItem("<None>", 0, self.TEMPLATE_NONE)
    if (#SS_VirtualBones.templateList > 0) then
        menu:AddItem("", 0, self.TEMPLATE_DIV_) -- div
        for i, entry in ipairs(SS_VirtualBones.templateList) do
            if (i <= (self.TEMPLATE_MAX_ - self.TEMPLATE_DIV_)) then
                menu:AddItem(entry.name, 0, self.TEMPLATE_DIV_ + i)
            else
                break
            end
        end
    end
end

function SS_VirtualBonesDialog:new()
    local d = LM.GUI.SimpleDialog('Create Virtual Bones', SS_VirtualBonesDialog)
    local l = d:GetLayout()

    l:PushH()
        l:AddChild(LM.GUI.StaticText("Template:"), LM.GUI.ALIGN_LEFT)
        d.tmplMenu = LM.GUI.Menu('Template')
        self:BuildTemplateMenu(d.tmplMenu)
        d.tmplMenuPopup = LM.GUI.PopupMenu(140, true)
        d.tmplMenuPopup:SetMenu(d.tmplMenu)
        l:AddChild(d.tmplMenuPopup, LM.GUI.ALIGN_LEFT, 0)
    l:Pop()
    d.scaleCompensCheck = LM.GUI.CheckBox('Scale compensation', d.SCALE_COMPENS)
    l:AddChild(d.scaleCompensCheck, LM.GUI.ALIGN_LEFT, 10)

    l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) ----
    d.selectedOnlyCheck = LM.GUI.CheckBox('Selected Bones only', d.SELECTED_ONLY)
    l:AddChild(d.selectedOnlyCheck, LM.GUI.ALIGN_LEFT, 0)
    d.showPinBonesCheck = LM.GUI.CheckBox('Pin Bones', d.SHOW_PINS)
    l:AddChild(d.showPinBonesCheck, LM.GUI.ALIGN_LEFT, 0)
    d.showTargetsCheck = LM.GUI.CheckBox('Target Bone targets', d.SHOW_TARGETS)
    l:AddChild(d.showTargetsCheck, LM.GUI.ALIGN_LEFT, 0)
    d.inheritStyleCheck = LM.GUI.CheckBox('Inherit Current Style', d.INHERIT_STYLE)
    l:AddChild(d.inheritStyleCheck, LM.GUI.ALIGN_LEFT, 0)
    l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) ----
    l:PushH(LM.GUI.ALIGN_RIGHT)
        d.resetText = LM.GUI.StaticText('Reset to default:')
        l:AddChild(d.resetText, LM.GUI.ALIGN_RIGHT, 0)
        d.resetButton = LM.GUI.Button('Reset', d.RESET)
        l:AddChild(d.resetButton, LM.GUI.ALIGN_RIGHT, 0)
    l:Pop()

    return d
end

function SS_VirtualBonesDialog:UpdateWidgets()
    self.showPinBonesCheck:SetValue(SS_VirtualBones.showPinBones)
    self.showTargetsCheck:SetValue(SS_VirtualBones.showTargets)
    self.inheritStyleCheck:SetValue(SS_VirtualBones.inheritStyle)
    self.scaleCompensCheck:SetValue(SS_VirtualBones.scaleCompens)
    self.selectedOnlyCheck:SetValue((SS_VirtualBones.selectedOnly or SS_VirtualBones.lastSelectedOnly) and SS_VirtualBones.isSelected)
    self.selectedOnlyCheck:Enable(SS_VirtualBones.isSelected)
    self.scaleCompensCheck:Enable(SS_VirtualBones.templateID > -1)
    self.tmplMenu:UncheckAll() -- reqd 4 reset
    self.tmplMenu:SetChecked(self.TEMPLATE_DIV_ + SS_VirtualBones.templateID -1, true)
    --self.tmplMenuPopup:Enable(#SS_VirtualBones.templateList > 0)
    self.tmplMenuPopup:Redraw()
end

function SS_VirtualBonesDialog:OnOK(moho)
    SS_VirtualBones.selectedOnly = self.selectedOnlyCheck:Value()
    if SS_VirtualBones.isSelected then SS_VirtualBones.lastSelectedOnly = SS_VirtualBones.selectedOnly end
    SS_VirtualBones.showPinBones = self.showPinBonesCheck:Value()
    SS_VirtualBones.showTargets  = self.showTargetsCheck:Value()
    SS_VirtualBones.inheritStyle = self.inheritStyleCheck:Value()
    SS_VirtualBones.scaleCompens = self.scaleCompensCheck:Value()
    SS_VirtualBones.templateID   = self.tmplMenu:FirstChecked()
end

function SS_VirtualBonesDialog:HandleMessage(msg)
    if (msg >= self.TEMPLATE_NONE) and (msg <= self.TEMPLATE_MAX_)   then
        if msg == self.TEMPLATE_NONE then
            SS_VirtualBones.templateLayer = nil
            SS_VirtualBones.templateUUID  = nil
            SS_VirtualBones.templateID    = -1
            self.tmplMenu:UncheckAll()
            self.tmplMenu:SetChecked(self.TEMPLATE_DIV_, true) -- ~=> -1
        end
        local _isTemplateSelected = (self.tmplMenu:FirstCheckedMsg() <= self.TEMPLATE_DIV_) or
                                    (self.tmplMenu:FirstCheckedMsg() > self.TEMPLATE_MAX_)
        self.inheritStyleCheck:Enable(_isTemplateSelected)
        self.scaleCompensCheck:Enable(not _isTemplateSelected)
        self.tmplMenuPopup:Redraw()
    elseif (msg == self.RESET) then
        SS_VirtualBones:ResetPrefs()
        self:UpdateWidgets()
    end
end


-- **************************************************
-- Utility
-- **************************************************

function SS_VirtualBones.dprint(...)
	if (SS_VirtualBones.SHOW_DEBUG) then
        print("Debug: " .. table.concat({...}, " "))
    end
end

function SS_VirtualBones.hex2rgb (hex)
    return tonumber("0x"..hex:sub(1,2)), tonumber("0x"..hex:sub(3,4)), tonumber("0x"..hex:sub(5,6))
end


-- **************************************************
-- Run, Run, Run I tell you ...
-- **************************************************



function SS_VirtualBones:Run(moho)
    local function ssRound(n, f)
        f = 10^ (f or 6)
        return (LM.Round(n * f)/f)
    end

    self._MOHO_Version = moho:AppVersion() and moho:AppVersion():match("(%d+%.?%d*)")+0 or -1
    local mohodoc = moho.document
    local boneLayer = moho:LayerAsBone(moho.layer)
    local frame0 = 0

	local skel = moho:Skeleton()
	if (skel:CountBones() <1) then
        return
    end

    self.isSelected = moho:CountSelectedBones(false) > 0
    self.templateList = {}
    for i = mohodoc:CountLayers() -1, 0, -1 do
        if (mohodoc:Layer(i):LayerType() == MOHO.LT_VECTOR) then
            local _vecLayer = moho:LayerAsVector(mohodoc:Layer(i))
            if (_vecLayer:Mesh()) and (_vecLayer:Mesh():CountPoints() > 1) then
                table.insert(self.templateList, {id=i, name=_vecLayer:Name(), uuid=_vecLayer:UUID()})
            end
        end
    end
    if (self.templateID < 2) or (not self.templateList[self.templateID -1]) or
       (self.templateList[self.templateID -1].uuid ~= self.templateUUID) then  -- del lastUsed template if mismatch
        self.templateLayer = nil
        self.templateID    = -1
        self.templateUUID  = nil
    end

    local dlog = SS_VirtualBonesDialog:new(moho)
    if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
        return
    end

    local fillColor = LM.rgb_color:new_local()
    local lineColor = LM.rgb_color:new_local()
    local startFrame, stopFrame = mohodoc:StartFrame(), mohodoc:EndFrame()
    local bonePos, boneTip = LM.Vector2:new_local(), LM.Vector2:new_local()
    local tmplMin, tmplMax = LM.Vector2:new_local(), LM.Vector2:new_local()
    local l, a, lineWidth, xyOff, xyRatio
    local templateLayer, tmplMesh, tmplHigh, tmplShapeCount, hasTemplate, hasInheritedStyles, entry

    if (self.templateID > 1) then
        entry = self.templateList[self.templateID -1]
        templateLayer = mohodoc:Layer(entry.id)
        templateLayer = templateLayer and moho:LayerAsVector(templateLayer)
    end
    hasTemplate = templateLayer and (templateLayer:UUID() == entry.uuid)
    if (hasTemplate) then
        self.templateUUID = entry.uuid
        tmplMesh = templateLayer:Mesh()
        tmplMesh:SelectAll()
        tmplMesh:SelectedBounds(tmplMin, tmplMax)
        tmplHigh = tmplMax.y - tmplMin.y
        tmplShapeCount = tmplMesh:CountShapes() -1
        for i =0, tmplShapeCount do
            if (tmplMesh:Shape(i).fInheritedStyle or tmplMesh:Shape(i).fInheritedStyle2) then
                hasInheritedStyles = true
                break
            end
        end
    end

    lineColor.r, lineColor.g, lineColor.b = self.hex2rgb(self.BONE_LINE_COLOR)
    lineColor.a = self.BONE_LINE_ALPHA
    fillColor.a = self.BONE_FILL_ALPHA
    lineWidth = moho:PixelToDoc(self.BONE_LINE_WIDTH)

    if  (not self.selectedOnly) or (self.selectedOnly and (moho:CountSelectedBones(false) > 0)) then

        local curFrame = moho.frame
        if (curFrame ~= frame0) then moho:SetCurFrame(frame0) end
        mohodoc:SetDirty()
        mohodoc:PrepUndo(moho.layer)

        local v = LM.Vector2:new_local()
        local vecLayer = moho:LayerAsVector(moho:CreateNewLayer(MOHO.LT_VECTOR))
        if (self._MOHO_Version >= 13.5) then
            vecLayer:SetIgnoredByLayerPicker(true)
        end
        moho:PlaceLayerInGroup(vecLayer, boneLayer, true)
        vecLayer:SetName(self.VIRTUAL_LAYER_NAME)
        local mesh = vecLayer:Mesh()
        local vecMin, vecMax, vecMid, vecOff = LM.Vector2:new_local(), LM.Vector2:new_local(), LM.Vector2:new_local(), LM.Vector2:new_local()
        local didCopy = false

        for iBone =0, skel:CountBones() -1 do
            local bone = skel:Bone(iBone)
            if (not bone.fHidden) and (bone.fSelected or not self.selectedOnly)  then
                local boneTag = LM.Clamp(bone:Tags(), 0, self.BONE_COLOR_MAX)
                bonePos:Set(bone.fPos)
                l = bone.fLength
                a = bone.fAngle
                if (bone.fParent ~= -1) then
                    local parentBone = skel:Bone(bone.fParent)
                    local xd = l * math.cos(a)
                    local yd = l * math.sin(a)
                    boneTip:Set(bone.fPos.x + xd, bone.fPos.y + yd)
                    parentBone.fMovedMatrix:Transform(bonePos)
                    parentBone.fMovedMatrix:Transform(boneTip)
                    a = math.atan2(boneTip.y - bonePos.y, boneTip.x - bonePos.x)
                end

                mesh:SelectNone()
                local x1 = bonePos.x
                local y1 = bonePos.y
                local ra = a + math.rad(-90)

                local isPin, didDrawPin = false, false
                if ((self._MOHO_Version >= 12) and bone:IsZeroLength()) or (l < 0.001) then -- pin?
                    isPin = true
                end

                local pointCount = mesh:CountPoints()
                if (hasTemplate) then
                    -- clone template
                    if (not isPin) or (isPin and self.showPinBones) then
                        if (isPin) then
                            l = moho:PixelToDoc(self.BONE_PIN_SIZE) * 2
                        end
                        local tmplScale = l / tmplHigh

                        if (not didCopy) then
                            moho:Copy(tmplMesh)
                            didCopy = true
                        end
                        moho:Paste()
                        for i =0, tmplShapeCount do
                            local _shape = mesh:Shape(mesh:CountShapes() -1 -tmplShapeCount +i)
                            if (hasInheritedStyles) then
                                _shape:CopyStyleProperties(tmplMesh:Shape(i)) -- copy inherited
                            end
                            if (self.scaleCompens) then
                                _shape.fMyStyle.fLineWidth = _shape.fMyStyle.fLineWidth * tmplScale -- stroke size compensation
                            end
                        end

                        mesh:SelectedBounds(vecMin, vecMax)
                        vecMid:Set(mesh:SelectedCenter())
                        vecOff.x = bonePos.x - vecMid.x
                        if (not isPin) then
                            vecOff.y = bonePos.y - vecMin.y
                        else
                            vecOff.y = bonePos.y - vecMid.y
                        end
                        mesh:PrepMovePoints()
                        if (not isPin) then
                            vecMid:Set(vecMid.x, vecMid.y - (vecMid.y - vecMin.y))
                        end
                        mesh:TransformPoints(vecOff, tmplScale, tmplScale, a - math.rad(90), vecMid)
                        moho:AddPointKeyframe(frame0)
                        for i =0, moho:CountSelectedPoints() -1 do
                            mesh:Point(pointCount + i).fParent = iBone
                        end
                    end

                else
                    -- Draw looky likey bones
                    if (not isPin) then
                        -- Bone
                        if (self._MOHO_Version >= 13.5) then
                            xyRatio = xyRatio or (bone:DisplayWidth(1)/l)/2
                            xyOff = LM.Clamp(l, .01, self.BONE_LENGTH_FACTOR) * xyRatio
                        else
                            xyOff = l * self.BONE_WIDE_FACTOR
                        end

                        v:Set(bonePos)
                        mesh:AddLonePoint(v, 0)
                        v:Set(xyOff, xyOff)
                        v:Rotate(ra)
                        v:Set(v.x + x1, v.y + y1)
                        mesh:AppendPoint(v, 0)
                        v:Set(0, l)
                        v:Rotate(ra)
                        v:Set(v.x + x1, v.y + y1)
                        mesh:AppendPoint(v, 0)
                        v:Set(-xyOff, xyOff)
                        v:Rotate(ra)
                        v:Set(v.x + x1, v.y + y1)
                        mesh:AppendPoint(v, 0)
                        v:Set(bonePos)
                        mesh:AppendPoint(v, 0)
                        mesh:WeldPoints(pointCount +4, pointCount, 0)
                        for i =0, 3 do
                            mesh:Point(pointCount +i):SetCurvature(MOHO.PEAKED, 0)
                            mesh:Point(pointCount +i).fParent = iBone
                        end

                    else
                        -- Pin
                        if (self.showPinBones) then
                            didDrawPin = true

                            if (self._MOHO_Version >= 13.5) then
                                xyOff = moho:PixelToDoc(bone:DisplayWidth(1))
                            else
                                xyOff = moho:PixelToDoc(self.BONE_PIN_SIZE)
                            end

                            local pointCount = mesh:CountPoints()

                            -- circ
                            v:Set(x1 - xyOff, y1)
                            mesh:AddLonePoint(v, 0)
                            v:Set(x1, y1 + xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 + xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1, y1 -xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 - xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            mesh:WeldPoints(pointCount +4, pointCount, 0)

                            for i=0, 3 do
                                mesh:Point(pointCount +i):SetCurvature(0.39, 0)  -- round
                                mesh:Point(pointCount +i).fParent = iBone        -- point bind
                            end

                            -- cross
                            v:Set(0, -xyOff)
                            v:Rotate(ra)
                            v:Set(v.x + x1, v.y + y1)
                            mesh:AddLonePoint(v, 0)
                            v:Set(0, xyOff)
                            v:Rotate(ra)
                            v:Set(v.x + x1, v.y + y1)
                            mesh:AppendPoint(v, 0)

                            v:Set(-xyOff, 0)
                            v:Rotate(ra)
                            v:Set(v.x + x1, v.y + y1)
                            mesh:AddLonePoint(v, 0)
                            v:Set(xyOff, 0)
                            v:Rotate(ra)
                            v:Set(v.x + x1, v.y + y1)
                            mesh:AppendPoint(v, 0)

                            for i=4, 7 do
                                mesh:Point(pointCount +i):SetCurvature(MOHO.PEAKED, 0)
                                mesh:Point(pointCount +i).fParent = iBone
                            end
                        end
                    end

                    -- Target marker
                    if (self.showTargets and ((not isPin) or didDrawPin))  then
                        local isTarget = skel:IsBoneATarget(iBone, 0)
                        if (not isTarget) then -- check target at any time?
                            for when = startFrame, stopFrame do --#todo crapzilla
                                if (skel:IsBoneATarget(iBone, when)) then
                                    isTarget = true
                                    break
                                end
                            end
                        end
                        if (isTarget) then

                            -- draw circ1
                            xyOff = moho:PixelToDoc(self.BONE_TARGET_OUT_SIZE)
                            local pointCount = mesh:CountPoints()
                            v:Set(x1 - xyOff, y1)
                            mesh:AddLonePoint(v, 0)
                            v:Set(x1, y1 + xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 + xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1, y1 -xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 - xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            mesh:WeldPoints(pointCount +4, pointCount, 0)

                            -- draw circ2
                            xyOff = moho:PixelToDoc(self.BONE_TARGET_IN_SIZE)
                            v:Set(x1 - xyOff, y1)
                            mesh:AddLonePoint(v, 0)
                            v:Set(x1, y1 + xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 + xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1, y1 -xyOff)
                            mesh:AppendPoint(v, 0)
                            v:Set(x1 - xyOff, y1)
                            mesh:AppendPoint(v, 0)
                            mesh:WeldPoints(pointCount +8, pointCount +4, 0)

                            for i=0, 7 do
                                mesh:Point(pointCount +i):SetCurvature(0.39, 0)  -- round
                                mesh:Point(pointCount +i).fParent = iBone        -- point bind
                            end
                        end
                    end

                end

                -- Create Shape (if non-template)
                if ((not hasTemplate) and (not isPin)) or didDrawPin then
                    mesh:SelectConnected()
                    local shapeID = moho:CreateShape(true)
                    if (shapeID ~= -1) then
                        local shape = mesh:Shape(shapeID)
                        shape:SetName(bone:Name())
                        if (not self.inheritStyle) then
                            shape:RemoveStyles()
                            if (boneTag == 0) then
                                shape.fHasFill = false
                            else
                                fillColor.r, fillColor.g, fillColor.b = self.hex2rgb(self.BONE_COLORS[boneTag])
                                shape.fHasFill = true
                                shape.fMyStyle.fFillCol:SetValue(0, fillColor)
                            end
                            shape.fHasOutline = true
                            shape.fMyStyle.fLineCol:SetValue(0, lineColor)
                            shape.fMyStyle.fLineWidth = lineWidth
                        end
                    else
                        self.dprint("Shape Create Failed: ", bone:Name())
                    end
                end
            end
        end

        moho:SetSelLayer(boneLayer, false, true)
        if (moho.frame ~= curFrame) then moho:SetCurFrame(curFrame) end
    end
end

Icon
SS - Virtual Bones
Listed

Script type: Button/Menu

Uploaded: Nov 02 2021, 01:05

Last modified: Mar 14 2023, 19:54

Script Version: 1.1 #530308

Create, Display and Render custom vector bones a.k.a Virtual Bones (ver 1.1)
The Virtual Bones tool allows you to Create, Display & Render Custom Vector Bones.

These Virtual Bones are look-a-like and behave-a-like replicas of the built-in Moho bones, and they are bound to those bones - in such a way that they Move, Stretch and Rotate at the same time and in the same way as their real counterparts.

So ... Why do you need Virtual Bones?

Virtual Bones are primarily of use because they are styleable & customizable, and they are always visible during editing & render.

Image

In summary they are:

- Render proof: Virtual Bones are rendered during export in high quality. Great for Reviews and Demonstrations or as a part of your Animations
- Styleable: It is easy to create custom styled or custom colored virtual bones - during creation or after creation
- Editable: Virtual Bones are implemented as vectors, which means they are always editable and can be manipulated with any vector tool or process
- Templatable: Any existing vector layer can be used as a template for Virtual Bones, with each Virtual Bone modelled as a resized copy of that template. Useful for diagramming, abstract animation, motion graphics and custom control handles
- Multipliable: You can create multiple Virtual Bones for the same Bone layer - each representing different sets of bones or simply  different styles
- Always visible (unless hidden by choice): During edit you can change layers away from the Bone layer group and the Virtual Bones will remain visible

Demo



How to use ?

To use:
  - Select a bone layer, and optionally one or more bones
  - Run the tool from the Tools palette
  - A popup panel will appear allowing you to review and adjust the settings

Options & Features

Image

Template: optionally Select a Template that the virtual bones should be created from **
* Scale compensation: is used to scale Stroke size to bone size during creation (default)
* Disable Scale compensation: to maintain constant Stroke size. Most beneficial with thin stroke outlines
If no Template is selected - look-a-like bones will be created (default)

Selected Bones only will process only the currently selected bones
Disable Selected Bones only to process all visible bones (default)

Pin Bones will include Pin Bones in the processing (default)
Disable Pin Bones to ignore Pin Bones

Target Bone targets will display Target Bone target indicators (default)
Disable Target Bone targets to not display Target Bone targets

Inherit Current Style will apply the current style in the style panel to the newly created Virtual Bones (non-template only)
Disable Inherit Current Style to mimic the look of the built-in bones, unless using a template (default)

- Use Reset to restore default settings. OK to Apply settings & changes. Cancel to Cancel

** Currently Template vector layers will only be listed and useable, if they are not empty and are not in a group (i.e. they must be located at the root layer level of the document).


Notes

- The tool always ignores Hidden and Shy bones
- The last used settings are automatically saved
- Compatible with AS11+
- Optimized for MH12+

Changelog:

  - 1.1 - Add: Ignore layer select. Support dynamic bone widths (Skinny Bones)

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: 913