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:
authorJulien Duroure <julien.duroure@gmail.com>2018-11-24 18:28:33 +0300
committerJulien Duroure <julien.duroure@gmail.com>2018-11-24 18:28:33 +0300
commitb1f2133fa2849da272e9d8404f371220226ddaf1 (patch)
tree25db56e0f2211bd1059fe0e04e78430a6156e021 /io_scene_gltf2/io/imp
parent8959f1798cfc86924493347304118c61bd5c7f7a (diff)
Initial commit of glTF 2.0 importer/exporter
Official Khronos Group Blender glTF 2.0 importer and exporter. glTF specification: https://github.com/KhronosGroup/glTF The upstream repository can be found here: https://github.com/KhronosGroup/glTF-Blender-IO Reviewed By: Bastien, Campbell Differential Revision: https://developer.blender.org/D3929
Diffstat (limited to 'io_scene_gltf2/io/imp')
-rwxr-xr-xio_scene_gltf2/io/imp/__init__.py16
-rwxr-xr-xio_scene_gltf2/io/imp/gltf2_io_binary.py178
-rwxr-xr-xio_scene_gltf2/io/imp/gltf2_io_gltf.py199
3 files changed, 393 insertions, 0 deletions
diff --git a/io_scene_gltf2/io/imp/__init__.py b/io_scene_gltf2/io/imp/__init__.py
new file mode 100755
index 00000000..d3c53771
--- /dev/null
+++ b/io_scene_gltf2/io/imp/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+"""IO imp package."""
+
diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py
new file mode 100755
index 00000000..5f51d95d
--- /dev/null
+++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py
@@ -0,0 +1,178 @@
+# 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 struct
+import base64
+from os.path import dirname, join, isfile, basename
+
+
+class BinaryData():
+ """Binary reader."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def get_binary_from_accessor(gltf, accessor_idx):
+ """Get binary from accessor."""
+ accessor = gltf.data.accessors[accessor_idx]
+ bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present!
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ accessor_offset = accessor.byte_offset
+ bufferview_offset = bufferView.byte_offset
+
+ if accessor_offset is None:
+ accessor_offset = 0
+ if bufferview_offset is None:
+ bufferview_offset = 0
+
+ return buffer[accessor_offset + bufferview_offset:accessor_offset + bufferview_offset + bufferView.byte_length]
+
+ @staticmethod
+ def get_data_from_accessor(gltf, accessor_idx):
+ """Get data from accessor."""
+ accessor = gltf.data.accessors[accessor_idx]
+
+ bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present!
+ buffer_data = BinaryData.get_binary_from_accessor(gltf, accessor_idx)
+
+ fmt_char = gltf.fmt_char_dict[accessor.component_type]
+ component_nb = gltf.component_nb_dict[accessor.type]
+ fmt = '<' + (fmt_char * component_nb)
+ stride_ = struct.calcsize(fmt)
+ # TODO data alignment stuff
+
+ if bufferView.byte_stride:
+ stride = bufferView.byte_stride
+ else:
+ stride = stride_
+
+ data = []
+ offset = 0
+ while len(data) < accessor.count:
+ element = struct.unpack_from(fmt, buffer_data, offset)
+ data.append(element)
+ offset += stride
+
+ if accessor.sparse:
+ sparse_indices_data = BinaryData.get_data_from_sparse(gltf, accessor.sparse, "indices")
+ sparse_values_values = BinaryData.get_data_from_sparse(
+ gltf,
+ accessor.sparse,
+ "values",
+ accessor.type,
+ accessor.component_type
+ )
+
+ # apply sparse
+ for cpt_idx, idx in enumerate(sparse_indices_data):
+ data[idx[0]] = sparse_values_values[cpt_idx]
+
+ # Normalization
+ if accessor.normalized:
+ for idx, tuple in enumerate(data):
+ new_tuple = ()
+ for i in tuple:
+ new_tuple += (float(i),)
+ data[idx] = new_tuple
+
+ return data
+
+ @staticmethod
+ def get_data_from_sparse(gltf, sparse, type_, type_val=None, comp_type=None):
+ """Get data from sparse."""
+ if type_ == "indices":
+ bufferView = gltf.data.buffer_views[sparse.indices.buffer_view]
+ offset = sparse.indices.byte_offset
+ component_nb = gltf.component_nb_dict['SCALAR']
+ fmt_char = gltf.fmt_char_dict[sparse.indices.component_type]
+ elif type_ == "values":
+ bufferView = gltf.data.buffer_views[sparse.values.buffer_view]
+ offset = sparse.values.byte_offset
+ component_nb = gltf.component_nb_dict[type_val]
+ fmt_char = gltf.fmt_char_dict[comp_type]
+
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ bin_data = buffer[bufferView.byte_offset + offset:bufferView.byte_offset + offset + bufferView.byte_length]
+
+ fmt = '<' + (fmt_char * component_nb)
+ stride_ = struct.calcsize(fmt)
+ # TODO data alignment stuff ?
+
+ if bufferView.byte_stride:
+ stride = bufferView.byte_stride
+ else:
+ stride = stride_
+
+ data = []
+ offset = 0
+ while len(data) < sparse.count:
+ element = struct.unpack_from(fmt, bin_data, offset)
+ data.append(element)
+ offset += stride
+
+ return data
+
+ @staticmethod
+ def get_image_data(gltf, img_idx):
+ """Get data from image."""
+ pyimage = gltf.data.images[img_idx]
+
+ image_name = "Image_" + str(img_idx)
+
+ if pyimage.uri:
+ sep = ';base64,'
+ if pyimage.uri[:5] == 'data:':
+ idx = pyimage.uri.find(sep)
+ if idx != -1:
+ data = pyimage.uri[idx + len(sep):]
+ return base64.b64decode(data), image_name
+
+ if isfile(join(dirname(gltf.filename), pyimage.uri)):
+ with open(join(dirname(gltf.filename), pyimage.uri), 'rb') as f_:
+ return f_.read(), basename(join(dirname(gltf.filename), pyimage.uri))
+ else:
+ pyimage.gltf.log.error("Missing file (index " + str(img_idx) + "): " + pyimage.uri)
+ return None, None
+
+ if pyimage.buffer_view is None:
+ return None, None
+
+ bufferView = gltf.data.buffer_views[pyimage.buffer_view]
+
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ bufferview_offset = bufferView.byte_offset
+
+ if bufferview_offset is None:
+ bufferview_offset = 0
+
+ return buffer[bufferview_offset:bufferview_offset + bufferView.byte_length], image_name
+
diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
new file mode 100755
index 00000000..1c9e67a2
--- /dev/null
+++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
@@ -0,0 +1,199 @@
+# 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 ..com.gltf2_io import gltf_from_dict
+from ..com.gltf2_io_debug import Log
+import logging
+import json
+import struct
+import base64
+from os.path import dirname, join, getsize, isfile
+
+
+class glTFImporter():
+ """glTF Importer class."""
+
+ def __init__(self, filename, import_settings):
+ """initialization."""
+ self.filename = filename
+ self.import_settings = import_settings
+ self.buffers = {}
+
+ if 'loglevel' not in self.import_settings.keys():
+ self.import_settings['loglevel'] = logging.ERROR
+
+ log = Log(import_settings['loglevel'])
+ self.log = log.logger
+ self.log_handler = log.hdlr
+
+ self.SIMPLE = 1
+ self.TEXTURE = 2
+ self.TEXTURE_FACTOR = 3
+
+ # TODO: move to a com place?
+ self.extensions_managed = [
+ 'KHR_materials_pbrSpecularGlossiness'
+ ]
+
+ # TODO : merge with io_constants
+ self.fmt_char_dict = {}
+ self.fmt_char_dict[5120] = 'b' # Byte
+ self.fmt_char_dict[5121] = 'B' # Unsigned Byte
+ self.fmt_char_dict[5122] = 'h' # Short
+ self.fmt_char_dict[5123] = 'H' # Unsigned Short
+ self.fmt_char_dict[5125] = 'I' # Unsigned Int
+ self.fmt_char_dict[5126] = 'f' # Float
+
+ self.component_nb_dict = {}
+ self.component_nb_dict['SCALAR'] = 1
+ self.component_nb_dict['VEC2'] = 2
+ self.component_nb_dict['VEC3'] = 3
+ self.component_nb_dict['VEC4'] = 4
+ self.component_nb_dict['MAT2'] = 4
+ self.component_nb_dict['MAT3'] = 9
+ self.component_nb_dict['MAT4'] = 16
+
+ @staticmethod
+ def bad_json_value(val):
+ """Bad Json value."""
+ raise ValueError('Json contains some unauthorized values')
+
+ def checks(self):
+ """Some checks."""
+ if self.data.asset.version != "2.0":
+ return False, "glTF version must be 2"
+
+ if self.data.extensions_required is not None:
+ for extension in self.data.extensions_required:
+ if extension not in self.data.extensions_used:
+ return False, "Extension required must be in Extension Used too"
+ if extension not in self.extensions_managed:
+ return False, "Extension " + extension + " is not available on this addon version"
+
+ if self.data.extensions_used is not None:
+ for extension in self.data.extensions_used:
+ if extension not in self.extensions_managed:
+ # Non blocking error #TODO log
+ pass
+
+ return True, None
+
+ def load_glb(self):
+ """Load binary glb."""
+ header = struct.unpack_from('<4sII', self.content)
+ self.format = header[0]
+ self.version = header[1]
+ self.file_size = header[2]
+
+ if self.format != b'glTF':
+ return False, "This file is not a glTF/glb file"
+
+ if self.version != 2:
+ return False, "glTF version doesn't match to 2"
+
+ if self.file_size != getsize(self.filename):
+ return False, "File size doesn't match"
+
+ offset = 12 # header size = 12
+
+ # TODO check json type for chunk 0, and BIN type for next ones
+
+ # json
+ type, len_, str_json, offset = self.load_chunk(offset)
+ if len_ != len(str_json):
+ return False, "Length of json part doesn't match"
+ try:
+ json_ = json.loads(str_json.decode('utf-8'), parse_constant=glTFImporter.bad_json_value)
+ self.data = gltf_from_dict(json_)
+ except ValueError as e:
+ return False, e.args[0]
+
+ # binary data
+ chunk_cpt = 0
+ while offset < len(self.content):
+ type, len_, data, offset = self.load_chunk(offset)
+ if len_ != len(data):
+ return False, "Length of bin buffer " + str(chunk_cpt) + " doesn't match"
+
+ self.buffers[chunk_cpt] = data
+ chunk_cpt += 1
+
+ self.content = None
+ return True, None
+
+ def load_chunk(self, offset):
+ """Load chunk."""
+ chunk_header = struct.unpack_from('<I4s', self.content, offset)
+ data_length = chunk_header[0]
+ data_type = chunk_header[1]
+ data = self.content[offset + 8: offset + 8 + data_length]
+
+ return data_type, data_length, data, offset + 8 + data_length
+
+ def read(self):
+ """Read file."""
+ # Check this is a file
+ if not isfile(self.filename):
+ return False, "Please select a file"
+
+ # Check if file is gltf or glb
+ with open(self.filename, 'rb') as f:
+ self.content = f.read()
+
+ self.is_glb_format = self.content[:4] == b'glTF'
+
+ # glTF file
+ if not self.is_glb_format:
+ self.content = None
+ with open(self.filename, 'r') as f:
+ content = f.read()
+ try:
+ self.data = gltf_from_dict(json.loads(content, parse_constant=glTFImporter.bad_json_value))
+ return True, None
+ except ValueError as e:
+ return False, e.args[0]
+
+ # glb file
+ else:
+ # Parsing glb file
+ success, txt = self.load_glb()
+ return success, txt
+
+ def is_node_joint(self, node_idx):
+ """Check if node is a joint."""
+ if not self.data.skins: # if no skin in gltf file
+ return False, None
+
+ for skin_idx, skin in enumerate(self.data.skins):
+ if node_idx in skin.joints:
+ return True, skin_idx
+
+ return False, None
+
+ def load_buffer(self, buffer_idx):
+ """Load buffer."""
+ buffer = self.data.buffers[buffer_idx]
+
+ if buffer.uri:
+ sep = ';base64,'
+ if buffer.uri[:5] == 'data:':
+ idx = buffer.uri.find(sep)
+ if idx != -1:
+ data = buffer.uri[idx + len(sep):]
+ self.buffers[buffer_idx] = base64.b64decode(data)
+ return
+
+ with open(join(dirname(self.filename), buffer.uri), 'rb') as f_:
+ self.buffers[buffer_idx] = f_.read()
+