Search code examples
pythonblender

How to get data and edit it on UV map edges?


I have a UV map: enter image description here

I want to get texture pixels around connected mesh sides like this (selected in red): enter image description here

How to do such thing with Python Blender API?


Solution

  • 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()
    bm.from_mesh(me)
    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
            fverts.append(v)
        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
    
    bmesh.ops.dissolve_edges(uvbm, 
            edges=[e for e in uvbm.edges if not e.is_boundary],
            )
    
    
    # optionally remove faces
    
    faces = uvbm.faces[:]
    while faces:
        uvbm.faces.remove(faces.pop())
    '''        
    #make an object to see it
    me = bpy.data.meshes.new("UVEdgeMesh")
    uvbm.to_mesh(me)
    ob = bpy.data.objects.new("UVEdgeMesh", me)
    bpy.context.collection.objects.link(ob)
    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))
        edge_pairs[key].append(e)
    
    # print result, add text object to show matching edges
    # Make sure to remove code below before running on detailed UV as in question, 
    # adding that many text objects via operator 
    # will slow code down considerably.
    
    uvbm.verts.ensure_lookup_table()
    for key, edges in edge_pairs.items():
    
        print(key, [e.index for e in edges]) 
    
        for e in edges:
            if not e.is_boundary:
                continue
            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.