93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
|
"""
|
||
|
Common functions useful for exporting blender scenes to GLTF with bpy
|
||
|
"""
|
||
|
|
||
|
|
||
|
from pathlib import Path
|
||
|
from sys import stderr
|
||
|
import bpy
|
||
|
from bpy.types import Object, Armature, Modifier, Mesh, Context, Collection
|
||
|
from functools import reduce
|
||
|
from operator import attrgetter, itemgetter, or_
|
||
|
|
||
|
|
||
|
|
||
|
def clear_selection(ctx: Context):
|
||
|
for obj in ctx.selected_objects:
|
||
|
obj.select_set(False)
|
||
|
|
||
|
|
||
|
def select_collection(ctx: Context, c: Collection):
|
||
|
for obj in c.objects:
|
||
|
obj.select_set(True)
|
||
|
|
||
|
# https://blenderartists.org/t/how-to-apply-all-the-modifiers-with-python/1314483/2
|
||
|
|
||
|
|
||
|
def apply_modifier(obj: Object, modifier: Modifier):
|
||
|
with bpy.context.temp_override(object=obj, modifier=modifier):
|
||
|
bpy.context.view_layer.update()
|
||
|
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||
|
bpy.context.view_layer.update()
|
||
|
|
||
|
|
||
|
def sort_uv_layers(mesh: Mesh):
|
||
|
# UV layers use non-pythonic reference semantics, safest to always reference by their indices
|
||
|
assert len(
|
||
|
mesh.uv_layers) <= 7, "There must be fewer than 8 UV maps to sort (one less than the Blender maximum of 8)"
|
||
|
initial_layers = [[i, layer.name]
|
||
|
for i, layer in enumerate(mesh.uv_layers)]
|
||
|
initial_layers.sort(key=itemgetter(1))
|
||
|
for i, layer_name in tuple(initial_layers):
|
||
|
assert mesh.uv_layers[i].name == layer_name, mesh.uv_layers[i].name + \
|
||
|
" " + layer_name
|
||
|
mesh.uv_layers.active = mesh.uv_layers[i]
|
||
|
new_layer = mesh.uv_layers.new(name=f"new_{mesh.uv_layers[i].name}")
|
||
|
assert new_layer == mesh.uv_layers[-1]
|
||
|
mesh.uv_layers.active = mesh.uv_layers[0]
|
||
|
mesh.uv_layers.remove(mesh.uv_layers[i])
|
||
|
for pair in initial_layers:
|
||
|
if pair[0] > i:
|
||
|
pair[0] -= 1
|
||
|
for layer in mesh.uv_layers:
|
||
|
layer.name = layer.name.replace("new_", "")
|
||
|
mesh.uv_layers.active = mesh.uv_layers[0]
|
||
|
|
||
|
def delete_uv_layer(mesh: Mesh, name: str):
|
||
|
for layer in mesh.uv_layers:
|
||
|
if layer.name == name:
|
||
|
mesh.uv_layers.remove(layer)
|
||
|
return
|
||
|
raise Exception(f"failed to remove UV layer with name {name}")
|
||
|
|
||
|
def convert_attribute(obj, attribute_name: str):
|
||
|
"Don't convert this into a function that does multiple at once, indices are too fucky for that"
|
||
|
for i, attr in enumerate(obj.data.attributes):
|
||
|
if attr.name == attribute_name:
|
||
|
obj.data.attributes.active_index = i
|
||
|
bpy.context.view_layer.update()
|
||
|
bpy.ops.geometry.attribute_convert(
|
||
|
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
|
||
|
bpy.context.view_layer.update()
|
||
|
return
|
||
|
raise Exception("attribute not found", attribute_name)
|
||
|
|
||
|
|
||
|
def apply_bone_groups(rig: Armature, skinned: Object):
|
||
|
"""May not be needed in future versions of Blender
|
||
|
Converts geometry nodes attributes for bone groups back to vertex groups
|
||
|
Something fishy with indices and attributes, so algorithm is not efficient just to be safe
|
||
|
"""
|
||
|
with bpy.context.temp_override(object=skinned):
|
||
|
for bone_name in map(attrgetter("name"), rig.bones):
|
||
|
for i, attribute in enumerate(skinned.data.attributes):
|
||
|
if attribute.name == bone_name:
|
||
|
skinned.data.attributes.active_index = i
|
||
|
bpy.context.view_layer.update()
|
||
|
bpy.ops.geometry.attribute_convert(
|
||
|
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
|
||
|
bpy.context.view_layer.update()
|
||
|
break
|
||
|
else:
|
||
|
print("Failed to find attribute for", bone_name, file=stderr)
|