FBX IO: Only import the first animation curve per channel #104866
@ -5,7 +5,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "FBX format",
|
"name": "FBX format",
|
||||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
||||||
"version": (5, 7, 0),
|
"version": (5, 7, 1),
|
||||||
"blender": (3, 6, 0),
|
"blender": (3, 6, 0),
|
||||||
"location": "File > Import-Export",
|
"location": "File > Import-Export",
|
||||||
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
||||||
|
@ -635,7 +635,8 @@ def _combine_curve_keyframe_times(times_and_values_tuples, initial_values):
|
|||||||
interpolating the keyframe values is a TODO."""
|
interpolating the keyframe values is a TODO."""
|
||||||
if len(times_and_values_tuples) == 1:
|
if len(times_and_values_tuples) == 1:
|
||||||
# Nothing to do when there is only a single curve.
|
# Nothing to do when there is only a single curve.
|
||||||
return times_and_values_tuples[0]
|
times, values = times_and_values_tuples[0]
|
||||||
|
return times, [values]
|
||||||
|
|
||||||
all_times = [t[0] for t in times_and_values_tuples]
|
all_times = [t[0] for t in times_and_values_tuples]
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
'name': 'glTF 2.0 format',
|
'name': 'glTF 2.0 format',
|
||||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||||
"version": (4, 0, 7),
|
"version": (4, 0, 15),
|
||||||
'blender': (4, 0, 0),
|
'blender': (4, 0, 0),
|
||||||
'location': 'File > Import-Export',
|
'location': 'File > Import-Export',
|
||||||
'description': 'Import-Export as glTF 2.0',
|
'description': 'Import-Export as glTF 2.0',
|
||||||
|
@ -119,7 +119,8 @@ def get_numpy_type(attribute_component_type):
|
|||||||
def get_attribute_type(component_type, data_type):
|
def get_attribute_type(component_type, data_type):
|
||||||
if gltf2_io_constants.DataType.num_elements(data_type) == 1:
|
if gltf2_io_constants.DataType.num_elements(data_type) == 1:
|
||||||
return {
|
return {
|
||||||
gltf2_io_constants.ComponentType.Float: "FLOAT"
|
gltf2_io_constants.ComponentType.Float: "FLOAT",
|
||||||
|
gltf2_io_constants.ComponentType.UnsignedByte: "INT" # What is the best for compatibility?
|
||||||
}[component_type]
|
}[component_type]
|
||||||
elif gltf2_io_constants.DataType.num_elements(data_type) == 2:
|
elif gltf2_io_constants.DataType.num_elements(data_type) == 2:
|
||||||
return {
|
return {
|
||||||
@ -132,7 +133,8 @@ def get_attribute_type(component_type, data_type):
|
|||||||
elif gltf2_io_constants.DataType.num_elements(data_type) == 4:
|
elif gltf2_io_constants.DataType.num_elements(data_type) == 4:
|
||||||
return {
|
return {
|
||||||
gltf2_io_constants.ComponentType.Float: "FLOAT_COLOR",
|
gltf2_io_constants.ComponentType.Float: "FLOAT_COLOR",
|
||||||
gltf2_io_constants.ComponentType.UnsignedShort: "BYTE_COLOR"
|
gltf2_io_constants.ComponentType.UnsignedShort: "BYTE_COLOR",
|
||||||
|
gltf2_io_constants.ComponentType.UnsignedByte: "BYTE_COLOR" # What is the best for compatibility?
|
||||||
}[component_type]
|
}[component_type]
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
@ -45,10 +45,19 @@ def is_bone_anim_channel(data_path: str) -> bool:
|
|||||||
|
|
||||||
def get_sk_exported(key_blocks):
|
def get_sk_exported(key_blocks):
|
||||||
return [
|
return [
|
||||||
key_block
|
k
|
||||||
for key_block in key_blocks
|
for k in key_blocks
|
||||||
if not skip_sk(key_block)
|
if not skip_sk(key_blocks, k)
|
||||||
]
|
]
|
||||||
|
|
||||||
def skip_sk(k):
|
def skip_sk(key_blocks, k):
|
||||||
return k == k.relative_key or k.mute
|
# Do not export:
|
||||||
|
# - if muted
|
||||||
|
# - if relative key is SK itself (this avoid exporting Basis too if user didn't change order)
|
||||||
|
# - the Basis (the first SK of the list)
|
||||||
|
return k == k.relative_key \
|
||||||
|
or k.mute \
|
||||||
|
or is_first_index(key_blocks, k) is True
|
||||||
|
|
||||||
|
def is_first_index(key_blocks, k):
|
||||||
|
return key_blocks[0].name == k.name
|
||||||
|
@ -30,7 +30,7 @@ def gather_animation_fcurves_channels(
|
|||||||
custom_range = (blender_action.frame_start, blender_action.frame_end)
|
custom_range = (blender_action.frame_start, blender_action.frame_end)
|
||||||
|
|
||||||
channels = []
|
channels = []
|
||||||
for chan in [chan for chan in channels_to_perform.values() if len(chan['properties']) != 0]:
|
for chan in [chan for chan in channels_to_perform.values() if len(chan['properties']) != 0 and chan['type'] != "EXTRA"]:
|
||||||
for channel_group in chan['properties'].values():
|
for channel_group in chan['properties'].values():
|
||||||
channel = __gather_animation_fcurve_channel(chan['obj_uuid'], channel_group, chan['bone'], custom_range, export_settings)
|
channel = __gather_animation_fcurve_channel(chan['obj_uuid'], channel_group, chan['bone'], custom_range, export_settings)
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
@ -73,10 +73,13 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
target = get_object_from_datapath(blender_object, object_path)
|
target = get_object_from_datapath(blender_object, object_path)
|
||||||
|
if blender_object.type == "ARMATURE" and fcurve.data_path.startswith("pose.bones["):
|
||||||
type_ = "BONE"
|
type_ = "BONE"
|
||||||
|
else:
|
||||||
|
type_ = "EXTRA"
|
||||||
if blender_object.type == "MESH" and object_path.startswith("key_blocks"):
|
if blender_object.type == "MESH" and object_path.startswith("key_blocks"):
|
||||||
shape_key = blender_object.data.shape_keys.path_resolve(object_path)
|
shape_key = blender_object.data.shape_keys.path_resolve(object_path)
|
||||||
if skip_sk(shape_key):
|
if skip_sk(blender_object.data.shape_keys.key_blocks, shape_key):
|
||||||
continue
|
continue
|
||||||
target = blender_object.data.shape_keys
|
target = blender_object.data.shape_keys
|
||||||
type_ = "SK"
|
type_ = "SK"
|
||||||
@ -86,7 +89,7 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
|
|||||||
if blender_object.type == "MESH":
|
if blender_object.type == "MESH":
|
||||||
try:
|
try:
|
||||||
shape_key = blender_object.data.shape_keys.path_resolve(object_path)
|
shape_key = blender_object.data.shape_keys.path_resolve(object_path)
|
||||||
if skip_sk(shape_key):
|
if skip_sk(blender_object.data.shape_keys.key_blocks, shape_key):
|
||||||
continue
|
continue
|
||||||
target = blender_object.data.shape_keys
|
target = blender_object.data.shape_keys
|
||||||
type_ = "SK"
|
type_ = "SK"
|
||||||
@ -181,7 +184,7 @@ def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender
|
|||||||
shapekeys_idx = {}
|
shapekeys_idx = {}
|
||||||
cpt_sk = 0
|
cpt_sk = 0
|
||||||
for sk in blender_object.data.shape_keys.key_blocks:
|
for sk in blender_object.data.shape_keys.key_blocks:
|
||||||
if skip_sk(sk):
|
if skip_sk(blender_object.data.shape_keys.key_blocks, sk):
|
||||||
continue
|
continue
|
||||||
shapekeys_idx[sk.name] = cpt_sk
|
shapekeys_idx[sk.name] = cpt_sk
|
||||||
cpt_sk += 1
|
cpt_sk += 1
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import typing
|
import typing
|
||||||
from .....blender.com.gltf2_blender_data_path import skip_sk
|
from .....blender.com.gltf2_blender_data_path import get_sk_exported
|
||||||
from ....com.gltf2_blender_data_path import get_target_object_path
|
from ....com.gltf2_blender_data_path import get_target_object_path
|
||||||
from ...gltf2_blender_gather_cache import cached
|
from ...gltf2_blender_gather_cache import cached
|
||||||
from ..gltf2_blender_gather_keyframes import Keyframe
|
from ..gltf2_blender_gather_keyframes import Keyframe
|
||||||
@ -165,9 +165,7 @@ def __gather_non_keyed_values(
|
|||||||
if object_path:
|
if object_path:
|
||||||
shapekeys_idx = {}
|
shapekeys_idx = {}
|
||||||
cpt_sk = 0
|
cpt_sk = 0
|
||||||
for sk in blender_object.data.shape_keys.key_blocks:
|
for sk in get_sk_exported(blender_object.data.shape_keys.key_blocks):
|
||||||
if skip_sk(sk):
|
|
||||||
continue
|
|
||||||
shapekeys_idx[cpt_sk] = sk.name
|
shapekeys_idx[cpt_sk] = sk.name
|
||||||
cpt_sk += 1
|
cpt_sk += 1
|
||||||
|
|
||||||
|
@ -294,6 +294,8 @@ def gather_action_animations( obj_uuid: int,
|
|||||||
channel = gather_sampled_object_channel(obj_uuid, prop, blender_action.name, True, get_gltf_interpolation("LINEAR"), export_settings)
|
channel = gather_sampled_object_channel(obj_uuid, prop, blender_action.name, True, get_gltf_interpolation("LINEAR"), export_settings)
|
||||||
elif type_ == "SK":
|
elif type_ == "SK":
|
||||||
channel = gather_sampled_sk_channel(obj_uuid, blender_action.name, export_settings)
|
channel = gather_sampled_sk_channel(obj_uuid, blender_action.name, export_settings)
|
||||||
|
elif type_ == "EXTRA":
|
||||||
|
channel = None
|
||||||
else:
|
else:
|
||||||
print("Type unknown. Should not happen")
|
print("Type unknown. Should not happen")
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
|
|||||||
sk_name = child.data.shape_keys.path_resolve(get_target_object_path(sk_c.data_path)).name
|
sk_name = child.data.shape_keys.path_resolve(get_target_object_path(sk_c.data_path)).name
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
if skip_sk(child.data.shape_keys.key_blocks[sk_name]):
|
if skip_sk(child.data.shape_keys.key_blocks, child.data.shape_keys.key_blocks[sk_name]):
|
||||||
continue
|
continue
|
||||||
idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c))
|
idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c))
|
||||||
existing_idx = dict(idx_channel_mapping)
|
existing_idx = dict(idx_channel_mapping)
|
||||||
|
@ -6,6 +6,7 @@ import math
|
|||||||
import bpy
|
import bpy
|
||||||
from mathutils import Matrix, Quaternion, Vector
|
from mathutils import Matrix, Quaternion, Vector
|
||||||
|
|
||||||
|
from ...io.com.gltf2_io_debug import print_console
|
||||||
from ...io.com import gltf2_io
|
from ...io.com import gltf2_io
|
||||||
from ...io.com import gltf2_io_extensions
|
from ...io.com import gltf2_io_extensions
|
||||||
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
|
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
@ -181,7 +182,9 @@ def __gather_mesh(vnode, blender_object, export_settings):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Be sure that object is valid (no NaN for example)
|
# Be sure that object is valid (no NaN for example)
|
||||||
blender_object.data.validate()
|
res = blender_object.data.validate()
|
||||||
|
if res is True:
|
||||||
|
print_console("WARNING", "Mesh " + blender_object.data.name + " is not valid, and may be exported wrongly")
|
||||||
|
|
||||||
modifiers = blender_object.modifiers
|
modifiers = blender_object.modifiers
|
||||||
if len(modifiers) == 0:
|
if len(modifiers) == 0:
|
||||||
|
@ -35,7 +35,7 @@ def gather_primitive_attributes(blender_primitive, export_settings):
|
|||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
def array_to_accessor(array, component_type, data_type, include_max_and_min=False):
|
def array_to_accessor(array, component_type, data_type, include_max_and_min=False, normalized=None):
|
||||||
|
|
||||||
amax = None
|
amax = None
|
||||||
amin = None
|
amin = None
|
||||||
@ -53,7 +53,7 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
|
|||||||
max=amax,
|
max=amax,
|
||||||
min=amin,
|
min=amin,
|
||||||
name=None,
|
name=None,
|
||||||
normalized=None,
|
normalized=normalized,
|
||||||
sparse=None,
|
sparse=None,
|
||||||
type=data_type,
|
type=data_type,
|
||||||
)
|
)
|
||||||
@ -183,6 +183,7 @@ def __gather_attribute(blender_primitive, attribute, export_settings):
|
|||||||
data['data'],
|
data['data'],
|
||||||
component_type=data['component_type'],
|
component_type=data['component_type'],
|
||||||
data_type=data['data_type'],
|
data_type=data['data_type'],
|
||||||
include_max_and_min=include_max_and_mins.get(attribute, False)
|
include_max_and_min=include_max_and_mins.get(attribute, False),
|
||||||
|
normalized=data.get('normalized')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import numpy as np
|
|||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from ...blender.com.gltf2_blender_data_path import get_sk_exported
|
from ...blender.com.gltf2_blender_data_path import get_sk_exported
|
||||||
from ...io.com.gltf2_io_debug import print_console
|
from ...io.com.gltf2_io_debug import print_console
|
||||||
from ...io.com.gltf2_io_constants import NORMALS_ROUNDING_DIGIT
|
from ...io.com.gltf2_io_constants import ROUNDING_DIGIT
|
||||||
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
|
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
from ...io.com import gltf2_io_constants
|
from ...io.com import gltf2_io_constants
|
||||||
from ..com import gltf2_blender_conversion
|
from ..com import gltf2_blender_conversion
|
||||||
@ -357,9 +357,16 @@ class PrimitiveCreator:
|
|||||||
def primitive_split(self):
|
def primitive_split(self):
|
||||||
# Calculate triangles and sort them into primitives.
|
# Calculate triangles and sort them into primitives.
|
||||||
|
|
||||||
|
try:
|
||||||
self.blender_mesh.calc_loop_triangles()
|
self.blender_mesh.calc_loop_triangles()
|
||||||
loop_indices = np.empty(len(self.blender_mesh.loop_triangles) * 3, dtype=np.uint32)
|
loop_indices = np.empty(len(self.blender_mesh.loop_triangles) * 3, dtype=np.uint32)
|
||||||
self.blender_mesh.loop_triangles.foreach_get('loops', loop_indices)
|
self.blender_mesh.loop_triangles.foreach_get('loops', loop_indices)
|
||||||
|
except:
|
||||||
|
# For some not valid meshes, we can't get loops without errors
|
||||||
|
# We already displayed a Warning message after validate() check, so here
|
||||||
|
# we can return without a new one
|
||||||
|
self.prim_indices = {}
|
||||||
|
return
|
||||||
|
|
||||||
self.prim_indices = {} # maps material index to TRIANGLES-style indices into dots
|
self.prim_indices = {} # maps material index to TRIANGLES-style indices into dots
|
||||||
|
|
||||||
@ -715,7 +722,7 @@ class PrimitiveCreator:
|
|||||||
|
|
||||||
self.normals = self.normals.reshape(len(self.blender_mesh.loops), 3)
|
self.normals = self.normals.reshape(len(self.blender_mesh.loops), 3)
|
||||||
|
|
||||||
self.normals = np.round(self.normals, NORMALS_ROUNDING_DIGIT)
|
self.normals = np.round(self.normals, ROUNDING_DIGIT)
|
||||||
# Force normalization of normals in case some normals are not (why ?)
|
# Force normalization of normals in case some normals are not (why ?)
|
||||||
PrimitiveCreator.normalize_vecs(self.normals)
|
PrimitiveCreator.normalize_vecs(self.normals)
|
||||||
|
|
||||||
@ -723,7 +730,7 @@ class PrimitiveCreator:
|
|||||||
for key_block in key_blocks:
|
for key_block in key_blocks:
|
||||||
ns = np.array(key_block.normals_split_get(), dtype=np.float32)
|
ns = np.array(key_block.normals_split_get(), dtype=np.float32)
|
||||||
ns = ns.reshape(len(self.blender_mesh.loops), 3)
|
ns = ns.reshape(len(self.blender_mesh.loops), 3)
|
||||||
ns = np.round(ns, NORMALS_ROUNDING_DIGIT)
|
ns = np.round(ns, ROUNDING_DIGIT)
|
||||||
self.morph_normals.append(ns)
|
self.morph_normals.append(ns)
|
||||||
|
|
||||||
# Transform for skinning
|
# Transform for skinning
|
||||||
@ -782,6 +789,7 @@ class PrimitiveCreator:
|
|||||||
self.tangents = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32)
|
self.tangents = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32)
|
||||||
self.blender_mesh.loops.foreach_get('tangent', self.tangents)
|
self.blender_mesh.loops.foreach_get('tangent', self.tangents)
|
||||||
self.tangents = self.tangents.reshape(len(self.blender_mesh.loops), 3)
|
self.tangents = self.tangents.reshape(len(self.blender_mesh.loops), 3)
|
||||||
|
self.tangents = np.round(self.tangents, ROUNDING_DIGIT)
|
||||||
|
|
||||||
# Transform for skinning
|
# Transform for skinning
|
||||||
if self.armature and self.blender_object:
|
if self.armature and self.blender_object:
|
||||||
@ -789,6 +797,7 @@ class PrimitiveCreator:
|
|||||||
tangent_transform = apply_matrix.to_quaternion().to_matrix()
|
tangent_transform = apply_matrix.to_quaternion().to_matrix()
|
||||||
self.tangents = PrimitiveCreator.apply_mat_to_all(tangent_transform, self.tangents)
|
self.tangents = PrimitiveCreator.apply_mat_to_all(tangent_transform, self.tangents)
|
||||||
PrimitiveCreator.normalize_vecs(self.tangents)
|
PrimitiveCreator.normalize_vecs(self.tangents)
|
||||||
|
self.tangents = np.round(self.tangents, ROUNDING_DIGIT)
|
||||||
|
|
||||||
if self.export_settings['gltf_yup']:
|
if self.export_settings['gltf_yup']:
|
||||||
PrimitiveCreator.zup2yup(self.tangents)
|
PrimitiveCreator.zup2yup(self.tangents)
|
||||||
|
@ -308,11 +308,19 @@ def previous_node(socket):
|
|||||||
return prev_socket.node
|
return prev_socket.node
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#TODOExt is this the same as __get_tex_from_socket from gather_image ?
|
def get_tex_from_socket(socket):
|
||||||
def has_image_node_from_socket(socket):
|
|
||||||
result = gltf2_blender_search_node_tree.from_socket(
|
result = gltf2_blender_search_node_tree.from_socket(
|
||||||
socket,
|
socket,
|
||||||
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
||||||
if not result:
|
if not result:
|
||||||
return False
|
return None
|
||||||
return True
|
if result[0].shader_node.image is None:
|
||||||
|
return None
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
def has_image_node_from_socket(socket):
|
||||||
|
return get_tex_from_socket(socket) is not None
|
||||||
|
|
||||||
|
def image_tex_is_valid_from_socket(socket):
|
||||||
|
res = get_tex_from_socket(socket)
|
||||||
|
return res is not None and res.shader_node.image is not None and res.shader_node.image.channels != 0
|
||||||
|
@ -55,6 +55,7 @@ def export_clearcoat(blender_material, export_settings):
|
|||||||
clearcoat_texture, clearcoat_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
clearcoat_texture, clearcoat_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
clearcoat_socket,
|
clearcoat_socket,
|
||||||
clearcoat_roughness_slots,
|
clearcoat_roughness_slots,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
clearcoat_extension['clearcoatTexture'] = clearcoat_texture
|
clearcoat_extension['clearcoatTexture'] = clearcoat_texture
|
||||||
@ -64,6 +65,7 @@ def export_clearcoat(blender_material, export_settings):
|
|||||||
clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
clearcoat_roughness_socket,
|
clearcoat_roughness_socket,
|
||||||
clearcoat_roughness_slots,
|
clearcoat_roughness_slots,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
|
clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
|
||||||
|
@ -52,7 +52,7 @@ def export_emission_texture(blender_material, export_settings):
|
|||||||
emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
|
emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
|
||||||
if emissive is None:
|
if emissive is None:
|
||||||
emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
|
emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
|
||||||
emissive_texture, use_actives_uvmap_emissive, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
|
emissive_texture, use_actives_uvmap_emissive, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), (), export_settings)
|
||||||
return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
|
return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
|
||||||
|
|
||||||
def export_emission_strength_extension(emissive_factor, export_settings):
|
def export_emission_strength_extension(emissive_factor, export_settings):
|
||||||
|
@ -40,6 +40,7 @@ def export_sheen(blender_material, export_settings):
|
|||||||
original_sheenColor_texture, original_sheenColor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
original_sheenColor_texture, original_sheenColor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
sheenColor_socket,
|
sheenColor_socket,
|
||||||
(sheenColor_socket,),
|
(sheenColor_socket,),
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
sheen_extension['sheenColorTexture'] = original_sheenColor_texture
|
sheen_extension['sheenColorTexture'] = original_sheenColor_texture
|
||||||
@ -64,6 +65,7 @@ def export_sheen(blender_material, export_settings):
|
|||||||
original_sheenRoughness_texture, original_sheenRoughness_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
original_sheenRoughness_texture, original_sheenRoughness_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
sheenRoughness_socket,
|
sheenRoughness_socket,
|
||||||
(sheenRoughness_socket,),
|
(sheenRoughness_socket,),
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture
|
sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture
|
||||||
|
@ -7,7 +7,8 @@ from .....io.com.gltf2_io_extensions import Extension
|
|||||||
from .....io.com.gltf2_io_constants import GLTF_IOR
|
from .....io.com.gltf2_io_constants import GLTF_IOR
|
||||||
from ....exp import gltf2_blender_get
|
from ....exp import gltf2_blender_get
|
||||||
from ....com.gltf2_blender_default import BLENDER_SPECULAR, BLENDER_SPECULAR_TINT
|
from ....com.gltf2_blender_default import BLENDER_SPECULAR, BLENDER_SPECULAR_TINT
|
||||||
from ...material import gltf2_blender_gather_texture_info
|
from ...material.gltf2_blender_gather_texture_info import gather_texture_info
|
||||||
|
from ...gltf2_blender_get import image_tex_is_valid_from_socket
|
||||||
|
|
||||||
def export_original_specular(blender_material, export_settings):
|
def export_original_specular(blender_material, export_settings):
|
||||||
specular_extension = {}
|
specular_extension = {}
|
||||||
@ -36,9 +37,10 @@ def export_original_specular(blender_material, export_settings):
|
|||||||
|
|
||||||
# Texture
|
# Texture
|
||||||
if gltf2_blender_get.has_image_node_from_socket(original_specular_socket):
|
if gltf2_blender_get.has_image_node_from_socket(original_specular_socket):
|
||||||
original_specular_texture, original_specular_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
original_specular_texture, original_specular_use_active_uvmap, _ = gather_texture_info(
|
||||||
original_specular_socket,
|
original_specular_socket,
|
||||||
(original_specular_socket,),
|
(original_specular_socket,),
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
specular_extension['specularTexture'] = original_specular_texture
|
specular_extension['specularTexture'] = original_specular_texture
|
||||||
@ -58,9 +60,10 @@ def export_original_specular(blender_material, export_settings):
|
|||||||
|
|
||||||
# Texture
|
# Texture
|
||||||
if gltf2_blender_get.has_image_node_from_socket(original_specularcolor_socket):
|
if gltf2_blender_get.has_image_node_from_socket(original_specularcolor_socket):
|
||||||
original_specularcolor_texture, original_specularcolor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
original_specularcolor_texture, original_specularcolor_use_active_uvmap, _ = gather_texture_info(
|
||||||
original_specularcolor_socket,
|
original_specularcolor_socket,
|
||||||
(original_specularcolor_socket,),
|
(original_specularcolor_socket,),
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
specular_extension['specularColorTexture'] = original_specularcolor_texture
|
specular_extension['specularColorTexture'] = original_specularcolor_texture
|
||||||
@ -86,12 +89,11 @@ def export_specular(blender_material, export_settings):
|
|||||||
if base_color_socket is None:
|
if base_color_socket is None:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# TODOExt replace by __has_image_node_from_socket calls
|
specular_not_linked = not image_tex_is_valid_from_socket(specular_socket)
|
||||||
specular_not_linked = isinstance(specular_socket, bpy.types.NodeSocket) and not specular_socket.is_linked
|
specular_tint_not_linked = not image_tex_is_valid_from_socket(specular_tint_socket)
|
||||||
specular_tint_not_linked = isinstance(specular_tint_socket, bpy.types.NodeSocket) and not specular_tint_socket.is_linked
|
base_color_not_linked = not image_tex_is_valid_from_socket(base_color_socket)
|
||||||
base_color_not_linked = isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked
|
transmission_not_linked = not image_tex_is_valid_from_socket(transmission_socket)
|
||||||
transmission_not_linked = isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked
|
ior_not_linked = not image_tex_is_valid_from_socket(ior_socket)
|
||||||
ior_not_linked = isinstance(ior_socket, bpy.types.NodeSocket) and not ior_socket.is_linked
|
|
||||||
|
|
||||||
specular = specular_socket.default_value if specular_not_linked else None
|
specular = specular_socket.default_value if specular_not_linked else None
|
||||||
specular_tint = specular_tint_socket.default_value if specular_tint_not_linked else None
|
specular_tint = specular_tint_socket.default_value if specular_tint_not_linked else None
|
||||||
@ -149,9 +151,10 @@ def export_specular(blender_material, export_settings):
|
|||||||
if base_color_not_linked:
|
if base_color_not_linked:
|
||||||
primary_socket = transmission_socket
|
primary_socket = transmission_socket
|
||||||
|
|
||||||
specularColorTexture, use_active_uvmap, specularColorFactor = gltf2_blender_gather_texture_info.gather_texture_info(
|
specularColorTexture, use_active_uvmap, specularColorFactor = gather_texture_info(
|
||||||
primary_socket,
|
primary_socket,
|
||||||
sockets,
|
sockets,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
filter_type='ANY')
|
filter_type='ANY')
|
||||||
if specularColorTexture is None:
|
if specularColorTexture is None:
|
||||||
|
@ -38,6 +38,7 @@ def export_transmission(blender_material, export_settings):
|
|||||||
combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
transmission_socket,
|
transmission_socket,
|
||||||
transmission_slots,
|
transmission_slots,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
if has_transmission_texture:
|
if has_transmission_texture:
|
||||||
|
@ -66,6 +66,7 @@ def export_volume(blender_material, export_settings):
|
|||||||
combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
thicknesss_socket,
|
thicknesss_socket,
|
||||||
thickness_slots,
|
thickness_slots,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
if has_thickness_texture:
|
if has_thickness_texture:
|
||||||
|
@ -28,6 +28,11 @@ class FillWhite:
|
|||||||
"""Fills a channel with all ones (1.0)."""
|
"""Fills a channel with all ones (1.0)."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class FillWith:
|
||||||
|
"""Fills a channel with all same values"""
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
class StoreData:
|
class StoreData:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
"""Store numeric data (not an image channel"""
|
"""Store numeric data (not an image channel"""
|
||||||
@ -99,6 +104,9 @@ class ExportImage:
|
|||||||
def fill_white(self, dst_chan: Channel):
|
def fill_white(self, dst_chan: Channel):
|
||||||
self.fills[dst_chan] = FillWhite()
|
self.fills[dst_chan] = FillWhite()
|
||||||
|
|
||||||
|
def fill_with(self, dst_chan, value):
|
||||||
|
self.fills[dst_chan] = FillWith(value)
|
||||||
|
|
||||||
def is_filled(self, chan: Channel) -> bool:
|
def is_filled(self, chan: Channel) -> bool:
|
||||||
return chan in self.fills
|
return chan in self.fills
|
||||||
|
|
||||||
@ -183,6 +191,8 @@ class ExportImage:
|
|||||||
for dst_chan, fill in self.fills.items():
|
for dst_chan, fill in self.fills.items():
|
||||||
if isinstance(fill, FillImage) and fill.image == image:
|
if isinstance(fill, FillImage) and fill.image == image:
|
||||||
out_buf[int(dst_chan)::4] = tmp_buf[int(fill.src_chan)::4]
|
out_buf[int(dst_chan)::4] = tmp_buf[int(fill.src_chan)::4]
|
||||||
|
elif isinstance(fill, FillWith):
|
||||||
|
out_buf[int(dst_chan)::4] = fill.value
|
||||||
|
|
||||||
tmp_buf = None # GC this
|
tmp_buf = None # GC this
|
||||||
|
|
||||||
|
@ -12,17 +12,18 @@ from ....io.exp import gltf2_io_binary_data, gltf2_io_image_data
|
|||||||
from ....io.com import gltf2_io_debug
|
from ....io.com import gltf2_io_debug
|
||||||
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
from ..gltf2_blender_gather_cache import cached
|
from ..gltf2_blender_gather_cache import cached
|
||||||
from . import gltf2_blender_search_node_tree
|
|
||||||
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage
|
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage
|
||||||
|
from ..gltf2_blender_get import get_tex_from_socket
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def gather_image(
|
def gather_image(
|
||||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
|
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
export_settings):
|
export_settings):
|
||||||
if not __filter_image(blender_shader_sockets, export_settings):
|
if not __filter_image(blender_shader_sockets, export_settings):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
image_data = __get_image_data(blender_shader_sockets, export_settings)
|
image_data = __get_image_data(blender_shader_sockets, default_sockets, export_settings)
|
||||||
if image_data.empty():
|
if image_data.empty():
|
||||||
# The export image has no data
|
# The export image has no data
|
||||||
return None, None
|
return None, None
|
||||||
@ -174,24 +175,32 @@ def __gather_uri(image_data, mime_type, name, export_settings):
|
|||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def __get_image_data(sockets, export_settings) -> ExportImage:
|
def __get_image_data(sockets, default_sockets, export_settings) -> ExportImage:
|
||||||
# For shared resources, such as images, we just store the portion of data that is needed in the glTF property
|
# For shared resources, such as images, we just store the portion of data that is needed in the glTF property
|
||||||
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
|
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
|
||||||
# resources.
|
# resources.
|
||||||
results = [__get_tex_from_socket(socket, export_settings) for socket in sockets]
|
results = [get_tex_from_socket(socket) for socket in sockets]
|
||||||
|
|
||||||
# Check if we need a simple mapping or more complex calculation
|
# Check if we need a simple mapping or more complex calculation
|
||||||
if any([socket.name == "Specular" and socket.node.type == "BSDF_PRINCIPLED" for socket in sockets]):
|
if any([socket.name == "Specular" and socket.node.type == "BSDF_PRINCIPLED" for socket in sockets]):
|
||||||
return __get_image_data_specular(sockets, results, export_settings)
|
return __get_image_data_specular(sockets, results, export_settings)
|
||||||
else:
|
else:
|
||||||
return __get_image_data_mapping(sockets, results, export_settings)
|
return __get_image_data_mapping(sockets, default_sockets, results, export_settings)
|
||||||
|
|
||||||
def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
|
def __get_image_data_mapping(sockets, default_sockets, results, export_settings) -> ExportImage:
|
||||||
"""
|
"""
|
||||||
Simple mapping
|
Simple mapping
|
||||||
Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
|
Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
|
||||||
"""
|
"""
|
||||||
composed_image = ExportImage()
|
composed_image = ExportImage()
|
||||||
|
|
||||||
|
default_metallic = None
|
||||||
|
default_roughness = None
|
||||||
|
if "Metallic" in [s.name for s in default_sockets]:
|
||||||
|
default_metallic = [s for s in default_sockets if s.name == "Metallic"][0].default_value
|
||||||
|
if "Roughness" in [s.name for s in default_sockets]:
|
||||||
|
default_roughness = [s for s in default_sockets if s.name == "Roughness"][0].default_value
|
||||||
|
|
||||||
for result, socket in zip(results, sockets):
|
for result, socket in zip(results, sockets):
|
||||||
# Assume that user know what he does, and that channels/images are already combined correctly for pbr
|
# Assume that user know what he does, and that channels/images are already combined correctly for pbr
|
||||||
# If not, we are going to keep only the first texture found
|
# If not, we are going to keep only the first texture found
|
||||||
@ -242,8 +251,14 @@ def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
|
|||||||
# Since metal/roughness are always used together, make sure
|
# Since metal/roughness are always used together, make sure
|
||||||
# the other channel is filled.
|
# the other channel is filled.
|
||||||
if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
|
if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
|
||||||
|
if default_roughness is not None:
|
||||||
|
composed_image.fill_with(Channel.G, default_roughness)
|
||||||
|
else:
|
||||||
composed_image.fill_white(Channel.G)
|
composed_image.fill_white(Channel.G)
|
||||||
elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
|
elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
|
||||||
|
if default_metallic is not None:
|
||||||
|
composed_image.fill_with(Channel.B, default_metallic)
|
||||||
|
else:
|
||||||
composed_image.fill_white(Channel.B)
|
composed_image.fill_white(Channel.B)
|
||||||
else:
|
else:
|
||||||
# copy full image...eventually following sockets might overwrite things
|
# copy full image...eventually following sockets might overwrite things
|
||||||
@ -271,7 +286,7 @@ def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
|
|||||||
|
|
||||||
composed_image.store_data("ior", sockets[4].default_value, type="Data")
|
composed_image.store_data("ior", sockets[4].default_value, type="Data")
|
||||||
|
|
||||||
results = [__get_tex_from_socket(socket, export_settings) for socket in sockets[:-1]] #Do not retrieve IOR --> No texture allowed
|
results = [get_tex_from_socket(socket) for socket in sockets[:-1]] #Do not retrieve IOR --> No texture allowed
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
0: "specular",
|
0: "specular",
|
||||||
@ -281,7 +296,7 @@ def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx, result in enumerate(results):
|
for idx, result in enumerate(results):
|
||||||
if __get_tex_from_socket(sockets[idx], export_settings):
|
if get_tex_from_socket(sockets[idx]):
|
||||||
|
|
||||||
composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
|
composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
|
||||||
|
|
||||||
@ -308,16 +323,6 @@ def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
|
|||||||
|
|
||||||
return composed_image
|
return composed_image
|
||||||
|
|
||||||
# TODOExt deduplicate
|
|
||||||
@cached
|
|
||||||
def __get_tex_from_socket(blender_shader_socket: bpy.types.NodeSocket, export_settings):
|
|
||||||
result = gltf2_blender_search_node_tree.from_socket(
|
|
||||||
blender_shader_socket,
|
|
||||||
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
|
|
||||||
def __is_blender_image_a_jpeg(image: bpy.types.Image) -> bool:
|
def __is_blender_image_a_jpeg(image: bpy.types.Image) -> bool:
|
||||||
if image.source != 'FILE':
|
if image.source != 'FILE':
|
||||||
|
@ -52,13 +52,13 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
|
|||||||
export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
|
export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
|
||||||
return mat_unlit
|
return mat_unlit
|
||||||
|
|
||||||
orm_texture = __gather_orm_texture(blender_material, export_settings)
|
orm_texture, default_sockets = __gather_orm_texture(blender_material, export_settings)
|
||||||
|
|
||||||
emissive_factor = __gather_emissive_factor(blender_material, export_settings)
|
emissive_factor = __gather_emissive_factor(blender_material, export_settings)
|
||||||
emissive_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, export_settings)
|
emissive_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, export_settings)
|
||||||
extensions, uvmap_actives_extensions = __gather_extensions(blender_material, emissive_factor, export_settings)
|
extensions, uvmap_actives_extensions = __gather_extensions(blender_material, emissive_factor, export_settings)
|
||||||
normal_texture, uvmap_actives_normal_texture = __gather_normal_texture(blender_material, export_settings)
|
normal_texture, uvmap_actives_normal_texture = __gather_normal_texture(blender_material, export_settings)
|
||||||
occlusion_texture, uvmap_actives_occlusion_texture = __gather_occlusion_texture(blender_material, orm_texture, export_settings)
|
occlusion_texture, uvmap_actives_occlusion_texture = __gather_occlusion_texture(blender_material, orm_texture, default_sockets, export_settings)
|
||||||
pbr_metallic_roughness, uvmap_actives_pbr_metallic_roughness = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
|
pbr_metallic_roughness, uvmap_actives_pbr_metallic_roughness = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
|
||||||
|
|
||||||
if any([i>1.0 for i in emissive_factor or []]) is True:
|
if any([i>1.0 for i in emissive_factor or []]) is True:
|
||||||
@ -303,7 +303,7 @@ def __gather_orm_texture(blender_material, export_settings):
|
|||||||
if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
|
if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
|
||||||
occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
|
occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
|
||||||
if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
|
if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
|
metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
|
||||||
roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
|
roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
|
||||||
@ -311,38 +311,43 @@ def __gather_orm_texture(blender_material, export_settings):
|
|||||||
hasMetal = metallic_socket is not None and gltf2_blender_get.has_image_node_from_socket(metallic_socket)
|
hasMetal = metallic_socket is not None and gltf2_blender_get.has_image_node_from_socket(metallic_socket)
|
||||||
hasRough = roughness_socket is not None and gltf2_blender_get.has_image_node_from_socket(roughness_socket)
|
hasRough = roughness_socket is not None and gltf2_blender_get.has_image_node_from_socket(roughness_socket)
|
||||||
|
|
||||||
|
default_sockets = ()
|
||||||
if not hasMetal and not hasRough:
|
if not hasMetal and not hasRough:
|
||||||
metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
|
metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
|
||||||
if metallic_roughness is None or not gltf2_blender_get.has_image_node_from_socket(metallic_roughness):
|
if metallic_roughness is None or not gltf2_blender_get.has_image_node_from_socket(metallic_roughness):
|
||||||
return None
|
return None, default_sockets
|
||||||
result = (occlusion, metallic_roughness)
|
result = (occlusion, metallic_roughness)
|
||||||
elif not hasMetal:
|
elif not hasMetal:
|
||||||
result = (occlusion, roughness_socket)
|
result = (occlusion, roughness_socket)
|
||||||
|
default_sockets = (metallic_socket,)
|
||||||
elif not hasRough:
|
elif not hasRough:
|
||||||
result = (occlusion, metallic_socket)
|
result = (occlusion, metallic_socket)
|
||||||
|
default_sockets = (roughness_socket,)
|
||||||
else:
|
else:
|
||||||
result = (occlusion, roughness_socket, metallic_socket)
|
result = (occlusion, roughness_socket, metallic_socket)
|
||||||
|
default_sockets = ()
|
||||||
|
|
||||||
if not gltf2_blender_gather_texture_info.check_same_size_images(result):
|
if not gltf2_blender_gather_texture_info.check_same_size_images(result):
|
||||||
print_console("INFO",
|
print_console("INFO",
|
||||||
"Occlusion and metal-roughness texture will be exported separately "
|
"Occlusion and metal-roughness texture will be exported separately "
|
||||||
"(use same-sized images if you want them combined)")
|
"(use same-sized images if you want them combined)")
|
||||||
return None
|
return None, ()
|
||||||
|
|
||||||
# Double-check this will past the filter in texture_info
|
# Double-check this will past the filter in texture_info
|
||||||
info, info_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
|
info, info_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, default_sockets, export_settings)
|
||||||
if info is None:
|
if info is None:
|
||||||
return None
|
return None, ()
|
||||||
|
|
||||||
return result
|
return result, default_sockets
|
||||||
|
|
||||||
def __gather_occlusion_texture(blender_material, orm_texture, export_settings):
|
def __gather_occlusion_texture(blender_material, orm_texture, default_sockets, export_settings):
|
||||||
occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
|
occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
|
||||||
if occlusion is None:
|
if occlusion is None:
|
||||||
occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
|
occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
|
||||||
occlusion_texture, use_active_uvmap_occlusion, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
|
occlusion_texture, use_active_uvmap_occlusion, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
|
||||||
occlusion,
|
occlusion,
|
||||||
orm_texture or (occlusion,),
|
orm_texture or (occlusion,),
|
||||||
|
default_sockets,
|
||||||
export_settings)
|
export_settings)
|
||||||
return occlusion_texture, ["occlusionTexture"] if use_active_uvmap_occlusion else None
|
return occlusion_texture, ["occlusionTexture"] if use_active_uvmap_occlusion else None
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ from ....io.com import gltf2_io
|
|||||||
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
from ...exp import gltf2_blender_get
|
from ...exp import gltf2_blender_get
|
||||||
from ..gltf2_blender_gather_cache import cached
|
from ..gltf2_blender_gather_cache import cached
|
||||||
from . import gltf2_blender_search_node_tree
|
from ..gltf2_blender_get import image_tex_is_valid_from_socket
|
||||||
from . import gltf2_blender_gather_texture_info
|
from .gltf2_blender_gather_texture_info import gather_texture_info
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export_settings):
|
def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export_settings):
|
||||||
@ -93,12 +93,12 @@ def __gather_base_color_texture(blender_material, export_settings):
|
|||||||
# keep sockets that have some texture : color and/or alpha
|
# keep sockets that have some texture : color and/or alpha
|
||||||
inputs = tuple(
|
inputs = tuple(
|
||||||
socket for socket in [base_color_socket, alpha_socket]
|
socket for socket in [base_color_socket, alpha_socket]
|
||||||
if socket is not None and __has_image_node_from_socket(socket)
|
if socket is not None and image_tex_is_valid_from_socket(socket)
|
||||||
)
|
)
|
||||||
if not inputs:
|
if not inputs:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
return gltf2_blender_gather_texture_info.gather_texture_info(inputs[0], inputs, export_settings)
|
return gather_texture_info(inputs[0], inputs, (), export_settings)
|
||||||
|
|
||||||
|
|
||||||
def __gather_extensions(blender_material, export_settings):
|
def __gather_extensions(blender_material, export_settings):
|
||||||
@ -126,24 +126,29 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
|
|||||||
metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
|
metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
|
||||||
roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
|
roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
|
||||||
|
|
||||||
hasMetal = metallic_socket is not None and __has_image_node_from_socket(metallic_socket)
|
hasMetal = metallic_socket is not None and image_tex_is_valid_from_socket(metallic_socket)
|
||||||
hasRough = roughness_socket is not None and __has_image_node_from_socket(roughness_socket)
|
hasRough = roughness_socket is not None and image_tex_is_valid_from_socket(roughness_socket)
|
||||||
|
|
||||||
|
default_sockets = ()
|
||||||
if not hasMetal and not hasRough:
|
if not hasMetal and not hasRough:
|
||||||
metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
|
metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
|
||||||
if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
|
if metallic_roughness is None or not image_tex_is_valid_from_socket(metallic_roughness):
|
||||||
return None, None, None
|
return None, None, None
|
||||||
texture_input = (metallic_roughness,)
|
texture_input = (metallic_roughness,)
|
||||||
elif not hasMetal:
|
elif not hasMetal:
|
||||||
texture_input = (roughness_socket,)
|
texture_input = (roughness_socket,)
|
||||||
|
default_sockets = (metallic_socket,)
|
||||||
elif not hasRough:
|
elif not hasRough:
|
||||||
texture_input = (metallic_socket,)
|
texture_input = (metallic_socket,)
|
||||||
|
default_sockets = (roughness_socket,)
|
||||||
else:
|
else:
|
||||||
texture_input = (metallic_socket, roughness_socket)
|
texture_input = (metallic_socket, roughness_socket)
|
||||||
|
default_sockets = ()
|
||||||
|
|
||||||
return gltf2_blender_gather_texture_info.gather_texture_info(
|
return gather_texture_info(
|
||||||
texture_input[0],
|
texture_input[0],
|
||||||
orm_texture or texture_input,
|
orm_texture or texture_input,
|
||||||
|
default_sockets,
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -160,14 +165,6 @@ def __gather_roughness_factor(blender_material, export_settings):
|
|||||||
return fac if fac != 1 else None
|
return fac if fac != 1 else None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __has_image_node_from_socket(socket):
|
|
||||||
result = gltf2_blender_search_node_tree.from_socket(
|
|
||||||
socket,
|
|
||||||
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
|
||||||
if not result:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_default_pbr_for_emissive_node():
|
def get_default_pbr_for_emissive_node():
|
||||||
return gltf2_io.MaterialPBRMetallicRoughness(
|
return gltf2_io.MaterialPBRMetallicRoughness(
|
||||||
base_color_factor=[0.0,0.0,0.0,1.0],
|
base_color_factor=[0.0,0.0,0.0,1.0],
|
||||||
|
@ -131,6 +131,7 @@ def gather_base_color_texture(info, export_settings):
|
|||||||
unlit_texture, unlit_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
unlit_texture, unlit_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
|
||||||
sockets[0],
|
sockets[0],
|
||||||
sockets,
|
sockets,
|
||||||
|
(),
|
||||||
export_settings,
|
export_settings,
|
||||||
)
|
)
|
||||||
return unlit_texture, ["unlitTexture"] if unlit_use_active_uvmap else None
|
return unlit_texture, ["unlitTexture"] if unlit_use_active_uvmap else None
|
||||||
|
@ -9,12 +9,13 @@ from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
|||||||
from ....io.com import gltf2_io
|
from ....io.com import gltf2_io
|
||||||
from ..gltf2_blender_gather_cache import cached
|
from ..gltf2_blender_gather_cache import cached
|
||||||
from ..gltf2_blender_gather_sampler import gather_sampler
|
from ..gltf2_blender_gather_sampler import gather_sampler
|
||||||
from . import gltf2_blender_search_node_tree
|
from ..gltf2_blender_get import get_tex_from_socket
|
||||||
from . import gltf2_blender_gather_image
|
from . import gltf2_blender_gather_image
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def gather_texture(
|
def gather_texture(
|
||||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
|
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
export_settings):
|
export_settings):
|
||||||
"""
|
"""
|
||||||
Gather texture sampling information and image channels from a blender shader texture attached to a shader socket.
|
Gather texture sampling information and image channels from a blender shader texture attached to a shader socket.
|
||||||
@ -27,7 +28,7 @@ def gather_texture(
|
|||||||
if not __filter_texture(blender_shader_sockets, export_settings):
|
if not __filter_texture(blender_shader_sockets, export_settings):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
source, factor = __gather_source(blender_shader_sockets, export_settings)
|
source, factor = __gather_source(blender_shader_sockets, default_sockets, export_settings)
|
||||||
|
|
||||||
texture = gltf2_io.Texture(
|
texture = gltf2_io.Texture(
|
||||||
extensions=__gather_extensions(blender_shader_sockets, export_settings),
|
extensions=__gather_extensions(blender_shader_sockets, export_settings),
|
||||||
@ -67,7 +68,7 @@ def __gather_name(blender_shader_sockets, export_settings):
|
|||||||
|
|
||||||
|
|
||||||
def __gather_sampler(blender_shader_sockets, export_settings):
|
def __gather_sampler(blender_shader_sockets, export_settings):
|
||||||
shader_nodes = [__get_tex_from_socket(socket) for socket in blender_shader_sockets]
|
shader_nodes = [get_tex_from_socket(socket) for socket in blender_shader_sockets]
|
||||||
if len(shader_nodes) > 1:
|
if len(shader_nodes) > 1:
|
||||||
gltf2_io_debug.print_console("WARNING",
|
gltf2_io_debug.print_console("WARNING",
|
||||||
"More than one shader node tex image used for a texture. "
|
"More than one shader node tex image used for a texture. "
|
||||||
@ -78,16 +79,5 @@ def __gather_sampler(blender_shader_sockets, export_settings):
|
|||||||
export_settings)
|
export_settings)
|
||||||
|
|
||||||
|
|
||||||
def __gather_source(blender_shader_sockets, export_settings):
|
def __gather_source(blender_shader_sockets, default_sockets, export_settings):
|
||||||
return gltf2_blender_gather_image.gather_image(blender_shader_sockets, export_settings)
|
return gltf2_blender_gather_image.gather_image(blender_shader_sockets, default_sockets, export_settings)
|
||||||
|
|
||||||
# Helpers
|
|
||||||
|
|
||||||
# TODOExt deduplicate
|
|
||||||
def __get_tex_from_socket(socket):
|
|
||||||
result = gltf2_blender_search_node_tree.from_socket(
|
|
||||||
socket,
|
|
||||||
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
return result[0]
|
|
||||||
|
@ -8,7 +8,7 @@ from ....io.com import gltf2_io
|
|||||||
from ....io.com.gltf2_io_extensions import Extension
|
from ....io.com.gltf2_io_extensions import Extension
|
||||||
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
from ...exp import gltf2_blender_get
|
from ...exp import gltf2_blender_get
|
||||||
from ..gltf2_blender_get import previous_node
|
from ..gltf2_blender_get import previous_node, get_tex_from_socket
|
||||||
from ..gltf2_blender_gather_sampler import detect_manual_uv_wrapping
|
from ..gltf2_blender_gather_sampler import detect_manual_uv_wrapping
|
||||||
from ..gltf2_blender_gather_cache import cached
|
from ..gltf2_blender_gather_cache import cached
|
||||||
from . import gltf2_blender_gather_texture
|
from . import gltf2_blender_gather_texture
|
||||||
@ -19,20 +19,25 @@ from . import gltf2_blender_search_node_tree
|
|||||||
# occlusion the primary_socket would be the occlusion socket, and
|
# occlusion the primary_socket would be the occlusion socket, and
|
||||||
# blender_shader_sockets would be the (O,R,M) sockets.
|
# blender_shader_sockets would be the (O,R,M) sockets.
|
||||||
|
|
||||||
def gather_texture_info(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
|
# Default socket parameter is used when there is a mapping between channels, and one of the channel is not a texture
|
||||||
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', filter_type, export_settings)
|
# In that case, we will create a texture with one channel from texture, other from default socket value
|
||||||
|
# Example: MetallicRoughness
|
||||||
|
|
||||||
|
def gather_texture_info(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'):
|
||||||
|
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, default_sockets, 'DEFAULT', filter_type, export_settings)
|
||||||
|
|
||||||
def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
|
def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
|
||||||
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', filter_type, export_settings)
|
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, (), 'NORMAL', filter_type, export_settings)
|
||||||
|
|
||||||
def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
|
def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'):
|
||||||
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
|
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, default_sockets, 'OCCLUSION', filter_type, export_settings)
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def __gather_texture_info_helper(
|
def __gather_texture_info_helper(
|
||||||
primary_socket: bpy.types.NodeSocket,
|
primary_socket: bpy.types.NodeSocket,
|
||||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
|
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
kind: str,
|
kind: str,
|
||||||
filter_type: str,
|
filter_type: str,
|
||||||
export_settings):
|
export_settings):
|
||||||
@ -41,7 +46,7 @@ def __gather_texture_info_helper(
|
|||||||
|
|
||||||
tex_transform, tex_coord, use_active_uvmap = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
|
tex_transform, tex_coord, use_active_uvmap = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
|
||||||
|
|
||||||
index, factor = __gather_index(blender_shader_sockets, export_settings)
|
index, factor = __gather_index(blender_shader_sockets, default_sockets, export_settings)
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'extensions': __gather_extensions(tex_transform, export_settings),
|
'extensions': __gather_extensions(tex_transform, export_settings),
|
||||||
@ -72,7 +77,7 @@ def __gather_texture_info_helper(
|
|||||||
def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
|
def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
|
||||||
if primary_socket is None:
|
if primary_socket is None:
|
||||||
return False
|
return False
|
||||||
if __get_tex_from_socket(primary_socket) is None:
|
if get_tex_from_socket(primary_socket) is None:
|
||||||
return False
|
return False
|
||||||
if not blender_shader_sockets:
|
if not blender_shader_sockets:
|
||||||
return False
|
return False
|
||||||
@ -80,12 +85,12 @@ def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, e
|
|||||||
return False
|
return False
|
||||||
if filter_type == "ALL":
|
if filter_type == "ALL":
|
||||||
# Check that all sockets link to texture
|
# Check that all sockets link to texture
|
||||||
if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
|
if any([get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
|
||||||
# sockets do not lead to a texture --> discard
|
# sockets do not lead to a texture --> discard
|
||||||
return False
|
return False
|
||||||
elif filter_type == "ANY":
|
elif filter_type == "ANY":
|
||||||
# Check that at least one socket link to texture
|
# Check that at least one socket link to texture
|
||||||
if all([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
|
if all([get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
|
||||||
return False
|
return False
|
||||||
elif filter_type == "NONE":
|
elif filter_type == "NONE":
|
||||||
# No check
|
# No check
|
||||||
@ -136,9 +141,9 @@ def __gather_occlusion_strength(primary_socket, export_settings):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def __gather_index(blender_shader_sockets, export_settings):
|
def __gather_index(blender_shader_sockets, default_sockets, export_settings):
|
||||||
# We just put the actual shader into the 'index' member
|
# We just put the actual shader into the 'index' member
|
||||||
return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets, export_settings)
|
return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets, default_sockets, export_settings)
|
||||||
|
|
||||||
|
|
||||||
def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
|
def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
|
||||||
@ -148,7 +153,7 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
|
|||||||
#
|
#
|
||||||
# The [UV Wrapping] is for wrap modes like MIRROR that use nodes,
|
# The [UV Wrapping] is for wrap modes like MIRROR that use nodes,
|
||||||
# [Mapping] is for KHR_texture_transform, and [UV Map] is for texCoord.
|
# [Mapping] is for KHR_texture_transform, and [UV Map] is for texCoord.
|
||||||
blender_shader_node = __get_tex_from_socket(primary_socket).shader_node
|
blender_shader_node = get_tex_from_socket(primary_socket).shader_node
|
||||||
|
|
||||||
# Skip over UV wrapping stuff (it goes in the sampler)
|
# Skip over UV wrapping stuff (it goes in the sampler)
|
||||||
result = detect_manual_uv_wrapping(blender_shader_node)
|
result = detect_manual_uv_wrapping(blender_shader_node)
|
||||||
@ -178,17 +183,6 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
|
|||||||
|
|
||||||
return texture_transform, texcoord_idx or None, use_active_uvmap
|
return texture_transform, texcoord_idx or None, use_active_uvmap
|
||||||
|
|
||||||
# TODOExt deduplicate
|
|
||||||
def __get_tex_from_socket(socket):
|
|
||||||
result = gltf2_blender_search_node_tree.from_socket(
|
|
||||||
socket,
|
|
||||||
gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
if result[0].shader_node.image is None:
|
|
||||||
return None
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
|
|
||||||
def check_same_size_images(
|
def check_same_size_images(
|
||||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||||
@ -199,7 +193,7 @@ def check_same_size_images(
|
|||||||
|
|
||||||
sizes = set()
|
sizes = set()
|
||||||
for socket in blender_shader_sockets:
|
for socket in blender_shader_sockets:
|
||||||
tex = __get_tex_from_socket(socket)
|
tex = get_tex_from_socket(socket)
|
||||||
if tex is None:
|
if tex is None:
|
||||||
return False
|
return False
|
||||||
size = tex.shader_node.image.size
|
size = tex.shader_node.image.size
|
||||||
|
@ -152,5 +152,5 @@ GLTF_DATA_TYPE_MAT4 = "MAT4"
|
|||||||
|
|
||||||
GLTF_IOR = 1.5
|
GLTF_IOR = 1.5
|
||||||
|
|
||||||
# Rounding digit used for normal rounding
|
# Rounding digit used for normal/tangent rounding
|
||||||
NORMALS_ROUNDING_DIGIT = 4
|
ROUNDING_DIGIT = 4
|
||||||
|
Loading…
Reference in New Issue
Block a user