Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b5bff9222 | |||
| 862849c032 | |||
| e060186716 | |||
| 07c4dd501f | |||
| e80c0c034d | |||
| f40b2faacb | |||
| d2b98716ff | |||
| e4f3cdbf17 | |||
| 1d34ac2dd8 | |||
| 3bb533ff64 | |||
| 69cae02160 | |||
| 5496078a39 | |||
| dbf2fb77f9 | |||
| 3de600cf64 | |||
| ba9d579176 | |||
| 35458f9aed | |||
| d2c30caef5 | |||
| b9f7a4acd0 | |||
| e626bdc5c5 | |||
| da2bfeb2fc | |||
| 2b53146e83 | |||
| 4ba594d712 |
@@ -1,4 +1,5 @@
|
|||||||
# Avatar Toolkit
|
# Avatar Toolkit
|
||||||
|
We are aware the wiki is down and are working on a new one, please don't report this.
|
||||||
|
|
||||||
## Avatar Toolkit is in Alpha, There will be issues, please ensure you report them!. If using a Alpha plugin isn't your fancy you can find Cats Blender Plugin [HERE](https://github.com/unofficalcats/Cats-Blender-Plugin-Unofficial-)!
|
## Avatar Toolkit is in Alpha, There will be issues, please ensure you report them!. If using a Alpha plugin isn't your fancy you can find Cats Blender Plugin [HERE](https://github.com/unofficalcats/Cats-Blender-Plugin-Unofficial-)!
|
||||||
#### Avatar Toolkit is in Alpha and will contain issues, please ensure you report them!
|
#### Avatar Toolkit is in Alpha and will contain issues, please ensure you report them!
|
||||||
@@ -35,7 +36,6 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
|
|||||||
- Blender 4.5 or newer is required
|
- Blender 4.5 or newer is required
|
||||||
- Blender 4.5 is the current recommended version
|
- Blender 4.5 is the current recommended version
|
||||||
|
|
||||||
|
|
||||||
2) Python Requirements
|
2) Python Requirements
|
||||||
- If using a custom Python installation with Blender, ensure NumPy is installed
|
- If using a custom Python installation with Blender, ensure NumPy is installed
|
||||||
- Default Blender installation includes all required packages
|
- Default Blender installation includes all required packages
|
||||||
@@ -44,6 +44,16 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
|
|||||||
- Download Blender directly from https://blender.org
|
- Download Blender directly from https://blender.org
|
||||||
- Use Blender 4.5 for the best experience
|
- Use Blender 4.5 for the best experience
|
||||||
|
|
||||||
|
#### Unfortunately, due to the increased number of people complaining to me (yes, we get DMs about this) that AT or CATS is broken when it's not, we are going to have to be a bit more strict about which Blender releases we will provide support for.
|
||||||
|
|
||||||
|
#### We only support the following Blender releases:
|
||||||
|
- Steam release
|
||||||
|
- The Blender website releases (there are downloads for Linux, Mac, and Windows)
|
||||||
|
|
||||||
|
#### We do not support the following what so ever and we will not give help if your running the following.
|
||||||
|
- We do not support the Windows Store due to it causing issues, and we also don't support the Snap Store for Linux.
|
||||||
|
- We do not support package manager releases on Linux. This is because package managers are normally run by the distro, and a lot of the time the distro will build Blender themselves and make their own changes which are not sanctioned by Blender (for example, bundling a newer version of Python which tends to break plugins). If you report a bug from anything apart from the Blender versions we support, you will be told we can't help you from now on.
|
||||||
|
|
||||||
#### Additional Plugins Requirements.
|
#### Additional Plugins Requirements.
|
||||||
Currently None.
|
Currently None.
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "avatar_toolkit"
|
id = "avatar_toolkit"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
name = "Avatar Toolkit"
|
name = "Avatar Toolkit"
|
||||||
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
||||||
maintainer = "Team NekoNeo"
|
maintainer = "Team NekoNeo"
|
||||||
type = "add-on"
|
type = "add-on"
|
||||||
|
|
||||||
blender_version_min = "4.5.0"
|
blender_version_min = "5.0.0"
|
||||||
|
|
||||||
license = [
|
license = [
|
||||||
"SPDX:GPL-3.0-or-later",
|
"SPDX:GPL-3.0-or-later",
|
||||||
|
|||||||
@@ -881,7 +881,8 @@ non_standard_mappings = {
|
|||||||
'left_leg': [
|
'left_leg': [
|
||||||
'mixamorig:LeftUpLeg', 'mixamorig_LeftUpLeg',
|
'mixamorig:LeftUpLeg', 'mixamorig_LeftUpLeg',
|
||||||
'ORG-thigh.L', 'thigh.L',
|
'ORG-thigh.L', 'thigh.L',
|
||||||
'lThighBend', 'lThigh', 'UpperLeg.L'
|
'lThighBend', 'lThigh', 'UpperLeg.L',
|
||||||
|
'LeftUpperLeg'
|
||||||
],
|
],
|
||||||
'left_knee': [
|
'left_knee': [
|
||||||
'mixamorig:LeftLeg', 'mixamorig_LeftLeg',
|
'mixamorig:LeftLeg', 'mixamorig_LeftLeg',
|
||||||
@@ -896,13 +897,14 @@ non_standard_mappings = {
|
|||||||
'left_toe': [
|
'left_toe': [
|
||||||
'mixamorig:LeftToeBase', 'mixamorig_LeftToeBase',
|
'mixamorig:LeftToeBase', 'mixamorig_LeftToeBase',
|
||||||
'ORG-toe.L', 'toe.L',
|
'ORG-toe.L', 'toe.L',
|
||||||
'lToe', 'Toes.L'
|
'lToe', 'Toes.L', 'LeftToeBase'
|
||||||
],
|
],
|
||||||
|
|
||||||
'right_leg': [
|
'right_leg': [
|
||||||
'mixamorig:RightUpLeg', 'mixamorig_RightUpLeg',
|
'mixamorig:RightUpLeg', 'mixamorig_RightUpLeg',
|
||||||
'ORG-thigh.R', 'thigh.R',
|
'ORG-thigh.R', 'thigh.R',
|
||||||
'rThighBend', 'rThigh', 'UpperLeg.R'
|
'rThighBend', 'rThigh', 'UpperLeg.R',
|
||||||
|
'RightUpperLeg'
|
||||||
],
|
],
|
||||||
'right_knee': [
|
'right_knee': [
|
||||||
'mixamorig:RightLeg', 'mixamorig_RightLeg',
|
'mixamorig:RightLeg', 'mixamorig_RightLeg',
|
||||||
@@ -917,7 +919,7 @@ non_standard_mappings = {
|
|||||||
'right_toe': [
|
'right_toe': [
|
||||||
'mixamorig:RightToeBase', 'mixamorig_RightToeBase',
|
'mixamorig:RightToeBase', 'mixamorig_RightToeBase',
|
||||||
'ORG-toe.R', 'toe.R',
|
'ORG-toe.R', 'toe.R',
|
||||||
'rToe', 'Toes.R'
|
'rToe', 'Toes.R', 'RightToeBase'
|
||||||
],
|
],
|
||||||
|
|
||||||
'thumb_1_l': [
|
'thumb_1_l': [
|
||||||
|
|||||||
+19
-5
@@ -10,6 +10,7 @@ from typing import Optional, List, Tuple, Callable, Any, Union
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Object, ID, Camera, Context
|
from bpy.types import Object, ID, Camera, Context
|
||||||
|
from bpy_extras import anim_utils
|
||||||
from mathutils import Vector, Matrix, Euler
|
from mathutils import Vector, Matrix, Euler
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -247,14 +248,27 @@ class MMDCamera:
|
|||||||
frame_count = frame_end - frame_start
|
frame_count = frame_end - frame_start
|
||||||
frames = range(frame_start, frame_end)
|
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 = []
|
fcurves = []
|
||||||
for i in range(3):
|
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):
|
for i in range(3):
|
||||||
fcurves.append(parent_action.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
|
fcurves.append(parent_channelbag.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_channelbag.fcurves.new(data_path="mmd_camera.angle")) # fov
|
||||||
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
|
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
|
||||||
fcurves.append(distance_action.fcurves.new(data_path="location", index=1)) # dis
|
fcurves.append(distance_channelbag.fcurves.new(data_path="location", index=1)) # dis
|
||||||
for c in fcurves:
|
for c in fcurves:
|
||||||
c.keyframe_points.add(frame_count)
|
c.keyframe_points.add(frame_count)
|
||||||
|
|
||||||
|
|||||||
@@ -481,7 +481,7 @@ class FnMaterial:
|
|||||||
|
|
||||||
preferred_output_node_target = {
|
preferred_output_node_target = {
|
||||||
"CYCLES": "CYCLES",
|
"CYCLES": "CYCLES",
|
||||||
"BLENDER_EEVEE_NEXT": "EEVEE",
|
"BLENDER_EEVEE": "EEVEE",
|
||||||
}.get(active_render_engine, "ALL")
|
}.get(active_render_engine, "ALL")
|
||||||
|
|
||||||
tex_node = None
|
tex_node = None
|
||||||
@@ -559,7 +559,12 @@ class FnMaterial:
|
|||||||
mat = self.material
|
mat = self.material
|
||||||
if mat.node_tree is None:
|
if mat.node_tree is None:
|
||||||
logger.debug(f"Creating node tree for {mat.name}")
|
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()
|
mat.node_tree.nodes.clear()
|
||||||
|
|
||||||
nodes, links = mat.node_tree.nodes, mat.node_tree.links
|
nodes, links = mat.node_tree.nodes, mat.node_tree.links
|
||||||
|
|||||||
@@ -534,7 +534,8 @@ class PMXImporter:
|
|||||||
elif b_bone.name in specialTipBones:
|
elif b_bone.name in specialTipBones:
|
||||||
mmd_bone.is_tip = True
|
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:
|
if not pmx_bone.isRotatable:
|
||||||
b_bone.lock_rotation = [True, True, True]
|
b_bone.lock_rotation = [True, True, True]
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class RigidBodyMaterial:
|
|||||||
mat.shadow_method = "NONE"
|
mat.shadow_method = "NONE"
|
||||||
mat.use_backface_culling = True
|
mat.use_backface_culling = True
|
||||||
mat.show_transparent_back = False
|
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, links = mat.node_tree.nodes, mat.node_tree.links
|
||||||
nodes.clear()
|
nodes.clear()
|
||||||
node_color = nodes.new("ShaderNodeBackground")
|
node_color = nodes.new("ShaderNodeBackground")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import os
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from bpy_extras import anim_utils
|
||||||
from mathutils import Quaternion, Vector
|
from mathutils import Quaternion, Vector
|
||||||
|
|
||||||
from ... import utils
|
from ... import utils
|
||||||
@@ -300,21 +301,31 @@ class VMDImporter:
|
|||||||
kp.handle_right = kp.co + Vector((1, 0))
|
kp.handle_right = kp.co + Vector((1, 0))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __keyframe_insert_inner(fcurves: bpy.types.ActionFCurves, path: str, index: int, frame: float, value: float):
|
def __get_channelbag(action: bpy.types.Action, target_id=None):
|
||||||
fcurve = fcurves.find(path, index=index)
|
"""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:
|
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"})
|
fcurve.keyframe_points.insert(frame, value, options={"FAST"})
|
||||||
|
|
||||||
@staticmethod
|
@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)):
|
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):
|
elif isinstance(value, Vector):
|
||||||
VMDImporter.__keyframe_insert_inner(fcurves, path, 0, frame, value[0])
|
VMDImporter.__keyframe_insert_inner(action, path, 0, frame, value[0], target_id, group_name)
|
||||||
VMDImporter.__keyframe_insert_inner(fcurves, path, 1, frame, value[1])
|
VMDImporter.__keyframe_insert_inner(action, path, 1, frame, value[1], target_id, group_name)
|
||||||
VMDImporter.__keyframe_insert_inner(fcurves, path, 2, frame, value[2])
|
VMDImporter.__keyframe_insert_inner(action, path, 2, frame, value[2], target_id, group_name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError("Unsupported type: {0}".format(type(value)))
|
raise TypeError("Unsupported type: {0}".format(type(value)))
|
||||||
@@ -407,16 +418,19 @@ class VMDImporter:
|
|||||||
assert bone_name_table.get(bone.name, name) == name
|
assert bone_name_table.get(bone.name, name) == name
|
||||||
bone_name_table[bone.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)
|
fcurves = [dummy_keyframe_points] * 7 # x, y, z, r0, r1, r2, (r3)
|
||||||
data_path_rot = prop_rot_map.get(bone.rotation_mode, "rotation_euler")
|
data_path_rot = prop_rot_map.get(bone.rotation_mode, "rotation_euler")
|
||||||
bone_rotation = getattr(bone, data_path_rot)
|
bone_rotation = getattr(bone, data_path_rot)
|
||||||
default_values = list(bone.location) + list(bone_rotation)
|
default_values = list(bone.location) + list(bone_rotation)
|
||||||
data_path = 'pose.bones["%s"].location' % bone.name
|
data_path = 'pose.bones["%s"].location' % bone.name
|
||||||
for axis_i in range(3):
|
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)
|
data_path = 'pose.bones["%s"].%s' % (bone.name, data_path_rot)
|
||||||
for axis_i in range(len(bone_rotation)):
|
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)):
|
for i in range(len(default_values)):
|
||||||
c = fcurves[i]
|
c = fcurves[i]
|
||||||
@@ -458,7 +472,9 @@ class VMDImporter:
|
|||||||
self.__setInterpolation(interp[idx : idx + 16 : 4], prev_kp, kp)
|
self.__setInterpolation(interp[idx : idx + 16 : 4], prev_kp, kp)
|
||||||
prev_kps = curr_kps
|
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)
|
self.__fixFcurveHandles(c)
|
||||||
|
|
||||||
# property animation
|
# property animation
|
||||||
@@ -518,7 +534,8 @@ class VMDImporter:
|
|||||||
continue
|
continue
|
||||||
logging.info("(mesh) frames:%5d name: %s", len(keyFrames), name)
|
logging.info("(mesh) frames:%5d name: %s", len(keyFrames), name)
|
||||||
shapeKey = shapeKeyDict[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))
|
fcurve.keyframe_points.add(len(keyFrames))
|
||||||
keyFrames.sort(key=lambda x: x.frame_number)
|
keyFrames.sort(key=lambda x: x.frame_number)
|
||||||
for k, v in zip(keyFrames, fcurve.keyframe_points):
|
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])
|
logging.debug("(Display) list(frame, show): %s", [(keyFrame.frame_number, bool(keyFrame.visible)) for keyFrame in propertyAnim])
|
||||||
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)
|
self.__assign_action(rootObj, action)
|
||||||
|
|
||||||
@@ -574,14 +591,18 @@ class VMDImporter:
|
|||||||
if self.__mirror:
|
if self.__mirror:
|
||||||
_loc, _rot = _MirrorMapper.get_location, _MirrorMapper.get_rotation3
|
_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 = []
|
fcurves = []
|
||||||
for i in range(3):
|
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):
|
for i in range(3):
|
||||||
fcurves.append(parent_action.fcurves.new(data_path="rotation_euler", index=i)) # rx, ry, rz
|
fcurves.append(parent_channelbag.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_channelbag.fcurves.new(data_path="mmd_camera.angle")) # fov
|
||||||
fcurves.append(parent_action.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
|
fcurves.append(parent_channelbag.fcurves.new(data_path="mmd_camera.is_perspective")) # persp
|
||||||
fcurves.append(distance_action.fcurves.new(data_path="location", index=1)) # dis
|
fcurves.append(distance_channelbag.fcurves.new(data_path="location", index=1)) # dis
|
||||||
for c in fcurves:
|
for c in fcurves:
|
||||||
c.keyframe_points.add(len(cameraAnim))
|
c.keyframe_points.add(len(cameraAnim))
|
||||||
|
|
||||||
@@ -640,10 +661,11 @@ class VMDImporter:
|
|||||||
_loc = _MirrorMapper.get_location if self.__mirror else lambda i: i
|
_loc = _MirrorMapper.get_location if self.__mirror else lambda i: i
|
||||||
for keyFrame in lampAnim:
|
for keyFrame in lampAnim:
|
||||||
frame = keyFrame.frame_number + self.__frame_margin
|
frame = keyFrame.frame_number + self.__frame_margin
|
||||||
self.__keyframe_insert(color_action.fcurves, "color", frame, Vector(keyFrame.color))
|
self.__keyframe_insert(color_action, "color", frame, Vector(keyFrame.color), lampObj)
|
||||||
self.__keyframe_insert(location_action.fcurves, "location", frame, Vector(_loc(keyFrame.direction)).xzy * -1)
|
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.detectLampChange(fcurve)
|
||||||
|
|
||||||
self.__assign_action(lampObj.data, color_action)
|
self.__assign_action(lampObj.data, color_action)
|
||||||
|
|||||||
@@ -127,9 +127,9 @@ def convertToBlenderShader(obj: bpy.types.Object, use_principled: bool = False,
|
|||||||
for i in obj.material_slots:
|
for i in obj.material_slots:
|
||||||
if not i.material:
|
if not i.material:
|
||||||
continue
|
continue
|
||||||
if not i.material.use_nodes:
|
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
|
||||||
logger.debug(f"Enabling nodes for material: {i.material.name}")
|
if not i.material.node_tree or len(i.material.node_tree.nodes) == 0:
|
||||||
i.material.use_nodes = True
|
logger.debug(f"Setting up node tree for material: {i.material.name}")
|
||||||
__convertToMMDBasicShader(i.material)
|
__convertToMMDBasicShader(i.material)
|
||||||
if use_principled:
|
if use_principled:
|
||||||
logger.debug(f"Converting material to Principled BSDF: {i.material.name}")
|
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:
|
for i in obj.material_slots:
|
||||||
if not i.material:
|
if not i.material:
|
||||||
continue
|
continue
|
||||||
if not i.material.use_nodes:
|
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
|
||||||
logger.debug(f"Enabling nodes for material: {i.material.name}")
|
|
||||||
i.material.use_nodes = True
|
|
||||||
FnMaterial.convert_to_mmd_material(i.material)
|
FnMaterial.convert_to_mmd_material(i.material)
|
||||||
|
|
||||||
def __convertToMMDBasicShader(material: Material) -> None:
|
def __convertToMMDBasicShader(material: Material) -> None:
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ class EdgePreviewSetup(Operator):
|
|||||||
return mat
|
return mat
|
||||||
|
|
||||||
def __make_shader(self, m: Material) -> None:
|
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
|
nodes, links = m.node_tree.nodes, m.node_tree.links
|
||||||
|
|
||||||
node_shader = nodes.get("mmd_edge_preview", None)
|
node_shader = nodes.get("mmd_edge_preview", None)
|
||||||
|
|||||||
@@ -475,7 +475,8 @@ class ViewBoneMorph(bpy.types.Operator):
|
|||||||
for morph_data in morph.data:
|
for morph_data in morph.data:
|
||||||
p_bone: Optional[bpy.types.PoseBone] = armature.pose.bones.get(morph_data.bone, None)
|
p_bone: Optional[bpy.types.PoseBone] = armature.pose.bones.get(morph_data.bone, None)
|
||||||
if p_bone:
|
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 = (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
|
mtx.translation = p_bone.location + morph_data.location
|
||||||
p_bone.matrix_basis = mtx
|
p_bone.matrix_basis = mtx
|
||||||
@@ -521,9 +522,10 @@ class ApplyBoneMorph(bpy.types.Operator):
|
|||||||
item.bone = p_bone.name
|
item.bone = p_bone.name
|
||||||
item.location = p_bone.location
|
item.location = p_bone.location
|
||||||
item.rotation = p_bone.rotation_quaternion if p_bone.rotation_mode == "QUATERNION" else p_bone.matrix_basis.to_quaternion()
|
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:
|
else:
|
||||||
p_bone.bone.select = False
|
p_bone.select = False
|
||||||
logger.info(f"Applied current pose to bone morph: {morph.name}")
|
logger.info(f"Applied current pose to bone morph: {morph.name}")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -32,16 +32,14 @@ class _SetShadingBase:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _reset_material_shading(context: Context, use_shadeless: bool = False) -> None:
|
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"):
|
# Note: material.use_nodes and material.use_shadeless are deprecated in Blender 5.0
|
||||||
for s in i.material_slots:
|
# Materials always use nodes now, and shadeless is handled differently
|
||||||
if s.material is None:
|
# This method is kept for compatibility but no longer modifies materials
|
||||||
continue
|
pass
|
||||||
s.material.use_nodes = False
|
|
||||||
s.material.use_shadeless = use_shadeless
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> Dict[str, str]:
|
def execute(self, context: Context) -> Dict[str, str]:
|
||||||
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
|
context.scene.render.engine = "BLENDER_EEVEE"
|
||||||
logger.debug(f"Setting render engine to BLENDER_EEVEE_NEXT")
|
logger.debug(f"Setting render engine to BLENDER_EEVEE")
|
||||||
|
|
||||||
shading_mode: Optional[str] = getattr(self, "_shading_mode", None)
|
shading_mode: Optional[str] = getattr(self, "_shading_mode", None)
|
||||||
for space in self._get_view3d_spaces(context):
|
for space in self._get_view3d_spaces(context):
|
||||||
|
|||||||
+6
-7
@@ -55,14 +55,13 @@ def selectSingleBone(context: bpy.types.Context, armature: Object, bone_name: st
|
|||||||
if reset_pose:
|
if reset_pose:
|
||||||
for p_bone in armature.pose.bones:
|
for p_bone in armature.pose.bones:
|
||||||
p_bone.matrix_basis.identity()
|
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
|
armature_bones: bpy.types.ArmatureBones = armature.data.bones
|
||||||
i: Bone
|
for p_bone in armature.pose.bones:
|
||||||
for i in armature_bones:
|
p_bone.select = p_bone.name == bone_name
|
||||||
i.select = i.name == bone_name
|
if p_bone.select:
|
||||||
i.select_head = i.select_tail = i.select
|
armature_bones.active = armature_bones[p_bone.name]
|
||||||
if i.select:
|
p_bone.hide = False
|
||||||
armature_bones.active = i
|
|
||||||
i.hide = False
|
|
||||||
|
|
||||||
|
|
||||||
__CONVERT_NAME_TO_L_REGEXP = re.compile("^(.*)左(.*)$")
|
__CONVERT_NAME_TO_L_REGEXP = re.compile("^(.*)左(.*)$")
|
||||||
|
|||||||
+26
-4
@@ -2,6 +2,7 @@ import traceback
|
|||||||
from types import FrameType
|
from types import FrameType
|
||||||
import bpy
|
import bpy
|
||||||
import bpy_extras
|
import bpy_extras
|
||||||
|
from bpy_extras import anim_utils
|
||||||
from numpy import double
|
from numpy import double
|
||||||
from typing import Set, Dict
|
from typing import Set, Dict
|
||||||
import re
|
import re
|
||||||
@@ -116,11 +117,32 @@ class AvatarToolkit_OT_ConvertResonite(Operator):
|
|||||||
|
|
||||||
|
|
||||||
def makeorexistingfcurve(action: bpy.types.Action, data_path: str, action_group: str, index=0) -> bpy.types.FCurve:
|
def makeorexistingfcurve(action: bpy.types.Action, data_path: str, action_group: str, index=0) -> bpy.types.FCurve:
|
||||||
fcurve = action.fcurves.find(data_path=data_path,index=index)
|
"""Get or create an F-Curve using Blender 5.0 channelbag system.
|
||||||
if fcurve == None:
|
|
||||||
return action.fcurves.new(data_path,action_group=action_group,index=index)
|
Blender 5.0 Breaking Change: The legacy action.fcurves API has been removed.
|
||||||
|
F-Curves are now accessed through channelbags. Each slot of an Action can have a channelbag.
|
||||||
|
This function has been migrated to use bpy_extras.anim_utils.action_ensure_channelbag_for_slot().
|
||||||
|
"""
|
||||||
|
# Get the action slot (assumes single slot for now - armature actions typically use first slot)
|
||||||
|
if not action.slots:
|
||||||
|
slot = action.slots.new(for_id=bpy.context.object.data if bpy.context.object and bpy.context.object.type == 'ARMATURE' else None)
|
||||||
else:
|
else:
|
||||||
print("fcurve with data \""+data_path+"\" already exists")
|
slot = action.slots[0]
|
||||||
|
|
||||||
|
# Get or create channelbag for this slot
|
||||||
|
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, slot)
|
||||||
|
|
||||||
|
# Use ensure() to get existing or create new F-Curve
|
||||||
|
fcurve = channelbag.fcurves.ensure(data_path, index=index, group_name=action_group)
|
||||||
|
|
||||||
|
if fcurve:
|
||||||
|
return fcurve
|
||||||
|
else:
|
||||||
|
print(f"fcurve with data \"{data_path}\" creation failed")
|
||||||
|
# Fallback: try to find or create manually
|
||||||
|
fcurve = channelbag.fcurves.find(data_path, index=index)
|
||||||
|
if fcurve is None:
|
||||||
|
fcurve = channelbag.fcurves.new(data_path, index=index, group_name=action_group)
|
||||||
return fcurve
|
return fcurve
|
||||||
|
|
||||||
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
|
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
|
||||||
|
|||||||
+2
-2
@@ -19,8 +19,8 @@ GITHUB_REPO = "teamneoneko/Avatar-Toolkit"
|
|||||||
|
|
||||||
# Define which version series this installation can update to
|
# Define which version series this installation can update to
|
||||||
# For example: ["0.1"] means only look for 0.1.x updates
|
# For example: ["0.1"] means only look for 0.1.x updates
|
||||||
# ["0.2", "0.3"] would look for both 0.2.x and 0.3.x updates
|
# ["0.2", "0.3"] would look for both 0.2.x and 0.3.x
|
||||||
ALLOWED_VERSION_SERIES = ["0.4"]
|
ALLOWED_ = ["0.5"]
|
||||||
|
|
||||||
is_checking_for_update: bool = False
|
is_checking_for_update: bool = False
|
||||||
update_needed: bool = False
|
update_needed: bool = False
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ def scale_images_to_largest(images: List[Image]) -> tuple[int, int]:
|
|||||||
x: int = 0
|
x: int = 0
|
||||||
y: int = 0
|
y: int = 0
|
||||||
|
|
||||||
valid_images = [img for img in images if img and img.has_data]
|
valid_images = []
|
||||||
|
for img in images:
|
||||||
|
if img:
|
||||||
|
try:
|
||||||
|
if img.has_data:
|
||||||
|
valid_images.append(img)
|
||||||
|
except ReferenceError:
|
||||||
|
# Image has been removed from Blender's memory
|
||||||
|
pass
|
||||||
|
|
||||||
if not valid_images:
|
if not valid_images:
|
||||||
return 0, 0
|
return 0, 0
|
||||||
@@ -66,50 +74,56 @@ def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
|
|||||||
new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
|
new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_albedo_replacement"
|
name = mat_slot.material.name + "_albedo_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.albedo = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.albedo = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.albedo = bpy.data.images[name]
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.normal = bpy.data.images[mat_slot.material.texture_atlas_normal]
|
new_mat_image_item.normal = bpy.data.images[mat_slot.material.texture_atlas_normal]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_normal_replacement"
|
name = mat_slot.material.name + "_normal_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.normal = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.normal = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32)
|
new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.normal = bpy.data.images[name]
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.emission = bpy.data.images[mat_slot.material.texture_atlas_emission]
|
new_mat_image_item.emission = bpy.data.images[mat_slot.material.texture_atlas_emission]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_emission_replacement"
|
name = mat_slot.material.name + "_emission_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.emission = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.emission = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.emission = bpy.data.images[name]
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.ambient_occlusion = bpy.data.images[mat_slot.material.texture_atlas_ambient_occlusion]
|
new_mat_image_item.ambient_occlusion = bpy.data.images[mat_slot.material.texture_atlas_ambient_occlusion]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_ambient_occlusion_replacement"
|
name = mat_slot.material.name + "_ambient_occlusion_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.ambient_occlusion = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.ambient_occlusion = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32)
|
new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.ambient_occlusion = bpy.data.images[name]
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.height = bpy.data.images[mat_slot.material.texture_atlas_height]
|
new_mat_image_item.height = bpy.data.images[mat_slot.material.texture_atlas_height]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_height_replacement"
|
name = mat_slot.material.name + "_height_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.height = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.height = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32)
|
new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.height = bpy.data.images[name]
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.roughness = bpy.data.images[mat_slot.material.texture_atlas_roughness]
|
new_mat_image_item.roughness = bpy.data.images[mat_slot.material.texture_atlas_roughness]
|
||||||
except Exception:
|
except Exception:
|
||||||
name = mat_slot.material.name + "_roughness_replacement"
|
name = mat_slot.material.name + "_roughness_replacement"
|
||||||
if name in bpy.data.images:
|
if name not in bpy.data.images:
|
||||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
|
||||||
new_mat_image_item.roughness = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
new_mat_image_item.roughness = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||||
new_mat_image_item.roughness.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,0.0]), 32*32)
|
new_mat_image_item.roughness.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,0.0]), 32*32)
|
||||||
|
else:
|
||||||
|
new_mat_image_item.roughness = bpy.data.images[name]
|
||||||
|
|
||||||
new_mat_image_item.material = mat_slot.material
|
new_mat_image_item.material = mat_slot.material
|
||||||
new_mat_image_item.parent_mesh = obj
|
new_mat_image_item.parent_mesh = obj
|
||||||
@@ -227,7 +241,7 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
|||||||
# Create material nodes
|
# Create material nodes
|
||||||
atlased_mat.material = bpy.data.materials.new(
|
atlased_mat.material = bpy.data.materials.new(
|
||||||
name=f"Atlas_Final_{context.scene.name}_{Path(bpy.data.filepath).stem}")
|
name=f"Atlas_Final_{context.scene.name}_{Path(bpy.data.filepath).stem}")
|
||||||
atlased_mat.material.use_nodes = True
|
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
|
||||||
atlased_mat.material.node_tree.nodes.clear()
|
atlased_mat.material.node_tree.nodes.clear()
|
||||||
|
|
||||||
principled_node = atlased_mat.material.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
principled_node = atlased_mat.material.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
|||||||
wm.progress_update(100)
|
wm.progress_update(100)
|
||||||
wm.progress_end()
|
wm.progress_end()
|
||||||
|
|
||||||
|
# Restore settings only for the base armature since merge_armature is removed during join
|
||||||
restore_breaking_settings_armature(base_armature, data_breaking_base)
|
restore_breaking_settings_armature(base_armature, data_breaking_base)
|
||||||
if merge_armature_name_stored in bpy.data.objects:
|
if merge_armature_name_stored in bpy.data.objects:
|
||||||
merge_armature_obj = bpy.data.objects[merge_armature_name_stored]
|
merge_armature_obj = bpy.data.objects[merge_armature_name_stored]
|
||||||
@@ -124,8 +125,9 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error merging armatures: {str(e)}\n{traceback.format_exc()}")
|
errormessage: str = traceback.format_exc()
|
||||||
self.report({'ERROR'}, traceback.format_exc())
|
logger.error(f"Error merging armatures: {str(e)}\n{errormessage}")
|
||||||
|
self.report({'ERROR'}, f"Error merging armatures: {errormessage}")
|
||||||
|
|
||||||
# Try to restore original mode even on error
|
# Try to restore original mode even on error
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import traceback
|
import traceback
|
||||||
import bpy
|
import bpy
|
||||||
|
import bpy_extras
|
||||||
|
from bpy_extras import anim_utils
|
||||||
import re
|
import re
|
||||||
from bpy.types import Operator, Context, EditBone, Object, Armature, Mesh
|
from bpy.types import Operator, Context, EditBone, Object, Armature, Mesh
|
||||||
from typing import Optional, Dict, Any, List, Tuple
|
from typing import Optional, Dict, Any, List, Tuple
|
||||||
@@ -347,10 +349,17 @@ class AvatarToolKit_OT_FlipCurrentKeyFrames(Operator):
|
|||||||
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
|
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
|
||||||
|
|
||||||
|
|
||||||
|
# Get channelbag for the action using Blender 5.0 API
|
||||||
|
action = armature.animation_data.action
|
||||||
|
if not action.slots:
|
||||||
|
slot = action.slots.new(for_id=armature.data)
|
||||||
|
else:
|
||||||
|
slot = action.slots[0]
|
||||||
|
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, slot)
|
||||||
|
|
||||||
#create a set for every frame time where we need to key a keyframe for the flipped pose
|
#create a set for every frame time where we need to key a keyframe for the flipped pose
|
||||||
times: Dict[float,list[bpy.types.FCurve]] = {}
|
times: Dict[float,list[bpy.types.FCurve]] = {}
|
||||||
for curve in armature.animation_data.action.fcurves:
|
for curve in channelbag.fcurves:
|
||||||
if not curve.data_path.startswith("pose"):
|
if not curve.data_path.startswith("pose"):
|
||||||
continue
|
continue
|
||||||
for point in curve.keyframe_points:
|
for point in curve.keyframe_points:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"messages": {
|
||||||
"AvatarToolkit.label": "Avatar Toolkit (Alpha 0.4.0)",
|
"AvatarToolkit.label": "Avatar Toolkit (Alpha 0.5.0)",
|
||||||
"AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there",
|
"AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there",
|
||||||
"AvatarToolkit.desc2": "will be issues, if you find any issues,",
|
"AvatarToolkit.desc2": "will be issues, if you find any issues,",
|
||||||
"AvatarToolkit.desc3": "please report it on our Github.",
|
"AvatarToolkit.desc3": "please report it on our Github.",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"messages": {
|
||||||
"AvatarToolkit.label": "アバターツールキット (アルファ 0.4.0)",
|
"AvatarToolkit.label": "アバターツールキット (アルファ 0.5.0)",
|
||||||
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
|
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
|
||||||
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
|
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
|
||||||
"AvatarToolkit.desc3": "GitHubで報告してください。",
|
"AvatarToolkit.desc3": "GitHubで報告してください。",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"messages": {
|
||||||
"AvatarToolkit.label": "아바타 툴킷 (알파 0.4.0)",
|
"AvatarToolkit.label": "아바타 툴킷 (알파 0.5.0)",
|
||||||
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
|
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
|
||||||
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
|
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
|
||||||
"AvatarToolkit.desc3": "Github에 보고해 주세요.",
|
"AvatarToolkit.desc3": "Github에 보고해 주세요.",
|
||||||
|
|||||||
Reference in New Issue
Block a user