Have UV Editor object translations been extended to the Python API (Blender 2.80)?

There is a much more detailed breakdown on Blender StackExchange of the problem at-hand, but TLDR:

I haven’t been able to translate objects through the UV Editor via Python yet, but maybe it’s possible via BMesh. Am I on a fools errand?

It’s not only possible to do using bmesh, it is the correct way to do it.

1 Like

Now that I’m not on my phone I can post a bit more details if you still need help. In Blender, UVs are stored in loops, and loops can only be accessed through bmfaces. It’s super weird, but here’s a really rough breakdown of how it works:

First of all, when I talk about “loops” i’m not talking about edgeloops. In Bmesh, each face has N loops (where N is the number of edges in the face. Think of a loop as being a vert with a pointer to the next and previous verts that make up the face’s boundary. A loop is also like Bmesh’s attic. Any type of data that is not raw geometry coordinates gets crammed in there and you have to spend a lot of time digging through crap to find what you need (I could go on a pretty good rant about this but I digress). Anyway, that’s oversimplifying it a bit, but that should help you understand what I’m talking about.

OK so once you have a loop, you have to specify what loop layer you want data from. In your case, you want UVs- so you have to get and verify the UV layer using bm.loops.layers.uv.verify(). You’ll need that to manipulate UVs on any given loop.

Another tip- BMesh has no concept of UV islands or UV boundaries, or anything like that. If you have two quad faces joined by one edge, you might think that there are 6 verts, and thus- 6 UVs. You would be incorrect, there are two quads, so there are FOUR loops per face, and therefore 8 loops (thus, 8 UVs). If you want to “weld” two Uvs together, you simply give them the same coordinate, as long as they share a vertex Blender will treat them like they’re welded when a user goes to click on it in the UV editor. This is important to note- because if you’re trying to transform a vert’s UVs, it likely has around 4 UVs that all need to be transformed with it, or you’ve effectively “unwelded” the UVs and created a new split in the uv island.

I threw this simple script together, it should help you understand the nuance a bit better:

import bpy
import bmesh

from mathutils import Vector

def main():
    me = bpy.context.edit_object.data
    bm = bmesh.from_edit_mesh(me)    
    
    uv_layer = bm.loops.layers.uv.verify()
    
    # it's useful to organize your UVs into a dictionary if you're manipulating a lot of stuff all at once,
    # otherwise you're going to be looping through bmface.loops a LOT.
    uv_verts = {}

    for face in bm.faces:
        print(f"Face #{face.index}")
        for loop in face.loops:
            print(f"\tv{loop.vert.index}: {loop[uv_layer].uv}")
            if loop.vert not in uv_verts:
                uv_verts[loop.vert] = [ loop[uv_layer] ]
            else:
                uv_verts[loop.vert].append( loop[uv_layer] )
    
    # now that we have a dictionary of UV verts, it's fairly simple to do transformations on the UVs on a per vertex basis
    # here we take all of the selected UV verts and move them up and to the right by .1
    for vert in uv_verts:
        for uv_loop in uv_verts[vert]:
            if uv_loop.select:
                uv_loop.uv += Vector( (0.1, 0.1) )

    bmesh.update_edit_mesh(me, False, False)

if bpy.context.object.mode == "EDIT" and bpy.context.object.type == "MESH":
    main()

Also, this is a really old doc so some of the info might be out of date/incorrect, but it was useful for me when I was trying to understanding how BMesh actually works: https://archive.blender.org/wiki/index.php/Dev:Source/Modeling/BMesh/Design/

2 Likes

Thanks for taking the time @testure . I’ll be full steam ahead implementing first thing Monday morning.

@testure I’ve copy pasted your response as an answer on Blender StackExchange. I’m holding off on marking it as the correct answer in case you’d like to add yours instead to get the points

No need, I don’t do the points thing :slight_smile:

2 Likes

really helpful :slight_smile:
Is all that required just to do a transform operation in the UVs?
I thought bpy.ops.transform.translate with UV editor window active would be enough?..
Thanks!

@CLss Afraid so. If you try bpy.ops.transform.translate transforms will occur - but they’ll be occurring in the 3D Viewport. The UV editor uses BMesh, which seems to think of objects in terms of their components.

1 Like

thanks for clarify.
will try this later.

Sorry, I’m just starting out in bpy…
Let’s say I would like to use @testure code within a function.
Could u tell me how and where should I put two arguments for Vector( (0.1, 0.1) ) ?
I mean, to replace 0.1 for whatever value I would need to later when the function’s been called.
Thanks a million, again.

@CLss here’s roughly the related function I ended up adapting from testure’s code. if you have any further questions about add-on architecture, I recommend you check the docs, and maybe download a free add-on or two to see how they set theirs up.

import bpy
import bmesh
from mathutils import Vector

# function
def bmeshTransformUv(x_vector,y_vector):
    if bpy.context.object.mode == "EDIT" and bpy.context.object.type == "MESH":
        main_element = bpy.context.edit_object.data
        bm = bmesh.from_edit_mesh(main_element)
        uv_layer = bm.loops.layers.uv.verify()
        # organize UVs into a dictionary to reduce bmface looping
        uv_verts = {}
        for face in bm.faces:
            for loop in face.loops:
                if loop.vert not in uv_verts:
                    uv_verts[loop.vert] = [ loop[uv_layer] ]
                else:
                    uv_verts[loop.vert].append( loop[uv_layer] )
        # take all of the selected UV verts and move them as requested
        for vert in uv_verts:
            for uv_loop in uv_verts[vert]:
                if uv_loop.select:
                    uv_loop.uv += Vector( (x_vector,y_vector) )
        bmesh.update_edit_mesh(main_element, False, False)

# how to call it (you'd probably replace 0.0 with some other dynamic function)
y_transform = 0.0
x_transform = 0.0
bmeshTransformUv(x_transform,y_transform)
1 Like

This works flawlessly.
I’ll continue my coding journey
Thanks again guys!

I think you probably want to be using ‘and’ here, not ‘or’. As a related aside, you can shorthand this by just checking for context.mode == ‘EDIT_MESH’

1 Like