FBX IO: Speed up transformation animation import #104870
@ -641,7 +641,6 @@ class discombobulator_dodads_list(Menu):
|
||||
bl_idname = "OBJECT_MT_discombobulator_dodad_list"
|
||||
bl_label = "List of saved Doodads"
|
||||
bl_description = "List of the saved Doodad Object Names"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -660,7 +659,6 @@ class discombob_help(Menu):
|
||||
bl_idname = "HELP_MT_discombobulator"
|
||||
bl_label = "Usage Information"
|
||||
bl_description = "Help"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
@ -7,7 +7,7 @@
|
||||
bl_info = {
|
||||
"name": "Is key Free",
|
||||
"author": "Antonio Vazquez (antonioya)",
|
||||
"version": (1, 1, 2),
|
||||
"version": (1, 1, 3),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "Text Editor > Sidebar > Dev Tab",
|
||||
"description": "Find free shortcuts, inform about used and print a key list",
|
||||
@ -16,6 +16,7 @@ bl_info = {
|
||||
}
|
||||
|
||||
import bpy
|
||||
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
@ -28,6 +29,7 @@ from bpy.types import (
|
||||
PropertyGroup,
|
||||
)
|
||||
|
||||
import unicodedata
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Class to find keymaps
|
||||
@ -498,6 +500,15 @@ class IsKeyFreeRunExportKeys(Operator):
|
||||
except:
|
||||
return None
|
||||
|
||||
def unicodelen(self, string):
|
||||
n = 0
|
||||
for c in string:
|
||||
if unicodedata.east_asian_width(c) in 'FWA':
|
||||
n += 2
|
||||
else:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
def execute(self, context):
|
||||
wm = bpy.context.window_manager
|
||||
from collections import defaultdict
|
||||
@ -536,7 +547,7 @@ class IsKeyFreeRunExportKeys(Operator):
|
||||
textblock.write("\n[%s]\nEntries: %s\n\n" % (ctx, len(mykeys[ctx])))
|
||||
line_k = sorted(mykeys[ctx])
|
||||
for keys in line_k:
|
||||
add_ticks = "-" * (max_line - (len(keys[0]) + len(keys[1])))
|
||||
add_ticks = "-" * (max_line - (self.unicodelen(keys[0]) + len(keys[1])))
|
||||
entries = "{ticks} {entry}".format(ticks=add_ticks, entry=keys[1])
|
||||
textblock.write("{name} {entry}\n".format(name=keys[0], entry=entries))
|
||||
|
||||
|
@ -12,6 +12,7 @@ class StormHydraRenderEngine(bpy.types.HydraRenderEngine):
|
||||
|
||||
bl_use_preview = True
|
||||
bl_use_gpu_context = True
|
||||
bl_use_materialx = True
|
||||
|
||||
bl_delegate_id = 'HdStormRendererPlugin'
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
bl_info = {
|
||||
"name": "FBX format",
|
||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
||||
"version": (5, 7, 0),
|
||||
"version": (5, 7, 2),
|
||||
"blender": (3, 6, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
||||
|
@ -698,39 +698,6 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
yield from sca
|
||||
|
||||
|
||||
def blen_read_animation_channel_curves(curves):
|
||||
"""Read one or (very rarely) more animation curves, that affect a single channel of a single property, from FBX
|
||||
data.
|
||||
|
||||
When there are multiple curves, they will be combined into a single sorted animation curve with later curves taking
|
||||
precedence when the curves contain duplicate times.
|
||||
|
||||
It is expected that there will almost never be more than a single curve to read because FBX's default animation
|
||||
system only uses the first curve assigned to a channel.
|
||||
|
||||
Returns an array of sorted, unique FBX keyframe times and an array of values for each of those keyframe times."""
|
||||
if len(curves) > 1:
|
||||
times_and_values_tuples = list(map(blen_read_single_animation_curve, curves))
|
||||
# The FBX animation system's default implementation only uses the first curve assigned to a channel.
|
||||
# Additional curves per channel are allowed by the FBX specification, but the handling of these curves is
|
||||
# considered the responsibility of the application that created them. Note that each curve node is expected to
|
||||
# have a unique set of channels, so these additional curves with the same channel would have to belong to
|
||||
# separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
|
||||
|
||||
# Combine the curves together to produce a single array of sorted keyframe times and a single array of values.
|
||||
# The arrays are concatenated in reverse so that if there are duplicate times in the read curves, then only the
|
||||
# value of the last occurrence is kept.
|
||||
all_times = np.concatenate([t[0] for t in reversed(times_and_values_tuples)])
|
||||
all_values = np.concatenate([t[1] for t in reversed(times_and_values_tuples)])
|
||||
# Get the unique, sorted times and the index in all_times of the first occurrence of each unique value.
|
||||
sorted_unique_times, unique_indices_in_all_times = np.unique(all_times, return_index=True)
|
||||
|
||||
values_of_sorted_unique_times = all_values[unique_indices_in_all_times]
|
||||
return sorted_unique_times, values_of_sorted_unique_times
|
||||
else:
|
||||
return blen_read_single_animation_curve(curves[0])
|
||||
|
||||
|
||||
def _combine_curve_keyframe_times(times_and_values_tuples, initial_values):
|
||||
"""Combine multiple parsed animation curves, that affect different channels, such that every animation curve
|
||||
contains the keyframes from every other curve, interpolating the values for the newly inserted keyframes in each
|
||||
@ -740,7 +707,8 @@ def _combine_curve_keyframe_times(times_and_values_tuples, initial_values):
|
||||
interpolating the keyframe values is a TODO."""
|
||||
if len(times_and_values_tuples) == 1:
|
||||
# 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]
|
||||
|
||||
@ -850,8 +818,8 @@ def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_of
|
||||
return key_times
|
||||
|
||||
|
||||
def blen_read_single_animation_curve(fbx_curve):
|
||||
"""Read a single animation curve from FBX data.
|
||||
def blen_read_animation_curve(fbx_curve):
|
||||
"""Read an animation curve from FBX data.
|
||||
|
||||
The parsed keyframe times are guaranteed to be strictly increasing."""
|
||||
key_times = parray_as_ndarray(elem_prop_first(elem_find_first(fbx_curve, b'KeyTime')))
|
||||
@ -922,11 +890,19 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
"""
|
||||
from bpy.types import Object, PoseBone, ShapeKey, Material, Camera
|
||||
|
||||
fbx_curves: dict[bytes, dict[int, list[FBXElem]]] = {}
|
||||
fbx_curves: dict[bytes, dict[int, FBXElem]] = {}
|
||||
for curves, fbxprop in cnodes.values():
|
||||
channels_dict = fbx_curves.setdefault(fbxprop, {})
|
||||
for (fbx_acdata, _blen_data), channel in curves.values():
|
||||
channels_dict.setdefault(channel, []).append(fbx_acdata)
|
||||
if channel in channels_dict:
|
||||
# Ignore extra curves when one has already been found for this channel because FBX's default animation
|
||||
# system implementation only uses the first curve assigned to a channel.
|
||||
# Additional curves per channel are allowed by the FBX specification, but the handling of these curves
|
||||
# is considered the responsibility of the application that created them. Note that each curve node is
|
||||
# expected to have a unique set of channels, so these additional curves with the same channel would have
|
||||
# to belong to separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
|
||||
continue
|
||||
channels_dict[channel] = fbx_acdata
|
||||
|
||||
# Leave if no curves are attached (if a blender curve is attached to scale but without keys it defaults to 0).
|
||||
if len(fbx_curves) == 0:
|
||||
@ -965,23 +941,23 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
for prop, nbr_channels, grpname in props for channel in range(nbr_channels)]
|
||||
|
||||
if isinstance(item, Material):
|
||||
for fbxprop, channel_to_curves in fbx_curves.items():
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
assert(fbxprop == b'DiffuseColor')
|
||||
for channel, curves in channel_to_curves.items():
|
||||
for channel, curve in channel_to_curve.items():
|
||||
assert(channel in {0, 1, 2})
|
||||
blen_curve = blen_curves[channel]
|
||||
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||
|
||||
elif isinstance(item, ShapeKey):
|
||||
deform_values = shape_key_deforms.setdefault(item, [])
|
||||
for fbxprop, channel_to_curves in fbx_curves.items():
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
assert(fbxprop == b'DeformPercent')
|
||||
for channel, curves in channel_to_curves.items():
|
||||
for channel, curve in channel_to_curve.items():
|
||||
assert(channel == 0)
|
||||
blen_curve = blen_curves[channel]
|
||||
|
||||
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
||||
values = values / 100.0
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||
@ -992,15 +968,15 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
deform_values.append(values.max())
|
||||
|
||||
elif isinstance(item, Camera):
|
||||
for fbxprop, channel_to_curves in fbx_curves.items():
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
is_focus_distance = fbxprop == b'FocusDistance'
|
||||
assert(fbxprop == b'FocalLength' or is_focus_distance)
|
||||
for channel, curves in channel_to_curves.items():
|
||||
for channel, curve in channel_to_curve.items():
|
||||
assert(channel == 0)
|
||||
# The indices are determined by the creation of the `props` list above.
|
||||
blen_curve = blen_curves[1 if is_focus_distance else 0]
|
||||
|
||||
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
if is_focus_distance:
|
||||
# Remap the imported values from FBX to Blender.
|
||||
values = values / 1000.0
|
||||
@ -1021,13 +997,13 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
times_and_values_tuples = []
|
||||
initial_values = []
|
||||
channel_keys = []
|
||||
for fbxprop, channel_to_curves in fbx_curves.items():
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
if fbxprop not in transform_prop_to_attr:
|
||||
# Currently, we only care about transformation curves.
|
||||
continue
|
||||
for channel, curves in channel_to_curves.items():
|
||||
for channel, curve in channel_to_curve.items():
|
||||
assert(channel in {0, 1, 2})
|
||||
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
|
||||
channel_keys.append((fbxprop, channel))
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'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),
|
||||
'location': 'File > Import-Export',
|
||||
'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):
|
||||
if gltf2_io_constants.DataType.num_elements(data_type) == 1:
|
||||
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]
|
||||
elif gltf2_io_constants.DataType.num_elements(data_type) == 2:
|
||||
return {
|
||||
@ -132,7 +133,8 @@ def get_attribute_type(component_type, data_type):
|
||||
elif gltf2_io_constants.DataType.num_elements(data_type) == 4:
|
||||
return {
|
||||
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]
|
||||
else:
|
||||
pass
|
||||
|
@ -45,10 +45,19 @@ def is_bone_anim_channel(data_path: str) -> bool:
|
||||
|
||||
def get_sk_exported(key_blocks):
|
||||
return [
|
||||
key_block
|
||||
for key_block in key_blocks
|
||||
if not skip_sk(key_block)
|
||||
k
|
||||
for k in key_blocks
|
||||
if not skip_sk(key_blocks, k)
|
||||
]
|
||||
|
||||
def skip_sk(k):
|
||||
return k == k.relative_key or k.mute
|
||||
def skip_sk(key_blocks, k):
|
||||
# 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)
|
||||
|
||||
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():
|
||||
channel = __gather_animation_fcurve_channel(chan['obj_uuid'], channel_group, chan['bone'], custom_range, export_settings)
|
||||
if channel is not None:
|
||||
@ -73,10 +73,13 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
|
||||
else:
|
||||
try:
|
||||
target = get_object_from_datapath(blender_object, object_path)
|
||||
type_ = "BONE"
|
||||
if blender_object.type == "ARMATURE" and fcurve.data_path.startswith("pose.bones["):
|
||||
type_ = "BONE"
|
||||
else:
|
||||
type_ = "EXTRA"
|
||||
if blender_object.type == "MESH" and object_path.startswith("key_blocks"):
|
||||
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
|
||||
target = blender_object.data.shape_keys
|
||||
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":
|
||||
try:
|
||||
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
|
||||
target = blender_object.data.shape_keys
|
||||
type_ = "SK"
|
||||
@ -181,7 +184,7 @@ def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender
|
||||
shapekeys_idx = {}
|
||||
cpt_sk = 0
|
||||
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
|
||||
shapekeys_idx[sk.name] = cpt_sk
|
||||
cpt_sk += 1
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import bpy
|
||||
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 ...gltf2_blender_gather_cache import cached
|
||||
from ..gltf2_blender_gather_keyframes import Keyframe
|
||||
@ -165,9 +165,7 @@ def __gather_non_keyed_values(
|
||||
if object_path:
|
||||
shapekeys_idx = {}
|
||||
cpt_sk = 0
|
||||
for sk in blender_object.data.shape_keys.key_blocks:
|
||||
if skip_sk(sk):
|
||||
continue
|
||||
for sk in get_sk_exported(blender_object.data.shape_keys.key_blocks):
|
||||
shapekeys_idx[cpt_sk] = sk.name
|
||||
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)
|
||||
elif type_ == "SK":
|
||||
channel = gather_sampled_sk_channel(obj_uuid, blender_action.name, export_settings)
|
||||
elif type_ == "EXTRA":
|
||||
channel = None
|
||||
else:
|
||||
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
|
||||
except:
|
||||
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
|
||||
idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c))
|
||||
existing_idx = dict(idx_channel_mapping)
|
||||
|
@ -6,6 +6,7 @@ import math
|
||||
import bpy
|
||||
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_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
|
||||
|
||||
# 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
|
||||
if len(modifiers) == 0:
|
||||
|
@ -35,7 +35,7 @@ def gather_primitive_attributes(blender_primitive, export_settings):
|
||||
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
|
||||
amin = None
|
||||
@ -53,7 +53,7 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
|
||||
max=amax,
|
||||
min=amin,
|
||||
name=None,
|
||||
normalized=None,
|
||||
normalized=normalized,
|
||||
sparse=None,
|
||||
type=data_type,
|
||||
)
|
||||
@ -183,6 +183,7 @@ def __gather_attribute(blender_primitive, attribute, export_settings):
|
||||
data['data'],
|
||||
component_type=data['component_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 ...blender.com.gltf2_blender_data_path import get_sk_exported
|
||||
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.com import gltf2_io_constants
|
||||
from ..com import gltf2_blender_conversion
|
||||
@ -357,9 +357,16 @@ class PrimitiveCreator:
|
||||
def primitive_split(self):
|
||||
# Calculate triangles and sort them into primitives.
|
||||
|
||||
self.blender_mesh.calc_loop_triangles()
|
||||
loop_indices = np.empty(len(self.blender_mesh.loop_triangles) * 3, dtype=np.uint32)
|
||||
self.blender_mesh.loop_triangles.foreach_get('loops', loop_indices)
|
||||
try:
|
||||
self.blender_mesh.calc_loop_triangles()
|
||||
loop_indices = np.empty(len(self.blender_mesh.loop_triangles) * 3, dtype=np.uint32)
|
||||
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
|
||||
|
||||
@ -715,7 +722,7 @@ class PrimitiveCreator:
|
||||
|
||||
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 ?)
|
||||
PrimitiveCreator.normalize_vecs(self.normals)
|
||||
|
||||
@ -723,7 +730,7 @@ class PrimitiveCreator:
|
||||
for key_block in key_blocks:
|
||||
ns = np.array(key_block.normals_split_get(), dtype=np.float32)
|
||||
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)
|
||||
|
||||
# Transform for skinning
|
||||
@ -782,6 +789,7 @@ class PrimitiveCreator:
|
||||
self.tangents = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32)
|
||||
self.blender_mesh.loops.foreach_get('tangent', self.tangents)
|
||||
self.tangents = self.tangents.reshape(len(self.blender_mesh.loops), 3)
|
||||
self.tangents = np.round(self.tangents, ROUNDING_DIGIT)
|
||||
|
||||
# Transform for skinning
|
||||
if self.armature and self.blender_object:
|
||||
@ -789,6 +797,7 @@ class PrimitiveCreator:
|
||||
tangent_transform = apply_matrix.to_quaternion().to_matrix()
|
||||
self.tangents = PrimitiveCreator.apply_mat_to_all(tangent_transform, self.tangents)
|
||||
PrimitiveCreator.normalize_vecs(self.tangents)
|
||||
self.tangents = np.round(self.tangents, ROUNDING_DIGIT)
|
||||
|
||||
if self.export_settings['gltf_yup']:
|
||||
PrimitiveCreator.zup2yup(self.tangents)
|
||||
|
@ -308,11 +308,19 @@ def previous_node(socket):
|
||||
return prev_socket.node
|
||||
return None
|
||||
|
||||
#TODOExt is this the same as __get_tex_from_socket from gather_image ?
|
||||
def has_image_node_from_socket(socket):
|
||||
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 False
|
||||
return True
|
||||
return None
|
||||
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_socket,
|
||||
clearcoat_roughness_slots,
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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_socket,
|
||||
clearcoat_roughness_slots,
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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")
|
||||
if emissive is None:
|
||||
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
|
||||
|
||||
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(
|
||||
sheenColor_socket,
|
||||
(sheenColor_socket,),
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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(
|
||||
sheenRoughness_socket,
|
||||
(sheenRoughness_socket,),
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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 ....exp import gltf2_blender_get
|
||||
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):
|
||||
specular_extension = {}
|
||||
@ -36,9 +37,10 @@ def export_original_specular(blender_material, export_settings):
|
||||
|
||||
# Texture
|
||||
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,),
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
specular_extension['specularTexture'] = original_specular_texture
|
||||
@ -58,9 +60,10 @@ def export_original_specular(blender_material, export_settings):
|
||||
|
||||
# Texture
|
||||
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,),
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
specular_extension['specularColorTexture'] = original_specularcolor_texture
|
||||
@ -86,12 +89,11 @@ def export_specular(blender_material, export_settings):
|
||||
if base_color_socket is None:
|
||||
return None, None
|
||||
|
||||
# TODOExt replace by __has_image_node_from_socket calls
|
||||
specular_not_linked = isinstance(specular_socket, bpy.types.NodeSocket) and not specular_socket.is_linked
|
||||
specular_tint_not_linked = isinstance(specular_tint_socket, bpy.types.NodeSocket) and not specular_tint_socket.is_linked
|
||||
base_color_not_linked = isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked
|
||||
transmission_not_linked = isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked
|
||||
ior_not_linked = isinstance(ior_socket, bpy.types.NodeSocket) and not ior_socket.is_linked
|
||||
specular_not_linked = not image_tex_is_valid_from_socket(specular_socket)
|
||||
specular_tint_not_linked = not image_tex_is_valid_from_socket(specular_tint_socket)
|
||||
base_color_not_linked = not image_tex_is_valid_from_socket(base_color_socket)
|
||||
transmission_not_linked = not image_tex_is_valid_from_socket(transmission_socket)
|
||||
ior_not_linked = not image_tex_is_valid_from_socket(ior_socket)
|
||||
|
||||
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
|
||||
@ -149,9 +151,10 @@ def export_specular(blender_material, export_settings):
|
||||
if base_color_not_linked:
|
||||
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,
|
||||
sockets,
|
||||
(),
|
||||
export_settings,
|
||||
filter_type='ANY')
|
||||
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(
|
||||
transmission_socket,
|
||||
transmission_slots,
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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(
|
||||
thicknesss_socket,
|
||||
thickness_slots,
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
if has_thickness_texture:
|
||||
|
@ -28,6 +28,11 @@ class FillWhite:
|
||||
"""Fills a channel with all ones (1.0)."""
|
||||
pass
|
||||
|
||||
class FillWith:
|
||||
"""Fills a channel with all same values"""
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
class StoreData:
|
||||
def __init__(self, data):
|
||||
"""Store numeric data (not an image channel"""
|
||||
@ -99,6 +104,9 @@ class ExportImage:
|
||||
def fill_white(self, dst_chan: Channel):
|
||||
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:
|
||||
return chan in self.fills
|
||||
|
||||
@ -183,6 +191,8 @@ class ExportImage:
|
||||
for dst_chan, fill in self.fills.items():
|
||||
if isinstance(fill, FillImage) and fill.image == image:
|
||||
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
|
||||
|
||||
|
@ -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.exp.gltf2_io_user_extensions import export_user_extensions
|
||||
from ..gltf2_blender_gather_cache import cached
|
||||
from . import gltf2_blender_search_node_tree
|
||||
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage
|
||||
from ..gltf2_blender_get import get_tex_from_socket
|
||||
|
||||
@cached
|
||||
def gather_image(
|
||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
export_settings):
|
||||
if not __filter_image(blender_shader_sockets, export_settings):
|
||||
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():
|
||||
# The export image has no data
|
||||
return None, None
|
||||
@ -174,24 +175,32 @@ def __gather_uri(image_data, mime_type, name, export_settings):
|
||||
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
|
||||
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
|
||||
# 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
|
||||
if any([socket.name == "Specular" and socket.node.type == "BSDF_PRINCIPLED" for socket in sockets]):
|
||||
return __get_image_data_specular(sockets, results, export_settings)
|
||||
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
|
||||
Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
|
||||
"""
|
||||
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):
|
||||
# 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
|
||||
@ -242,9 +251,15 @@ def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
|
||||
# Since metal/roughness are always used together, make sure
|
||||
# the other channel is filled.
|
||||
if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
|
||||
composed_image.fill_white(Channel.G)
|
||||
if default_roughness is not None:
|
||||
composed_image.fill_with(Channel.G, default_roughness)
|
||||
else:
|
||||
composed_image.fill_white(Channel.G)
|
||||
elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
|
||||
composed_image.fill_white(Channel.B)
|
||||
if default_metallic is not None:
|
||||
composed_image.fill_with(Channel.B, default_metallic)
|
||||
else:
|
||||
composed_image.fill_white(Channel.B)
|
||||
else:
|
||||
# copy full image...eventually following sockets might overwrite things
|
||||
composed_image = ExportImage.from_blender_image(result.shader_node.image)
|
||||
@ -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")
|
||||
|
||||
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 = {
|
||||
0: "specular",
|
||||
@ -281,7 +296,7 @@ def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
@ -308,16 +323,6 @@ def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
|
||||
|
||||
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:
|
||||
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)
|
||||
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_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, 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)
|
||||
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)
|
||||
|
||||
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):
|
||||
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):
|
||||
return None
|
||||
return None, None
|
||||
|
||||
metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
|
||||
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)
|
||||
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:
|
||||
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):
|
||||
return None
|
||||
return None, default_sockets
|
||||
result = (occlusion, metallic_roughness)
|
||||
elif not hasMetal:
|
||||
result = (occlusion, roughness_socket)
|
||||
default_sockets = (metallic_socket,)
|
||||
elif not hasRough:
|
||||
result = (occlusion, metallic_socket)
|
||||
default_sockets = (roughness_socket,)
|
||||
else:
|
||||
result = (occlusion, roughness_socket, metallic_socket)
|
||||
default_sockets = ()
|
||||
|
||||
if not gltf2_blender_gather_texture_info.check_same_size_images(result):
|
||||
print_console("INFO",
|
||||
"Occlusion and metal-roughness texture will be exported separately "
|
||||
"(use same-sized images if you want them combined)")
|
||||
return None
|
||||
return None, ()
|
||||
|
||||
# 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:
|
||||
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")
|
||||
if occlusion is None:
|
||||
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,
|
||||
orm_texture or (occlusion,),
|
||||
default_sockets,
|
||||
export_settings)
|
||||
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 ...exp import gltf2_blender_get
|
||||
from ..gltf2_blender_gather_cache import cached
|
||||
from . import gltf2_blender_search_node_tree
|
||||
from . import gltf2_blender_gather_texture_info
|
||||
from ..gltf2_blender_get import image_tex_is_valid_from_socket
|
||||
from .gltf2_blender_gather_texture_info import gather_texture_info
|
||||
|
||||
@cached
|
||||
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
|
||||
inputs = tuple(
|
||||
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:
|
||||
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):
|
||||
@ -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")
|
||||
roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
|
||||
|
||||
hasMetal = metallic_socket is not None and __has_image_node_from_socket(metallic_socket)
|
||||
hasRough = roughness_socket is not None and __has_image_node_from_socket(roughness_socket)
|
||||
hasMetal = metallic_socket is not None and image_tex_is_valid_from_socket(metallic_socket)
|
||||
hasRough = roughness_socket is not None and image_tex_is_valid_from_socket(roughness_socket)
|
||||
|
||||
default_sockets = ()
|
||||
if not hasMetal and not hasRough:
|
||||
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
|
||||
texture_input = (metallic_roughness,)
|
||||
elif not hasMetal:
|
||||
texture_input = (roughness_socket,)
|
||||
default_sockets = (metallic_socket,)
|
||||
elif not hasRough:
|
||||
texture_input = (metallic_socket,)
|
||||
default_sockets = (roughness_socket,)
|
||||
else:
|
||||
texture_input = (metallic_socket, roughness_socket)
|
||||
default_sockets = ()
|
||||
|
||||
return gltf2_blender_gather_texture_info.gather_texture_info(
|
||||
return gather_texture_info(
|
||||
texture_input[0],
|
||||
orm_texture or texture_input,
|
||||
default_sockets,
|
||||
export_settings,
|
||||
)
|
||||
|
||||
@ -160,14 +165,6 @@ def __gather_roughness_factor(blender_material, export_settings):
|
||||
return fac if fac != 1 else 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():
|
||||
return gltf2_io.MaterialPBRMetallicRoughness(
|
||||
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(
|
||||
sockets[0],
|
||||
sockets,
|
||||
(),
|
||||
export_settings,
|
||||
)
|
||||
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 ..gltf2_blender_gather_cache import cached
|
||||
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
|
||||
|
||||
@cached
|
||||
def gather_texture(
|
||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
export_settings):
|
||||
"""
|
||||
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):
|
||||
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(
|
||||
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):
|
||||
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:
|
||||
gltf2_io_debug.print_console("WARNING",
|
||||
"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)
|
||||
|
||||
|
||||
def __gather_source(blender_shader_sockets, export_settings):
|
||||
return gltf2_blender_gather_image.gather_image(blender_shader_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]
|
||||
def __gather_source(blender_shader_sockets, default_sockets, export_settings):
|
||||
return gltf2_blender_gather_image.gather_image(blender_shader_sockets, default_sockets, export_settings)
|
||||
|
@ -8,7 +8,7 @@ from ....io.com import gltf2_io
|
||||
from ....io.com.gltf2_io_extensions import Extension
|
||||
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||
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_cache import cached
|
||||
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
|
||||
# blender_shader_sockets would be the (O,R,M) sockets.
|
||||
|
||||
def gather_texture_info(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
|
||||
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', filter_type, export_settings)
|
||||
# Default socket parameter is used when there is a mapping between channels, and one of the channel is not a texture
|
||||
# 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'):
|
||||
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'):
|
||||
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
|
||||
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, default_sockets, 'OCCLUSION', filter_type, export_settings)
|
||||
|
||||
|
||||
@cached
|
||||
def __gather_texture_info_helper(
|
||||
primary_socket: bpy.types.NodeSocket,
|
||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
default_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
kind: str,
|
||||
filter_type: str,
|
||||
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)
|
||||
|
||||
index, factor = __gather_index(blender_shader_sockets, export_settings)
|
||||
index, factor = __gather_index(blender_shader_sockets, default_sockets, export_settings)
|
||||
|
||||
fields = {
|
||||
'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):
|
||||
if primary_socket is None:
|
||||
return False
|
||||
if __get_tex_from_socket(primary_socket) is None:
|
||||
if get_tex_from_socket(primary_socket) is None:
|
||||
return False
|
||||
if not blender_shader_sockets:
|
||||
return False
|
||||
@ -80,12 +85,12 @@ def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, e
|
||||
return False
|
||||
if filter_type == "ALL":
|
||||
# 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
|
||||
return False
|
||||
elif filter_type == "ANY":
|
||||
# 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
|
||||
elif filter_type == "NONE":
|
||||
# No check
|
||||
@ -136,9 +141,9 @@ def __gather_occlusion_strength(primary_socket, export_settings):
|
||||
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
|
||||
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):
|
||||
@ -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,
|
||||
# [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)
|
||||
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
|
||||
|
||||
# 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(
|
||||
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
|
||||
@ -199,7 +193,7 @@ def check_same_size_images(
|
||||
|
||||
sizes = set()
|
||||
for socket in blender_shader_sockets:
|
||||
tex = __get_tex_from_socket(socket)
|
||||
tex = get_tex_from_socket(socket)
|
||||
if tex is None:
|
||||
return False
|
||||
size = tex.shader_node.image.size
|
||||
|
@ -152,5 +152,5 @@ GLTF_DATA_TYPE_MAT4 = "MAT4"
|
||||
|
||||
GLTF_IOR = 1.5
|
||||
|
||||
# Rounding digit used for normal rounding
|
||||
NORMALS_ROUNDING_DIGIT = 4
|
||||
# Rounding digit used for normal/tangent rounding
|
||||
ROUNDING_DIGIT = 4
|
||||
|
@ -113,7 +113,6 @@ class PIE_MT_SelectionsEM(Menu):
|
||||
class PIE_MT_SelectAllBySelection(Menu):
|
||||
bl_idname = "PIE_MT_selectallbyselection"
|
||||
bl_label = "Verts Edges Faces"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -194,7 +193,6 @@ class PIE_OT_vertsedgesfacesop(Operator):
|
||||
class PIE_MT_SelectLoopSelection(Menu):
|
||||
bl_idname = "OBJECT_MT_selectloopselection"
|
||||
bl_label = "Verts Edges Faces"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
Loading…
Reference in New Issue
Block a user