Adding an empty UV map to a mesh

texturing
add-ons

(John Nagle) #1

I’m writing an automatic impostor generator. You fit a box, or other mesh object, around the objects to be impostored, run my “Impostor Maker” add on, and you get an impostor. The source objects are rendered in ortho mode looking straight at each face. Those textures are composited into a big image. UVs are constructed to map the texture onto the target object. Intended use is low-LOD models for Second Life, which are usually terrible.

All that works. See screenshot below. On the left, a 3D model with 3163 triangles. On the right, an impostor with 12 triangles. The one thing I can’t do yet is create UVs where none existed. I have to unwrap from the UI first to create the UV arrays so I can overwrite them. Does anyone know how to do that?

Previous discussion in 2016. Result of discussion - nobody found the answer, although many things were tried.


(kkar) #2

You might be able to use Camera uv projection modifier and then apply it, in the code if you need to do it in python use “bpy.ops.mesh.uv_texture_add()” , unless I misunderstood it :slight_smile:


(John Nagle) #3

It turns out to be really easy, but not documented anywhere I can find it.

Add an empty UV layer:

    me = obj.data     # mesh info
    if not me.uv_layers.active : # if no UV layer
        me.uv_texture_add()     # add UV layer

The data layer functions don’t seem to be well documented, but usually correspond to the user level “ops” operators. A few others I’ve been able to make work:

Create an image object:

    image = bpy.data.images.new(name=name, 
        width=width, height=height, alpha=True) 

Unlike the operator level version, there’s no “color” parameter, so if you need to clear the image to something, that has to be done separately.

Is the data layer documented somewhere? Mostly I’m finding snippets here and on Stack Overflow that hint at how to use it. Yet it’s clearly the way to do things when you need to link up textures, materials, images, nodes, etc.


(John Nagle) #4

Oops, it turns out

me.uv_texture_add()     # add UV layer

won’t work. Back to dumb old

    oldactive = bpy.context.scene.objects.active
    bpy.context.scene.objects.active = target
    bpy.ops.mesh.uv_texture_add()
    bpy.context.scene.objects.active = oldactive

(iceythe) #5

You almost had it, but you should use object.data.uv_textures.new() to add UV layers instead.

import bpy
C = bpy.context

for o in C.selected_objects:
    if o.type == 'MESH':
        if len(o.data.uv_layers) < 1:
            o.data.uv_textures.new()

The python console in Blender has auto-complete if you need to find your way around bpy.data


(John Nagle) #6

Thanks. That worked.

While I’m on here, any idea how to do a “pack_islands” at the data level? I’ve been looking around in the Python console at things like

py.data.meshes[‘Cube.001’].uv_textures

and

py.data.meshes[‘Cube.001’].uv_textures[“UVMap”]

but I’m not finding the object on which one can apply “pack_islands”. It would be nice if the data interface was documented.


(-HENDRIX-) #7

I don’t think you can do that without the operator, unless you want to write custom packing code. :wink:


(iceythe) #8

‘pack_islands’ operator isn’t well documented, but it works on whatever is the active UV layer on the object. To use it requires the object to be active, to have a UV layer, have the UVs selected, and be in edit mode, though I’m guessing internally it performs transforms on the mesh loops object mode.

If you just want to pass a list of objects or even just an object through the operator, you could wrap it into a function:

import bpy
from bpy import context as C, ops as O, data as D
from time import time


kwargs = { # pack_islands operator settings
'rotate' : True,
'margin' : .001}


def core(obj):
    C.scene.objects.active = obj
    O.object.editmode_toggle()
    O.uv.select_all(action='SELECT')
    O.uv.pack_islands(**kwargs)
    O.object.editmode_toggle()


def pack_uvs(input):
    if C.mode != 'OBJECT': #pre-set required mode
        O.object.editmode_toggle()
        
    start = time()
            
    if type(input) is list:
        for obj in input:
            if obj.type == 'MESH':
                if obj.data.uv_textures:
                    core(obj)
        end = abs(round(start - time(), 2))
        print ("Finished in", end, "seconds.")

    elif type(input) is bpy.types.Object:
        if input.type == 'MESH':
                if input.data.uv_textures:
                    core(input)

    for obj in D.objects: # restore selection
        obj.select = False if not obj in selection else True
    C.scene.objects.active = ao

selection, ao = C.selected_objects, C.active_object

pack_uvs(C.selected_objects)

It’s not fast - 10 meshes with 2k islands each will take 4-5 seconds. However 100 meshes with 100 islands only takes 0.25 seconds.
The main take-away is the core() function, as that’s how you can use it on a targeted object.

The alternative is like the post above me, you would need to write your own packing code. Or have some insight into how to override the operator context to work on a given object instead of the active one.


(John Nagle) #9

Thanks very much. I was hoping to avoid doing all that, and thanks for writing it out.

Incidentally, setting “margin” to 1 will crash Blender with a segmentation fault. The values for “margin” are in the UV range, so a value of 1 means nothing will fit. Still, crashing is not good.


(John Nagle) #10

More on the Blender crash. Traceback:

backtrace

./blender(BLI_system_backtrace+0x20) [0x1a6c700]
./blender() [0x1078395]
/lib/x86_64-linux-gnu/libc.so.6(+0x354b0) [0x7f97f26654b0]
./blender(BKE_mesh_calc_poly_normal+0x37) [0x18075b7]
./blender(RNA_property_float_get_array+0xa4) [0x19073a4]
./blender() [0x146eadf]
./blender(_BaseMathObject_ReadCallback+0x21) [0x14a0461]
./blender() [0x14ab2f7]
./blender(PyObject_Str+0x6f) [0x2e9f92f]
./blender() [0x2ef72be]
./blender() [0x2eb4c33]
./blender(PyObject_Call+0x5c) [0x2e4f06c]
./blender(PyEval_EvalFrameEx+0x3812) [0x2f123a2]
./blender() [0x2f1869e]
./blender(PyEval_EvalFrameEx+0x6c8a) [0x2f1581a]
...

My add-on, after packing the UVs, is continuing on and precomputing some things before rendering, but it doesn’t get very far after UV packing. It’s crashing on a reference to “poly.normal”, where “poly” is one of the polygons from the object for which we just packed the UVs. That’s apparently a lazy evaluation inside Blender; note the call to “BKE_mesh_calc_poly_normal”,which is something that was called implicitly by trying to access “poly.normal”. This suggests that packing the UVs either invalidated something, and some fields of the object are no longer valid, or it corrupted Blender’s internal representation of the object.

The mesh it’s working on is six faces, a cube, but with the faces not connected. Easy case.

Checking the bug tracker, found two closed bugs regarding UV packing, nothing directly relevant. Any suggestions?


(Mikkok) #11

Maybe set margin to 0.03 if 1 is too big?


(John Nagle) #12

That wasn’t it. Even margin=0.0 doesn’t fix it. The UV packing actually works OK, and the material and image windows get updated, but some internal data structure may be damaged.

I may have to make a simple example and submit a bug report. But not tonight.


(John Nagle) #13

Wrote my own layout engine. Easier than dealing with some bug in pack_islands().

Entire add-on: https://github.com/John-Nagle/Impostor-maker