Help: Weld the two closest loose selected endpoints of unclosed curves

Moho allows users to write new tools and plugins. Discuss scripting ideas and problems here.

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
User avatar
Lukas
Posts: 1308
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Help: Weld the two closest loose selected endpoints of unclosed curves

Post by Lukas »

Hi everyone,

I could use some help on this code. What I'm trying to achieve is:

When you've got multiple unclosed curves, you can select a couple of endpoints (or just two entire curves), and the script should find the two selected endpoints closest to each other and weld them together.

So basically, it should weld the two closest loose selected endpoints.

So far I've got this code, and it works correctly in some cases, but in other cases it welds the points completely wrong and I can't seem to figure out why. I think the mistake has something to do with the "attachSeg" variable that I'm not getting right, but I'm kind of lost... 😑 If you try it with a couple of random unclosed curves you'll see it immediately.

Code: Select all

local mesh = moho:DrawingMesh()
moho.document:PrepUndo(moho.drawingLayer)
moho.document:SetDirty()
-- * Find end points:
local endpointIDs = {}
local endpoints = {}
for i = 0, mesh:CountPoints() - 1 do
	local pt = mesh:Point(i)
	if (pt.fSelected) then
		if pt:IsEndpoint() then
			table.insert(endpointIDs, i)
			table.insert(endpoints, pt)
		end
	end
end
--
local shortestDistance = 9999999
local movingPointID
local solidPointID
local movingPoint
local solidPoint
for i = 1, #endpoints do
	local firstEndPoint = endpoints[i]
	for j = 1, #endpoints do
		local otherEndPoint = endpoints[j]
		if firstEndPoint ~= otherEndPoint then
			if firstEndPoint:Curve(0) ~= otherEndPoint:Curve(0) or #endpoints == 2 then
				local distance = FO_Utilities:Distance(moho, firstEndPoint.fPos, otherEndPoint.fPos)
				if distance < shortestDistance then
					shortestDistance = distance
					movingPoint = firstEndPoint
					solidPoint = otherEndPoint
					movingPointID = mesh:PointID(firstEndPoint)
					solidPointID = mesh:PointID(otherEndPoint)
				end
			end
		end
	end
end
local pos = mesh:Point(movingPointID).fPos
-- * Find curve to attach copied point to:
local movingCurveID
local solidCurveID
for i = 0, mesh:CountCurves() - 1 do
	local curve = mesh:Curve(i)
	local curveID = mesh:CurveID(curve)
	if movingPoint:IsPointOnCurve(curveID) then
		movingCurveID = curveID
	end
	if solidPoint:IsPointOnCurve(curveID) then
		solidCurveID = curveID
	end
end
-- * Find curve and edge to attach new point to: TODO is this part wrong???
local attachCurveID
local attachSeg
attachCurveID, attachSeg = solidPoint:GetEndpointEdge(attachCurveID, attachSeg)
attachSeg = attachSeg + 1
-- * Add new point:
mesh:AddPoint(pos, attachCurveID, attachSeg, 0) -- * M_Mesh:AddPoint(pos, attachCurveID, attachSeg, frame, correctBezierHandles, preserveCorners)
local newPoint = mesh:Point(mesh:CountPoints()-1)
local newPointID = mesh:PointID(newPoint)
-- * Weld points:
print ("___welding:___")
print ("- movingPoint: "..movingPointID.." on curve "..movingCurveID)
print ("- solidPoint: "..solidPointID.." on curve "..solidCurveID)
print ("* newPoint: "..newPointID.." on curve "..attachCurveID.." (segment "..attachSeg..")")
mesh:WeldPoints(movingPointID, newPointID, 0)
-- *** Peak corners:
-- * Peak new point:
newPoint:SetCurvature(MOHO.PEAKED, moho.drawingLayerFrame)
newPoint:ResetControlHandles(moho.drawingLayerFrame)
-- * Peak existing end point:
solidPoint:SetCurvature(MOHO.PEAKED, moho.drawingLayerFrame)
solidPoint:ResetControlHandles(moho.drawingLayerFrame)
Any suggestions or pointers are appreciated!
User avatar
synthsin75
Posts: 10047
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by synthsin75 »

References FO_Utilities:Distance, not included.
User avatar
Lukas
Posts: 1308
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by Lukas »

synthsin75 wrote: Tue Apr 06, 2021 9:05 pm References FO_Utilities:Distance, not included.
Ow, missed that one... It's this:

Code: Select all

-- **************************************************
-- Get distance between vectors
-- **************************************************
function FO_Utilities:Distance(moho, vector1, vector2)
	local distance = vector1 - vector2
	distance = math.abs(distance:Mag())
	return distance
end
User avatar
synthsin75
Posts: 10047
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by synthsin75 »

I see the error. I guess my first question would be why do you even need to mess with segments at all? Shouldn't it just be checking for selected, endpoint proximity, add a point, and weld it to the other?

At first I thought ClosestPoint would work, but you can only ignore one point ID at a time with that.
User avatar
synthsin75
Posts: 10047
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by synthsin75 »

I don't know that this method is very bulletproof, as I haven't tested in extensively, but this is one idea:

Code: Select all

	local pt, pt2
	for i=0, mesh:CountPoints()-1 do
		pt = mesh:Point(i)
		if (pt.fSelected and pt:IsEndpoint()) then
			local pos = pt.fPos
			local cid = mesh:ClosestPoint(pos, i, -1, true)
			local cpt = mesh:Point(cid)
			if (cpt.fSelected and cpt:IsEndpoint()) then
				pt2 = cpt
				break
			else
				local count = 1
				while true do
					pos = cpt.fPos
					cid = mesh:ClosestPoint(pos, cid, -1, true)
					cpt = mesh:Point(cid)
					if (cpt.fSelected and cpt:IsEndpoint()) then
						break
					else
						count = count + 1
						if (count == moho:CountPoints()) then
							break
						end
					end
				end
				if (cpt.fSelected and cpt:IsEndpoint()) then
					pt2 = cpt
					break
				end
			end
		end
	end
	mesh:AddPoint(pt.fPos, mesh:PointID(pt2), frame)
	mesh:WeldPoints(mesh:CountPoints()-1, mesh:PointID(pt), frame)
User avatar
Lukas
Posts: 1308
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by Lukas »

synthsin75 wrote: Tue Apr 06, 2021 11:17 pm I guess my first question would be why do you even need to mess with segments at all? Shouldn't it just be checking for selected, endpoint proximity, add a point, and weld it to the other?
Ow jackpot, I really didn't know what I was doing.

Thanks Wes!

You're code is definitely more elegant and honestly harder to wrap my amateur head around. I tried it and had some trouble with some extra things I didn't mention. I wanted to exclude endpoint welding if they are part of the same curve. Unless no other endpoints of other curves were selected. That way you can use the script to close a single curve, but only if a single curve is selected, otherwise it would always weld 1 curve to another, even if their own endpoints are closest.

So now I'm doing it this way and it works! Thanks for your help as always!

Code: Select all

		local mesh = moho:DrawingMesh()
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
		-- * Find end points:
		local endpointIDs = {}
		local endpoints = {}
		for i = 0, mesh:CountPoints() - 1 do
			local pt = mesh:Point(i)
			if (pt.fSelected) then
				if pt:IsEndpoint() then
					table.insert(endpointIDs, i)
					table.insert(endpoints, pt)
				end
			end
		end
		--
		local shortestDistance = 9999999
		local movingPointID
		local solidPointID
		local movingPoint
		local solidPoint
		for i = 1, #endpoints do
			local firstEndPoint = endpoints[i]
			for j = 1, #endpoints do
				local otherEndPoint = endpoints[j]
				if firstEndPoint ~= otherEndPoint then
					if firstEndPoint:Curve(0) ~= otherEndPoint:Curve(0) or #endpoints == 2 then
						local distance = FO_Utilities:Distance(moho, firstEndPoint.fPos, otherEndPoint.fPos)
						if distance < shortestDistance then
							shortestDistance = distance
							movingPoint = firstEndPoint
							solidPoint = otherEndPoint
							movingPointID = mesh:PointID(firstEndPoint)
							solidPointID = mesh:PointID(otherEndPoint)
						end
					end
				end
			end
		end
		local pos = mesh:Point(movingPointID).fPos
		-- * Add point:
		mesh:AddPoint(pos, mesh:PointID(movingPoint), 0)
		local newPoint = mesh:Point(mesh:CountPoints()-1)
		local newPointID = mesh:PointID(newPoint)
		-- * Weld point:
		mesh:WeldPoints(newPointID, solidPointID, 0)
		-- *** Peak corners:
		-- * Peak new point:
		movingPoint:SetCurvature(MOHO.PEAKED, moho.drawingLayerFrame)
		movingPoint:ResetControlHandles(moho.drawingLayerFrame)
		-- * Peak existing end point:
		solidPoint:SetCurvature(MOHO.PEAKED, moho.drawingLayerFrame)
		solidPoint:ResetControlHandles(moho.drawingLayerFrame)
		-- * Select entire curve:
		self:HandleMessage(moho, view, self.FLOOD_SELECT)
		-- * Update widgets:
		self:UpdateWidgets(moho)
User avatar
synthsin75
Posts: 10047
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Help: Weld the two closest loose selected endpoints of unclosed curves

Post by synthsin75 »

Yeah, I only tested that on a pretty simple case, but I'm glad I was able to help. :wink:
Post Reply