提交 348a3c3c 编写于 作者: R repsac

Updated bl_info author, version, and blender metadata.

Fixed menu label to read "Three.js".
Reimplemented the "Frame index as time" option.
Animation data is an array of all actions in the scene.
Reimplemented the latest skeletal animation logic that had previously been missed.
When copying textures destination files will be removed first.
Return values of several mush functions were not consistent and causing errors.
上级 af040867
......@@ -35,9 +35,9 @@ SETTINGS_FILE_EXPORT = 'three_settings_export.js'
bl_info = {
'name': 'Three.js Format',
'author': 'Ed Caspersen (repsac)',
'version': (1, 0, 0),
'blender': (2, 7, 2),
'author': 'repsac, mrdoob, yomotsu, mpk, jpweeks',
'version': (1, 1, 0),
'blender': (2, 7, 3),
'location': 'File > Import-Export',
'description': 'Export Three.js formatted JSON files.',
'warning': '',
......@@ -232,6 +232,7 @@ def save_settings_export(properties):
constants.MORPH_TARGETS: properties.option_animation_morph,
constants.ANIMATION: properties.option_animation_skeletal,
constants.FRAME_STEP: properties.option_frame_step,
constants.FRAME_INDEX_AS_TIME: properties.option_frame_index_as_time,
constants.INFLUENCES_PER_VERTEX: properties.option_influences
}
......@@ -366,6 +367,10 @@ def restore_settings_export(properties):
properties.option_frame_step = settings.get(
constants.FRAME_STEP,
constants.EXPORT_OPTIONS[constants.FRAME_STEP])
properties.option_frame_index_as_time = settings.get(
constants.FRAME_INDEX_AS_TIME,
constants.EXPORT_OPTIONS[constants.FRAME_INDEX_AS_TIME])
## }
def compression_types():
......@@ -522,6 +527,11 @@ class ExportThree(bpy.types.Operator, ExportHelper):
description='Export animation (skeletal)',
default=constants.EXPORT_OPTIONS[constants.ANIMATION])
option_frame_index_as_time = BoolProperty(
name='Frame index as time',
description='Use (original) frame index as frame time',
default=constants.EXPORT_OPTIONS[constants.FRAME_INDEX_AS_TIME])
option_frame_step = IntProperty(
name='Frame step',
description='Animation frame step',
......@@ -669,11 +679,13 @@ class ExportThree(bpy.types.Operator, ExportHelper):
row.prop(self.properties, 'option_animation_skeletal')
row = layout.row()
row.prop(self.properties, 'option_frame_step')
row = layout.row()
row.prop(self.properties, 'option_frame_index_as_time')
## }
def menu_func_export(self, context):
default_path = bpy.data.filepath.replace('.blend', constants.EXTENSION)
text = 'Three (%s)' % constants.EXTENSION
text = 'Three.js (%s)' % constants.EXTENSION
operator = self.layout.operator(ExportThree.bl_idname, text=text)
operator.filepath = default_path
......
......@@ -49,7 +49,8 @@ SCALE = 'scale'
COMPRESSION = 'compression'
MAPS = 'maps'
FRAME_STEP = 'frameStep'
ANIMATION = 'animation'
FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
ANIMATION = 'animations'
MORPH_TARGETS = 'morphTargets'
SKIN_INDICES = 'skinIndices'
SKIN_WEIGHTS = 'skinWeights'
......@@ -93,6 +94,7 @@ EXPORT_OPTIONS = {
FACE_MATERIALS: False,
SCALE: 1,
FRAME_STEP: 1,
FRAME_INDEX_AS_TIME: False,
SCENE: True,
MIX_COLORS: False,
COMPRESSION: None,
......@@ -210,10 +212,15 @@ IMAGE = 'image'
NAME = 'name'
PARENT = 'parent'
#@TODO move to api.constants?
LENGTH = 'length'
FPS = 'fps'
HIERARCHY = 'hierarchy'
POS = 'pos'
ROTQ = 'rotq'
ROT = 'rot'
SCL = 'scl'
TIME = 'time'
KEYS = 'keys'
AMBIENT = 'ambient'
COLOR = 'color'
......
......@@ -24,57 +24,61 @@ def _mesh(func):
def animation(mesh, options):
logger.debug('mesh.animation(%s, %s)', mesh, options)
armature = _armature(mesh)
if armature and armature.animation_data:
return _skeletal_animations(armature, options)
animations = []
if armature:
for action in data.actions:
logger.info('Processing action %s', action.name)
animations.append(
_skeletal_animations(action, armature, options))
else:
logger.warning('No armature found')
return animations
@_mesh
def bones(mesh):
logger.debug('mesh.bones(%s)', mesh)
armature = _armature(mesh)
if not armature: return
bones = []
bone_map = {}
if not armature:
return bones, bone_map
bone_count = 0
bone_index_rel = 0
for bone in armature.data.bones:
logger.info('Parsing bone %s', bone.name)
armature_matrix = armature.matrix_world
for bone_count, pose_bone in enumerate(armature.pose.bones):
armature_bone = pose_bone.bone
bone_index = None
if bone.parent is None or bone.parent.use_deform is False:
bone_pos = bone.head_local
if armature_bone.parent is None:
bone_matrix = armature_matrix * armature_bone.matrix_local
bone_index = -1
else:
bone_pos = bone.head_local - bone.parent.head_local
bone_index = 0
index = 0
for parent in armature.data.bones:
if parent.name == bone.parent.name:
bone_index = bone_map.get(index)
parent_bone = armature_bone.parent
parent_matrix = armature_matrix * parent_bone.matrix_local
bone_matrix = armature_matrix * armature_bone.matrix_local
bone_matrix = parent_matrix.inverted() * bone_matrix
bone_index = index = 0
for pose_parent in armature.pose.bones:
armature_parent = pose_parent.bone.name
if armature_parent == parent_bone.name:
bone_index = index
index += 1
bone_world_pos = armature.matrix_world * bone_pos
x_axis = bone_world_pos.x
y_axis = bone_world_pos.z
z_axis = -bone_world_pos.y
if bone.use_deform:
logger.debug('Adding bone %s at: %s, %s',
bone.name, bone_index, bone_index_rel)
bone_map[bone_count] = bone_index_rel
bone_index_rel += 1
bones.append({
constants.PARENT: bone_index,
constants.NAME: bone.name,
constants.POS: (x_axis, y_axis, z_axis),
constants.ROTQ: (0,0,0,1)
})
else:
logger.debug('Ignoring bone %s at: %s, %s',
bone.name, bone_index, bone_index_rel)
bone_map[bone_count] = bone_count
bone_count += 1
pos, rot, scl = bone_matrix.decompose()
bones.append({
constants.PARENT: bone_index,
constants.NAME: armature_bone.name,
constants.POS: (pos.x, pos.z, -pos.y),
constants.ROTQ: (rot.x, rot.z, -rot.y, rot.w),
'scl': (scl.x, scl.z, scl.y)
})
return (bones, bone_map)
......@@ -101,8 +105,6 @@ def buffer_normal(mesh, options):
return normals_
@_mesh
def buffer_position(mesh, options):
position = []
......@@ -367,12 +369,12 @@ def materials(mesh, options):
@_mesh
def normals(mesh, options):
logger.debug('mesh.normals(%s, %s)', mesh, options)
flattened = []
normal_vectors = []
for vector in _normals(mesh, options):
flattened.extend(vector)
normal_vectors.extend(vector)
return flattened
return normal_vectors
@_mesh
......@@ -631,12 +633,13 @@ def _armature(mesh):
def _skinning_data(mesh, bone_map, influences, array_index):
armature = _armature(mesh)
if not armature: return
manifest = []
if not armature:
return manifest
obj = object_.objects_using_mesh(mesh)[0]
logger.debug('Skinned object found %s', obj.name)
manifest = []
for vertex in mesh.vertices:
bone_array = []
for group in vertex.groups:
......@@ -648,9 +651,9 @@ def _skinning_data(mesh, bone_map, influences, array_index):
if index >= len(bone_array):
manifest.append(0)
continue
for bone_index, bone in enumerate(armature.data.bones):
if bone.name != obj.vertex_groups[bone_array[index][0]].name:
name = obj.vertex_groups[bone_array[index][0]].name
for bone_index, bone in enumerate(armature.pose.bones):
if bone.name != name:
continue
if array_index is 0:
entry = bone_map.get(bone_index, -1)
......@@ -665,101 +668,177 @@ def _skinning_data(mesh, bone_map, influences, array_index):
return manifest
def _skeletal_animations(armature, options):
action = armature.animation_data.action
def _find_channels(action, bone, channel_type):
result = []
if len(action.groups):
group_index = -1
for index, group in enumerate(action.groups):
if group.name == bone.name:
group_index = index
#@TODO: break?
if group_index > -1:
for channel in action.groups[group_index].channels:
if channel_type in channel.data_path:
result.append(channel)
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = [bone_label in channel.data_path,
channel_type in channel.data_path]
if all(data_path):
result.append(channel)
return result
def _skeletal_animations(action, armature, options):
try:
current_context = context.area.type
except AttributeError:
logger.warning('No context, possibly running in batch mode')
else:
context.area.type = 'DOPESHEET_EDITOR'
context.space_data.mode = 'ACTION'
context.area.spaces.active.action = action
armature_matrix = armature.matrix_world
fps = context.scene.render.fps
end_frame = action.frame_range[1]
start_frame = action.frame_range[0]
frame_length = end_frame - start_frame
l,r,s = armature.matrix_world.decompose()
rotation_matrix = r.to_matrix()
hierarchy = []
parent_index = -1
frame_step = options.get(constants.FRAME_STEP, 1)
fps = context.scene.render.fps
used_frames = int(frame_length / frame_step) + 1
keys = []
channels_location = []
channels_rotation = []
channels_scale = []
for pose_bone in armature.pose.bones:
logger.info('Processing channels for %s',
pose_bone.bone.name)
keys.append([])
channels_location.append(
_find_channels(action,
pose_bone.bone,
'location'))
channels_rotation.append(
_find_channels(action,
pose_bone.bone,
'rotation_quaternion'))
channels_rotation.append(
_find_channels(action,
pose_bone.bone,
'rotation_euler'))
channels_scale.append(
_find_channels(action,
pose_bone.bone,
'scale'))
frame_step = options[constants.FRAME_STEP]
frame_index_as_time = options[constants.FRAME_INDEX_AS_TIME]
for frame_index in range(0, used_frames):
if frame_index == used_frames - 1:
frame = end_frame
else:
frame = start_frame + frame_index * frame_step
start = int(start_frame)
end = int(end_frame / frame_step) + 1
logger.info('Processing frame %d', frame)
#@TODO need key constants
for bone in armature.data.bones:
if bone.use_deform is False:
logger.info('Skipping animation data for bone %s', bone.name)
continue
time = frame - start_frame
if frame_index_as_time is False:
time = time / fps
logger.info('Parsing animation data for bone %s', bone.name)
context.scene.frame_set(frame)
keys = []
for frame in range(start, end):
computed_frame = frame * frame_step
pos, pchange = _position(bone, computed_frame,
action, armature.matrix_world)
rot, rchange = _rotation(bone, computed_frame,
action, rotation_matrix)
bone_index = 0
def has_keyframe_at(channels, frame):
def find_keyframe_at(channel, frame):
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
return keyframe
return None
for channel in channels:
if not find_keyframe_at(channel, frame) is None:
return True
return False
for pose_bone in armature.pose.bones:
logger.info('Processing bone %s', pose_bone.bone.name)
if pose_bone.parent is None:
bone_matrix = armature_matrix * pose_bone.matrix
else:
parent_matrix = armature_matrix * pose_bone.parent.matrix
bone_matrix = armature_matrix * pose_bone.matrix
bone_matrix = parent_matrix.inverted() * bone_matrix
pos, rot, scl = bone_matrix.decompose()
pchange = True or has_keyframe_at(
channels_location[bone_index], frame)
rchange = True or has_keyframe_at(
channels_rotation[bone_index], frame)
schange = True or has_keyframe_at(
channels_scale[bone_index], frame)
# flip y and z
px, py, pz = pos.x, pos.z, -pos.y
rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
sx, sy, sz = scl.x, scl.z, scl.y
keyframe = {constants.TIME: time}
if frame == start_frame or frame == end_frame:
keyframe.update({
constants.POS: [px, py, pz],
constants.ROT: [rx, ry, rz, rw],
constants.SCL: [sx, sy, sz]
})
elif any([pchange, rchange, schange]):
if pchange is True:
keyframe[constants.POS] = [px, py, pz]
if rchange is True:
keyframe[constants.ROT] = [rx, ry, rz, rw]
if schange is True:
keyframe[constants.SCL] = [sx, sy, sz]
if len(keyframe.keys()) > 1:
logger.info('Recording keyframe data for %s %s',
pose_bone.bone.name, str(keyframe))
keys[bone_index].append(keyframe)
else:
logger.info('No anim data to record for %s',
pose_bone.bone.name)
if frame == start_frame:
time = (frame * frame_step - start_frame) / fps
keyframe = {
'time': time,
'pos': [px, py, pz],
'rot': [rx, ry, rz, rw],
'scl': [1, 1, 1]
}
keys.append(keyframe)
# END-FRAME: needs pos, rot and scl attributes
# with animation length (required frame)
elif frame == end_frame / frame_step:
time = frame_length / fps
keyframe = {
'time': time,
'pos': [px, py, pz],
'rot': [rx, ry, rz, rw],
'scl': [1, 1, 1]
}
keys.append(keyframe)
# MIDDLE-FRAME: needs only one of the attributes,
# can be an empty frame (optional frame)
elif pchange == True or rchange == True:
time = (frame * frame_step - start_frame) / fps
if pchange == True and rchange == True:
keyframe = {
'time': time,
'pos': [px, py, pz],
'rot': [rx, ry, rz, rw]
}
elif pchange == True:
keyframe = {
'time': time,
'pos': [px, py, pz]
}
elif rchange == True:
keyframe = {
'time': time,
'rot': [rx, ry, rz, rw]
}
keys.append(keyframe)
hierarchy.append({'keys': keys, 'parent': parent_index})
parent_index += 1
#@TODO key constants
bone_index += 1
hierarchy = []
bone_index = 0
for pose_bone in armature.pose.bones:
hierarchy.append({
constants.PARENT: bone_index - 1,
constants.KEYS: keys[bone_index]
})
bone_index += 1
if frame_index_as_time is False:
frame_length = frame_length / fps
context.scene.frame_set(start_frame)
if context.area:
context.area.type = current_context
animation = {
'hierarchy': hierarchy,
'length':frame_length / fps,
'fps': fps,
'name': action.name
constants.HIERARCHY: hierarchy,
constants.LENGTH:frame_length,
constants.FPS: fps,
constants.NAME: action.name
}
return animation
......
import os
import sys
import shutil
import traceback
from .. import constants, logger
from . import _json
......@@ -11,6 +14,22 @@ def copy_registered_textures(dest, registration):
def copy(src, dst):
logger.debug('io.copy(%s, %s)' % (src, dst))
if os.path.exists(dst) and os.path.isfile(src):
file_name = os.path.basename(src)
dst = os.path.join(dst, file_name)
logger.info('Destination file exists, attempting to remove %s', dst)
try:
os.remove(dst)
except:
logger.error('Failed to remove destination file')
info = sys.exc_info()
trace = traceback.format_exception(
info[0], info[1], info[2].tb_next)
trace = ''.join(trace)
logger.error(trace)
raise
shutil.copy(src, dst)
......
......@@ -131,14 +131,14 @@ function loadGeometry( data, url ) {
var material = new THREE.MeshFaceMaterial( data.materials );
var mesh;
if ( data.geometry.animation !== undefined ) {
if ( data.geometry.animations !== undefined && data.geometry.animations.length > 0 ) {
console.log( 'loading animation' );
data.materials[ 0 ].skinning = true;
mesh = new THREE.SkinnedMesh( data.geometry, material, false);
mesh = new THREE.SkinnedMesh( data.geometry, material, false );
var name = data.geometry.animation.name;
animation = new THREE.Animation( mesh, data.geometry.animation );
var name = data.geometry.animations[0].name;
animation = new THREE.Animation( mesh, data.geometry.animations[0] );
} else {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册