-- **************************************************
-- Intro
-- **************************************************

ScriptName = "SS_VirtualBones"

-- **************************************************
-- Virtual Bones - Create Custom Virtual Bones
-- version:	01.00 AS11/MS12+ #510803/511030
-- by Sam Cogheil (SimplSam)
-- **************************************************

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

	This work is licensed under a GNU General Public License v3.0 license
	Please see: https://www.gnu.org/licenses/gpl-3.0.en.html

	You can:
		Usage - Use/Reuse Freely
		Adapt - remix, transform, and build upon the material for any purpose, even commercially
		Share - copy and 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 GPL-3.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.

        ShareAlike - If you remix, transform, or build upon the material, you must distribute your
        contributions under the same license as this original.

	Warranty:

		Your use of this software material is at your own risk.

		By accepting to use this material you acknowledge that Sam Cogheil / SimplSam
		("The Developer") make no warranties whatsoever - expressed or implied for the
		merchantability or fitness for a particular purpose of the software provided.

		The Developer 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.0 #511030"
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.boneColors = {}
SS_VirtualBones.boneColors[00] = "000000"  -- Plain (unused)
SS_VirtualBones.boneColors[01] = "C80000"  -- Red
SS_VirtualBones.boneColors[02] = "FCA600"  -- Orange
SS_VirtualBones.boneColors[03] = "FCFF00"  -- Yellow
SS_VirtualBones.boneColors[04] = "00C800"  -- Green
SS_VirtualBones.boneColors[05] = "0000C8"  -- Blue
SS_VirtualBones.boneColors[06] = "A400F0"  -- Purple
SS_VirtualBones.boneColors[07] = "B0823F"  -- Tan
SS_VirtualBones.boneColors[08] = "FF9EB0"  -- Pink
SS_VirtualBones.boneColors[09] = "00C6B2"  -- Turquoise
SS_VirtualBones.boneColors[10] = "007074"  -- Cadet Blue
SS_VirtualBones.boneColors[11] = "EA5C2C"  -- Coral
SS_VirtualBones.boneColorMax   = 11
SS_VirtualBones.boneLineColor  = "69A9A1"  -- Outliner Blue
SS_VirtualBones.boneFillAlpha  = 96
SS_VirtualBones.boneLineAlpha  = 255
SS_VirtualBones.boneLineWidth  = 1.6
SS_VirtualBones.pinBoneSize    = 54
SS_VirtualBones.targetInSize   = 7
SS_VirtualBones.targetOutSize  = 12

--= Other
SS_VirtualBones.virtualLayerName = "_virtual_bones"
SS_VirtualBones.showDebug        = 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.showDebug) 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)

    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
    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(SS_VirtualBones.boneLineColor)
    lineColor.a = SS_VirtualBones.boneLineAlpha
    fillColor.a = SS_VirtualBones.boneFillAlpha
    lineWidth = moho:PixelToDoc(SS_VirtualBones.boneLineWidth)

    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))
        moho:PlaceLayerInGroup(vecLayer, boneLayer, true)
        vecLayer:SetName(self.virtualLayerName)
        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.boneColorMax)
                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(SS_VirtualBones.pinBoneSize) * 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
                        local xyOff = l * .075
                        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
                            local xyOff = moho:PixelToDoc(SS_VirtualBones.pinBoneSize)
                            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
                            local xyOff = moho:PixelToDoc(SS_VirtualBones.targetOutSize)
                            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(SS_VirtualBones.targetInSize)
                            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(SS_VirtualBones.boneColors[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
Virtual Bones
Listed

Script type: Button/Menu

Uploaded: Nov 02 2021, 01:05

Last modified: Nov 02 2021, 18:49

Create, Display and Render custom vector bones (Virtual Bones)
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+

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