MapVertsOnSurface_ver3.py

Dijkstra detection part is the same as dijkstra.py.
If isRing is True, only verts on the shortest pathes between selected 3 verts will be mapped, otherwise all verts mapped.

Now eF(evaluation function) is detachable.
Choose one from distBtwn and dist2plane, or write your own.

# ver.3 implemented Dijkstra

# let u, v vectors on the plane. b the origin on it.
# let a a point off plane. and n a normal of the plane.
import sys
import math
import bmesh
import numpy as np
################################################################################
# Dijkstra part (same as dijkstra.py)

# Evalation functions.
# Assign one to eF to use it.

def distBtwn(env, v0, v1):
    dX = v0.co[0] - v1.co[0] 
    dY = v0.co[1] - v1.co[1] 
    dZ = v0.co[2] - v1.co[2] 
    return math.sqrt(dX*dX + dY*dY + dZ*dZ) 

def dist2plane(env, v0, v1):
    return abs(np.dot(env.co_start-v1.co, env.nml))
    
class DjEnv:
    def __init__(self, nml, co_start, bmesh):
        self.co_start = co_start
        self.nml = nml
        self.bm = bmesh

class VDijkstra:
    def __init__(self, env, evalF, vert):
        self.id = vert.index
        self.v = vert
        self.bestParent = None
        self.cost2start = float('inf')
        self.costs = {}
        # init peers
        self.peers = set()
        for e in self.v.link_edges:
            v_other = e.other_vert(self.v)
            self.peers.add(v_other.index)
            self.costs[v_other.index] = evalF(env, self.v, v_other)
        print('vds['+str(self.id)+'].peers = '+str(self.peers))

#def procDijkstra(vds, H, A, start, goal):
def procDijkstra(vds, H, A, start, goal, depth=0):
    print('---------------------------------------------')
    print('depth: '+str(depth))
    if vds[goal].bestParent is not None:
        print('alreadily reached the goal.')
        return
    #
    d_min_i = None
    d_min_p = None
    d_best = float('inf')
    for i in A:
        vd = vds[i]
        w = vd.peers.intersection(H)
        #print('w = '+str(w))
        for p in w:
            vp = vds[p]
            d = vd.costs[p] + vp.cost2start
            if d < d_best:
                print('found best')
                d_best = d
                d_min_i = i
                d_min_p = p
    vds[d_min_i].cost2start = d_best
    vds[d_min_i].bestParent = d_min_p
    if d_min_i == goal:
        vds[goal].bestParent = d_min_p
        return
    #
    H.add(d_min_i)
    A.remove(d_min_i)
    A = A.union(vds[d_min_i].peers - H)
    #
    print('d_min_i = '+str(d_min_i))
    print('d_min_p = '+str(d_min_p))
    print('H = '+str(H))
    print('A = '+str(A))
    procDijkstra(vds, H, A, start, goal, depth+1)
    return

def djk(env, eF, start, goal):
    print('start = '+str(start), 'goal = '+str(goal))
    #bpy.ops.object.mode_set(mode="OBJECT")
    #o = bpy.context.scene.objects.active
    #b = o.data
    #bm = bmesh.new()
    #bm.from_mesh(b)
    #bm.verts.index_update()
    #
    bmv = env.bm.verts
    nVerts = len(bmv)
    vds = []
    for i in range(nVerts):
        vds.append(VDijkstra(env, eF, bmv[i]))
    #
    print("num verts = "+str(len(vds)))
    #
    H = set()
    A = set()
    # 
    H.add(vds[start].id)
    H = H.union(vds[start].peers)
    s = set()
    for i in H:
        s = s.union(vds[i].peers)
        vds[i].cost2start = distBtwn(None, vds[i].v, vds[start].v)
        vds[i].bestParent = start
    print('H = '+str(H))
    A = s - H
    print('A = '+str(A))
    #
    procDijkstra(vds, H, A, start, goal)
    i = goal
    while True:
        if not isinstance(i,int):
            print('*ERROR* i is invalid.')
            break
        elif i == start:
            print('reached start.')
            break
        #bpy.context.object.data.vertices[i].select = True
        env.bm.verts[i].select = True
        print(str(i)+' -> '+str(vds[i].bestParent))
        i = vds[i].bestParent
    bpy.ops.object.mode_set(mode="EDIT")
################################################################################

def mapOnPlane(a, env, b0, b1, b2):
    u = np.array([b1[0]-b0[0], b1[1]-b0[1], b1[2]-b0[2]])
    v = np.array([b2[0]-b0[0], b2[1]-b0[1], b2[2]-b0[2]])
    n = np.cross(u, v)
    tX = np.array([n, u, v])
    X = np.array([tX[:,0], tX[:,1], tX[:,2]])
    print(X)
#    X = np.array([[n[0], u[0], v[0]], [n[1], u[1], v[1]], [n[2], u[2], v[2]]])
#    print(X)
    Y = np.array([b0[0]-a[0], b0[1]-a[1], b0[2]-a[2]])
#    print(u,v,n,X,Y)
    s = np.linalg.solve(X, Y)
    return (a[0] + s[0]*n[0], a[1] + s[0]*n[1], a[2] + s[0]*n[2])

# If ringOnly is True, verts on the ring with selected-verts will be mapped.
# Only 1st to 3rd verts account selection.
# If False, the plane is the one which involves first three verts selected.
# eF = evaluation function
def pressLine(isRing=True, eF = distBtwn):
    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    ao = bpy.context.scene.objects.active
    bm = bmesh.from_edit_mesh(ao.data)
    vs = bm.verts
    # nt = total num of vs. ns = num of selected verts.
    nt = 0
    ns = 0
    bases = []
    for v in vs:
        nt += 1
        if v.select:
            ns += 1
            bases.append(v.index)
    print("verts="+str(nt)+" selected="+str(ns))
    #
    if ns != 3:
        print("Please select 3 vertices.")
        return
    #
    b0 = vs[bases[0]].co
    b1 = vs[bases[1]].co
    b2 = vs[bases[2]].co
    nml = np.cross(b1-b0, b2-b0)
    nml = nml/np.linalg.norm(nml)
    print('nml = '+str(nml))
    env = DjEnv(nml,b0,bm)
    if isRing:
        for i in range(3):
            #pass
            djk(env, eF, bases[i], bases[i-1])
    #
    for i in range(nt):
        #print(i, vs[i].co)
        if isRing is False or vs[i].select:
            vs[i].co = mapOnPlane(vs[i].co, env, vs[bases[0]].co, vs[bases[1]].co, vs[bases[2]].co)
        #print(i, vs[i].co)
    ao.data.update()