1
$\begingroup$

I have made a script that:

  • Uses a normal dot with two values to bake two stencils, then create and assign a new mat.
  • Uses one of them with a texture (rocks..) to create a displace map
  • Displaces the mesh
  • Uses three textures (rocks, soil, grass) with the stencils to bake diffuse and normals and pack them
  • replace the textures with the newly baked ones.

When I select more than one object it gives me a circular reference in texture stack every obj after the first, which means on of my image is in use by the object before baking. I troubleshot the problem to be in the very last part of my script which is assigning the normal texture to the material, so that when I comment that part off the script runs fine even with multiple objects selected.

So i guess I have to post it all, please have a look at it:

class gen():
    def textures(i, c):
    # Create the images for each object
    bpy.context.scene.objects.active = i
    bpy.ops.image.new(name = 'Stencil'+str(c))
    bpy.ops.image.new(name = '2Stencil'+str(c))
    bpy.ops.image.new(name = 'Displ'+str(c))
    bpy.ops.image.new(name = 'Diffuse'+str(c))
    bpy.ops.image.new(name = 'Normal'+str(c))
    bpy.data.materials['Normal'].node_tree.nodes['Normal'].outputs[0].default_value = (0,1,1)
    bpy.data.materials['Normal'].node_tree.nodes['Mapping'].scale[2] = 20

    #assign the images and bake stencils from 'Normal'
    for face in i.data.uv_textures.active.data:
        face.image = bpy.data.images['Stencil' + str(c)]
    bpy.data.scenes['Scene'].render.bake_type = 'TEXTURE'
    bpy.ops.object.bake_image()
    #needs 'Normal' for stencil
    bpy.data.materials['Normal'].node_tree.nodes['Mapping'].scale[2] = 40

    for face in i.data.uv_textures.active.data:
        face.image = bpy.data.images['2Stencil' + str(c)]
    bpy.ops.object.bake_image()

    #Create and assign a new material ans textures for displacement
    bpy.ops.material.new()
    m = bpy.data.materials['Material']
    m.name = 'Ground'+str(c)
    m.texture_slots.add()
    m.texture_slots.add()
    m.texture_slots.add()
    m.texture_slots.add()
    m.texture_slots.add()
    #get rid of original material
    bpy.ops.object.material_slot_remove(0)    
    bpy.ops.object.material_slot_add()
    i.material_slots[''].material = m
    m.diffuse_color = (0,0,0)

    #créer au préalable/needs une texture de 'Rochers', 'Herbe', 'Dirt'
    m.active_texture_index = 4
    m.texture_slots[4].texture = bpy.data.textures['Herbe']
    m.texture_slots['Herbe'].texture_coords = 'UV'
    m.texture_slots['Herbe'].use_map_color_spec = True
    m.texture_slots['Herbe'].scale[0] = 5
    m.texture_slots['Herbe'].scale[1] = 5

    bpy.ops.texture.new()
    t = bpy.data.textures['Texture']
    t.name = 'Stencil'+str(c)
    m.active_texture_index = 3
    m.texture_slots[3].texture = bpy.data.textures['Stencil'+str(c)]
    m.texture_slots['Stencil'+str(c)].texture_coords = 'UV'
    m.active_texture.type = 'IMAGE'
    m.active_texture.image = bpy.data.images['Stencil'+str(c)]
    m.texture_slots['Stencil'+str(c)].use_rgb_to_intensity = True
    m.texture_slots['Stencil'+str(c)].use_stencil = True
    m.texture_slots['Stencil'+str(c)].use_map_color_diffuse = False

    m.active_texture_index = 2
    m.texture_slots[2].texture = bpy.data.textures['Rochers']
    m.texture_slots['Rochers'].texture_coords = 'UV'
    m.texture_slots['Rochers'].use_map_color_spec = True

    bpy.ops.texture.new()
    t = bpy.data.textures['Texture']
    t.name = '2Stencil'+str(c)    
    m.active_texture_index = 1
    m.texture_slots[1].texture = bpy.data.textures['2Stencil'+str(c)]
    m.active_texture.type = 'IMAGE'
    m.active_texture.image = bpy.data.images['2Stencil'+str(c)]
    m.texture_slots['2Stencil'+str(c)].blend_type = 'SUBTRACT'
    m.texture_slots['2Stencil'+str(c)].texture_coords = 'UV'
    m.texture_slots['2Stencil'+str(c)].use_rgb_to_intensity = True
    m.texture_slots['2Stencil'+str(c)].use_stencil = True
    m.texture_slots['2Stencil'+str(c)].use_map_color_diffuse = False

    m.active_texture_index = 0
    m.texture_slots[0].texture = bpy.data.textures['Dirt']
    m.texture_slots['Dirt'].texture_coords = 'UV'
    m.texture_slots['Dirt'].use_map_color_spec = True
    m.texture_slots['Dirt'].scale[0] = 5
    m.texture_slots['Dirt'].scale[1] = 5
    #deactivate the textures not needed for displace
    m.use_textures[0] = False
    m.use_textures[3] = False
    m.use_textures[4] = False

    #Bake
    for face in i.data.uv_textures.active.data:
        face.image = bpy.data.images['Displ' + str(c)]
    bpy.ops.object.bake_image()

    bpy.ops.object.modifier_add(type='DISPLACE')
    i.modifiers['Displace'].direction = 'Z'
    bpy.ops.texture.new()
    t = bpy.data.textures['Texture']
    t.name = 'Displ'+str(c)
    bpy.data.textures['Displ'+str(c)].type = 'IMAGE'
    bpy.data.textures['Displ'+str(c)].image = bpy.data.images['Displ'+str(c)]
    i.modifiers['Displace'].texture = bpy.data.textures['Displ'+str(c)]
    i.modifiers['Displace'].texture_coords = 'UV'
    i.modifiers['Displace'].strength = 0.25
    bpy.ops.object.modifier_apply(apply_as='DATA', modifier = 'Displace')

    #Reactivate the textures, now the mesh is displaced

    m.use_textures[0] = True
    m.use_textures[3] = True
    m.use_textures[4] = True
    m.texture_slots['Stencil'+str(c)].invert = True
    m.texture_slots['2Stencil'+str(c)].invert = True

    for face in i.data.uv_textures.active.data:
        face.image = bpy.data.images['Diffuse'+str(c)]
    bpy.data.scenes['Scene'].render.bake_type = 'TEXTURE'
    bpy.ops.object.bake_image()
    bpy.data.images['Diffuse'+str(c)].pack(as_png=True)
    for face in i.data.uv_textures.active.data:
        face.image = bpy.data.images['Normal'+str(c)]
    bpy.data.scenes['Scene'].render.bake_type = 'NORMALS'
    bpy.ops.object.bake_image()            
    bpy.data.images['Normal'+str(c)].pack(as_png=True)

    #Textures are baked and packed, now (maybe useless) deactivate all the slots

    m.active_texture_index = 2
    m.texture_slots[2].use = False
    m.texture_slots.clear(2)
    m.active_texture_index = 3
    m.texture_slots[3].use = False
    m.texture_slots.clear(3)
    m.active_texture_index = 4
    m.texture_slots[4].use = False
    m.texture_slots.clear(4)

    #Next assign newly created textures.    

    m.active_texture_index = 0
    bpy.ops.texture.new()
    t = bpy.data.textures['Texture']
    t.name = 'Diffuse'+str(c)
    t.type = 'IMAGE'
    t.image = bpy.data.images['Diffuse'+str(c)]
    m.texture_slots[0].texture = bpy.data.textures['Diffuse'+str(c)]
    m.texture_slots[0].use_map_color_spec = True
    m.texture_slots[0].texture_coords = 'UV'
'''      
    m.active_texture_index = 1
    bpy.ops.texture.new()
    t = bpy.data.textures['Texture']
    t.name = 'Normal'+str(c)
    t.type = 'IMAGE'
    t.image = bpy.data.images['Normal'+str(c)]
    m.texture_slots[1].texture = bpy.data.textures['Normal'+str(c)]
    m.texture_slots[1].texture_coords = 'UV'
    m.texture_slots[1].use_map_normal = True
    m.texture_slots[1].use_map_color_diffuse = False
    t.use_normal_map = True
    m.texture_slots[1].normal_factor = 0.4
'''
    def lod():
    d = 1
    for i in bpy.context.selected_objects:
        bpy.context.scene.objects.active = i  
        bpy.ops.object.modifier_add(type='DECIMATE')
        bpy.context.object.modifiers["Decimate"].ratio = 0.1
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier = 'Decimate')    
        bpy.context.object.name = i.name + str(d)
        bpy.ops.object.duplicate()

        bpy.ops.object.modifier_add(type='DECIMATE')
        bpy.context.object.modifiers["Decimate"].ratio = 0.3
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier = 'Decimate')
        bpy.context.object.name = i.name + str(d+1)
        bpy.ops.object.duplicate()
        bpy.ops.object.modifier_add(type='DECIMATE')
        bpy.context.object.modifiers["Decimate"].ratio = 0.5
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier = 'Decimate')
        bpy.context.object.name = i.name + str(d+2)
        bpy.ops.object.duplicate()
        bpy.ops.object.modifier_add(type='DECIMATE')
        bpy.context.object.modifiers["Decimate"].ratio = 0.5
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier = 'Decimate')
        bpy.context.object.name = i.name + str(d+3)

        bpy.context.scene.objects.active = i
        bpy.ops.object.lod_add()
        bpy.ops.object.lod_add()
        bpy.ops.object.lod_add()
        i.lod_levels[1].object = bpy.data.objects[i.name+str(d+1)]
        i.lod_levels[2].object = bpy.data.objects[i.name+str(d+2)]
        i.lod_levels[3].object = bpy.data.objects[i.name+str(d+3)]

        bpy.data.objects[i.name+str(d+1)].hide = True
        bpy.data.objects[i.name+str(d+2)].hide = True
        bpy.data.objects[i.name+str(d+3)].hide = True
        d = d + 1

Gen = gen
c = 1
for i in bpy.context.selected_objects:
    gen.textures(i, c)
    c = c + 1
#gen.lod()

To run the script ; create a plane, unwrap, subdivide displace and everything you like, create a material node with a geometry input normal slot connected to a normal node, which has its dot connected to the factor of a mix, white above, black below ; AND you must add a mapping node between the geometry and normal nodes. Create three textures somewhere whose name should be 'Rochers' 'Dirt' and 'Herbe' corresponding to the textures of rocks, soil(dirt), and grass. Before running the script you should scale the plane up to eight time and apply all its modifiers and its scale. Of course use glsl shading.

There should not be a material named 'Material' nor a texture named 'Texture' in the scene.

$\endgroup$
  • 1
    $\begingroup$ On a quick peruse, the first time you run bpy.ops.image.new(name = 'Normal'+str(c)) it will add an image named (for c=1) "Normal1", the next time an image named "Normal1.001" and so on. If you are not cleaning out your images between runs, the script will try to use "Normal1" I would suggest using API methods like normal_img = bpy.data.images.new(...) $\endgroup$ – batFINGER Oct 26 '17 at 9:47
  • $\begingroup$ thanks it works, i replaced every bpy.ops...new() by obj = bpy.data...new(...). I share a paste of the code if it can help any. pastebin.com/n8snPg83 $\endgroup$ – Yvain Oct 26 '17 at 11:03
  • $\begingroup$ Post as an answer if you like. As a rule of thumb try and avoid operators when there is an API method. $\endgroup$ – batFINGER Oct 26 '17 at 11:06
0
$\begingroup$

This was quite messy but the comments led to an appropriate solution where every operator is instead replaced by it's bpy.data equivalent when available. This is better to initialize a texture as t = bpy.data.textures.new(...) than doing an operation with ops because you don't have to get the texture by its name, you have the t symbol to play with so this must certainly arrange matters. I also made the script to make object active at the beginning, and deselect it at the end of each iteration.

$\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.