# Getting a closest point to 3d cursor gives a wrong result

I'm trying to find a point on an edge closest to 3d cursor with .intersect_point_line() but I'm getting a wrong point as a result, here I create an Empty in the result coordinate.

What am I doing wrong? When I'm printing out the results the distances are correct but it looks like verts indices are wrong..? My code:

import bpy
import bmesh
import math
import mathutils

def main():
ob = bpy.context.view_layer.objects.active
if not bpy.context.view_layer.objects.active:
return
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)

cursor_pos= bpy.context.scene.cursor.location.copy()
bm.faces.ensure_lookup_table()

print("start")
print("cursor_location: ", cursor_pos)

dist = 1000000
good_res = None

for index, edge in enumerate(bm.faces[0].edges):
res = mathutils.geometry.intersect_point_line(cursor_pos, edge.verts[0].co, edge.verts[1].co)
if res[1] < dist:
dist = res[1]
good_res = res[0]

bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=good_res, scale=(1, 1, 1))

main()


Global coords, and calculate the distance.

Firstly mesh coordinates are local and the scene cursor location is global. Either convert scene cursor to local

scene_cursor_local = ob.matrix_world.inverted() @ scene.cursor.location


and then the result back to global when adding the empty.

ob.matrix_world @ good_pos


This would require only 2 matrix multiplications.

or convert the mesh coordinates to global (have done this below)

bm.transform(ob.matrix_world)


since we are not writing back to the bmesh so it will not be reflected permanently.

What closest point on line does.

>>> geometry.intersect_point_line(
intersect_point_line(pt, line_p1, line_p2)
.. function:: intersect_point_line(pt, line_p1, line_p2)
Takes a point and a line and returns a tuple with the closest point on the line and its distance from the first point of the line as a percentage of the length of the line.
:arg pt: Point
:type pt: :class:mathutils.Vector
:arg line_p1: First point of the line
:type line_p1: :class:mathutils.Vector
:arg line_p1: Second point of the line
:type line_p1: :class:mathutils.Vector
:rtype: (:class:mathutils.Vector, float)


Test runs.

>>> geometry.intersect_point_line((0, 0, 1), (0, 0, 0), (0, 0, 2))
(Vector((0.0, 0.0, 1.0)), 0.5)

>>> geometry.intersect_point_line((0, 1, 1), (0, 0, 0), (0, 0, 2))
(Vector((0.0, 0.0, 1.0)), 0.5)

>>>


It finds returns the point on the line closest to the point, and what percentage this is from the start point, ie 0 is the start point, 1 the other end. It does not go outside the line.

In example above notice both have the same result, whereas one is right in the middle of the line, the other is not. Also evident in question image, result is least ratio from corner aka edge vert 0. Flipping the normals will change the vertex order will again shift that result, if it is 10% from corner (as is) will be 90% way with verts wound the other way.

Instead calculate the distance by subtracting the hit from the cursor location.

Test code:

Have changed up code to add empty at closes point of all edges, rather than only that of zeroth face.

import bpy
import bmesh
from mathutils.geometry import intersect_point_line as ipl

def main(scene, ob):
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)
bm.transform(ob.matrix_world) # put in global coords
cursor_pos = scene.cursor.location.copy()

print("start")
print("cursor_location: ", cursor_pos)

dist = 1000000
good_res = None

for edge in bm.edges:
hit, pc = ipl(cursor_pos, edge.verts[0].co, edge.verts[1].co)
d = (cursor_pos - hit).length
if d < dist:
dist = d
good_res = hit

type='PLAIN_AXES',
align='WORLD',
location=good_res,
)

main(bpy.context.scene, bpy.context.object)


Other constructs for min / max

Instead of the set a super high value as max and test during each iteration, there are other ways to find a minimum and maximum (numpy is extremely good at this)

Another is to make a list comprehension of all the hits and sort them. The zeroth will be the closest, the last the furthest away.

    hits = sorted(
[ipl(cursor_pos, edge.verts[0].co, edge.verts[1].co)[0]
for edge in bm.edges],
key = lambda p : (p - cursor_pos).length
)