yu00’s blog

プログラミングに関する備忘録です

Blender Python API Tips

検証 Blender Version : 3.2.1

コマンドラインからスクリプトを実行する

sample.blendを起動し、sample.pyを実行する例です

"C:\Program Files\Blender Foundation\Blender 3.2\blender.exe" sample.blend --python sample.py
import bpy

def _override_context():
    idx = bpy.context.window_manager.windows[:].index(bpy.context.window)
    window = bpy.context.window_manager.windows[idx]        
    screen = window.screen
    area = [
        area for area in screen.areas
        if area.type == 'VIEW_3D'
    ][0]
    region = [
        region for region in area.regions 
        if region.type == 'WINDOW'
    ][0]
    return bpy.context.temp_override(window=window, area=area, region=region)

if __name__ == '__main__':
    with _override_context():
        # ここにスクリプトを記述
        # 以下はCubeオブジェクトのUVをスケールする例
        bpy.data.objects['Cube'].select_set(True)
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.context.area.ui_type = 'UV'
        bpy.context.scene.tool_settings.use_uv_select_sync = True
        bpy.ops.uv.select_all(action='SELECT')
        bpy.ops.transform.resize(value=(0.5, 1, 1))

Context Overrideについて

コマンドラインから実行するとbpy.context.areaなどがNoneなので、
それらを使用する場合はbpy.context.temp_overrideが必要になります。

参考 : https://stackoverflow.com/questions/70958391/execute-script-after-blender-is-fully-loaded

オブジェクトを結合する

オブジェクトCube, Cube.001を結合してCube_Joinにリネームする例です。

import bpy

def join_objects(join_name, object_names):
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    for object_name in object_names:
        bpy.data.objects[object_name].select_set(True)
    bpy.context.view_layer.objects.active = bpy.data.objects[object_names[0]]
    bpy.ops.object.join()
    bpy.data.objects[object_names[0]].name = join_name

if __name__ == '__main__':
    join_objects('Cube_Join', ['Cube', 'Cube.001'])

マテリアルを結合して1つのマテリアルにする

オブジェクトCubeのマテリアルMaterial, Material.001(に割り当てられた面)を
結合してMaterial_Joinにリネームする例です。

import bpy

def _set_active_material_slot(object, material):
    for i, material_slot in enumerate(object.material_slots):
        if material is material_slot.material:
            object.active_material_index = i

def join_materials(object_name, join_name, material_names):
    object = bpy.data.objects[object_name]
    object.select_set(True)
    bpy.ops.object.mode_set(mode='EDIT')
    materials = bpy.data.materials
    bpy.ops.mesh.select_all(action='DESELECT')

    for material_name in material_names:
        material = materials[material_name]
        _set_active_material_slot(object, material)
        bpy.ops.object.material_slot_select()
    
    material = materials[material_name]
    _set_active_material_slot(object, material)
    bpy.ops.object.material_slot_assign()

    bpy.ops.object.mode_set(mode='OBJECT')
    for material_name in material_names[1:]:
        material = materials[material_name]
        _set_active_material_slot(object, material)
        bpy.ops.object.material_slot_remove()
    
    materials[material_names[0]].name = join_name

if __name__ == '__main__':
    join_materials('Cube', 'Material_Join', ['Material', 'Material.001'])

マテリアルのテクスチャを差し替える

マテリアルMaterialのテクスチャをtexture2.pngに差し替える例です。

import bpy

def _load_image(image_path, image_name):
    if image_name not in bpy.data.images:
        image = bpy.data.images.load(image_path)
        image.name = image_name

def relink_material_texture(material_name, image_path):
    image_name = bpy.path.basename(image_path)
    _load_image(image_path, image_name)
    material = bpy.data.materials[material_name]
    node = [link.from_node for link in material.node_tree.links
        if (
            link.from_node.type == 'TEX_IMAGE' and
            link.to_node.type == 'BSDF_PRINCIPLED' and
            link.from_socket.name == 'Color' and
            link.to_socket.name == 'Base Color'
        )
    ][0]
    node.image = bpy.data.images[image_name]

if __name__ == '__main__':
    relink_material_texture('Material', '//texture2.png')

UVを結合して1つのテクスチャにする

オブジェクトPlane, Plane.001, Plane.002のUV(512x512pixel)を変形し、
オブジェクト・マテリアルをPlane_Join, Material_Joinに結合し、
テクスチャをtexture_join.png(1024x1024pixel)に差し替える例です。

import bpy
import numpy as np

def transform_uv(object_name, join_image_name, src_image_name, pos):
    """
    UVを変形する
    pos : 左上を原点としたイメージの位置(pixel)
    """

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    object = bpy.data.objects[object_name]
    object.select_set(True)
    bpy.context.view_layer.objects.active = object

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.context.area.ui_type = 'UV'
    image_editor = bpy.context.space_data
    bpy.context.scene.tool_settings.use_uv_select_sync = True

    join_image = bpy.data.images[join_image_name]
    join_image_size = np.array(join_image.size[:])

    src_image = bpy.data.images[src_image_name]
    src_image_size = np.array(src_image.size[:])

    bpy.ops.uv.select_all(action='SELECT')
    image_editor.cursor_location = [0, 1]
    image_editor.pivot_point = 'CURSOR'

    resize = src_image_size / join_image_size
    bpy.ops.transform.resize(
        value=(resize[0], resize[1], 1),
        orient_type='GLOBAL',
        orient_matrix=np.eye(3), 
        constraint_axis=(True, True, True)
    )
    
    translate = np.array(pos) / join_image_size
    bpy.ops.transform.translate(
        value=(translate[0], -translate[1], 0),
        orient_matrix=np.eye(3), 
        constraint_axis=(True, True, True)
    )

infos = {
    'join_object_name' : 'Plane_Join',
    'join_material_name' : 'Material_Join',
    'join_image_name' : 'texture_join.png',
    'objects' : [
        {
            'name' : 'Plane',
            'image' : 'texture1.png',
            'material' : 'Material',
            'pos' : (0, 0),
        },
        {
            'name' : 'Plane.001',
            'image' : 'texture2.png',
            'material' : 'Material.001',
            'pos' : (512, 0),
        },
        {
            'name' : 'Plane.002',
            'image' : 'texture3.png',
            'material' : 'Material.002',
            'pos' : (0, 512),
        },
    ]
}

if __name__ == '__main__':
    for info in infos['objects']:
        transform_uv(
            info['name'],
            infos['join_image_name'],
            info['image'],
            info['pos']
        )

    join_objects(
        infos['join_object_name'],
        [info['name'] for info in infos['objects']]
    )
    join_materials(
        infos['join_object_name'],
        infos['join_material_name'],
        [info['material'] for info in infos['objects']]
    )
    relink_material_texture(
        infos['join_material_name'],
        '//'+infos['join_image_name']
    )