浏览代码

Merge pull request #6158 from repsac/io_three

Normalizing quaternion for skeletal animation exports
Mr.doob 10 年之前
父节点
当前提交
294cd28a7d

+ 6 - 4
utils/exporters/blender/addons/io_three/__init__.py

@@ -42,7 +42,7 @@ SETTINGS_FILE_EXPORT = 'three_settings_export.js'
 bl_info = {
 bl_info = {
     'name': "Three.js Format",
     'name': "Three.js Format",
     'author': "repsac, mrdoob, yomotsu, mpk, jpweeks",
     'author': "repsac, mrdoob, yomotsu, mpk, jpweeks",
-    'version': (1, 2, 3),
+    'version': (1, 3, 0),
     'blender': (2, 7, 3),
     'blender': (2, 7, 3),
     'location': "File > Export",
     'location': "File > Export",
     'description': "Export Three.js formatted JSON files.",
     'description': "Export Three.js formatted JSON files.",
@@ -623,8 +623,10 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Copy textures",
         description="Copy textures",
         default=constants.EXPORT_OPTIONS[constants.COPY_TEXTURES])
         default=constants.EXPORT_OPTIONS[constants.COPY_TEXTURES])
 
 
-    option_texture_folder = StringProperty(name="Texture folder",
-        description="add this folder to textures path", default="")
+    option_texture_folder = StringProperty(
+        name="Texture folder",
+        description="add this folder to textures path",
+        default=constants.EXPORT_OPTIONS[constants.TEXTURE_FOLDER])
 
 
     option_lights = BoolProperty(
     option_lights = BoolProperty(
         name="Lights",
         name="Lights",
@@ -822,7 +824,7 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row.prop(self.properties, 'option_copy_textures')
         row.prop(self.properties, 'option_copy_textures')
 
 
         row = layout.row()
         row = layout.row()
-        row.prop(self.properties, "option_texture_folder")
+        row.prop(self.properties, 'option_texture_folder')
 
 
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_scale')
         row.prop(self.properties, 'option_scale')

+ 5 - 4
utils/exporters/blender/addons/io_three/constants.py

@@ -63,7 +63,7 @@ LIGHTS = 'lights'
 FACE_MATERIALS = 'faceMaterials'
 FACE_MATERIALS = 'faceMaterials'
 SKINNING = 'skinning'
 SKINNING = 'skinning'
 COPY_TEXTURES = 'copyTextures'
 COPY_TEXTURES = 'copyTextures'
-TEXTURE_FOLDER = "textureFolder"
+TEXTURE_FOLDER = 'textureFolder'
 ENABLE_PRECISION = 'enablePrecision'
 ENABLE_PRECISION = 'enablePrecision'
 PRECISION = 'precision'
 PRECISION = 'precision'
 DEFAULT_PRECISION = 6
 DEFAULT_PRECISION = 6
@@ -111,7 +111,7 @@ EXPORT_OPTIONS = {
     CAMERAS: False,
     CAMERAS: False,
     LIGHTS: False,
     LIGHTS: False,
     COPY_TEXTURES: True,
     COPY_TEXTURES: True,
-    TEXTURE_FOLDER: "",
+    TEXTURE_FOLDER: '',
     LOGGING: DEBUG,
     LOGGING: DEBUG,
     ENABLE_PRECISION: True,
     ENABLE_PRECISION: True,
     PRECISION: DEFAULT_PRECISION,
     PRECISION: DEFAULT_PRECISION,
@@ -173,7 +173,7 @@ UUID = 'uuid'
 MATRIX = 'matrix'
 MATRIX = 'matrix'
 POSITION = 'position'
 POSITION = 'position'
 QUATERNION = 'quaternion'
 QUATERNION = 'quaternion'
-ROTATION ='rotation'
+ROTATION = 'rotation'
 SCALE = 'scale'
 SCALE = 'scale'
 
 
 UV = 'uv'
 UV = 'uv'
@@ -321,7 +321,8 @@ BASIC = 'basic'
 
 
 NORMAL_BLENDING = 'NormalBlending'
 NORMAL_BLENDING = 'NormalBlending'
 
 
-DBG_COLORS = (0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee)
+DBG_COLORS = (0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee,
+              0xeeee00, 0x00eeee, 0xee00ee)
 
 
 DOUBLE_SIDED = 'doubleSided'
 DOUBLE_SIDED = 'doubleSided'
 
 

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

@@ -18,10 +18,10 @@ def batch_mode():
 
 
     :return: Whether or not the session is interactive
     :return: Whether or not the session is interactive
     :rtype: bool
     :rtype: bool
-       
+
     """
     """
     return bpy.context.area is None
     return bpy.context.area is None
-      
+
 
 
 def init():
 def init():
     """Initializing the api module. Required first step before
     """Initializing the api module. Required first step before

+ 34 - 0
utils/exporters/blender/addons/io_three/exporter/api/animation.py

@@ -1,3 +1,4 @@
+import math
 import mathutils
 import mathutils
 from bpy import data, context
 from bpy import data, context
 from .. import constants, logger, utilities
 from .. import constants, logger, utilities
@@ -88,6 +89,7 @@ def _parse_rest_action(action, armature, options, round_off, round_val):
                                      action, armature.matrix_world)
                                      action, armature.matrix_world)
             rot, rchange = _rotation(bone, computed_frame,
             rot, rchange = _rotation(bone, computed_frame,
                                      action, rotation_matrix)
                                      action, rotation_matrix)
+            rot = _normalize_quaternion(rot)
 
 
             if round_off:
             if round_off:
                 pos_x, pos_y, pos_z = utilities.round_off(
                 pos_x, pos_y, pos_z = utilities.round_off(
@@ -274,6 +276,7 @@ def _parse_pose_action(action, armature, options, round_off, round_val):
                 bone_matrix = parent_matrix.inverted() * bone_matrix
                 bone_matrix = parent_matrix.inverted() * bone_matrix
 
 
             pos, rot, scl = bone_matrix.decompose()
             pos, rot, scl = bone_matrix.decompose()
+            rot = _normalize_quaternion(rot)
 
 
             pchange = True or has_keyframe_at(
             pchange = True or has_keyframe_at(
                 channels_location[bone_index], frame)
                 channels_location[bone_index], frame)
@@ -569,3 +572,34 @@ def _handle_position_channel(channel, frame, position):
             position.z = value
             position.z = value
 
 
     return change
     return change
+
+
+def _quaternion_length(quat):
+    """Calculate the length of a quaternion
+
+    :param quat: Blender quaternion object
+    :rtype: float
+
+    """
+    return math.sqrt(quat.x * quat.x + quat.y * quat.y +
+                     quat.z * quat.z + quat.w * quat.w)
+
+
+def _normalize_quaternion(quat):
+    """Normalize a quaternion
+
+    :param quat: Blender quaternion object
+    :returns: generic quaternion enum object with normalized values
+    :rtype: object
+
+    """
+    enum = type('Enum', (), {'x': 0, 'y': 0, 'z': 0, 'w': 1})
+    length = _quaternion_length(quat)
+    if length is not 0:
+        length = 1 / length
+        enum.x = quat.x * length
+        enum.y = quat.y * length
+        enum.z = quat.z * length
+        enum.w = quat.w * length
+    return enum
+

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

@@ -340,7 +340,7 @@ def morph_targets(mesh, options):
             break
             break
     else:
     else:
         logger.info("No valid morph data detected")
         logger.info("No valid morph data detected")
-        return
+        return []
 
 
     manifest = []
     manifest = []
     for index, morph in enumerate(morphs):
     for index, morph in enumerate(morphs):

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

@@ -115,7 +115,7 @@ class Geometry(base_classes.BaseNode):
     def copy(self, scene=True):
     def copy(self, scene=True):
         """Copy the geometry definitions to a standard dictionary.
         """Copy the geometry definitions to a standard dictionary.
 
 
-        :param scene: toggle for scene formatting 
+        :param scene: toggle for scene formatting
                       (Default value = True)
                       (Default value = True)
         :type scene: bool
         :type scene: bool
         :rtype: dict
         :rtype: dict
@@ -135,16 +135,17 @@ class Geometry(base_classes.BaseNode):
 
 
         return data
         return data
 
 
-    def copy_textures(self, texture_folder=""):
+    def copy_textures(self, texture_folder=''):
         """Copy the textures to the destination directory."""
         """Copy the textures to the destination directory."""
         logger.debug("Geometry().copy_textures()")
         logger.debug("Geometry().copy_textures()")
         if self.options.get(constants.COPY_TEXTURES):
         if self.options.get(constants.COPY_TEXTURES):
             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)
+                full_path = os.path.join(dirname, texture_folder)
                 io.copy_registered_textures(
                 io.copy_registered_textures(
-                    os.path.join(os.path.dirname(self.scene.filepath), texture_folder),
-                    texture_registration)
+                    full_path, texture_registration)
 
 
     def parse(self):
     def parse(self):
         """Parse the current node"""
         """Parse the current node"""
@@ -169,7 +170,7 @@ class Geometry(base_classes.BaseNode):
         """Write the geometry definitions to disk. Uses the
         """Write the geometry definitions to disk. Uses the
         desitnation path of the scene.
         desitnation path of the scene.
 
 
-        :param filepath: optional output file path 
+        :param filepath: optional output file path
                         (Default value = None)
                         (Default value = None)
         :type filepath: str
         :type filepath: str
 
 
@@ -307,7 +308,8 @@ class Geometry(base_classes.BaseNode):
                     pass
                     pass
                 continue
                 continue
 
 
-            if key in skip: continue
+            if key in skip:
+                continue
 
 
             metadata[key] = len(self[key])
             metadata[key] = len(self[key])
 
 
@@ -430,12 +432,12 @@ class Geometry(base_classes.BaseNode):
 
 
             self[constants.INFLUENCES_PER_VERTEX] = influences
             self[constants.INFLUENCES_PER_VERTEX] = influences
             self[constants.SKIN_INDICES] = api.mesh.skin_indices(
             self[constants.SKIN_INDICES] = api.mesh.skin_indices(
-                self.node, bone_map, influences)
+                self.node, bone_map, influences) or []
             self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights(
             self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights(
-                self.node, bone_map, influences)
+                self.node, bone_map, influences) or []
 
 
         if self.options.get(constants.MORPH_TARGETS):
         if self.options.get(constants.MORPH_TARGETS):
             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)
+                self.node, self.options) or []