3
$\begingroup$

I'd like to to perform translations on objects within the UV Editor via a Python script. Attempts so far have only affected the 3D Viewport.


From a user-based, non-scripted perspective, there are two methods I'm aware of: (1) toggle to transform mode and drag the object (2) adjust the X/Y values in UV Editor > UV Vertex.

However, neither of these methods print useful commands to the console.

Transform

This method prints a standard transform command, which if replicated only affects the 3D Viewport

bpy.ops.transform.translate(value=(0.16838, -0.0870929, 0), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=True, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)

UV Transform

UV Vertex

And oddly, adjusting the UV Vertex values will print nothing to the console.

enter image description here


API Documentation

The UV operators documentation is where I would expect a solution, but it makes no mention of translations.

I've noticed space related parameters in the bpy.ops.transform.translate, but it seems they have more to do with the values of the translation rather than its context.

I'm currently exploring bmesh, but admittedly, I'm not entirely sure I understand the concept. A plugin I'm reading through from 2.79 does something like this:

bmesh.faces[0].loops[0].uv += vector

Any help is much appreciated. Thanks for taking the time.

$\endgroup$
3
$\begingroup$

The following is a response I received from Testure on Blender DevTalk:

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

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( (10.0, 10.0) )

    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.

$\endgroup$

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.