Skip to content

Commit

Permalink
Merge pull request #5 from /issues/4/support-2.81
Browse files Browse the repository at this point in the history
Add 2.80+ support
  • Loading branch information
igelbox committed Feb 28, 2020
2 parents e737260 + 19abb5a commit e78a64d
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 45 deletions.
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ cache:
directories:
- blender

env:
- BLENDER_VERSION=2.81

install:
- sh ./tests/ci-prepare.sh

script:
- ./blender/blender --factory-startup -noaudio -b --python-exit-code 1 --python tests/runner.py
- ./blender/$BLENDER_VERSION/blender --factory-startup -noaudio -b --python-exit-code 1 --python tests/runner.py

after_success:
- bash <(curl -s https://codecov.io/bash)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The main goal is to __link/synchronize__ target armature __bones__ with a source
Thus, allowing you to __use your next favorite tools__ for baking/exporting/using the result motions. For the glory of the Unix way.

# How to Install
* For Blender 2.80+ - __not supported yet__, the fix is [still in progress](https://github.com/igelbox/blender-retarget/issues/4).
* For Blender 2.80+ - use *animation-retarget-x.x.x.zip* from the [latest release](https://github.com/igelbox/blender-retarget/releases/latest) page.
* For Blender 2.79 - the initial [animation-retarget-0.1.0.zip](https://github.com/igelbox/blender-retarget/releases/download/v0.1.0/animation-retarget-0.1.0.zip) should work fine.

# How to Use
Expand Down
4 changes: 2 additions & 2 deletions animation_retarget/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
bl_info = {
'name': 'Animation Retargeting Tools',
'author': 'Vakhurin Sergey (igel)',
'version': (0, 1, 0),
'blender': (2, 77, 0),
'version': (1, 0, 0),
'blender': (2, 80, 0),
'category': 'Animation',
'location': 'Properties > Object, Properties > Bone',
'description': 'Applies an animation from one armature to another',
Expand Down
57 changes: 38 additions & 19 deletions animation_retarget/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

__CONFIG_PREFIX_BONE__ = 'bone:'

__TRICK_BLENDER28_ACTION_PREFIX__ = 'Just-a-Trick-to-Refresh'


def mapping_to_text(target_obj):
def put_nonempty_value(data, name, value):
Expand Down Expand Up @@ -81,7 +83,7 @@ def parse_tuple(text):
if name in value:
setattr(prop, name, parse_tuple(value[name]))
prop.invalidate_cache()
bpy.context.scene.update()
bpy.context.view_layer.update()


__ZERO_V16__ = (0,) * 16
Expand All @@ -94,24 +96,41 @@ def clear_mapping(target_obj):
prop.use_location = prop.use_rotation = False
prop.source_to_target_rest = prop.delta_transform = __ZERO_V16__
prop.invalidate_cache()
bpy.context.scene.update()
bpy.context.view_layer.update()


def trick_blender28(target_obj):
animation_data = target_obj.animation_data_create()
action = animation_data.action
if not action:
for act in bpy.data.actions:
if act.name.startswith(__TRICK_BLENDER28_ACTION_PREFIX__):
action = act
break
if not action:
action = bpy.data.actions.new(__TRICK_BLENDER28_ACTION_PREFIX__)
animation_data.action = action


class RelativeObjectTransform(bpy.types.PropertyGroup):
b_type = bpy.types.Object

def _update_source(self, _context):
if self.source:
for bone in self.id_data.pose.bones:
target_obj = self.id_data
for bone in target_obj.pose.bones:
if bone.animation_retarget.source:
bone.animation_retarget.update_link()

source = bpy.props.StringProperty(
trick_blender28(target_obj)

source: bpy.props.StringProperty(
name='Source Object',
description='An object whose animation will be used',
update=_update_source,
)


def _prop_to_pose_bone(obj, prop):
for bone in obj.pose.bones:
if bone.animation_retarget == prop:
Expand All @@ -130,7 +149,7 @@ def _update_source(self, _context):
if self.source:
self.update_link()

source = bpy.props.StringProperty(
source: bpy.props.StringProperty(
name='Source Bone',
description='A bone whose animation will be used',
update=_update_source,
Expand Down Expand Up @@ -165,7 +184,7 @@ def _set_use_rotation(self, value):
bone.name, fcurve.array_index + 3
)

use_rotation = bpy.props.BoolProperty(
use_rotation: bpy.props.BoolProperty(
name='Link Rotation',
description='Link rotation to the source bone',
get=_get_use_rotation, set=_set_use_rotation,
Expand Down Expand Up @@ -195,17 +214,17 @@ def _set_use_location(self, value):
bone.name, fcurve.array_index
)

use_location = bpy.props.BoolProperty(
use_location: bpy.props.BoolProperty(
name='Link Location',
description='Link location to the source bone',
get=_get_use_location, set=_set_use_location,
)

source_to_target_rest = bpy.props.FloatVectorProperty(
source_to_target_rest: bpy.props.FloatVectorProperty(
description='-private-data-',
size=16,
)
delta_transform = bpy.props.FloatVectorProperty(
delta_transform: bpy.props.FloatVectorProperty(
description='-private-data-',
size=16,
)
Expand Down Expand Up @@ -244,13 +263,13 @@ def update_link(self):
target_obj = self.id_data
target_bone = _prop_to_pose_bone(target_obj, self)

source = source_obj.matrix_world * source_bone.matrix
source_rest = source * source_bone.matrix_basis.inverted()
target = target_obj.matrix_world * target_bone.matrix
target_rest = target * target_bone.matrix_basis.inverted()
source = source_obj.matrix_world @ source_bone.matrix
source_rest = source @ source_bone.matrix_basis.inverted()
target = target_obj.matrix_world @ target_bone.matrix
target_rest = target @ target_bone.matrix_basis.inverted()

source_to_target_rest = target_rest.inverted() * source_rest
delta_transform = source.inverted() * target
source_to_target_rest = target_rest.inverted() @ source_rest
delta_transform = source.inverted() @ target

self.source_to_target_rest = (
*source_to_target_rest.row[0],
Expand All @@ -266,14 +285,14 @@ def update_link(self):
)
self._invalidate()

frame_cache = bpy.props.FloatVectorProperty(
frame_cache: bpy.props.FloatVectorProperty(
description='-private-data-',
size=8,
)

def _invalidate(self):
self.invalidate_cache()
bpy.context.scene.update()
bpy.context.view_layer.update()

def invalidate_cache(self):
self.frame_cache[7] = 0
Expand All @@ -289,7 +308,7 @@ def _get_transform(self):

source_to_target_rest = _fvec16_to_matrix4(self.source_to_target_rest)
delta_transform = _fvec16_to_matrix4(self.delta_transform)
transform = source_to_target_rest * source_bone.matrix_basis * delta_transform
transform = source_to_target_rest @ source_bone.matrix_basis @ delta_transform

location = transform.to_translation()
quaternion = transform.to_quaternion()
Expand All @@ -304,7 +323,7 @@ def _get_transform(self):
self.frame_cache = cache = (*location, *rotation, frame)
return cache

transform = bpy.props.FloatVectorProperty(size=8, get=_get_transform)
transform: bpy.props.FloatVectorProperty(size=8, get=_get_transform)

__CLASSES__ = (
RelativeObjectTransform,
Expand Down
26 changes: 25 additions & 1 deletion animation_retarget/ops.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import bpy

from .core import mapping_to_text, text_to_mapping, clear_mapping
from .core import mapping_to_text, text_to_mapping, clear_mapping, trick_blender28

WM = bpy.context.window_manager

Expand Down Expand Up @@ -62,11 +62,35 @@ def poll(cls, context):
return False
return True

class OBJECT_OT_TrickBlender(bpy.types.Operator):
bl_idname = "animation_retarget.trick_blender"
bl_label = "Fix Refreshing"
bl_description = "Trick Blender 2.8x 'depsgraph' to force driver variables refresh each frame"

def execute(self, context):
target_obj = context.active_object
trick_blender28(target_obj)
return {'FINISHED'}

@classmethod
def poll(cls, context):
target_obj = context.active_object
if (not target_obj) or (target_obj.type not in {'ARMATURE'}):
return False

if not target_obj.animation_retarget.source:
return False # No worries, we fix this on source prop update

animation_data = target_obj.animation_data
if animation_data and animation_data.action:
return False
return True

__CLASSES__ = (
OBJECT_OT_CopyMapping,
OBJECT_OT_PasteMapping,
OBJECT_OT_ClearMapping,
OBJECT_OT_TrickBlender,
)

def register():
Expand Down
25 changes: 14 additions & 11 deletions animation_retarget/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class AbstractBasePanel(bpy.types.Panel):
def draw_header(self, _context):
self.layout.label(icon='PLUGIN')

class ObjectPanel(AbstractBasePanel):
class OBJECT_PT_ObjectPanel(AbstractBasePanel):
bl_context = 'object'

@classmethod
Expand All @@ -19,6 +19,9 @@ def draw(self, context):
layout = self.layout
data = context.object.animation_retarget

if bpy.ops.animation_retarget.trick_blender.poll():
layout.operator('animation_retarget.trick_blender', icon='ERROR')

col = layout.column(align=True)
row = col.row(align=True)
row.operator('animation_retarget.copy_mapping', icon='COPYDOWN', text='Copy')
Expand All @@ -32,20 +35,20 @@ def draw(self, context):
col = layout.column(align=True)
for bone in context.object.pose.bones:
row = col.row(align=True)
row.label(bone.name)
row.label(text=bone.name)
bone_data = bone.animation_retarget
row.prop_search(bone_data, 'source', source.pose, 'bones', text='')
alert = PoseBonePanel.draw_buttons(row, source, bone_data, hide_texts=True)
alert = BONE_PT_PoseBonePanel.draw_buttons(row, source, bone_data, hide_texts=True)
row.active = alert or bool(bone.animation_retarget.source)


class PoseBonePanel(AbstractBasePanel):
class BONE_PT_PoseBonePanel(AbstractBasePanel):
bl_context = 'bone'

@classmethod
def poll(cls, context):
return (
ObjectPanel.poll(context)
OBJECT_PT_ObjectPanel.poll(context)
and context.active_pose_bone
)

Expand All @@ -55,12 +58,12 @@ def draw(self, context):

source_object = bpy.data.objects.get(context.active_object.animation_retarget.source)
if not source_object:
layout.label('Select the source object on the Object Properties panel', icon='INFO')
layout.label(text='Select the source object on the Object Properties panel', icon='INFO')
return

layout.prop_search(data, 'source', source_object.pose, 'bones')
row = layout.row(align=True)
PoseBonePanel.draw_buttons(row, source_object, data)
BONE_PT_PoseBonePanel.draw_buttons(row, source_object, data)

@classmethod
def draw_buttons(cls, layout, source_object, data, hide_texts=False):
Expand All @@ -81,17 +84,17 @@ def modified_layout(layout, use_flag):
modified_layout(
layout,
data.use_rotation,
).prop(data, 'use_rotation', toggle=True, icon='MAN_ROT', **kwargs)
).prop(data, 'use_rotation', toggle=True, icon='CON_ROTLIKE', **kwargs)
modified_layout(
layout,
data.use_location,
).prop(data, 'use_location', toggle=True, icon='MAN_TRANS', **kwargs)
).prop(data, 'use_location', toggle=True, icon='CON_LOCLIKE', **kwargs)
return no_source and (data.use_rotation or data.use_location)


__CLASSES__ = (
ObjectPanel,
PoseBonePanel,
OBJECT_PT_ObjectPanel,
BONE_PT_PoseBonePanel,
)

def register():
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/test_addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ def test_blinfo(self):
self.assertIsNotNone(animation_retarget.bl_info)

def test_enabled(self):
self.assertIn('animation_retarget', bpy.context.user_preferences.addons)
self.assertIn('animation_retarget', bpy.context.preferences.addons)
4 changes: 2 additions & 2 deletions tests/cases/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ def test_clear(self):
def create_armature(name):
arm = bpy.data.armatures.new(name)
obj = bpy.data.objects.new(name, arm)
bpy.context.scene.objects.link(obj)
bpy.context.scene.objects.active = obj
bpy.context.scene.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj

bpy.ops.object.mode_set(mode='EDIT')
try:
Expand Down
16 changes: 9 additions & 7 deletions tests/ci-prepare.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
#!/bin/sh
set -e

if [ ! -e "blender/blender" ]; then
TBZ=blender-2.77-linux-glibc211-x86_64.tar.bz2
DIR="blender/$BLENDER_VERSION"
if [ ! -e "$DIR/blender" ]; then
PYTHON_VERSION="3.7"
TBZ="blender-$BLENDER_VERSION-linux-glibc217-x86_64.tar.bz2"

mkdir -p blender
wget http://download.blender.org/release/Blender2.77/$TBZ
tar jxf $TBZ -C blender --strip-components 1
mkdir -p "$DIR"
wget "http://download.blender.org/release/Blender$BLENDER_VERSION/$TBZ"
tar jxf $TBZ -C "$DIR" --strip-components 1

TGT=$HOME/.config/blender/2.77/scripts/addons
TGT="$HOME/.config/blender/$BLENDER_VERSION/scripts/addons"
mkdir -p $TGT
ln -s animation_retarget.py $TGT/

wget https://pypi.python.org/packages/53/fe/9e0fbdbca15c2c1253379c3a694f4315a420555e7874445b06edeaeacaea/coverage-4.2.tar.gz#md5=1e09362a3f35d589f942359441050a6e
tar zxf coverage-4.2.tar.gz
mv coverage-4.2/coverage blender/2.77/python/lib/python3.5/
mv coverage-4.2/coverage "$DIR/$BLENDER_VERSION/python/lib/python$PYTHON_VERSION/"
rm -rf coverage-4.2
fi

0 comments on commit e78a64d

Please sign in to comment.