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

ScriptName = "AE_Magnet"

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

AE_Magnet = {}

AE_Magnet.BASE_STR = 2145

function AE_Magnet:Name()
	return "Magnet"
end

function AE_Magnet:Version()
	return "6.48"
end

function AE_Magnet:Description()
	return MOHO.Localize("/Scripts/Tool/Magnet/Description=Move points within a radius of influence for selected layers(hold <alt> to adjust radius, <ctrl/cmd> to select points)")
end

function AE_Magnet:Creator()
	return "Smith Micro Software, improved by Alexandra Evseeva"
end

function AE_Magnet:UILabel()
	return(MOHO.Localize("/Scripts/Tool/Magnet/Magnet=Magnet"))
end

function AE_Magnet:LoadPrefs(prefs)
	self.magnetRadius = prefs:GetFloat("AE_Magnet.magnetRadius", 0.5)
	self.selectedOnly = prefs:GetBool("AE_Magnet.selectedOnly", false)
  self.multilayerMode = prefs:GetBool("AE_Magnet.multilayerMode", false)
end

function AE_Magnet:SavePrefs(prefs)
	prefs:SetFloat("AE_Magnet.magnetRadius", self.magnetRadius)
	prefs:SetBool("AE_Magnet.selectedOnly", self.selectedOnly)
  prefs:SetBool("AE_Magnet.multilayerMode", self.multilayerMode)
end

function AE_Magnet:NonDragMouseMove()
	return true -- Call MouseMoved() even if the mouse button is not down
end

function AE_Magnet:ResetPrefs()
	self.magnetRadius = 0.5
	self.selectedOnly = false
  self.multilayerMode = false
end

-- **************************************************
-- Recurring values
-- **************************************************

AE_Magnet.newMethod = false
AE_Magnet.magnetRadius = 0.5
AE_Magnet.selectedOnly = true
AE_Magnet.dragging = false
AE_Magnet.dragVec = LM.Vector2:new_local()
AE_Magnet.dragVec:Set(10000.0, 10000.0)
AE_Magnet.dragResize = false
AE_Magnet.selectionMode = false
AE_Magnet.multilayerMode = false
AE_Magnet.startRadius = 0
AE_Magnet.wasMoved = false

AE_Magnet.minVec = LM.Vector2:new_local()
AE_Magnet.maxVec = LM.Vector2:new_local()



-- **************************************************
-- The guts of this script
-- **************************************************

function AE_Magnet:IsEnabled(moho)
--[[ 
	if (moho:CountPoints() > 0) then
		return true
	end
	return false
 --]]  
  return true
end

function AE_Magnet:IsRelevant(moho)
--[[
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end
  --]]
	return true
end

function AE_Magnet:OnInputDeviceEvent(moho, deviceEvent)
	return LM_TransformPoints:OnInputDeviceEvent(moho, deviceEvent)
end

function AE_Magnet:OnMouseDown(moho, mouseEvent)
  self.wasMoved = false
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if (mouseEvent.altKey) then
		self.dragResize = true
		self.startRadius = self.magnetRadius
		self.dragVec:Set(mouseEvent.drawingVec)
		mouseEvent.view:DrawMe()
		return
	elseif (mouseEvent.ctrlKey) then
		self.selectionMode = true
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseDown(moho, mouseEvent)
		return
	end

	self.dragging = true
	self.dragVec:Set(mouseEvent.drawingVec)

  if not self.multilayerMode then
	 moho.document:PrepUndo(moho.drawingLayer)
  else
   moho.document:PrepMultiUndo()
  end
  
	moho.document:SetDirty()
  
  --print("Mouse down")


  self.scaleList = {}
  self.endFPos = {}  
  self.selList = {}
  self.layers = {}
  --deltas is from the previous version. It can be deleted now.
  self.deltas = {}
  --print("mouse: ", mouseEvent.drawingVec.x, ", ", mouseEvent.drawingVec.y)
  local vec = LM.Vector2:new_local()
  local m = LM.Matrix:new_local()
  local absMouse = LM.Vector2:new_local()
  vec:Set(mouseEvent.drawingVec)
  moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
  m:Transform(vec)
  absMouse:Set(vec)
  --print("abs: ", absMouse.x, ", ", absMouse.y)
  for la = 0, moho.document:CountSelectedLayers()-1 do
    local layer = moho.document:GetSelectedLayer(la)
    layer:GetFullTransform(moho.frame, m, moho.document)
    m:Invert()
    m:Transform(vec) 
    m:Invert()
    --print(layer:Name(), "\n", vec.x, ", ", vec.y)
    if (self.multilayerMode or layer == moho.drawingLayer) and layer:LayerType() == MOHO.LT_VECTOR  then
      mesh = moho:LayerAsVector(layer):Mesh()
    	for i = 0, mesh:CountPoints() - 1 do
    		local pt = mesh:Point(i)
    		if (not pt.fHidden and not (self.selectedOnly and not pt.fSelected)) then
    			local v = pt.fPos - mouseEvent.drawingVec
          if(self.multilayerMode and layer~=moho.drawingLayer) then
             vec:Set(pt.fPos)
             m:Transform(vec)
             v = vec - absMouse
          end
    			local magInfluence = v:Mag()
          --print(magInfluence)
    			if (self.newMethod) then
    				-- new method (more similar to how bones work)
    				-- however, the "strength" is set to a constant 0.5 - the user would need to be able to change this
    				magInfluence = magInfluence * magInfluence
    				magInfluence = 0.5 / (1 + magInfluence * magInfluence * 1000)
    				if (magInfluence > 0.001 or (self.selectedOnly and pt.fSelected)) then
    					pt.fSelected = true
    					if (magInfluence <= 0.001) then
    						magInfluence = 0.0
    					end
    					table.insert(self.scaleList, magInfluence)
              table.insert(self.endFPos, LM.Vector2:new_local())
              table.insert(self.layers, layer)
			  --------------------------- from the previous version. Can be deleted now
              table.insert(self.deltas, LM.Vector2:new_local())			  
              self.deltas[#self.deltas]:Set(pt.fPos - pt.fAnimPos:GetValue(moho.frame))
			  -------------------------------------------------------------------------			  
    				else
    					pt.fSelected = false
    				end
    			else
    				-- old method
    				if (magInfluence <= self.magnetRadius or (self.selectedOnly and pt.fSelected)) then
    					-- mark the point as selected, and compute the magnet's influence over it
    					pt.fSelected = true
    					magInfluence = magInfluence / self.magnetRadius
    					if (magInfluence > 1.0) then
    						magInfluence = 1.0
    					end
    					table.insert(self.scaleList, LM.Slerp(magInfluence, 1, 0))
              table.insert(self.endFPos, LM.Vector2:new_local())
              table.insert(self.layers, layer)
			  --------------------------- from the previous version. Can be deleted now
              table.insert(self.deltas, LM.Vector2:new_local())			  
              self.deltas[#self.deltas]:Set(pt.fPos - pt.fAnimPos:GetValue(moho.frame))
			  -------------------------------------------------------------------------
    				else
    					pt.fSelected = false
    				end
    			end
    		end
    	end
    
    	mesh:PrepMovePoints()    
      for p=0, mesh:CountPoints()-1 do
         if mesh:Point(p).fSelected then
            table.insert(self.selList, mesh:Point(p))
         end
      end
    end
  end  
  
	--TODO:fix and uncomment next lines to work in onion skin mode
	--for now these lines do not work correctly
	
	--[[
	for la = 0, moho.document:CountSelectedLayers()-1 do
		local layer = moho.document:GetSelectedLayer(la)
		if (self.multilayerMode or layer == moho.drawingLayer) and layer:LayerType() == MOHO.LT_VECTOR  then
			moho:AddPointKeyframe(moho.drawingFrame, layer, MOHO.MohoGlobals.EditMultipleKeys)
		end
	end
  
	self:CorrectPointPos(moho)
	
	for la = 0, moho.document:CountSelectedLayers()-1 do
		local layer = moho.document:GetSelectedLayer(la)
		if (self.multilayerMode or layer == moho.drawingLayer) and layer:LayerType() == MOHO.LT_VECTOR  then
			moho:AddPointKeyframe(moho.drawingFrame, layer, MOHO.MohoGlobals.EditMultipleKeys)
		end
	end	
	
	--]]

  
	mouseEvent.view:DrawMe()
end

function AE_Magnet:OnMouseMoved(moho, mouseEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end
	if (self.dragResize) then
		self.magnetRadius = self.startRadius + (mouseEvent.pt.x - mouseEvent.startPt.x) / 600
		if (self.magnetRadius < 0.01) then
			self.magnetRadius = 0.01
		end
		if (self.magnetRadius > 5.0) then
			self.magnetRadius = 5.0
		end
		mouseEvent.view:DrawMe()
		return
	elseif (self.selectionMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseMoved(moho, mouseEvent)
		return
	end

	self.dragVec:Set(mouseEvent.drawingVec)
	if (not self.dragging) then
		mouseEvent.view:DrawMe()
		return
	end

	local offset = mouseEvent.drawingVec - mouseEvent.drawingStartVec
	local vec = LM.Vector2:new_local()
	local vec2 = LM.Vector2:new_local()
	local m = LM.Matrix:new_local()
	local mloc = LM.Matrix:new_local()


	moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)

  
	for i, pt in ipairs(self.selList) do
    local pointOffset = offset    
    if(self.multilayerMode and self.layers[i]~=moho.drawingLayer)then
      vec:Set(mouseEvent.drawingVec)
      vec2:Set(mouseEvent.drawingStartVec)
      self.layers[i]:GetFullTransform(moho.frame, mloc, moho.document)
      mloc:Invert()
      mloc:Multiply(m)
      mloc:Transform(vec)
      mloc:Transform(vec2)
      pointOffset = vec - vec2      
      --print("moving for ", pointOffset.x, ", ", pointOffset.y)
    end
		pt.fPos = pt.fTempPos + pointOffset * self.scaleList[i]
    if(self.endFPos[i]~=nil)then 
      self.endFPos[i]:Set(pt.fPos)
    end
	end
	self.wasMoved = true
	mouseEvent.view:DrawMe()
end

function AE_Magnet:OnMouseUp(moho, mouseEvent)

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	self.dragging = false
	self.dragVec:Set(mouseEvent.drawingVec)

	if (self.dragResize) then
		self.dragResize = false
		mouseEvent.view:DrawMe()
		return
	elseif (self.selectionMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseUp(moho, mouseEvent)
		self.selectionMode = false
		mouseEvent.view:DrawMe()
		return
	end


	if self.wasMoved then
		for la = 0, moho.document:CountSelectedLayers()-1 do
			local layer = moho.document:GetSelectedLayer(la)
			--layerFrame = moho.frame + layer:TotalTimingOffset()			
			moho:AddPointKeyframe(moho.drawingFrame, layer, MOHO.MohoGlobals.EditMultipleKeys)
		end
		self:CorrectPointPos(moho)
		for la = 0, moho.document:CountSelectedLayers()-1 do
			local layer = moho.document:GetSelectedLayer(la)
			--layerFrame = moho.frame + layer:TotalTimingOffset()
			moho:AddPointKeyframe(moho.drawingFrame, layer, MOHO.MohoGlobals.EditMultipleKeys)
		end			
	else
		for i, pt in ipairs(self.selList) do
			frame = moho.drawingFrame + self.layers[i]:TotalTimingOffset()
			pt.fAnimPos:SetValue(frame, pt.fAnimPos:GetValue(frame))
		end
	end
	
	--moho:NewKeyframe(CHANNEL_POINT)
	
	moho:UpdateUI()

	self.selList = nil
	self.scaleList = nil
end

function AE_Magnet:OnKeyDown(moho, keyEvent)
	if (not self.dragging) then
		LM_SelectPoints:OnKeyDown(moho, keyEvent)
	end
end

function AE_Magnet:DrawMe(moho, view)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if (self.selectionMode) then
		LM_SelectPoints:DrawMe(moho, view)
		return
	end

	MOHO.DrawToolSizeCircle(moho, view, self.dragVec, self.magnetRadius, self.dragResize)
end

-- **************************************************
-- Tool options - create and respond to tool's UI
-- **************************************************

AE_Magnet.CHANGE = MOHO.MSG_BASE
AE_Magnet.DUMMY = MOHO.MSG_BASE + 1
AE_Magnet.RESET = MOHO.MSG_BASE + 2
AE_Magnet.SELECTITEM = MOHO.MSG_BASE + 3
AE_Magnet.SELECTCHILDREN = MOHO.MSG_BASE + 4

function AE_Magnet:DoLayout(moho, layout)
	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/Magnet/MagnetRadius=Magnet radius")))
	self.radius = LM.GUI.TextControl(0, "00.0000", self.CHANGE, LM.GUI.FIELD_UFLOAT)
	self.radius:SetWheelInc(0.01)
	layout:AddChild(self.radius)

	self.selectedCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/Magnet/SelectedPointsOnly=Selected points only"), self.CHANGE)
	layout:AddChild(self.selectedCheck)
  self.multilayerCheck = LM.GUI.CheckBox("Multi layer", self.CHANGE)
  layout:AddChild(self.multilayerCheck)
  
  self.selectChildrenButton = LM.GUI.Button("Select child layers", self.SELECTCHILDREN)
  layout:AddChild(self.selectChildrenButton)

end

function AE_Magnet:UpdateWidgets(moho)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	self.radius:SetValue(self.magnetRadius)
	self.selectedCheck:SetValue(self.selectedOnly)
  self.multilayerCheck:SetValue(self.multilayerMode)
end

function AE_Magnet:HandleMessage(moho, view, msg)
  if(msg == self.SELECTCHILDREN) then 
    local layerCollection = self:SelectChildLayers(moho, moho.layer, MOHO.LT_VECTOR)
    if moho.layer:LayerType() ~= MOHO.LT_VECTOR and #layerCollection > 0 then
      moho:SetSelLayer(layerCollection[1])
      for k,v in pairs(layerCollection) do moho:SetSelLayer(v, true) end
    end
    moho:UpdateUI()
    return
  end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if (msg == self.RESET) then
		self.magnetRadius = 0.5
		moho:UpdateUI()
	elseif (msg == self.CHANGE) then
		self.magnetRadius = self.radius:FloatValue()
		if (self.magnetRadius < 0.001) then
			self.magnetRadius = 0.001
		end
		self.selectedOnly = self.selectedCheck:Value()
    self.multilayerMode = self.multilayerCheck:Value()
	elseif (msg >= self.SELECTITEM) then
		mesh:SelectNone()
		local i = msg - self.SELECTITEM
		local name = mesh:Group(i):Name()
		mesh:SelectGroup(name)
		moho:UpdateUI()
	end
end

function AE_Magnet:CorrectPointPos(moho)

	for i, pt in ipairs(self.selList) do
		if(self.endFPos[i]~=nil) then
			--print(self.endFPos[i].x, self.endFPos[i].y)
			--print(pt.fPos.x, pt.fPos.y)
			local delta = self.endFPos[i] - pt.fPos
			pt.fPos = self.endFPos[i] + delta

		end
	end
  
end

function AE_Magnet:printPos(moho, pos)
      print("x = ", pos.x, " y = ", pos.y)
      local layerMatrix = LM.Matrix:new_local()
      moho.layer:GetFullTransform(moho.frame, layerMatrix, moho.document)
      local getAbsPos = LM.Vector2:new_local()
      getAbsPos:Set(pos)
      layerMatrix:Transform(getAbsPos)
      print("x = ", getAbsPos.x, " y = ", getAbsPos.y)
end

function AE_Magnet:SelectChildLayers(moho, layer, layer_type)
   local layerCollection = {}
   layer:SetSecondarySelection(true)
   if layer_type and layer:LayerType() ~= layer_type then layer:SetSecondarySelection(false) end
   if not layer_type or layer:LayerType() == layer_type then table.insert(layerCollection, layer) end
   if layer:IsGroupType() then
      local group = moho:LayerAsGroup(layer)
      for child = 0, group:CountLayers()-1 do 
         local childCollection = self:SelectChildLayers(moho, group:Layer(child), layer_type)
         for k,v in pairs(childCollection) do table.insert(layerCollection, v) end
      end
   end
   return layerCollection
end

Icon
Multilayer magnet
Listed

Script type: Tool

Uploaded: Sep 15 2020, 08:55

Last modified: Jul 02 2021, 03:51

Magnet working with all the selected layers.
Also fixed errors on smartbone driven points.
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: 635