diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2018-11-24 18:28:33 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2018-11-24 18:28:33 +0300 |
commit | b1f2133fa2849da272e9d8404f371220226ddaf1 (patch) | |
tree | 25db56e0f2211bd1059fe0e04e78430a6156e021 /io_scene_gltf2/blender/com | |
parent | 8959f1798cfc86924493347304118c61bd5c7f7a (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/blender/com')
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_conversion.py | 42 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_data_path.py | 28 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_image.py | 32 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_image_util.py | 121 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_json.py | 38 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py | 59 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_math.py | 159 |
7 files changed, 479 insertions, 0 deletions
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py new file mode 100755 index 00000000..95fa292d --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py @@ -0,0 +1,42 @@ +# 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 mathutils import Matrix, Quaternion + +def matrix_gltf_to_blender(mat_input): + """Matrix from glTF format to Blender format.""" + mat = Matrix([mat_input[0:4], mat_input[4:8], mat_input[8:12], mat_input[12:16]]) + mat.transpose() + return mat + +def loc_gltf_to_blender(loc): + """Location.""" + return loc + +def scale_gltf_to_blender(scale): + """Scaling.""" + return scale + +def quaternion_gltf_to_blender(q): + """Quaternion from glTF to Blender.""" + return Quaternion([q[3], q[0], q[1], q[2]]) + +def scale_to_matrix(scale): + """Scale to matrix.""" + mat = Matrix() + for i in range(3): + mat[i][i] = scale[i] + + return mat + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_data_path.py b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py new file mode 100755 index 00000000..c5ce4025 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py @@ -0,0 +1,28 @@ +# 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. + + +def get_target_property_name(data_path: str) -> str: + """Retrieve target property.""" + return data_path.rsplit('.', 1)[-1] + + +def get_target_object_path(data_path: str) -> str: + """Retrieve target object data path without property""" + path_split = data_path.rsplit('.', 1) + self_targeting = len(path_split) < 2 + if self_targeting: + return "" + return path_split[0] + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image.py b/io_scene_gltf2/blender/com/gltf2_blender_image.py new file mode 100755 index 00000000..7564070d --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_image.py @@ -0,0 +1,32 @@ +# 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. + +# +# Imports +# + +from ...io.com.gltf2_io_image import create_img_from_pixels + + +def create_img_from_blender_image(blender_image): + """ + Create a new image object using the given blender image. + + Returns the created image object. + """ + if blender_image is None: + return None + + return create_img_from_pixels(blender_image.size[0], blender_image.size[1], blender_image.pixels[:]) + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image_util.py b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py new file mode 100755 index 00000000..e2563a52 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py @@ -0,0 +1,121 @@ +# 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 os +import shutil +import bpy +import zlib +import struct +from io_scene_gltf2.blender.exp import gltf2_blender_get + + +def create_image_file(context, blender_image, dst_path, file_format): + """Create JPEG or PNG file from a given Blender image.""" + # Check, if source image exists e.g. does not exist if image is packed. + file_exists = 1 + try: + src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library) + file = open(src_path) + except IOError: + file_exists = 0 + else: + file.close() + + if file_exists == 0: + # Image does not exist on disk ... + blender_image.filepath = dst_path + # ... so save it. + blender_image.save() + + elif file_format == blender_image.file_format: + # Copy source image to destination, keeping original format. + + src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library) + + # Required for comapre. + src_path = src_path.replace('\\', '/') + dst_path = dst_path.replace('\\', '/') + + # Check that source and destination path are not the same using os.path.abspath + # because bpy.path.abspath seems to not always return an absolute path + if os.path.abspath(dst_path) != os.path.abspath(src_path): + shutil.copyfile(src_path, dst_path) + + else: + # Render a new image to destination, converting to target format. + + # TODO: Reusing the existing scene means settings like exposure are applied on export, + # which we don't want, but I'm not sure how to create a new Scene object through the + # Python API. See: https://github.com/KhronosGroup/glTF-Blender-Exporter/issues/184. + + tmp_file_format = context.scene.render.image_settings.file_format + tmp_color_depth = context.scene.render.image_settings.color_depth + + context.scene.render.image_settings.file_format = file_format + context.scene.render.image_settings.color_depth = '8' + blender_image.save_render(dst_path, context.scene) + + context.scene.render.image_settings.file_format = tmp_file_format + context.scene.render.image_settings.color_depth = tmp_color_depth + + +def create_image_data(context, export_settings, blender_image, file_format): + """Create JPEG or PNG byte array from a given Blender image.""" + if blender_image is None: + return None + + if file_format == 'PNG': + return _create_png_data(blender_image) + else: + return _create_jpg_data(context, export_settings, blender_image) + + +def _create_jpg_data(context, export_settings, blender_image): + """Create a JPEG byte array from a given Blender image.""" + uri = gltf2_blender_get.get_image_uri(export_settings, blender_image) + path = export_settings['gltf_filedirectory'] + uri + + create_image_file(context, blender_image, path, 'JPEG') + + jpg_data = open(path, 'rb').read() + os.remove(path) + + return jpg_data + + +def _create_png_data(blender_image): + """Create a PNG byte array from a given Blender image.""" + width, height = blender_image.size + + buf = bytearray([int(channel * 255.0) for channel in blender_image.pixels]) + + # + # Taken from 'blender-thumbnailer.py' in Blender. + # + + # reverse the vertical line order and add null bytes at the start + width_byte_4 = width * 4 + raw_data = b"".join( + b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4)) + + def png_pack(png_tag, data): + chunk_head = png_tag + data + return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)) + + return b"".join([ + b'\x89PNG\r\n\x1a\n', + png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)), + png_pack(b'IDAT', zlib.compress(raw_data, 9)), + png_pack(b'IEND', b'')]) + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_json.py b/io_scene_gltf2/blender/com/gltf2_blender_json.py new file mode 100755 index 00000000..fbf833c1 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_json.py @@ -0,0 +1,38 @@ +# 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 json +import bpy + + +class BlenderJSONEncoder(json.JSONEncoder): + """Blender JSON Encoder.""" + + def default(self, obj): + if isinstance(obj, bpy.types.ID): + return dict( + name=obj.name, + type=obj.__class__.__name__ + ) + return super(BlenderJSONEncoder, self).default(obj) + + +def is_json_convertible(data): + """Test, if a data set can be expressed as JSON.""" + try: + json.dumps(data, cls=BlenderJSONEncoder) + return True + except: + return False + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py new file mode 100755 index 00000000..05f35954 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py @@ -0,0 +1,59 @@ +# 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. + + +def get_output_node(node_tree): + """Retrive output node.""" + output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0] + return output + + +def get_output_surface_input(node_tree): + """Retrieve surface input of output node.""" + output_node = get_output_node(node_tree) + return output_node.inputs['Surface'] + + +def get_diffuse_texture(node_tree): + """Retrieve diffuse texture node.""" + for node in node_tree.nodes: + print(node.name) + if node.label == 'BASE COLOR': + return node + + return None + + +def get_preoutput_node_output(node_tree): + """Retrieve node just before output node.""" + output_node = get_output_node(node_tree) + preoutput_node = output_node.inputs['Surface'].links[0].from_node + + # Pre output node is Principled BSDF or any BSDF => BSDF + if 'BSDF' in preoutput_node.type: + return preoutput_node.outputs['BSDF'] + elif 'SHADER' in preoutput_node.type: + return preoutput_node.outputs['Shader'] + else: + print(preoutput_node.type) + + +def get_base_color_node(node_tree): + """Returns the last node of the diffuse block.""" + for node in node_tree.nodes: + if node.label == 'BASE COLOR': + return node + + return None + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py new file mode 100755 index 00000000..26eb6396 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py @@ -0,0 +1,159 @@ +# 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 bpy +import typing +import math +from mathutils import Matrix, Vector, Quaternion, Euler + +from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_property_name + + +def multiply(a, b): + """Multiplication.""" + return a @ b + + +def list_to_mathutils(values: typing.List[float], data_path: str) -> typing.Union[Vector, Quaternion, Euler]: + """Transform a list to blender py object.""" + target = get_target_property_name(data_path) + + if target == 'location': + return Vector(values) + elif target == 'rotation_axis_angle': + angle = values[0] + axis = values[1:] + return Quaternion(axis, math.radians(angle)) + elif target == 'rotation_euler': + return Euler(values).to_quaternion() + elif target == 'rotation_quaternion': + return Quaternion(values) + elif target == 'scale': + return Vector(values) + elif target == 'value': + return values + + return values + + +def mathutils_to_gltf(x: typing.Union[Vector, Quaternion]) -> typing.List[float]: + """Transform a py object to glTF list.""" + if isinstance(x, Vector): + return list(x) + if isinstance(x, Quaternion): + # Blender has w-first quaternion notation + return [x[1], x[2], x[3], x[0]] + else: + return list(x) + + +def to_yup() -> Matrix: + """Transform to Yup.""" + return Matrix( + ((1.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, -1.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 1.0)) + ) + + +to_zup = to_yup + + +def swizzle_yup(v: typing.Union[Vector, Quaternion], data_path: str) -> typing.Union[Vector, Quaternion]: + """Manage Yup.""" + target = get_target_property_name(data_path) + swizzle_func = { + "location": swizzle_yup_location, + "rotation_axis_angle": swizzle_yup_rotation, + "rotation_euler": swizzle_yup_rotation, + "rotation_quaternion": swizzle_yup_rotation, + "scale": swizzle_yup_scale, + "value": swizzle_yup_value + }.get(target) + + if swizzle_func is None: + raise RuntimeError("Cannot transform values at {}".format(data_path)) + + return swizzle_func(v) + + +def swizzle_yup_location(loc: Vector) -> Vector: + """Manage Yup location.""" + return Vector((loc[0], loc[2], -loc[1])) + + +def swizzle_yup_rotation(rot: Quaternion) -> Quaternion: + """Manage Yup rotation.""" + return Quaternion((rot[0], rot[1], rot[3], -rot[2])) + + +def swizzle_yup_scale(scale: Vector) -> Vector: + """Manage Yup scale.""" + return Vector((scale[0], scale[2], scale[1])) + + +def swizzle_yup_value(value: typing.Any) -> typing.Any: + """Manage Yup value.""" + return value + + +def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \ + .Union[Vector, Quaternion]: + """Manage transformations.""" + target = get_target_property_name(data_path) + transform_func = { + "location": transform_location, + "rotation_axis_angle": transform_rotation, + "rotation_euler": transform_rotation, + "rotation_quaternion": transform_rotation, + "scale": transform_scale, + "value": transform_value + }.get(target) + + if transform_func is None: + raise RuntimeError("Cannot transform values at {}".format(data_path)) + + return transform_func(v, transform) + + +def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: + """Transform location.""" + m = Matrix.Translation(location) + m = multiply(transform, m) + return m.to_translation() + + +def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion: + """Transform rotation.""" + m = rotation.to_matrix().to_4x4() + m = multiply(transform, m) + return m.to_quaternion() + + +def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: + """Transform scale.""" + m = Matrix.Identity(4) + m[0][0] = scale.x + m[1][1] = scale.y + m[2][2] = scale.z + m = multiply(transform, m) + + return m.to_scale() + + +def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector: + """Transform value.""" + return value + |