ScriptName = 'WP_spineImport'
-- **************************************************
-- Import images from Spine Json files
-- Created by Maarten de Haas, Wigglepixel
-- **************************************************
--[[ ***** Licence & Warranty *****
Copyright 2023 - Maarten de Haas / Wigglepixel
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 Maarten de Haas, Wigglepixel 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:
* Lua JSON parser: https://github.com/rxi/json.lua (rxi)
]]
-- **************************************************
-- General information about this script
-- **************************************************
WP_spineImport = {}
local json
WP_spineImport.WP_UTILS_REQUIRED_VERSION = { 1, 3, 0 }
function WP_spineImport:Name()
return 'Spine Import'
end
function WP_spineImport:Version()
return '1.1.3'
end
function WP_spineImport:UILabel()
return 'Spine.json Images Import'
end
function WP_spineImport:Description()
return 'Import assets from default skin in Spine json file'
end
function WP_spineImport:Creator()
return 'Maarten de Haas, Wigglepixel'
end
function WP_spineImport:ColorizeIcon()
return false
end
-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************
function WP_spineImport:IsRelevant(moho)
if (not _did_my_resources_package_path) then
package.path = package.path .. ";" .. moho:UserAppDir() .. "/scripts/ScriptResources/?.lua;"
_did_my_resources_package_path = true
end
json = json or require('json.json')
return true
end
function WP_spineImport:IsEnabled(moho)
return (moho.document:CurrentDocAction() == '')
end
-- **************************************************
-- GO
-- **************************************************
function WP_spineImport:Run(moho)
local mohoDoc = moho.document
local alertOnWarnings = false
local warningsList = {}
local imgExtSearchOrder = { 'png', 'jpg', 'jpeg' }
local OSPathsUseForwardSlashes -- will be set after browsing path
local spineObj
-- CHECK UTILS VERSION
local utilsVersionCheck = WP_utils:compareVersion(WP_spineImport.WP_UTILS_REQUIRED_VERSION, WP_utils.VERSION)
if (utilsVersionCheck < 0) then
WP_utils:alert("Halted. This script requires at least version "..WP_utils:ver2str(WP_spineImport.WP_UTILS_REQUIRED_VERSION).." of 'wp_utils'")
return
elseif (utilsVersionCheck > 0) then
WP_utils:alert("Caution! This loaded 'wp_utils' is newer than the expected version "..WP_utils:ver2str(WP_spineImport.WP_UTILS_REQUIRED_VERSION))
end
-- ADD WARNING TO THE LIST
local function raiseWarning(msg)
table.insert(warningsList, '- '..msg)
if (alertOnWarnings) then WP_utils:alert(newMsg) end
end
-- SEARCHES BONE IN BONES OBJECT. RETURNS BONE IF FOUND, OR nil IF NOT FOUND
local function findBone(name)
if (spineObj and spineObj.bones) then
for _, bone in ipairs(spineObj.bones) do
if (bone.name == name) then
return bone
end
end
end
return nil
end
-- CALCULATE TRANSFORMS AND SET PARENT BONE REFERENCES
local function processBones()
if (spineObj and spineObj.bones) then
local bones = spineObj.bones
local parentBone
for _, bone in ipairs(bones) do
bone.parentObj = nil -- will be set below
-- TRANSLATION IN LOCAL SPACE
if (bone.x == nil) then bone.x = 0 end
if (bone.y == nil) then bone.y = 0 end
-- POSITION IN WORLD COORDINATES (USED FOR CALCULATING POSITION OF IMAGES)
bone.worldX = bone.x
bone.worldY = bone.y
-- LENGTH (UNDEPENDENT OF TRANSFORMATIONS)
if (bone.length == nil) then bone.length = 0 end
-- RELATIVE/LOCAL ROTATION (IN DEGREES)
if (bone.rotation == nil) then bone.rotation = 0 end
-- WORLD/ABSOLUTE ROTATION (IN DEGREES)
bone.worldRotation = bone.rotation
-- LOCAL SCALE (1.0 = SAME SCALING AS PARENT)
if (bone.scaleX == nil) then bone.scaleX = 1 end
if (bone.scaleY == nil) then bone.scaleY = 1 end
if (bone.scaleX ~= bone.scaleY) then
raiseWarning("Independent X/Y bone scaling is not supported. Using scaleX as scale for bone '"..bone.name.."'.")
end
bone.scaleSingle = bone.scaleX
bone.scaleY = bone.scaleX -- TEMP SOLUTION FOR IMAGE CALCULATIONS BECAUSE INDEPENDENT SCALING IS NOT SUPPORTED ON BONES
-- WORLD/ABSOLUTE SCALE
bone.worldScaleX = bone.scaleX -- will be set below
bone.worldScaleY = bone.scaleY -- will be set below
-- LOCAL SHEAR (NOT SUPPORTED YET)
if (bone.shearX == nil) then bone.shearX = 0 end
if (bone.shearY == nil) then bone.shearY = 0 end
if (bone.shearX ~= 0 or bone.shearY ~= 0) then
raiseWarning("Shear on bones is not supported. Ignoring Shear on bone '"..bone.name.."'.")
end
-- LOCAL TRANSFORMATION VECTOR (POSITION, ROTATION, SCALE)
bone.locVec2 = LM.Vector2:new_local()
bone.locVec2.x = bone.x -- will be set below
bone.locVec2.y = bone.y -- will be set below
-- WORLD TRANSFORMATION VECTOR (POSITION, ROTATION, SCALE)
bone.worldVec2 = LM.Vector2:new_local()
bone.worldVec2.x = bone.x -- will be set below
bone.worldVec2.y = bone.y -- will be set below
if (bone.parent) then
parentBone = findBone(bone.parent)
if (parentBone) then
-- SET PARENT BONE OBJECT
bone.parentObj = parentBone
-- CALC LOCAL TRANSFORMATION VECTOR
if (parentBone.worldRotation ~= 0) then
bone.locVec2:Rotate(WP_utils:deg2rad(parentBone.worldRotation))
end
bone.locVec2.x = bone.locVec2.x * parentBone.scaleSingle
bone.locVec2.y = bone.locVec2.y * parentBone.scaleSingle
-- WORLD TRANSFORMATION VECTOR AND WORLD POSITION
bone.worldVec2:Set(parentBone.worldVec2 + bone.locVec2)
bone.worldX = bone.worldVec2.x
bone.worldY = bone.worldVec2.y
-- CALC WORLD ROTATION
bone.worldRotation = parentBone.worldRotation + bone.rotation
-- CALC WORLD SCALE
bone.worldScaleX = parentBone.worldScaleX * bone.worldScaleX
bone.worldScaleY = parentBone.worldScaleY * bone.worldScaleY
end
end
end
end
end
-- CREATES LAYER WITH IMAGE
local function addImgAsLayer(parentLayer, name, imgFile, posPx, rotDeg, scale, tintColStr)
local layer = moho:LayerAsImage(moho:CreateNewLayer(MOHO.LT_IMAGE, false))
layer:SetName(name)
-- CONVERT FORWARD SLASHES TO BACKWARDS SLASHES ON WINDOWS, OTHERWISE 'REVEAL SOURCE IMAGE' ISN'T WORKING IN WINDOWS
if (OSPathsUseForwardSlashes == false) then imgFile = imgFile:gsub('/', '\\') end
layer:SetSourceImage(imgFile)
if (posPx) then
local layerTransVec3 = WP_utils:getMohoRelLayerTransVec3(moho, posPx.x, posPx.y)
layer.fTranslation:SetValue(0, layerTransVec3)
end
if (rotDeg) then
layer.fRotationZ:SetValue(0, WP_utils:deg2rad(rotDeg))
end
if (scale) then
local scaleVec3 = LM.Vector3:new_local()
scaleVec3:Set(scale.x, scale.y, 1)
layer.fScale:SetValue(0, scaleVec3)
end
if (parentLayer) then
moho:PlaceLayerInGroup(layer, parentLayer, true, false)
end
-- ADD TINT LAYER
if (tintColStr) then
local tintLayer = moho:DuplicateLayer(layer, true)
tintLayer:SetName(name..'_tint')
-- COLORIZE
local colVec = spineHexColToMohoColVec(tintColStr)
tintLayer.fLayerColor:SetValue(0, colVec)
tintLayer.fLayerColorOn:SetValue(0, true)
-- BLENDING MODE
tintLayer:SetBlendingMode(MOHO.BM_MULTIPLY)
end
end
-- RETURN SKIN OBJECT FROM SKINS ARRAY (ONLY FOR ARRAY FORMATTED SKINS)
local function getSkinObjFromSkinsArr(skinsArr, skinName)
for _, value in ipairs(skinsArr) do
if (value.name ~= nil and value.name == skinName) then
return value
end
end
return nil
end
-- CONVERTS SPINE COLOR STRING TO MOHO COLOR VECTOR
local function spineHexColToMohoColVec(hexStr)
local rgbVec = LM.rgb_color:new_local()
if (hexStr:len() == 8) then
rgbVec.r = tonumber('0x'..hexStr:sub(1,2))
rgbVec.g = tonumber('0x'..hexStr:sub(3,4))
rgbVec.b = tonumber('0x'..hexStr:sub(5,6))
rgbVec.a = tonumber('0x'..hexStr:sub(7,8))
return rgbVec
else
return nil
end
end
-- =========================================================================
-- SETTINGS
local debugMode = true
local spineFileExt = '.json'
local importScale = 1 -- set scale to scale up or down complete group
local importBones = true -- set to true to import bones too
local expandBoneGroupLayerAfterCreation = true -- if bone layer is expanded after creation
local importImagesFromMeshes = false -- set to true to import images from meshes (caution! locations wont work because mesh UV's don't get imported)
alertOnWarnings = true
local showBoneLabels = false -- true to show label on each bone
local cancelOnImageNotFound = false -- if set to true importer will cancel import when an image is not found. if false only a warning will be registered
local alwaysCreateUniqueLayerNames = false -- checks through all layers if there is already a layer with that name. If so adds a postfix number
-- GET SPINE FILE
local spineFile = LM.GUI.OpenFile('Please Spine JSON File... (Wigglepixel Spine Import v'..WP_spineImport:Version()..')')
if (spineFile ~= '') then
local fileExt = string.match(spineFile, spineFileExt..'$')
if (fileExt ~= '.json') then
WP_utils:alert('Only Spine files with extension .json are alowed')
return
end
else
return
end
if (string.find(spineFile, '/')) then OSPathsUseForwardSlashes = true else OSPathsUseForwardSlashes = false end
local absSpineFileBasePath = WP_utils:getPath(spineFile)
local spineFilenameWithoutPath = WP_utils:getFilenameFromPath(spineFile)
local spineFileBasename = WP_utils:trim(string.sub(spineFilenameWithoutPath, 1, -(string.len(spineFileExt) + 1)))
spineObj = json.decode(WP_utils:readFile(spineFile))
-- GET IMAGES PATH
local absImagesPath = absSpineFileBasePath
if (spineObj.skeleton and type(spineObj.skeleton.images) == 'string') then
local imgPath = WP_utils:trim(spineObj.skeleton.images)
if (imgPath ~= '') then
if (imgPath:sub(-1, -1) == '/') then imgPath = imgPath:sub(1, -2) end
if (imgPath:sub(1,1) == '.') then
-- relative
absImagesPath = (absImagesPath..'/'..imgPath):gsub('/./', '/') -- remove /./ same dir dot halfway path. throws issues on windows
else
-- absolute
absImagesPath = imgPath
end
end
end
absImagesPath = absImagesPath:gsub('\\', '/')-- all forward slash
-- GET BONES + SET REFERENCE TO PARENT BONES AND CALCULATE WORLD TRANSFORMS
processBones()
-- GET SKINS
if (spineObj.skins == nil or type(spineObj.skins) ~= 'table') then
WP_utils:alert('Halted. No or unrecognized skins object found in spine file. Didn\'t make any changes to your Moho file yet.')
return
end
local skinsCount = WP_utils:arrLen(spineObj.skins)
if (skinCount == 0) then
WP_utils:alert('Halted. No skins found inside skins object in spine file. Didn\'t make any changes to your Moho file yet.')
return
end
-- GET SKIN
local skinName = 'default'
local attachmentsObj
local skinsIsArrayFormatted = spineObj.skins[1] ~= nil -- new spine formatting for skins object
if (skinsIsArrayFormatted) then
local skinObj = getSkinObjFromSkinsArr(spineObj.skins, skinName)
if (skinObj == nil) then
WP_utils:alert('Halted. Skin not found inside skins object in spine file. Didn\'t make any changes to your Moho file yet.')
do return end
end
attachmentsObj = skinObj.attachments
if (attachmentsObj == nil) then
WP_utils:alert('Halted. Skin is missing attachments object. Didn\'t make any changes to your Moho file yet.')
do return end
end
else
attachmentsObj = spineObj.skins[skinName]
end
-- GET SLOT NAMES AND SKIN DATA
local assetsData = {}
local layerName -- = slot
local assetName -- = attachment
local boneObj -- = bound bone object
local assetType -- = spine attachment type
local localRelImgPath -- = relative path and basename for image file
local assetIndex = 0
local imgWorldVec2 = LM.Vector2:new_local() -- temp vector to calculate world position
for i, value in ipairs(spineObj.slots) do
-- SET BONE AS REFERENCE
value.boneObj = findBone(value.bone)
boneObj = value.boneObj
-- GET SLOT NAME AS LAYER NAME
layerName = value.name
if (type(layerName) ~= 'string' or layerName:len() == 0) then
WP_utils:alert('Halted. Name for Layer/slot at position '..tostring(i)..' (one-based) is missing in spine file. Didn\'t make any changes to your Moho file yet.')
return
end
for i, value in ipairs(assetsData) do
if (assetsData[i].layerName == layerName) then
WP_utils:alert("Halted. Layer/slot '"..layerName.."' has duplicates in spine file. Didn't make any changes to your Moho file yet.")
return
end
end
-- GET ATTACHMENT NAME AS ASSETNAME (SPINE.JSON DOCUMENTATION SAYS: IF MISSING WE SHOULD ASSUME THERE IS NO ATTACHMENT)
assetName = value.attachment
if (assetName == nil) then
raiseWarning("Skipping Layer/slot '"..layerName.."'. Attachment/image is missing in spine file. Either this slot is corrupted or is missing an attachment.")
end
-- IMAGE FILE (+ CHECK IF IT EXISTS)
if (attachmentsObj[layerName] and attachmentsObj[layerName][assetName] and assetName ~= nil) then
-- GET ATTACHMENT TYPE
assetType = attachmentsObj[layerName][assetName].type
if (assetType == nil) then assetType = 'region' end
if (assetType == 'region' or (importImagesFromMeshes and assetType == 'mesh')) then
assetIndex = assetIndex + 1
localRelImgPath = assetName
-- CREATE NEW ASSET ITEM
assetsData[assetIndex] = {}
assetsData[assetIndex].index = assetIndex
assetsData[assetIndex].layerName = layerName
assetsData[assetIndex].assetName = assetName
assetsData[assetIndex].imgOrgSize = { width = 0, height = 0 } -- original image size (unscaled and undeformed)
assetsData[assetIndex].imgFile = ''
assetsData[assetIndex].parentBoneObj = nil -- will be set later
assetsData[assetIndex].boneObj = boneObj
assetsData[assetIndex].imgPosPx = { x = 0, y = 0 } -- local position ofset of image in relation to bone
assetsData[assetIndex].imgPosPxWorld = { x = 0, y = 0 } -- world position of image
assetsData[assetIndex].boneRotDegWorld = 0 -- rotation of bound bone in world coordinates
assetsData[assetIndex].imgRotDeg = 0 -- local rotation in relation to bone
assetsData[assetIndex].imgRotDegWorld = 0 -- image rotation in total
assetsData[assetIndex].boneScaleWorld = { x = 1, y = 1 } -- bone world scale
assetsData[assetIndex].imgScale = { x = 1, y = 1 } -- local image scale
assetsData[assetIndex].imgScaleWorld = { x = 1, y = 1 } -- world image scale (total scale)
local props = attachmentsObj[layerName][assetName]
-- GET BONE AND UPDATE POS, ROT, SCALE OF IMAGE
if (boneObj) then
assetsData[assetIndex].parentBoneObj = boneObj.parentObj
assetsData[assetIndex].imgPosPxWorld.x = boneObj.worldX
assetsData[assetIndex].imgPosPxWorld.y = boneObj.worldY
assetsData[assetIndex].boneRotDegWorld = boneObj.worldRotation
assetsData[assetIndex].imgRotDegWorld = boneObj.worldRotation
assetsData[assetIndex].boneScaleWorld.x = boneObj.worldScaleX
assetsData[assetIndex].boneScaleWorld.y = boneObj.worldScaleY
assetsData[assetIndex].imgScaleWorld.x = boneObj.worldScaleX
assetsData[assetIndex].imgScaleWorld.y = boneObj.worldScaleY
end
if (props ~= nil) then
-- GET POSITION AND SIZE DATA
if (props.x ~= nil and type(props.x) == 'number') then
assetsData[assetIndex].imgPosPx.x = props.x
end
if (props.y ~= nil and type(props.y) == 'number') then
assetsData[assetIndex].imgPosPx.y = props.y
end
if (props.width ~= nil and type(props.width) == 'number') then
assetsData[assetIndex].imgOrgSize.width = props.width
end
if (props.height ~= nil and type(props.height) == 'number') then
assetsData[assetIndex].imgOrgSize.height = props.height
end
if (props.rotation ~= nil and type(props.rotation) == 'number') then
assetsData[assetIndex].imgRotDeg = props.rotation
assetsData[assetIndex].imgRotDegWorld = assetsData[assetIndex].imgRotDegWorld + props.rotation
end
if (props.scaleX ~= nil and type(props.scaleX) == 'number') then
assetsData[assetIndex].imgScale.x = props.scaleX
assetsData[assetIndex].imgScaleWorld.x = assetsData[assetIndex].imgScaleWorld.x * props.scaleX
end
if (props.scaleY ~= nil and type(props.scaleY) == 'number') then
assetsData[assetIndex].imgScale.y = props.scaleY
assetsData[assetIndex].imgScaleWorld.y = assetsData[assetIndex].imgScaleWorld.y * props.scaleY
end
if (props.color ~= nil and type(props.color) == 'string') then
assetsData[assetIndex].tintColStr = props.color
end
if (props.path ~= nil and type(props.path) == 'string') then
localRelImgPath = props.path
end
-- CALC WORLD POS (USING ROTATED AND SCALED AXIS)
imgWorldVec2.x = props.x
imgWorldVec2.y = props.y
if (boneObj.worldRotation ~= 0) then
imgWorldVec2:Rotate(WP_utils:deg2rad(boneObj.worldRotation))
end
imgWorldVec2.x = imgWorldVec2.x * boneObj.scaleX
imgWorldVec2.y = imgWorldVec2.y * boneObj.scaleY
imgWorldVec2:Set(boneObj.worldVec2 + imgWorldVec2)
assetsData[assetIndex].imgPosPxWorld.x = imgWorldVec2.x
assetsData[assetIndex].imgPosPxWorld.y = imgWorldVec2.y
-- GET IMAGE FILE AND CHECK IF IT EXISTS
if (props.sequence ~= nil) then
raiseWarning("Image sequences aren't supported. Only the first frame will be loaded for asset '"..assetName.."' (layer '"..layerName.."').")
-- GET IMAGE SEQUENCE FIRST FRAME INDEX
if (type(props.sequence.start) ~= 'number') then
WP_utils:alert('Halted. Missing image sequence \'Start\' property on asset "'..assetName..'". Didn\'t make any changes to your Moho file yet.')
return
end
local imgSeqStartIndex = props.sequence.start
-- GET IMAGE SEQUENCE DIGITS COUNT
if (type(props.sequence.digits) ~= 'number') then
WP_utils:alert('Halted. Missing image sequence \'Digits\' property on asset "'..assetName..'". Didn\'t make any changes to your Moho file yet.')
return
end
local imgSeqDigits = props.sequence.digits
if (imgSeqDigits < 1) then imgSeqDigits = 1 end
local imgSeqFrameNrStr = string.format('%0'..imgSeqDigits..'d', imgSeqStartIndex)
localRelImgPath = localRelImgPath..imgSeqFrameNrStr
end
assetsData[assetIndex].imgFile = absImagesPath .. '/'..localRelImgPath
-- SEARCH IMAGE FILE
local imgIsFound = false
for _, imgExt in ipairs(imgExtSearchOrder) do
imgIsFound = WP_utils:fileExists(assetsData[assetIndex].imgFile..'.'..imgExt)
if (imgIsFound) then
assetsData[assetIndex].imgFile = (assetsData[assetIndex].imgFile..'.'..imgExt)
break
end
end
if (imgIsFound == false) then
local msg = "File '"..assetsData[assetIndex].imgFile.."' (+ .png/jpg/jpeg) not found (layer '"..layerName.."')."
if (cancelOnImageNotFound == true) then
WP_utils:alert("Halted. "..msg.." Didn\'t make any changes to your Moho file yet.")
return
else
raiseWarning(msg)
end
end
else
WP_utils:alert('Halted. Missing props for attachment '..i..'. Didn\'t make any changes to your Moho file yet.')
return
end
else
raiseWarning("Skipping asset '"..assetName.."', because currently '"..assetType.."' assets aren't supported.")
end
end
end
-- --------------------------------------------------------------------------------------------------------------------
-- ADD IMAGES AS LAYERS TO MOHO
-- --------------------------------------------------------------------------------------------------------------------
-- GOTO FRAME ZERO
if (moho.frame ~= 0) then moho:SetCurFrame(0) end
-- PREP MULTI UNDO AND RAISE DIRTY FLAG
mohoDoc:PrepMultiUndo()
mohoDoc:SetDirty()
-- SELECT TOP LAYER
moho:SetSelLayer(mohoDoc:Layer(mohoDoc:CountLayers() -1), false, true)
-- CREATE BONES LAYER
local boneGroupLayer = moho:CreateNewLayer(MOHO.LT_BONE, true)
local boneGroupLayerName = spineFileBasename.." ('"..skinName.."' skin)"
if (WP_utils:layerExists(moho, boneGroupLayerName)) then
boneGroupLayerName = WP_utils:findNextFreeLayerName(moho, boneGroupLayerName)
end
boneGroupLayer:SetName(boneGroupLayerName)
boneGroupLayer = moho:LayerAsBone(boneGroupLayer)
boneGroupLayer:Expand(expandBoneGroupLayerAfterCreation)
local skeleton = boneGroupLayer:Skeleton()
-- IMPORT SCALING
if (importScale ~= 1) then
local importScaleVec3 = LM.Vector3:new_local()
importScaleVec3:Set(importScale, importScale, 1)
boneGroupLayer.fScale:SetValue(0, importScaleVec3)
end
-- CREATE IMAGE LAYERS
local layerName
local autoRenameLayers = nil
local docIsModified = false
for i in ipairs(assetsData) do
layerName = assetsData[i].layerName
-- CHECK IF LAYERNAME EXISTS (ONLY IF SETTING IS ENABLED)
if (alwaysCreateUniqueLayerNames) then
if (WP_utils:layerExists(moho, layerName)) then
if (autoRenameLayers == nil) then
autoRenameLayers = WP_utils:alert('Layer "'..layerName..'" already exists. Do you want to continue with auto-renaming new duplicate layers? (Hit Cancel to exit without making changes to your moho project).', true)
if (autoRenameLayers == 1) then
return nil
end
end
layerName = WP_utils:findNextFreeLayerName(moho, layerName)
end
end
-- CREATE LAYER
addImgAsLayer(
boneGroupLayer,
layerName,
assetsData[i].imgFile,
assetsData[i].imgPosPxWorld,
assetsData[i].imgRotDegWorld,
assetsData[i].imgScaleWorld,
assetsData[i].tintColStr)
end
-- SELECT GROUP LAYER
moho:SetSelLayer(boneGroupLayer)
-- --------------------------------------------------------------------------------------------------------------------
-- ADD BONES TO MOHO
-- --------------------------------------------------------------------------------------------------------------------
if (importBones and spineObj and spineObj.bones) then
local newBone
local hasParent = false
for i, bone in ipairs(spineObj.bones) do
hasParent = bone.parentObj ~= nil
-- ===============================
-- ADD BONE
-- ===============================
-- CREATE BONE
newBone = skeleton:AddBone(0)
newBone:SetName(bone.name)
newBone:ShowLabel(showBoneLabels)
-- UPDATE BONE DATA FOR CHILDREN
bone.mohoBoneObj = newBone
bone.mohoBoneId = skeleton:BoneID(newBone)
if (hasParent == false) then
--=====================================================
-- NO PARENT
--=====================================================
-- SET PARENT
newBone.fParent = -1
newBone.fAnimParent:SetValue(0, -1)
-- SET POSITION
local bonePosMohoVec2 = WP_utils:getMohoRelLayerTransVec2(moho, bone.x, bone.y)
newBone.fPos = bonePosMohoVec2
newBone.fAnimPos:SetValue(0, bonePosMohoVec2)
-- SET ROTATION
local boneRotationMohoRad = WP_utils:deg2rad(bone.rotation)
newBone.fAngle = boneRotationMohoRad
newBone.fAnimAngle:SetValue(0, boneRotationMohoRad)
-- CALCULATE MOHO SCALE
newBone.fScale = bone.scaleSingle
newBone.fAnimScale:SetValue(0, bone.scaleSingle)
-- SET BONE LENGTH
local boneLengthVec2 = WP_utils:getMohoLayerSizeVec2(moho, bone.length, 0)
boneLengthVec2:Rotate(WP_utils:deg2rad(bone.rotation))
local boneLengthMoho = boneLengthVec2:Mag()
newBone.fLength = boneLengthMoho * bone.scaleSingle
else
--=====================================================
-- WITH PARENT
--=====================================================
-- SET PARENT
newBone.fParent = bone.parentObj.mohoBoneId
newBone.fAnimParent:SetValue(0, bone.parentObj.mohoBoneId)
-- GET DATA FROM MOHO PARENT BONE
local mohoParentBone = bone.parentObj.mohoBoneObj
local parentBonePosVec2Moho = mohoParentBone.fAnimPos:GetValue(0)
local parentBoneScale = mohoParentBone.fAnimScale:GetValue(0)
-- SET BONE LENGTH
local boneLengthVec2 = WP_utils:getMohoLayerSizeVec2(moho, bone.length, 0)
boneLengthVec2:Rotate(WP_utils:deg2rad(bone.rotation))
local boneLengthMoho = boneLengthVec2:Mag()
newBone.fLength = boneLengthMoho * parentBoneScale * bone.scaleSingle
-- SET POSITION OF CHILD BONE
local bonePosMohoVec2 = WP_utils:getMohoLayerSizeVec2(moho, bone.x * parentBoneScale, bone.y * parentBoneScale)
newBone.fPos = bonePosMohoVec2
newBone.fAnimPos:SetValue(0, bonePosMohoVec2)
-- SET ROTATION OF CHILD BONE
local boneRotationMohoRad = WP_utils:deg2rad(bone.rotation)
newBone.fAngle = boneRotationMohoRad
newBone.fAnimAngle:SetValue(0, boneRotationMohoRad)
-- CALCULATE MOHO SCALE
newBone.fScale = parentBoneScale * bone.scaleSingle
newBone.fAnimScale:SetValue(0, parentBoneScale * bone.scaleSingle)
end
-- UPDATE BONE TRANSFORM MATRICES (IS THIS NEEDED?)
skeleton:UpdateBoneMatrix(bone.mohoBoneId)
end
end
-- FINISHED
local warningsCount = WP_utils:arrLen(warningsList)
if (warningsCount == 0) then
if (debugMode == false) then
WP_utils:alert('Successfully Finished importing!')
end
else
print('----------------------------------------------------------------------------------------------')
print('WIGGLEPIXEL SPINE IMPORT v'..WP_spineImport:Version())
print('')
print('Completed with '..warningsCount..' warnings:')
print('')
for _, msg in ipairs(warningsList) do
print(msg)
end
print('----------------------------------------------------------------------------------------------')
print('')
if (debugMode == false) then
WP_utils:alert('Completed with '..warningsCount..' warnings (see Lua Console for details)')
end
end
end
WP Import from Spine JSON
Listed
Author: wigglepixel
View Script
Script type: Tool
Uploaded: Oct 16 2023, 08:04
Last modified: Dec 10 2023, 07:28
Import Assets from Spine JSON (To import from Affinity, Krita, Spine, Photoshop, AfterEffects, Gimp etc.)

IMPORT FROM AFFINITY DESIGNER OR PHOTO
Tutorial video on how to use the script to import Affinity (Photo/Designer) Layers as Image Layers with cropped images in Moho:
IMPORT FROM KRITA
Tutorial video on how to use the script to import Krita Layers as Image Layers with cropped images in Moho:
IMPORT FROM SPINE
Short tutorial video on how to use the script to import Region Attachments (images) and their transformations at the setup pose from Spine as Moho 14 Layers:
IMPORT FROM PHOTOSHOP, AFTEREFFECTS, GIMP ETC.
There are spine JSON export scripts for many programs. Look here for export scripts made by Esoteric Software (creator of Spine) for at least Photoshop, AfterEffects, Gimp etc. : https://github.com/EsotericSoftware/spine-scripts
----------------------------------------------------------------------------------------------------------
VERSION HISTORY
----------------------------------------------------------------------------------------------------------
1.1.3 [current]
- Changed: Made compatible with newer utils file
1.1.2
- Changed: Made compatible with newer utils file
- Added: Retina icon
1.1.1
- Fixed: Typo
1.1.0
- Added: If there is bone data available it now imports bones and creates the same skeleton in Moho, complete with transforms translation, rotation and scale*. Bone shearing will be ignored tho. *: Different scaling for X and Y isn't supported. If a bone has a different scale value for x and y then the x-value will be used as scale in Moho.
- Changed: Improve warnings output
- Changed: different scaleX and scaleY values in bones aren't supported. The image transformspath to calculate images from bones in the tree is now calculated accordingly to keep image transforms the same as the bone transformations.
1.0.0
- Initial release. Imports and transforms images.
----------------------------------------------------------------------------------------------------------
ABOUT THE CURRENT VERSION
----------------------------------------------------------------------------------------------------------
How to install the script in Moho?
* Click on the 'Download for Install Script command''-button below to download the zip-file
* Unzip the downloaded file
* Start Moho
* Open the Scripts menu --> choose 'Install Script' --> Browse through the unziped folder and choose the folder named 'wp_spine_import_for-moho-install-script-command' --> It should now install the script
* Once installation is done Moho shows a dialog saying 'Installation Complete!'
* Restart Moho (only after restarting the tool will become available)
* You should now see the tool with the spine logo in the tools panel. Ready to use!
Why I created this script?
1. Make exporting layers from Affinity way easier and quicker
I started this script because Affinity (Mainly Photo and Designer) (Affinity website) offers a great way to export all layers to individual cropped images on disc, together with a spine.json file containing all layer data, like positions of all images.
This makes it extremely easy and fast to import all layers and images directly into Spine. However, Moho so far couldn't open these spine.json files. So it took a long time to import all these individual images to moho layers and position all these layers in the right spot. By using this script this now is as easy in Moho as it is in Spine and it's now importing and positioning all images/layers via the spine.json file with a single click so we don't have to spend lots of time importing images anymore and can start rigging right away.
2. Make exporting layers from Krita way easier and quicker
There is also a very useful script for Krita (Krita Website) around to export Krita layers to spine.json. Although I didn't tested the outputed spine.json with that script yet, it should work just fine too. You can find it here: Krita-to-spine script download
Not shown in the tutorial video above, because I wonder if it's useful in Spine, but it's even possible to add special naming to Krita group layers that the export script recognises to automatically create bones etc.. See the krita-to-spine script readme (follow the download link) for more information.
3. Make importing images from spine.json possible too?
After having the import for Affinity-generated spine.json files working I wondered how far I could take this to import spine.json files generated by Spine itself (Esoteric Software/Spine website). These files are more complicated because Spine also has a complete skeleton system, like Mohos bones, and images inside spine files have a hierarchical tree structure with bones. But also all bones can have translations/offsets, rotations, scales and shear values. Even images bound to bones can have these transformations applied. And they all influence each other via parenting.
Besides this in Spine it's possible to tint images, which isn't currently possible in Moho.
Spine has more features, like multiple skeletons in a file, multiple skins per skeleton, meshes, several other types of attachments besides images and transforms. But these aren't supported by this script (yet?) and is way over the original use case for the script, which was to import all layers as images from Affinity.
All images are imported now and get the right transformations (except shears) in Moho, just like in Spine. And when it finds tinted images/slots in the spine.json file it generates extra reference tint-layers in Moho to have a similar effect. At the moment these aren't having exactly the same result/blending, so these will probably be tweaked later. But at least it imports. And I consider this a feature that won't be used by many people.
4. Importing from other software (Photoshop, After Effects, Gimp etc.)?
There are Spine JSON exporters around for Photoshop, Gimp and other software too. So basically any software you have and has an export script to export to Spine JSON fileformat is with this script now able to export to Moho. Esoteric Software, the creators of Spine, have a repository with some exporter scripts for several programs here: https://github.com/EsotericSoftware/spine-scripts

What does the script do?
This first version generates a group with image layers. The group is transformed to a bone layer in moho, but doesn't contain bones yet. A later version will probably add an option to load the bones too, if the spine.json contains bone data.
The bone data in the spine.json is however used to determine the transformations of the images to position/rotate/scale them on the right spots in Moho.
How to use the script?
This is a new script! Backup your moho file before use just to be sure.
Hit the tool button, browse the spine.json file (only json files are supported)
How to export Affinity or Krita layers, or Spine content to spine.json?
See tutorial videos above!
Supported Moho version
Tested in Moho 14.x.
About support of spine.json features
Everything imports fine from files generated by Affinity
About more advanced spine features as generated by Spine:
- Only the default skin (skin named 'default'
will currently be imported. Multi-skin is not supported.
- Imports region attachments (= image without a mesh). Images having a mesh will not be imported.
- Like spine the valid supported image file formats are png, jpg and jpeg (will search for these formats in that order, like Spine).
- Positions, rotations and scaling* of images and bones are calculated hierarchical through the bones to their final position.
- *) Different/Individual scaling for X and Y on bones isn't supported. If a bone has a unequal values for scaleX and scaleY then the scaleX value will be used as scale for that bone in Moho.
- Shear on bones aren't currently supported.
- Constraints aren't supported.
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: 325
WP Import from Spine JSON
Listed
Author: wigglepixel
View Script
Script type: Tool
Uploaded: Oct 16 2023, 08:04
Last modified: Dec 10 2023, 07:28
Import Assets from Spine JSON (To import from Affinity, Krita, Spine, Photoshop, AfterEffects, Gimp etc.)

IMPORT FROM AFFINITY DESIGNER OR PHOTO
Tutorial video on how to use the script to import Affinity (Photo/Designer) Layers as Image Layers with cropped images in Moho:
IMPORT FROM KRITA
Tutorial video on how to use the script to import Krita Layers as Image Layers with cropped images in Moho:
IMPORT FROM SPINE
Short tutorial video on how to use the script to import Region Attachments (images) and their transformations at the setup pose from Spine as Moho 14 Layers:
IMPORT FROM PHOTOSHOP, AFTEREFFECTS, GIMP ETC.
There are spine JSON export scripts for many programs. Look here for export scripts made by Esoteric Software (creator of Spine) for at least Photoshop, AfterEffects, Gimp etc. : https://github.com/EsotericSoftware/spine-scripts
----------------------------------------------------------------------------------------------------------
VERSION HISTORY
----------------------------------------------------------------------------------------------------------
1.1.3 [current]
- Changed: Made compatible with newer utils file
1.1.2
- Changed: Made compatible with newer utils file
- Added: Retina icon
1.1.1
- Fixed: Typo
1.1.0
- Added: If there is bone data available it now imports bones and creates the same skeleton in Moho, complete with transforms translation, rotation and scale*. Bone shearing will be ignored tho. *: Different scaling for X and Y isn't supported. If a bone has a different scale value for x and y then the x-value will be used as scale in Moho.
- Changed: Improve warnings output
- Changed: different scaleX and scaleY values in bones aren't supported. The image transformspath to calculate images from bones in the tree is now calculated accordingly to keep image transforms the same as the bone transformations.
1.0.0
- Initial release. Imports and transforms images.
----------------------------------------------------------------------------------------------------------
ABOUT THE CURRENT VERSION
----------------------------------------------------------------------------------------------------------
How to install the script in Moho?
* Click on the 'Download for Install Script command''-button below to download the zip-file
* Unzip the downloaded file
* Start Moho
* Open the Scripts menu --> choose 'Install Script' --> Browse through the unziped folder and choose the folder named 'wp_spine_import_for-moho-install-script-command' --> It should now install the script
* Once installation is done Moho shows a dialog saying 'Installation Complete!'
* Restart Moho (only after restarting the tool will become available)
* You should now see the tool with the spine logo in the tools panel. Ready to use!
Why I created this script?
1. Make exporting layers from Affinity way easier and quicker
I started this script because Affinity (Mainly Photo and Designer) (Affinity website) offers a great way to export all layers to individual cropped images on disc, together with a spine.json file containing all layer data, like positions of all images.
This makes it extremely easy and fast to import all layers and images directly into Spine. However, Moho so far couldn't open these spine.json files. So it took a long time to import all these individual images to moho layers and position all these layers in the right spot. By using this script this now is as easy in Moho as it is in Spine and it's now importing and positioning all images/layers via the spine.json file with a single click so we don't have to spend lots of time importing images anymore and can start rigging right away.
2. Make exporting layers from Krita way easier and quicker
There is also a very useful script for Krita (Krita Website) around to export Krita layers to spine.json. Although I didn't tested the outputed spine.json with that script yet, it should work just fine too. You can find it here: Krita-to-spine script download
Not shown in the tutorial video above, because I wonder if it's useful in Spine, but it's even possible to add special naming to Krita group layers that the export script recognises to automatically create bones etc.. See the krita-to-spine script readme (follow the download link) for more information.
3. Make importing images from spine.json possible too?
After having the import for Affinity-generated spine.json files working I wondered how far I could take this to import spine.json files generated by Spine itself (Esoteric Software/Spine website). These files are more complicated because Spine also has a complete skeleton system, like Mohos bones, and images inside spine files have a hierarchical tree structure with bones. But also all bones can have translations/offsets, rotations, scales and shear values. Even images bound to bones can have these transformations applied. And they all influence each other via parenting.
Besides this in Spine it's possible to tint images, which isn't currently possible in Moho.
Spine has more features, like multiple skeletons in a file, multiple skins per skeleton, meshes, several other types of attachments besides images and transforms. But these aren't supported by this script (yet?) and is way over the original use case for the script, which was to import all layers as images from Affinity.
All images are imported now and get the right transformations (except shears) in Moho, just like in Spine. And when it finds tinted images/slots in the spine.json file it generates extra reference tint-layers in Moho to have a similar effect. At the moment these aren't having exactly the same result/blending, so these will probably be tweaked later. But at least it imports. And I consider this a feature that won't be used by many people.
4. Importing from other software (Photoshop, After Effects, Gimp etc.)?
There are Spine JSON exporters around for Photoshop, Gimp and other software too. So basically any software you have and has an export script to export to Spine JSON fileformat is with this script now able to export to Moho. Esoteric Software, the creators of Spine, have a repository with some exporter scripts for several programs here: https://github.com/EsotericSoftware/spine-scripts

What does the script do?
This first version generates a group with image layers. The group is transformed to a bone layer in moho, but doesn't contain bones yet. A later version will probably add an option to load the bones too, if the spine.json contains bone data.
The bone data in the spine.json is however used to determine the transformations of the images to position/rotate/scale them on the right spots in Moho.
How to use the script?
This is a new script! Backup your moho file before use just to be sure.
Hit the tool button, browse the spine.json file (only json files are supported)
How to export Affinity or Krita layers, or Spine content to spine.json?
See tutorial videos above!
Supported Moho version
Tested in Moho 14.x.
About support of spine.json features
Everything imports fine from files generated by Affinity
About more advanced spine features as generated by Spine:
- Only the default skin (skin named 'default'
- Imports region attachments (= image without a mesh). Images having a mesh will not be imported.
- Like spine the valid supported image file formats are png, jpg and jpeg (will search for these formats in that order, like Spine).
- Positions, rotations and scaling* of images and bones are calculated hierarchical through the bones to their final position.
- *) Different/Individual scaling for X and Y on bones isn't supported. If a bone has a unequal values for scaleX and scaleY then the scaleX value will be used as scale for that bone in Moho.
- Shear on bones aren't currently supported.
- Constraints aren't supported.
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: 325