I have a UV map. I want to get texture pixels around connected mesh sides like this (selected in pink and red):

enter image description here

or even on separated islands like this: enter image description here

How to do such thing with Python Blender API?

In theory I understand that one shall do something with bpy.ops.uv.select_border yet what to do then:

  1. How to get UVs that shall be "on the other side of the edge" for some part of border UVs?
  2. How to get pixel coordinates on the texture?
  • $\begingroup$ Why am I doing this: I want to be able to do inpaint (content aware fill) around UV edges for use with square material textures like this one $\endgroup$ – Blender Mar 22 '20 at 19:16
  • $\begingroup$ I'm not sure to understand what you ask. For 1/ each concerned edge, its faces and for each face the uv coordinates of the edge? 2/ What is the relation with the indicated link? $\endgroup$ – lemon Mar 23 '20 at 13:11
  • $\begingroup$ @lemon: please look at updated Question, hope this clears out the details of what I mean. Basically given any of the pink UV edges, how to select (1) pixels on the texture that surrounds it, (2) edge it will be connected to on a model and is not connected directly on UV map (is on a different location than the first one), (3) and surrounding pixels of (2) $\endgroup$ – Blender Mar 23 '20 at 18:55
  • $\begingroup$ Ok, I'll try an answer. But refering to the links in the question, not sure this is what you want... we'll see. $\endgroup$ – lemon Mar 23 '20 at 19:40

Make a "UV" bmesh

enter image description here

Given the mesh object above with UV, make a bmesh from the uv's

  • loop faces
  • loop face loops add a vert to new bmesh at uv.x, uv.y, 0
  • create a face from verts
  • remove doubles
  • (optionally) dissolve non boundary edges
  • (optionally) remove faces.
  • added an integer layer to both verts and faces with the index of the orignal verts and faces.

Test script, run in object mode

import bpy
import bmesh

context = bpy.context
ob = context.object
me = ob.data
bm = bmesh.new()
uvbm = bmesh.new()

uv_layer = bm.loops.layers.uv.verify()
vert_index = uvbm.verts.layers.int.new("index")
face_index = uvbm.faces.layers.int.new("index")
# adjust uv coordinates
for face in bm.faces:
    fverts = []
    for loop in face.loops:
        uv = loop[uv_layer].uv
        v = uvbm.verts.new((uv.x, uv.y, 0))
        v[vert_index] = loop.vert.index
    f = bmesh.ops.contextual_create(uvbm, geom=fverts)["faces"].pop()
    f[face_index] = face.index

# remove doubles

bmesh.ops.remove_doubles(uvbm, verts=uvbm.verts, dist=1e-7)


# ignore face indices of original if using any option here
# optionally disolve non boundary edges

        edges=[e for e in uvbm.edges if not e.is_boundary],

# optionally remove faces

faces = uvbm.faces[:]
while faces:
#make an object to see it
me = bpy.data.meshes.new("UVEdgeMesh")
ob = bpy.data.objects.new("UVEdgeMesh", me)
ob.show_wire = True

# make a LUT based on verts of original
from collections import defaultdict

edge_pairs = defaultdict(list)
boundary_edges = [e for e in uvbm.edges if e.is_boundary]

for e in boundary_edges:
    key = tuple(sorted(v[vert_index] for v in e.verts))

# print result, add text object to show matching edges

for key, edges in edge_pairs.items():

    print(key, [e.index for e in edges]) 

    for e in edges:
        if not e.is_boundary:
        f = e.link_faces[0]
        p = (e.verts[0].co + e.verts[1].co) / 2
        p += (f.calc_center_median() - p) / 4
        bpy.ops.object.text_add(radius=0.04, location=p)
        bpy.context.object.data.body = f"{key}"

Creates a bmesh, here it is converted to a mesh

enter image description here

The xy coordinates of the "UV" bmesh are UV coordinates. The only edges are uv boundary edges. Convert to pixel coordinates using the data.

Uses the method here to associate verts with original using the method here https://blender.stackexchange.com/a/70729/15543 to store the indices of original verts / edges in a data layer of "UV" bmesh.

Output on example above. First row, the two edges that are made from vertices 0 and 1 from original mesh are edges 0 and 14 in "UV" bmesh.

(0, 1) [0, 14]
(0, 2) [1, 17]
(2, 3) [2, 4]
(1, 3) [3, 22]
(2, 6) [5, 16]
(6, 7) [6, 8]
(3, 7) [7, 23]
(4, 6) [9, 19]
(4, 5) [10, 12]
(5, 7) [11, 20]
(0, 4) [13, 18]
(1, 5) [15, 21]

EDIT: To visualize this further, added a text object to each face from each edge. Eg see where the edge made of vertices (i, j) is matched on two faces. Each text object is located inset one quarter of the way from edge centre to face centre.

enter image description here Alternatively could have simply printed original vert indices at verts

Remember the uvbmesh is in UV coordinates mapped in the real number range [0, 1] in U and V. The pixel coordinates is simply mapping this into discrete integer range based on image dimension.

  • $\begingroup$ This approach seems to miss the part where I need not only one part of the edge but also the edge/pixels it is connected to? $\endgroup$ – Blender Mar 24 '20 at 7:33
  • $\begingroup$ Please take the second half of the reward here $\endgroup$ – Blender Mar 24 '20 at 10:15
  • $\begingroup$ Cheers. Pasted answer there too. Added note to remove using the text objects via operator on such a detailed UV. $\endgroup$ – batFINGER Mar 24 '20 at 10:32

A proposal script for this question.

The script is commented below and the main function get_boundary_uv_coords will get an array of instances of EdgeUV that have the following fields:

  • The concerned edge index edge_index
  • The corresponding vertices: v1_index and v2_index
  • The first face index: face1_index
  • The uv coords the each vertex of this face: uv11 and uv12
  • The second face index: face2_index
  • The uv coords the each vertex of this face: uv21 and uv22

From that, you'll have all the edges that correspond to a seam.

I've choosed to give all things as indexes so that you can use directly mesh or bmesh information after that.

Additionally, the two last functions uv_to_image and pixel_from_co will respectively get the image coordinates from uv coordinate and RGBA values from image coordinates.

The principles of the script are:

  • Creating a BMesh from the mesh object
  • Get boundaries: edges that have 2 parts on the uv map (the mesh is supposed to be manifold: all edges have maximum of two faces)
  • From these boundaries, look to the corresponding uv map coordinates, using the currently selected uv layer.

The script.

import bpy
import bmesh
from mathutils import Vector

#Store key information using indexes so that you can use it either in bmesh or mesh
class EdgeUV:
    def __init__(self, edge_index, v1_index, v2_index,
        face1_index, uv11, uv12, face2_index, uv21, uv22):

        self.edge_index = edge_index
        self.v1_index = v1_index
        self.v2_index = v2_index
        self.face1_index = face1_index
        self.uv11 = uv11
        self.uv12 = uv12
        self.face2_index = face2_index
        self.uv21 = uv21
        self.uv22 = uv22

    def __str__(self):
        return "<{self.edge_index}/{self.v1_index}/{self.v2_index}: [{self.face1_index}: {self.uv11}, {self.uv12}] [{self.face2_index}: {self.uv21}, {self.uv22}]>".format(self=self)

#Get the concerned edges
#The one that should be seams and correspond to two faces
def get_island_boundary_edges(bm):
    #Store the current seams
    current_seams = [e for e in bm.edges if e.seam]
    #Clear seams
    for e in current_seams:
        e.seam = False
    #Make seams from uv islands
    #Get the result
    boundaries = [e for e in bm.edges if e.seam and len(e.link_faces) == 2]
    #Restore seams
    for e in current_seams:
        e.seam = True
    return boundaries

#Get the uv coordinate for the vertices in the uv loop of the face
def get_uvs(face, uv_layer, v1, v2):
    uv1, uv2 = None, None
    for loop in face.loops:
        if loop.vert == v1:
            uv1 = Vector(loop[uv_layer].uv)
        elif loop.vert == v2:
            uv2 = Vector(loop[uv_layer].uv)
        if uv1 and uv2:
            return uv1, uv2
    return Vector((-1, -1)), Vector((-1, -1))

#Get the complete result in form of array of EdgeUV
def get_boundary_uv_coords(context, object):
    bpy.ops.object.mode_set(mode = 'EDIT')

    #Make bmesh object
    bm = bmesh.from_edit_mesh(object.data)

    #Get the wanted boundaries
    boundary_edges = get_island_boundary_edges(bm)

    #Current uv layer    
    uv_layer = bm.loops.layers.uv.active

    result = []

    for edge in boundary_edges:
        #Get the faces
        face1 = edge.link_faces[0] # We know to have 2 faces 
        face2 = edge.link_faces[1]

        #Get the vertices
        v1 = edge.verts[0]
        v2 = edge.verts[1]

        #UV coordinates for face1 and v1, v2
        uv11, uv12 = get_uvs(face1, uv_layer, v1, v2)

        #UV coordinates for face2 and v1, v2
        uv21, uv22 = get_uvs(face2, uv_layer, v1, v2)

        edge_uv = EdgeUV( edge.index, v1.index, v2.index, face1.index, uv11, uv12, face2.index, uv21, uv22 )
        result.append( edge_uv )

    bpy.ops.object.mode_set(mode = 'OBJECT')

    return result

#Converts uv coordinates to image size
def uv_to_image(image, uv):
    w, h = image.size
    return (round(uv[0] * (w-1)), round(uv[1] * (h-1)))

#Get image pixel
def pixel_from_co(image, co):
    w = image.size[0]
    i = 4 * (co[1] * w + co[0])
    return img.pixels[index:index+3] #RGBA

uvs = get_boundary_uv_coords(bpy.context, bpy.context.active_object)

for uv in uvs:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.