Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_scene_gltf2/blender/exp')
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_export_keys.py1
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_extract.py43
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather.py9
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py2
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py22
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py13
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py60
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py5
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_image.py95
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py260
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py81
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py61
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py35
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py8
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py68
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py168
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py47
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py6
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py18
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py75
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py31
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py58
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py17
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py44
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py55
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_get.py27
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_image.py46
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py94
28 files changed, 1124 insertions, 325 deletions
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
index 812db3f9..96f97af1 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -20,7 +20,6 @@ RENDERABLE = 'gltf_renderable'
ACTIVE_COLLECTION = 'gltf_active_collection'
SKINS = 'gltf_skins'
DEF_BONES_ONLY = 'gltf_def_bones'
-DISPLACEMENT = 'gltf_displacement'
FORCE_SAMPLING = 'gltf_force_sampling'
FRAME_RANGE = 'gltf_frame_range'
FRAME_STEP = 'gltf_frame_step'
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index d4f34126..93947acb 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -39,6 +39,18 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
if export_settings[gltf2_blender_export_keys.COLORS]:
color_max = len(blender_mesh.vertex_colors)
+ colors_attributes = []
+ rendered_color_idx = blender_mesh.attributes.render_color_index
+
+ if color_max > 0:
+ colors_attributes.append(rendered_color_idx)
+ # Then find other ones
+ colors_attributes.extend([
+ i for i in range(len(blender_mesh.color_attributes)) if i != rendered_color_idx \
+ and blender_mesh.vertex_colors.find(blender_mesh.color_attributes[i].name) != -1
+ ])
+
+
armature = None
skin = None
if blender_vertex_groups and export_settings[gltf2_blender_export_keys.SKINS]:
@@ -110,7 +122,7 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)]
for uv_i in range(tex_coord_max):
dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)]
- for col_i in range(color_max):
+ for col_i, _ in enumerate(colors_attributes):
dot_fields += [
('color%dr' % col_i, np.float32),
('color%dg' % col_i, np.float32),
@@ -163,8 +175,12 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
dots['uv%dy' % uv_i] = uvs[:, 1]
del uvs
- for col_i in range(color_max):
- colors = __get_colors(blender_mesh, col_i)
+ colors_types = []
+ for col_i, blender_col_i in enumerate(colors_attributes):
+ colors, colors_type, domain = __get_colors(blender_mesh, col_i, blender_col_i)
+ if domain == "POINT":
+ colors = colors[dots['vertex_index']]
+ colors_types.append(colors_type)
dots['color%dr' % col_i] = colors[:, 0]
dots['color%dg' % col_i] = colors[:, 1]
dots['color%db' % col_i] = colors[:, 2]
@@ -251,13 +267,16 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i]
attributes['TEXCOORD_%d' % tex_coord_i] = uvs
- for color_i in range(color_max):
+ for color_i, _ in enumerate(colors_attributes):
colors = np.empty((len(prim_dots), 4), dtype=np.float32)
colors[:, 0] = prim_dots['color%dr' % color_i]
colors[:, 1] = prim_dots['color%dg' % color_i]
colors[:, 2] = prim_dots['color%db' % color_i]
colors[:, 3] = prim_dots['color%da' % color_i]
- attributes['COLOR_%d' % color_i] = colors
+ attributes['COLOR_%d' % color_i] = {}
+ attributes['COLOR_%d' % color_i]["data"] = colors
+
+ attributes['COLOR_%d' % color_i]["norm"] = colors_types[color_i] == "BYTE_COLOR"
if skin:
joints = [[] for _ in range(num_joint_sets)]
@@ -525,13 +544,15 @@ def __get_uvs(blender_mesh, uv_i):
return uvs
-def __get_colors(blender_mesh, color_i):
- colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32)
- layer = blender_mesh.vertex_colors[color_i]
- blender_mesh.color_attributes[layer.name].data.foreach_get('color', colors)
- colors = colors.reshape(len(blender_mesh.loops), 4)
+def __get_colors(blender_mesh, color_i, blender_color_i):
+ if blender_mesh.color_attributes[blender_color_i].domain == "POINT":
+ colors = np.empty(len(blender_mesh.vertices) * 4, dtype=np.float32) #POINT
+ else:
+ colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) #CORNER
+ blender_mesh.color_attributes[blender_color_i].data.foreach_get('color', colors)
+ colors = colors.reshape(-1, 4)
# colors are already linear, no need to switch color space
- return colors
+ return colors, blender_mesh.color_attributes[blender_color_i].data_type, blender_mesh.color_attributes[blender_color_i].domain
def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index 6153bc33..721ef115 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -59,6 +59,8 @@ def __gather_scene(blender_scene, export_settings):
# Now, we can filter tree if needed
vtree.filter()
+ vtree.variants_reset_to_original()
+
export_user_extensions('vtree_after_filter_hook', export_settings, vtree)
export_settings['vtree'] = vtree
@@ -94,9 +96,12 @@ def __gather_animations(blender_scene, export_settings):
if export_settings['gltf_nla_strips'] is False:
# Fake an animation with all animations of the scene
merged_tracks = {}
- merged_tracks['Animation'] = []
+ merged_tracks_name = 'Animation'
+ if(len(export_settings['gltf_nla_strips_merged_animation_name']) > 0):
+ merged_tracks_name = export_settings['gltf_nla_strips_merged_animation_name']
+ merged_tracks[merged_tracks_name] = []
for idx, animation in enumerate(animations):
- merged_tracks['Animation'].append(idx)
+ merged_tracks[merged_tracks_name].append(idx)
to_delete_idx = []
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
index 98ae8b82..3e67f1f7 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
@@ -37,7 +37,7 @@ def gather_animation_channels(obj_uuid: int,
if blender_action.use_frame_range is True:
bake_range_start = blender_action.frame_start
bake_range_end = blender_action.frame_end
- force_range = True # keyframe_points is read-only, we cant restrict here
+ force_range = True # keyframe_points is read-only, we can't restrict here
else:
groups = __get_channel_groups(blender_action, blender_object, export_settings)
# Note: channels has some None items only for SK if some SK are not animated
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
index e1ed19ea..53d78945 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
@@ -398,11 +398,17 @@ def gather_keyframes(blender_obj_uuid: str,
key.set_first_tangent()
else:
# otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
- # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
- # normally
+ # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
+ # normally, but only works for locally linear transformation. The more non-linear a transform, the
+ # more imprecise this method is.
+ # We could use any other (v1, t1) for which (v1 - v0) / (t1 - t0) equals the tangent. By using t+1
+ # for both in and out tangents, we guarantee that (even if there are errors or numerical imprecisions)
+ # symmetrical control points translate to symmetrical tangents.
+ # Note: I am not sure that linearity is never broken with quaternions and their normalization.
+ # Especially at sign swap it might occur that the value gets negated but the control point not.
+ # I have however not once encountered an issue with this.
key.in_tangent = [
- c.keyframe_points[i].co[1] + ((c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1]
- ) / (frame - frames[i - 1]))
+ c.keyframe_points[i].co[1] + (c.keyframe_points[i].handle_left[1] - c.keyframe_points[i].co[1]) / (c.keyframe_points[i].handle_left[0] - c.keyframe_points[i].co[0])
for c in channels if c is not None
]
# Construct the out tangent
@@ -410,12 +416,10 @@ def gather_keyframes(blender_obj_uuid: str,
# end out-tangent should become all zero
key.set_last_tangent()
else:
- # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
- # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
- # normally
+ # otherwise construct an in tangent coordinate from the keyframes control points.
+ # This happens the same way how in tangents are handled above.
key.out_tangent = [
- c.keyframe_points[i].co[1] + ((c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]
- ) / (frames[i + 1] - frame))
+ c.keyframe_points[i].co[1] + (c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]) / (c.keyframe_points[i].handle_right[0] - c.keyframe_points[i].co[0])
for c in channels if c is not None
]
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
index 1ee98a29..5c8011ed 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
@@ -414,6 +414,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
transform = parent_inverse
values = []
+ fps = bpy.context.scene.render.fps
for keyframe in keyframes:
# Transform the data and build gltf control points
value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform, need_rotation_correction)
@@ -426,11 +427,11 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform, need_rotation_correction)
if is_yup and blender_object_if_armature is None:
in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
- # the tangent in glTF is relative to the keyframe value
+ # the tangent in glTF is relative to the keyframe value and uses seconds
if not isinstance(value, list):
- in_tangent = value - in_tangent
+ in_tangent = fps * (in_tangent - value)
else:
- in_tangent = [value[i] - in_tangent[i] for i in range(len(value))]
+ in_tangent = [fps * (in_tangent[i] - value[i]) for i in range(len(value))]
keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value # append
if keyframe.out_tangent is not None:
@@ -438,11 +439,11 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform, need_rotation_correction)
if is_yup and blender_object_if_armature is None:
out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
- # the tangent in glTF is relative to the keyframe value
+ # the tangent in glTF is relative to the keyframe value and uses seconds
if not isinstance(value, list):
- out_tangent = value - out_tangent
+ out_tangent = fps * (out_tangent - value)
else:
- out_tangent = [value[i] - out_tangent[i] for i in range(len(value))]
+ out_tangent = [fps * (out_tangent[i] - value[i]) for i in range(len(value))]
keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent) # append
values += keyframe_value
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
index 20a919dc..bdb2ee00 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
@@ -64,21 +64,24 @@ def gather_animations( obj_uuid: int,
# No TRS animation are found for this object.
# But we need to bake, in case we export selection
# (Only when force sampling is ON)
- # If force sampling is OFF, can lead to inconsistant export anyway
+ # If force sampling is OFF, can lead to inconsistent export anyway
if export_settings['gltf_selected'] is True and blender_object.type != "ARMATURE" and export_settings['gltf_force_sampling'] is True:
- channels = __gather_channels_baked(obj_uuid, export_settings)
- if channels is not None:
- animation = gltf2_io.Animation(
- channels=channels,
- extensions=None, # as other animations
- extras=None, # Because there is no animation to get extras from
- name=blender_object.name, # Use object name as animation name
- samplers=[]
- )
-
- __link_samplers(animation, export_settings)
- if animation is not None:
- animations.append(animation)
+ # We also have to check if this is a skinned mesh, because we don't have to force animation baking on this case
+ # (skinned meshes TRS must be ignored, says glTF specification)
+ if export_settings['vtree'].nodes[obj_uuid].skin is None:
+ channels = __gather_channels_baked(obj_uuid, export_settings)
+ if channels is not None:
+ animation = gltf2_io.Animation(
+ channels=channels,
+ extensions=None, # as other animations
+ extras=None, # Because there is no animation to get extras from
+ name=blender_object.name, # Use object name as animation name
+ samplers=[]
+ )
+
+ __link_samplers(animation, export_settings)
+ if animation is not None:
+ animations.append(animation)
elif export_settings['gltf_selected'] is True and blender_object.type == "ARMATURE":
# We need to bake all bones. Because some bone can have some constraints linking to
# some other armature bones, for example
@@ -319,20 +322,21 @@ def __get_blender_actions(blender_object: bpy.types.Object,
action_on_type[strip.action.name] = "SHAPEKEY"
# If there are only 1 armature, include all animations, even if not in NLA
- if blender_object.type == "ARMATURE":
- if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1:
- # Keep all actions on objects (no Shapekey animation)
- for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]:
- # We need to check this is an armature action
- # Checking that at least 1 bone is animated
- if not __is_armature_action(act):
- continue
- # Check if this action is already taken into account
- if act.name in blender_tracks.keys():
- continue
- blender_actions.append(act)
- blender_tracks[act.name] = None
- action_on_type[act.name] = "OBJECT"
+ if export_settings['gltf_export_anim_single_armature'] is True:
+ if blender_object.type == "ARMATURE":
+ if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1:
+ # Keep all actions on objects (no Shapekey animation)
+ for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]:
+ # We need to check this is an armature action
+ # Checking that at least 1 bone is animated
+ if not __is_armature_action(act):
+ continue
+ # Check if this action is already taken into account
+ if act.name in blender_tracks.keys():
+ continue
+ blender_actions.append(act)
+ blender_tracks[act.name] = None
+ action_on_type[act.name] = "OBJECT"
export_user_extensions('gather_actions_hook', export_settings, blender_object, blender_actions, blender_tracks, action_on_type)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
index b0e538c8..9da5cc65 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
@@ -13,7 +13,7 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
drivers = []
# Take into account skinned mesh, and mesh parented to a bone of the armature
- children_list = export_settings['vtree'].nodes[blender_armature_uuid].children
+ children_list = export_settings['vtree'].nodes[blender_armature_uuid].children.copy()
for bone in export_settings['vtree'].get_all_bones(blender_armature_uuid):
children_list.extend(export_settings['vtree'].nodes[bone].children)
@@ -74,7 +74,8 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
else:
all_sorted_channels.append(existing_idx[i])
- if len(all_sorted_channels) > 0:
+ # Checks there are some driver on SK, and that there is not only invalid drivers
+ if len(all_sorted_channels) > 0 and not all([i is None for i in all_sorted_channels]):
drivers.append((child_uuid, tuple(all_sorted_channels)))
return tuple(drivers)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
index 0dfce9f9..81e79a50 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -11,7 +11,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
from io_scene_gltf2.io.exp import gltf2_io_binary_data
from io_scene_gltf2.io.exp import gltf2_io_image_data
from io_scene_gltf2.io.com import gltf2_io_debug
-from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage
+from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage, StoreImage, StoreData
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
@@ -21,26 +21,31 @@ def gather_image(
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
export_settings):
if not __filter_image(blender_shader_sockets, export_settings):
- return None
+ return None, None
image_data = __get_image_data(blender_shader_sockets, export_settings)
if image_data.empty():
# The export image has no data
- return None
+ return None, None
mime_type = __gather_mime_type(blender_shader_sockets, image_data, export_settings)
name = __gather_name(image_data, export_settings)
+ factor = None
+
if image_data.original is None:
- uri = __gather_uri(image_data, mime_type, name, export_settings)
+ uri, factor_uri = __gather_uri(image_data, mime_type, name, export_settings)
else:
# Retrieve URI relative to exported glTF files
uri = __gather_original_uri(image_data.original.filepath, export_settings)
# In case we can't retrieve image (for example packed images, with original moved)
# We don't create invalid image without uri
+ factor_uri = None
if uri is None: return None
- buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+ buffer_view, factor_buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+
+ factor = factor_uri if uri is not None else factor_buffer_view
image = __make_image(
buffer_view,
@@ -54,7 +59,7 @@ def gather_image(
export_user_extensions('gather_image_hook', export_settings, image, blender_shader_sockets)
- return image
+ return image, factor
def __gather_original_uri(original_uri, export_settings):
@@ -98,8 +103,9 @@ def __filter_image(sockets, export_settings):
@cached
def __gather_buffer_view(image_data, mime_type, name, export_settings):
if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE':
- return gltf2_io_binary_data.BinaryData(data=image_data.encode(mime_type))
- return None
+ data, factor = image_data.encode(mime_type)
+ return gltf2_io_binary_data.BinaryData(data=data), factor
+ return None, None
def __gather_extensions(sockets, export_settings):
@@ -165,13 +171,14 @@ def __gather_name(export_image, export_settings):
def __gather_uri(image_data, mime_type, name, export_settings):
if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_SEPARATE':
# as usual we just store the data in place instead of already resolving the references
+ data, factor = image_data.encode(mime_type=mime_type)
return gltf2_io_image_data.ImageData(
- data=image_data.encode(mime_type=mime_type),
+ data=data,
mime_type=mime_type,
name=name
- )
+ ), factor
- return None
+ return None, None
def __get_image_data(sockets, export_settings) -> ExportImage:
@@ -179,6 +186,18 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
# 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]
+
+ # 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)
+
+def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
+ """
+ Simple mapping
+ Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
+ """
composed_image = ExportImage()
for result, socket in zip(results, sockets):
# Assume that user know what he does, and that channels/images are already combined correctly for pbr
@@ -217,6 +236,12 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
dst_chan = Channel.R
elif socket.name == 'Clearcoat Roughness':
dst_chan = Channel.G
+ elif socket.name == 'Thickness': # For KHR_materials_volume
+ dst_chan = Channel.G
+ elif socket.name == "Specular": # For original KHR_material_specular
+ dst_chan = Channel.A
+ elif socket.name == "Sigma": # For KHR_materials_sheen
+ dst_chan = Channel.A
if dst_chan is not None:
composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
@@ -243,6 +268,54 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
return composed_image
+def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
+ """
+ calculating Specular Texture, settings needed data
+ """
+ from io_scene_gltf2.blender.exp.gltf2_blender_texture_specular import specular_calculation
+ composed_image = ExportImage()
+ composed_image.set_calc(specular_calculation)
+
+ 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
+
+ mapping = {
+ 0: "specular",
+ 1: "specular_tint",
+ 2: "base_color",
+ 3: "transmission"
+ }
+
+ for idx, result in enumerate(results):
+ if __get_tex_from_socket(sockets[idx], export_settings):
+
+ composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
+
+ # rudimentarily try follow the node tree to find the correct image data.
+ src_chan = None if idx == 2 else Channel.R
+ for elem in result.path:
+ if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateColor):
+ src_chan = {
+ 'Red': Channel.R,
+ 'Green': Channel.G,
+ 'Blue': Channel.B,
+ }[elem.from_socket.name]
+ if elem.from_socket.name == 'Alpha':
+ src_chan = Channel.A
+ # For base_color, keep all channels, as this is a Vec, not scalar
+ if idx != 2:
+ composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+ else:
+ if src_chan is not None:
+ composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+
+ else:
+ composed_image.store_data(mapping[idx], sockets[idx].default_value, type="Data")
+
+ 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(
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
index 56c3acff..c0d17fd3 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -1,21 +1,27 @@
# SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
from copy import deepcopy
import bpy
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
from io_scene_gltf2.io.com import gltf2_io
-from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info, gltf2_blender_export_keys
-from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
-
from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metallic_roughness
-from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.blender.exp import gltf2_blender_get
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_volume import export_volume
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_emission import export_emission_factor, \
+ export_emission_texture, export_emission_strength_extension
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_sheen import export_sheen
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_specular import export_specular
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_transmission import export_transmission
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_clearcoat import export_clearcoat
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_ior import export_ior
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
@cached
def get_material_cache_key(blender_material, active_uvmap_index, export_settings):
@@ -39,24 +45,31 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
if not __filter_material(blender_material, export_settings):
return None
- mat_unlit = __gather_material_unlit(blender_material, active_uvmap_index, export_settings)
+ mat_unlit = __export_unlit(blender_material, active_uvmap_index, export_settings)
if mat_unlit is not None:
export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
return mat_unlit
orm_texture = __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, 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)
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:
+ # Strength is set on extension
+ emission_strength = max(emissive_factor)
+ emissive_factor = [f / emission_strength for f in emissive_factor]
+
+
base_material = gltf2_io.Material(
alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
alpha_mode=__gather_alpha_mode(blender_material, export_settings),
double_sided=__gather_double_sided(blender_material, export_settings),
- emissive_factor=__gather_emissive_factor(blender_material, export_settings),
+ emissive_factor=emissive_factor,
emissive_texture=emissive_texture,
extensions=extensions,
extras=__gather_extras(blender_material, export_settings),
@@ -106,6 +119,14 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
material.extensions["KHR_materials_clearcoat"].extension['clearcoatNormalTexture'].tex_coord = active_uvmap_index
elif tex == "transmissionTexture": #TODO not tested yet
material.extensions["KHR_materials_transmission"].extension['transmissionTexture'].tex_coord = active_uvmap_index
+ elif tex == "specularTexture":
+ material.extensions["KHR_materials_specular"].extension['specularTexture'].tex_coord = active_uvmap_index
+ elif tex == "specularColorTexture":
+ material.extensions["KHR_materials_specular"].extension['specularColorTexture'].tex_coord = active_uvmap_index
+ elif tex == "sheenColorTexture":
+ material.extensions["KHR_materials_sheen"].extension['sheenColorTexture'].tex_coord = active_uvmap_index
+ elif tex == "sheenRoughnessTexture":
+ material.extensions["KHR_materials_sheen"].extension['sheenRoughnessTexture'].tex_coord = active_uvmap_index
# If material is not using active UVMap, we need to return the same material,
# Even if multiples meshes are using different active UVMap
@@ -188,72 +209,61 @@ def __gather_double_sided(blender_material, export_settings):
def __gather_emissive_factor(blender_material, export_settings):
- emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
- if emissive_socket is None:
- emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
- if isinstance(emissive_socket, bpy.types.NodeSocket):
- if export_settings['gltf_image_format'] != "NONE":
- factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
- else:
- factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
-
- if factor is None and emissive_socket.is_linked:
- # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
- # we have to manually set it to all ones.
- factor = [1.0, 1.0, 1.0]
-
- if factor is None: factor = [0.0, 0.0, 0.0]
-
- # Handle Emission Strength
- strength_socket = None
- if emissive_socket.node.type == 'EMISSION':
- strength_socket = emissive_socket.node.inputs['Strength']
- elif 'Emission Strength' in emissive_socket.node.inputs:
- strength_socket = emissive_socket.node.inputs['Emission Strength']
- strength = (
- gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
- if strength_socket is not None
- else None
- )
- if strength is not None:
- factor = [f * strength for f in factor]
-
- # Clamp to range [0,1]
- factor = [min(1.0, f) for f in factor]
-
- if factor == [0, 0, 0]: factor = None
-
- return factor
-
- return None
-
+ return export_emission_factor(blender_material, export_settings)
def __gather_emissive_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)
- return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+ return export_emission_texture(blender_material, export_settings)
-def __gather_extensions(blender_material, export_settings):
+def __gather_extensions(blender_material, emissive_factor, export_settings):
extensions = {}
# KHR_materials_clearcoat
actives_uvmaps = []
- clearcoat_extension, use_actives_uvmap_clearcoat = __gather_clearcoat_extension(blender_material, export_settings)
+ clearcoat_extension, use_actives_uvmap_clearcoat = export_clearcoat(blender_material, export_settings)
if clearcoat_extension:
extensions["KHR_materials_clearcoat"] = clearcoat_extension
actives_uvmaps.extend(use_actives_uvmap_clearcoat)
# KHR_materials_transmission
- transmission_extension, use_actives_uvmap_transmission = __gather_transmission_extension(blender_material, export_settings)
+ transmission_extension, use_actives_uvmap_transmission = export_transmission(blender_material, export_settings)
if transmission_extension:
extensions["KHR_materials_transmission"] = transmission_extension
actives_uvmaps.extend(use_actives_uvmap_transmission)
+ # KHR_materials_emissive_strength
+ if any([i>1.0 for i in emissive_factor or []]):
+ emissive_strength_extension = export_emission_strength_extension(emissive_factor, export_settings)
+ if emissive_strength_extension:
+ extensions["KHR_materials_emissive_strength"] = emissive_strength_extension
+
+ # KHR_materials_volume
+
+ volume_extension, use_actives_uvmap_volume_thickness = export_volume(blender_material, export_settings)
+ if volume_extension:
+ extensions["KHR_materials_volume"] = volume_extension
+ actives_uvmaps.extend(use_actives_uvmap_volume_thickness)
+
+ # KHR_materials_specular
+ specular_extension, use_actives_uvmap_specular = export_specular(blender_material, export_settings)
+ if specular_extension:
+ extensions["KHR_materials_specular"] = specular_extension
+ actives_uvmaps.extend(use_actives_uvmap_specular)
+
+ # KHR_materials_sheen
+ sheen_extension, use_actives_uvmap_sheen = export_sheen(blender_material, export_settings)
+ if sheen_extension:
+ extensions["KHR_materials_sheen"] = sheen_extension
+ actives_uvmaps.extend(use_actives_uvmap_sheen)
+
+ # KHR_materials_ior
+ # Keep this extension at the end, because we export it only if some others are exported
+ ior_extension = export_ior(blender_material, extensions, export_settings)
+ if ior_extension:
+ extensions["KHR_materials_ior"] = ior_extension
+
return extensions, actives_uvmaps if extensions else None
@@ -271,7 +281,7 @@ def __gather_normal_texture(blender_material, export_settings):
normal = gltf2_blender_get.get_socket(blender_material, "Normal")
if normal is None:
normal = gltf2_blender_get.get_socket_old(blender_material, "Normal")
- normal_texture, use_active_uvmap_normal = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+ normal_texture, use_active_uvmap_normal, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
normal,
(normal,),
export_settings)
@@ -283,20 +293,20 @@ def __gather_orm_texture(blender_material, export_settings):
# If not fully shared, return None, so the images will be cached and processed separately.
occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
- if occlusion is None or not __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")
- if occlusion is None or not __has_image_node_from_socket(occlusion):
+ if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
return None
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 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)
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 gltf2_blender_get.has_image_node_from_socket(metallic_roughness):
return None
result = (occlusion, metallic_roughness)
elif not hasMetal:
@@ -313,7 +323,7 @@ def __gather_orm_texture(blender_material, export_settings):
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, export_settings)
if info is None:
return None
@@ -323,7 +333,7 @@ def __gather_occlusion_texture(blender_material, orm_texture, 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_texture, use_active_uvmap_occlusion, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
occlusion,
orm_texture or (occlusion,),
export_settings)
@@ -336,129 +346,7 @@ def __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settin
orm_texture,
export_settings)
-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 __gather_clearcoat_extension(blender_material, export_settings):
- clearcoat_enabled = False
- has_clearcoat_texture = False
- has_clearcoat_roughness_texture = False
-
- clearcoat_extension = {}
- clearcoat_roughness_slots = ()
-
- clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
- clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
- clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
-
- if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
- clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
- clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
- elif __has_image_node_from_socket(clearcoat_socket):
- fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
- # default value in glTF is 0.0, but if there is a texture without factor, use 1
- clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
- has_clearcoat_texture = True
- clearcoat_enabled = True
-
- if not clearcoat_enabled:
- return None, None
-
- if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
- clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
- elif __has_image_node_from_socket(clearcoat_roughness_socket):
- fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
- # default value in glTF is 0.0, but if there is a texture without factor, use 1
- clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
- has_clearcoat_roughness_texture = True
-
- # Pack clearcoat (R) and clearcoatRoughness (G) channels.
- if has_clearcoat_texture and has_clearcoat_roughness_texture:
- clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
- elif has_clearcoat_texture:
- clearcoat_roughness_slots = (clearcoat_socket,)
- elif has_clearcoat_roughness_texture:
- clearcoat_roughness_slots = (clearcoat_roughness_socket,)
-
- use_actives_uvmaps = []
-
- if len(clearcoat_roughness_slots) > 0:
- if has_clearcoat_texture:
- 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
- if clearcoat_texture_use_active_uvmap:
- use_actives_uvmaps.append("clearcoatTexture")
- if has_clearcoat_roughness_texture:
- 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
- if clearcoat_roughness_texture_use_active_uvmap:
- use_actives_uvmaps.append("clearcoatRoughnessTexture")
- if __has_image_node_from_socket(clearcoat_normal_socket):
- clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
- clearcoat_normal_socket,
- (clearcoat_normal_socket,),
- export_settings
- )
- clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
- if clearcoat_normal_texture_use_active_uvmap:
- use_actives_uvmaps.append("clearcoatNormalTexture")
-
- return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
-
-def __gather_transmission_extension(blender_material, export_settings):
- transmission_enabled = False
- has_transmission_texture = False
-
- transmission_extension = {}
- transmission_slots = ()
-
- transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
-
- if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
- transmission_extension['transmissionFactor'] = transmission_socket.default_value
- transmission_enabled = transmission_extension['transmissionFactor'] > 0
- elif __has_image_node_from_socket(transmission_socket):
- transmission_extension['transmissionFactor'] = 1.0
- has_transmission_texture = True
- transmission_enabled = True
-
- if not transmission_enabled:
- return None, None
-
- # Pack transmission channel (R).
- if has_transmission_texture:
- transmission_slots = (transmission_socket,)
-
- use_actives_uvmaps = []
-
- if len(transmission_slots) > 0:
- combined_texture, use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
- transmission_socket,
- transmission_slots,
- export_settings,
- )
- if has_transmission_texture:
- transmission_extension['transmissionTexture'] = combined_texture
- if use_active_uvmap:
- use_actives_uvmaps.append("transmissionTexture")
-
- return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps
-
-
-def __gather_material_unlit(blender_material, active_uvmap_index, export_settings):
+def __export_unlit(blender_material, active_uvmap_index, export_settings):
gltf2_unlit = gltf2_blender_gather_materials_unlit
info = gltf2_unlit.detect_shadeless_material(blender_material, export_settings)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
new file mode 100644
index 00000000..65c164b4
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_clearcoat(blender_material, export_settings):
+ clearcoat_enabled = False
+ has_clearcoat_texture = False
+ has_clearcoat_roughness_texture = False
+
+ clearcoat_extension = {}
+ clearcoat_roughness_slots = ()
+
+ clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
+ clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
+ clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
+
+ if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
+ clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
+ clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
+ elif gltf2_blender_get.has_image_node_from_socket(clearcoat_socket):
+ fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
+ # default value in glTF is 0.0, but if there is a texture without factor, use 1
+ clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
+ has_clearcoat_texture = True
+ clearcoat_enabled = True
+
+ if not clearcoat_enabled:
+ return None, None
+
+ if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
+ clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
+ elif gltf2_blender_get.has_image_node_from_socket(clearcoat_roughness_socket):
+ fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
+ # default value in glTF is 0.0, but if there is a texture without factor, use 1
+ clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
+ has_clearcoat_roughness_texture = True
+
+ # Pack clearcoat (R) and clearcoatRoughness (G) channels.
+ if has_clearcoat_texture and has_clearcoat_roughness_texture:
+ clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
+ elif has_clearcoat_texture:
+ clearcoat_roughness_slots = (clearcoat_socket,)
+ elif has_clearcoat_roughness_texture:
+ clearcoat_roughness_slots = (clearcoat_roughness_socket,)
+
+ use_actives_uvmaps = []
+
+ if len(clearcoat_roughness_slots) > 0:
+ if has_clearcoat_texture:
+ 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
+ if clearcoat_texture_use_active_uvmap:
+ use_actives_uvmaps.append("clearcoatTexture")
+ if has_clearcoat_roughness_texture:
+ 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
+ if clearcoat_roughness_texture_use_active_uvmap:
+ use_actives_uvmaps.append("clearcoatRoughnessTexture")
+ if gltf2_blender_get.has_image_node_from_socket(clearcoat_normal_socket):
+ clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+ clearcoat_normal_socket,
+ (clearcoat_normal_socket,),
+ export_settings
+ )
+ clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
+ if clearcoat_normal_texture_use_active_uvmap:
+ use_actives_uvmaps.append("clearcoatNormalTexture")
+
+ return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
new file mode 100644
index 00000000..562fc19d
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_emission_factor(blender_material, export_settings):
+ emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
+ if emissive_socket is None:
+ emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
+ if isinstance(emissive_socket, bpy.types.NodeSocket):
+ if export_settings['gltf_image_format'] != "NONE":
+ factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
+ else:
+ factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
+
+ if factor is None and emissive_socket.is_linked:
+ # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
+ # we have to manually set it to all ones.
+ factor = [1.0, 1.0, 1.0]
+
+ if factor is None: factor = [0.0, 0.0, 0.0]
+
+ # Handle Emission Strength
+ strength_socket = None
+ if emissive_socket.node.type == 'EMISSION':
+ strength_socket = emissive_socket.node.inputs['Strength']
+ elif 'Emission Strength' in emissive_socket.node.inputs:
+ strength_socket = emissive_socket.node.inputs['Emission Strength']
+ strength = (
+ gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
+ if strength_socket is not None
+ else None
+ )
+ if strength is not None:
+ factor = [f * strength for f in factor]
+
+ # Clamp to range [0,1]
+ # Official glTF clamp to range [0,1]
+ # If we are outside, we need to use extension KHR_materials_emissive_strength
+
+ if factor == [0, 0, 0]: factor = None
+
+ return factor
+
+ return None
+
+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)
+ return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+
+def export_emission_strength_extension(emissive_factor, export_settings):
+ emissive_strength_extension = {}
+ emissive_strength_extension['emissiveStrength'] = max(emissive_factor)
+
+ return Extension('KHR_materials_emissive_strength', emissive_strength_extension, False) \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
new file mode 100644
index 00000000..fc219c01
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+
+def export_ior(blender_material, extensions, export_settings):
+ ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+ if not ior_socket:
+ return None
+
+ # We don't manage case where socket is linked, always check default value
+ if ior_socket.is_linked:
+ # TODOExt: add warning?
+ return None
+
+ if ior_socket.default_value == GLTF_IOR:
+ return None
+
+ # Export only if the following extensions are exported:
+ need_to_export_ior = [
+ 'KHR_materials_transmission',
+ 'KHR_materials_volume',
+ 'KHR_materials_specular'
+ ]
+
+ if not any([e in extensions.keys() for e in need_to_export_ior]):
+ return None
+
+ ior_extension = {}
+ ior_extension['ior'] = ior_socket.default_value
+
+ return Extension('KHR_materials_ior', ior_extension, False) \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
index 0b40ffd6..a5929c05 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
@@ -15,8 +15,8 @@ def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export
if not __filter_pbr_material(blender_material, export_settings):
return None, None
- base_color_texture, use_active_uvmap_base_color_texture = __gather_base_color_texture(blender_material, export_settings)
- metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
+ base_color_texture, use_active_uvmap_base_color_texture, _ = __gather_base_color_texture(blender_material, export_settings)
+ metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture, _ = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
material = gltf2_io.MaterialPBRMetallicRoughness(
base_color_factor=__gather_base_color_factor(blender_material, export_settings),
@@ -92,7 +92,7 @@ def __gather_base_color_texture(blender_material, export_settings):
if socket is not None and __has_image_node_from_socket(socket)
)
if not inputs:
- return None, None
+ return None, None, None
return gltf2_blender_gather_texture_info.gather_texture_info(inputs[0], inputs, export_settings)
@@ -128,7 +128,7 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
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):
- return None, None
+ return None, None, None
texture_input = (metallic_roughness,)
elif not hasMetal:
texture_input = (roughness_socket,)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
new file mode 100644
index 00000000..03625ecb
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_sheen(blender_material, export_settings):
+ sheen_extension = {}
+
+ sheenColor_socket = gltf2_blender_get.get_socket(blender_material, "sheenColor")
+ sheenRoughness_socket = gltf2_blender_get.get_socket(blender_material, "sheenRoughness")
+
+ if sheenColor_socket is None or sheenRoughness_socket is None:
+ return None, None
+
+ sheenColor_non_linked = isinstance(sheenColor_socket, bpy.types.NodeSocket) and not sheenColor_socket.is_linked
+ sheenRoughness_non_linked = isinstance(sheenRoughness_socket, bpy.types.NodeSocket) and not sheenRoughness_socket.is_linked
+
+
+ use_actives_uvmaps = []
+
+ if sheenColor_non_linked is True:
+ color = sheenColor_socket.default_value[:3]
+ if color != (0.0, 0.0, 0.0):
+ sheen_extension['sheenColorFactor'] = color
+ else:
+ # Factor
+ fac = gltf2_blender_get.get_factor_from_socket(sheenColor_socket, kind='RGB')
+ if fac is not None and fac != [0.0, 0.0, 0.0]:
+ sheen_extension['sheenColorFactor'] = fac
+
+ # Texture
+ if gltf2_blender_get.has_image_node_from_socket(sheenColor_socket):
+ 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
+ if original_sheenColor_use_active_uvmap:
+ use_actives_uvmaps.append("sheenColorTexture")
+
+
+ if sheenRoughness_non_linked is True:
+ fac = sheenRoughness_socket.default_value
+ if fac != 0.0:
+ sheen_extension['sheenRoughnessFactor'] = fac
+ else:
+ # Factor
+ fac = gltf2_blender_get.get_factor_from_socket(sheenRoughness_socket, kind='VALUE')
+ if fac is not None and fac != 0.0:
+ sheen_extension['sheenRoughnessFactor'] = fac
+
+ # Texture
+ if gltf2_blender_get.has_image_node_from_socket(sheenRoughness_socket):
+ 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
+ if original_sheenRoughness_use_active_uvmap:
+ use_actives_uvmaps.append("sheenRoughnessTexture")
+
+ return Extension('KHR_materials_sheen', sheen_extension, False), use_actives_uvmaps
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
new file mode 100644
index 00000000..22414b13
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
@@ -0,0 +1,168 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+from io_scene_gltf2.blender.com.gltf2_blender_default import BLENDER_SPECULAR, BLENDER_SPECULAR_TINT
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+
+def export_original_specular(blender_material, export_settings):
+ specular_extension = {}
+
+ original_specular_socket = gltf2_blender_get.get_socket_old(blender_material, 'Specular')
+ original_specularcolor_socket = gltf2_blender_get.get_socket_old(blender_material, 'Specular Color')
+
+ if original_specular_socket is None or original_specularcolor_socket is None:
+ return None, None
+
+ specular_non_linked = isinstance(original_specular_socket, bpy.types.NodeSocket) and not original_specular_socket.is_linked
+ specularcolor_non_linked = isinstance(original_specularcolor_socket, bpy.types.NodeSocket) and not original_specularcolor_socket.is_linked
+
+
+ use_actives_uvmaps = []
+
+ if specular_non_linked is True:
+ fac = original_specular_socket.default_value
+ if fac != 1.0:
+ specular_extension['specularFactor'] = fac
+ else:
+ # Factor
+ fac = gltf2_blender_get.get_factor_from_socket(original_specular_socket, kind='VALUE')
+ if fac is not None and fac != 1.0:
+ specular_extension['specularFactor'] = fac
+
+ # 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_socket,
+ (original_specular_socket,),
+ export_settings,
+ )
+ specular_extension['specularTexture'] = original_specular_texture
+ if original_specular_use_active_uvmap:
+ use_actives_uvmaps.append("specularTexture")
+
+
+ if specularcolor_non_linked is True:
+ color = original_specularcolor_socket.default_value[:3]
+ if color != [1.0, 1.0, 1.0]:
+ specular_extension['specularColorFactor'] = color
+ else:
+ # Factor
+ fac = gltf2_blender_get.get_factor_from_socket(original_specularcolor_socket, kind='RGB')
+ if fac is not None and fac != [1.0, 1.0, 1.0]:
+ specular_extension['specularColorFactor'] = fac
+
+ # 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_socket,
+ (original_specularcolor_socket,),
+ export_settings,
+ )
+ specular_extension['specularColorTexture'] = original_specularcolor_texture
+ if original_specularcolor_use_active_uvmap:
+ use_actives_uvmaps.append("specularColorTexture")
+
+ return Extension('KHR_materials_specular', specular_extension, False), use_actives_uvmaps
+
+def export_specular(blender_material, export_settings):
+
+ if export_settings['gltf_original_specular'] is True:
+ return export_original_specular(blender_material, export_settings)
+
+ specular_extension = {}
+ specular_ext_enabled = False
+
+ specular_socket = gltf2_blender_get.get_socket(blender_material, 'Specular')
+ specular_tint_socket = gltf2_blender_get.get_socket(blender_material, 'Specular Tint')
+ base_color_socket = gltf2_blender_get.get_socket(blender_material, 'Base Color')
+ transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+ ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+ 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 = specular_socket.default_value if specular_not_linked else None
+ specular_tint = specular_tint_socket.default_value if specular_tint_not_linked else None
+ transmission = transmission_socket.default_value if transmission_not_linked else None
+ ior = ior_socket.default_value if ior_not_linked else GLTF_IOR # textures not supported #TODOExt add warning?
+ base_color = base_color_socket.default_value[0:3]
+
+ no_texture = (transmission_not_linked and specular_not_linked and specular_tint_not_linked and
+ (specular_tint == 0.0 or (specular_tint != 0.0 and base_color_not_linked)))
+
+ use_actives_uvmaps = []
+
+ if no_texture:
+ if specular != BLENDER_SPECULAR or specular_tint != BLENDER_SPECULAR_TINT:
+ import numpy as np
+ # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+ specular_ext_enabled = True
+
+ def normalize(c):
+ luminance = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+ assert(len(c) == 3)
+ l = luminance(c)
+ if l == 0:
+ return np.array(c)
+ return np.array([c[0] / l, c[1] / l, c[2] / l])
+
+ f0_from_ior = ((ior - 1)/(ior + 1))**2
+ tint_strength = (1 - specular_tint) + normalize(base_color) * specular_tint
+ specular_color = (1 - transmission) * (1 / f0_from_ior) * 0.08 * specular * tint_strength + transmission * tint_strength
+ specular_extension['specularColorFactor'] = list(specular_color)
+ else:
+ if specular_not_linked and specular == BLENDER_SPECULAR and specular_tint_not_linked and specular_tint == BLENDER_SPECULAR_TINT:
+ return None, None
+
+ # Trying to identify cases where exporting a texture will not be needed
+ if specular_not_linked and transmission_not_linked and \
+ specular == 0.0 and transmission == 0.0:
+
+ specular_extension['specularColorFactor'] = [0.0, 0.0, 0.0]
+ return specular_extension, []
+
+
+ # There will be a texture, with a complex calculation (no direct channel mapping)
+ sockets = (specular_socket, specular_tint_socket, base_color_socket, transmission_socket, ior_socket)
+ # Set primary socket having a texture
+ primary_socket = specular_socket
+ if specular_not_linked:
+ primary_socket = specular_tint_socket
+ if specular_tint_not_linked:
+ primary_socket = base_color_socket
+ if base_color_not_linked:
+ primary_socket = transmission_socket
+
+ specularColorTexture, use_active_uvmap, specularColorFactor = gltf2_blender_gather_texture_info.gather_texture_info(
+ primary_socket,
+ sockets,
+ export_settings,
+ filter_type='ANY')
+ if specularColorTexture is None:
+ return None, None
+ if use_active_uvmap:
+ use_actives_uvmaps.append("specularColorTexture")
+
+ specular_ext_enabled = True
+ specular_extension['specularColorTexture'] = specularColorTexture
+
+
+ if specularColorFactor is not None:
+ specular_extension['specularColorFactor'] = specularColorFactor
+
+
+ specular_extension = Extension('KHR_materials_specular', specular_extension, False) if specular_ext_enabled else None
+ return specular_extension, use_actives_uvmaps \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
new file mode 100644
index 00000000..fdc5d8c7
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_transmission(blender_material, export_settings):
+ transmission_enabled = False
+ has_transmission_texture = False
+
+ transmission_extension = {}
+ transmission_slots = ()
+
+ transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+
+ if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+ transmission_extension['transmissionFactor'] = transmission_socket.default_value
+ transmission_enabled = transmission_extension['transmissionFactor'] > 0
+ elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+ fac = gltf2_blender_get.get_factor_from_socket(transmission_socket, kind='VALUE')
+ transmission_extension['transmissionFactor'] = fac if fac is not None else 1.0
+ has_transmission_texture = True
+ transmission_enabled = True
+
+ if not transmission_enabled:
+ return None, None
+
+ # Pack transmission channel (R).
+ if has_transmission_texture:
+ transmission_slots = (transmission_socket,)
+
+ use_actives_uvmaps = []
+
+ if len(transmission_slots) > 0:
+ combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+ transmission_socket,
+ transmission_slots,
+ export_settings,
+ )
+ if has_transmission_texture:
+ transmission_extension['transmissionTexture'] = combined_texture
+ if use_active_uvmap:
+ use_actives_uvmaps.append("transmissionTexture")
+
+ return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
index e104b7f1..b501f98f 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
@@ -1,9 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
from io_scene_gltf2.blender.exp import gltf2_blender_get
-
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
def detect_shadeless_material(blender_material, export_settings):
"""Detect if this material is "shadeless" ie. should be exported
@@ -127,7 +127,7 @@ def gather_base_color_texture(info, export_settings):
# because gather_image determines how to pack images based on the
# names of sockets, and the names are hard-coded to a Principled
# style graph.
- 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,
export_settings,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
new file mode 100644
index 00000000..4c452e6a
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from typing import Dict, Any
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io_variants
+
+
+@cached
+def gather_variant(variant_idx, export_settings) -> Dict[str, Any]:
+
+ variant = gltf2_io_variants.Variant(
+ name=bpy.data.scenes[0].gltf2_KHR_materials_variants_variants[variant_idx].name,
+ extensions=None,
+ extras=None
+ )
+ return variant.to_dict()
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
new file mode 100644
index 00000000..8a69e3f6
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_volume(blender_material, export_settings):
+ # Implementation based on https://github.com/KhronosGroup/glTF-Blender-IO/issues/1454#issuecomment-928319444
+
+ # If no transmission --> No volume
+ transmission_enabled = False
+ transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+ if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+ transmission_enabled = transmission_socket.default_value > 0
+ elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+ transmission_enabled = True
+
+ if transmission_enabled is False:
+ return None, None
+
+ volume_extension = {}
+ has_thickness_texture = False
+ thickness_slots = ()
+
+ thicknesss_socket = gltf2_blender_get.get_socket_old(blender_material, 'Thickness')
+ if thicknesss_socket is None:
+ # If no thickness (here because there is no glTF Material Output node), no volume extension export
+ return None, None
+
+ density_socket = gltf2_blender_get.get_socket(blender_material, 'Density', volume=True)
+ attenuation_color_socket = gltf2_blender_get.get_socket(blender_material, 'Color', volume=True)
+ # Even if density or attenuation are not set, we export volume extension
+
+ if isinstance(attenuation_color_socket, bpy.types.NodeSocket):
+ rgb = gltf2_blender_get.get_const_from_default_value_socket(attenuation_color_socket, kind='RGB')
+ volume_extension['attenuationColor'] = rgb
+
+ if isinstance(density_socket, bpy.types.NodeSocket):
+ density = gltf2_blender_get.get_const_from_default_value_socket(density_socket, kind='VALUE')
+ volume_extension['attenuationDistance'] = 1.0 / density if density != 0 else None # infinity (Using None as glTF default)
+
+
+ if isinstance(thicknesss_socket, bpy.types.NodeSocket) and not thicknesss_socket.is_linked:
+ val = thicknesss_socket.default_value
+ if val == 0.0:
+ # If no thickness, no volume extension export
+ return None, None
+ volume_extension['thicknessFactor'] = val
+ elif gltf2_blender_get.has_image_node_from_socket(thicknesss_socket):
+ fac = gltf2_blender_get.get_factor_from_socket(thicknesss_socket, kind='VALUE')
+ # default value in glTF is 0.0, but if there is a texture without factor, use 1
+ volume_extension['thicknessFactor'] = fac if fac != None else 1.0
+ has_thickness_texture = True
+
+ # Pack thickness channel (R).
+ if has_thickness_texture:
+ thickness_slots = (thicknesss_socket,)
+
+ use_actives_uvmaps = []
+
+ if len(thickness_slots) > 0:
+ combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+ thicknesss_socket,
+ thickness_slots,
+ export_settings,
+ )
+ if has_thickness_texture:
+ volume_extension['thicknessTexture'] = combined_texture
+ if use_active_uvmap:
+ use_actives_uvmaps.append("thicknessTexture")
+
+ return Extension('KHR_materials_volume', volume_extension, False), use_actives_uvmaps \ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
index 72f0268c..1af588b9 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
@@ -44,7 +44,7 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
amin = np.amin(array, axis=0).tolist()
return gltf2_io.Accessor(
- buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()),
+ buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
byte_offset=None,
component_type=component_type,
count=len(array),
@@ -124,28 +124,34 @@ def __gather_colors(blender_primitive, export_settings):
color_index = 0
color_id = 'COLOR_' + str(color_index)
while blender_primitive["attributes"].get(color_id) is not None:
- colors = blender_primitive["attributes"][color_id]
+ colors = blender_primitive["attributes"][color_id]["data"]
if type(colors) is not np.ndarray:
colors = np.array(colors, dtype=np.float32)
colors = colors.reshape(len(colors) // 4, 4)
- # Convert to normalized ushorts
- colors *= 65535
- colors += 0.5 # bias for rounding
- colors = colors.astype(np.uint16)
+ if blender_primitive["attributes"][color_id]["norm"] is True:
+ comp_type = gltf2_io_constants.ComponentType.UnsignedShort
+
+ # Convert to normalized ushorts
+ colors *= 65535
+ colors += 0.5 # bias for rounding
+ colors = colors.astype(np.uint16)
+
+ else:
+ comp_type = gltf2_io_constants.ComponentType.Float
attributes[color_id] = gltf2_io.Accessor(
- buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes()),
+ buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
byte_offset=None,
- component_type=gltf2_io_constants.ComponentType.UnsignedShort,
+ component_type=comp_type,
count=len(colors),
extensions=None,
extras=None,
max=None,
min=None,
name=None,
- normalized=True,
+ normalized=blender_primitive["attributes"][color_id]["norm"],
sparse=None,
type=gltf2_io_constants.DataType.Vec4,
)
@@ -167,6 +173,13 @@ def __gather_skins(blender_primitive, export_settings):
max_bone_set_index += 1
max_bone_set_index -= 1
+ # Here, a set represents a group of 4 weights.
+ # So max_bone_set_index value:
+ # if -1 => No weights
+ # if 1 => Max 4 weights
+ # if 2 => Max 8 weights
+ # etc...
+
# If no skinning
if max_bone_set_index < 0:
return attributes
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 367c30f5..576a1418 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -12,10 +12,12 @@ from io_scene_gltf2.blender.exp import gltf2_blender_extract
from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes
from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_variants
from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.io.exp import gltf2_io_binary_data
from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.io.com import gltf2_io_extensions
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
@@ -86,7 +88,7 @@ def gather_primitives(
primitive = gltf2_io.MeshPrimitive(
attributes=internal_primitive['attributes'],
- extensions=None,
+ extensions=__gather_extensions(blender_mesh, material_idx, active_uvmap_idx, export_settings),
extras=None,
indices=internal_primitive['indices'],
material=material,
@@ -148,7 +150,7 @@ def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings
return None
element_type = gltf2_io_constants.DataType.Scalar
- binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes())
+ binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes(), bufferViewTarget=gltf2_io_constants.BufferViewTarget.ELEMENT_ARRAY_BUFFER)
return gltf2_blender_gather_accessors.gather_accessor(
binary_data,
component_type,
@@ -214,3 +216,55 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
morph_index += 1
return targets
return None
+
+def __gather_extensions(blender_mesh,
+ material_idx: int,
+ active_uvmap_idx,
+ export_settings):
+ extensions = {}
+
+ if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+ return None
+
+ if bpy.data.scenes[0].get('gltf2_KHR_materials_variants_variants') is None:
+ return None
+ if len(bpy.data.scenes[0]['gltf2_KHR_materials_variants_variants']) == 0:
+ return None
+
+ # Material idx is the slot idx. Retrieve associated variant, if any
+ mapping = []
+ for i in [v for v in blender_mesh.gltf2_variant_mesh_data if v.material_slot_index == material_idx]:
+ variants = []
+ for idx, v in enumerate(i.variants):
+ if v.variant.variant_idx in [o.variant.variant_idx for o in i.variants[:idx]]:
+ # Avoid duplicates
+ continue
+ vari = gltf2_blender_gather_materials_variants.gather_variant(v.variant.variant_idx, export_settings)
+ if vari is not None:
+ variant_extension = gltf2_io_extensions.ChildOfRootExtension(
+ name="KHR_materials_variants",
+ path=["variants"],
+ extension=vari
+ )
+ variants.append(variant_extension)
+ if len(variants) > 0:
+ if i.material:
+ mat = gltf2_blender_gather_materials.gather_material(
+ i.material,
+ active_uvmap_idx,
+ export_settings
+ )
+ else:
+ # empty slot
+ mat = None
+ mapping.append({'material': mat, 'variants': variants})
+
+ if len(mapping) > 0:
+ extensions["KHR_materials_variants"] = gltf2_io_extensions.Extension(
+ name="KHR_materials_variants",
+ extension={
+ "mappings": mapping
+ }
+ )
+
+ return extensions if extensions else None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
index e8c6baf1..ccfd42e5 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
@@ -26,24 +26,26 @@ def gather_texture(
"""
if not __filter_texture(blender_shader_sockets, export_settings):
- return None
+ return None, None
+
+ source, factor = __gather_source(blender_shader_sockets, export_settings)
texture = gltf2_io.Texture(
extensions=__gather_extensions(blender_shader_sockets, export_settings),
extras=__gather_extras(blender_shader_sockets, export_settings),
name=__gather_name(blender_shader_sockets, export_settings),
sampler=__gather_sampler(blender_shader_sockets, export_settings),
- source=__gather_source(blender_shader_sockets, export_settings)
+ source= source
)
# although valid, most viewers can't handle missing source properties
# This can have None source for "keep original", when original can't be found
if texture.source is None:
- return None
+ return None, None
export_user_extensions('gather_texture_hook', export_settings, texture, blender_shader_sockets)
- return texture
+ return texture, factor
def __filter_texture(blender_shader_sockets, export_settings):
@@ -66,13 +68,14 @@ def __gather_name(blender_shader_sockets, export_settings):
def __gather_sampler(blender_shader_sockets, export_settings):
- shader_nodes = [__get_tex_from_socket(socket).shader_node 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. "
"The resulting glTF sampler will behave like the first shader node tex image.")
+ first_valid_shader_node = next(filter(lambda x: x is not None, shader_nodes)).shader_node
return gltf2_blender_gather_sampler.gather_sampler(
- shader_nodes[0],
+ first_valid_shader_node,
export_settings)
@@ -81,7 +84,7 @@ def __gather_source(blender_shader_sockets, export_settings):
# Helpers
-
+# TODOExt deduplicate
def __get_tex_from_socket(socket):
result = gltf2_blender_search_node_tree.from_socket(
socket,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
index 15b101ad..5fe2da32 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
@@ -19,14 +19,14 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
# 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):
- return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', export_settings)
+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)
-def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
- return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', 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)
-def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
- return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', 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)
@cached
@@ -34,16 +34,19 @@ def __gather_texture_info_helper(
primary_socket: bpy.types.NodeSocket,
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
kind: str,
+ filter_type: str,
export_settings):
- if not __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
- return None, None
+ if not __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
+ return None, None, None
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)
+
fields = {
'extensions': __gather_extensions(tex_transform, export_settings),
'extras': __gather_extras(blender_shader_sockets, export_settings),
- 'index': __gather_index(blender_shader_sockets, export_settings),
+ 'index': index,
'tex_coord': tex_coord
}
@@ -59,14 +62,14 @@ def __gather_texture_info_helper(
texture_info = gltf2_io.MaterialOcclusionTextureInfoClass(**fields)
if texture_info.index is None:
- return None, None
+ return None, None, None
export_user_extensions('gather_texture_info_hook', export_settings, texture_info, blender_shader_sockets)
- return texture_info, use_active_uvmap
+ return texture_info, use_active_uvmap, factor
-def __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
+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:
@@ -75,9 +78,18 @@ def __filter_texture_info(primary_socket, blender_shader_sockets, export_setting
return False
if not all([elem is not None for elem in blender_shader_sockets]):
return False
- 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
+ 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]):
+ # 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]):
+ return False
+ elif filter_type == "NONE":
+ # No check
+ pass
return True
@@ -163,7 +175,7 @@ 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,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
index ba63e049..c654b445 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -95,10 +95,18 @@ class VExportTree:
bpy.context.window.scene = blender_scene
depsgraph = bpy.context.evaluated_depsgraph_get()
+ # Gather parent/children information once, as calling bobj.children is
+ # very expensive operation : takes O(len(bpy.data.objects)) time.
+ blender_children = dict()
+ for bobj in bpy.data.objects:
+ bparent = bobj.parent
+ blender_children.setdefault(bobj, [])
+ blender_children.setdefault(bparent, []).append(bobj)
+
for blender_object in [obj.original for obj in depsgraph.scene_eval.objects if obj.parent is None]:
- self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4))
+ self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4), blender_children)
- def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None):
+ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, blender_children, armature_uuid=None, dupli_world_matrix=None):
node = VExportNode()
node.uuid = str(uuid.uuid4())
node.parent_uuid = parent_uuid
@@ -163,10 +171,16 @@ class VExportTree:
# So real world matrix is collection world_matrix @ "world_matrix" of object
node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy()
if node.blender_type == VExportNode.CAMERA and self.export_settings[gltf2_blender_export_keys.CAMERAS]:
- correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ if self.export_settings[gltf2_blender_export_keys.YUP]:
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ else:
+ correction = Matrix.Identity(4).to_quaternion()
node.matrix_world @= correction.to_matrix().to_4x4()
elif node.blender_type == VExportNode.LIGHT and self.export_settings[gltf2_blender_export_keys.LIGHTS]:
- correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ if self.export_settings[gltf2_blender_export_keys.YUP]:
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ else:
+ correction = Matrix.Identity(4).to_quaternion()
node.matrix_world @= correction.to_matrix().to_4x4()
elif node.blender_type == VExportNode.BONE:
if self.export_settings['gltf_current_frame'] is True:
@@ -193,42 +207,42 @@ class VExportTree:
# standard children
if blender_bone is None and blender_object.is_instancer is False:
- for child_object in blender_object.children:
+ for child_object in blender_children[blender_object]:
if child_object.parent_bone:
# Object parented to bones
# Will be manage later
continue
else:
# Classic parenting
- self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+ self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, blender_children)
# Collections
if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
for dupli_object in blender_object.instance_collection.all_objects:
if dupli_object.parent is not None:
continue
- self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world)
+ self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world, blender_children)
# Armature : children are bones with no parent
if blender_object.type == "ARMATURE" and blender_bone is None:
for b in [b for b in blender_object.pose.bones if b.parent is None]:
- self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, node.uuid)
+ self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, blender_children, node.uuid)
# Bones
if blender_object.type == "ARMATURE" and blender_bone is not None:
for b in blender_bone.children:
- self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, armature_uuid)
+ self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, blender_children, armature_uuid)
# Object parented to bone
if blender_bone is not None:
- for child_object in [c for c in blender_object.children if c.parent_bone is not None and c.parent_bone == blender_bone.name]:
- self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+ for child_object in [c for c in blender_children[blender_object] if c.parent_bone is not None and c.parent_bone == blender_bone.name]:
+ self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, blender_children)
# Duplis
if blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION':
depsgraph = bpy.context.evaluated_depsgraph_get()
for (dupl, mat) in [(dup.object.original, dup.matrix_world.copy()) for dup in depsgraph.object_instances if dup.parent and id(dup.parent.original) == id(blender_object)]:
- self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, dupli_world_matrix=mat)
+ self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, blender_children, dupli_world_matrix=mat)
def get_all_objects(self):
return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE]
@@ -462,3 +476,20 @@ class VExportTree:
skin = gather_skin(n.uuid, self.export_settings)
skins.append(skin)
return skins
+
+ def variants_reset_to_original(self):
+ # Only if Variants are displayed and exported
+ if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+ return
+ objects = [self.nodes[o].blender_object for o in self.get_all_node_of_type(VExportNode.OBJECT) if self.nodes[o].blender_object.type == "MESH" \
+ and self.nodes[o].blender_object.data.get('gltf2_variant_default_materials') is not None]
+ for obj in objects:
+ # loop on material slots ( primitives )
+ for mat_slot_idx, s in enumerate(obj.material_slots):
+ # Check if there is a default material for this slot
+ for i in obj.data.gltf2_variant_default_materials:
+ if i.material_slot_index == mat_slot_idx:
+ s.material = i.default_material
+ break
+
+ # If not found, keep current material as default
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
index e38906e6..9e468186 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
@@ -4,9 +4,10 @@
import bpy
from mathutils import Vector, Matrix
-from ..com.gltf2_blender_material_helpers import get_gltf_node_name
+from ..com.gltf2_blender_material_helpers import get_gltf_node_name, get_gltf_node_old_name
from ...blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf
from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
def get_animation_target(action_group: bpy.types.ActionGroup):
@@ -47,7 +48,7 @@ def get_node_socket(blender_material, type, name):
return None
-def get_socket(blender_material: bpy.types.Material, name: str):
+def get_socket(blender_material: bpy.types.Material, name: str, volume=False):
"""
For a given material input name, retrieve the corresponding node tree socket.
@@ -70,8 +71,15 @@ def get_socket(blender_material: bpy.types.Material, name: str):
elif name == "Background":
type = bpy.types.ShaderNodeBackground
name = "Color"
+ elif name == "sheenColor":
+ return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Color")
+ elif name == "sheenRoughness":
+ return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Sigma")
else:
- type = bpy.types.ShaderNodeBsdfPrincipled
+ if volume is False:
+ type = bpy.types.ShaderNodeBsdfPrincipled
+ else:
+ type = bpy.types.ShaderNodeVolumeAbsorption
return get_node_socket(blender_material, type, name)
@@ -86,11 +94,11 @@ def get_socket_old(blender_material: bpy.types.Material, name: str):
:param name: the name of the socket
:return: a blender NodeSocket
"""
- gltf_node_group_name = get_gltf_node_name().lower()
+ gltf_node_group_names = [get_gltf_node_name().lower(), get_gltf_node_old_name().lower()]
if blender_material.node_tree and blender_material.use_nodes:
nodes = [n for n in blender_material.node_tree.nodes if \
isinstance(n, bpy.types.ShaderNodeGroup) and \
- (n.node_tree.name.startswith('glTF Metallic Roughness') or n.node_tree.name.lower() == gltf_node_group_name)]
+ (n.node_tree.name.startswith('glTF Metallic Roughness') or n.node_tree.name.lower() in gltf_node_group_names)]
inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], [])
if inputs:
return inputs[0]
@@ -297,3 +305,12 @@ def previous_node(socket):
if prev_socket is not None:
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):
+ 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
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index 8b9db89a..6730f479 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -27,6 +27,18 @@ class FillWhite:
"""Fills a channel with all ones (1.0)."""
pass
+class StoreData:
+ def __init__(self, data):
+ """Store numeric data (not an image channel"""
+ self.data = data
+
+class StoreImage:
+ """
+ Store a channel with the channel src_chan from a Blender image.
+ This channel will be used for numpy calculation (no direct channel mapping)
+ """
+ def __init__(self, image: bpy.types.Image):
+ self.image = image
class ExportImage:
"""Custom image class.
@@ -55,9 +67,13 @@ class ExportImage:
def __init__(self, original=None):
self.fills = {}
+ self.stored = {}
- # In case of keeping original texture images
- self.original = original
+ self.original = original # In case of keeping original texture images
+ self.numpy_calc = None
+
+ def set_calc(self, numpy_calc):
+ self.numpy_calc = numpy_calc # In case of numpy calculation (no direct channel mapping)
@staticmethod
def from_blender_image(image: bpy.types.Image):
@@ -73,6 +89,12 @@ class ExportImage:
def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channel):
self.fills[dst_chan] = FillImage(image, src_chan)
+ def store_data(self, identifier, data, type='Image'):
+ if type == "Image": # This is an image
+ self.stored[identifier] = StoreImage(data)
+ else: # This is a numeric value
+ self.stored[identifier] = StoreData(data)
+
def fill_white(self, dst_chan: Channel):
self.fills[dst_chan] = FillWhite()
@@ -81,7 +103,7 @@ class ExportImage:
def empty(self) -> bool:
if self.original is None:
- return not self.fills
+ return not (self.fills or self.stored)
else:
return False
@@ -103,7 +125,7 @@ class ExportImage:
len(set(fill.image.name for fill in self.fills.values())) == 1
)
- def encode(self, mime_type: Optional[str]) -> bytes:
+ def encode(self, mime_type: Optional[str]) -> Tuple[bytes, bool]:
self.file_format = {
"image/jpeg": "JPEG",
"image/png": "PNG"
@@ -111,10 +133,14 @@ class ExportImage:
# Happy path = we can just use an existing Blender image
if self.__on_happy_path():
- return self.__encode_happy()
+ return self.__encode_happy(), None
- # Unhappy path = we need to create the image self.fills describes.
- return self.__encode_unhappy()
+ # Unhappy path = we need to create the image self.fills describes or self.stores describes
+ if self.numpy_calc is None:
+ return self.__encode_unhappy(), None
+ else:
+ pixels, width, height, factor = self.numpy_calc(self.stored)
+ return self.__encode_from_numpy_array(pixels, (width, height)), factor
def __encode_happy(self) -> bytes:
return self.__encode_from_image(self.blender_image())
@@ -147,7 +173,7 @@ class ExportImage:
else:
# Image is the wrong size; make a temp copy and scale it.
with TmpImageGuard() as guard:
- _make_temp_image_copy(guard, src_image=image)
+ make_temp_image_copy(guard, src_image=image)
tmp_image = guard.image
tmp_image.scale(width, height)
tmp_image.pixels.foreach_get(tmp_buf)
@@ -197,7 +223,7 @@ class ExportImage:
# Copy to a temp image and save.
with TmpImageGuard() as guard:
- _make_temp_image_copy(guard, src_image=image)
+ make_temp_image_copy(guard, src_image=image)
tmp_image = guard.image
return _encode_temp_image(tmp_image, self.file_format)
@@ -228,7 +254,7 @@ class TmpImageGuard:
bpy.data.images.remove(self.image, do_unlink=True)
-def _make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
+def make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
"""Makes a temporary copy of src_image. Will be cleaned up with guard."""
guard.image = src_image.copy()
tmp_image = guard.image
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
new file mode 100644
index 00000000..6321f128
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+import numpy as np
+from .gltf2_blender_gather_image import StoreImage, StoreData
+from .gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+
+def specular_calculation(stored):
+
+ # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+
+ # Find all Blender images used
+ images = []
+ for fill in stored.values():
+ if isinstance(fill, StoreImage):
+ if fill.image not in images:
+ images.append(fill.image)
+
+ if not images:
+ # No ImageFills; use a 1x1 white pixel
+ pixels = np.array([1.0, 1.0, 1.0, 1.0], np.float32)
+ return pixels, 1, 1
+
+ width = max(image.size[0] for image in images)
+ height = max(image.size[1] for image in images)
+
+ buffers = {}
+
+ for identifier, image in [(ident, store.image) for (ident, store) in stored.items() if isinstance(store, StoreImage)]:
+ tmp_buf = np.empty(width * height * 4, np.float32)
+
+ if image.size[0] == width and image.size[1] == height:
+ image.pixels.foreach_get(tmp_buf)
+ else:
+ # Image is the wrong size; make a temp copy and scale it.
+ with TmpImageGuard() as guard:
+ make_temp_image_copy(guard, src_image=image)
+ tmp_image = guard.image
+ tmp_image.scale(width, height)
+ tmp_image.pixels.foreach_get(tmp_buf)
+
+ buffers[identifier] = np.reshape(tmp_buf, [width, height, 4])
+
+ # keep only needed channels
+ ## scalar
+ for i in ['specular', 'specular_tint', 'transmission']:
+ if i in buffers.keys():
+ buffers[i] = buffers[i][:,:,stored[i + "_channel"].data]
+ else:
+ buffers[i] = np.full((width, height, 1), stored[i].data)
+
+ # Vector 3
+ for i in ['base_color']:
+ if i in buffers.keys():
+ if i + "_channel" not in stored.keys():
+ buffers[i] = buffers[i][:,:,:3]
+ else:
+ # keep only needed channel
+ for c in range(3):
+ if c != stored[i+"_channel"].data:
+ buffers[i][:, :, c] = 0.0
+ buffers[i] = buffers[i][:,:,:3]
+ else:
+ buffers[i] = np.full((width, height, 3), stored[i].data[0:3])
+
+ ior = stored['ior'].data
+
+ # calculation
+ stack3 = lambda v: np.dstack([v]*3)
+
+ def normalize(c):
+ luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2]
+ l = luminance(c)
+ # TODOExt Manage all 0
+ return c / stack3(l)
+
+
+ f0_from_ior = ((ior - 1)/(ior + 1))**2
+ tint_strength = (1 - stack3(buffers['specular_tint'])) + normalize(buffers['base_color']) * stack3(buffers['specular_tint'])
+ out_buf = (1 - stack3(buffers['transmission'])) * (1 / f0_from_ior) * 0.08 * stack3(buffers['specular']) * tint_strength + stack3(buffers['transmission']) * tint_strength
+
+ # Manage values > 1.0 -> Need to apply factor
+ factor = None
+ factors = [np.amax(out_buf[:, :, i]) for i in range(3)]
+
+ if any([f > 1.0 for f in factors]):
+ factor = [1.0 if f < 1.0 else f for f in factors]
+ out_buf /= factor
+
+ out_buf = np.dstack((out_buf, np.ones((width, height)))) # Set alpha (glTF specular) to 1
+ out_buf = np.reshape(out_buf, (width * height * 4))
+
+ return np.float32(out_buf), width, height, [float(f) for f in factor] if factor else None