1

I have the following object: Source object

The UV grid on the left hand side is what my script currently does. Here is the code:

    def _process_shell_uv(self, object):
        mesh = object.data
        bm = bmesh.new()
        bm.from_mesh(mesh)
        bm.verts.ensure_lookup_table()
        bm.faces.ensure_lookup_table()
        uv_layer = bm.loops.layers.uv.verify()
        verts = bm.verts
    vert_groups = []

    for vert_idx in self.shell_verts['OuterShell']:
        inc = 0
        verts_in_loop = []
        for idx in range(0, 64):
            verts_in_loop.append(vert_idx + inc)
            inc += len(self.shell_verts['All'])

        vert_groups.append(verts_in_loop)

    a_y_co = 1
    b_y_co = 1
    c_y_co = 0
    d_y_co = 0

    for a, b in zip(vert_groups, vert_groups[1:]):
        faces_to_map = []
        a_x_co = 0
        b_x_co = 1
        c_x_co = 1
        d_x_co = 0 

        for idx in range(len(a)):
            if idx == len(a) -1:
                x = 0
                y = 0
            else:
                x = idx+1
                y = idx+1

            for face in bm.faces[:]:
                if verts[a[idx]] in face.verts[:]\
                and verts[b[idx]] in face.verts[:]\
                and verts[a[x]] in face.verts[:]\
                and verts[b[y]] in face.verts[:]:              
                    faces_to_map.append(face)


        for face in faces_to_map:
            lengths = [edge.calc_length() for edge in face.edges[:]]
            face.loops[0][uv_layer].uv = (a_x_co, a_y_co)
            face.loops[1][uv_layer].uv = (b_x_co, b_y_co)
            face.loops[2][uv_layer].uv = (c_x_co, c_y_co)
            face.loops[3][uv_layer].uv = (d_x_co, d_y_co)

            a_x_co += 1
            b_x_co += 1
            c_x_co += 1
            d_x_co += 1

        a_y_co += 1
        b_y_co += 1
        c_y_co += 1
        d_y_co += 1

    mesh.update()
    bm.to_mesh(mesh)
    bm.free()
    return

I am essentially taking each face and making a 1x1 UV mapping for it. Each vertical strip of the object is laid out horizontally, like this:

enter image description here

But now what I want to do is instead of each face being a 1x1 square in the UV map, I want it to be related to the original size of the faces; effectively I want to unwrap the entire thing to a flat plane, keeping all of the relative length differences. But I don't know what to do in my code to fix it.

The desired result is something like this: enter image description here

Where each vertical strip is more or less the same according to the geometry. Here is a closer look:

enter image description here

I'm trying to automate a UV unwrapping process so that all of my cylindrical objects follow this kind of format, and more or lesss give me the same result each time. I will add a texture afterwards and have manual control over the scale in a custom addon I am building.

Gorgious
  • 30,723
  • 2
  • 44
  • 101
DrewTNBD
  • 98
  • 10
  • 1
    "But I don't know what to do in my code" - it's hard to help you fixing your code (as opposed to just writing an entire new solution) if you don't paste whole code. For example, I can only guess what is self.shell_verts. In principle, your problem is simple: if the UV map should always consist of X/Y aligned grid, and the width of the cells is already correct, you can calculate the height of each cell as h = face.calc_area() / sum((f.calc_area() for f in column_of_faces)) – Markus von Broady Sep 05 '21 at 11:18
  • 2
    https://blender.stackexchange.com/questions/185087/transform-cylinder-to-flat-plane-cylinder-maze-map – batFINGER Sep 05 '21 at 13:23
  • I'm really sorry but I don't understand how either of those answers are pertinent to UV mapping. – DrewTNBD Sep 06 '21 at 11:45
  • A UV map is 2 dimensional, therefore it's a 3D figure flattened into a 2D shape. The Q&A mentioned by batFINGER is a similar process. Keep in mind, it's not the same, hence he didn't mark this question as a duplicate, just pointed to a related material. – Markus von Broady Sep 06 '21 at 12:04
  • I've noticed that if I look at my object from the front and run a Blender sphere projection, then scale it along the Y axis to make it tall enough to cover the full UV height space, then I get what I am looking for. So I guess I'm looking to do that at a low level. I don't understand the math in the posts above nor how to apply it to the UV space. I ran the example code and it worked here for duplicating and flattening out a mesh, but I'm having trouble attaching that to my use case. Sorry, I'm being a n00b! I don't expect the answer on a silver plate. I just don't understand what I've got. – DrewTNBD Sep 06 '21 at 13:39
  • have you considered a cylinder projection in ortho front view? The default cylinder UV would be this, plus project from view top and bottom for the caps. Mapping height to V and angle around equator ro U. – batFINGER Sep 06 '21 at 18:12
  • if your cylinder is aligned on the Z axis as it appears to be, something like bpy.ops.uv.align(axis='ALIGN_Y') followed by bpy.ops.uv.cylinder_project() if you have all of the vertices selected might work – Marty Fouts Sep 06 '21 at 19:48

1 Answers1

0

In the end I have settled for a bpy.ops solution:

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.cylinder_project(direction='ALIGN_TO_OBJECT',
align='POLAR_ZX',
radius=1.0,
correct_aspect=True,
clip_to_bounds=False,
scale_to_bounds=True)

bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT')

DrewTNBD
  • 98
  • 10