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

ScriptName = "AE_HandsTable"

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

AE_HandsTable = {}

function AE_HandsTable:Name()
	return 'Hands table'
end

function AE_HandsTable:Version()
	return '1.3'
end

function AE_HandsTable:UILabel()
	return 'Hands table'
end

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

function AE_HandsTable:Description()
	return 'Click to select a switch, alt-click to select another switch. Select switch child to setup the table'
end

function AE_HandsTable:ColorizeIcon()
	return true
end

function AE_HandsTable:LoadPrefs(prefs)
	self.radius = prefs:GetFloat("AE_HandsTable.radius", 0.1)
	self.startX = prefs:GetFloat("AE_HandsTable.startX", 0)
	self.startY = prefs:GetFloat("AE_HandsTable.startY", 0)
end

function AE_HandsTable:SavePrefs(prefs)
	prefs:SetFloat("AE_HandsTable.radius",self.radius)
	prefs:SetFloat("AE_HandsTable.startX",self.startX)
	prefs:SetFloat("AE_HandsTable.startY",self.startY)	
end

function AE_HandsTable:ResetPrefs(prefs)
	self.radius = 0.1
	self.startX = 0
	self.startY = 0
end

AE_HandsTable.radius = 0.1
AE_HandsTable.startX = 0
AE_HandsTable.startY = 0
AE_HandsTable.defaultStartX = 0
AE_HandsTable.defaultStartY = 1
AE_HandsTable.defaultRadius = 0.1
AE_HandsTable.over = false
AE_HandsTable.fixed = {0,0,0,0}
AE_HandsTable.layerDataPrefix = 'AE_HandsTable'
AE_HandsTable.lastLayer = nil
--AE_HandsTable.nameTableLength = 0
AE_HandsTable.flipIcons = false
AE_HandsTable.flipVIcons = false
AE_HandsTable.previousName = nil

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

function AE_HandsTable:IsRelevant(moho)
	return true
end

function AE_HandsTable:IsEnabled(moho)
	--self:UpdateNameTable(moho)
	return true
end

-- **************************************************
-- Table interface
-- **************************************************

AE_HandsTable.CELL = 10
AE_HandsTable.HLEGEND = 2
AE_HandsTable.VLEGEND = 3
AE_HandsTable.LEGEND = 4
AE_HandsTable.SKIP = 5
AE_HandsTable.CONTENT_CELL = 11
AE_HandsTable.CURRENT = 6

function AE_HandsTable:IterateTable(startX, startY, radius)
	startX = startX or self.startX or 0
	startY = startY or self.startY or 0
	radius = radius or self.radius or 0.1
	if radius <= 0 then radius = 0.1 end
	local fixedStartX = startX + 10 * radius
	local i, j = 0,-1
	local fx = 0
	local cr = 0
	local n = 8
	local iterateFn = function()
		j = j + 1
		if j > n then 
			j = 0
			i = i + 1
		end
		if i > n then		
			fx = fx + 1
			if fx <= (#self.fixed - 1) then return fx, 0, fixedStartX, startY-fx*2*radius, self.LEGEND end

			cr = cr + 1
			if cr > (#self.fixed - 1) then return nil end
			return  cr, 0, startX + cr*radius*2, startY - (n+1)*radius, self.CURRENT 

		end
		local lx = startX + i*radius
		local ly = startY - j*radius
		local cellType = self.CELL
		if i == 0 and j == 0 then cellType = self.SKIP
		elseif j == 0 then cellType = self.HLEGEND
		elseif i == 0 then cellType = self.VLEGEND
		end
		return i, j, lx, ly, cellType
	end
	return iterateFn
end

function AE_HandsTable:GetTwoLegends()
	local legends = {1,2,3,4}
	for fx = 1, 3 do
		if self.fixed[fx] > 0 then
			for i, val in pairs(legends) do
				if val == fx then 
					table.remove(legends, i)
					break
				end
			end
			if #legends == 2 then return legends end
		end
	end
	return {legends[1], legends[2]}
end

function AE_HandsTable:GetElementSignature(i, j)
	i = i or self.i
	j = j or self.j
	local result = {}
	local legends = self:GetTwoLegends()
	for dim, fixed in pairs(self.fixed) do
		if fixed > 0 then 
			table.insert(result, fixed)
		else 
			if legends[1] == dim then table.insert(result, i)
			elseif legends[2] == dim then table.insert(result, j)
			else table.insert(result, 0)
			end
		end
	end
	return result
end

function AE_HandsTable:FixValue(fix, value)
	if fix > #self.fixed then return false end
	if value == 0 then 
		self.fixed[fix] = 0
		return true
	end
	local numFixed = 0
	for i, val in pairs(self.fixed) do
		if val > 0 then numFixed = numFixed + 1 end
	end
	if numFixed >= (#self.fixed - 2) then return false end
	self.fixed[fix] = value
	return true
end

function AE_HandsTable:TestMouse(moho, mouseEvent)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.vec)
	local matrix = LM.Matrix:new_local()
	moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
	matrix:Transform(vec)
	local v = LM.Vector2:new_local()
	local picked = false
	local dist = self.radius/3
	local legendDist = self.radius
	for i, j, lx, ly, cellType in self:IterateTable() do
		if cellType ~= self.SKIP then
			v:Set(lx,ly)
			local m = (vec - v):Mag()
			if cellType == self.LEGEND then 
				if m < legendDist then return true, i, j, cellType end
			elseif m < dist then return true, i, j, cellType
			end
		end
	end
	return false
end

-- **************************************************
-- Keyboard/Mouse Control
-- **************************************************

function AE_HandsTable:NonDragMouseMove()
	return true
end

function AE_HandsTable:OnMouseMoved(moho, mouseEvent)
	local over, i, j, cellType = self:TestMouse(moho, mouseEvent)
	local changed = (self.over ~= over) 
	self.over = over
	if not changed then 
		if not over then return end
		if cellType == self.LEGEND then return end
		if cellType == self.CURRENT then return end
		if self.i == i and self.j == j then return end
	end
	if cellType ~= self.LEGEND and cellType ~= self.CURRENT then
		self.i = i
		self.j = j
	end
	
	if cellType == self.CELL then self:EvalSwitch(moho, false) end
	
	mouseEvent.view:DrawMe()		
end

function AE_HandsTable:PickSwitch(moho, mouseEvent)
	local layer = mouseEvent.view:PickGlobalLayer(mouseEvent.pt)
	local switch = moho:LayerAsSwitch(layer) or layer:AncestorSwitchLayer()
	if not switch then 
		--TODO: examine all the other layers by select and mouseEvent.view:PickShape(mouseEvent.pt) or something else for bitmaps
	end
	while switch:AncestorSwitchLayer() and not (AE_Utilities:IsAncestor(switch:AncestorSwitchLayer(), moho.layer) or switch:AncestorSwitchLayer() == moho.layer) do
		switch = switch:AncestorSwitchLayer()
	end
	if switch then 
		moho:SetSelLayer(switch)
		moho:ShowLayerInLayersPalette(switch)
		return true
	end
		
	return false 
end

function AE_HandsTable:OnMouseDown(moho, mouseEvent)
	if (not moho:LayerAsSwitch(moho.layer) and not moho.layer:AncestorSwitchLayer()) or mouseEvent.altKey then 
		local result = self:PickSwitch(moho, mouseEvent)
		if result then self:UpdateWidgets(moho) end
		return 
	end
	local over, i, j, cellType = self:TestMouse(moho, mouseEvent)
	if not over then return end
	local legends = self:GetTwoLegends()
	if cellType == self.CELL then
		local currentSignature = self:GetElementSignature(i, j)
		for dim, val in pairs(currentSignature) do
			if val == 0 then
				self:FixValue(legends[1], i)
				self:FixValue(legends[2], j)
				return
			end
		end
		self:SetupLayer(moho, currentSignature)
		self:EvalSwitch(moho, true)
	elseif cellType == self.LEGEND then self:FixValue(i, 0)
	elseif cellType == self.HLEGEND then self:FixValue(legends[1], i)
	elseif cellType == self.VLEGEND then self:FixValue(legends[2], j)
	elseif cellType == self.CURRENT then
		local currentElementSignature = self:ReadLayerData(moho, moho.layer)
		if currentElementSignature and currentElementSignature[i] > 0 then
			self:FixValue(i, currentElementSignature[i])
		end
	end 
end

function AE_HandsTable:OnMouseUp(moho, mouseEvent)
	
end

function AE_HandsTable:OnKeyDown(moho, keyEvent)
	
end

function AE_HandsTable:OnKeyUp(moho, keyEvent)
	
end

function AE_HandsTable:OnInputDeviceEvent(moho, deviceEvent)
	
end

function AE_HandsTable:DrawMe(moho, view)
	if not moho:LayerAsSwitch(moho.layer) and not moho.layer:AncestorSwitchLayer() then return end
	
	if not self.icons then self:ReadIcons(moho) end

	local g = view:Graphics()
	local v = LM.Vector2:new_local()

	g:Push()
	
	local defaultColor = {255, 0, 0}
	local R, G, B = table.unpack(defaultColor)
	g:SetColor(R, G, B, 255)
	local legends = self:GetTwoLegends()
	local currentElementSignature = self:ReadLayerData(moho, moho.layer)
	
	for i, j, lx, ly, cellType in self:IterateTable() do
		v:Set(lx,ly)
		if cellType == self.SKIP then
			--if self.nameTableLength > 0 then g:FrameCircle(v, self.radius/3) end
		elseif cellType == self.CELL then
			local filledCell = self:HasAnything(self:GetElementSignature(i,j))
			if filledCell then
				g:FrameCircle(v, self.radius/3)
			end
			if self.over then
				if self.i == i and self.j == j then 
					g:FillCircle(v, self.radius/3)
				elseif self.i == i or self.j == j then
					g:SetColor(R, G, B, 64)
					g:FillCircle(v, self.radius/3)
					g:SetColor(R, G, B, 255)
				end
			end
		elseif cellType == self.LEGEND then
			local fixedValue = self.icons[i] and self.icons[i][self.fixed[i]]
			if fixedValue then
				self:DrawIcon(moho, g, lx, ly, self.radius, fixedValue) 
			end
		elseif cellType == self.CURRENT then
			if currentElementSignature then 
				local currentIcon = self.icons[i] and self.icons[i][currentElementSignature[i]]
				if currentIcon then 
					self:DrawIcon(moho, g, lx, ly, self.radius/2, currentIcon) 
				end
			end
		else
			local curArray = nil
			if cellType == self.HLEGEND then curArray = self.icons[legends[1]] and self.icons[legends[1]][i] end
			if cellType == self.VLEGEND then curArray = self.icons[legends[2]] and self.icons[legends[2]][j] end
			if curArray then self:DrawIcon(moho, g, lx, ly, self.radius/2, curArray) end
			if self.i == i and self.j == j then 
				g:SetColor(R, G, B, 64)
				g:FillCircle(v, self.radius/3)
				g:SetColor(R, G, B, 255)				
			end
		end
	end

	g:Pop()

end

-- **************************************************
-- Tool Panel Layout
-- **************************************************

AE_HandsTable.FLIPICONS = MOHO.MSG_BASE + 2
AE_HandsTable.FLIPVICONS = MOHO.MSG_BASE + 4
AE_HandsTable.RESETVIEW = MOHO.MSG_BASE + 3

function AE_HandsTable:DoLayout(moho, layout)
	
	self.flipIconsButton = LM.GUI.ImageButton("ScriptResources/flip_points_h", "Flip icons", true, self.FLIPICONS)
	layout:AddChild(self.flipIconsButton,  LM.GUI.ALIGN_LEFT, 0)
	
	self.flipVIconsButton = LM.GUI.ImageButton("ScriptResources/flip_points_v", "Flip icons", true, self.FLIPVICONS)
	layout:AddChild(self.flipVIconsButton,  LM.GUI.ALIGN_LEFT, 0)	
	
	self.resetViewButton = LM.GUI.Button('Reset view', self.RESETVIEW)
	layout:AddChild(self.resetViewButton, LM.GUI.ALIGN_LEFT, 0)	
	
end

function AE_HandsTable:HandleMessage(moho, view, msg)
	if msg == self.BUTTON_1 then
		self:WriteIcon(moho)
	elseif msg == self.BUTTON_2 then
		self:ReadIcons(moho)
	elseif msg == self.FLIPICONS then
		self.flipIcons = not self.flipIcons
	elseif msg == self.FLIPVICONS then
		self.flipVIcons = not self.flipVIcons		
	elseif msg == self.RESETVIEW then
		local zoom = moho.view:Graphics():ViewZoom()
		local offset = moho.view:Graphics():ViewOffset()
		self.startX = self.defaultStartX - offset.x
		self.startY = self.defaultStartY - offset.y
		self.radius = self.defaultRadius/zoom
	end
end

function AE_HandsTable:UpdateWidgets(moho)
	self.flipIconsButton:SetValue(self.flipIcons)
	self.flipVIconsButton:SetValue(self.flipVIcons)
	self:UpdateNameTable(moho)
end

function AE_HandsTable:UpdateNameTable(moho)
	local newLayer = moho.layer:AncestorSwitchLayer() or moho.layer
	if self.lastLayer ~= newLayer then
		self.lastLayer = newLayer		
		self:FillNameTable(moho, newLayer)
		local switchLayer = moho:LayerAsSwitch(newLayer)
		if switchLayer then self.previousName = switchLayer:GetValue(moho.layerFrame) end
	end
end

-- **************************************************
-- Other Methods
-- **************************************************

function AE_HandsTable:DrawIcon(moho, g, x, y, size, pointsArray)
	local p1 = LM.Vector2:new_local()
	local p2 = LM.Vector2:new_local()
	for i, val in pairs(pointsArray) do
		if i > 1 then 
			local p1x = pointsArray[i-1].x * size
			local p2x = val.x * size
			local p1y = pointsArray[i-1].y * size
			local p2y = val.y * size
			if self.flipIcons then
				p1x = x - p1x + size/2
				p2x = x - p2x + size/2				 
			else
				p1x = x + p1x - size/2
				p2x = x + p2x - size/2
			end
			if self.flipVIcons then
				p1y = y - p1y + size/2
				p2y = y - p2y + size/2				 
			else
				p1y = y + p1y - size/2
				p2y = y + p2y - size/2
			end			
			p1:Set(p1x, p1y)
			p2:Set(p2x, p2y)
			g:DrawLine(p1.x, p1.y, p2.x, p2.y)
		end
	end
end

function AE_HandsTable:EvalSwitch(moho, fix)
	local switchLayer = moho:LayerAsSwitch(moho.layer)
	if not switchLayer then return end
	local signature = self:GetElementSignature()
	if not signature then return end
	local layerName = self:GetNameTable(moho, signature)
	if not layerName then 
		if self.previousName then switchLayer:SetValue(moho.frame, self.previousName) end
		return
	end
	local childLayer = switchLayer:LayerByName(layerName)
	if not childLayer then 
		self:FillNameTable(moho, switchLayer)
		moho:UpdateUI()
		return
	end
	switchLayer:SetValue(moho.frame, layerName)
	if fix then 
		self.previousName = layerName
		moho:UpdateUI()
	end 	
end

function AE_HandsTable:SetupLayer(moho, signature)
	local layer = moho.layer:AncestorSwitchChild()
	if not layer then return end
	local oldSignature = self:ReadLayerData(moho, layer)
	if oldSignature and LM.GUI.Alert(LM.GUI.ALERT_INFO,
		 "This layer is always applied to", 
		table.concat(oldSignature, ' '), 
		"Do you realy want to change it?", "YES", "Cancel") == 1 then return end
	signature = signature or self:GetElementSignature()
	local oldLayerName = self:GetNameTable(moho, signature)
	if oldLayerName and LM.GUI.Alert(LM.GUI.ALERT_INFO,
		 "This place is always occupied by", 
		oldLayerName, 
		"Do you realy want to replace it?", "YES", "Cancel") == 1 then return end
	self:WriteLayerData(moho, layer, signature, nil, true)
	self:SetNameTable(moho, signature, layer)
	if oldSignature then 
		self:FillNameTable(moho, layer:AncestorSwitchLayer())
	end
end

function AE_HandsTable:GetFolder(moho, filename)
	if filename then filename = filename .. ".dat"
	else filename = "" end
	
	local dir = "/Scripts/ScriptResources/ae_hand_table/"
	local sVersion = string.gsub(moho:AppVersion(), "^(%d+)(%.%d+)(%..+)", "%1%2")
	local version = tonumber(sVersion) 
	if version < 13 then 
		dir = string.lower(dir)
	end
	
	return moho:UserAppDir() .. dir .. filename
end

function AE_HandsTable:WriteIcon(moho)
	local mesh = moho:Mesh()
	if not mesh then return end
	local bounds = moho.layer:Bounds(moho.frame)
	local maxBound = bounds:MaxDimension2D()
	local addVect = bounds.fMax - bounds.fMin
	if addVect.x < addVect.y then
		addVect:Set((1-addVect.x/maxBound)/2, 0)
	else
		addVect:Set(0, (1-addVect.y/maxBound)/2)	
	end
	
	local curve = mesh:Curve(0)
	local pointsArray = {}
	for p = 0, curve:CountPoints() - 1 do
		local point = curve:Point(p)
		table.insert(pointsArray, {x=(point.fPos.x - bounds.fMin.x)/maxBound + addVect.x,y=(point.fPos.y - bounds.fMin.y)/maxBound + addVect.y})
	end
	-- save to file
	local filePath = self:GetFolder(moho, "0")
	local file = io.open(filePath, "a+")
	io.output(file)
	for i, val in pairs(pointsArray) do 
		io.write(string.char(127 + val.x * 128)..string.char(127 + val.y * 128))
	end
	io.write("\n")
	io.close()
end

function AE_HandsTable:ReadIcons(moho)
	self.icons = {}
	for dim = 1, #self.fixed do
		local filePath = self:GetFolder(moho, tostring(dim))
		if AE_Utilities:FileExists(filePath) then
			self.icons[dim] = {}
			for line in io.lines(filePath) do
				local nextPointsArray = {}
				for i = 1, string.len(line)-1, 2 do
					local lx = string.byte(string.sub(line, i, i+1))
					local ly = string.byte(string.sub(line, i+1, i+2))
					table.insert(nextPointsArray, {x=(lx-127)/128, y=(ly-127)/128})
				end
				table.insert(self.icons[dim], nextPointsArray)
			end			
		end
	end
end


function AE_HandsTable:EncodeLayerData(dataArray)
	local encodedString = ''
	for dim, val in pairs(dataArray) do
		local sn = 127 + val
		encodedString = encodedString .. string.char(sn)
	end
	return encodedString
	
end

function AE_HandsTable:DecodeLayerData(dataString)
	local dataArray = {}
	for i = 1, #dataString do
		local val = string.byte(string.sub(dataString, i, i+1))
		table.insert(dataArray, val-127)
	end
	return dataArray
end

function AE_HandsTable:WriteLayerData(moho, layer, dataArray, i, doReplace)
	local data = layer:ScriptData()
	dataName = self.layerDataPrefix
	if i and doReplace then
		dataName = dataName .. i
	else
		--TODO: find a suitable i to add (and also must write an iterator function for reading such values)
	end
	local encodedString = self:EncodeLayerData(dataArray)
	data:Set(dataName, encodedString)	
end

function AE_HandsTable:ReadLayerData(moho, layer, i)
	if not layer then return nil end
	local data = layer:ScriptData()	
	local dataName = self.layerDataPrefix
	if i then
		dataName = dataName .. i
	end
	local stringValue = data:GetString(dataName)
	if stringValue and #stringValue == #self.fixed then return self:DecodeLayerData(stringValue) end
	return nil
end

function AE_HandsTable:SetNameTable(moho, signature, layer) --, dontCount)
	if not self.nameTable then self.nameTable = {} end
	local curTable = self.nameTable
	for i, val in pairs(signature) do
		if i < #signature then
			if not curTable[val] then curTable[val] = {} end
			curTable = curTable[val]
		else
			if layer then curTable[val] = layer:Name()
			else curTable[val] = nil
			end
			break
		end
	end
	--if not dontCount then self.nameTableLength = self:NameTableLength() end
end

function AE_HandsTable:HasAnything(signature, level, t)
	level = level or 1
	if level == 1 then t = self.nameTable end
	local i1, i2 = 1, 8
	if signature[level] > 0 then 
		i1 = signature[level]
		i2 = signature[level]
	end
	for i = i1, i2 do
		if t[i] then
			if type(t[i]) == 'table' then
				result = self:HasAnything(signature, level+1, t[i])
				if result then return true end
			else 
				return true
			end
		end
	end
	return false
end

function AE_HandsTable:GetNameTable(moho, signature)
	if not self.nameTable then return nil end
	if true then
		return self.nameTable[signature[1]] and 
			self.nameTable[signature[1]][signature[2]] and
			self.nameTable[signature[1]][signature[2]][signature[3]]  and
			self.nameTable[signature[1]][signature[2]][signature[3]][signature[4]]
	end
	local curTable = self.nameTable
	for i, val in pairs(signature) do
		if i < #signature then
			if not curTable[val] then return nil end
			curTable = curTable[val]
		else
			return curTable[val]
		end
	end
end

function AE_HandsTable:FillNameTable(moho, layer)
	self.nameTable = {}	
	--self.nameTableLength = 0
	local switchLayer = moho:LayerAsSwitch(layer)
	if not switchLayer then return end
	for i = 0, switchLayer:CountLayers() - 1 do
		local nextLayer = switchLayer:Layer(i)
		local signature = self:ReadLayerData(moho, nextLayer) --TODO: call iterator hear
		if signature then
			self:SetNameTable(moho, signature, nextLayer) --, true)
			--self.nameTableLength = self.nameTableLength + 1
		end
	end
end

--[[
function AE_HandsTable:NameTableLength(nextTable)
	nextTable = nextTable or self.nameTable
	if not nextTable then return 0 end
	local subCounts = 0
	for i,val in pairs(nextTable) do
		if type(val) ~= "table" then return #nextTable end
		subCounts = subCounts + self:NameTableLength(val)
	end
	return subCounts
end
--]]




Icon
Switch hands organizer
Listed

Script type: Tool

Uploaded: Apr 21 2022, 15:46

Last modified: Jun 28 2022, 11:16

Graphic UI showing 4D table for sorting hand variants used in switch layer
Click to select switch layer, alt-click to select another switch layer.
While switch layer is selected use table to see (mouse move) and apply (mouse  click) any of sorted switch children. 
Click on upper table levels walks your to the deeper one. Watching and appliing is possible on the last level. 
To go level up click on the right icons showing selected levels to deselect it. 
Select switch child layer in Moho Layers list View to setup the table (to set a cell for each child)



UPDATES:
v. 1.2: added Vertical Flip button (for display hand icons)
v. 1.3: adapted for Moho 12.x versions (was compartible with 13.5 only)

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