(Rewritten) Delete vertices from edges, keeping textures.

(Please scroll down for a script that works)

Someone asked how to remove a vertex from the middle of an edge while keeping the uv mapping intact, https://blenderartists.org/forum/viewtopic.php?t=32961

In fact that should be the default behavior of the merge vertex tool, I think, only a few more lines of code would be needed.

I thought I could use such a tool, because after many repeated removals of vertices using merge tool, the uv map gets totally ruined. So I came with this little script which works like the ‘at cursor’ option of the merge tool (alt-m), first selecting the vertex where the merging will happen, then placing the cursor (shift-s) on this vertex and then selecting the vertices which are to be removed.

Take for example, this:
http://img127.exs.cx/img127/8932/1885.jpg

Which after three merge operations becomes a cleaner object, while keeping the textures:
http://img120.exs.cx/img120/2342/2501.jpg

However it only works for objects for wich size and rotation have been applied, and I i don’t know how to convert the global cursor position into local object coordinates. If anyone knows how to do that, I will appreciate your help.


import Blender

fpu= 1.0/65536

Blender.Window.EditMode(0)

curspos=Blender.Window.GetCursorPos()

ob=Blender.Object.GetSelected()
if len(ob)==1:
  if ob[0].getType()=='Mesh':
    mesh=ob[0].getData()

    # Is there a way to quikly get selected vertices?
    merged=[]
    mergepoint=0

    #Find vertex at cursor position.
    for vrt in mesh.verts:
      if vrt.sel:
        if abs(vrt.co[0]-curspos[0])<fpu:
          if abs(vrt.co[1]-curspos[1])<fpu:
            if abs(vrt.co[2]-curspos[2])<fpu:
              mergepoint=vrt
        #Or other selected vertices.
        else:

          merged.append(vrt)

    faces1=[]
    faces2=[]

    if (mergepoint!=0)&(len(merged)):
      for face in mesh.faces:
        for v in merged:
          if (v in face.v)&(mergepoint not in face.v):
            faces2.append((face,v))

          if mergepoint in face.v:
            faces1.append(face)

      #List of deletable vertices and faces.
      remverts=[]
      remfaces=[]

      for face1 in faces1:
        for face2 in faces2:
          v2=face2[1]
          if v2 in face1.v:
            #Another common vertex is needed to know if faces are adjacent.
            common=0
            for v in face2[0].v:
              if v==v2:
                continue

              if v in face1:
                common=1

            if common:
              i1=face1.v.index(mergepoint)
              i2=face2[0].v.index(v2)
              #Tampering with face vertices seems to be dangerous, so use copies.
              fv0=face1.v[:]
              fv2=face2[0].v[:]

              #Remove vertex from both polygons.
              fv0.remove(v2)
              fv2.remove(v2)
              if v2 not in remverts:
                remverts.append(v2)

              #Insert vertex at cursor to substitute old vertex.
              fv2.insert(i2,mergepoint)

              #Remove vertex from face containing merge point
              if len(fv0)>2:
                f1=Blender.NMesh.Face(fv0)
                mesh.faces.append(f1)
                f1.mode=face1.mode
                f1.transp=face1.transp
                f1.col=face1.col
                if face1.image:
                  f1.image=face1.image
                if len(face1.uv)!=0:
                  uv=face1.uv[:]
                  uv.pop(face1.v.index(v2))
                  f1.uv=uv

              if face1 not in remfaces:
                remfaces.append(face1)

              f2=Blender.NMesh.Face(fv2)
              mesh.faces.append(f2)
              f2.mode=face2[0].mode
              f2.transp=face2[0].transp
              f2.col=face2[0].col
              if face2[0].image:
                f2.image=face2[0].image
              if len(face2[0].uv)!=0:
                uv=face2[0].uv[:]
                #Insert uv vertex
                uv.insert(i2,face1.uv[face1.v.index(mergepoint)])
                #Remove old value
                uv.pop(i2+1)
                f2.uv=uv

              mesh.faces.remove(face2[0])

      for f in remfaces:
        mesh.faces.remove(f)

      for v in remverts:
        mesh.verts.remove(v)

      mesh.update()

Blender.Window.EditMode(1)


just find the selected vertex is not enough? why do you need cursor position?

Yes, I just had realized that, why did I even go the way Blender does it? It is a bad idea.

Just selecting a vertex along an edge should be enough, I’ll work on that.

A great addition to blender I’m sure, but what is the variable name “fpu” short for and what does it’s values represent?

A great addition to blender I’m sure, but what is the variable name “fpu” short for and what does it’s values represent?

Maybe not the most adequate name for a variable (Floating Point Unit, the math coprocessor), on some other scripts it is called epsilon or “smal number”, and it is given different values. It is a margin of error, since you can’t always trust that two variables will have the exact same value. This will expalin better than I can: http://docs.python.org/tut/node15.html

Ahhh, but of course. Your choice of name for this variable makes sense to me now. I just couldn’t see it, and thanks for the link. I took a peak at it but feel it’s a little too much for my old brain right now :expressionless:

Maybe one last explaination!?
The value 65536 put into the fpu varable still baffels me. i.e I can’t grasp what this number represent. Why this particular value? Where does it come from? Ohh, I feel sooo n00bish! :frowning:

As for the purpose of this script. I think you should finish it without the “at cursor” requirement (as you planed) and put it to blender foundation to implement it in a future release. That is how usefull I think this is! 8)

Please keep up the good work!

(Five blushing faces) More than a hundred hits and it doesn’t even work…

I totally rewrote the whole thing from scratch. The version above works only if the object is scaled to 1, rotated to 0,0,0 and has its center at (0,0,0) (Didn’t even subtract the object center coordinates from the cursor’s), and it didn’t take care of cases where the vertices to be merged were shared by several polygons.

So here is a new one. It works a lot better. I only still don’t know why vertex colors are not copied correctly. The bad thing is with some objects I get segmentaion faults (Blender crashes) when entering and leaving edit mode. I guess something gets corrupted.

Usage: Select the vertex you want removed (must be on an edge or the script will fail) and run the script.

import Blender

fpu= 1.0/65536

def succ(i,n):
    i+=1
    if i==n:
      i=0
    return i

def pred(i,n):
    i-=1
    if i<0:
      i=n-1
    return i

#Create new faces with exchanged vertices:
def newface(face,newvertlist,uv,vrtcol):
    #Create new face.
    f1=Blender.NMesh.Face(newvertlist)
    mesh.faces.append(f1)

    #Inherit attributes from old faces.
    f1.mode = face.mode
    f1.transp = face.transp
    f1.col = vrtcol
    f1.smooth = face.smooth
    if face.image:
        f1.image = face.image
    if len(face.uv)!=0:
        f1.uv=uv

    return f1

# I will assume this vertex shares same uv coordinates for polygons.
def findsameuv(f0,facelist, v1, v2):
    for f in facelist:
        if (f0!=f)&(v2 in f.v):
            i1=f0.v.index(v1)
            i2=f.v.index(v1)
            if len(f0.uv):
                if (abs(f0.uv[i1][0]-f.uv[i2][0])<fpu)&(abs(f0.uv[i1][1]-f.uv[i2][1])<fpu):
                    return f

#v1: vertex to remove, v2: vertex at merge point.
def merge(facelist, v1, v2):

    for f in facelist:
        f2=f		#If this face contains target vertex, use itself.
        if v2 not in f.v:	#Do not test polygons which contain target vertex.
            f2=findsameuv(f,facelist,v1,v2)
        i2=f2.v.index(v2)
        fv = f.v[:]	#Copy of current face vertices.
        uv1 = f.uv[:]	#Copy of current uv coordinates.
        vcol = f.col[:]	#Copy of current vertex colors.
        i1=fv.index(v1)
        #Remove selected vertex from polygon.
        fv.remove(v1)

        #Vertex colors.
        vcol.remove(f.col[i1])
        vcol.insert(i1, f2.col[i2])

        #UV coordinates.
        if len(uv1):
            uv1.remove(uv1[i1])
            uv1.insert(i1, f2.uv[i2])

        #Insert target vertex.
        fv.insert(i1, v2)

        if len(fv)>2:
            # Create new faces with same attributes.
            nf1 = newface(f,fv,uv1,vcol)

#Find a polygon corner colinear to selected vertex.
def getmergepoint(faces, v1):

    for i in range(len(faces)-1):
        f0=faces[i]
        L1 = len(f0.v)
        i1 = f0.v.index(v1)
        #Two neighbor vertices of selected vertex.
        vf1 = [f0.v[pred(i1,L1)], f0.v[succ(i1,L1)]]

        for f in faces[i+1:]:
              L2 = len(f.v)
              i2 = f.v.index(v1)
              #Two neighbor vertices of selected vertex.
              vf2 = [f.v[pred(i2,L2)], f.v[succ(i2,L2)]]

              for cv1 in vf1:
                  if cv1 in f.v:	#Shared vertices not allowed
                      continue

                  for cv2 in vf2:
                      if cv2 in f0.v:	#Shared vertices not allowed
                          continue

                      vec1=Blender.Mathutils.Vector([cv1.co[0]-v1.co[0], cv1.co[1]-v1.co[1], cv1.co[2]-v1.co[2]])
                      vec2=Blender.Mathutils.Vector([cv2.co[0]-v1.co[0], cv2.co[1]-v1.co[1], cv2.co[2]-v1.co[2]])

                      #Are they collinear?
                      if (Blender.Mathutils.DotVecs(vec1,vec2)/vec1.length/vec2.length)+1<fpu:

                          if L1==3:
                              cornvert = cv1
                          else:
                              cornvert = cv2

                          #Break loop now
                          return cornvert

    return 0

Blender.Window.EditMode(0)

ob=Blender.Object.GetSelected()
if len(ob)==1:
    if ob[0].getType()=='Mesh':
        mesh=ob[0].getData()

        go=1
        while go:
            go=0

            # Get selected vertex.
            for v0 in mesh.verts:
                if v0.sel:
                    go=1

                    #List of faces which contain selected vertex.
                    linked=[]
                    for f in mesh.faces:
                        if v0 in f.v:
                            linked.append(f)

                    cv=0
                    if len(linked):
                        # Try to get vertex at merge point.
                        cv = getmergepoint(linked, v0)
                    if cv:
                        # Do the vertex exchange and removal.
                        merge(linked, v0, cv)
                        # Remove selected vertex from mesh.
                        mesh.verts.remove(v0)
                        # Remove old faces.
                        for f in linked:
                            mesh.faces.remove(f)
                    else:
                        Blender.Draw.PupMenu("Error:%t|Vertex not on edge")
                        go=0

        mesh.update()

Blender.Window.EditMode(1)


xyr@no:

65536 is 2 raised to the 16th power, that is, a bit shifted 16 places. I just choose a number whose internal representation would do a nice round binary number. But I would get the same result by using 0.00001 or something like that.

I think too this could be a simple but helpful addition to Blender, but I would be happy if only things worked as they should regarding mesh subdivisions and uv maps and weights.

Anyone?

Well, even if nobody finds it useful, I have been using it and it really works for me (an idea from another forum!). I am not using the merge tool anymore, I don’t need to move the cursor and there are no holes in my uv maps.

I find out it feels a lot nicer if I am not too strict on what “on an edge” means, that is, I use a big margin of tolerance. It becomes sort of a “delete from the straightest line” tool.

I wrote an update with nice fixes, it is now hosted here

http://img97.exs.cx/img97/6587/snapshot27.jpg

I would love to test this script out but your link doesn’t work…

http://kokcito.freeserverhost.net/rsve03.py

Well, this script is a nice start. I don’t think I will be using it much though because it doesn’t work consistently. Blender definetly needs more tools that preserve UVs. Hope to see more work on this!

TorQ