Browse Source

Allows export of non-skinning Vertex Groups as BufferGeometry custom attributes.

tschw 10 years ago
parent
commit
b15f5bf85e

+ 14 - 1
utils/exporters/blender/addons/io_three/__init__.py

@@ -38,7 +38,7 @@ logging.basicConfig(
 
 
 bl_info = {
 bl_info = {
     'name': "Three.js Format",
     'name': "Three.js Format",
-    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa",
+    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw",
     'version': (1, 4, 0),
     'version': (1, 4, 0),
     'blender': (2, 7, 3),
     'blender': (2, 7, 3),
     'location': "File > Export",
     'location': "File > Export",
@@ -305,6 +305,10 @@ def restore_export_settings(properties, settings):
         constants.APPLY_MODIFIERS,
         constants.APPLY_MODIFIERS,
         constants.EXPORT_OPTIONS[constants.APPLY_MODIFIERS])
         constants.EXPORT_OPTIONS[constants.APPLY_MODIFIERS])
 
 
+    properties.option_extra_vgroups = settings.get(
+        constants.EXTRA_VGROUPS,
+        constants.EXPORT_OPTIONS[constants.EXTRA_VGROUPS])
+
     properties.option_geometry_type = settings.get(
     properties.option_geometry_type = settings.get(
         constants.GEOMETRY_TYPE,
         constants.GEOMETRY_TYPE,
         constants.EXPORT_OPTIONS[constants.GEOMETRY_TYPE])
         constants.EXPORT_OPTIONS[constants.GEOMETRY_TYPE])
@@ -428,6 +432,7 @@ def set_settings(properties):
         constants.NORMALS: properties.option_normals,
         constants.NORMALS: properties.option_normals,
         constants.SKINNING: properties.option_skinning,
         constants.SKINNING: properties.option_skinning,
         constants.BONES: properties.option_bones,
         constants.BONES: properties.option_bones,
+        constants.EXTRA_VGROUPS: properties.option_extra_vgroups,
         constants.APPLY_MODIFIERS: properties.option_apply_modifiers,
         constants.APPLY_MODIFIERS: properties.option_apply_modifiers,
         constants.GEOMETRY_TYPE: properties.option_geometry_type,
         constants.GEOMETRY_TYPE: properties.option_geometry_type,
 
 
@@ -560,6 +565,11 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Export bones",
         description="Export bones",
         default=constants.EXPORT_OPTIONS[constants.BONES])
         default=constants.EXPORT_OPTIONS[constants.BONES])
 
 
+    option_extra_vgroups = StringProperty(
+        name="Extra Vertex Groups",
+        description="Non-skinning vertex groups to export (comma-separated, w/ star wildcard, BufferGeometry only).",
+        default=constants.EXPORT_OPTIONS[constants.EXTRA_VGROUPS])
+
     option_apply_modifiers = BoolProperty(
     option_apply_modifiers = BoolProperty(
         name="Apply Modifiers",
         name="Apply Modifiers",
         description="Apply Modifiers to mesh objects",
         description="Apply Modifiers to mesh objects",
@@ -762,6 +772,9 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row.prop(self.properties, 'option_bones')
         row.prop(self.properties, 'option_bones')
         row.prop(self.properties, 'option_skinning')
         row.prop(self.properties, 'option_skinning')
 
 
+        row = layout.row()
+        row.prop(self.properties, 'option_extra_vgroups')
+
         row = layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_apply_modifiers')
         row.prop(self.properties, 'option_apply_modifiers')
 
 

+ 2 - 0
utils/exporters/blender/addons/io_three/constants.py

@@ -47,6 +47,7 @@ UVS = 'uvs'
 APPLY_MODIFIERS = 'applyModifiers'
 APPLY_MODIFIERS = 'applyModifiers'
 COLORS = 'colors'
 COLORS = 'colors'
 MIX_COLORS = 'mixColors'
 MIX_COLORS = 'mixColors'
+EXTRA_VGROUPS = 'extraVertexGroups'
 SCALE = 'scale'
 SCALE = 'scale'
 COMPRESSION = 'compression'
 COMPRESSION = 'compression'
 MAPS = 'maps'
 MAPS = 'maps'
@@ -98,6 +99,7 @@ EXPORT_OPTIONS = {
     UVS: True,
     UVS: True,
     APPLY_MODIFIERS: True,
     APPLY_MODIFIERS: True,
     COLORS: False,
     COLORS: False,
+    EXTRA_VGROUPS: '',
     MATERIALS: False,
     MATERIALS: False,
     FACE_MATERIALS: False,
     FACE_MATERIALS: False,
     SCALE: 1,
     SCALE: 1,

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

@@ -5,6 +5,7 @@ morph targets) with the geometry nodes.
 """
 """
 
 
 import operator
 import operator
+import re
 from bpy import data, types, context
 from bpy import data, types, context
 from . import material, texture, animation
 from . import material, texture, animation
 from . import object as object_
 from . import object as object_
@@ -177,6 +178,124 @@ def buffer_uv(mesh):
     return uvs_
     return uvs_
 
 
 
 
+@_mesh
+def extra_vertex_group_count(mesh, patterns):
+    """
+    Return the number of non-skinning vertex groups that match a list
+    of comma-separated strings with star character wildcards.
+
+    :param mesh:
+    :param patterns:
+
+    """
+    return len(_extra_vertex_groups(mesh, patterns))
+
+
+@_mesh
+def extra_vertex_group_name(mesh, patterns, index):
+    """
+    Return the name of an extra vertex group.
+
+    :param mesh:
+    :param patterns:
+    :param index:
+
+    """
+
+    obj = object_.objects_using_mesh(mesh)[0]
+    extra_vgroups = _extra_vertex_groups(mesh, patterns)
+    return obj.vertex_groups[extra_vgroups[index]].name
+
+
+@_mesh
+def extra_vertex_group(mesh, patterns, index):
+    """
+    Return (indexed) data of an extra vertex group.
+
+    :param mesh:
+    :param patterns:
+    :param index:
+
+    """
+    data = []
+    extra_vgroups = _extra_vertex_groups(mesh, patterns)
+    group_index = extra_vgroups[index]
+    for vertex in mesh.vertices:
+        weight = None
+        for group in vertex.groups:
+            if group.group == group_index:
+                weight = group.weight
+        data.append(weight or 0.0)
+    return data
+
+
+@_mesh
+def buffer_extra_vertex_group(mesh, patterns, index):
+    """
+    Return a buffer of the values within an extra vertex group.
+
+    :param mesh:
+    :param patterns:
+    :param index:
+
+    """
+    data = []
+    extra_vgroups = _extra_vertex_groups(mesh, patterns)
+    group_index = extra_vgroups[index]
+    for face in mesh.tessfaces:
+        for vertex_index in face.vertices:
+            vertex = mesh.vertices[vertex_index]
+            weight = None
+            for group in vertex.groups:
+                if group.group == group_index:
+                    weight = group.weight
+            data.append(weight or 0.0)
+    return data
+
+
+def _extra_vertex_groups(mesh, patterns):
+    """
+
+    :param mesh:
+    :param patterns:
+
+    """
+    logger.debug("mesh._extra_vertex_groups(%s)", mesh)
+    pattern_re = None
+    extra_vgroups = []
+    if not patterns.strip():
+        return extra_vgroups
+    armature = _armature(mesh)
+    obj = object_.objects_using_mesh(mesh)[0]
+    for vgroup_index, vgroup in enumerate(obj.vertex_groups):
+        # Skip bone weights:
+        vgroup_name = vgroup.name
+        if armature:
+            is_bone_weight=False
+            for bone in armature.pose.bones:
+                if bone.name == vgroup_name:
+                    is_bone_weight=True
+                    break
+            if (is_bone_weight):
+                continue
+
+        if pattern_re is None:
+            # Translate user-friendly patterns to a regular expression:
+            # Join the whitespace-stripped, initially comma-separated
+            # entries to alternatives. Escape all characters except
+            # the star and replace that one with '.*?'.
+            pattern_re = '^(?:' + '|'.join(
+                map(lambda entry:
+                    '.*?'.join(map(re.escape, entry.strip().split('*'))),
+                patterns.split(',')) ) + ')$'
+
+        if not re.match(pattern_re, vgroup_name):
+            continue
+
+        extra_vgroups.append(vgroup_index)
+    return extra_vgroups
+
+
 @_mesh
 @_mesh
 def faces(mesh, options, materials=None):
 def faces(mesh, options, materials=None):
     """
     """

+ 48 - 0
utils/exporters/blender/addons/io_three/exporter/geometry.py

@@ -260,6 +260,15 @@ class Geometry(base_classes.BaseNode):
             else:
             else:
                 logger.info("No animation data found for %s", self.node)
                 logger.info("No animation data found for %s", self.node)
 
 
+        option_extra_vgroups = self.options.get(constants.EXTRA_VGROUPS)
+
+        if option_extra_vgroups:
+            patterns = option_extra_vgroups
+            for i in range(
+                    api.mesh.extra_vertex_group_count(self.node, patterns)):
+                name = api.mesh.extra_vertex_group_name(self.node, patterns, i)
+                components.append(name)
+
         for component in components:
         for component in components:
             try:
             try:
                 data[component] = self[component]
                 data[component] = self[component]
@@ -363,6 +372,7 @@ class Geometry(base_classes.BaseNode):
         options_vertices = self.options.get(constants.VERTICES)
         options_vertices = self.options.get(constants.VERTICES)
         option_normals = self.options.get(constants.NORMALS)
         option_normals = self.options.get(constants.NORMALS)
         option_uvs = self.options.get(constants.UVS)
         option_uvs = self.options.get(constants.UVS)
+        option_extra_vgroups = self.options.get(constants.EXTRA_VGROUPS)
 
 
         pos_tuple = (constants.POSITION, options_vertices,
         pos_tuple = (constants.POSITION, options_vertices,
                      api.mesh.buffer_position, 3)
                      api.mesh.buffer_position, 3)
@@ -388,6 +398,29 @@ class Geometry(base_classes.BaseNode):
                 constants.ARRAY: array
                 constants.ARRAY: array
             }
             }
 
 
+        if option_extra_vgroups:
+
+            patterns = option_extra_vgroups
+
+            for i in range(
+                    api.mesh.extra_vertex_group_count(self.node, patterns)):
+
+                key = api.mesh.extra_vertex_group_name(self.node, patterns, i)
+                array = api.mesh.buffer_extra_vertex_group(self.node, patterns, i)
+
+                if not array:
+                    logger.warning("No array could be made for %s", key)
+                    continue
+
+                logger.info("Exporting extra vertex group %s", key)
+
+                self[constants.ATTRIBUTES][key] = {
+                    constants.ITEM_SIZE: 1,
+                    constants.TYPE: constants.FLOAT_32,
+                    constants.ARRAY: array
+                }
+
+
     def _parse_geometry(self):
     def _parse_geometry(self):
         """Parse the geometry to Three.Geometry specs"""
         """Parse the geometry to Three.Geometry specs"""
         if self.options.get(constants.VERTICES):
         if self.options.get(constants.VERTICES):
@@ -448,3 +481,18 @@ 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 []
+
+        # In the moment there is no way to add extra data to a Geomtry in
+        # Three.js. In case it does some day, here is the code:
+        #
+        # option_extra_vgroups = self.options.get(constants.EXTRA_VGROUPS)
+        # if option_extra_vgroups:
+        #
+        #     patterns = option_extra_vgroups
+        #     for i in range(
+        #             api.mesh.extra_vertex_group_count(self.node, patterns)):
+        #
+        #         key = api.mesh.extra_vertex_group_name(self.node, patterns, i)
+        #         logger.info("Exporting extra vertex group %s", key)
+        #         self[key] = api.mesh.extra_vertex_group(self.node, patterns, i)
+