Migrate to Blender 5.0 API

- Replaced action.fcurves with channelbag system
- Updated EEVEE_NEXT to EEVEE render engine
- Removed deprecated material.use_nodes and use_shadeless
- Fixed bone selection/hide API for Pose mode
This commit is contained in:
Yusarina
2025-11-15 02:45:37 +00:00
parent d2b98716ff
commit f40b2faacb
13 changed files with 132 additions and 62 deletions
+19 -5
View File
@@ -10,6 +10,7 @@ from typing import Optional, List, Tuple, Callable, Any, Union
import bpy
from bpy.types import Object, ID, Camera, Context
from bpy_extras import anim_utils
from mathutils import Vector, Matrix, Euler
import traceback
@@ -247,14 +248,27 @@ class MMDCamera:
frame_count = frame_end - frame_start
frames = range(frame_start, frame_end)
# Get channelbags for camera actions using Blender 5.0 API
if not parent_action.slots:
parent_slot = parent_action.slots.new(for_id=mmd_cam_root)
else:
parent_slot = parent_action.slots[0]
parent_channelbag = anim_utils.action_ensure_channelbag_for_slot(parent_action, parent_slot)
if not distance_action.slots:
distance_slot = distance_action.slots.new(for_id=mmd_cam)
else:
distance_slot = distance_action.slots[0]
distance_channelbag = anim_utils.action_ensure_channelbag_for_slot(distance_action, distance_slot)
fcurves = []
for i in range(3):
fcurves.append(parent_action.fcurves.new(data_path="location", index=i)) # x, y, z
fcurves.append(parent_channelbag.fcurves.new(data_path="location", index=i)) # x, y, z
for i in range(3):
fcurves.append(parent_action.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.angle")) # fov
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
fcurves.append(distance_action.fcurves.new(data_path="location", index=1)) # dis
fcurves.append(parent_channelbag.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.angle")) # fov
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
fcurves.append(distance_channelbag.fcurves.new(data_path="location", index=1)) # dis
for c in fcurves:
c.keyframe_points.add(frame_count)
+7 -2
View File
@@ -481,7 +481,7 @@ class FnMaterial:
preferred_output_node_target = {
"CYCLES": "CYCLES",
"BLENDER_EEVEE_NEXT": "EEVEE",
"BLENDER_EEVEE": "EEVEE",
}.get(active_render_engine, "ALL")
tex_node = None
@@ -559,7 +559,12 @@ class FnMaterial:
mat = self.material
if mat.node_tree is None:
logger.debug(f"Creating node tree for {mat.name}")
mat.use_nodes = True
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
# Creating a new material automatically creates a node tree
if mat.node_tree is None:
# Fallback: node tree should exist, but if not, log warning
logger.warning(f"Node tree is None for material {mat.name} - this should not happen")
return
mat.node_tree.nodes.clear()
nodes, links = mat.node_tree.nodes, mat.node_tree.links
+2 -1
View File
@@ -534,7 +534,8 @@ class PMXImporter:
elif b_bone.name in specialTipBones:
mmd_bone.is_tip = True
b_bone.bone.hide = not pmx_bone.visible # or mmd_bone.is_tip
# Blender 5.0: use pose bone hide for Pose/Object mode visibility
b_bone.hide = not pmx_bone.visible # or mmd_bone.is_tip
if not pmx_bone.isRotatable:
b_bone.lock_rotation = [True, True, True]
+1 -1
View File
@@ -82,7 +82,7 @@ class RigidBodyMaterial:
mat.shadow_method = "NONE"
mat.use_backface_culling = True
mat.show_transparent_back = False
mat.use_nodes = True
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
nodes, links = mat.node_tree.nodes, mat.node_tree.links
nodes.clear()
node_color = nodes.new("ShaderNodeBackground")
+43 -21
View File
@@ -11,6 +11,7 @@ import os
from typing import Union
import bpy
from bpy_extras import anim_utils
from mathutils import Quaternion, Vector
from ... import utils
@@ -300,21 +301,31 @@ class VMDImporter:
kp.handle_right = kp.co + Vector((1, 0))
@staticmethod
def __keyframe_insert_inner(fcurves: bpy.types.ActionFCurves, path: str, index: int, frame: float, value: float):
fcurve = fcurves.find(path, index=index)
def __get_channelbag(action: bpy.types.Action, target_id=None):
"""Get or create channelbag for action using Blender 5.0 API."""
if not action.slots:
slot = action.slots.new(for_id=target_id)
else:
slot = action.slots[0]
return anim_utils.action_ensure_channelbag_for_slot(action, slot)
@staticmethod
def __keyframe_insert_inner(action: bpy.types.Action, path: str, index: int, frame: float, value: float, target_id=None, group_name=None):
channelbag = VMDImporter.__get_channelbag(action, target_id)
fcurve = channelbag.fcurves.find(path, index=index)
if fcurve is None:
fcurve = fcurves.new(path, index=index)
fcurve = channelbag.fcurves.new(path, index=index, group_name=group_name)
fcurve.keyframe_points.insert(frame, value, options={"FAST"})
@staticmethod
def __keyframe_insert(fcurves: bpy.types.ActionFCurves, path: str, frame: float, value: Union[int, float, Vector]):
def __keyframe_insert(action: bpy.types.Action, path: str, frame: float, value: Union[int, float, Vector], target_id=None, group_name=None):
if isinstance(value, (int, float)):
VMDImporter.__keyframe_insert_inner(fcurves, path, 0, frame, value)
VMDImporter.__keyframe_insert_inner(action, path, 0, frame, value, target_id, group_name)
elif isinstance(value, Vector):
VMDImporter.__keyframe_insert_inner(fcurves, path, 0, frame, value[0])
VMDImporter.__keyframe_insert_inner(fcurves, path, 1, frame, value[1])
VMDImporter.__keyframe_insert_inner(fcurves, path, 2, frame, value[2])
VMDImporter.__keyframe_insert_inner(action, path, 0, frame, value[0], target_id, group_name)
VMDImporter.__keyframe_insert_inner(action, path, 1, frame, value[1], target_id, group_name)
VMDImporter.__keyframe_insert_inner(action, path, 2, frame, value[2], target_id, group_name)
else:
raise TypeError("Unsupported type: {0}".format(type(value)))
@@ -407,16 +418,19 @@ class VMDImporter:
assert bone_name_table.get(bone.name, name) == name
bone_name_table[bone.name] = name
# Get channelbag for this action
channelbag = self.__get_channelbag(action, armObj.data)
fcurves = [dummy_keyframe_points] * 7 # x, y, z, r0, r1, r2, (r3)
data_path_rot = prop_rot_map.get(bone.rotation_mode, "rotation_euler")
bone_rotation = getattr(bone, data_path_rot)
default_values = list(bone.location) + list(bone_rotation)
data_path = 'pose.bones["%s"].location' % bone.name
for axis_i in range(3):
fcurves[axis_i] = action.fcurves.new(data_path=data_path, index=axis_i, action_group=bone.name)
fcurves[axis_i] = channelbag.fcurves.new(data_path=data_path, index=axis_i, group_name=bone.name)
data_path = 'pose.bones["%s"].%s' % (bone.name, data_path_rot)
for axis_i in range(len(bone_rotation)):
fcurves[3 + axis_i] = action.fcurves.new(data_path=data_path, index=axis_i, action_group=bone.name)
fcurves[3 + axis_i] = channelbag.fcurves.new(data_path=data_path, index=axis_i, group_name=bone.name)
for i in range(len(default_values)):
c = fcurves[i]
@@ -458,7 +472,9 @@ class VMDImporter:
self.__setInterpolation(interp[idx : idx + 16 : 4], prev_kp, kp)
prev_kps = curr_kps
for c in action.fcurves:
# Get channelbag to iterate fcurves
channelbag = self.__get_channelbag(action, armObj.data)
for c in channelbag.fcurves:
self.__fixFcurveHandles(c)
# property animation
@@ -518,7 +534,8 @@ class VMDImporter:
continue
logging.info("(mesh) frames:%5d name: %s", len(keyFrames), name)
shapeKey = shapeKeyDict[name]
fcurve = action.fcurves.new(data_path='key_blocks["%s"].value' % shapeKey.name)
channelbag = self.__get_channelbag(action, meshObj.data.shape_keys)
fcurve = channelbag.fcurves.new(data_path='key_blocks["%s"].value' % shapeKey.name)
fcurve.keyframe_points.add(len(keyFrames))
keyFrames.sort(key=lambda x: x.frame_number)
for k, v in zip(keyFrames, fcurve.keyframe_points):
@@ -541,7 +558,7 @@ class VMDImporter:
logging.debug("(Display) list(frame, show): %s", [(keyFrame.frame_number, bool(keyFrame.visible)) for keyFrame in propertyAnim])
for keyFrame in propertyAnim:
self.__keyframe_insert(action.fcurves, "mmd_root.show_meshes", keyFrame.frame_number + self.__frame_margin, float(keyFrame.visible))
self.__keyframe_insert(action, "mmd_root.show_meshes", keyFrame.frame_number + self.__frame_margin, float(keyFrame.visible), rootObj)
self.__assign_action(rootObj, action)
@@ -574,14 +591,18 @@ class VMDImporter:
if self.__mirror:
_loc, _rot = _MirrorMapper.get_location, _MirrorMapper.get_rotation3
# Get channelbags for camera actions
parent_channelbag = self.__get_channelbag(parent_action, mmdCamera.parent)
distance_channelbag = self.__get_channelbag(distance_action, mmdCamera.distance)
fcurves = []
for i in range(3):
fcurves.append(parent_action.fcurves.new(data_path="location", index=i)) # x, y, z
fcurves.append(parent_channelbag.fcurves.new(data_path="location", index=i)) # x, y, z
for i in range(3):
fcurves.append(parent_action.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.angle")) # fov
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
fcurves.append(distance_action.fcurves.new(data_path="location", index=1)) # dis
fcurves.append(parent_channelbag.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.angle")) # fov
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
fcurves.append(distance_channelbag.fcurves.new(data_path="location", index=1)) # dis
for c in fcurves:
c.keyframe_points.add(len(cameraAnim))
@@ -640,10 +661,11 @@ class VMDImporter:
_loc = _MirrorMapper.get_location if self.__mirror else lambda i: i
for keyFrame in lampAnim:
frame = keyFrame.frame_number + self.__frame_margin
self.__keyframe_insert(color_action.fcurves, "color", frame, Vector(keyFrame.color))
self.__keyframe_insert(location_action.fcurves, "location", frame, Vector(_loc(keyFrame.direction)).xzy * -1)
self.__keyframe_insert(color_action, "color", frame, Vector(keyFrame.color), lampObj)
self.__keyframe_insert(location_action, "location", frame, Vector(_loc(keyFrame.direction)).xzy * -1, mmdLamp)
for fcurve in location_action.fcurves:
location_channelbag = self.__get_channelbag(location_action, mmdLamp)
for fcurve in location_channelbag.fcurves:
self.detectLampChange(fcurve)
self.__assign_action(lampObj.data, color_action)
+4 -6
View File
@@ -127,9 +127,9 @@ def convertToBlenderShader(obj: bpy.types.Object, use_principled: bool = False,
for i in obj.material_slots:
if not i.material:
continue
if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
if not i.material.node_tree or len(i.material.node_tree.nodes) == 0:
logger.debug(f"Setting up node tree for material: {i.material.name}")
__convertToMMDBasicShader(i.material)
if use_principled:
logger.debug(f"Converting material to Principled BSDF: {i.material.name}")
@@ -143,9 +143,7 @@ def convertToMMDShader(obj: bpy.types.Object) -> None:
for i in obj.material_slots:
if not i.material:
continue
if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
FnMaterial.convert_to_mmd_material(i.material)
def __convertToMMDBasicShader(material: Material) -> None:
+1 -1
View File
@@ -386,7 +386,7 @@ class EdgePreviewSetup(Operator):
return mat
def __make_shader(self, m: Material) -> None:
m.use_nodes = True
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
nodes, links = m.node_tree.nodes, m.node_tree.links
node_shader = nodes.get("mmd_edge_preview", None)
+5 -3
View File
@@ -475,7 +475,8 @@ class ViewBoneMorph(bpy.types.Operator):
for morph_data in morph.data:
p_bone: Optional[bpy.types.PoseBone] = armature.pose.bones.get(morph_data.bone, None)
if p_bone:
p_bone.bone.select = True
# Blender 5.0: use pose bone select property directly
p_bone.select = True
mtx = (p_bone.matrix_basis.to_3x3() @ Quaternion(*morph_data.rotation.to_axis_angle()).to_matrix()).to_4x4()
mtx.translation = p_bone.location + morph_data.location
p_bone.matrix_basis = mtx
@@ -521,9 +522,10 @@ class ApplyBoneMorph(bpy.types.Operator):
item.bone = p_bone.name
item.location = p_bone.location
item.rotation = p_bone.rotation_quaternion if p_bone.rotation_mode == "QUATERNION" else p_bone.matrix_basis.to_quaternion()
p_bone.bone.select = True
# Blender 5.0: use pose bone select property directly
p_bone.select = True
else:
p_bone.bone.select = False
p_bone.select = False
logger.info(f"Applied current pose to bone morph: {morph.name}")
return {"FINISHED"}
+6 -8
View File
@@ -32,16 +32,14 @@ class _SetShadingBase:
@staticmethod
def _reset_material_shading(context: Context, use_shadeless: bool = False) -> None:
for i in (x for x in context.scene.objects if x.type == "MESH" and x.mmd_type == "NONE"):
for s in i.material_slots:
if s.material is None:
continue
s.material.use_nodes = False
s.material.use_shadeless = use_shadeless
# Note: material.use_nodes and material.use_shadeless are deprecated in Blender 5.0
# Materials always use nodes now, and shadeless is handled differently
# This method is kept for compatibility but no longer modifies materials
pass
def execute(self, context: Context) -> Dict[str, str]:
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
logger.debug(f"Setting render engine to BLENDER_EEVEE_NEXT")
context.scene.render.engine = "BLENDER_EEVEE"
logger.debug(f"Setting render engine to BLENDER_EEVEE")
shading_mode: Optional[str] = getattr(self, "_shading_mode", None)
for space in self._get_view3d_spaces(context):
+6 -7
View File
@@ -55,14 +55,13 @@ def selectSingleBone(context: bpy.types.Context, armature: Object, bone_name: st
if reset_pose:
for p_bone in armature.pose.bones:
p_bone.matrix_basis.identity()
# Blender 5.0: bone selection in Pose mode now uses pose.bones[].select
armature_bones: bpy.types.ArmatureBones = armature.data.bones
i: Bone
for i in armature_bones:
i.select = i.name == bone_name
i.select_head = i.select_tail = i.select
if i.select:
armature_bones.active = i
i.hide = False
for p_bone in armature.pose.bones:
p_bone.select = p_bone.name == bone_name
if p_bone.select:
armature_bones.active = armature_bones[p_bone.name]
p_bone.hide = False
__CONVERT_NAME_TO_L_REGEXP = re.compile("^(.*)左(.*)$")