diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2018-12-18 23:31:29 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2018-12-18 23:31:29 +0300 |
commit | bf867f50228505710c51eb7d76832415c36d9f74 (patch) | |
tree | 0bff37b29027c65fd0dcd9f333772c2619f80532 /io_scene_gltf2/blender | |
parent | 9aa6c8058b32675b2636632d6735f66baf6300b1 (diff) |
glTF exporter: various fixes & enhancement
* Fix some Yup conversions
* reading material from glTF node group material if exists
* Fix normal export
* Round transforms near 0 and 1
* Fix exporting from Edit mode
* Various image format management
Diffstat (limited to 'io_scene_gltf2/blender')
12 files changed, 123 insertions, 44 deletions
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py index 26eb6396..dd15ce2f 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_math.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py @@ -29,7 +29,11 @@ def list_to_mathutils(values: typing.List[float], data_path: str) -> typing.Unio """Transform a list to blender py object.""" target = get_target_property_name(data_path) - if target == 'location': + if target == 'delta_location': + return Vector(values) # TODO Should be Vector(values) - Vector(something)? + elif target == 'delta_rotation_euler': + return Euler(values).to_quaternion() # TODO Should be multiply(Euler(values).to_quaternion(), something)? + elif target == 'location': return Vector(values) elif target == 'rotation_axis_angle': angle = values[0] @@ -75,6 +79,8 @@ def swizzle_yup(v: typing.Union[Vector, Quaternion], data_path: str) -> typing.U """Manage Yup.""" target = get_target_property_name(data_path) swizzle_func = { + "delta_location": swizzle_yup_location, + "delta_rotation_euler": swizzle_yup_rotation, "location": swizzle_yup_location, "rotation_axis_angle": swizzle_yup_rotation, "rotation_euler": swizzle_yup_rotation, @@ -114,6 +120,8 @@ def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Ma """Manage transformations.""" target = get_target_property_name(data_path) transform_func = { + "delta_location": transform_location, + "delta_rotation_euler": transform_rotation, "location": transform_location, "rotation_axis_angle": transform_rotation, "rotation_euler": transform_rotation, @@ -157,3 +165,8 @@ def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector: """Transform value.""" return value + +def round_if_near(value: float, target: float) -> float: + """If value is very close to target, round to target.""" + return value if abs(value - target) > 2.0e-6 else target + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py index 1ddeb6a5..418453c2 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_export.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import bpy import sys import traceback @@ -25,6 +26,9 @@ from io_scene_gltf2.io.exp import gltf2_io_export def save(context, export_settings): """Start the glTF 2.0 export and saves to content either to a .gltf or .glb file.""" + if bpy.context.active_object is not None: + bpy.ops.object.mode_set(mode='OBJECT') + __notify_start(context) json, buffer = __export(export_settings) __write_file(json, buffer, export_settings) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py index 8762a90f..87c9d426 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -648,7 +648,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp bone_count = 0 - if vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: + if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: joint = [] weight = [] for group_element in vertex.groups: @@ -668,13 +668,15 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp # joint_index = 0 - modifiers_dict = {m.type: m for m in modifiers} - if "ARMATURE" in modifiers_dict: - armature = modifiers_dict["ARMATURE"].object - skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) - for index, j in enumerate(skin.joints): - if j.name == vertex_group_name: - joint_index = index + + if modifiers is not None: + modifiers_dict = {m.type: m for m in modifiers} + if "ARMATURE" in modifiers_dict: + armature = modifiers_dict["ARMATURE"].object + skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) + for index, j in enumerate(skin.joints): + if j.name == vertex_group_name: + joint_index = index joint_weight = group_element.weight diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py index fbe18323..2e4ac1d7 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py @@ -67,6 +67,8 @@ def __gather_path(channels: typing.Tuple[bpy.types.FCurve], ) -> str: target = channels[0].data_path.split('.')[-1] path = { + "delta_location": "translation", + "delta_rotation_euler": "rotation", "location": "translation", "rotation_axis_angle": "rotation", "rotation_euler": "rotation", diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py index 7562d8c2..6ef7fb0b 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -35,6 +35,8 @@ class Keyframe: def __get_target_len(self): length = { + "delta_location": 3, + "delta_rotation_euler": 3, "location": 3, "rotation_axis_angle": 4, "rotation_euler": 3, diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py index c94f1528..6846128d 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py @@ -107,37 +107,40 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve], target_datapath = channels[0].data_path - transform = Matrix.Identity(4) + transform = blender_object.matrix_parent_inverse + + isYup = export_settings[gltf2_blender_export_keys.YUP] if blender_object.type == "ARMATURE": bone = blender_object.path_resolve(get_target_object_path(target_datapath)) if isinstance(bone, bpy.types.PoseBone): - transform = bone.bone.matrix_local if bone.parent is not None: parent_transform = bone.parent.bone.matrix_local - transform = gltf2_blender_math.multiply(parent_transform.inverted(), transform) - # if not export_settings[gltf2_blender_export_keys.YUP]: - # transform = gltf2_blender_math.multiply(gltf2_blender_math.to_zup(), transform) + transform = gltf2_blender_math.multiply(transform, parent_transform.inverted()) + # if not isYup: + # transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_zup()) else: # only apply the y-up conversion to root bones, as child bones already are in the y-up space - if export_settings[gltf2_blender_export_keys.YUP]: - transform = gltf2_blender_math.multiply(gltf2_blender_math.to_yup(), transform) + if isYup: + transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_yup()) + local_transform = bone.bone.matrix_local + transform = gltf2_blender_math.multiply(transform, local_transform) values = [] for keyframe in keyframes: # Transform the data and extract value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform) - if export_settings[gltf2_blender_export_keys.YUP] and not blender_object.type == "ARMATURE": + if isYup and not blender_object.type == "ARMATURE": value = gltf2_blender_math.swizzle_yup(value, target_datapath) keyframe_value = gltf2_blender_math.mathutils_to_gltf(value) if keyframe.in_tangent is not None: in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform) - if export_settings[gltf2_blender_export_keys.YUP] and not blender_object.type == "ARMATURE": + if isYup and not blender_object.type == "ARMATURE": in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath) keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value if keyframe.out_tangent is not None: out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform) - if export_settings[gltf2_blender_export_keys.YUP] and not blender_object.type == "ARMATURE": + if isYup and not blender_object.type == "ARMATURE": out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath) keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent) values += keyframe_value diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py index 3740fefa..bfbc03ed 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py @@ -156,7 +156,7 @@ def __get_blender_actions(blender_object: bpy.types.Object for strip in track.strips: blender_actions.append(strip.action) - if blender_object.type == "MESH"\ + if blender_object.type == "MESH" \ and blender_object.data is not None \ and blender_object.data.shape_keys is not None \ and blender_object.data.shape_keys.animation_data is not None: diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index 04b67da9..427f07ce 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -41,7 +41,7 @@ def gather_material(blender_material, export_settings): alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings), alpha_mode=__gather_alpha_mode(blender_material, export_settings), double_sided=__gather_double_sided(blender_material, export_settings), - emissive_factor=__gather_emmissive_factor(blender_material, export_settings), + emissive_factor=__gather_emissive_factor(blender_material, export_settings), emissive_texture=__gather_emissive_texture(blender_material, export_settings), extensions=__gather_extensions(blender_material, export_settings), extras=__gather_extras(blender_material, export_settings), @@ -94,8 +94,10 @@ def __gather_double_sided(blender_material, export_settings): return None -def __gather_emmissive_factor(blender_material, export_settings): +def __gather_emissive_factor(blender_material, export_settings): emissive_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive") + if emissive_socket is None: + emissive_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "EmissiveFactor") if isinstance(emissive_socket, bpy.types.NodeSocket) and not emissive_socket.is_linked: return list(emissive_socket.default_value)[0:3] return None @@ -103,6 +105,8 @@ def __gather_emmissive_factor(blender_material, export_settings): def __gather_emissive_texture(blender_material, export_settings): emissive = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive") + if emissive is None: + emissive = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Emissive") return gltf2_blender_gather_texture_info.gather_texture_info((emissive,), export_settings) @@ -127,15 +131,19 @@ def __gather_name(blender_material, export_settings): def __gather_normal_texture(blender_material, export_settings): normal = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Normal") + if normal is None: + normal = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Normal") return gltf2_blender_gather_material_normal_texture_info_class.gather_material_normal_texture_info_class( (normal,), export_settings) def __gather_occlusion_texture(blender_material, export_settings): - emissive = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion") + occlusion = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion") + if occlusion is None: + occlusion = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Occlusion") return gltf2_blender_gather_material_occlusion_texture_info_class.gather_material_occlusion_texture_info_class( - (emissive,), + (occlusion,), export_settings) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py index 16b991f0..2a6315bf 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py @@ -46,6 +46,8 @@ def __gather_base_color_factor(blender_material, export_settings): base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color") if base_color_socket is None: base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColorFactor") if isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked: return list(base_color_socket.default_value) return None @@ -54,6 +56,8 @@ def __gather_base_color_texture(blender_material, export_settings): base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color") if base_color_socket is None: base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColor") return gltf2_blender_gather_texture_info.gather_texture_info((base_color_socket,), export_settings) @@ -67,6 +71,8 @@ def __gather_extras(blender_material, export_settings): def __gather_metallic_factor(blender_material, export_settings): metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") + if metallic_socket is None: + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicFactor") if isinstance(metallic_socket, bpy.types.NodeSocket) and not metallic_socket.is_linked: return metallic_socket.default_value return None @@ -78,6 +84,8 @@ def __gather_metallic_roughness_texture(blender_material, export_settings): if metallic_socket is None and roughness_socket is None: metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "MetallicRoughness") + if metallic_roughness is None: + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness") texture_input = (metallic_roughness,) else: texture_input = (metallic_socket, roughness_socket) @@ -87,6 +95,8 @@ def __gather_metallic_roughness_texture(blender_material, export_settings): def __gather_roughness_factor(blender_material, export_settings): roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") + if roughness_socket is None: + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "RoughnessFactor") if isinstance(roughness_socket, bpy.types.NodeSocket) and not roughness_socket.is_linked: return roughness_socket.default_value return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index 0913d464..f6533c14 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -17,6 +17,7 @@ import bpy from mathutils import Quaternion from . import gltf2_blender_export_keys +from io_scene_gltf2.blender.com import gltf2_blender_math from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras @@ -50,16 +51,17 @@ def gather_node(blender_object, export_settings): ) node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings) - if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS]: - correction_node = __get_correction_node(blender_object, export_settings) - correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]} - del node.extensions["KHR_lights_punctual"] - node.children.append(correction_node) - if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]: - correction_node = __get_correction_node(blender_object, export_settings) - correction_node.camera = node.camera - node.children.append(correction_node) - node.camera = None + if export_settings[gltf2_blender_export_keys.YUP]: + if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS]: + correction_node = __get_correction_node(blender_object, export_settings) + correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]} + del node.extensions["KHR_lights_punctual"] + node.children.append(correction_node) + if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]: + correction_node = __get_correction_node(blender_object, export_settings) + correction_node.camera = node.camera + node.children.append(correction_node) + node.camera = None return node @@ -153,8 +155,18 @@ def __gather_mesh(blender_object, export_settings): modifiers = None if export_settings[gltf2_blender_export_keys.APPLY]: + auto_smooth = blender_object.data.use_auto_smooth + if auto_smooth: + blender_object = blender_object.copy() + edge_split = blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT') + edge_split.split_angle = blender_object.data.auto_smooth_angle + edge_split.use_edge_angle = not blender_object.data.has_custom_normals + blender_mesh = blender_object.to_mesh(bpy.context.depsgraph, True) skip_filter = True + + if auto_smooth: + bpy.data.objects.remove(blender_object) else: blender_mesh = blender_object.data skip_filter = False @@ -179,6 +191,12 @@ def __gather_trans_rot_scale(blender_object, export_settings): trans = -gltf2_blender_extract.convert_swizzle_location( blender_object.instance_collection.instance_offset, export_settings) translation, rotation, scale = (None, None, None) + trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \ + gltf2_blender_math.round_if_near(trans[2], 0.0) + rot[0], rot[1], rot[2], rot[3] = gltf2_blender_math.round_if_near(rot[0], 0.0), gltf2_blender_math.round_if_near(rot[1], 0.0), \ + gltf2_blender_math.round_if_near(rot[2], 0.0), gltf2_blender_math.round_if_near(rot[3], 1.0) + sca[0], sca[1], sca[2] = gltf2_blender_math.round_if_near(sca[0], 1.0), gltf2_blender_math.round_if_near(sca[1], 1.0), \ + gltf2_blender_math.round_if_near(sca[2], 1.0) if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0: translation = [trans[0], trans[1], trans[2]] if rot[0] != 0.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 1.0: diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py index 6d025980..7801190b 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py @@ -43,20 +43,38 @@ def get_socket_or_texture_slot(blender_material: bpy.types.Material, name: str): :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise """ if blender_material.node_tree and blender_material.use_nodes: - i = [input for input in blender_material.node_tree.inputs] - o = [output for output in blender_material.node_tree.outputs] - nodes = [node for node in blender_material.node_tree.nodes] + #i = [input for input in blender_material.node_tree.inputs] + #o = [output for output in blender_material.node_tree.outputs] if name == "Emissive": - nodes = filter(lambda n: isinstance(n, bpy.types.ShaderNodeEmission), nodes) + type = bpy.types.ShaderNodeEmission name = "Color" else: - nodes = filter(lambda n: isinstance(n, bpy.types.ShaderNodeBsdfPrincipled), nodes) + type = bpy.types.ShaderNodeBsdfPrincipled + nodes = [n for n in blender_material.node_tree.nodes if isinstance(n, type)] inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], []) - if not inputs: - return None - return inputs[0] + if inputs: + return inputs[0] + + return None + + +def get_socket_or_texture_slot_old(blender_material: bpy.types.Material, name: str): + """ + For a given material input name, retrieve the corresponding node tree socket in the special glTF Metallic Roughness nodes (which might be deprecated?). + + :param blender_material: a blender material for which to get the socket/slot + :param name: the name of the socket/slot + :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise + """ + if blender_material.node_tree and blender_material.use_nodes: + nodes = [n for n in blender_material.node_tree.nodes if \ + isinstance(n, bpy.types.ShaderNodeGroup) and \ + n.node_tree.name.startswith('glTF Metallic Roughness')] + inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], []) + if inputs: + return inputs[0] return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py index 6561567e..65efafef 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py @@ -144,8 +144,7 @@ class GlTF2Exporter: :return: """ for image in self.__images: - dst_path = output_path + image.name + ".png" - + 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. |