Open2

Blenderスクリプトでモデルの軽量化を自動化

KanataYAMAGISHIKanataYAMAGISHI

https://qiita.com/masterkeaton12/items/21a7ddddb4304622403b
https://blender.stackexchange.com/questions/191091/bake-a-texture-map-with-python
https://docs.blender.org/api/current/bpy.types.BakeSettings.html
https://docs.blender.org/api/current/bpy.types.GeometryNodeMergeByDistance.html
https://docs.blender.org/api/current/bpy.ops.object.html
https://blender.stackexchange.com/questions/45099/duplicating-a-mesh-object
https://b3d.interplanety.org/en/how-to-apply-modifier-on-selected-objects/
https://blender.stackexchange.com/questions/157531/blender-2-8-python-add-texture-image

Decimateをする。

def decimate(ratio):
    bpy.ops.object.modifier_add(type='DECIMATE')

    decim = bpy.context.object.modifiers["Decimate"]
    decim.ratio = ratio

https://docs.blender.org/api/current/bpy.types.DecimateModifier.html

Merge By Distanceに相当するものが直接は見つからなかったが、Mesh Operatorを見ていたらbpy.ops.mesh.remove_doubles()なるものが。説明を見るとそれっぽいので、これを使うことにした。ただし、thresholdの単位はmではなさそうなので動作させながら適当な値を地道に検証していく必要があった。
https://docs.blender.org/api/current/bpy.ops.mesh.html
なお、Mesh Operatorを使用する前には以下を実行して、デフォルトのObject ModeからEdit Modeへと変更しないと、「blender contect is incorrect」と怒られてしまう。

bpy.ops.object.mode_set(mode = "EDIT")

使い方の方はこれでかなりいい感じ
https://blender.stackexchange.com/questions/174525/how-to-merge-all-vertices-by-distance-and-remove-all-inner-faces

import bpy

def decimate(ratio):
    bpy.ops.object.modifier_add(type='DECIMATE')

    decim = bpy.context.object.modifiers["Decimate"]
    decim.ratio = ratio

## manipulation of duplicated object
bpy.ops.object.duplicate()
decimate(0.05)
bpy.ops.object.modifier_apply(modifier="Decimate")

# Editモードにする
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold = 0.02)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type = 'FACE')
bpy.ops.mesh.select_interior_faces()
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')

ob = bpy.context.active_object

# Get material
mat = bpy.data.materials.get("Material")
if mat is None:
    # create material
    mat = bpy.data.materials.new(name="Material")

mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
texImage = mat.node_tree.nodes.new('ShaderNodeTexImage')
texImage.image = bpy.data.images.new(name="Image Texture", width=2048, height=2048)
mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color'])

# Assign it to object
if ob.data.materials:
    # assign to 1st material slot
    ob.data.materials[0] = mat
else:
    # no slots
    ob.data.materials.append(mat)

https://blender.stackexchange.com/questions/191091/bake-a-texture-map-with-python

https://qiita.com/SaitoTsutomu/items/f95fcc7b58f22b872bcf
↑このサイトのこのへんが役立ちそう

def execute(self, context):
        # ベイクの設定
        render = context.scene.render
        render.engine = "CYCLES"
        render.bake.use_pass_direct = False
        render.bake.use_pass_indirect = False
        render.bake.use_pass_color = True
        render.bake.use_selected_to_active = False
        obj = context.active_object
        tt = ["Base Color", "Roughness", "Normal"]
        dct = {t: [lst, None] for t in tt if (lst := list(get_node_data(obj, t)))}
        for target, lsts in dct.items():
            lsts[1] = bake_target(context, target, lsts[0])
        for target, lsts in dct.items():
            for nd in lsts[0]:
                nodes = nd.node_tree.nodes
                # 画像テクスチャノード作成
                image_node = nodes.new(type="ShaderNodeTexImage")
                image_node.image = lsts[1]
                # ベイク画像に変更
                if target == "Normal":
                    image_node.image.colorspace_settings.name = "Non-Color"
                    nmlmp_node = nodes.get("Normal Map") or nodes.new(type="ShaderNodeNormalMap")
                    nd.node_tree.links.new(image_node.outputs["Color"], nmlmp_node.inputs["Color"])
                    nd.node_tree.links.new(nmlmp_node.outputs[target], nd.bsdf.inputs[target])
                else:
                    nd.node_tree.links.new(image_node.outputs["Color"], nd.bsdf.inputs[target])
        self.report({"INFO"}, "Done" if dct else "Nothing")
        return {"FINISHED"}
KanataYAMAGISHIKanataYAMAGISHI
import bpy
import time

def decimate(ratio):
    bpy.ops.object.modifier_add(type='DECIMATE')

    decim = bpy.context.object.modifiers["Decimate"]
    decim.ratio = ratio

## manipulation of duplicated object
bpy.ops.object.duplicate()
decimate(0.05)
bpy.ops.object.modifier_apply(modifier="Decimate")

# Editモードにする
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold = 0.02)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type = 'FACE')
bpy.ops.mesh.select_interior_faces()
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')

ob = bpy.context.active_object

# Get material
mat = bpy.data.materials.get("Material")
if mat is None:
    # create material
    mat = bpy.data.materials.new(name="Material")

mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
texImage = mat.node_tree.nodes.new('ShaderNodeTexImage')
texImage.image = bpy.data.images.new(name="ImageTexture"+str(time.time()), width=2048, height=2048)
mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color'])

# Assign it to object
if ob.data.materials:
    # assign to 1st material slot
    ob.data.materials[0] = mat
else:
    # no slots
    ob.data.materials.append(mat)

## Bakeまで自動化しようと思ったけど諦めた

## Get the active object and selected object
#active_obj = bpy.context.view_layer.objects.active
#selected_objs = bpy.context.selected_objects

## Set render settings
#render = bpy.context.scene.render
#render.engine = "CYCLES"
#render.bake.use_pass_direct = False
#render.bake.use_pass_indirect = False
#render.bake.use_pass_color = True
#render.bake.use_selected_to_active = True

## Set the device and feature set
#bpy.context.scene.cycles.device = "GPU"

## Loop through selected objects and bake their textures onto the active object
#for obj in selected_objs:
#    if obj != active_obj and obj.type == 'MESH':
##        bpy.context.view_layer.objects.active = obj
#        bpy.ops.object.bake(type='DIFFUSE')