C3D: import markers as armature's bones

This commit is contained in:
Daniel Monteiro Basso 2017-02-03 22:32:51 +00:00
parent dbddb9496a
commit 7ef069a9df
1 changed files with 180 additions and 109 deletions

View File

@ -25,8 +25,8 @@
bl_info = {
"name": "C3D Graphics Lab Motion Capture file (.c3d)",
"author": "Daniel Monteiro Basso <daniel@basso.inf.br>",
"version": (2013, 12, 10, 1),
"blender": (2, 69, 5),
"version": (2015, 5, 5, 1),
"blender": (2, 74, 1),
"location": "File > Import",
"description": "Imports C3D Graphics Lab Motion Capture files",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
@ -37,14 +37,14 @@ bl_info = {
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
IntProperty,
)
StringProperty,
BoolProperty,
FloatProperty,
IntProperty,
)
import os
import math
import time
from mathutils import Vector
from . import import_c3d
@ -56,6 +56,7 @@ class C3DAnimateCloud(bpy.types.Operator):
bl_idname = "import_anim.c3danim"
bl_label = "Animate C3D"
is_armature = False
markerset = None
uname = None
curframe = 0
@ -64,25 +65,46 @@ class C3DAnimateCloud(bpy.types.Operator):
timer = None
Y_up = False
def update_empty(self, fno, ml, m):
name = self.unames[self.prefix + ml]
o = bpy.context.scene.objects[name]
p = Vector(m.position) * self.scale
o.location = Vector((p[0], -p[2], p[1])) if self.Y_up else p
o.keyframe_insert('location', frame=fno)
def update_bone(self, fno, ml, m, bones):
name = self.prefix + ml
if name not in bones:
return
b = bones[name]
p = Vector(m.position) * self.scale
b.matrix.translation = Vector((p[0], -p[2], p[1])) if self.Y_up else p
b.keyframe_insert('location', -1, fno, name)
def update_frame(self):
fno = self.curframe
if not self.use_frame_no:
fno = (self.curframe - self.markerset.startFrame) / self.fskip
for i in range(self.fskip):
self.markerset.readNextFrameData()
if self.is_armature:
bones = bpy.context.active_object.pose.bones
for ml in self.markerset.markerLabels:
m = self.markerset.getMarker(ml, self.curframe)
if m.confidence < self.confidence:
continue
if self.is_armature:
self.update_bone(fno, ml, m, bones)
else:
self.update_empty(fno, ml, m)
def modal(self, context, event):
if event.type == 'ESC':
return self.cancel(context)
if event.type == 'TIMER':
if self.curframe > self.markerset.endFrame:
return self.cancel(context)
fno = self.curframe
if not self.use_frame_no:
fno = (self.curframe - self.markerset.startFrame) / self.fskip
for i in range(self.fskip):
self.markerset.readNextFrameData()
for ml in self.markerset.markerLabels:
name = self.unames[self.prefix + ml]
o = bpy.context.scene.objects[name]
m = self.markerset.getMarker(ml, self.curframe)
p = Vector(m.position) * self.scale
o.location = Vector((p[0], -p[2], p[1])) if self.Y_up else p
if m.confidence >= self.confidence:
o.keyframe_insert('location', frame=fno)
self.update_frame()
self.curframe += self.fskip
return {'PASS_THROUGH'}
@ -106,91 +128,98 @@ class C3DImporter(bpy.types.Operator):
bl_label = "Import C3D"
filepath = StringProperty(
subtype='FILE_PATH',
)
subtype='FILE_PATH',
)
Y_up = BoolProperty(
name="Up vector is Y axis",
default=False,
description="Check when the data uses Y-up, uncheck when it uses Z-up",
)
name="Up vector is Y axis",
default=False,
description="Check when the data uses Y-up, uncheck when it uses Z-up",
)
from_inches = BoolProperty(
name="Convert from inches to meters",
default=False,
description="Scale by 2.54/100",
)
name="Convert from inches to meters",
default=False,
description="Scale by 2.54/100",
)
scale = FloatProperty(
name="Scale",
default=1.0,
description="Scale the positions by this value",
min=0.0000001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
)
name="Scale",
default=1.0,
description="Scale the positions by this value",
min=0.0000001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
)
auto_scale = BoolProperty(
name="Adjust scale automatically",
default=False,
description="Guess correct scale factor",
)
name="Adjust scale automatically",
default=False,
description="Guess correct scale factor",
)
auto_magnitude = BoolProperty(
name="Adjust scale magnitude",
default=True,
description="Automatically adjust scale magnitude",
)
name="Adjust scale magnitude",
default=True,
description="Automatically adjust scale magnitude",
)
create_armature = BoolProperty(
name="Create an armature",
default=True,
description="Import the markers as bones instead of empties",
)
size = FloatProperty(
name="Empty Size",
default=.03,
description="The size of each empty",
min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
)
name="Empty or bone size",
default=.03,
description="The size of each empty or bone",
min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
)
x_ray = BoolProperty(
name="Use X-Ray",
default=True,
description="Show the empties over other objects",
)
name="Use X-Ray",
default=True,
description="Show the empties or armature over other objects",
)
frame_skip = IntProperty(
name="Fps divisor",
default=4,
# usually the sample rate is 120, so the default 4 gives you 30fps
description="Frame supersampling factor",
min=1,
)
name="Fps divisor",
default=1,
description="Frame supersampling factor",
min=1,
)
use_frame_no = BoolProperty(
name="Use frame numbers",
default=False,
description="Offset start of animation according to the source",
)
name="Use frame numbers",
default=False,
description="Offset start of animation according to the source",
)
show_names = BoolProperty(
name="Show Names", default=False,
description="Show the markers' name",
)
name="Show Names", default=False,
description="Show the markers' name",
)
prefix = StringProperty(
name="Name Prefix", maxlen=32,
description="Prefix object names with this",
)
name="Name Prefix", maxlen=32,
description="Prefix object names with this",
)
use_existing = BoolProperty(
name="Use existing empties",
default=False,
description="Use previously created homonymous empties",
)
name="Use existing empties or armature",
default=False,
description="Use previously created homonymous empties or bones",
)
confidence = FloatProperty(
name="Minimum Confidence Level", default=0,
description="Only consider markers with at least "
"this confidence level",
min=-1., max=1000000.0,
soft_min=-1., soft_max=100.0,
)
name="Minimum Confidence Level", default=0,
description="Only consider markers with at least "
"this confidence level",
min=-1., max=1000000.0,
soft_min=-1., soft_max=100.0,
)
filter_glob = StringProperty(default="*.c3d;*.csv", options={'HIDDEN'})
def find_height(self, ms):
"""
Heuristic to find the height of the subject in the markerset
(only works for standing poses)
(only works for standing poses and you must have correct data
on the first frame)
"""
zmin = None
zmin, zmax = None, None
hidx = 1 if self.properties.Y_up else 2
for ml in ms.markerLabels:
# check if LTOE is a substring of this marker label
if 'LTOE' in ml:
# substitute the substring to get the head marker
hd = ml.replace('LTOE', 'LFHD')
if hd not in ms.markerLabels:
break
@ -198,6 +227,7 @@ class C3DImporter(bpy.types.Operator):
pmax_idx = ms.markerLabels.index(hd)
zmin = ms.frames[0][pmin_idx].position[hidx]
zmax = ms.frames[0][pmax_idx].position[hidx]
break
if zmin is None: # could not find named markers, get extremes
allz = [m.position[hidx] for m in ms.frames[0]]
zmin, zmax = min(allz), max(allz)
@ -205,43 +235,32 @@ class C3DImporter(bpy.types.Operator):
def adjust_scale_magnitude(self, height, scale):
mag = math.log10(height * scale)
#print('mag',mag, 'scale',scale)
return scale * math.pow(10, -int(mag))
def adjust_scale(self, height, scale):
"""
Try to find the correct scale for some common configurations
found in CMU's c3d files.
"""
factor = height * scale / 1.75 # normalize
if factor < 0.5:
scale /= 10.0
factor *= 10.0
cmu_factors = [(1.0, 1.0), (1.1, 1.45), (1.6, 1.6), (2.54, 2.54)]
sqerr, fix = min(((cf[0] - factor) ** 2.0, 1.0 / cf[1])
for cf in cmu_factors)
#print('height * scale: {:.2f}'.format(height * scale))
#print(factor, fix)
sqerr, fix = min(
((cf[0] - factor) ** 2.0, 1.0 / cf[1])
for cf in cmu_factors
)
return scale * fix
def execute(self, context):
s = self.properties.size
empty_size = (s, s, s)
ms = import_c3d.read(self.properties.filepath, onlyHeader=True)
ms.readNextFrameData()
#print(ms.fileName)
# determine the final scale
height = self.find_height(ms)
#print('h', height)
scale = 1.0 if not self.properties.from_inches else 0.0254
scale *= ms.scale
if self.properties.auto_magnitude:
scale = self.adjust_scale_magnitude(height, scale)
#print('scale',scale)
if self.properties.auto_scale:
scale = self.adjust_scale(height, scale)
scale *= self.properties.scale
# create the empties and get their collision-free names
def create_empties(self, ms):
"""
Create the empties and get their collision-free names
"""
unames = {}
use_existing = self.properties.use_existing
s = self.properties.size
empty_size = (s, s, s)
for ml in ms.markerLabels:
name = self.properties.prefix + ml
if use_existing and name in bpy.context.scene.objects:
@ -256,10 +275,62 @@ class C3DImporter(bpy.types.Operator):
o.show_x_ray = self.properties.x_ray
for name in unames.values():
bpy.context.scene.objects[name].select = True
return unames
def create_armature_obj(self, ms, scale):
"""
Create or use existing armature, return a bone dict,
leave the armature in POSE mode
"""
head_dir = Vector((0, 0, self.properties.size))
ao = bpy.context.active_object
# when using an existing armature we restrict importing
# the markers only for existing bones
if not self.properties.use_existing or not ao or ao.type != 'ARMATURE':
bpy.ops.object.add(type='ARMATURE', enter_editmode=True)
arm = bpy.context.active_object
arm.name = os.path.basename(self.properties.filepath)
arm.data.show_names = self.properties.show_names
arm.show_x_ray = self.properties.x_ray
for idx, ml in enumerate(ms.markerLabels):
name = self.properties.prefix + ml
bpy.ops.armature.select_all(action='DESELECT')
bpy.ops.armature.bone_primitive_add(name=name)
pos = Vector(ms.frames[0][idx].position) * scale
if self.properties.Y_up:
pos = Vector((pos[0], -pos[2], pos[1]))
b = arm.data.edit_bones[name]
b.head = pos + head_dir
b.tail = pos
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.select_all(action='SELECT')
def execute(self, context):
ms = import_c3d.read(self.properties.filepath, onlyHeader=True)
ms.readNextFrameData()
# determine the final scale
height = self.find_height(ms)
scale = 1.0 if not self.properties.from_inches else 0.0254
scale *= ms.scale
if self.properties.auto_magnitude:
scale = self.adjust_scale_magnitude(height, scale)
if self.properties.auto_scale:
scale = self.adjust_scale(height, scale)
scale *= self.properties.scale
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
if self.properties.create_armature:
self.create_armature_obj(ms, scale)
else:
unames = self.create_empties(ms)
# start animating the empties
C3DAnimateCloud.markerset = ms
C3DAnimateCloud.unames = unames
C3DAnimateCloud.is_armature = self.properties.create_armature
if not C3DAnimateCloud.is_armature:
C3DAnimateCloud.unames = unames
C3DAnimateCloud.scale = scale
C3DAnimateCloud.Y_up = self.properties.Y_up
C3DAnimateCloud.fskip = self.properties.frame_skip