From 3ea1673580ab68ecb713da3233a4f6beaafff5d9 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 21 Jul 2020 20:19:01 +0200 Subject: glTF exporter: performance: using numpy Thanks scurest! --- io_scene_gltf2/__init__.py | 2 +- .../gltf2_blender_gather_primitive_attributes.py | 185 +++++++++------------ .../blender/exp/gltf2_blender_gather_primitives.py | 58 +------ io_scene_gltf2/blender/exp/gltf2_blender_utils.py | 67 -------- io_scene_gltf2/io/com/gltf2_io_constants.py | 12 ++ 5 files changed, 96 insertions(+), 228 deletions(-) delete mode 100755 io_scene_gltf2/blender/exp/gltf2_blender_utils.py diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 16fc681e..0beb10a1 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 3, 33), + "version": (1, 3, 34), 'blender': (2, 90, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py index f5856257..8912d921 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import numpy as np + from . import gltf2_blender_export_keys from io_scene_gltf2.io.com import gltf2_io from io_scene_gltf2.io.com import gltf2_io_constants from io_scene_gltf2.io.com import gltf2_io_debug from io_scene_gltf2.io.exp import gltf2_io_binary_data -from io_scene_gltf2.blender.exp import gltf2_blender_utils def gather_primitive_attributes(blender_primitive, export_settings): @@ -36,72 +37,79 @@ def gather_primitive_attributes(blender_primitive, export_settings): return attributes +def array_to_accessor(array, component_type, data_type, include_max_and_min=False): + dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(component_type) + num_elems = gltf2_io_constants.DataType.num_elements(data_type) + + if type(array) is not np.ndarray: + array = np.array(array, dtype=dtype) + array = array.reshape(len(array) // num_elems, num_elems) + + assert array.dtype == dtype + assert array.shape[1] == num_elems + + amax = None + amin = None + if include_max_and_min: + amax = np.amax(array, axis=0).tolist() + amin = np.amin(array, axis=0).tolist() + + return gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()), + byte_offset=None, + component_type=component_type, + count=len(array), + extensions=None, + extras=None, + max=amax, + min=amin, + name=None, + normalized=None, + sparse=None, + type=data_type, + ) + + def __gather_position(blender_primitive, export_settings): position = blender_primitive["attributes"]["POSITION"] - componentType = gltf2_io_constants.ComponentType.Float return { - "POSITION": gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list(position, componentType), - byte_offset=None, - component_type=componentType, - count=len(position) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3), - extensions=None, - extras=None, - max=gltf2_blender_utils.max_components(position, gltf2_io_constants.DataType.Vec3), - min=gltf2_blender_utils.min_components(position, gltf2_io_constants.DataType.Vec3), - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec3 + "POSITION": array_to_accessor( + position, + component_type=gltf2_io_constants.ComponentType.Float, + data_type=gltf2_io_constants.DataType.Vec3, + include_max_and_min=True ) } def __gather_normal(blender_primitive, export_settings): - if export_settings[gltf2_blender_export_keys.NORMALS]: - normal = blender_primitive["attributes"]['NORMAL'] - return { - "NORMAL": gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list(normal, gltf2_io_constants.ComponentType.Float), - byte_offset=None, - component_type=gltf2_io_constants.ComponentType.Float, - count=len(normal) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec3 - ) - } - return {} + if not export_settings[gltf2_blender_export_keys.NORMALS]: + return {} + normal = blender_primitive["attributes"].get('NORMAL') + if not normal: + return {} + return { + "NORMAL": array_to_accessor( + normal, + component_type=gltf2_io_constants.ComponentType.Float, + data_type=gltf2_io_constants.DataType.Vec3, + ) + } def __gather_tangent(blender_primitive, export_settings): - if export_settings[gltf2_blender_export_keys.TANGENTS]: - if blender_primitive["attributes"].get('TANGENT') is not None: - tangent = blender_primitive["attributes"]['TANGENT'] - return { - "TANGENT": gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list( - tangent, gltf2_io_constants.ComponentType.Float), - byte_offset=None, - component_type=gltf2_io_constants.ComponentType.Float, - count=len(tangent) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec4 - ) - } - - return {} + if not export_settings[gltf2_blender_export_keys.TANGENTS]: + return {} + tangent = blender_primitive["attributes"].get('TANGENT') + if not tangent: + return {} + return { + "TANGENT": array_to_accessor( + tangent, + component_type=gltf2_io_constants.ComponentType.Float, + data_type=gltf2_io_constants.DataType.Vec4, + ) + } def __gather_texcoord(blender_primitive, export_settings): @@ -111,20 +119,10 @@ def __gather_texcoord(blender_primitive, export_settings): tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) while blender_primitive["attributes"].get(tex_coord_id) is not None: tex_coord = blender_primitive["attributes"][tex_coord_id] - attributes[tex_coord_id] = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list( - tex_coord, gltf2_io_constants.ComponentType.Float), - byte_offset=None, + attributes[tex_coord_id] = array_to_accessor( + tex_coord, component_type=gltf2_io_constants.ComponentType.Float, - count=len(tex_coord) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec2), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec2 + data_type=gltf2_io_constants.DataType.Vec2, ) tex_coord_index += 1 tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) @@ -138,20 +136,10 @@ def __gather_colors(blender_primitive, export_settings): color_id = 'COLOR_' + str(color_index) while blender_primitive["attributes"].get(color_id) is not None: internal_color = blender_primitive["attributes"][color_id] - attributes[color_id] = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list( - internal_color, gltf2_io_constants.ComponentType.Float), - byte_offset=None, + attributes[color_id] = array_to_accessor( + internal_color, component_type=gltf2_io_constants.ComponentType.Float, - count=len(internal_color) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec4 + data_type=gltf2_io_constants.DataType.Vec4, ) color_index += 1 color_id = 'COLOR_' + str(color_index) @@ -173,20 +161,10 @@ def __gather_skins(blender_primitive, export_settings): # joints internal_joint = blender_primitive["attributes"][joint_id] - joint = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list( - internal_joint, gltf2_io_constants.ComponentType.UnsignedShort), - byte_offset=None, + joint = array_to_accessor( + internal_joint, component_type=gltf2_io_constants.ComponentType.UnsignedShort, - count=len(internal_joint) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec4 + data_type=gltf2_io_constants.DataType.Vec4, ) attributes[joint_id] = joint @@ -201,21 +179,10 @@ def __gather_skins(blender_primitive, export_settings): factor = 1.0 / total internal_weight[idx:idx + 4] = [w * factor for w in weight_slice] - weight = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData.from_list( - internal_weight, gltf2_io_constants.ComponentType.Float), - byte_offset=None, + weight = array_to_accessor( + internal_weight, component_type=gltf2_io_constants.ComponentType.Float, - count=len(internal_weight) // gltf2_io_constants.DataType.num_elements( - gltf2_io_constants.DataType.Vec4), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec4 + data_type=gltf2_io_constants.DataType.Vec4, ) attributes[weight_id] = weight diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py index 22f0bc6d..1a2ae00d 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -21,7 +21,6 @@ from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached from io_scene_gltf2.blender.exp import gltf2_blender_extract from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes -from io_scene_gltf2.blender.exp import gltf2_blender_utils from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials from io_scene_gltf2.io.com import gltf2_io @@ -160,26 +159,11 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings if blender_primitive["attributes"].get(target_position_id): target = {} internal_target_position = blender_primitive["attributes"][target_position_id] - binary_data = gltf2_io_binary_data.BinaryData.from_list( + target["POSITION"] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_position, - gltf2_io_constants.ComponentType.Float - ) - target["POSITION"] = gltf2_io.Accessor( - buffer_view=binary_data, - byte_offset=None, component_type=gltf2_io_constants.ComponentType.Float, - count=len(internal_target_position) // gltf2_io_constants.DataType.num_elements( - gltf2_io_constants.DataType.Vec3), - extensions=None, - extras=None, - max=gltf2_blender_utils.max_components( - internal_target_position, gltf2_io_constants.DataType.Vec3), - min=gltf2_blender_utils.min_components( - internal_target_position, gltf2_io_constants.DataType.Vec3), - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec3 + data_type=gltf2_io_constants.DataType.Vec3, + include_max_and_min=True, ) if export_settings[NORMALS] \ @@ -187,48 +171,20 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings and blender_primitive["attributes"].get(target_normal_id): internal_target_normal = blender_primitive["attributes"][target_normal_id] - binary_data = gltf2_io_binary_data.BinaryData.from_list( + target['NORMAL'] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_normal, - gltf2_io_constants.ComponentType.Float, - ) - target['NORMAL'] = gltf2_io.Accessor( - buffer_view=binary_data, - byte_offset=None, component_type=gltf2_io_constants.ComponentType.Float, - count=len(internal_target_normal) // gltf2_io_constants.DataType.num_elements( - gltf2_io_constants.DataType.Vec3), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec3 + data_type=gltf2_io_constants.DataType.Vec3, ) if export_settings[TANGENTS] \ and export_settings[MORPH_TANGENT] \ and blender_primitive["attributes"].get(target_tangent_id): internal_target_tangent = blender_primitive["attributes"][target_tangent_id] - binary_data = gltf2_io_binary_data.BinaryData.from_list( + target['TANGENT'] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_tangent, - gltf2_io_constants.ComponentType.Float, - ) - target['TANGENT'] = gltf2_io.Accessor( - buffer_view=binary_data, - byte_offset=None, component_type=gltf2_io_constants.ComponentType.Float, - count=len(internal_target_tangent) // gltf2_io_constants.DataType.num_elements( - gltf2_io_constants.DataType.Vec3), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=None, - sparse=None, - type=gltf2_io_constants.DataType.Vec3 + data_type=gltf2_io_constants.DataType.Vec3, ) targets.append(target) morph_index += 1 diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_utils.py b/io_scene_gltf2/blender/exp/gltf2_blender_utils.py deleted file mode 100755 index 8d5baae7..00000000 --- a/io_scene_gltf2/blender/exp/gltf2_blender_utils.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2018 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -from io_scene_gltf2.io.com import gltf2_io_constants - - -# TODO: we could apply functional programming to these problems (currently we only have a single use case) - -def split_list_by_data_type(l: list, data_type: gltf2_io_constants.DataType): - """ - Split a flat list of components by their data type. - - E.g.: A list [0,1,2,3,4,5] of data type Vec3 would be split to [[0,1,2], [3,4,5]] - :param l: the flat list - :param data_type: the data type of the list - :return: a list of lists, where each element list contains the components of the data type - """ - if not (len(l) % gltf2_io_constants.DataType.num_elements(data_type) == 0): - raise ValueError("List length does not match specified data type") - num_elements = gltf2_io_constants.DataType.num_elements(data_type) - return [l[i:i + num_elements] for i in range(0, len(l), num_elements)] - - -def max_components(l: list, data_type: gltf2_io_constants.DataType) -> list: - """ - Find the maximum components in a flat list. - - This is required, for example, for the glTF2.0 accessor min and max properties - :param l: the flat list of components - :param data_type: the data type of the list (determines the length of the result) - :return: a list with length num_elements(data_type) containing the maximum per component along the list - """ - components_lists = split_list_by_data_type(l, data_type) - result = [-math.inf] * gltf2_io_constants.DataType.num_elements(data_type) - for components in components_lists: - for i, c in enumerate(components): - result[i] = max(result[i], c) - return result - - -def min_components(l: list, data_type: gltf2_io_constants.DataType) -> list: - """ - Find the minimum components in a flat list. - - This is required, for example, for the glTF2.0 accessor min and max properties - :param l: the flat list of components - :param data_type: the data type of the list (determines the length of the result) - :return: a list with length num_elements(data_type) containing the minimum per component along the list - """ - components_lists = split_list_by_data_type(l, data_type) - result = [math.inf] * gltf2_io_constants.DataType.num_elements(data_type) - for components in components_lists: - for i, c in enumerate(components): - result[i] = min(result[i], c) - return result diff --git a/io_scene_gltf2/io/com/gltf2_io_constants.py b/io_scene_gltf2/io/com/gltf2_io_constants.py index 873e004e..983fe9ab 100755 --- a/io_scene_gltf2/io/com/gltf2_io_constants.py +++ b/io_scene_gltf2/io/com/gltf2_io_constants.py @@ -34,6 +34,18 @@ class ComponentType(IntEnum): ComponentType.Float: 'f' }[component_type] + @classmethod + def to_numpy_dtype(cls, component_type): + import numpy as np + return { + ComponentType.Byte: np.int8, + ComponentType.UnsignedByte: np.uint8, + ComponentType.Short: np.int16, + ComponentType.UnsignedShort: np.uint16, + ComponentType.UnsignedInt: np.uint32, + ComponentType.Float: np.float32, + }[component_type] + @classmethod def from_legacy_define(cls, type_define): return { -- cgit v1.2.3