Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py')
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py301
1 files changed, 301 insertions, 0 deletions
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
new file mode 100755
index 00000000..65efafef
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
@@ -0,0 +1,301 @@
+# 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.
+
+from typing import Optional, List, Dict
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_extensions
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.exp import gltf2_io_image_data
+from io_scene_gltf2.io.exp import gltf2_io_buffer
+
+import bpy
+import os
+from shutil import copyfile
+
+class GlTF2Exporter:
+ """
+ The glTF exporter flattens a scene graph to a glTF serializable format.
+
+ Any child properties are replaced with references where necessary
+ """
+
+ def __init__(self, copyright=None):
+ self.__finalized = False
+
+ asset = gltf2_io.Asset(
+ copyright=copyright,
+ extensions=None,
+ extras=None,
+ generator='Khronos Blender glTF 2.0 I/O',
+ min_version=None,
+ version='2.0')
+
+ self.__gltf = gltf2_io.Gltf(
+ accessors=[],
+ animations=[],
+ asset=asset,
+ buffers=[],
+ buffer_views=[],
+ cameras=[],
+ extensions={},
+ extensions_required=[],
+ extensions_used=[],
+ extras=None,
+ images=[],
+ materials=[],
+ meshes=[],
+ nodes=[],
+ samplers=[],
+ scene=-1,
+ scenes=[],
+ skins=[],
+ textures=[]
+ )
+
+ self.__buffer = gltf2_io_buffer.Buffer()
+ self.__images = []
+
+ # mapping of all glTFChildOfRootProperty types to their corresponding root level arrays
+ self.__childOfRootPropertyTypeLookup = {
+ gltf2_io.Accessor: self.__gltf.accessors,
+ gltf2_io.Animation: self.__gltf.animations,
+ gltf2_io.Buffer: self.__gltf.buffers,
+ gltf2_io.BufferView: self.__gltf.buffer_views,
+ gltf2_io.Camera: self.__gltf.cameras,
+ gltf2_io.Image: self.__gltf.images,
+ gltf2_io.Material: self.__gltf.materials,
+ gltf2_io.Mesh: self.__gltf.meshes,
+ gltf2_io.Node: self.__gltf.nodes,
+ gltf2_io.Sampler: self.__gltf.samplers,
+ gltf2_io.Scene: self.__gltf.scenes,
+ gltf2_io.Skin: self.__gltf.skins,
+ gltf2_io.Texture: self.__gltf.textures
+ }
+
+ self.__propertyTypeLookup = [
+ gltf2_io.AccessorSparseIndices,
+ gltf2_io.AccessorSparse,
+ gltf2_io.AccessorSparseValues,
+ gltf2_io.AnimationChannel,
+ gltf2_io.AnimationChannelTarget,
+ gltf2_io.AnimationSampler,
+ gltf2_io.Asset,
+ gltf2_io.CameraOrthographic,
+ gltf2_io.CameraPerspective,
+ gltf2_io.MeshPrimitive,
+ gltf2_io.TextureInfo,
+ gltf2_io.MaterialPBRMetallicRoughness,
+ gltf2_io.MaterialNormalTextureInfoClass,
+ gltf2_io.MaterialOcclusionTextureInfoClass
+ ]
+
+ @property
+ def glTF(self):
+ if not self.__finalized:
+ raise RuntimeError("glTF requested, but buffers are not finalized yet")
+ return self.__gltf
+
+ def finalize_buffer(self, output_path=None, buffer_name=None, is_glb=False):
+ """Finalize the glTF and write buffers."""
+ if self.__finalized:
+ raise RuntimeError("Tried to finalize buffers for finalized glTF file")
+
+ if is_glb:
+ uri = None
+ elif output_path and buffer_name:
+ with open(output_path + buffer_name, 'wb') as f:
+ f.write(self.__buffer.to_bytes())
+ uri = buffer_name
+ else:
+ uri = self.__buffer.to_embed_string()
+
+ buffer = gltf2_io.Buffer(
+ byte_length=self.__buffer.byte_length,
+ extensions=None,
+ extras=None,
+ name=None,
+ uri=uri
+ )
+ self.__gltf.buffers.append(buffer)
+
+ self.__finalized = True
+
+ if is_glb:
+ return self.__buffer.to_bytes()
+
+ def finalize_images(self, output_path):
+ """
+ Write all images.
+
+ Due to a current limitation the output_path must be the same as that of the glTF file
+ :param output_path:
+ :return:
+ """
+ for image in self.__images:
+ dst_path = output_path + image.name + image.get_extension()
+ src_path = bpy.path.abspath(image.filepath)
+ if os.path.isfile(src_path):
+ # Source file exists.
+ if os.path.abspath(dst_path) != os.path.abspath(src_path):
+ # Only copy, if source and destination are not the same.
+ copyfile(src_path, dst_path)
+ else:
+ # Source file does not exist e.g. it is packed or has been generated.
+ with open(dst_path, 'wb') as f:
+ f.write(image.to_png_data())
+
+ def add_scene(self, scene: gltf2_io.Scene, active: bool = True):
+ """
+ Add a scene to the glTF.
+
+ The scene should be built up with the generated glTF classes
+ :param scene: gltf2_io.Scene type. Root node of the scene graph
+ :param active: If true, sets the glTD.scene index to the added scene
+ :return: nothing
+ """
+ if self.__finalized:
+ raise RuntimeError("Tried to add scene to finalized glTF file")
+
+ # for node in scene.nodes:
+ # self.__traverse(node)
+ scene_num = self.__traverse(scene)
+ if active:
+ self.__gltf.scene = scene_num
+
+ def add_animation(self, animation: gltf2_io.Animation):
+ """
+ Add an animation to the glTF.
+
+ :param animation: glTF animation, with python style references (names)
+ :return: nothing
+ """
+ if self.__finalized:
+ raise RuntimeError("Tried to add animation to finalized glTF file")
+
+ self.__traverse(animation)
+
+ def __to_reference(self, property):
+ """
+ Append a child of root property to its respective list and return a reference into said list.
+
+ If the property is not child of root, the property itself is returned.
+ :param property: A property type object that should be converted to a reference
+ :return: a reference or the object itself if it is not child or root
+ """
+ gltf_list = self.__childOfRootPropertyTypeLookup.get(type(property), None)
+ if gltf_list is None:
+ # The object is not of a child of root --> don't convert to reference
+ return property
+
+ return self.__append_unique_and_get_index(gltf_list, property)
+
+ @staticmethod
+ def __append_unique_and_get_index(target: list, obj):
+ if obj in target:
+ return target.index(obj)
+ else:
+ index = len(target)
+ target.append(obj)
+ return index
+
+ def __add_image(self, image: gltf2_io_image_data.ImageData):
+ self.__images.append(image)
+ # TODO: we need to know the image url at this point already --> maybe add all options to the constructor of the
+ # exporter
+ # TODO: allow embedding of images (base64)
+ return image.name + ".png"
+
+ @classmethod
+ def __get_key_path(cls, d: dict, keypath: List[str], default=[]):
+ """Create if necessary and get the element at key path from a dict"""
+ key = keypath.pop(0)
+
+ if len(keypath) == 0:
+ v = d.get(key, default)
+ d[key] = v
+ return v
+
+ d_key = d.get(key, {})
+ d[key] = d_key
+ return cls.__get_key_path(d[key], keypath, default)
+
+ def __traverse(self, node):
+ """
+ Recursively traverse a scene graph consisting of gltf compatible elements.
+
+ The tree is traversed downwards until a primitive is reached. Then any ChildOfRoot property
+ is stored in the according list in the glTF and replaced with a index reference in the upper level.
+ """
+ def __traverse_property(node):
+ for member_name in [a for a in dir(node) if not a.startswith('__') and not callable(getattr(node, a))]:
+ new_value = self.__traverse(getattr(node, member_name))
+ setattr(node, member_name, new_value) # usually this is the same as before
+
+ # # TODO: maybe with extensions hooks we can find a more elegant solution
+ # if member_name == "extensions" and new_value is not None:
+ # for extension_name in new_value.keys():
+ # self.__append_unique_and_get_index(self.__gltf.extensions_used, extension_name)
+ # self.__append_unique_and_get_index(self.__gltf.extensions_required, extension_name)
+ return node
+
+ # traverse nodes of a child of root property type and add them to the glTF root
+ if type(node) in self.__childOfRootPropertyTypeLookup:
+ node = __traverse_property(node)
+ idx = self.__to_reference(node)
+ # child of root properties are only present at root level --> replace with index in upper level
+ return idx
+
+ # traverse lists, such as children and replace them with indices
+ if isinstance(node, list):
+ for i in range(len(node)):
+ node[i] = self.__traverse(node[i])
+ return node
+
+ if isinstance(node, dict):
+ for key in node.keys():
+ node[key] = self.__traverse(node[key])
+ return node
+
+ # traverse into any other property
+ if type(node) in self.__propertyTypeLookup:
+ return __traverse_property(node)
+
+ # binary data needs to be moved to a buffer and referenced with a buffer view
+ if isinstance(node, gltf2_io_binary_data.BinaryData):
+ buffer_view = self.__buffer.add_and_get_view(node)
+ return self.__to_reference(buffer_view)
+
+ # image data needs to be saved to file
+ if isinstance(node, gltf2_io_image_data.ImageData):
+ return self.__add_image(node)
+
+ # extensions
+ if isinstance(node, gltf2_io_extensions.Extension):
+ extension = self.__traverse(node.extension)
+ self.__append_unique_and_get_index(self.__gltf.extensions_used, node.name)
+ self.__append_unique_and_get_index(self.__gltf.extensions_required, node.name)
+
+ # extensions that lie in the root of the glTF.
+ # They need to be converted to a reference at place of occurrence
+ if isinstance(node, gltf2_io_extensions.ChildOfRootExtension):
+ root_extension_list = self.__get_key_path(self.__gltf.extensions, [node.name] + node.path)
+ idx = self.__append_unique_and_get_index(root_extension_list, extension)
+ return idx
+
+ return extension
+
+ # do nothing for any type that does not match a glTF schema (primitives)
+ return node
+