glTF: remove no more needed files

This commit is contained in:
Julien Duroure 2019-01-29 18:16:01 +01:00
parent d609009748
commit 6b465fdceb
5 changed files with 0 additions and 1344 deletions

View File

@ -1,121 +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 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'')])

View File

@ -1,638 +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.
#
# Imports
#
import bpy
from . import gltf2_blender_export_keys
from . import gltf2_blender_extract
from mathutils import Matrix, Quaternion, Euler
#
# Globals
#
JOINT_NODE = 'JOINT'
NEEDS_CONVERSION = 'CONVERSION_NEEDED'
CUBIC_INTERPOLATION = 'CUBICSPLINE'
LINEAR_INTERPOLATION = 'LINEAR'
STEP_INTERPOLATION = 'STEP'
BEZIER_INTERPOLATION = 'BEZIER'
CONSTANT_INTERPOLATION = 'CONSTANT'
#
# Functions
#
def animate_get_interpolation(export_settings, blender_fcurve_list):
"""
Retrieve the glTF interpolation, depending on a fcurve list.
Blender allows mixing and more variations of interpolations.
In such a case, a conversion is needed.
"""
if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
return NEEDS_CONVERSION
#
interpolation = None
keyframe_count = None
for blender_fcurve in blender_fcurve_list:
if blender_fcurve is None:
continue
#
current_keyframe_count = len(blender_fcurve.keyframe_points)
if keyframe_count is None:
keyframe_count = current_keyframe_count
if current_keyframe_count > 0 > blender_fcurve.keyframe_points[0].co[0]:
return NEEDS_CONVERSION
if keyframe_count != current_keyframe_count:
return NEEDS_CONVERSION
#
for blender_keyframe in blender_fcurve.keyframe_points:
is_bezier = blender_keyframe.interpolation == BEZIER_INTERPOLATION
is_linear = blender_keyframe.interpolation == LINEAR_INTERPOLATION
is_constant = blender_keyframe.interpolation == CONSTANT_INTERPOLATION
if interpolation is None:
if is_bezier:
interpolation = CUBIC_INTERPOLATION
elif is_linear:
interpolation = LINEAR_INTERPOLATION
elif is_constant:
interpolation = STEP_INTERPOLATION
else:
interpolation = NEEDS_CONVERSION
return interpolation
else:
if is_bezier and interpolation != CUBIC_INTERPOLATION:
interpolation = NEEDS_CONVERSION
return interpolation
elif is_linear and interpolation != LINEAR_INTERPOLATION:
interpolation = NEEDS_CONVERSION
return interpolation
elif is_constant and interpolation != STEP_INTERPOLATION:
interpolation = NEEDS_CONVERSION
return interpolation
elif not is_bezier and not is_linear and not is_constant:
interpolation = NEEDS_CONVERSION
return interpolation
if interpolation is None:
interpolation = NEEDS_CONVERSION
return interpolation
def animate_convert_rotation_axis_angle(axis_angle):
"""Convert an axis angle to a quaternion rotation."""
q = Quaternion((axis_angle[1], axis_angle[2], axis_angle[3]), axis_angle[0])
return [q.x, q.y, q.z, q.w]
def animate_convert_rotation_euler(euler, rotation_mode):
"""Convert an euler angle to a quaternion rotation."""
rotation = Euler((euler[0], euler[1], euler[2]), rotation_mode).to_quaternion()
return [rotation.x, rotation.y, rotation.z, rotation.w]
def animate_convert_keys(key_list):
"""Convert Blender key frames to glTF time keys depending on the applied frames per second."""
times = []
for key in key_list:
times.append(key / bpy.context.scene.render.fps)
return times
def animate_gather_keys(export_settings, fcurve_list, interpolation):
"""
Merge and sort several key frames to one set.
If an interpolation conversion is needed, the sample key frames are created as well.
"""
keys = []
frame_start = bpy.context.scene.frame_start
frame_end = bpy.context.scene.frame_end
if interpolation == NEEDS_CONVERSION:
start = None
end = None
for blender_fcurve in fcurve_list:
if blender_fcurve is None:
continue
if start is None:
start = blender_fcurve.range()[0]
else:
start = min(start, blender_fcurve.range()[0])
if end is None:
end = blender_fcurve.range()[1]
else:
end = max(end, blender_fcurve.range()[1])
#
add_epsilon_keyframe = False
for blender_keyframe in blender_fcurve.keyframe_points:
if add_epsilon_keyframe:
key = blender_keyframe.co[0] - 0.001
if key not in keys:
keys.append(key)
add_epsilon_keyframe = False
if blender_keyframe.interpolation == CONSTANT_INTERPOLATION:
add_epsilon_keyframe = True
if add_epsilon_keyframe:
key = end - 0.001
if key not in keys:
keys.append(key)
key = start
while key <= end:
if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end):
keys.append(key)
key += export_settings[gltf2_blender_export_keys.FRAME_STEP]
keys.sort()
else:
for blender_fcurve in fcurve_list:
if blender_fcurve is None:
continue
for blender_keyframe in blender_fcurve.keyframe_points:
key = blender_keyframe.co[0]
if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end):
if key not in keys:
keys.append(key)
keys.sort()
return keys
def animate_location(export_settings, location, interpolation, node_type, node_name, action_name, matrix_correction,
matrix_basis):
"""Calculate/gather the key value pairs for location transformations."""
joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
if not joint_cache.get(node_name):
joint_cache[node_name] = {}
keys = animate_gather_keys(export_settings, location, interpolation)
times = animate_convert_keys(keys)
result = {}
result_in_tangent = {}
result_out_tangent = {}
keyframe_index = 0
for timeIndex, time in enumerate(times):
translation = [0.0, 0.0, 0.0]
in_tangent = [0.0, 0.0, 0.0]
out_tangent = [0.0, 0.0, 0.0]
if node_type == JOINT_NODE:
if joint_cache[node_name].get(keys[keyframe_index]):
translation, tmp_rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
else:
bpy.context.scene.frame_set(keys[keyframe_index])
matrix = matrix_correction * matrix_basis
translation, tmp_rotation, tmp_scale = matrix.decompose()
joint_cache[node_name][keys[keyframe_index]] = [translation, tmp_rotation, tmp_scale]
else:
channel_index = 0
for blender_fcurve in location:
if blender_fcurve is not None:
if interpolation == CUBIC_INTERPOLATION:
blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
translation[channel_index] = blender_key_frame.co[1]
if timeIndex == 0:
in_tangent_value = 0.0
else:
factor = 3.0 / (time - times[timeIndex - 1])
in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
if timeIndex == len(times) - 1:
out_tangent_value = 0.0
else:
factor = 3.0 / (times[timeIndex + 1] - time)
out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
in_tangent[channel_index] = in_tangent_value
out_tangent[channel_index] = out_tangent_value
else:
value = blender_fcurve.evaluate(keys[keyframe_index])
translation[channel_index] = value
channel_index += 1
# handle parent inverse
matrix = Matrix.Translation(translation)
matrix = matrix_correction * matrix
translation = matrix.to_translation()
translation = gltf2_blender_extract.convert_swizzle_location(translation, export_settings)
in_tangent = gltf2_blender_extract.convert_swizzle_location(in_tangent, export_settings)
out_tangent = gltf2_blender_extract.convert_swizzle_location(out_tangent, export_settings)
result[time] = translation
result_in_tangent[time] = in_tangent
result_out_tangent[time] = out_tangent
keyframe_index += 1
return result, result_in_tangent, result_out_tangent
def animate_rotation_axis_angle(export_settings, rotation_axis_angle, interpolation, node_type, node_name, action_name,
matrix_correction, matrix_basis):
"""Calculate/gather the key value pairs for axis angle transformations."""
joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
if not joint_cache.get(node_name):
joint_cache[node_name] = {}
keys = animate_gather_keys(export_settings, rotation_axis_angle, interpolation)
times = animate_convert_keys(keys)
result = {}
keyframe_index = 0
for time in times:
axis_angle_rotation = [1.0, 0.0, 0.0, 0.0]
if node_type == JOINT_NODE:
if joint_cache[node_name].get(keys[keyframe_index]):
tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
else:
bpy.context.scene.frame_set(keys[keyframe_index])
matrix = matrix_correction * matrix_basis
tmp_location, rotation, tmp_scale = matrix.decompose()
joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
else:
channel_index = 0
for blender_fcurve in rotation_axis_angle:
if blender_fcurve is not None:
value = blender_fcurve.evaluate(keys[keyframe_index])
axis_angle_rotation[channel_index] = value
channel_index += 1
rotation = animate_convert_rotation_axis_angle(axis_angle_rotation)
# handle parent inverse
rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2]))
matrix = rotation.to_matrix().to_4x4()
matrix = matrix_correction * matrix
rotation = matrix.to_quaternion()
# Bring back to internal Quaternion notation.
rotation = gltf2_blender_extract.convert_swizzle_rotation(
[rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
# Bring back to glTF Quaternion notation.
rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
result[time] = rotation
keyframe_index += 1
return result
def animate_rotation_euler(export_settings, rotation_euler, rotation_mode, interpolation, node_type, node_name,
action_name, matrix_correction, matrix_basis):
"""Calculate/gather the key value pairs for euler angle transformations."""
joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
if not joint_cache.get(node_name):
joint_cache[node_name] = {}
keys = animate_gather_keys(export_settings, rotation_euler, interpolation)
times = animate_convert_keys(keys)
result = {}
keyframe_index = 0
for time in times:
euler_rotation = [0.0, 0.0, 0.0]
if node_type == JOINT_NODE:
if joint_cache[node_name].get(keys[keyframe_index]):
tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
else:
bpy.context.scene.frame_set(keys[keyframe_index])
matrix = matrix_correction * matrix_basis
tmp_location, rotation, tmp_scale = matrix.decompose()
joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
else:
channel_index = 0
for blender_fcurve in rotation_euler:
if blender_fcurve is not None:
value = blender_fcurve.evaluate(keys[keyframe_index])
euler_rotation[channel_index] = value
channel_index += 1
rotation = animate_convert_rotation_euler(euler_rotation, rotation_mode)
# handle parent inverse
rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2]))
matrix = rotation.to_matrix().to_4x4()
matrix = matrix_correction * matrix
rotation = matrix.to_quaternion()
# Bring back to internal Quaternion notation.
rotation = gltf2_blender_extract.convert_swizzle_rotation(
[rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
# Bring back to glTF Quaternion notation.
rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
result[time] = rotation
keyframe_index += 1
return result
def animate_rotation_quaternion(export_settings, rotation_quaternion, interpolation, node_type, node_name, action_name,
matrix_correction, matrix_basis):
"""Calculate/gather the key value pairs for quaternion transformations."""
joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
if not joint_cache.get(node_name):
joint_cache[node_name] = {}
keys = animate_gather_keys(export_settings, rotation_quaternion, interpolation)
times = animate_convert_keys(keys)
result = {}
result_in_tangent = {}
result_out_tangent = {}
keyframe_index = 0
for timeIndex, time in enumerate(times):
rotation = [1.0, 0.0, 0.0, 0.0]
in_tangent = [1.0, 0.0, 0.0, 0.0]
out_tangent = [1.0, 0.0, 0.0, 0.0]
if node_type == JOINT_NODE:
if joint_cache[node_name].get(keys[keyframe_index]):
tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
else:
bpy.context.scene.frame_set(keys[keyframe_index])
matrix = matrix_correction * matrix_basis
tmp_location, rotation, tmp_scale = matrix.decompose()
joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
else:
channel_index = 0
for blender_fcurve in rotation_quaternion:
if blender_fcurve is not None:
if interpolation == CUBIC_INTERPOLATION:
blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
rotation[channel_index] = blender_key_frame.co[1]
if timeIndex == 0:
in_tangent_value = 0.0
else:
factor = 3.0 / (time - times[timeIndex - 1])
in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
if timeIndex == len(times) - 1:
out_tangent_value = 0.0
else:
factor = 3.0 / (times[timeIndex + 1] - time)
out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
in_tangent[channel_index] = in_tangent_value
out_tangent[channel_index] = out_tangent_value
else:
value = blender_fcurve.evaluate(keys[keyframe_index])
rotation[channel_index] = value
channel_index += 1
rotation = Quaternion((rotation[0], rotation[1], rotation[2], rotation[3]))
in_tangent = gltf2_blender_extract.convert_swizzle_rotation(in_tangent, export_settings)
out_tangent = gltf2_blender_extract.convert_swizzle_rotation(out_tangent, export_settings)
# handle parent inverse
matrix = rotation.to_matrix().to_4x4()
matrix = matrix_correction * matrix
rotation = matrix.to_quaternion()
# Bring back to internal Quaternion notation.
rotation = gltf2_blender_extract.convert_swizzle_rotation(
[rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
# Bring to glTF Quaternion notation.
rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
in_tangent = [in_tangent[1], in_tangent[2], in_tangent[3], in_tangent[0]]
out_tangent = [out_tangent[1], out_tangent[2], out_tangent[3], out_tangent[0]]
result[time] = rotation
result_in_tangent[time] = in_tangent
result_out_tangent[time] = out_tangent
keyframe_index += 1
return result, result_in_tangent, result_out_tangent
def animate_scale(export_settings, scale, interpolation, node_type, node_name, action_name, matrix_correction,
matrix_basis):
"""Calculate/gather the key value pairs for scale transformations."""
joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
if not joint_cache.get(node_name):
joint_cache[node_name] = {}
keys = animate_gather_keys(export_settings, scale, interpolation)
times = animate_convert_keys(keys)
result = {}
result_in_tangent = {}
result_out_tangent = {}
keyframe_index = 0
for timeIndex, time in enumerate(times):
scale_data = [1.0, 1.0, 1.0]
in_tangent = [0.0, 0.0, 0.0]
out_tangent = [0.0, 0.0, 0.0]
if node_type == JOINT_NODE:
if joint_cache[node_name].get(keys[keyframe_index]):
tmp_location, tmp_rotation, scale_data = joint_cache[node_name][keys[keyframe_index]]
else:
bpy.context.scene.frame_set(keys[keyframe_index])
matrix = matrix_correction * matrix_basis
tmp_location, tmp_rotation, scale_data = matrix.decompose()
joint_cache[node_name][keys[keyframe_index]] = [tmp_location, tmp_rotation, scale_data]
else:
channel_index = 0
for blender_fcurve in scale:
if blender_fcurve is not None:
if interpolation == CUBIC_INTERPOLATION:
blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
scale_data[channel_index] = blender_key_frame.co[1]
if timeIndex == 0:
in_tangent_value = 0.0
else:
factor = 3.0 / (time - times[timeIndex - 1])
in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
if timeIndex == len(times) - 1:
out_tangent_value = 0.0
else:
factor = 3.0 / (times[timeIndex + 1] - time)
out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
in_tangent[channel_index] = in_tangent_value
out_tangent[channel_index] = out_tangent_value
else:
value = blender_fcurve.evaluate(keys[keyframe_index])
scale_data[channel_index] = value
channel_index += 1
scale_data = gltf2_blender_extract.convert_swizzle_scale(scale_data, export_settings)
in_tangent = gltf2_blender_extract.convert_swizzle_scale(in_tangent, export_settings)
out_tangent = gltf2_blender_extract.convert_swizzle_scale(out_tangent, export_settings)
# handle parent inverse
matrix = Matrix()
matrix[0][0] = scale_data.x
matrix[1][1] = scale_data.y
matrix[2][2] = scale_data.z
matrix = matrix_correction * matrix
scale_data = matrix.to_scale()
result[time] = scale_data
result_in_tangent[time] = in_tangent
result_out_tangent[time] = out_tangent
keyframe_index += 1
return result, result_in_tangent, result_out_tangent
def animate_value(export_settings, value_parameter, interpolation,
node_type, node_name, matrix_correction, matrix_basis):
"""Calculate/gather the key value pairs for scalar anaimations."""
keys = animate_gather_keys(export_settings, value_parameter, interpolation)
times = animate_convert_keys(keys)
result = {}
result_in_tangent = {}
result_out_tangent = {}
keyframe_index = 0
for timeIndex, time in enumerate(times):
value_data = []
in_tangent = []
out_tangent = []
for blender_fcurve in value_parameter:
if blender_fcurve is not None:
if interpolation == CUBIC_INTERPOLATION:
blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
value_data.append(blender_key_frame.co[1])
if timeIndex == 0:
in_tangent_value = 0.0
else:
factor = 3.0 / (time - times[timeIndex - 1])
in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
if timeIndex == len(times) - 1:
out_tangent_value = 0.0
else:
factor = 3.0 / (times[timeIndex + 1] - time)
out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
in_tangent.append(in_tangent_value)
out_tangent.append(out_tangent_value)
else:
value = blender_fcurve.evaluate(keys[keyframe_index])
value_data.append(value)
result[time] = value_data
result_in_tangent[time] = in_tangent
result_out_tangent[time] = out_tangent
keyframe_index += 1
return result, result_in_tangent, result_out_tangent

View File

@ -1,455 +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.
#
# Imports
#
import bpy
from . import gltf2_blender_export_keys
from . import gltf2_blender_get
from ...io.com.gltf2_io_debug import print_console
from ..com.gltf2_blender_image import create_img_from_blender_image
from ...io.com import gltf2_io_image
#
# Globals
#
PREVIEW = 'PREVIEW'
GLOSSINESS = 'glTF Specular Glossiness'
ROUGHNESS = 'glTF Metallic Roughness'
#
# Functions
#
def filter_merge_image(export_settings, blender_image):
metallic_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Metallic")
roughness_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Roughness")
if metallic_channel < 0 and roughness_channel < 0:
return False
output = export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE]
if export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE) is None:
width = blender_image.image.size[0]
height = blender_image.image.size[1]
output = gltf2_io_image.create_img(width, height, r=1.0, g=1.0, b=1.0, a=1.0)
source = create_img_from_blender_image(blender_image.image)
if metallic_channel >= 0:
gltf2_io_image.copy_img_channel(output, dst_channel=2, src_image=source, src_channel=metallic_channel)
output.name = blender_image.image.name + output.name
if roughness_channel >= 0:
gltf2_io_image.copy_img_channel(output, dst_channel=1, src_image=source, src_channel=roughness_channel)
if metallic_channel < 0:
output.name = output.name + blender_image.image.name
return True
def filter_used_materials():
"""Gather and return all unfiltered, valid Blender materials."""
materials = []
for blender_material in bpy.data.materials:
if blender_material.node_tree and blender_material.use_nodes:
for currentNode in blender_material.node_tree.nodes:
if isinstance(currentNode, bpy.types.ShaderNodeGroup):
if currentNode.node_tree.name.startswith(ROUGHNESS):
materials.append(blender_material)
elif currentNode.node_tree.name.startswith(GLOSSINESS):
materials.append(blender_material)
elif isinstance(currentNode, bpy.types.ShaderNodeBsdfPrincipled):
materials.append(blender_material)
else:
materials.append(blender_material)
return materials
def filter_apply(export_settings):
"""
Gathers and filters the objects and assets to export.
Also filters out invalid, deleted and not exportable elements.
"""
filtered_objects = []
implicit_filtered_objects = []
for blender_object in bpy.data.objects:
if blender_object.users == 0:
continue
if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
continue
if not export_settings[gltf2_blender_export_keys.LAYERS] and not blender_object.layers[0]:
continue
filtered_objects.append(blender_object)
if export_settings[gltf2_blender_export_keys.SELECTED] or not export_settings[gltf2_blender_export_keys.LAYERS]:
current_parent = blender_object.parent
while current_parent:
if current_parent not in implicit_filtered_objects:
implicit_filtered_objects.append(current_parent)
current_parent = current_parent.parent
export_settings[gltf2_blender_export_keys.FILTERED_OBJECTS] = filtered_objects
# Meshes
filtered_meshes = {}
filtered_vertex_groups = {}
temporary_meshes = []
for blender_mesh in bpy.data.meshes:
if blender_mesh.users == 0:
continue
current_blender_mesh = blender_mesh
current_blender_object = None
skip = True
for blender_object in filtered_objects:
current_blender_object = blender_object
if current_blender_object.type != 'MESH':
continue
if current_blender_object.data == current_blender_mesh:
skip = False
use_auto_smooth = current_blender_mesh.use_auto_smooth
if use_auto_smooth:
if current_blender_mesh.shape_keys is None:
current_blender_object = current_blender_object.copy()
else:
use_auto_smooth = False
print_console('WARNING',
'Auto smooth and shape keys cannot be exported in parallel. '
'Falling back to non auto smooth.')
if export_settings[gltf2_blender_export_keys.APPLY] or use_auto_smooth:
# TODO: maybe add to new exporter
if not export_settings[gltf2_blender_export_keys.APPLY]:
current_blender_object.modifiers.clear()
if use_auto_smooth:
blender_modifier = current_blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT')
blender_modifier.split_angle = current_blender_mesh.auto_smooth_angle
blender_modifier.use_edge_angle = not current_blender_mesh.has_custom_normals
current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW)
temporary_meshes.append(current_blender_mesh)
break
if skip:
continue
filtered_meshes[blender_mesh.name] = current_blender_mesh
filtered_vertex_groups[blender_mesh.name] = current_blender_object.vertex_groups
# Curves
for blender_curve in bpy.data.curves:
if blender_curve.users == 0:
continue
current_blender_curve = blender_curve
current_blender_mesh = None
current_blender_object = None
skip = True
for blender_object in filtered_objects:
current_blender_object = blender_object
if current_blender_object.type not in ('CURVE', 'FONT'):
continue
if current_blender_object.data == current_blender_curve:
skip = False
current_blender_object = current_blender_object.copy()
if not export_settings[gltf2_blender_export_keys.APPLY]:
current_blender_object.modifiers.clear()
current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW)
temporary_meshes.append(current_blender_mesh)
break
if skip:
continue
filtered_meshes[blender_curve.name] = current_blender_mesh
filtered_vertex_groups[blender_curve.name] = current_blender_object.vertex_groups
#
export_settings[gltf2_blender_export_keys.FILTERED_MESHES] = filtered_meshes
export_settings[gltf2_blender_export_keys.FILTERED_VERTEX_GROUPS] = filtered_vertex_groups
export_settings[gltf2_blender_export_keys.TEMPORARY_MESHES] = temporary_meshes
#
filtered_materials = []
for blender_material in filter_used_materials():
if blender_material.users == 0:
continue
for mesh_name, blender_mesh in filtered_meshes.items():
for compare_blender_material in blender_mesh.materials:
if compare_blender_material == blender_material and blender_material not in filtered_materials:
filtered_materials.append(blender_material)
#
for blender_object in filtered_objects:
if blender_object.material_slots:
for blender_material_slot in blender_object.material_slots:
if blender_material_slot.link == 'DATA':
continue
if blender_material_slot.material not in filtered_materials:
filtered_materials.append(blender_material_slot.material)
export_settings[gltf2_blender_export_keys.FILTERED_MATERIALS] = filtered_materials
#
filtered_textures = []
filtered_merged_textures = []
temp_filtered_texture_names = []
for blender_material in filtered_materials:
if blender_material.node_tree and blender_material.use_nodes:
per_material_textures = []
for blender_node in blender_material.node_tree.nodes:
if is_valid_node(blender_node) and blender_node not in filtered_textures:
add_node = False
add_merged_node = False
for blender_socket in blender_node.outputs:
if blender_socket.is_linked:
for blender_link in blender_socket.links:
if isinstance(blender_link.to_node, bpy.types.ShaderNodeGroup):
is_roughness = blender_link.to_node.node_tree.name.startswith(ROUGHNESS)
is_glossiness = blender_link.to_node.node_tree.name.startswith(GLOSSINESS)
if is_roughness or is_glossiness:
add_node = True
break
elif isinstance(blender_link.to_node, bpy.types.ShaderNodeBsdfPrincipled):
add_node = True
break
elif isinstance(blender_link.to_node, bpy.types.ShaderNodeNormalMap):
add_node = True
break
elif isinstance(blender_link.to_node, bpy.types.ShaderNodeSeparateRGB):
add_merged_node = True
break
if add_node or add_merged_node:
break
if add_node:
filtered_textures.append(blender_node)
# TODO: Add displacement texture, as not stored in node tree.
if add_merged_node:
if len(per_material_textures) == 0:
filtered_merged_textures.append(per_material_textures)
per_material_textures.append(blender_node)
else:
for blender_texture_slot in blender_material.texture_slots:
if is_valid_texture_slot(blender_texture_slot) and \
blender_texture_slot not in filtered_textures and \
blender_texture_slot.name not in temp_filtered_texture_names:
accept = False
if blender_texture_slot.use_map_color_diffuse:
accept = True
if blender_texture_slot.use_map_ambient:
accept = True
if blender_texture_slot.use_map_emit:
accept = True
if blender_texture_slot.use_map_normal:
accept = True
if export_settings[gltf2_blender_export_keys.DISPLACEMENT]:
if blender_texture_slot.use_map_displacement:
accept = True
if accept:
filtered_textures.append(blender_texture_slot)
temp_filtered_texture_names.append(blender_texture_slot.name)
export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES] = filtered_textures
#
filtered_images = []
filtered_merged_images = []
filtered_images_use_alpha = {}
for blender_texture in filtered_textures:
if isinstance(blender_texture, bpy.types.ShaderNodeTexImage):
if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images:
filtered_images.append(blender_texture.image)
alpha_socket = blender_texture.outputs.get('Alpha')
if alpha_socket is not None and alpha_socket.is_linked:
filtered_images_use_alpha[blender_texture.image.name] = True
else:
if is_valid_image(blender_texture.texture.image) and blender_texture.texture.image not in filtered_images:
filtered_images.append(blender_texture.texture.image)
if blender_texture.use_map_alpha:
filtered_images_use_alpha[blender_texture.texture.image.name] = True
#
for per_material_textures in filtered_merged_textures:
export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE] = None
for blender_texture in per_material_textures:
if isinstance(blender_texture, bpy.types.ShaderNodeTexImage):
if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images:
filter_merge_image(export_settings, blender_texture)
img = export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE)
if img is not None:
filtered_merged_images.append(img)
export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES].append(img)
export_settings[gltf2_blender_export_keys.FILTERED_MERGED_IMAGES] = filtered_merged_images
export_settings[gltf2_blender_export_keys.FILTERED_IMAGES] = filtered_images
export_settings[gltf2_blender_export_keys.FILTERED_IMAGES_USE_ALPHA] = filtered_images_use_alpha
#
filtered_cameras = []
for blender_camera in bpy.data.cameras:
if blender_camera.users == 0:
continue
if export_settings[gltf2_blender_export_keys.SELECTED]:
if blender_camera not in filtered_objects:
continue
filtered_cameras.append(blender_camera)
export_settings[gltf2_blender_export_keys.FILTERED_CAMERAS] = filtered_cameras
#
#
filtered_lights = []
for blender_light in bpy.data.lamps:
if blender_light.users == 0:
continue
if export_settings[gltf2_blender_export_keys.SELECTED]:
if blender_light not in filtered_objects:
continue
if blender_light.type == 'HEMI':
continue
filtered_lights.append(blender_light)
export_settings[gltf2_blender_export_keys.FILTERED_LIGHTS] = filtered_lights
#
#
for implicit_object in implicit_filtered_objects:
if implicit_object not in filtered_objects:
filtered_objects.append(implicit_object)
#
#
#
group_index = {}
if export_settings[gltf2_blender_export_keys.SKINS]:
for blender_object in filtered_objects:
if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0:
continue
for blender_bone in blender_object.pose.bones:
group_index[blender_bone.name] = len(group_index)
export_settings[gltf2_blender_export_keys.GROUP_INDEX] = group_index
def is_valid_node(blender_node):
return isinstance(blender_node, bpy.types.ShaderNodeTexImage) and is_valid_image(blender_node.image)
def is_valid_image(image):
return image is not None and \
image.users != 0 and \
image.size[0] > 0 and \
image.size[1] > 0
def is_valid_texture_slot(blender_texture_slot):
return blender_texture_slot is not None and \
blender_texture_slot.texture and \
blender_texture_slot.texture.users != 0 and \
blender_texture_slot.texture.type == 'IMAGE' and \
blender_texture_slot.texture.image is not None and \
blender_texture_slot.texture.image.users != 0 and \
blender_texture_slot.texture.image.size[0] > 0 and \
blender_texture_slot.texture.image.size[1] > 0

View File

@ -1,89 +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 bpy
import typing
class Filter:
"""Base class for all node tree filter operations."""
def __call__(self, obj: bpy.types.Object):
return True
class ByName(Filter):
"""
Filter the objects by name.
example usage:
find_objects(FilterByName("Cube"))
"""
def __init__(self, name):
self.name = name
def __call__(self, obj: bpy.types.Object):
return obj.name == self.name
class ByDataType(Filter):
"""Filter the scene objects by their data type."""
def __init__(self, data_type: str):
self.type = data_type
def __call__(self, obj: bpy.types.Object):
return obj.type == self.type
class ByDataInstance(Filter):
"""Filter the scene objects by a specific ID instance."""
def __init__(self, data_instance: bpy.types.ID):
self.data = data_instance
def __call__(self, obj: bpy.types.Object):
return self.data == obj.data
def find_objects(object_filter: typing.Union[Filter, typing.Callable]):
"""
Find objects in the scene where the filter expression is true.
:param object_filter: should be a function(x: object) -> bool
:return: a list of shader nodes for which filter is true
"""
results = []
for obj in bpy.context.scene.objects:
if object_filter(obj):
results.append(obj)
return results
def find_objects_from(obj: bpy.types.Object, object_filter: typing.Union[Filter, typing.Callable]):
"""
Search for objects matching a filter function below a specified object.
:param obj: the starting point of the search
:param object_filter: a function(x: object) -> bool
:return: a list of objects which passed the filter
"""
results = []
if object_filter(obj):
results.append(obj)
for child in obj.children:
results += find_objects_from(child, object_filter)
return results

View File

@ -1,41 +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 typing
def chunks(lst: typing.Sequence[typing.Any], n: int) -> typing.List[typing.Any]:
"""
Generator that yields successive n sized chunks of the list l
:param lst: the list to be split
:param n: the length of the chunks
:return: a sublist of at most length n
"""
result = []
for i in range(0, len(lst), n):
result.append(lst[i:i + n])
return result
def unzip(*args: typing.Iterable[typing.Any]) -> typing.Iterable[typing.Iterable[typing.Any]]:
"""
Unzip the list. Inverse of the builtin zip
:param args: a list of lists or multiple list arguments
:return: a list of unzipped lists
"""
if len(args) == 1:
args = args[0]
return zip(*args)