-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add an ability to copy/paste/clear armature mapping
- Loading branch information
Showing
5 changed files
with
312 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import bpy | ||
|
||
from .core import mapping_to_text, text_to_mapping, clear_mapping | ||
|
||
WM = bpy.context.window_manager | ||
|
||
|
||
class OBJECT_OT_CopyMapping(bpy.types.Operator): | ||
bl_idname = "animation_retarget.copy_mapping" | ||
bl_label = "Copy Mapping" | ||
bl_description = "Copy the current mapping to the clipboard" | ||
|
||
def execute(self, context): | ||
target_obj = context.active_object | ||
WM.clipboard = mapping_to_text(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 | ||
return True | ||
|
||
|
||
class OBJECT_OT_PasteMapping(bpy.types.Operator): | ||
bl_idname = "animation_retarget.paste_mapping" | ||
bl_label = "Paste Mapping" | ||
bl_description = "Paste the current mapping from the clipboard" | ||
|
||
def execute(self, context): | ||
target_obj = context.active_object | ||
text_to_mapping(WM.clipboard, 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 WM.clipboard: | ||
return False | ||
return True | ||
|
||
|
||
class OBJECT_OT_ClearMapping(bpy.types.Operator): | ||
bl_idname = "animation_retarget.clear_mapping" | ||
bl_label = "Clear Mapping" | ||
bl_description = "Clear the current mapping" | ||
|
||
def execute(self, context): | ||
target_obj = context.active_object | ||
clear_mapping(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 | ||
return True | ||
|
||
|
||
__CLASSES__ = ( | ||
OBJECT_OT_CopyMapping, | ||
OBJECT_OT_PasteMapping, | ||
OBJECT_OT_ClearMapping, | ||
) | ||
|
||
def register(): | ||
for clas in __CLASSES__: | ||
bpy.utils.register_class(clas) | ||
def unregister(): | ||
for clas in reversed(__CLASSES__): | ||
bpy.utils.unregister_class(clas) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
from tests import utils | ||
|
||
import bpy | ||
|
||
from animation_retarget import ops | ||
|
||
|
||
class WM: | ||
def __init__(self): | ||
self.clipboard = '' | ||
|
||
|
||
class TestOperations(utils.BaseTestCase): | ||
def setUp(self): | ||
super().setUp() | ||
ops.WM = WM() | ||
|
||
def tearDown(self): | ||
ops.WM = bpy.context.window_manager | ||
super().tearDown() | ||
|
||
def test_copy(self): | ||
operator = bpy.ops.animation_retarget.copy_mapping | ||
# no armature | ||
self.assertFalse(operator.poll()) | ||
|
||
src = create_armature('src') | ||
tgt = create_armature('tgt') | ||
# no source | ||
self.assertFalse(operator.poll()) | ||
|
||
tgt.animation_retarget.source = src.name | ||
prop = tgt.pose.bones['root'].animation_retarget | ||
prop.source = 'root' | ||
prop.use_location = True | ||
# all ok | ||
self.assertTrue(operator.poll()) | ||
|
||
operator() | ||
self.assertEqual(ops.WM.clipboard, """[object] | ||
source = src | ||
[bone:root] | ||
source = root | ||
use_location = True | ||
source_to_target_rest = (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) | ||
delta_transform = (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) | ||
""") | ||
|
||
def test_paste(self): | ||
operator = bpy.ops.animation_retarget.paste_mapping | ||
# no armature | ||
self.assertFalse(operator.poll()) | ||
|
||
create_armature('src') | ||
tgt = create_armature('tgt') | ||
ops.WM.clipboard = '' | ||
# the clipboard is empty | ||
self.assertFalse(operator.poll()) | ||
ops.WM.clipboard = """ | ||
[object] | ||
source=src | ||
[bone:root] | ||
source = root | ||
use_rotation = True | ||
source_to_target_rest = (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) | ||
""" | ||
# all ok | ||
self.assertTrue(operator.poll()) | ||
|
||
operator() | ||
self.assertEqual(tgt.animation_retarget.source, 'src') | ||
prop = tgt.pose.bones['root'].animation_retarget | ||
self.assertEqual(prop.source, 'root') | ||
self.assertFalse(prop.use_location) | ||
self.assertTrue(prop.use_rotation) | ||
|
||
def test_clear(self): | ||
operator = bpy.ops.animation_retarget.clear_mapping | ||
# no armature | ||
self.assertFalse(operator.poll()) | ||
|
||
src = create_armature('src') | ||
tgt = create_armature('tgt') | ||
tgt.animation_retarget.source = src.name | ||
prop = tgt.pose.bones['root'].animation_retarget | ||
prop.source = 'root' | ||
prop.use_location = True | ||
prop.use_rotation = True | ||
# all ok | ||
self.assertTrue(operator.poll()) | ||
|
||
operator() | ||
self.assertEqual(tgt.animation_retarget.source, '') | ||
self.assertEqual(prop.source, '') | ||
self.assertFalse(prop.use_location) | ||
self.assertFalse(prop.use_rotation) | ||
|
||
|
||
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.ops.object.mode_set(mode='EDIT') | ||
try: | ||
root = arm.edit_bones.new('root') | ||
root.tail = (0, 0, 1) | ||
child = arm.edit_bones.new('child') | ||
child.parent = root | ||
child.head = root.tail | ||
child.tail = (0, 1, 1) | ||
finally: | ||
bpy.ops.object.mode_set(mode='OBJECT') | ||
return obj |