Skip to content

Commit

Permalink
Merge branch 'main' into fix_2321
Browse files Browse the repository at this point in the history
  • Loading branch information
julienduroure committed Sep 12, 2024
2 parents 9e3282d + cee66f7 commit 005b733
Show file tree
Hide file tree
Showing 20 changed files with 119 additions and 58 deletions.
36 changes: 19 additions & 17 deletions addons/io_scene_gltf2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (4, 3, 13),
"version": (4, 3, 32),
'blender': (4, 2, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
Expand Down Expand Up @@ -170,6 +170,18 @@ def is_draco_available():

return is_draco_available.draco_exists

def set_debug_log():
import logging
if bpy.app.debug_value == 0: # Default values => Display all messages except debug ones
return logging.INFO
elif bpy.app.debug_value == 1:
return logging.WARNING
elif bpy.app.debug_value == 2:
return logging.ERROR
elif bpy.app.debug_value == 3:
return logging.CRITICAL
elif bpy.app.debug_value == 4:
return logging.DEBUG

class ConvertGLTF2_Base:
"""Base class containing options that should be exposed during both import and export."""
Expand Down Expand Up @@ -1008,7 +1020,7 @@ def invoke(self, context, event):
for addon_name in preferences.addons.keys():
try:
if hasattr(sys.modules[addon_name], 'glTF2ExportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ExportUserExtensions'):
exporter_extension_layout_draw[addon_name] = sys.modules[addon_name].draw
exporter_extension_layout_draw[addon_name] = sys.modules[addon_name].draw_export if hasattr(sys.modules[addon_name], 'draw_export') else sys.modules[addon_name].draw
except Exception:
pass

Expand Down Expand Up @@ -1052,7 +1064,7 @@ def execute(self, context):
# All custom export settings are stored in this container.
export_settings = {}

export_settings['loglevel'] = logging.INFO
export_settings['loglevel'] = set_debug_log()

export_settings['exported_images'] = {}
export_settings['exported_texture_nodes'] = []
Expand Down Expand Up @@ -1499,6 +1511,8 @@ def export_panel_data_armature(layout, operator):
row.prop(operator, 'export_armature_object_remove')
row = body.row()
row.prop(operator, 'export_hierarchy_flatten_bones')
row = body.row()
row.prop(operator, 'export_leaf_bone')


def export_panel_data_skinning(layout, operator):
Expand Down Expand Up @@ -1835,7 +1849,7 @@ def invoke(self, context, event):
for addon_name in preferences.addons.keys():
try:
if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
importer_extension_layout_draw[addon_name] = sys.modules[addon_name].draw
importer_extension_layout_draw[addon_name] = sys.modules[addon_name].draw_import if hasattr(sys.modules[addon_name], 'draw_import') else sys.modules[addon_name].draw
except Exception:
pass

Expand All @@ -1848,7 +1862,7 @@ def execute(self, context):
def import_gltf2(self, context):
import os

self.set_debug_log()
self.loglevel = set_debug_log()
import_settings = self.as_keywords()

user_extensions = []
Expand Down Expand Up @@ -1907,18 +1921,6 @@ def unit_import(self, filename, import_settings):
self.report({'ERROR'}, e.args[0])
return {'CANCELLED'}

def set_debug_log(self):
import logging
if bpy.app.debug_value == 0: # Default values => Display all messages except debug ones
self.loglevel = logging.INFO
elif bpy.app.debug_value == 1:
self.loglevel = logging.WARNING
elif bpy.app.debug_value == 2:
self.loglevel = logging.ERROR
elif bpy.app.debug_value == 3:
self.loglevel = logging.CRITICAL
elif bpy.app.debug_value == 4:
self.loglevel = logging.DEBUG

def import_bone_panel(layout, operator):
header, body = layout.panel("GLTF_import_bone", default_closed=False)
Expand Down
12 changes: 12 additions & 0 deletions addons/io_scene_gltf2/blender/com/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def get_component_type(attribute_component_type):
"FLOAT_COLOR": gltf2_io_constants.ComponentType.Float,
"FLOAT_VECTOR": gltf2_io_constants.ComponentType.Float,
"FLOAT_VECTOR_4": gltf2_io_constants.ComponentType.Float,
"QUATERNION": gltf2_io_constants.ComponentType.Float,
"FLOAT4X4": gltf2_io_constants.ComponentType.Float,
"INT": gltf2_io_constants.ComponentType.Float, # No signed Int in glTF accessor
"FLOAT": gltf2_io_constants.ComponentType.Float,
"BOOLEAN": gltf2_io_constants.ComponentType.Float,
Expand All @@ -120,6 +122,8 @@ def get_data_type(attribute_component_type):
"FLOAT_COLOR": gltf2_io_constants.DataType.Vec4,
"FLOAT_VECTOR": gltf2_io_constants.DataType.Vec3,
"FLOAT_VECTOR_4": gltf2_io_constants.DataType.Vec4,
"QUATERNION": gltf2_io_constants.DataType.Vec4,
"FLOAT4X4": gltf2_io_constants.DataType.Mat4,
"INT": gltf2_io_constants.DataType.Scalar,
"FLOAT": gltf2_io_constants.DataType.Scalar,
"BOOLEAN": gltf2_io_constants.DataType.Scalar,
Expand All @@ -133,6 +137,8 @@ def get_data_length(attribute_component_type):
"FLOAT_COLOR": 4,
"FLOAT_VECTOR": 3,
"FLOAT_VECTOR_4": 4,
"QUATERNION": 4,
"FLOAT4X4": 16,
"INT": 1,
"FLOAT": 1,
"BOOLEAN": 1
Expand All @@ -146,6 +152,8 @@ def get_numpy_type(attribute_component_type):
"FLOAT_COLOR": np.float32,
"FLOAT_VECTOR": np.float32,
"FLOAT_VECTOR_4": np.float32,
"QUATERNION": np.float32,
"FLOAT4X4": np.float32,
"INT": np.float32, #signed integer are not supported by glTF
"FLOAT": np.float32,
"BOOLEAN": np.float32,
Expand All @@ -172,6 +180,10 @@ def get_attribute_type(component_type, data_type):
gltf2_io_constants.ComponentType.UnsignedShort: "BYTE_COLOR",
gltf2_io_constants.ComponentType.UnsignedByte: "BYTE_COLOR" # What is the best for compatibility?
}.get(component_type, None)
elif gltf2_io_constants.DataType.num_elements(data_type) == 16:
return {
gltf2_io_constants.ComponentType.Float: "FLOAT4X4"
}.get(component_type, None)
else:
pass

Expand Down
4 changes: 2 additions & 2 deletions addons/io_scene_gltf2/blender/com/gltf2_blender_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,8 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn
layout.context_pointer_set("id", action)

if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item.action, "name", text="", emboss=False)
layout.prop(item, "keep", text="", emboss=True)
layout.split().prop(item.action, "name", text="", emboss=False)
layout.split().prop(item, "keep", text="", emboss=True)

elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ def gather_sampled_object_channel(
animation_channel,
channel,
export_settings['vtree'].nodes[obj_uuid].blender_object,
None, # No bone
action_name,
node_channel_is_animated
)

Expand Down
5 changes: 2 additions & 3 deletions addons/io_scene_gltf2/blender/exp/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,10 @@ def __write_file(json, buffer, export_settings):

def __notify_start(context, export_settings):
export_settings['log'].info('Starting glTF 2.0 export')
context.window_manager.progress_begin(0, 100)
context.window_manager.progress_update(0)
context.window.cursor_set('WAIT')


def __notify_end(context, elapsed, export_settings):
export_settings['log'].info('Finished glTF 2.0 export in {} s'.format(elapsed))
context.window_manager.progress_end()
context.window.cursor_set('DEFAULT')
print()
10 changes: 7 additions & 3 deletions addons/io_scene_gltf2/blender/exp/material/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ def gather_image(
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)
uri = __gather_original_uri(
image_data.original.filepath,
blender_shader_sockets[0].socket.id_data.library,
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
Expand All @@ -81,9 +85,9 @@ def gather_image(
# We also return image_data, as it can be used to generate same file with another extension for WebP management
return image, image_data, factor, None

def __gather_original_uri(original_uri, export_settings):
def __gather_original_uri(original_uri, library, export_settings):
path_to_image = bpy.path.abspath(original_uri, library=library)

path_to_image = bpy.path.abspath(original_uri)
if not os.path.exists(path_to_image): return None
try:
rel_path = os.path.relpath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ def __gather_base_color_factor(blender_material, export_settings):
path_alpha = alpha_info['alphaPath']

base_color_socket = get_socket(blender_material.node_tree, blender_material.use_nodes,"Base Color")
if base_color_socket.socket is None:
base_color_socket = get_socket(blender_material.node_tree, blender_material.use_nodes,"BaseColor")
if base_color_socket.socket is None:
base_color_socket = get_socket(blender_material.node_tree, blender_material.use_nodes,"BaseColor")
if base_color_socket.socket is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,13 @@ def get_constant(self, in_soc=None):
color = color[:3] # drop unused alpha component (assumes shader tree)
return color, "node_tree." + nav.out_socket.path_from_id() + ".default_value"

elif self.in_socket.type == 'SHADER':
# Historicaly, we manage RGB node plugged into a shader socket (output node)
if nav.node.type == 'RGB':
color = list(nav.out_socket.default_value)
color = color[:3]
return color, "node_tree." + nav.out_socket.path_from_id() + ".default_value"

elif self.in_socket.type == 'VALUE':
if nav.node.type == 'VALUE':
return nav.out_socket.default_value, "node_tree." + nav.out_socket.path_from_id() + ".default_value"
Expand Down
12 changes: 10 additions & 2 deletions addons/io_scene_gltf2/blender/exp/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,16 @@ def __gather_mesh(vnode, blender_object, export_settings):
depsgraph = bpy.context.evaluated_depsgraph_get()
blender_mesh_owner = blender_object.evaluated_get(depsgraph)
blender_mesh = blender_mesh_owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
for prop in [p for p in blender_object.data.keys() if p not in BLACK_LIST]:
blender_mesh[prop] = blender_object.data[prop]
# Seems now (from 4.2) the custom properties are Statically Typed
# so no need to copy them in that case, because overwriting them will crash
if len(blender_mesh.keys()) == 0:
# Copy custom properties
for prop in [p for p in blender_object.data.keys() if p not in BLACK_LIST]:
blender_mesh[prop] = blender_object.data[prop]
else:
# But we need to remove some properties that are not needed
for prop in [p for p in blender_object.data.keys() if p in BLACK_LIST]:
del blender_mesh[prop]
# Store that this evaluated mesh has been created by the exporter, and is not a GN instance mesh
blender_mesh['gltf2_mesh_applied'] = True

Expand Down
8 changes: 7 additions & 1 deletion addons/io_scene_gltf2/blender/exp/primitive_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ def manage_material_info(self):
else:
self.export_settings['log'].warning('We are not managing this case (UDIM for {})'.format(tex))

self.additional_materials.append((new_material, material_info, int(str(id(base_material)) + str(u) + str(v))))
self.additional_materials.append((new_material, material_info, int(str(id(base_material)) + str(u) + str(v)), "10" + str(v) + str(u+1)))


# Now, we need to add additional Vertex Color if needed
Expand Down Expand Up @@ -1282,6 +1282,12 @@ def __get_layer_attribute(self, attr):
elif attr['blender_data_type'] == "FLOAT_VECTOR":
self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('vector', data)
data = data.reshape(-1, attr['len'])
elif attr['blender_data_type'] == "QUATERNION":
self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data)
data = data.reshape(-1, attr['len'])
elif attr['blender_data_type'] == "FLOAT4X4":
self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data)
data = data.reshape(-1, attr['len'])
elif attr['blender_data_type'] == "FLOAT_VECTOR_4": # Specific case for tangent
pass
elif attr['blender_data_type'] == "INT":
Expand Down
5 changes: 4 additions & 1 deletion addons/io_scene_gltf2/blender/exp/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ def gather_primitives(
material = get_final_material(blender_mesh, blender_mat, internal_primitive['uvmap_attributes_index'], base_material, material_info["uv_info"], export_settings)
else:
# UDIM case
base_material, material_info, unique_material_id = udim_material
base_material, material_info, unique_material_id, tile = udim_material
material = get_final_material(blender_mesh, unique_material_id, internal_primitive['uvmap_attributes_index'], base_material, material_info["uv_info"], export_settings)

# Force change name of material to get the tile number in the name
material.name = material.name + "." + tile

primitive = gltf2_io.MeshPrimitive(
attributes=internal_primitive['attributes'],
extensions=__gather_extensions(blender_mesh, internal_primitive['material'], internal_primitive['uvmap_attributes_index'], export_settings),
Expand Down
24 changes: 16 additions & 8 deletions addons/io_scene_gltf2/blender/exp/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ class VExportNode:
COLLECTION = 6
INSTANCE = 7 # For instances of GN

INSTANCIER = 8
NOT_INSTANCIER = 9
INST_COLLECTION = 7
INST_COLLECTION = 8


# Parent type, to be set on child regarding its parent
Expand Down Expand Up @@ -89,7 +87,7 @@ def __init__(self):
self.data = None
self.materials = None

self.is_instancier = VExportNode.NOT_INSTANCIER
self.is_instancer = False

def add_child(self, uuid):
self.children.append(uuid)
Expand Down Expand Up @@ -156,8 +154,17 @@ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, par
# add to parent if needed
if parent_uuid is not None:
self.add_children(parent_uuid, node.uuid)
if self.nodes[parent_uuid].blender_type == VExportNode.INST_COLLECTION or original_object is not None:

# 2 cases where we will need to store the fact that children are in collection or a real children
# 1. GN instance
# 2. Old Dupli vertices feature
# For any other case, children are real children
if (self.nodes[parent_uuid].blender_type == VExportNode.INST_COLLECTION or original_object is not None) or \
(self.nodes[parent_uuid].blender_object is not None and self.nodes[parent_uuid].blender_object.is_instancer is True):
self.nodes[parent_uuid].children_type[node.uuid] = VExportNode.CHILDREN_IS_IN_COLLECTION if is_children_in_collection is True else VExportNode.CHILDREN_REAL
else:
# We are in a regular case where children are real children
self.nodes[parent_uuid].children_type[node.uuid] = VExportNode.CHILDREN_REAL
else:
self.roots.append(node.uuid)

Expand Down Expand Up @@ -365,7 +372,8 @@ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, par
self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, dupli_world_matrix=mat)

# Geometry Nodes instances
if self.export_settings['gltf_gn_mesh'] is True:
# Make sure to not check instances for instanced collection, because we will export what's inside the collection twice
if self.export_settings['gltf_gn_mesh'] is True and node.blender_type == VExportNode.OBJECT:
# Do not force export as empty
# Because GN graph can have both geometry and instances
depsgraph = bpy.context.evaluated_depsgraph_get()
Expand All @@ -376,7 +384,7 @@ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, par
continue
if type(inst.object.data).__name__ == "Mesh" and len(inst.object.data.vertices) == 0:
continue # This is nested instances, and this mesh has no vertices, so is an instancier for other instances
node.is_instancier = VExportNode.INSTANCIER
node.is_instancer = True
self.recursive_node_traverse(None, None, node.uuid, parent_coll_matrix_world, new_delta or delta, blender_children, dupli_world_matrix=inst.matrix_world.copy(), data=inst.object.data, original_object=blender_object, is_children_in_collection=True)

def get_all_objects(self):
Expand Down Expand Up @@ -459,7 +467,7 @@ def recursive_filter_tag(self, uuid, parent_keep_tag):
self.export_settings['log'].error("This should not happen")

for child in self.nodes[uuid].children:
if self.nodes[uuid].blender_type == VExportNode.INST_COLLECTION or self.nodes[uuid].is_instancier == VExportNode.INSTANCIER:
if self.nodes[uuid].blender_type == VExportNode.INST_COLLECTION or self.nodes[uuid].is_instancer == True:
# We need to split children into 2 categories: real children, and objects inside the collection
if self.nodes[uuid].children_type[child] == VExportNode.CHILDREN_IS_IN_COLLECTION:
self.recursive_filter_tag(child, self.nodes[uuid].keep_tag)
Expand Down
13 changes: 5 additions & 8 deletions addons/io_scene_gltf2/blender/imp/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,12 @@ def create_spot(gltf, light_id):
spot = bpy.data.lights.new(name=pylight['name'], type="SPOT")

# Angles
if 'spot' in pylight.keys() and 'outerConeAngle' in pylight['spot']:
spot.spot_size = BlenderLight.calc_spot_cone_outer(gltf, pylight['spot']['outerConeAngle'])
else:
spot.spot_size = pi / 2
if 'spot' in pylight.keys():
gltf_default_outer_cone_angle = pi / 4
gltf_default_inner_cone_angle = 0.0

if 'spot' in pylight.keys() and 'innerConeAngle' in pylight['spot']:
spot.spot_blend = BlenderLight.calc_spot_cone_inner(gltf, pylight['spot']['outerConeAngle'], pylight['spot']['innerConeAngle'])
else:
spot.spot_blend = 1.0
spot.spot_size = BlenderLight.calc_spot_cone_outer(gltf, pylight['spot'].get('outerConeAngle', gltf_default_outer_cone_angle))
spot.spot_blend = BlenderLight.calc_spot_cone_inner(gltf, pylight['spot'].get('outerConeAngle', gltf_default_outer_cone_angle), pylight['spot'].get('innerConeAngle', gltf_default_inner_cone_angle))

if 'intensity' in pylight.keys():
spot.energy = BlenderLight.calc_energy_pointlike(gltf, pylight['intensity'])
Expand Down
4 changes: 3 additions & 1 deletion addons/io_scene_gltf2/io/com/gltf2_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

# NOTE: this file is modified for addonExtension use. See
# https://github.com/KhronosGroup/glTF-Blender-IO/commit/62ff119d8ffeab48f66e9d2699741407d532fe0f
# NOTE: this file is modified for mix/max accessor value check. See
# https://github.com/KhronosGroup/glTF-Blender-IO/pull/2338/commits/5178b5f61ab942704b85ff51262a3d595e70d2b5

import sys
import traceback
Expand Down Expand Up @@ -87,7 +89,7 @@ def from_bool(x):


def to_float(x):
assert isinstance(x, float)
assert isinstance(x, (int, float)) and not isinstance(x, bool)
return x


Expand Down
Loading

0 comments on commit 005b733

Please sign in to comment.