Curve Point Split Script — Curvature Resets to 0.39 After Splitting
Posted: Wed Sep 16, 2026 5:49 am
Hi everyone,
I’ve written a Moho menu script that splits a selected point on a curve into two independent endpoints. The script runs correctly: the user selects a point and executes the split from the menu.
Current approach:
Read the selected point’s position and curvature data via M_Curve:GetCurvature, GetWeight, and GetOffset (for both front and back sides);
Use M_Mesh:AddPoint to create a coincident point at the same position;
Use M_Mesh:DeleteEdge to remove the segment between the original point and the new point, breaking the curve;
Attempt to restore curvature data before and after deleting the edge.
The problem:
Regardless of the original curvature value, after splitting, both the original and new points have their curvature reset to 0.39, and the curve visibly changes shape at the split location. I have tried the following without success:
Restoring curvature via M_Curve:SetCurvature / SetWeight / SetOffset before and after DeleteEdge;
Using M_Curve:GetControlHandle / SetControlHandle to manipulate handles directly;
Setting correctBezierHandles = false in M_Mesh:AddPoint;
Setting the last parameter of M_Mesh:DeleteEdge to false;
Directly manipulating the five sub-channels of the “Point Curvature” animation channel;
Changing the order of curvature restoration (before deletion, after deletion, and both).
My question:
Is there a correct way to split a point on a curve while preserving its curvature? Or is this a limitation of the current scripting API? If it is a limitation, is there an officially recommended alternative (e.g., duplicating the layer and deleting points from copies)?
The plugin code is available upon request. Any guidance or suggestions would be greatly appreciated.
Thank you!
I’ve written a Moho menu script that splits a selected point on a curve into two independent endpoints. The script runs correctly: the user selects a point and executes the split from the menu.
Current approach:
Read the selected point’s position and curvature data via M_Curve:GetCurvature, GetWeight, and GetOffset (for both front and back sides);
Use M_Mesh:AddPoint to create a coincident point at the same position;
Use M_Mesh:DeleteEdge to remove the segment between the original point and the new point, breaking the curve;
Attempt to restore curvature data before and after deleting the edge.
The problem:
Regardless of the original curvature value, after splitting, both the original and new points have their curvature reset to 0.39, and the curve visibly changes shape at the split location. I have tried the following without success:
Restoring curvature via M_Curve:SetCurvature / SetWeight / SetOffset before and after DeleteEdge;
Using M_Curve:GetControlHandle / SetControlHandle to manipulate handles directly;
Setting correctBezierHandles = false in M_Mesh:AddPoint;
Setting the last parameter of M_Mesh:DeleteEdge to false;
Directly manipulating the five sub-channels of the “Point Curvature” animation channel;
Changing the order of curvature restoration (before deletion, after deletion, and both).
My question:
Is there a correct way to split a point on a curve while preserving its curvature? Or is this a limitation of the current scripting API? If it is a limitation, is there an officially recommended alternative (e.g., duplicating the layer and deleting points from copies)?
The plugin code is available upon request. Any guidance or suggestions would be greatly appreciated.
Thank you!
Code: Select all
-- ss_split_point.lua
-- Implements a true break at a point on a curve in Moho, preserving the shape by saving/restoring the full curvature sub-channels.
-- Installation location: User Documents/Moho Pro/scripts/tool/
-- Usage: Select a point with the "Select Points" tool, then activate this tool and click once on the canvas.
ScriptName = "SS_SplitPoint"
SS_SplitPoint = {}
function SS_SplitPoint:Name()
return "Split Point (Real)"
end
function SS_SplitPoint:Version()
return "7.0"
end
function SS_SplitPoint:Description()
return "Break a curve at the selected point, preserving the full curvature sub-channels."
end
function SS_SplitPoint:IsEnabled(moho)
if (moho.layer:LayerType() ~= MOHO.LT_VECTOR) then return false end
if (moho:CountSelectedPoints() < 1) then return false end
return true
end
---------------------------------------------------------------------
-- Core logic
---------------------------------------------------------------------
function SS_SplitPoint:OnMouseDown(moho, mouseEvent)
local layer = moho:LayerAsVector(moho.layer)
if (layer == nil) then return end
local mesh = layer:Mesh()
if (mesh == nil) then return end
local frame = moho.frame
-- 1. Get the first selected point
local selPtID = -1
local selList = MOHO.SelectedPointList(mesh)
for i, pt in ipairs(selList) do
selPtID = mesh:ClosestPoint(pt.fPos)
break
end
if (selPtID < 0) then return end
local selPoint = mesh:Point(selPtID)
local posA = LM.Vector2:new_local()
posA:Set(selPoint.fPos.x, selPoint.fPos.y)
-- 2. Find the curve and segment that this point belongs to
local targetCurveID, targetSegID = -1, -1
for curveID = 0, mesh:CountCurves() - 1 do
local curve = mesh:Curve(curveID)
if (curve ~= nil) then
for segID = 0, curve:CountSegments() - 1 do
local p1 = LM.Vector2:new_local()
local p2 = LM.Vector2:new_local()
local p3 = LM.Vector2:new_local()
local p4 = LM.Vector2:new_local()
curve:GetControlPoints(segID, p1, p2, p3, p4, false)
if (self:VecEqual(p1, posA) or self:VecEqual(p4, posA)) then
targetCurveID = curveID
targetSegID = segID
break
end
end
end
if (targetCurveID >= 0) then break end
end
if (targetCurveID < 0 or targetSegID < 0) then
moho:UpdateUI()
return
end
local curve = mesh:Curve(targetCurveID)
if (curve == nil) then return end
-- 3. Check whether this point is a curve endpoint (connected to only one segment)
if (curve:CountSegments() <= 1) then
return
end
-- 4. * Save the full curvature data of the original point (five sub-channels)
local curvePtID_A = curve:PointID(selPoint)
if (curvePtID_A < 0) then
moho:UpdateUI()
return
end
-- Save the curvature itself
local savedCurvature = curve:GetCurvature(curvePtID_A, frame)
-- Save the front side (near point a) weight and offset
local savedWeightIn = curve:GetWeight(curvePtID_A, frame, true)
local savedOffsetIn = curve:GetOffset(curvePtID_A, frame, true)
-- Save the back side (near point e) weight and offset
local savedWeightOut = curve:GetWeight(curvePtID_A, frame, false)
local savedOffsetOut = curve:GetOffset(curvePtID_A, frame, false)
-- 5. Add a new point B at the original position, disabling Bezier handle auto-correction
mesh:AddPoint(posA, selPtID, targetSegID, false, frame, false)
-- 6. Find the mesh ID of the newly added point B
local newPtID = -1
for i = 0, mesh:CountPoints() - 1 do
local pt = mesh:Point(i)
if (pt ~= nil and self:VecEqual(pt.fPos, posA) and i ~= selPtID) then
newPtID = i
break
end
end
if (newPtID < 0) then
moho:UpdateUI()
return
end
-- 7. Find the segment between A and B and delete it
local foundSeg = false
for curveID = 0, mesh:CountCurves() - 1 do
local c = mesh:Curve(curveID)
if (c ~= nil) then
for segID = 0, c:CountSegments() - 1 do
local p1 = LM.Vector2:new_local()
local p2 = LM.Vector2:new_local()
local p3 = LM.Vector2:new_local()
local p4 = LM.Vector2:new_local()
c:GetControlPoints(segID, p1, p2, p3, p4, false)
if (self:VecEqual(p1, posA) and self:VecEqual(p4, posA)) then
mesh:DeleteEdge(curveID, segID, frame, false)
foundSeg = true
break
end
end
end
if (foundSeg) then break end
end
-- 8. * After deleting the segment, restore the saved curvature data to the new point B
local finalCurve = mesh:Curve(targetCurveID)
if (finalCurve == nil) then
moho:UpdateUI()
return
end
local newPoint = mesh:Point(newPtID)
if (newPoint == nil) then
moho:UpdateUI()
return
end
local curvePtID_B = finalCurve:PointID(newPoint)
if (curvePtID_B < 0) then
moho:UpdateUI()
return
end
-- Restore curvature
finalCurve:SetCurvature(curvePtID_B, savedCurvature, frame)
-- Restore the front side (near point a) weight and offset
finalCurve:SetWeight(curvePtID_B, savedWeightIn, frame, true)
finalCurve:SetOffset(curvePtID_B, savedOffsetIn, frame, true)
-- Restore the back side (near point e) weight and offset
finalCurve:SetWeight(curvePtID_B, savedWeightOut, frame, false)
finalCurve:SetOffset(curvePtID_B, savedOffsetOut, frame, false)
moho:UpdateUI()
end
---------------------------------------------------------------------
-- Helper function: compare two Vector2 values for equality (tolerance 1e-4)
---------------------------------------------------------------------
function SS_SplitPoint:VecEqual(a, b)
if (a == nil or b == nil) then return false end
local dx = a.x - b.x
local dy = a.y - b.y
return (dx * dx + dy * dy) < 1e-8
end
---------------------------------------------------------------------
-- Standard tool functions
---------------------------------------------------------------------
function SS_SplitPoint:Run(moho) end
function SS_SplitPoint:DoLayout(moho, layout) end
function SS_SplitPoint:OnMouseMove(moho, mouseEvent) end
function SS_SplitPoint:OnMouseUp(moho, mouseEvent) end
function SS_SplitPoint:OnKeyDown(moho, keyEvent) end
function SS_SplitPoint:OnKeyUp(moho, keyEvent) end
function SS_SplitPoint:OnSetCursor(moho, mouseEvent)
mouseEvent:SetCursor(MOHO.CROSSHAIR)
end
function SS_SplitPoint:OnDelete(moho) end
function SS_SplitPoint:OnScriptError(moho) end
return SS_SplitPoint