Browse Source

manual merge of blender Clip (blend shape + node animation) support.

Ben Houston 10 years ago
parent
commit
454847c973

+ 33 - 3
utils/exporters/blender/addons/io_three/__init__.py

@@ -39,8 +39,8 @@ logging.basicConfig(
 bl_info = {
 bl_info = {
     'name': "Three.js Format",
     'name': "Three.js Format",
     'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw",
     'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw",
-    'version': (1, 4, 0),
-    'blender': (2, 73, 0),
+    'version': (1, 5, 0),
+    'blender': (2, 74, 0),
     'location': "File > Export",
     'location': "File > Export",
     'description': "Export Three.js formatted JSON files.",
     'description': "Export Three.js formatted JSON files.",
     'warning': "Importer not included.",
     'warning': "Importer not included.",
@@ -322,7 +322,7 @@ def restore_export_settings(properties, settings):
         constants.INDEX_TYPE,
         constants.INDEX_TYPE,
         constants.EXPORT_OPTIONS[constants.INDEX_TYPE])
         constants.EXPORT_OPTIONS[constants.INDEX_TYPE])
     ## }
     ## }
-
+   
     ## Materials {
     ## Materials {
     properties.option_materials = settings.get(
     properties.option_materials = settings.get(
         constants.MATERIALS,
         constants.MATERIALS,
@@ -414,10 +414,18 @@ def restore_export_settings(properties, settings):
         constants.MORPH_TARGETS,
         constants.MORPH_TARGETS,
         constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
         constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
 
 
+    properties.option_blend_shape = settings.get(
+        constants.BLEND_SHAPES,
+        constants.EXPORT_OPTIONS[constants.BLEND_SHAPES])
+
     properties.option_animation_skeletal = settings.get(
     properties.option_animation_skeletal = settings.get(
         constants.ANIMATION,
         constants.ANIMATION,
         constants.EXPORT_OPTIONS[constants.ANIMATION])
         constants.EXPORT_OPTIONS[constants.ANIMATION])
 
 
+    properties.option_keyframes = settings.get(
+        constants.KEYFRAMES,
+        constants.EXPORT_OPTIONS[constants.KEYFRAMES])
+
     properties.option_frame_step = settings.get(
     properties.option_frame_step = settings.get(
         constants.FRAME_STEP,
         constants.FRAME_STEP,
         constants.EXPORT_OPTIONS[constants.FRAME_STEP])
         constants.EXPORT_OPTIONS[constants.FRAME_STEP])
@@ -470,7 +478,9 @@ def set_settings(properties):
         constants.HIERARCHY: properties.option_hierarchy,
         constants.HIERARCHY: properties.option_hierarchy,
 
 
         constants.MORPH_TARGETS: properties.option_animation_morph,
         constants.MORPH_TARGETS: properties.option_animation_morph,
+        constants.BLEND_SHAPES: properties.option_blend_shape,
         constants.ANIMATION: properties.option_animation_skeletal,
         constants.ANIMATION: properties.option_animation_skeletal,
+        constants.KEYFRAMES: properties.option_keyframes,
         constants.FRAME_STEP: properties.option_frame_step,
         constants.FRAME_STEP: properties.option_frame_step,
         constants.FRAME_INDEX_AS_TIME: properties.option_frame_index_as_time,
         constants.FRAME_INDEX_AS_TIME: properties.option_frame_index_as_time,
         constants.INFLUENCES_PER_VERTEX: properties.option_influences
         constants.INFLUENCES_PER_VERTEX: properties.option_influences
@@ -684,12 +694,22 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Export animation (morphs)",
         description="Export animation (morphs)",
         default=constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
         default=constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
 
 
+    option_blend_shape = BoolProperty(
+        name="Blend Shape animation",
+        description="Export Blend Shapes",
+        default=constants.EXPORT_OPTIONS[constants.BLEND_SHAPES])
+
     option_animation_skeletal = EnumProperty(
     option_animation_skeletal = EnumProperty(
         name="",
         name="",
         description="Export animation (skeletal)",
         description="Export animation (skeletal)",
         items=animation_options(),
         items=animation_options(),
         default=constants.OFF)
         default=constants.OFF)
 
 
+    option_keyframes = BoolProperty(
+        name="Keyframe animation",
+        description="Export animation (keyframes)",
+        default=constants.EXPORT_OPTIONS[constants.KEYFRAMES])
+
     option_frame_index_as_time = BoolProperty(
     option_frame_index_as_time = BoolProperty(
         name="Frame index as time",
         name="Frame index as time",
         description="Use (original) frame index as frame time",
         description="Use (original) frame index as frame time",
@@ -804,6 +824,7 @@ class ExportThree(bpy.types.Operator, ExportHelper):
 
 
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_index_type')
         row.prop(self.properties, 'option_index_type')
+
         ## }
         ## }
 
 
         layout.separator()
         layout.separator()
@@ -831,12 +852,21 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_animation_morph')
         row.prop(self.properties, 'option_animation_morph')
 
 
+        row = layout.row()
+        row.prop(self.properties, 'option_blend_shape')
+
         row = layout.row()
         row = layout.row()
         row.label(text="Skeletal animations:")
         row.label(text="Skeletal animations:")
 
 
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_animation_skeletal')
         row.prop(self.properties, 'option_animation_skeletal')
 
 
+        row = layout.row()
+        row.label(text="Keyframe animations:")
+
+        row = layout.row()
+        row.prop(self.properties, 'option_keyframes')
+
         layout.row()
         layout.row()
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_influences')
         row.prop(self.properties, 'option_influences')

+ 8 - 1
utils/exporters/blender/addons/io_three/constants.py

@@ -51,7 +51,6 @@ NUMERIC = {
     'LinearMipMapNearestFilter': 1007,
     'LinearMipMapNearestFilter': 1007,
     'LinearMipMapLinearFilter': 1008
     'LinearMipMapLinearFilter': 1008
 }
 }
-
 JSON = 'json'
 JSON = 'json'
 EXTENSION = '.%s' % JSON
 EXTENSION = '.%s' % JSON
 INDENT = 'indent'
 INDENT = 'indent'
@@ -79,7 +78,11 @@ MAPS = 'maps'
 FRAME_STEP = 'frameStep'
 FRAME_STEP = 'frameStep'
 FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
 FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
 ANIMATION = 'animations'
 ANIMATION = 'animations'
+CLIPS="clips"
+KEYFRAMES = 'tracks'
 MORPH_TARGETS = 'morphTargets'
 MORPH_TARGETS = 'morphTargets'
+MORPH_TARGETS_ANIM = 'morphTargetsAnimation'
+BLEND_SHAPES = 'blendShapes'
 POSE = 'pose'
 POSE = 'pose'
 REST = 'rest'
 REST = 'rest'
 SKIN_INDICES = 'skinIndices'
 SKIN_INDICES = 'skinIndices'
@@ -141,9 +144,11 @@ EXPORT_OPTIONS = {
     COMPRESSION: None,
     COMPRESSION: None,
     MAPS: False,
     MAPS: False,
     ANIMATION: OFF,
     ANIMATION: OFF,
+    KEYFRAMES: False,
     BONES: False,
     BONES: False,
     SKINNING: False,
     SKINNING: False,
     MORPH_TARGETS: False,
     MORPH_TARGETS: False,
+    BLEND_SHAPES: False,
     CAMERAS: False,
     CAMERAS: False,
     LIGHTS: False,
     LIGHTS: False,
     HIERARCHY: False,
     HIERARCHY: False,
@@ -220,6 +225,8 @@ NORMAL = 'normal'
 ITEM_SIZE = 'itemSize'
 ITEM_SIZE = 'itemSize'
 ARRAY = 'array'
 ARRAY = 'array'
 
 
+FLOAT_32 = 'Float32Array'
+
 VISIBLE = 'visible'
 VISIBLE = 'visible'
 CAST_SHADOW = 'castShadow'
 CAST_SHADOW = 'castShadow'
 RECEIVE_SHADOW = 'receiveShadow'
 RECEIVE_SHADOW = 'receiveShadow'

+ 3 - 2
utils/exporters/blender/addons/io_three/exporter/api/material.py

@@ -19,12 +19,13 @@ def _material(func):
 
 
         """
         """
 
 
+        material = None
         if isinstance(name, types.Material):
         if isinstance(name, types.Material):
             material = name
             material = name
-        else:
+        elif name:
             material = data.materials[name]
             material = data.materials[name]
 
 
-        return func(material, *args, **kwargs)
+        return func(material, *args, **kwargs) if material else None
 
 
     return inner
     return inner
 
 

+ 68 - 1
utils/exporters/blender/addons/io_three/exporter/api/mesh.py

@@ -68,7 +68,6 @@ def skeletal_animation(mesh, options):
 
 
     return animations
     return animations
 
 
-
 @_mesh
 @_mesh
 def bones(mesh, options):
 def bones(mesh, options):
     """
     """
@@ -431,6 +430,74 @@ def morph_targets(mesh, options):
 
 
     return manifest
     return manifest
 
 
+@_mesh
+def blend_shapes(mesh, options):
+    """
+
+    :param mesh:
+    :param options:
+
+    """
+    logger.debug("mesh.blend_shapes(%s, %s)", mesh, options)
+    manifest = []
+    if mesh.shape_keys:
+        logger.info("mesh.blend_shapes -- there's shape keys")
+        key_blocks = mesh.shape_keys.key_blocks
+        for key in key_blocks.keys()[1:]:     # skip "Basis"
+            logger.info("mesh.blend_shapes -- key %s", key)
+            morph = []
+            for d in key_blocks[key].data:
+                co = d.co
+                morph.append([co.x, co.y, co.z])
+            manifest.append({
+                constants.NAME: key,
+                constants.VERTICES: morph
+            })
+    else:
+        logger.debug("No valid blend_shapes detected")
+    return manifest
+
+@_mesh
+def animated_blend_shapes(mesh, options):
+    """
+
+    :param mesh:
+    :param options:
+
+    """
+    logger.debug("mesh.animated_blend_shapes(%s, %s)", mesh, options)
+    tracks = []
+    shp = mesh.shape_keys
+    animCurves = shp.animation_data
+    if animCurves:
+        animCurves = animCurves.action.fcurves
+
+    for key in shp.key_blocks.keys()[1:]:    # skip "Basis"
+        key_name = constants.MORPH_TARGETS+"["+key+"]"
+        found_animation = False
+        data_path = 'key_blocks["'+key+'"].value'
+        values = []
+        if animCurves:
+            for fcurve in animCurves:
+                if fcurve.data_path == data_path:
+                    for xx in fcurve.keyframe_points:
+                        values.append({ "time": xx.co.x, "value": xx.co.y })
+                    found_animation = True
+                    break # no need to continue
+
+        if found_animation:
+            tracks.append({
+                constants.NAME: key_name,
+                constants.TYPE: "number",
+                constants.KEYS: values
+            });
+
+    animation = [{
+        constants.KEYFRAMES: tracks,
+        constants.FPS: context.scene.render.fps,
+        constants.NAME: "default"
+    }]
+    return animation
 
 
 @_mesh
 @_mesh
 def materials(mesh, options):
 def materials(mesh, options):

+ 5 - 2
utils/exporters/blender/addons/io_three/exporter/api/object.py

@@ -201,11 +201,11 @@ def animated_xform(obj):
         else:
         else:
             i += 1
             i += 1
 
 
-    animation = {
+    animation = [{
         constants.KEYFRAMES: tracks,
         constants.KEYFRAMES: tracks,
         constants.FPS: context.scene.render.fps,
         constants.FPS: context.scene.render.fps,
         constants.NAME: obj.name
         constants.NAME: obj.name
-    }
+    }]
     return animation
     return animation
 
 
 @_object
 @_object
@@ -667,3 +667,6 @@ def _valid_node(obj, valid_types, options):
 
 
     # if we get this far assume that the mesh is valid
     # if we get this far assume that the mesh is valid
     return True
     return True
+
+
+

+ 11 - 4
utils/exporters/blender/addons/io_three/exporter/geometry.py

@@ -45,7 +45,7 @@ class Geometry(base_classes.BaseNode):
             ext = constants.PACK
             ext = constants.PACK
 
 
         key = ''
         key = ''
-        for key in (constants.MORPH_TARGETS, constants.ANIMATION):
+        for key in (constants.MORPH_TARGETS, constants.ANIMATION, constants.CLIPS):
             if key in self.keys():
             if key in self.keys():
                 break
                 break
         else:
         else:
@@ -152,7 +152,7 @@ class Geometry(base_classes.BaseNode):
             texture_registration = self.register_textures()
             texture_registration = self.register_textures()
             if texture_registration:
             if texture_registration:
                 logger.info("%s has registered textures", self.node)
                 logger.info("%s has registered textures", self.node)
-                dirname = os.path.dirname(self.scene.filepath)
+                dirname = os.path.dirname(os.path.abspath(self.scene.filepath))
                 full_path = os.path.join(dirname, texture_folder)
                 full_path = os.path.join(dirname, texture_folder)
                 io.copy_registered_textures(
                 io.copy_registered_textures(
                     full_path, texture_registration)
                     full_path, texture_registration)
@@ -206,7 +206,7 @@ class Geometry(base_classes.BaseNode):
         """
         """
         logger.debug("Geometry().write_animation(%s)", filepath)
         logger.debug("Geometry().write_animation(%s)", filepath)
 
 
-        for key in (constants.MORPH_TARGETS, constants.ANIMATION):
+        for key in (constants.MORPH_TARGETS, constants.ANIMATION, constants.CLIPS):
             try:
             try:
                 data = self[key]
                 data = self[key]
                 break
                 break
@@ -245,7 +245,7 @@ class Geometry(base_classes.BaseNode):
                       constants.INDEX]
                       constants.INDEX]
 
 
         data = {}
         data = {}
-        anim_components = [constants.MORPH_TARGETS, constants.ANIMATION]
+        anim_components = [constants.MORPH_TARGETS, constants.ANIMATION, constants.MORPH_TARGETS_ANIM, constants.CLIPS]
         if self.options.get(constants.EMBED_ANIMATION):
         if self.options.get(constants.EMBED_ANIMATION):
             components.extend(anim_components)
             components.extend(anim_components)
         else:
         else:
@@ -564,6 +564,13 @@ class Geometry(base_classes.BaseNode):
             logger.info("Parsing %s", constants.MORPH_TARGETS)
             logger.info("Parsing %s", constants.MORPH_TARGETS)
             self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
             self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
                 self.node, self.options) or []
                 self.node, self.options) or []
+        elif self.options.get(constants.BLEND_SHAPES):
+            logger.info("Parsing %s", constants.BLEND_SHAPES)
+            mt = api.mesh.blend_shapes(self.node, self.options) or []
+            self[constants.MORPH_TARGETS] = mt
+            if len(mt) > 0:  # there's blend shapes, let check for animation
+                self[constants.CLIPS] = api.mesh.animated_blend_shapes(self.node, self.options) or []
+
 
 
         # In the moment there is no way to add extra data to a Geomtry in
         # In the moment there is no way to add extra data to a Geomtry in
         # Three.js. In case there is some day, here is the code:
         # Three.js. In case there is some day, here is the code:

+ 5 - 0
utils/exporters/blender/addons/io_three/exporter/object.py

@@ -119,6 +119,11 @@ class Object(base_classes.BaseNode):
         elif self[constants.TYPE] in lights:
         elif self[constants.TYPE] in lights:
             self._init_light()
             self._init_light()
 
 
+        no_anim = (None, False, constants.OFF)
+        if self.options.get(constants.KEYFRAMES) not in no_anim:
+            logger.info("Export Transform Animation for %s", self.node)
+            self[constants.CLIPS] = api.object.animated_xform(self.node)
+
         if self.options.get(constants.HIERARCHY, False):
         if self.options.get(constants.HIERARCHY, False):
             for child in api.object.children(self.node, self.scene.valid_types):
             for child in api.object.children(self.node, self.scene.valid_types):
                 if not self.get(constants.CHILDREN):
                 if not self.get(constants.CHILDREN):