Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************
ScriptName = "SS_MultiLayerTransformPoints"

-- **************************************************
-- Transform points on multiple layers
-- based on: LM_TransformPoints 10.0 (Lost Marble)
-- version:  +01.04 MH12+ #530605
-- by:       Sam Cogheil (SimplSam)
-- **************************************************

SS_MultiLayerTransformPoints = {}

-- SS_MultiLayerTransformPoints.BASE_STR = 2375

function SS_MultiLayerTransformPoints:Name()
    return "Multi-Layer Transform Points"
end

function SS_MultiLayerTransformPoints:Version()
    return "10+01.04 #5306"
end

function SS_MultiLayerTransformPoints:Description()
    return "Move/Scale/Rotate selected points on multiple layers (hold <shift> to constrain, <alt> to scale to center, <shift> while scaling to squash, <ctrl/cmd> to select points)"
end

function SS_MultiLayerTransformPoints:Creator()
    return "Lost Marble LLC + Sam Cogheil (SimplSam)"
end

function SS_MultiLayerTransformPoints:UILabel()
    return ("Multi-Layer Transform Points")
    -- return (MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPoints=Multi-Layer Transform Points"))
end

function SS_MultiLayerTransformPoints:LoadPrefs(prefs)
    self.autoWeld = prefs:GetBool("SS_MultiLayerTransformPoints.autoWeld", false)
    self.autoFill = prefs:GetBool("SS_MultiLayerTransformPoints.autoFill", false)
    self.autoStroke = prefs:GetBool("SS_MultiLayerTransformPoints.autoStroke", false)
    self.showHandles = prefs:GetBool("SS_MultiLayerTransformPoints.showHandles", false)
    self.fixedHandles = prefs:GetBool("SS_MultiLayerTransformPoints.fixedHandles", false)
end

function SS_MultiLayerTransformPoints:SavePrefs(prefs)
    prefs:SetBool("SS_MultiLayerTransformPoints.autoWeld", self.autoWeld)
    prefs:SetBool("SS_MultiLayerTransformPoints.autoFill", self.autoFill)
    prefs:SetBool("SS_MultiLayerTransformPoints.autoStroke", self.autoStroke)
    prefs:SetBool("SS_MultiLayerTransformPoints.showHandles", self.showHandles)
    prefs:SetBool("SS_MultiLayerTransformPoints.fixedHandles", self.fixedHandles)
end

function SS_MultiLayerTransformPoints:ResetPrefs()
    SS_MultiLayerTransformPoints.autoWeld = false
    SS_MultiLayerTransformPoints.autoWeldRadius = 12
    SS_MultiLayerTransformPoints.autoFill = false
    SS_MultiLayerTransformPoints.autoStroke = false
    SS_MultiLayerTransformPoints.showHandles = false
    SS_MultiLayerTransformPoints.fixedHandles = true
end

function SS_MultiLayerTransformPoints:ColorizeIcon()
    return true
end

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

function SS_MultiLayerTransformPoints:HideConstructionCurves(moho)
    return false -- true
end

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

SS_MultiLayerTransformPoints.dragging = false
SS_MultiLayerTransformPoints.keyMovement = false
SS_MultiLayerTransformPoints.mode = 0 -- 0:translate, 1:rotate, 2:top left scale, 3:top right scale, 4: bottom left scale, 5: bottom right scale, 6:left scale, 7:right scale, 8:top scale, 9:bottom scale, 10:bezier handle, 11:pivot offset, 100:select points
SS_MultiLayerTransformPoints.MODE_TRANS = 0
SS_MultiLayerTransformPoints.MODE_ROTATE = 1
SS_MultiLayerTransformPoints.MODE_SCALE_ = 2
SS_MultiLayerTransformPoints.MODE_SCALE_TOPLFT = 2
SS_MultiLayerTransformPoints.MODE_SCALE_TOPRGT = 3
SS_MultiLayerTransformPoints.MODE_SCALE_BOTLFT = 4
SS_MultiLayerTransformPoints.MODE_SCALE_BOTRGT = 5
SS_MultiLayerTransformPoints.MODE_SCALE_LEFT = 6
SS_MultiLayerTransformPoints.MODE_SCALE_RIGHT = 7
SS_MultiLayerTransformPoints.MODE_SCALE_TOP = 8
SS_MultiLayerTransformPoints.MODE_SCALE_BOT = 9
SS_MultiLayerTransformPoints.MODE_SCALE_MAX_ = 9
SS_MultiLayerTransformPoints.MODE_BEZIER = 10
SS_MultiLayerTransformPoints.MODE_PIVOT = 11
SS_MultiLayerTransformPoints.MODE_SELECT = 100
SS_MultiLayerTransformPoints.numSel = 0
SS_MultiLayerTransformPoints.selID = -1
SS_MultiLayerTransformPoints.fillingShape = false
SS_MultiLayerTransformPoints.NumNotSet = -10000000
SS_MultiLayerTransformPoints.endWeldVec = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.endWeldVec:Set(SS_MultiLayerTransformPoints.NumNotSet, SS_MultiLayerTransformPoints.NumNotSet)
SS_MultiLayerTransformPoints.endWeldToPoint = true
SS_MultiLayerTransformPoints.startAngle = 0
SS_MultiLayerTransformPoints.lastVec = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.lastVecPt = LM.Point:new_local()
SS_MultiLayerTransformPoints.pivotCenterVec = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.pivotCenterVec:Set(SS_MultiLayerTransformPoints.NumNotSet, SS_MultiLayerTransformPoints.NumNotSet)
SS_MultiLayerTransformPoints.lastSelectedCount = 0
SS_MultiLayerTransformPoints.pivotOffset = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.markerR = 8

SS_MultiLayerTransformPoints.selMesh = nil
SS_MultiLayerTransformPoints.selLayer = nil
SS_MultiLayerTransformPoints.selCenter = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.selCenter3z = LM.Vector3:new_local()
SS_MultiLayerTransformPoints.selectedMin = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.selectedMax = LM.Vector2:new_local()
SS_MultiLayerTransformPoints.centerVecPt = LM.Point:new_local()
SS_MultiLayerTransformPoints.offsetCenterPt = LM.Point:new_local()
SS_MultiLayerTransformPoints.selCenterIsDirty = true
SS_MultiLayerTransformPoints.mmeshPickWidth = 6
SS_MultiLayerTransformPoints.allowShapePicking = true
SS_MultiLayerTransformPoints.lastFrame = SS_MultiLayerTransformPoints.NumNotSet
SS_MultiLayerTransformPoints.multiMeshes = {}
SS_MultiLayerTransformPoints.multiSelectedPointsCount = -1
SS_MultiLayerTransformPoints.AltKeyCode1 = 92 -- backslash
SS_MultiLayerTransformPoints.AltKeyCode2 = 96 -- backtick

math.atan2 = math.atan2 or math.atan

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

function SS_MultiLayerTransformPoints:IsEnabled(moho)
    self.selCenterIsDirty = true
    return true
end

function SS_MultiLayerTransformPoints:IsRelevant(moho)
    local mesh = moho:DrawingMesh()
    if (mesh or moho.layer:IsGroupType()) then
        self.numSel = self:MultimeshCountSelectedPoints(moho, true)
        self.selCenterIsDirty = true
        return true
    else
        return false
    end
end

function SS_MultiLayerTransformPoints:MultimeshCountPoints(moho)
    local _count = 0
    for _, mmesh in pairs(self.multiMeshes) do
        _count = _count + mmesh.mesh:CountPoints()
    end
    return _count
end

function SS_MultiLayerTransformPoints:MultimeshCountSelectedPoints(moho, doRecalc)
    local function MeshCountSelectedPoints(_mesh)
        local count = 0
        for i = 0, _mesh:CountPoints() - 1 do
            count = count + (_mesh:Point(i).fSelected and 1 or 0)
        end
        return count
    end

    if (doRecalc or (self.multiSelectedPointsCount == -1)) then
        self.multiMeshes = {}
        self.multiLayersCount = 0
        self.multiSelectedPointsCount = 0
        self.multiPointsCount = 0

        local function ProcessLayer(_layer)
            if (_layer:IsGroupType()) then
                local groupLayer = moho:LayerAsGroup(_layer)
                for j = 0, groupLayer:CountLayers() - 1 do
                    ProcessLayer(groupLayer:Layer(j))
                end
            else
                if (_layer:LayerType() == MOHO.LT_VECTOR) and (not self.multiMeshes[moho.document:LayerAbsoluteID(_layer) + 1]) then
                    local mmesh = {}
                    mmesh.layer = _layer
                    mmesh.mesh = moho:LayerAsVector(mmesh.layer):Mesh()
                    mmesh.pointsCount = mmesh.mesh:CountPoints()
                    if (mmesh.pointsCount > 0) then
                        self.multiLayersCount = self.multiLayersCount + 1
                        mmesh.selectedPointsCount = MeshCountSelectedPoints(mmesh.mesh)
                        self.multiMeshes[moho.document:LayerAbsoluteID(mmesh.layer) + 1] = mmesh
                        self.multiSelectedPointsCount = self.multiSelectedPointsCount + mmesh.selectedPointsCount
                    end
                    self.multiPointsCount = self.multiPointsCount + mmesh.pointsCount
                end
            end
        end

        for i = 0, moho.document:CountSelectedLayers() - 1 do
            ProcessLayer(moho.document:GetSelectedLayer(i))
        end
    end
    return self.multiSelectedPointsCount
end

-- Find nearesrt point - in 'Group children + Selected layers'
function SS_MultiLayerTransformPoints:MultimeshPickClosestPoint(moho, pt, pickWidth) -- pt not vec
    local lastMag = pickWidth or 10000000
    lastMag = lastMag + 1
    local lastPointID = -1
    local lastLayer, lastMesh
    local m = LM.Matrix:new_local()
    for _, mmesh in pairs(self.multiMeshes) do
        mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
        local v = moho.view:Point2Vec(pt, m)
        local pointID = mmesh.mesh:ClosestPoint(v)
        if (pointID ~= -1) then
            local point = mmesh.mesh:Point(pointID)
            local ptPos = LM.Point:new_local()
            local vcPos = LM.Vector2:new_local()
            vcPos:Set(point.fPos)
            m:Transform(vcPos)
            moho.view:Graphics():WorldToScreen(vcPos, ptPos)
            local mag = math.abs(ptPos.x - pt.x) + math.abs(ptPos.y - pt.y)
            if (mag < lastMag) then
                lastPointID = pointID
                lastMesh = mmesh.mesh
                lastLayer = mmesh.layer
                lastMag = mag
            end
        end
    end
    return lastLayer, lastMesh, lastPointID, lastMag
end

function SS_MultiLayerTransformPoints:MultimeshMovePoints(moho, offset)
    if (self.multiSelectedPointsCount < 1) then
        return
    end

    local m = LM.Matrix:new_local()
    local vec = LM.Vector2:new_local()
    local vec3zFrom = LM.Vector3:new_local()
    local vec3zTo = LM.Vector3:new_local()

    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            if (self:UseFixedHandles(moho)) then
                mmesh.mesh:PrepFixedHandles()
            end

            mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
            m:Invert()

            vec3zFrom:Set(self.centerVec3z)
            vec3zTo:Set(self.centerVec3z.x + offset.x, self.centerVec3z.y + offset.y, self.centerVec3z.z)

            if (moho.gridOn) and (self.multiSelectedPointsCount == 1) then
                local snapVec = LM.Vector2:new_local()
                snapVec:Set(vec3zTo.x, vec3zTo.y)
                moho:SnapToGrid(snapVec)
                vec3zTo.x, vec3zTo.y = snapVec.x, snapVec.y
            end

            m:Transform(vec3zFrom)
            m:Transform(vec3zTo)
            vec:Set(vec3zTo.x - vec3zFrom.x, vec3zTo.y - vec3zFrom.y)

            mmesh.mesh:TranslatePoints(vec)

            moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer, true) -- MOHO.MohoGlobals.EditMultipleKeys)
            if (self:UseFixedHandles(moho)) then
                mmesh.mesh:PreserveHandlePositions()
            end
        end
    end
end

function SS_MultiLayerTransformPoints:MultimeshSelectedMeshBounds(moho, frame, view)
    local bbox = LM.BBox:new_local()
    local vec = LM.Vector2:new_local()
    local m = LM.Matrix:new_local()

    -- pre-Transform
    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            mmesh.layer:GetFullTransform(frame, m, moho.document)
            for i = 0, mmesh.mesh:CountPoints() - 1 do
                if (mmesh.mesh:Point(i).fSelected) then
                    vec:Set(mmesh.mesh:Point(i).fPos)
                    m:Transform(vec)
                    bbox:AccumulatePoint(vec)
                end
            end
        end
    end

    -- make sure the bounding box is not too thin in one direction
    local xLen = bbox.fMax.x - bbox.fMin.x
    local yLen = bbox.fMax.y - bbox.fMin.y
    local center
    if (xLen < yLen / 10.0) then
        center = (bbox.fMin.x + bbox.fMax.x) / 2.0
        bbox.fMin.x = center - yLen / 10.0
        bbox.fMax.x = center + yLen / 10.0
    elseif (yLen < xLen / 10.0) then
        center = (bbox.fMin.y + bbox.fMax.y) / 2.0
        bbox.fMin.y = center - xLen / 10.0
        bbox.fMax.y = center + xLen / 10.0
    end

    -- make sure the bounding box is not too small
    local minLength = 150
    local v = LM.Vector2:new_local()
    local pt1 = LM.Point:new_local()
    local pt2 = LM.Point:new_local()

    v:Set(bbox.fMin.x, bbox.fMin.y)
    view:Graphics():WorldToScreen(v, pt1)
    v:Set(bbox.fMax.x, bbox.fMax.y)
    view:Graphics():WorldToScreen(v, pt2)

    pt1 = pt2 - pt1
    local length = math.sqrt(pt1.x * pt1.x + pt1.y * pt1.y)
    if (length < minLength) then
        center = (bbox.fMin + bbox.fMax) / 2.0
        v = bbox.fMax - center
        bbox.fMax = center + v * (minLength / length) / 2.0
        v = bbox.fMin - center
        bbox.fMin = center + v * (minLength / length) / 2.0
    end

    return bbox
end

function SS_MultiLayerTransformPoints:MultimeshTestMousePoint(moho, mouseEvent)
    -- Returns what mode the tool would be in if the user clicked at the current mouse location
    self.numSel = self:MultimeshCountSelectedPoints(moho) -- moho:CountSelectedPoints()
    if (self.numSel < 2) then
        return self.MODE_TRANS
    end

    local bbox = self:MultimeshSelectedMeshBounds(moho, moho.frame, mouseEvent.view)
    local v = LM.Vector2:new_local()
    local pt = LM.Point:new_local()

    -- #SC test for Pivot first (priority)
    -- test for pivot point
    if (self.dragging) then
        v:Set(self.centerVec)
    else
        v = self:MultimeshSelectedCenter(moho) + self.pivotOffset
    end
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_PIVOT
    end

    -- test for uniform scaling
    v:Set(bbox.fMin.x, bbox.fMin.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_BOTLFT
    end

    v:Set(bbox.fMin.x, bbox.fMax.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_TOPLFT
    end

    v:Set(bbox.fMax.x, bbox.fMax.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_TOPRGT
    end

    v:Set(bbox.fMax.x, bbox.fMin.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_BOTRGT
    end

    -- test for X scaling
    v:Set(bbox.fMin.x, (bbox.fMin.y + bbox.fMax.y) * 0.5)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_LEFT
    end

    v:Set(bbox.fMax.x, (bbox.fMin.y + bbox.fMax.y) * 0.5)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_RIGHT
    end

    -- test for Y scaling
    v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMin.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_BOT
    end

    v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMax.y)
    mouseEvent.view:Graphics():WorldToScreen(v, pt)
    if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
        return self.MODE_SCALE_TOP
    end

    -- test for translation outside the bounding box
    mouseEvent.view:Graphics():ScreenToWorld(mouseEvent.pt, v)

    local rotWidth = bbox.fMax.x - bbox.fMin.x
    if (bbox.fMax.y - bbox.fMin.y > rotWidth) then
        rotWidth = bbox.fMax.y - bbox.fMin.y
    end
    rotWidth = rotWidth * 0.1
    if (v.x < bbox.fMin.x - rotWidth or v.x > bbox.fMax.x + rotWidth or v.y < bbox.fMin.y - rotWidth or v.y > bbox.fMax.y + rotWidth) then -- #SC
        return self.MODE_TRANS
    end

    -- test for rotation
    if (v.x < bbox.fMin.x or v.x > bbox.fMax.x or v.y < bbox.fMin.y or v.y > bbox.fMax.y) then -- #SC
        return self.MODE_ROTATE
    end

    return self.MODE_TRANS -- translation inside the bounding box
end

function SS_MultiLayerTransformPoints:MultimeshSelectNone()
    for _, mmesh in pairs(self.multiMeshes) do
        mmesh.mesh:SelectNone()
    end
    self.multiSelectedPointsCount = 0
end

function SS_MultiLayerTransformPoints:MultimeshSelectAll(moho)
    for _, mmesh in pairs(self.multiMeshes) do
        mmesh.mesh:SelectAll()
    end
    self:MultimeshSelectedCenter(moho, true)
end

function SS_MultiLayerTransformPoints:MultimeshSelectConnected(moho)
    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            mmesh.mesh:SelectConnected()
        end
    end
    self:MultimeshSelectedCenter(moho, true)
end

function SS_MultiLayerTransformPoints:MultimeshSelectedBounds(moho)
    local bbox = LM.BBox:new_local()
    local m = LM.Matrix:new_local()
    local v3 = LM.Vector3:new_local()

    -- pre-Transform + also reCount points/selected
    self.multiPointsCount = 0
    self.multiSelectedPointsCount = 0
    for _, mmesh in pairs(self.multiMeshes) do
        mmesh.selectedPointsCount = 0
        mmesh.layer:GetFullTransform(moho.drawingFrame, m, moho.document)
        mmesh.pointsCount = mmesh.mesh:CountPoints()
        for i = 0, mmesh.pointsCount - 1 do
            if (mmesh.mesh:Point(i).fSelected) then
                v3:Set(mmesh.mesh:Point(i).fPos.x, mmesh.mesh:Point(i).fPos.y, 0)
                m:Transform(v3)
                bbox:AccumulatePoint(v3)
                mmesh.selectedPointsCount = mmesh.selectedPointsCount + 1
            end
        end
        self.multiPointsCount = self.multiPointsCount + mmesh.pointsCount
        self.multiSelectedPointsCount = self.multiSelectedPointsCount + mmesh.selectedPointsCount
    end
    return bbox -- 3D
end

function SS_MultiLayerTransformPoints:MultimeshSelectedCenter(moho, doRecalc) -- #SC called in Draw
    if (doRecalc or self.selCenterIsDirty or (moho.drawingFrame ~= self.lastFrame)) then
        self.selBBox = self:MultimeshSelectedBounds(moho)
        self.selCenter:Set(self.selBBox:Center2D())
        self.selCenter3z:Set(self.selBBox:Center())
        self.selCenterIsDirty = false
        self.lastFrame = moho.drawingFrame
    end
    return self.selCenter, self.selCenter3z, self.selBBox
end

function SS_MultiLayerTransformPoints:MultimeshSelectOnMouseDown(moho, mouseEvent)

    self.selCenterIsDirty = true

    LM_SelectPoints.isMouseDragging = true
    local lassoMode = false
    LM_SelectPoints.ctrlKeySelection = false
    if (LM_SelectPoints.lassoMode) then
        if (not (mouseEvent.ctrlKey)) then
            lassoMode = true
        else
            LM_SelectPoints.ctrlKeySelection = true
        end
    else
        if (mouseEvent.ctrlKey) then
            lassoMode = true
            LM_SelectPoints.ctrlKeySelection = true
        end
    end

    LM_SelectPoints.selMode = LM_SelectPoints.SELMODE_GROUP_PTS
    if (not mouseEvent.shiftKey and not mouseEvent.altKey) then
        self:MultimeshSelectNone() -- #SC Only 1 lined changed
    end

    if (LM_SelectPoints.selMode == LM_SelectPoints.SELMODE_GROUP_PTS) then
        if (lassoMode) then
            LM_SelectPoints.lassoList = {{mouseEvent.startPt.x, mouseEvent.startPt.y}}
            LM_SelectPoints.previousX = mouseEvent.startPt.x
            LM_SelectPoints.previousY = mouseEvent.startPt.y
        else
            LM_SelectPoints.selRect.left = mouseEvent.startPt.x
            LM_SelectPoints.selRect.top = mouseEvent.startPt.y
            LM_SelectPoints.selRect.right = mouseEvent.pt.x
            LM_SelectPoints.selRect.bottom = mouseEvent.pt.y
            mouseEvent.view:Graphics():SelectionRect(LM_SelectPoints.selRect)
        end
    end
    mouseEvent.view:DrawMe()
end

function SS_MultiLayerTransformPoints:OnMouseDown(moho, mouseEvent)

    self.dragging = true
    self.endWeldVec:Set(self.NumNotSet, self.NumNotSet)
    self.pivotCenterVec:Set(self.NumNotSet, self.NumNotSet)
    self.centerVec = self:MultimeshSelectedCenter(moho)
    self.centerVec = self.centerVec + self.pivotOffset
    self.startPivotOffset = LM.Vector2:new_local()
    self.startPivotOffset:Set(self.pivotOffset)
    self.startPivotOffsetPt = LM.Point:new_local() -- #SC
    mouseEvent.view:Graphics():WorldToScreen(self.startPivotOffset, self.startPivotOffsetPt)
    mouseEvent.view:Graphics():WorldToScreen(self.centerVec, self.centerVecPt)

    self.mode = self:MultimeshTestMousePoint(moho, mouseEvent)
    if (mouseEvent.ctrlKey) then
        self.mode = self.MODE_SELECT
        mouseEvent.ctrlKey = false
        self:MultimeshSelectOnMouseDown(moho, mouseEvent) -- LM_SelectPoints:OnMouseDown(moho, mouseEvent)
        return
    end

    local bbox = self:MultimeshSelectedBounds(moho)
    self.selectedMin, self.selectedMax = bbox.fMin, bbox.fMax
    self.selectedMin.y, self.selectedMax.y = self.selectedMax.y, self.selectedMin.y -- make min Y at Top
    moho.document:SetDirty()
    self.numSel = self:MultimeshCountSelectedPoints(moho, true) -- self.numSel = moho:CountSelectedPoints(true)

    if (self.selID == -1) then -- if this isn't true, then a point has already been selected (maybe by the add point tool)
        -- if (self.mode == self.MODE_TRANS and self.numSel < 2 and self.showHandles) then -- #SC
        --     LM_Curvature:TestForClosestHandle(moho, mouseEvent)
        --     if (LM_Curvature.selID >= 0 and LM_Curvature.handleSide ~= 0) then -- a bezier handle was selected
        --         self.mode = self.MODE_BEZIER
        --         local point = mesh:Point(LM_Curvature.selID)
        --         if (point:IsEndpoint()) then
        --             -- If working with a curve endpoint, and its handles have not been extended, don't do it with this tool.
        --             -- It's preferable to just move the endpoint.
        --             -- If the user wants to move the handle, they must use the curvature tool.
        --             local curve = nil
        --             local ptPos = -1
        --             curve, ptPos = point:Curve(0, ptPos)
        --             local weight = curve:GetWeight(ptPos, moho.drawingFrame, LM_Curvature.handleSide == -1)
        --             local offset = curve:GetOffset(ptPos, moho.drawingFrame, LM_Curvature.handleSide == -1)
        --             local wDiff = weight - 1.0
        --             local oDiff = offset - 0.0
        --             if (math.abs(wDiff) < 0.0001 and math.abs(oDiff) < 0.0001 and curve:CountPoints() > 2) then
        --                 self.mode = self.MODE_TRANS
        --             end
        --         end
        --     end
        --     self.selID = -1
        --     self.handleSide = 0
        -- end
        -- if (self.mode ~= self.MODE_PIVOT) then
        moho.document:PrepUndo()
        -- end
    end

    if (self.mode == self.MODE_TRANS) then -- translate
        if (self.selID == -1) then
            mouseEvent.shiftKey = mouseEvent.altKey
            mouseEvent.ctrlKey = mouseEvent.altKey
            self.numSel = self:MultimeshCountSelectedPoints(moho) -- self.numSel = moho:CountSelectedPoints(true)
            if (self.numSel < 2) then -- move just a single point
                local pickWidth
                if (self.numSel < 1) then -- else nil
                    pickWidth = self.mmeshPickWidth * 10
                end
                self.selLayer, self.selMesh, self.selID = self:MultimeshPickClosestPoint(moho, mouseEvent.startPt, pickWidth)
                if (self.selMesh and self.selID >= 0) then
                    self.numSel = 1
                    self:MultimeshSelectNone()
                    self.selMesh:Point(self.selID).fSelected = true
                    self.numSel = self:MultimeshCountSelectedPoints(moho, true)
                else
                    self.mode = self.MODE_SELECT
                    self:MultimeshSelectOnMouseDown(moho, mouseEvent)
                    return
                end

            else
                self.selLayer, self.selMesh, self.selID = self:MultimeshPickClosestPoint(moho, mouseEvent.startPt)
            end
        end

    elseif (self.mode == self.MODE_ROTATE) then -- rotate
        mouseEvent.shiftKey = mouseEvent.altKey
        mouseEvent.ctrlKey = mouseEvent.altKey
        self.numSel = self:MultimeshCountSelectedPoints(moho, true)
        self.startAngle = 0
        self.lastVec:Set(mouseEvent.drawingVec)
        self.lastVecPt:Set(mouseEvent.pt)

    elseif (self.mode == self.MODE_BEZIER) then -- bezier handle
        LM_Curvature:OnMouseDown(moho, mouseEvent)
        return

    elseif (self.mode == self.MODE_PIVOT) then -- pivot point
        self.numSel = self:MultimeshCountSelectedPoints(moho, true)
        self.lastVec:Set(mouseEvent.drawingVec)

    else -- scale
        self.lastScaleX = 1.0
        self.lastScaleY = 1.0

        if (self.numSel < 2) then
            mouseEvent.shiftKey = mouseEvent.altKey
            mouseEvent.ctrlKey = mouseEvent.altKey
            self.numSel = self:MultimeshCountSelectedPoints(moho, true)
        end
        if (self.numSel < 2) then
            mouseEvent.view:DrawMe()
            return
        end

        local selectedMaxPt = LM.Point:new_local()
        local selectedMinPt = LM.Point:new_local()
        local center = LM.Vector2:new_local()

        if (mouseEvent.altKey or self.isPivotSet) then
            center:Set(self.centerVec3z.x + self.pivotOffset.x, self.centerVec3z.y + self.pivotOffset.y)
            mouseEvent.view:Graphics():WorldToScreen(center, self.offsetCenterPt)
            mouseEvent.altKey = true

        else
            mouseEvent.view:Graphics():WorldToScreen(self.selectedMin, selectedMinPt)
            mouseEvent.view:Graphics():WorldToScreen(self.selectedMax, selectedMaxPt)

            if (self.mode == self.MODE_SCALE_LEFT) then -- LEFT
                self.offsetCenterPt:Set(selectedMaxPt.x, (selectedMinPt.y + selectedMaxPt.y) / 2.0)
            elseif (self.mode == self.MODE_SCALE_RIGHT) then -- RIGHT
                self.offsetCenterPt:Set(selectedMinPt.x, (selectedMinPt.y + selectedMaxPt.y) / 2.0)
            elseif (self.mode == self.MODE_SCALE_TOP) then -- TOP
                self.offsetCenterPt:Set((selectedMinPt.x + selectedMaxPt.x) / 2.0, selectedMaxPt.y)
            elseif (self.mode == self.MODE_SCALE_BOT) then -- BOTTOM
                self.offsetCenterPt:Set((selectedMinPt.x + selectedMaxPt.x) / 2.0, selectedMinPt.y)

            elseif (self.mode == self.MODE_SCALE_BOTLFT) then -- BL
                self.offsetCenterPt:Set(selectedMaxPt.x, selectedMinPt.y)
            elseif (self.mode == self.MODE_SCALE_TOPLFT) then -- TL
                self.offsetCenterPt:Set(selectedMaxPt.x, selectedMaxPt.y)
            elseif (self.mode == self.MODE_SCALE_TOPRGT) then -- TR
                self.offsetCenterPt:Set(selectedMinPt.x, selectedMaxPt.y)
            elseif (self.mode == self.MODE_SCALE_BOTRGT) then -- BR
                self.offsetCenterPt:Set(selectedMinPt.x, selectedMinPt.y)
            end
        end
    end

    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            mmesh.mesh:PrepMovePoints()
        end
    end

    mouseEvent.view:DrawMe()
    moho:UpdateUI()
end

function SS_MultiLayerTransformPoints:MultimeshSelectOnMouseMoved(moho, mouseEvent)
    local lassoMode = false
    if (LM_SelectPoints.lassoMode) then
        if (not (LM_SelectPoints.ctrlKeySelection)) then
            lassoMode = true
        end
    else
        if (LM_SelectPoints.ctrlKeySelection) then
            lassoMode = true
        end
    end

    if (LM_SelectPoints.selMode == LM_SelectPoints.SELMODE_GROUP_PTS) then
        if (lassoMode) then
            local g = mouseEvent.view:Graphics()

            g:SetSmoothing(true)
            g:Push()
            local m = g:CurrentTransform()
            m:Invert()
            g:ApplyMatrix(m)
            g:SetColor(MOHO.MohoGlobals.SelCol)
            g:MoveTo(LM_SelectPoints.previousX, LM_SelectPoints.previousY)
            g:LineTo(mouseEvent.pt.x, mouseEvent.pt.y)
            g:Pop()
            g:SetSmoothing(false)
            mouseEvent.view:RefreshView()

            table.insert(LM_SelectPoints.lassoList, {mouseEvent.pt.x, mouseEvent.pt.y})
            LM_SelectPoints.previousX = mouseEvent.pt.x
            LM_SelectPoints.previousY = mouseEvent.pt.y
        else
            mouseEvent.view:Graphics():SelectionRect(LM_SelectPoints.selRect)
            LM_SelectPoints.selRect.right = mouseEvent.pt.x
            LM_SelectPoints.selRect.bottom = mouseEvent.pt.y
            mouseEvent.view:Graphics():SelectionRect(LM_SelectPoints.selRect)
            mouseEvent.view:RefreshView()
        end
        mouseEvent.view:DrawMe()
    end
end

function SS_MultiLayerTransformPoints:OnMouseMoved(moho, mouseEvent)

    if (not self.dragging) then
        self.centerVec, self.centerVec3z = self:MultimeshSelectedCenter(moho)
        local mode = self:MultimeshTestMousePoint(moho, mouseEvent)

        if (mode == self.MODE_TRANS) then
            if (self.numSel == 0) then
                local _, _, selID = self:MultimeshPickClosestPoint(moho, mouseEvent.pt, self.mmeshPickWidth * 10)
                if (selID >= 0) then
                    mouseEvent.view:SetCursor(MOHO.moveCursor)
                else
                    mouseEvent.view:SetCursor(MOHO.crosshairCursor)
                    return
                end
            else
                mouseEvent.view:SetCursor(MOHO.moveCursor)
            end

        elseif (mode == self.MODE_ROTATE) then
            mouseEvent.view:SetCursor(MOHO.rotateCursor)
        elseif (mode == self.MODE_PIVOT) then
            mouseEvent.view:SetCursor(MOHO.crosshairCursor)
        else
            mouseEvent.view:SetCursor(MOHO.scaleCursor)
        end
        mouseEvent.view:DrawMe()
        return
    end

    if (self.mode == self.MODE_SELECT) then
        mouseEvent.ctrlKey = false
        self:MultimeshSelectOnMouseMoved(moho, mouseEvent) -- LM_SelectPoints:OnMouseMoved(moho, mouseEvent)
    elseif (self.mode == self.MODE_TRANS) then
        self:OnMouseMoved_T(moho, mouseEvent)
    elseif (self.mode == self.MODE_ROTATE) then
        self:OnMouseMoved_R(moho, mouseEvent)
    elseif (self.mode == self.MODE_BEZIER) then
        LM_Curvature:OnMouseMoved(moho, mouseEvent)
        return
    elseif (self.mode == self.MODE_PIVOT) then
        self:OnMouseMoved_P(moho, mouseEvent)
    else -- scale
        self:OnMouseMoved_S(moho, mouseEvent)
    end
end

function SS_MultiLayerTransformPoints:OnMouseMoved_T(moho, mouseEvent)

    if (mouseEvent.ctrlKey) then -- hold down the control key to just select a single point and not move it
        return
    end

    self:MultimeshCountSelectedPoints(moho) -- ?
    local curVec3z = LM.Vector3:new_local()
    local startVec3z = LM.Vector3:new_local()
    local m = LM.Matrix:new_local()

    startVec3z:Set(mouseEvent.drawingStartVec.x, mouseEvent.drawingStartVec.y, 0)
    curVec3z:Set(mouseEvent.drawingVec.x, mouseEvent.drawingVec.y, 0)
    moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
    m:Transform(startVec3z)
    m:Transform(curVec3z)

    if (mouseEvent.shiftKey) then
        if (math.abs(curVec3z.x - startVec3z.x) > math.abs(curVec3z.y - startVec3z.y)) then
            curVec3z.y = startVec3z.y
        else
            curVec3z.x = startVec3z.x
        end
    end

    if (moho.gridOn) and (self.multiSelectedPointsCount > 1) then
        local snapVec = LM.Vector2:new_local()
        snapVec:Set(curVec3z.x, curVec3z.y)
        moho:SnapToGrid(snapVec)
        curVec3z.x, curVec3z.y = snapVec.x, snapVec.y
    end

    local mouseDist = math.abs(mouseEvent.pt.x - mouseEvent.startPt.x) + math.abs(mouseEvent.pt.y - mouseEvent.startPt.y)
    if (mouseDist >= 4) then
        self:MultimeshMovePoints(moho, curVec3z - startVec3z)
    end

    mouseEvent.view:DrawMe()
end

function SS_MultiLayerTransformPoints:OnMouseMoved_S(moho, mouseEvent)
    if (self.numSel < 2) then
        return
    end

    local center = LM.Vector2:new_local()
    local scaling = LM.Vector2:new_local()
    local p1 = mouseEvent.startPt - self.offsetCenterPt -- self.centerVecPt
    local p2 = mouseEvent.pt - self.offsetCenterPt -- self.centerVecPt
    scaling.x = p2.x / p1.x
    scaling.y = p2.y / p1.y

    if (self.mode == 6 or self.mode == 7) then -- horizontal scaling
        scaling.y = 1
        if (mouseEvent.shiftKey) then
            scaling.y = 1 / scaling.x
        end
    elseif (self.mode == 8 or self.mode == 9) then -- vertical scaling
        scaling.x = 1
        if (mouseEvent.shiftKey) then
            scaling.x = 1 / scaling.y
        end
    else
        if (not mouseEvent.shiftKey) then
            scaling.x = (scaling.x + scaling.y) / 2
            scaling.y = scaling.x
        end
    end

    local flip = false
    if (scaling.x * self.lastScaleX < -0.0001) and (scaling.y * self.lastScaleY > 0.0001) then
        flip = true
    elseif (scaling.y * self.lastScaleY < -0.0001) and (scaling.x * self.lastScaleX > 0.0001) then
        flip = true
    end

    local mInv = LM.Matrix:new_local()
    local vec = LM.Vector2:new_local()
    local vec3 = LM.Vector3:new_local()
    local lVec = LM.Vector2:new_local()
    local scaleVec = LM.Vector2:new_local()
    local angle

    moho.view:Graphics():ScreenToWorld(self.offsetCenterPt, center)
    self.pivotCenterVec:Set(center)

    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            scaleVec:Set(scaling)
            mmesh.layer:GetFullTransform(moho.frame, mInv, moho.document)
            mInv:Invert()

            lVec:Set(1, 0)
            mInv:Transform(lVec)
            angle = math.deg(math.atan2(lVec.y, lVec.x))
            if ((angle > 45) and (angle < 135)) or ((angle < -45) and ((angle > -135))) then
                scaleVec.x, scaleVec.y = scaleVec.y, scaleVec.x
            end

            vec3:Set(center.x, center.y, self.centerVec3z.z)
            mInv:Transform(vec3)
            vec:Set(vec3.x, vec3.y)
            mmesh.mesh:ScalePoints(scaleVec.x, scaleVec.y, vec, flip)

            moho:AddPointKeyframe(moho.frame, mmesh.layer, MOHO.MohoGlobals.EditMultipleKeys)
            if (self:UseFixedHandles(moho)) then
                mmesh.mesh:PreserveHandlePositions()
            end
        end
    end

    if (mouseEvent.altKey) then
        self.pivotOffset = self.pivotCenterVec - self:MultimeshSelectedCenter(moho) -- , true)

    else
        local v = self.centerVec - center
        v.x = v.x * scaling.x
        v.y = v.y * scaling.y
        v = v + center
        self.pivotOffset = v - self:MultimeshSelectedCenter(moho, true)
    end

    if (flip) then
        self.lastScaleX = scaling.x
        self.lastScaleY = scaling.y
    end

    mouseEvent.view:DrawMe()
end

function SS_MultiLayerTransformPoints:OnMouseMoved_R(moho, mouseEvent)

    if (self.numSel < 2) then
        return
    end

    local v1, v2 = LM.Vector2:new_local(), LM.Vector2:new_local()
    local angle = self.startAngle
    local p1 = self.lastVecPt - self.centerVecPt
    local p2 = mouseEvent.pt - self.centerVecPt
    v1:Set(p1.x, -p1.y)
    v2:Set(p2.x, -p2.y)
    v2:Rotate(-math.atan2(v1.y, v1.x))
    angle = angle + math.atan2(v2.y, v2.x)

    self.startAngle = angle
    if (mouseEvent.shiftKey) then
        angle = angle / (math.pi / 8)
        angle = (math.pi / 8) * LM.Round(angle)
    end

    local mInv = LM.Matrix:new_local()
    local vec3 = LM.Vector3:new_local()
    local vec = LM.Vector2:new_local()
    self.pivotCenterVec:Set(self.selCenter3z.x + self.pivotOffset.x, self.selCenter3z.y + self.pivotOffset.y)
    for _, mmesh in pairs(self.multiMeshes) do
        if (mmesh.selectedPointsCount > 0) then
            local _angle = angle
            if (mmesh.layer.fFlipH.value ~= mmesh.layer.fFlipV.value) then
                _angle = -_angle
            end
            mmesh.layer:GetFullTransform(moho.frame, mInv, moho.document)
            mInv:Invert()
            vec3:Set(self.pivotCenterVec.x, self.pivotCenterVec.y, self.selCenter3z.z)
            mInv:Transform(vec3)
            vec:Set(vec3.x, vec3.y)
            mmesh.mesh:RotatePoints(_angle, vec)
            moho:AddPointKeyframe(moho.frame, mmesh.layer, true)
            if (self:UseFixedHandles(moho)) then
                mmesh.mesh:PreserveHandlePositions()
            end
        end
    end

    self.lastVecPt:Set(mouseEvent.pt) -- self.lastVec:Set(mouseEvent.drawingVec)
    mouseEvent.view:DrawMe()
end

function SS_MultiLayerTransformPoints:OnMouseMoved_P(moho, mouseEvent)
    local curPt = mouseEvent.pt - mouseEvent.startPt

    if (false and mouseEvent.ctrlKey) then -- #SC Set to 0,0 on Canvas
        -- local v = self:MultimeshSelectedCenter(moho)
        -- self.pivotOffset:Set(-v.x, -v.y)

    else
        if (mouseEvent.shiftKey) then
            if (math.abs(mouseEvent.drawingVec.x - mouseEvent.drawingStartVec.x) > math.abs(mouseEvent.drawingVec.y - mouseEvent.drawingStartVec.y)) then
                curPt.y = 0
            else
                curPt.x = 0
            end
        end

        self.pivotOffsetPt = self.startPivotOffsetPt + curPt
        mouseEvent.view:Graphics():ScreenToWorld(self.pivotOffsetPt, self.pivotOffset)
        self.centerVec = self:MultimeshSelectedCenter(moho) + self.pivotOffset
        if (moho.gridOn) then
            moho:SnapToGrid(self.centerVec)
            self.pivotOffset = self.centerVec - self:MultimeshSelectedCenter(moho)
        end
    end

    mouseEvent.view:DrawMe()
end

function SS_MultiLayerTransformPoints:MultimeshSelectSingleClickSelect(moho, mouseEvent, tryToPreserveSelection, allowEmptySelection)
    -- if tryToPreserveSelection is true, then don't change the selection if some points will remain selected
    self:MultimeshCountSelectedPoints(moho, true)
    if (self.multiPointsCount < 1) then
        return
    end

    if (tryToPreserveSelection and self.multiSelectedPointsCount < 2) then -- self:MultimeshCountSelectedPoints(moho) -- moho:CountSelectedPoints()
        self:MultimeshSelectNone()
    end
    if (not tryToPreserveSelection and not mouseEvent.shiftKey and not mouseEvent.altKey) then
        self:MultimeshSelectNone()
    end

    local layer, mesh, id = self:MultimeshPickClosestPoint(moho, mouseEvent.pt, self.mmeshPickWidth * 2)
    local p = LM.Point:new_local()
    local c = LM.Point:new_local() -- cursor
    local m = LM.Matrix:new_local()
    local v = LM.Vector2:new_local()
    c:Set(mouseEvent.pt)
    if (id >= 0) then -- pick a point
        moho.layer:GetFullTransform(moho.drawingFrame, m, moho.document)
        v:Set(mesh:Point(id).fPos)
        m:Transform(v)
        mouseEvent.view:Graphics():WorldToScreen(v, p)
        self.selMode = self.SELMODE_PT
        if (tryToPreserveSelection) then
            if (not mesh:Point(id).fSelected) then
                self:MultimeshSelectNone()
            end
        end
        if (mouseEvent.altKey) then
            mesh:Point(id).fSelected = false
        else
            mesh:Point(id).fSelected = true
        end
    else
        if (tryToPreserveSelection) then
            for _, mmesh in pairs(self.multiMeshes) do
                for i = 0, mmesh.mesh:CountPoints() - 1 do
                    local point = mmesh.mesh:Point(i)
                    point.fPrevSelected = point.fSelected
                    point.fSelected = false
                end
            end
            self.multiSelectedPointsCount = 0
        end

        local curveID = -1
        local segID = -1
        local didShape

        if (self.allowShapePicking) then -- try to pick a shape
            local mmesh, shape
            id, mmesh = next(self.multiMeshes)
            if (self.multiLayersCount == 1) and (moho.document:LayerAbsoluteID(moho.drawingLayer) + 1 == id) then
                -- sngl layer selected
                local shapeID = mouseEvent.view:PickShape(mouseEvent.pt)
                if (shapeID ~= -1) then
                    shape = mmesh.mesh:Shape(shapeID)
                end

            else -- try Pick Globally
                mmesh = self.multiMeshes[moho.document:LayerAbsoluteID(mouseEvent.view:PickGlobalLayer(mouseEvent.pt)) + 1]
                shape = mouseEvent.view:PickGlobalShape(mouseEvent.pt)
            end

            if not (mmesh and shape) then -- else try Active layer priority
                mmesh = self.multiMeshes[moho.document:LayerAbsoluteID(moho.layer) + 1]
                local shapeID = mouseEvent.view:PickShape(mouseEvent.pt)
                if (shapeID ~= -1) then
                    shape = mmesh.mesh:Shape(shapeID)
                else
                    shape = nil
                end
            end

            if (mmesh and shape) then
                self.selMode = LM_SelectPoints.SELMODE_SHAPE
                for i = 0, shape:CountEdges() - 1 do
                    curveID, segID = shape:GetEdge(i, curveID, segID)
                    local curve = mmesh.mesh:Curve(curveID)
                    if (mouseEvent.altKey) then
                        curve:Point(segID).fSelected = false
                        curve:Point(segID + 1).fSelected = false
                    else
                        curve:Point(segID).fSelected = true
                        curve:Point(segID + 1).fSelected = true
                    end
                end
                didShape = true
            end
        end

        if (not didShape) then
            layer, curveID, segID = mouseEvent.view:PickGlobalEdge(mouseEvent.pt, curveID, segID, self.mmeshPickWidth) -- **
            local mmesh = layer and self.multiMeshes[moho.document:LayerAbsoluteID(layer) + 1] -- #SC Is Edge in Selected Layers?
            if (mmesh and curveID >= 0 and segID >= 0) then -- an edge was clicked on
                self.selMode = LM_SelectPoints.SELMODE_EDGE
                local curve = mmesh.mesh:Curve(curveID)

                -- new method - select the points in the curve that was clicked on
                if (mouseEvent.altKey) then
                    local isCurveSelected = true
                    for i = 0, curve:CountPoints() - 1 do
                        if (not curve:Point(i).fSelected) then
                            isCurveSelected = false
                            break
                        end
                    end
                    for i = 0, curve:CountPoints() - 1 do
                        local point = curve:Point(i)
                        if ((not isCurveSelected) or (point:CountCurves() < 2)) then
                            point.fSelected = false
                            if (point.fHidden) then
                                point.fSelected = false
                            end
                        end
                    end
                else
                    for i = 0, curve:CountPoints() - 1 do
                        local point = curve:Point(i)
                        if (not point.fHidden) then
                            point.fSelected = true
                        end
                    end
                end
            end
        end

        if (not allowEmptySelection) then
            local numSel = self:MultimeshCountSelectedPoints(moho, true) -- moho:CountSelectedPoints(true)
            if (numSel < 1) then
                for _, mmesh in pairs(self.multiMeshes) do
                    for i = 0, mmesh.mesh:CountPoints() - 1 do
                        local point = mmesh.mesh:Point(i)
                        point.fSelected = point.fPrevSelected
                        point.fPrevSelected = false
                    end
                end
            end
        end

        if (tryToPreserveSelection) then
            local preserveSelection = false
            -- pass 1 - check if any of the selection is still selected
            for _, mmesh in pairs(self.multiMeshes) do
                for i = 0, mmesh.mesh:CountPoints() - 1 do
                    local point = mmesh.mesh:Point(i)
                    if (point.fPrevSelected and point.fSelected) then
                        preserveSelection = true
                    end
                end
            end
            -- pass 2 - preserve the selection
            if (preserveSelection) then
                for _, mmesh in pairs(self.multiMeshes) do
                    for i = 0, mmesh.mesh:CountPoints() - 1 do
                        local point = mmesh.mesh:Point(i)
                        point.fSelected = point.fPrevSelected
                        point.fPrevSelected = false
                    end
                end
            else
                for _, mmesh in pairs(self.multiMeshes) do
                    for i = 0, mmesh.mesh:CountPoints() - 1 do
                        local point = mmesh.mesh:Point(i)
                        point.fPrevSelected = false
                    end
                end
            end
        end
    end

    self:MultimeshCountSelectedPoints(moho, true)
    if (mouseEvent.doubleClick) and (self.multiSelectedPointsCount > 0) then
        for _, mmesh in pairs(self.multiMeshes) do
            if (mmesh.selectedPointsCount > 0) then
                moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer, MOHO.MohoGlobals.EditMultipleKeys)
            end
        end
    end
    self.pivotOffset:Set(0, 0) -- #SC+
    self.isPivotSet = false
end

function SS_MultiLayerTransformPoints:MultimeshSelectOnMouseUp(moho, mouseEvent) -- from LM_SelectPoints

    if (self.multiSelectedPointsCount == -1) then
        self:MultimeshCountSelectedPoints(moho, true)
    end

    self.allowShapePicking = true

    local lassoMode = false
    if (LM_SelectPoints.lassoMode) then
        if (not (LM_SelectPoints.ctrlKeySelection)) then
            lassoMode = true
        end
    else
        if (LM_SelectPoints.ctrlKeySelection) then
            lassoMode = true
        end
    end

    local mouseDist = math.abs(mouseEvent.pt.x - mouseEvent.startPt.x) + math.abs(mouseEvent.pt.y - mouseEvent.startPt.y)
    if (mouseDist < 8) then
        -- Single-click selection - select a shape or curve under the mouse
        self:MultimeshSelectSingleClickSelect(moho, mouseEvent, false, true)

    elseif (LM_SelectPoints.selMode == LM_SelectPoints.SELMODE_GROUP_PTS and lassoMode) then
        -- draw the finalized lasso outline
        local g = mouseEvent.view:Graphics()
        g:SetSmoothing(true)
        g:Push()
        local m = g:CurrentTransform()
        m:Invert()
        g:ApplyMatrix(m)
        g:SetColor(MOHO.MohoGlobals.SelCol)
        g:MoveTo(LM_SelectPoints.previousX, LM_SelectPoints.previousY)
        g:LineTo(mouseEvent.startPt.x, mouseEvent.startPt.y)
        g:Pop()
        g:SetSmoothing(false)
        mouseEvent.view:RefreshView()
        LM.Snooze(100)
    end

    LM_SelectPoints.isMouseDragging = false

    if (LM_SelectPoints.selMode == LM_SelectPoints.SELMODE_GROUP_PTS and self:MultimeshCountPoints(moho) > 0) then
        if (lassoMode) then
            local g = mouseEvent.view:Graphics()

            -- draw the lasso shape, and do hit testing
            -- 1 - draw the lasso shape
            local end1 = LM.Vector2:new_local()
            local end2 = LM.Vector2:new_local()
            g:Clear(0, 0, 0, 0)
            g:Push()
            local m = g:CurrentTransform()
            m:Invert()
            g:ApplyMatrix(m)
            g:SetColor(255, 255, 255, 255)
            g:BeginShape()
            for i = 1, #LM_SelectPoints.lassoList - 1 do
                end1:Set(LM_SelectPoints.lassoList[i][1], LM_SelectPoints.lassoList[i][2])
                end2:Set(LM_SelectPoints.lassoList[i + 1][1], LM_SelectPoints.lassoList[i + 1][2])
                g:AddLine(end1, end2)
            end
            end1:Set(LM_SelectPoints.lassoList[#LM_SelectPoints.lassoList][1], LM_SelectPoints.lassoList[#LM_SelectPoints.lassoList][2])
            end2:Set(LM_SelectPoints.lassoList[1][1], LM_SelectPoints.lassoList[1][2])
            g:AddLine(end1, end2)
            g:EndShape()
            g:Pop()
            -- test code to view the lasso's shape
            -- mouseEvent.view:RefreshView()
            -- LM.Snooze(1000)

            -- 2 - do hit testing on the lasso shape
            local v = LM.Vector2:new_local()
            local screenPt = LM.Point:new_local()
            -- local m = LM.Matrix:new_local()

            for _, mmesh in pairs(self.multiMeshes) do
                mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
                for i = 0, mmesh.mesh:CountPoints() - 1 do
                    local pt = mmesh.mesh:Point(i)
                    if (not pt.fHidden) then
                        v:Set(pt.fPos)
                        m:Transform(v)
                        g:WorldToScreen(v, screenPt)
                        if (g:IsFullWhite(screenPt)) then
                            if (mouseEvent.altKey) then
                                pt.fSelected = false
                            else
                                pt.fSelected = true
                            end
                        end
                    end
                end
            end

        else --  box select
            local v = LM.Vector2:new_local()
            local screenPt = LM.Point:new_local()
            local m = LM.Matrix:new_local()

            LM_SelectPoints.selRect:Normalize()
            for _, mmesh in pairs(self.multiMeshes) do
                mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
                for i = 0, mmesh.mesh:CountPoints() - 1 do
                    local pt = mmesh.mesh:Point(i)
                    if (not pt.fHidden) then
                        v:Set(pt.fPos)
                        m:Transform(v)
                        mouseEvent.view:Graphics():WorldToScreen(v, screenPt)
                        if (LM_SelectPoints.selRect:Contains(screenPt)) then
                            if (mouseEvent.altKey) then
                                pt.fSelected = false
                            else
                                pt.fSelected = true
                            end
                        end
                    end
                end
            end
        end
    end
    LM_SelectPoints.lassoList = nil
    -- moho:UpdateSelectedChannels() -- #SC Todo?

    self:MultimeshCountSelectedPoints(moho, true)
end

function SS_MultiLayerTransformPoints:OnMouseUp(moho, mouseEvent)

    self.allowShapePicking = true
    if (self.mode == self.MODE_SELECT) then
        mouseEvent.ctrlKey = false
        self:MultimeshSelectOnMouseUp(moho, mouseEvent) -- LM_SelectPoints:OnMouseUp(moho, mouseEvent)
        self.dragging = false
        self.selID = -1
        mouseEvent.view:DrawMe()
        moho:UpdateUI()
        return
    end

    if (self.multiPointsCount < 1) then
        return
    end

    self.dragging = false
    if (self.mode == self.MODE_BEZIER) then -- bezier handle
        LM_Curvature:OnMouseUp(moho, mouseEvent)

    elseif (self.mode == self.MODE_PIVOT) then
        if (self.pivotOffset.x ~= 0 or self.pivotOffset.y ~= 0) then
            self.isPivotSet = true
        else
            self.isPivotSet = false
        end

    else -- if (self.mode ~= self.MODE_PIVOT) then
        moho:UpdateUI()
        if (self.mode == self.MODE_TRANS) then
            -- translate : don't modify the pivot
        elseif (self.mode == self.MODE_ROTATE) then
            -- rotate : move the pivot
            local v = self:MultimeshSelectedCenter(moho, true)
            self.pivotOffset = self.centerVec - v

        elseif (self.mode >= self.MODE_SCALE_) and (self.mode <= self.MODE_SCALE_MAX_) then
            -- scale : update the pivot
            if (self.isPivotSet) then
                self.pivotOffset = self.pivotCenterVec - self:MultimeshSelectedCenter(moho, true)
            else
                self.pivotOffset:Set(0, 0)
            end
        end
    end

    self.selID = -1

    -- if (mouseEvent.pt.x == mouseEvent.startPt.x and mouseEvent.pt.y == mouseEvent.startPt.y) then
    local mouseDist = math.abs(mouseEvent.pt.x - mouseEvent.startPt.x) + math.abs(mouseEvent.pt.y - mouseEvent.startPt.y)
    if (mouseDist < 4) then
        -- #34389 - allow shift key to pass to select points so extended selections are possible
        -- mouseEvent.shiftKey = false
        mouseEvent.ctrlKey = false
        mouseEvent.altKey = false

        self:MultimeshSelectSingleClickSelect(moho, mouseEvent, false, true) -- sel from multlayer
    end

    moho:UpdateUI()
end

function SS_MultiLayerTransformPoints:OnKeyDown(moho, keyEvent)

    if (keyEvent.keyCode == LM.GUI.KEY_BIND) then -- Add Keyframe (no weld)
        for _, mmesh in pairs(self.multiMeshes) do
            if (mmesh.selectedPointsCount > 0) then
                moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer, MOHO.MohoGlobals.EditMultipleKeys)
            end
        end

    elseif ((keyEvent.keyCode == LM.GUI.KEY_DELETE) or (keyEvent.keyCode == LM.GUI.KEY_BACKSPACE)) then
        if (not self.dragging) then
            moho.document:PrepUndo()
            moho.document:SetDirty()
            for _, mmesh in pairs(self.multiMeshes) do
                MOHO.DeleteSelectedPoints(mmesh.mesh)
            end
        end

    elseif (keyEvent.keyCode == 9) or (keyEvent.altKey and ((keyEvent.keyCode == self.AltKeyCode1) or (keyEvent.keyCode == self.AltKeyCode2))) then -- tab
        -- Select connected glob"
        if (self.multiSelectedPointsCount > 0) then
            moho.document:PrepUndo()
            moho.document:SetDirty()
            self:MultimeshSelectConnected(moho)
        end

    elseif (keyEvent.keyCode == LM.GUI.KEY_ESCAPE) or (keyEvent.keyCode == 27) or (keyEvent.keyCode == self.AltKeyCode1) or (keyEvent.keyCode == self.AltKeyCode2) or (keyEvent.keyCode == LM.GUI.KEY_DESELECT) then
        -- Deselect All in Group
        if (self.multiSelectedPointsCount > 0) then
            moho.document:PrepUndo()
            moho.document:SetDirty()
            self:MultimeshSelectNone()
        end

    elseif (keyEvent.ctrlKey and keyEvent.altKey and (keyEvent.keyCode == 65 or keyEvent.keyCode == 97)) then -- A
        -- Select All in Group
        moho.document:PrepUndo()
        moho.document:SetDirty()
        self:MultimeshSelectAll(moho)

    elseif (keyEvent.ctrlKey) then
        -- nudge
        local pt1, pt2 = LM.Point:new_local(), LM.Point:new_local()
        local vc1, vc2 = LM.Vector2:new_local(), LM.Vector2:new_local()
        local inc = 1
        if (keyEvent.shiftKey) then
            inc = 10
        end
        pt1:Set(0, 0)
        moho.view:Graphics():ScreenToWorld(pt1, vc1)
        local function Nudge(xoff, yoff)
            moho.document:PrepUndo()
            for _, mmesh in pairs(self.multiMeshes) do
                if (mmesh.selectedPointsCount > 0) then
                    mmesh.mesh:PrepMovePoints()
                end
            end
            pt2:Set(xoff, yoff)
            moho.view:Graphics():ScreenToWorld(pt2, vc2)
            self:MultimeshMovePoints(moho, vc2 - vc1)
        end

        if (keyEvent.keyCode == LM.GUI.KEY_UP) then
            Nudge(0, -inc)

        elseif (keyEvent.keyCode == LM.GUI.KEY_DOWN) then
            Nudge(0, inc)

        elseif (keyEvent.keyCode == LM.GUI.KEY_LEFT) then
            Nudge(-inc, 0)

        elseif (keyEvent.keyCode == LM.GUI.KEY_RIGHT) then
            Nudge(inc, 0)
        end
    end

    -- print(("KEY: Alt/Opt: %s, Ctrl/Cmd: %s, Shft: %s :: "):format(tostring(keyEvent.altKey), tostring(keyEvent.ctrlKey), tostring(keyEvent.shiftKey)), tostring(keyEvent.keyCode)) -- , " - ", keyEvent.key)

    keyEvent.view:DrawMe()
    moho:UpdateUI()
end

function SS_MultiLayerTransformPoints:OnInputDeviceEvent(moho, deviceEvent)

    if (self:MultimeshCountSelectedPoints(moho) < 1) then -- moho:CountSelectedPoints() < 1) then
        return false
    end

    -- #SC TODO
    -- if (deviceEvent.inputData:GetString("DeviceType") == "Wacom Multitouch") then
    --     local mtState = deviceEvent.inputData:GetInt("MultitouchState")
    --     if (mtState == 1) then -- first finger down
    --         self.mtFingersTouching = 0 -- we'll get the correct number on the next event
    --         self.mtTranslate = LM.Vector2:new_local()
    --         self.mtStartVec = LM.Vector2:new_local()
    --         self.mtAccumTranslate = LM.Vector2:new_local()
    --         self.mtScale = 1.0
    --         self.mtStartScale = 1.0
    --         self.mtAccumScale = 1.0
    --         self.mtAngle = 0.0
    --         self.mtStartAngle = 0.0
    --         self.mtAccumAngle = 0.0

    --         moho.document:SetDirty()
    --         moho.document:PrepUndo()
    --         self.numSel = self:MultimeshCountSelectedPoints(moho) -- moho:CountSelectedPoints()
    --         mesh:PrepMovePoints()
    --         if (self:UseFixedHandles(moho)) then
    --             mesh:PrepFixedHandles()
    --         end
    --         self.centerVec = self:MultimeshSelectedCenter(moho)
    --         self.centerVec = self.centerVec + self.pivotOffset
    --         self.mtTranslate:Set(0.0, 0.0)
    --         self.mtScale = 1.0
    --         self.mtAngle = 0.0
    --     elseif (mtState == 3) then -- last finger up
    --         self.mtFingersTouching = 0
    --         moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
    --         moho:NewKeyframe(CHANNEL_POINT)
    --         moho:UpdateUI()
    --     elseif (mtState == 2) then -- dragging
    --         local fingersTouching = deviceEvent.inputData:GetInt("FingersTouching")
    --         if (fingersTouching ~= self.mtFingersTouching) then
    --             self.mtAccumTranslate = self.mtAccumTranslate + self.mtTranslate
    --             self.mtAccumScale = self.mtAccumScale * self.mtScale
    --             self.mtAccumAngle = self.mtAccumAngle + self.mtAngle
    --             self.mtFingersTouching = fingersTouching
    --             self.mtStartVec = deviceEvent.inputData:GetVector2("MultitouchCenterVector")
    --             self.mtStartAngle = deviceEvent.inputData:GetFloat("MultitouchAngle")
    --             self.mtStartScale = deviceEvent.inputData:GetFloat("MultitouchScale")
    --         end

    --         if (deviceEvent.inputData:GetBool("DoubleTouch")) then
    --             if (fingersTouching == 2) then
    --                 self:HandleMessage(moho, deviceEvent.view, self.RESET)
    --                 if (frame ~= moho.frame) then
    --                     moho:SetCurFrame(moho.frame) -- force a refresh when editing a key at a different frame
    --                 end
    --                 moho.document:DepthSort()
    --             end
    --             return true
    --         end

    --         self.mtTranslate = deviceEvent.inputData:GetVector2("MultitouchCenterVector") - self.mtStartVec
    --         if (deviceEvent.shiftKey) then
    --             if (math.abs(self.mtTranslate.x) > math.abs(self.mtTranslate.y)) then
    --                 self.mtTranslate.y = 0
    --             else
    --                 self.mtTranslate.x = 0
    --             end
    --         end
    --         self.mtScale = 1.0
    --         self.mtAngle = 0.0
    --         if (self.mtFingersTouching > 1) then
    --             self.mtAngle = deviceEvent.inputData:GetFloat("MultitouchAngle") - self.mtStartAngle
    --             self.mtScale = deviceEvent.inputData:GetFloat("MultitouchScale") / self.mtStartScale
    --         end
    --         mesh:TransformPoints(self.mtAccumTranslate + self.mtTranslate, self.mtAccumScale * self.mtScale, self.mtAccumScale * self.mtScale, self.mtAccumAngle + self.mtAngle, self.centerVec)
    --         if (self:UseFixedHandles(moho)) then
    --             mesh:PreserveHandlePositions()
    --         end

    --         moho:AddPointKeyframe(moho.drawingFrame)
    --     end

    --     return true
    -- end

    return false
end

function SS_MultiLayerTransformPoints:DrawMe(moho, view)
    if (moho:IsPlaying()) then
        return
    end

    if (self.lastSelectedCount ~= self:MultimeshCountSelectedPoints(moho)) then -- moho:CountSelectedPoints()) then
        self.lastSelectedCount = self:MultimeshCountSelectedPoints(moho)
        self.pivotOffset:Set(0, 0)
        self.isPivotSet = false
    end

    if (self.mode == self.MODE_SELECT and self.dragging) then
        LM_SelectPoints:DrawMe(moho, view)

    elseif (self.dragging and self.numSel == 1) then
        if (moho.drawingLayer:CurrentAction() ~= "" or moho:DisableDrawingTools()) then
            return -- welding in the middle of an action can lead to unexpected results
        end
        local g = view:Graphics()
        local matrix = LM.Matrix:new_local()

        moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
        g:Push()
        g:ApplyMatrix(matrix)

        if (self.endWeldToPoint) then
            g:SetColor(0, 255, 0, 128)
        else
            g:SetColor(255, 0, 0, 128)
        end
        g:SetSmoothing(true)
        g:SetBezierTolerance(2)
        g:FillCircle(self.endWeldVec, moho:PixelToDoc(self.autoWeldRadius) / g:CurrentScale(false))
        g:SetSmoothing(false)

        g:Pop()

    elseif (not self.dragging or self.mode == self.MODE_PIVOT) then

        self.numSel = self:MultimeshCountSelectedPoints(moho)
        if (self.numSel >= 2) then
            local g = view:Graphics()
            local min = LM.Vector2:new_local()
            local max = LM.Vector2:new_local()
            local centerVec = LM.Vector2:new_local()
            local v = LM.Vector2:new_local()
            local vc1 = LM.ColorVector:new_local()
            local vc2 = LM.ColorVector:new_local()

            vc1:Set(MOHO.MohoGlobals.SelCol)
            vc2:Set(MOHO.MohoGlobals.BackCol)
            -- vc1 = (vc1 * 3 + vc2 * 4) / 7
            vc1 = (vc1 + vc2) / 2
            local col = vc1:AsColorStruct()

            local bbox = self:MultimeshSelectedMeshBounds(moho, moho.drawingFrame, view)
            min:Set(bbox.fMin.x, bbox.fMin.y)
            max:Set(bbox.fMax.x, bbox.fMax.y)

            -- moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document) -- #SC pre-Trans
            g:Push()
            -- g:ApplyMatrix(matrix)

            g:SetColor(col)
            g:SetPenWidth(1)
            g:SetSmoothing(true)
            g:DrawLine(min.x, min.y, max.x, min.y) -- bigbox
            g:DrawLine(min.x, max.y, max.x, max.y)
            g:DrawLine(min.x, min.y, min.x, max.y)
            g:DrawLine(max.x, min.y, max.x, max.y)

            g:SetPenWidth(1)
            local rotWidth = max.x - min.x
            if (max.y - min.y > rotWidth) then
                rotWidth = max.y - min.y
            end
            rotWidth = rotWidth * 0.1
            g:DrawLine(min.x - rotWidth, min.y - rotWidth, min.x - rotWidth, max.y + rotWidth) -- outer
            g:DrawLine(min.x - rotWidth, max.y + rotWidth, max.x + rotWidth, max.y + rotWidth)
            g:DrawLine(max.x + rotWidth, max.y + rotWidth, max.x + rotWidth, min.y - rotWidth)
            g:DrawLine(max.x + rotWidth, min.y - rotWidth, min.x - rotWidth, min.y - rotWidth)

            g:SetPenWidth(2)
            v:Set(min.x, min.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set(min.x, max.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set(max.x, min.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set(max.x, max.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set((min.x + max.x) / 2, min.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set((min.x + max.x) / 2, max.y)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set(min.x, (min.y + max.y) / 2)
            g:FrameCirclePixelRadius(v, self.markerR)
            v:Set(max.x, (min.y + max.y) / 2)
            g:FrameCirclePixelRadius(v, self.markerR)
            g:SetPenWidth(1)

            -- Pivot
            if (self.dragging) then
                centerVec:Set(self.centerVec)
            else
                centerVec = self:MultimeshSelectedCenter(moho)
                centerVec = centerVec + self.pivotOffset
            end
            g:SetColor(col)
            g:SetSmoothing(true)
            g:DrawLine(centerVec.x - 0.05, centerVec.y, centerVec.x + 0.05, centerVec.y)
            g:DrawLine(centerVec.x, centerVec.y - 0.05, centerVec.x, centerVec.y + 0.05)
            v:Set(centerVec.x, centerVec.y)
            g:FrameCirclePixelRadius(v, self.markerR)

            g:Pop()

        elseif (self.dragging) then -- #SC

        end
    end

    -- Draw bezier handles
    if (self.showHandles) then

        local g = view:Graphics()
        local layerMatrix = LM.Matrix:new_local()

        moho.layer:GetFullTransform(moho.frame, layerMatrix, moho.document)
        g:Push()
        g:ApplyMatrix(layerMatrix)
        g:SetSmoothing(true)

        local selectedOnly = false
        if (self:MultimeshCountSelectedPoints(moho) < 2) then -- moho:CountSelectedPoints() < 2) then
            for _, mmesh in pairs(self.multiMeshes) do
                moho:LayerAsVector(mmesh.layer):DrawHandles(moho.document, g, selectedOnly) -- #SC Only works on Active layer
            end
        end

        g:Pop()
    end

    local g = view:Graphics()
    local m = LM.Matrix:new_local()

    -- #SC draw dem dots
    for _, mmesh in pairs(self.multiMeshes) do
        mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
        g:Push()
        g:ApplyMatrix(m)
        for iPoint = 0, mmesh.mesh:CountPoints() - 1 do
            local point = mmesh.mesh:Point(iPoint)
            if not point.fHidden then
                if point.fSelected then
                    g:SetColor(MOHO.MohoGlobals.SelCol)
                    g:DrawDiamondMarker(point.fPos.x, point.fPos.y, 6)
                else
                    g:SetColor(MOHO.MohoGlobals.ElemCol)
                    g:DrawDiamondMarker(point.fPos.x, point.fPos.y, 4)
                end
            end
        end
        g:Pop()
    end

    -- #SC Show circ cross target @ pivot
    if (self.dragging) then
        g:Push()
        g:SetSmoothing(true)
        g:SetColor(MOHO.MohoGlobals.InacCol) --     g:SetColor(0, 0, 255, 255)
        g:DrawLine(self.pivotCenterVec.x - 0.01, self.pivotCenterVec.y, self.pivotCenterVec.x + 0.01, self.pivotCenterVec.y)
        g:DrawLine(self.pivotCenterVec.x, self.pivotCenterVec.y - 0.01, self.pivotCenterVec.x, self.pivotCenterVec.y + 0.01)
        g:FrameCirclePixelRadius(self.pivotCenterVec, self.markerR / 2)
        g:FrameCirclePixelRadius(self.pivotCenterVec, self.markerR)
        g:Pop()
    end
end

function SS_MultiLayerTransformPoints:WeldPoints(moho, view, dragging)

    -- if (moho.drawingLayer:CurrentAction() ~= "" or moho:DisableDrawingTools()) then
    --     return false -- welding in the middle of an action can lead to unexpected results
    -- 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()
    -- 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)
    -- if (closestID < 0) then
    --     self.selID = -1
    --     return false
    -- end
    -- testVec2:Set(mesh:Point(closestID).fPos)

    -- if (mesh:Point(self.selID).fHidden or mesh:Point(closestID).fHidden) then
    --     return false
    -- end

    -- -- 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.drawingLayer: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 < self.autoWeldRadius * self.autoWeldRadius) 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.drawingLayerFrame)) then
    --         moho:Click()
    --         if (self.selID < closestID) then
    --             closestID = closestID - 1
    --         end
    --         self.selID = -1 -- flag to end this interaction
    --         if ((self.autoFill or self.autoStroke) and (not mesh:ContinuousTriangulation())) then
    --             self:CreateShape(moho, mesh, closestID, self.autoFill)
    --         end
    --     end
    -- else
    --     return false
    -- end

    -- self.selID = -1
    -- return true
end

function SS_MultiLayerTransformPoints:CreateShape(moho, mesh, ptID, filled)
    -- mesh:Point(ptID).fSelected = true
    -- mesh:SelectConnected()
    -- self.fillingShape = true
    -- moho.view:DrawMe()
    -- local shapeID = moho:CreateShape(filled, false, 0)
    -- self.fillingShape = false
    -- if (shapeID >= 0) then
    --     local shape = mesh:Shape(shapeID)
    --     shape.fSelected = true
    --     if (shape.fFillAllowed) then
    --         shape.fHasFill = self.autoFill
    --     end
    --     shape.fHasOutline = self.autoStroke
    --     moho:NewKeyframe(CHANNEL_FILL)
    --     moho:NewKeyframe(CHANNEL_LINE)
    --     moho:UpdateSelectedChannels()

    -- else
    --     self:MultimeshSelectNone() -- mesh:SelectNone()
    --     mesh:Point(ptID).fSelected = true

    -- end
    -- return shapeID
end

function SS_MultiLayerTransformPoints:UseFixedHandles(moho)
    if (not MOHO.IsMohoPro()) then
        return false
    end
    if (self.showHandles and self.fixedHandles) then -- and moho.drawingFrame == 0) then
        return true
    else
        return false
    end
end

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

SS_MultiLayerTransformPoints.DUMMY = MOHO.MSG_BASE
SS_MultiLayerTransformPoints.CHANGE = MOHO.MSG_BASE + 1
SS_MultiLayerTransformPoints.MODIFY_S = MOHO.MSG_BASE + 2
SS_MultiLayerTransformPoints.MODIFY_R = MOHO.MSG_BASE + 3
SS_MultiLayerTransformPoints.RESET = MOHO.MSG_BASE + 4
SS_MultiLayerTransformPoints.AUTOWELD = MOHO.MSG_BASE + 5
SS_MultiLayerTransformPoints.AUTOFILL = MOHO.MSG_BASE + 6
SS_MultiLayerTransformPoints.AUTOSTROKE = MOHO.MSG_BASE + 7
SS_MultiLayerTransformPoints.SHOWHANDLES = MOHO.MSG_BASE + 8
SS_MultiLayerTransformPoints.FIXEDHANDLES = MOHO.MSG_BASE + 9
SS_MultiLayerTransformPoints.FLIP_H = MOHO.MSG_BASE + 10
SS_MultiLayerTransformPoints.FLIP_V = MOHO.MSG_BASE + 11
SS_MultiLayerTransformPoints.SELECTITEM = MOHO.MSG_BASE + 12
SS_MultiLayerTransformPoints.autoWeld = false
SS_MultiLayerTransformPoints.autoWeldRadius = 12
SS_MultiLayerTransformPoints.autoFill = false
SS_MultiLayerTransformPoints.autoStroke = false
SS_MultiLayerTransformPoints.showHandles = false
SS_MultiLayerTransformPoints.fixedHandles = true

SS_MultiLayerTransformPoints.ALIGN_ = MOHO.MSG_BASE + 20
SS_MultiLayerTransformPoints.ALIGN_LEFT = MOHO.MSG_BASE + 21
SS_MultiLayerTransformPoints.ALIGN_HORIZ = MOHO.MSG_BASE + 22
SS_MultiLayerTransformPoints.ALIGN_RIGHT = MOHO.MSG_BASE + 23
SS_MultiLayerTransformPoints.ALIGN_CENTER = MOHO.MSG_BASE + 24
SS_MultiLayerTransformPoints.ALIGN_TOP = MOHO.MSG_BASE + 25
SS_MultiLayerTransformPoints.ALIGN_VERT = MOHO.MSG_BASE + 26
SS_MultiLayerTransformPoints.ALIGN_BOT = MOHO.MSG_BASE + 27
SS_MultiLayerTransformPoints.ALIGN_MAX_ = MOHO.MSG_BASE + 29

function SS_MultiLayerTransformPoints:DoLayout(moho, layout)

    self.numSel = self:MultimeshCountSelectedPoints(moho, true)
    self.selCenterIsDirty = true

    self.menu = LM.GUI.Menu(MOHO.Localize("/Scripts/Tool/TransformPoints/SelectGroup=Select Group"))

    self.popup = LM.GUI.PopupMenu(120, false)
    self.popup:SetMenu(self.menu)
    layout:AddChild(self.popup)

    self.resetBut = LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformPoints/Reset=Reset"), self.RESET)
    layout:AddChild(self.resetBut)

    layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformPoints/Position=Position")))

    self.textX = LM.GUI.TextControl(0, "00.000", self.CHANGE, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/X=X:"))
    self.textX:SetWheelInc(0.1)
    layout:AddChild(self.textX)

    self.textY = LM.GUI.TextControl(0, "00.000", self.CHANGE, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/Y=Y:"))
    self.textY:SetWheelInc(0.1)
    layout:AddChild(self.textY)

    layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Scale=Scale")))

    self.scaleX = LM.GUI.TextControl(0, "00.000", self.MODIFY_S, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/X=X:"))
    self.scaleX:SetWheelInc(0)
    layout:AddChild(self.scaleX)

    self.scaleY = LM.GUI.TextControl(0, "00.000", self.MODIFY_S, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/Y=Y:"))
    self.scaleY:SetWheelInc(0)
    layout:AddChild(self.scaleY)

    layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Angle=Angle:")))
    self.angle = LM.GUI.TextControl(0, "000.000", self.MODIFY_R, LM.GUI.FIELD_FLOAT)
    self.angle:SetWheelInc(0)
    layout:AddChild(self.angle)

    -- #SC self.autoWeldCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoWeld=Auto-weld"), self.AUTOWELD)
    -- layout:AddChild(self.autoWeldCheck)

    -- self.autoFillCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoFill=Auto-fill"), self.AUTOFILL)
    -- layout:AddChild(self.autoFillCheck)

    -- self.autoStrokeCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoStroke=Auto-stroke"), self.AUTOSTROKE)
    -- layout:AddChild(self.autoStrokeCheck)

    -- if (MOHO.IsMohoPro()) then
    --     self.showHandleCheck = LM.GUI.ImageButton("ScriptResources/show_handles", MOHO.Localize("/Scripts/Tool/TransformPoints/ShowBezierHandles=Show Bezier Handles"), true, self.SHOWHANDLES, true)
    --     layout:AddChild(self.showHandleCheck)

    --     self.fixedHandleCheck = LM.GUI.ImageButton("ScriptResources/fixed_handles", MOHO.Localize("/Scripts/Tool/TransformPoints/FixedBezierHandles=Fixed Bezier Handles"), true, self.FIXEDHANDLES, true)
    --     layout:AddChild(self.fixedHandleCheck)
    -- end

    self.flipH = LM.GUI.ImageButton("ScriptResources/flip_points_h", MOHO.Localize("/Scripts/Tool/SelectPoints/FlipH=Flip Horizontally"), false, self.FLIP_H, true)
    layout:AddChild(self.flipH)

    self.flipV = LM.GUI.ImageButton("ScriptResources/flip_points_v", MOHO.Localize("/Scripts/Tool/SelectPoints/FlipV=Flip Vertically"), false, self.FLIP_V, true)
    layout:AddChild(self.flipV)

    -- #SC
    layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL) -- |

    layout:AddChild(LM.GUI.StaticText("Align:"))

    self.alignLeftButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_left", "Align Left", false, self.ALIGN_LEFT, true)
    layout:AddChild(self.alignLeftButton)

    self.alignHorzButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_center_horiz", "Align Center Horizontally", false, self.ALIGN_HORIZ, true)
    layout:AddChild(self.alignHorzButton)

    self.alignRightButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_right", "Align Right", false, self.ALIGN_RIGHT, true)
    layout:AddChild(self.alignRightButton)

    self.alignCntrButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_center", "Align Centers", false, self.ALIGN_CENTER, true)
    layout:AddChild(self.alignCntrButton)

    self.alignTopButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_top", "Align Top", false, self.ALIGN_TOP, true)
    layout:AddChild(self.alignTopButton)

    self.alignVertButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_center_vert", "Align Center Vertically", false, self.ALIGN_VERT, true)
    layout:AddChild(self.alignVertButton)

    self.alignBottButton = LM.GUI.ImageButton("ScriptResources/ss_multi_layer_transform_points/ss_align_bottom", "Align Bottom", false, self.ALIGN_BOT, true)
    layout:AddChild(self.alignBottButton)

end

function SS_MultiLayerTransformPoints:UpdateWidgets(moho)
    if (moho:CurrentTool() ~= "SS_MultiLayerTransformPoints") then
        return -- this could get called when doing a double-tap on a multitouch Wacom device with a different active tool
    end

    -- MOHO.BuildGroupMenu(self.menu, mesh, self.SELECTITEM, self.DUMMY) --#SC todo

    local center = self:MultimeshSelectedCenter(moho)

    self.textX:SetValue(center.x)
    self.textY:SetValue(center.y)

    self.scaleX:SetValue(1)
    self.scaleY:SetValue(1)

    self.angle:SetValue(0)

    -- #SC
    -- if (MOHO.IsMohoPro()) then
    --     self.showHandleCheck:SetValue(self.showHandles)
    --     self.fixedHandleCheck:SetValue(self.fixedHandles)
    --     self.fixedHandleCheck:Enable(self.showHandles)
    -- end

    self.resetBut:Enable(moho.drawingFrame > 0)

    local isMultiSelect = self.multiSelectedPointsCount > 1

    self.scaleX:Enable(isMultiSelect)
    self.scaleY:Enable(isMultiSelect)
    self.angle:Enable(isMultiSelect)

    self.flipH:Enable(isMultiSelect)
    self.flipV:Enable(isMultiSelect)

    self.alignLeftButton:Enable(isMultiSelect)
    self.alignHorzButton:Enable(isMultiSelect)
    self.alignRightButton:Enable(isMultiSelect)
    self.alignCntrButton:Enable(isMultiSelect)
    self.alignTopButton:Enable(isMultiSelect)
    self.alignVertButton:Enable(isMultiSelect)
    self.alignBottButton:Enable(isMultiSelect)
end

function SS_MultiLayerTransformPoints:HandleMessage(moho, view, msg)

    local v = LM.Vector2:new_local()
    local vec = LM.Vector2:new_local()
    local vec3 = LM.Vector3:new_local()
    local m = LM.Matrix:new_local()
    local mInv = LM.Matrix:new_local()
    local lVec = LM.Vector2:new_local()
    local offset = LM.Vector2:new_local()
    local angle
    local center, center3z, bbox = self:MultimeshSelectedCenter(moho, true) -- recount, bbox etc

    if (self.multiSelectedPointsCount > 0) then

        if (msg == self.CHANGE) then -- translate
            moho.document:PrepUndo()
            moho.document:SetDirty()
            offset.x = self.textX:FloatValue() - center.x
            offset.y = self.textY:FloatValue() - center.y
            self.pivotCenterVec:Set(center + offset)

            for _, mmesh in pairs(self.multiMeshes) do
                if (mmesh.selectedPointsCount > 0) then
                    mmesh.mesh:PrepMovePoints()
                end
            end

            self:MultimeshMovePoints(moho, offset)
            moho:NewKeyframe(CHANNEL_POINT)
            moho:UpdateUI()

        elseif (msg == self.MODIFY_S) then
            moho.document:PrepUndo()
            moho.document:SetDirty()
            local center3zOff = LM.Vector3:new_local()
            center3zOff:Set(center3z.x + self.pivotOffset.x, center3z.y + self.pivotOffset.y, center3z.z)
            self.pivotCenterVec:Set(center3zOff.x, center3zOff.y)

            local scaleX = self.scaleX:FloatValue()
            local scaleY = self.scaleY:FloatValue()
            self.scaleX:SetValue(1)
            self.scaleY:SetValue(1)

            local flip = false
            if (scaleX < 0.001) and (scaleY > 0.001) then
                flip = true
            elseif (scaleY < 0.001) and (scaleX > 0.001) then
                flip = true
            end

            for _, mmesh in pairs(self.multiMeshes) do
                if (mmesh.selectedPointsCount > 0) then
                    if (self:UseFixedHandles(moho)) then
                        mmesh.mesh:PrepFixedHandles()
                    end

                    mmesh.mesh:PrepMovePoints()
                    mmesh.layer:GetFullTransform(moho.frame, mInv, moho.document)
                    mInv:Invert()

                    lVec:Set(1, 0)
                    mInv:Transform(lVec)
                    angle = math.deg(math.atan2(lVec.y, lVec.x))
                    if ((angle > 45) and (angle < 135)) or ((angle < -45) and ((angle > -135))) then
                        scaleX, scaleY = scaleY, scaleX
                    end

                    vec3:Set(center3zOff)
                    mInv:Transform(vec3)
                    vec:Set(vec3.x, vec3.y)
                    mmesh.mesh:ScalePoints(scaleX, scaleY, vec, flip)

                    moho:AddPointKeyframe(moho.frame, mmesh.layer, MOHO.MohoGlobals.EditMultipleKeys)
                    if (self:UseFixedHandles(moho)) then
                        mmesh.mesh:PreserveHandlePositions()
                    end
                end
            end

            self.pivotOffset = self.pivotCenterVec - self:MultimeshSelectedCenter(moho, true)
            moho:NewKeyframe(CHANNEL_POINT)
            moho:UpdateUI()

        elseif (msg == self.MODIFY_R) then
            moho.document:PrepUndo()
            moho.document:SetDirty()
            local center3zOff = LM.Vector3:new_local()
            center3zOff:Set(center3z.x + self.pivotOffset.x, center3z.y + self.pivotOffset.y, center3z.z)
            self.pivotCenterVec:Set(center3zOff.x, center3zOff.y)

            angle = math.rad(self.angle:FloatValue())
            self.angle:SetValue(0)
            for _, mmesh in pairs(self.multiMeshes) do
                if (mmesh.selectedPointsCount > 0) then
                    mmesh.mesh:PrepMovePoints()
                    local _angle = angle
                    if (mmesh.layer.fFlipH.value ~= mmesh.layer.fFlipV.value) then
                        _angle = -_angle
                    end
                    mmesh.layer:GetFullTransform(moho.frame, mInv, moho.document)
                    mInv:Invert()
                    vec3:Set(center3zOff)
                    mInv:Transform(vec3)
                    vec:Set(vec3.x, vec3.y)
                    mmesh.mesh:RotatePoints(_angle, vec)
                    moho:AddPointKeyframe(moho.frame, mmesh.layer, MOHO.MohoGlobals.EditMultipleKeys)
                    if (self:UseFixedHandles(moho)) then
                        mmesh.mesh:PreserveHandlePositions()
                    end
                end
            end

            self.pivotOffset = self.pivotCenterVec - self:MultimeshSelectedCenter(moho, true)
            moho:NewKeyframe(CHANNEL_POINT)
            moho:UpdateUI()

        elseif (msg == self.FLIP_H) or (msg == self.FLIP_V) then

            if (self.multiSelectedPointsCount > 1) then
                moho.document:PrepUndo()
                moho.document:SetDirty()
                center3z.x, center3z.y = center3z.x + self.pivotOffset.x, center3z.y + self.pivotOffset.y
                for _, mmesh in pairs(self.multiMeshes) do
                    if (mmesh.selectedPointsCount > 0) then
                        mmesh.mesh:PrepMovePoints()
                        mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
                        mInv:Set(m)
                        mInv:Invert()
                        for i = 0, mmesh.mesh:CountPoints() - 1 do
                            local pt = mmesh.mesh:Point(i)
                            if (pt.fSelected) then
                                vec3:Set(pt.fPos.x, pt.fPos.y, 0)
                                m:Transform(vec3) -- > wrld
                                if (msg == self.FLIP_H) then
                                    vec3.x = center3z.x + center3z.x - vec3.x
                                else -- > self.FLIP_V
                                    vec3.y = (2 * center3z.y) - vec3.y
                                end
                                mInv:Transform(vec3)
                                pt.fPos:Set(vec3.x, vec3.y)
                                pt:FlipControlHandles(moho.drawingFrame)
                            end
                        end
                        moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer)
                        mmesh.layer:UpdateCurFrame()
                    end
                end
                v:Set(center3z.x, center3z.y)
                self.pivotOffset = v - self:MultimeshSelectedCenter(moho, true)
                moho:NewKeyframe(CHANNEL_POINT)
                moho:UpdateUI()
            end

        elseif (msg == self.RESET) then
            if (moho.drawingFrame > 0) then
                moho.document:PrepUndo()
                moho.document:SetDirty()
                for _, mmesh in pairs(self.multiMeshes) do
                    if (mmesh.selectedPointsCount > 0) then
                        mmesh.mesh:PrepMovePoints()
                        for i = 0, mmesh.mesh:CountPoints() - 1 do
                            local pt = mmesh.mesh:Point(i)
                            if (pt.fSelected) then
                                pt:SetPos(pt.fAnimPos:GetValue(0), moho.drawingLayerFrame)
                            end
                        end
                        moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer, false)
                    end
                end
            end
            self:UpdateWidgets(moho)
            moho:NewKeyframe(CHANNEL_POINT)
            moho:UpdateUI()

        elseif (msg > self.ALIGN_) and (msg <= self.ALIGN_MAX_) then

            moho.document:PrepUndo()
            moho.document:SetDirty()

            local xloc, yloc
            if (msg == self.ALIGN_LEFT) then
                xloc = bbox.fMin.x
            elseif (msg == self.ALIGN_HORIZ) then
                xloc = center.x
            elseif (msg == self.ALIGN_RIGHT) then
                xloc = bbox.fMax.x

            elseif (msg == self.ALIGN_CENTER) then
                xloc, yloc = center.x, center.y

            elseif (msg == self.ALIGN_TOP) then
                yloc = bbox.fMax.y -- dn/up
            elseif (msg == self.ALIGN_VERT) then
                yloc = center.y
            elseif (msg == self.ALIGN_BOT) then
                yloc = bbox.fMin.y -- up/dn
            end

            self.pivotCenterVec:Set(xloc or center.x, yloc or center.y)
            for _, mmesh in pairs(self.multiMeshes) do
                if (mmesh.selectedPointsCount > 0) then
                    mmesh.mesh:PrepMovePoints()
                    if (self:UseFixedHandles(moho)) then
                        mmesh.mesh:PrepFixedHandles()
                    end

                    mmesh.layer:GetFullTransform(moho.frame, m, moho.document)
                    mInv:Set(m)
                    mInv:Invert()
                    for i = 0, mmesh.mesh:CountPoints() - 1 do
                        local pt = mmesh.mesh:Point(i)
                        if (pt.fSelected) then
                            vec3:Set(pt.fPos.x, pt.fPos.y, 0)
                            m:Transform(vec3)
                            vec3.x, vec3.y = xloc or vec3.x, yloc or vec3.y
                            mInv:Transform(vec3)
                            pt.fPos:Set(vec3.x, vec3.y)
                        end
                    end

                    moho:AddPointKeyframe(moho.drawingFrame, mmesh.layer, true)
                    if (self:UseFixedHandles(moho)) then
                        mmesh.mesh:PreserveHandlePositions()
                    end
                end
            end

            moho:NewKeyframe(CHANNEL_POINT)
            moho:UpdateUI()
        end

        self:MultimeshSelectedCenter(moho, true)
    end

    -- if (msg == self.AUTOWELD) then
    --     -- #SC self.autoWeld = self.autoWeldCheck:Value()
    -- elseif (msg == self.AUTOFILL) then
    --     -- #SC self.autoFill = self.autoFillCheck:Value()
    -- elseif (msg == self.AUTOSTROKE) then
    --     -- #SC self.autoStroke = self.autoStrokeCheck:Value()
    -- elseif (msg == self.SHOWHANDLES) then
    --     -- #SC if (MOHO.IsMohoPro()) then
    --     --     self.showHandles = self.showHandleCheck:Value()
    --     --     moho:UpdateUI()
    --     -- end
    -- elseif (msg == self.FIXEDHANDLES) then
    --     -- #SC if (MOHO.IsMohoPro()) then
    --     --     self.fixedHandles = self.fixedHandleCheck:Value()
    --     -- end

    -- elseif (msg >= self.SELECTITEM) then
    --     -- #SC mesh:SelectNone()
    --     -- local i = msg - self.SELECTITEM
    --     -- local name = mesh:Group(i):Name()
    --     -- mesh:SelectGroup(name)
    --     -- moho:UpdateUI()
    -- end
end

Icon
SS - Multi Layer Transform Points
Listed

Script type: Tool

Uploaded: Jun 04 2023, 05:42

Last modified: Jun 05 2023, 12:44

Quickly Select & Transform multiple points across multiple selected layer groups (ver +1.04)
SS Multi-Layer Transform Points is a convenience tool which allows you to quickly transform selected points across multiple selected layers. The tool was developed as an addition to the existing built-in Transform Points tool, and does not replace it, but allows you to select, move, scale and rotate selected points on multiple layers at the same time, or simply select child-content layers from one or more groups of layers - without having to leave the currently selected groups/layers.


    Image


Enhanced Features:

- Multi-Layer points visibility & editing - if the layer is selected, or the layer is a child of a selected group layer
- Proximity based Select or Transform
- Scale & Flip can use pivot offset
- Nearest (single) point manipulation - across all selected layers
- Global Shape, Edge and Bounding Box selection - across all selected layers
- Does not create static-keyframe during a simple single-click selection / deselection
- Can add static-keyframe using double-click selection, or keyboard [Enter] with selected points
- Align points to other points - left, mid-l/r, right, middles, top, mid-t/b, bottom
- The Transformation bounding box is always upright rectangular (does not rotate with layer)
- Use Ctrl-Alt-A to select all points on all selected layers / groups of layers
- Eight angular-points of fixed rotation (when using the Rotation Shift modifier)

It should otherwise be very similarly to the built-in Transform Points tool.


Video Demo:


    Image


Tool Options:

The tool has 4 main modes of operation - plus flip & point alignment, all working with multiple-layers

1. Select
Single or multiple Points/Shapes/Edges can be selected by single-click, drag / ctrl-drag or ctrl-alt-a (select all). You can ctrl-shift-click to add individual points to selection, or you can shift-click Shapes or Edges to add them to selection. Esc or single-click canvas background to deselect. All point selection actions will be limited to content in the currently selected Groups and Layers.

2. Translate
Used to move a single or groups of selected Points. This can be achieved via numerical entry, or using the mouse to drag the selection, or by using ctrl-[up/down/left/right] to nudge the selected points. If zero points are selected the tools will automatically switch between Select and Transform mode - based on point proximity. If 1 points are already selected, the one-click select will choose the nearest point to the mouse - so you can easily drag the nearest point.

3.Scale
Used to scale & resize the group of selected Points. Drag side-handles (Top / Bottom / Left / Right) to stretch with pivot anchor at the opposite side, or alt-drag side-handles to stretch uniformly from center. Drag corner-handles to uniformly scale to the opposite corner, or shift-drag corner-handles to freeform scale from the opposite corner. You can also move the Pivot point to scale (in any of the above modes) towards that pivot location.

4. Rotate
Drag between the inner and outer bounding box guide lines to rotate the selection about the pivot point. Shift-drag allows you to rotate in 8 fixed 45° increments. You can also drag and move the pivot point to change the pivot point of rotation.

5. Flip & Align
Allows you to Flip points Horizontally and Vertically about the center or about the pivot point, and Align selected points Left, mid-Left/Right, Right, Centers, Top, mid-Top/Bottom & Bottom.

For the most part the tool behaves like the built-in Transform Points tool, so you can refer to the docs for that for further options.

So … Why do you need the Multi-Layer Transform Points tool?

Speed and convenience. The addon can enhance your animation and point manipulation workflow. Particularly handy when wishing to maintain point and shape alignment across different layers, but also handy for selecting or picking content on different layers without having to change the currently selected layers.

Version:

  - version: 01.04 #530605 MH12.5+
  - release: 10.0+1.04 (based on LM Transform Points 10.0)
  - by Sam Cogheil (SimplSam)


How do I get set up ?

  To install:

  - Use the Moho ‘Scripts’ > ‘Install Script …’ menu (after you Download for Install Script, and extract the zip files)

  To use:

  - Run the Multi-Layer Transform Points tool from the Tools palette
  - The tool will be invoked allowing you to manipulate the vector points of your animation content
  - You can select multiple vector layers and/or multiple parent group layers to enable them for editing


Notes & Limitations

Notes:

  - Compatible with MH12.5+

Known Limitations:

  - Construction curves are only visible on the Active layer (last selected), but can be turned on manually using layer quick-properties
  - Point Welding across multiple layers is not possible, thus the feature (as a whole) is not supported
  - Bezier Handles would only be visible on a single-selected layer, thus they are not currently supported
  - Copy/Paste only works from and to the Active (last selected) layer - if it is a vector layer
  - Point Groups are not currently supported
  - Some odd point placement / behaviour may occur with:
  - Switch layers & Frame by Frame content layers – so they are not officially supported
  - Scaling of content on rotated layers which are not rotated close to 0, 90, 180 or 270 degrees
  - 3D translated layers (i.e. Rotated about X or Y axis) and Sheared layers

 
Special Thanks to:

  - Stan (and the team): MOHO Scripting – https://mohoscripting.com
  - The friendly faces @ Lost Marble Moho forum – https://www.lostmarble.com/forum/
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: 773