-- ************************************************** -- Provide Moho with the name of this script object -- ************************************************** ScriptName = "LM_TranslatePoints" -- ************************************************** -- General information about this script -- ************************************************** LM_TranslatePoints = {} function LM_TranslatePoints:Name() return "Translate Points plus toggle-select" end function LM_TranslatePoints:Version() return "5.0" end function LM_TranslatePoints:Description() return "Move selected points (hold to constrain, press to weld, hold to disable auto-welding)" end function LM_TranslatePoints:Creator() return "Lost Marble, toggle-select added by Myles Strous" end function LM_TranslatePoints:UILabel() return("Translate Points plus toggle-select") end function LM_TranslatePoints:LoadPrefs(prefs) self.autoWeld = prefs:GetBool("LM_TranslatePoints.autoWeld", true) end function LM_TranslatePoints:SavePrefs(prefs) prefs:SetBool("LM_TranslatePoints.autoWeld", self.autoWeld) end -- ************************************************** -- Recurring values -- ************************************************** LM_TranslatePoints.dragging = false LM_TranslatePoints.numSel = 0 LM_TranslatePoints.selID = -1 -- ************************************************** -- The guts of this script -- ************************************************** function LM_TranslatePoints:IsEnabled(moho) if (moho:CountPoints() > 0) then return true end return false end function LM_TranslatePoints:OnMouseDown(moho, mouseEvent) local mesh = moho:Mesh() if (mesh == nil) then return end self.dragging = true moho.document:SetDirty() if (self.selID == -1) then -- if this isn't true, then a point has already been selected (maybe by the add point tool) moho.document:PrepUndo(moho.layer) end -- --------------- snip ----------------------------- if (mouseEvent.altKey) then local i = mouseEvent.view:PickPoint(mouseEvent.pt) if (i >= 0) then --pick a point mesh:Point(i).fSelected = not mesh:Point(i).fSelected moho:UpdateSelectedChannels() end end local selectedPt = LM.Vector2:new_local() -- --------------- snip ----------------------------- self.numSel = moho:CountSelectedPoints() if (self.selID == -1) then -- --------------- snip ----------------------------- if ((self.numSel == 1) and (mouseEvent.altKey)) then for i, pt in MOHO.SelectedPointList(mesh) do selectedPt.x = pt.fPos.x selectedPt.y = pt.fPos.y self.selID = mesh:ClosestPoint(selectedPt) end else -- --------------- snip ----------------------------- if (self.numSel < 2) then -- move just a single point -- find the closest point here self.selID = mesh:ClosestPoint(mouseEvent.startVec) if (self.selID >= 0) then self.numSel = 1 mesh:SelectNone() mesh:Point(self.selID).fSelected = true moho:UpdateSelectedChannels() end else self.selID = mesh:ClosestPoint(mouseEvent.startVec) end end end mesh:PrepMovePoints() mouseEvent.view:DrawMe() end function LM_TranslatePoints:OnMouseMoved(moho, mouseEvent) local mesh = moho:Mesh() if (mesh == nil) then return end if (mouseEvent.ctrlKey) then -- hold down the control key to just select a single point and not move it return end local curVec = mouseEvent.vec - mouseEvent.startVec if (mouseEvent.shiftKey) then if (math.abs(mouseEvent.vec.x - mouseEvent.startVec.x) > math.abs(mouseEvent.vec.y - mouseEvent.startVec.y)) then curVec.y = 0 else curVec.x = 0 end end if (self.numSel < 2) then -- just working with 1 point if (self.selID == -1) then return end local pt = mesh:Point(self.selID) pt.fPos:Set(pt.fTempPos + curVec) if (moho.gridOn) then moho:SnapToGrid(pt.fPos) end else -- move multiple points if (moho.gridOn) then if (self.selID > -1) then local pt = mesh:Point(self.selID) local tempVec = pt.fTempPos + curVec moho:SnapToGrid(tempVec) curVec:Set(tempVec - pt.fTempPos) else moho:SnapToGrid(curVec) end end mesh:TranslatePoints(curVec) end moho:AddPointKeyframe(moho.frame) mouseEvent.view:DrawMe() end function LM_TranslatePoints:OnMouseUp(moho, mouseEvent) local mesh = moho:Mesh() if (mesh == nil) then return end -- if we're just moving a single point, then try to auto-weld it if (self.selID >= 0 and self.numSel == 1 and self.autoWeld and not(mouseEvent.altKey)) then if (mesh:Point(self.selID):IsEndpoint()) then if (not LM_TranslatePoints:WeldPoints(moho, mouseEvent.view, self.dragging)) then -- Failed to weld this endpoint to another point. -- Instead, try welding it to the middle of a nearby curve. local m = LM.Matrix:new_local() local v = LM.Vector2:new_local() local pt = LM.Point:new_local() local curveID = -1 local segID = -1 moho.layer:GetFullTransform(moho.frame, m, moho.document) v:Set(mesh:Point(self.selID).fPos) m:Transform(v) mouseEvent.view:Graphics():WorldToScreen(v, pt) curveID, segID = mesh:Point(self.selID):GetEndpointEdge(curveID, segID) curveID, segID = mouseEvent.view:PickEdge(pt, curveID, segID) if (curveID >= 0) then -- add a point in the middle of some curve if (not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID)) then -- don't weld the point back on itself mesh:AddPoint(mesh:Point(self.selID).fPos, curveID, segID, moho.frame) if (mesh:WeldPoints(self.selID, mesh:CountPoints() - 1, moho.frame)) then moho:Click() end end end end end end moho:AddPointKeyframe(moho.frame) moho:NewKeyframe(CHANNEL_POINT) self.selID = -1 self.dragging = false mouseEvent.view:DrawMe() end function LM_TranslatePoints:OnKeyDown(moho, keyEvent) local mesh = moho:Mesh() if (mesh == nil) then return end if (keyEvent.key == ' ') then local doit = false if (self.dragging) then if (self.numSel == 1) then doit = true end else if (moho:CountSelectedPoints() == 1) then doit = true end end if (doit) then -- try welding the selected point to the nearest other point if (not LM_TranslatePoints:WeldPoints(moho, keyEvent.view, self.dragging)) then -- Failed to weld this endpoint to another point. -- Instead, try welding it to the middle of a nearby curve. local seldID = -1 for i = 0, mesh:CountPoints() - 1 do if (mesh:Point(i).fSelected) then selID = i break end end if (selID >= 0) then local m = LM.Matrix:new_local() local v = LM.Vector2:new_local() local pt = LM.Point:new_local() local curveID = -1 local segID = -1 moho.layer:GetFullTransform(moho.frame, m, moho.document) v:Set(mesh:Point(selID).fPos) m:Transform(v) keyEvent.view:Graphics():WorldToScreen(v, pt) curveID, segID = keyEvent.view:PickEdge(pt, curveID, segID) if (curveID >= 0) then -- add a point in the middle of some curve if (not mesh:Curve(curveID):IsPointOnSegment(selID, segID)) then -- don't weld the point back on itself mesh:AddPoint(mesh:Point(selID).fPos, curveID, segID, moho.frame) if (mesh:WeldPoints(selID, mesh:CountPoints() - 1, moho.frame)) then moho:Click() end self.selID = -1 self.numSel = -1 mesh:SelectNone() end end end else self.selID = -1 self.numSel = -1 mesh:SelectNone() end keyEvent.view:DrawMe() end elseif ((keyEvent.keyCode == LM.GUI.KEY_DELETE) or (keyEvent.keyCode == LM.GUI.KEY_BACKSPACE)) then moho.document:PrepUndo(moho.layer) moho.document:SetDirty() MOHO.DeleteSelectedPoints(mesh) keyEvent.view:DrawMe() elseif (keyEvent.ctrlKey) then local inc = 1 if (keyEvent.shiftKey) then inc = 10 end local m = LM.Matrix:new_local() moho.layer:GetFullTransform(moho.frame, m, moho.document) local fakeME = {} fakeME.view = keyEvent.view fakeME.pt = LM.Point:new_local() fakeME.pt:Set(keyEvent.view:Graphics():Width() / 2, keyEvent.view:Graphics():Height() / 2) fakeME.startPt = LM.Point:new_local() fakeME.startPt:Set(fakeME.pt) fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m); fakeME.startVec = keyEvent.view:Point2Vec(fakeME.pt, m); fakeME.shiftKey = false fakeME.ctrlKey = false fakeME.altKey = keyEvent.altKey fakeME.penPressure = 0 if (keyEvent.keyCode == LM.GUI.KEY_UP) then moho.document:PrepUndo(moho.layer) self.selID = self:SelIDForNudge(moho, mesh) if (self.selID == -2) then self.selID = -1 return end self:OnMouseDown(moho, fakeME) fakeME.pt.y = fakeME.pt.y - inc fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m); self:OnMouseMoved(moho, fakeME) self:OnMouseUp(moho, fakeME) elseif (keyEvent.keyCode == LM.GUI.KEY_DOWN) then moho.document:PrepUndo(moho.layer) self.selID = self:SelIDForNudge(moho, mesh) if (self.selID == -2) then self.selID = -1 return end self:OnMouseDown(moho, fakeME) fakeME.pt.y = fakeME.pt.y + inc fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m); self:OnMouseMoved(moho, fakeME) self:OnMouseUp(moho, fakeME) elseif (keyEvent.keyCode == LM.GUI.KEY_LEFT) then moho.document:PrepUndo(moho.layer) self.selID = self:SelIDForNudge(moho, mesh) if (self.selID == -2) then self.selID = -1 return end self:OnMouseDown(moho, fakeME) fakeME.pt.x = fakeME.pt.x - inc fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m); self:OnMouseMoved(moho, fakeME) self:OnMouseUp(moho, fakeME) elseif (keyEvent.keyCode == LM.GUI.KEY_RIGHT) then moho.document:PrepUndo(moho.layer) self.selID = self:SelIDForNudge(moho, mesh) if (self.selID == -2) then self.selID = -1 return end self:OnMouseDown(moho, fakeME) fakeME.pt.x = fakeME.pt.x + inc fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m); self:OnMouseMoved(moho, fakeME) self:OnMouseUp(moho, fakeME) end end end function LM_TranslatePoints:WeldPoints(moho, view, dragging) local mesh = moho:Mesh() if (mesh == nil) then return false end moho.document:SetDirty() -- try welding to the nearest point if (not dragging) then self.selID = -1 self.numSel = 0 for i = 0, mesh:CountPoints() - 1 do local pt = mesh:Point(i) if (pt.fSelected) then self.numSel = self.numSel + 1 self.selID = i if (self.numSel > 1) then break end end end if (self.numSel > 1) then self.selID = -1 end if (self.selID < 0) then return false end moho.document:PrepUndo(moho.layer) end if (self.selID < 0) then return false end local testVec1 = LM.Vector2:new_local() local testVec2 = LM.Vector2:new_local() testVec1:Set(mesh:Point(self.selID).fPos) local closestID = mesh:ClosestPoint(testVec1, self.selID) testVec2:Set(mesh:Point(closestID).fPos) -- convert the two chosen points to pixel coordinates -- this is to make sure that they are actually close enough in screen space local p1 = LM.Point:new_local() local p2 = LM.Point:new_local() local m = LM.Matrix:new_local() moho.layer:GetFullTransform(moho.frame, m, moho.document) m:Transform(testVec1) m:Transform(testVec2) view:Graphics():WorldToScreen(testVec1, p1) view:Graphics():WorldToScreen(testVec2, p2) p1.x = p1.x - p2.x p1.y = p1.y - p2.y closest = (p1.x * p1.x) + (p1.y * p1.y) if (closest < 100) then -- found one close enough to weld if (mesh:ArePointsAdjacent(self.selID, closestID)) then -- this avoids welding adjacent points, particularly the end point of a curve to the previous point return true end if (mesh:WeldPoints(self.selID, closestID, moho.frame)) then moho:Click() self.selID = -1 -- flag to end this interaction end else return false end self.selID = -1 return true end function LM_TranslatePoints:SelIDForNudge(moho, mesh) local id = moho:CountSelectedPoints() if (id < 1) then return -2 elseif (id > 1) then return -1 else for i = 0, mesh:CountPoints() - 1 do if (mesh:Point(i).fSelected) then return i end end end end -- ************************************************** -- Tool options - create and respond to tool's UI -- ************************************************** LM_TranslatePoints.DUMMY = MOHO.MSG_BASE LM_TranslatePoints.CHANGE = MOHO.MSG_BASE + 1 LM_TranslatePoints.RESET = MOHO.MSG_BASE + 2 LM_TranslatePoints.AUTOWELD = MOHO.MSG_BASE + 3 LM_TranslatePoints.SELECTITEM = MOHO.MSG_BASE + 4 LM_TranslatePoints.autoWeld = true function LM_TranslatePoints:DoLayout(moho, layout) self.menu = LM.GUI.Menu("Select Group") self.popup = LM.GUI.PopupMenu(128, false) self.popup:SetMenu(self.menu) layout:AddChild(self.popup) layout:AddChild(LM.GUI.StaticText("Position")) layout:AddChild(LM.GUI.StaticText("X:")) self.textX = LM.GUI.TextControl(0, "00.0000", self.CHANGE, LM.GUI.FIELD_FLOAT) self.textX:SetWheelInc(0.1) layout:AddChild(self.textX) layout:AddChild(LM.GUI.StaticText("Y:")) self.textY = LM.GUI.TextControl(0, "00.0000", self.CHANGE, LM.GUI.FIELD_FLOAT) self.textY:SetWheelInc(0.1) layout:AddChild(self.textY) layout:AddChild(LM.GUI.Button("Reset", self.RESET)) self.autoWeldCheck = LM.GUI.CheckBox("Auto-weld", self.AUTOWELD) layout:AddChild(self.autoWeldCheck) end function LM_TranslatePoints:UpdateWidgets(moho) local mesh = moho:Mesh() if (mesh == nil) then return end MOHO.BuildGroupMenu(self.menu, mesh, self.SELECTITEM, self.DUMMY) local center = mesh:SelectedCenter() self.textX:SetValue(center.x) self.textY:SetValue(center.y) self.autoWeldCheck:SetValue(self.autoWeld) end function LM_TranslatePoints:HandleMessage(moho, view, msg) local mesh = moho:Mesh() if (mesh == nil) then return end if (msg == self.CHANGE) then moho.document:PrepUndo(moho.layer) moho.document:SetDirty() local offset = mesh:SelectedCenter() offset.x = self.textX:FloatValue() - offset.x offset.y = self.textY:FloatValue() - offset.y mesh:PrepMovePoints() mesh:TranslatePoints(offset) moho:AddPointKeyframe(moho.frame) moho:NewKeyframe(CHANNEL_POINT) elseif (msg == self.RESET) then if (moho.frame > 0) then moho.document:PrepUndo(moho.layer) moho.document:SetDirty() for i = 0, mesh:CountPoints() - 1 do local pt = mesh:Point(i) if (pt.fSelected) then pt:SetPos(pt.fAnimPos:GetValue(0), moho.frame) end end end self:UpdateWidgets(moho) moho:NewKeyframe(CHANNEL_POINT) elseif (msg == self.AUTOWELD) then self.autoWeld = self.autoWeldCheck: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