FBX IO: Speed up parsing by multithreading array decompression #104739

Merged
Thomas Barlow merged 7 commits from Mysteryem/blender-addons:fbx_parse_multithread_pr into main 2024-01-12 21:32:36 +01:00
29 changed files with 144 additions and 188 deletions
Showing only changes of commit 0e4e3e4337 - Show all commits

View File

@ -5,8 +5,8 @@
bl_info = {
"name": "3D-Coat Applink",
"author": "Kalle-Samuli Riihikoski (haikalle)",
"version": (4, 9, 34),
"blender": (2, 80, 0),
"version": (4, 9, 35),
"blender": (4, 1, 0),
"location": "Scene > 3D-Coat Applink",
"description": "Transfer data between 3D-Coat/Blender",
"warning": "",

View File

@ -521,7 +521,7 @@ def CreateTextureLine(type, act_material, main_mat, texcoat, coat3D, notegroup,
main_material.links.new(applink_tree.outputs[5], disp_node.inputs[0])
main_material.links.new(disp_node.outputs[0], out_mat.inputs[2])
coatMat.cycles.displacement_method = 'BOTH'
coatMat.displacement_method = 'BOTH'
else:
if (texcoat['alpha'] != []):

View File

@ -659,7 +659,7 @@ def CreateTextureLine(type, act_material, main_mat, texcoat, coat3D, notegroup,
main_material.links.new(applink_tree.outputs[5], disp_node.inputs[0])
main_material.links.new(disp_node.outputs[0], out_mat.inputs[2])
coatMat.cycles.displacement_method = 'BOTH'
coatMat.displacement_method = 'BOTH'
else:
if (texcoat['alpha'] != []):

View File

@ -286,7 +286,7 @@ class MAX3DS_PT_export_include(bpy.types.Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = True
layout.use_property_decorate = False
sfile = context.space_data
operator = sfile.active_operator
@ -367,4 +367,4 @@ def unregister():
if __name__ == "__main__":
register()
register()

View File

@ -5,7 +5,7 @@
bl_info = {
"name": "FBX format",
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
"version": (5, 11, 0),
"version": (5, 11, 2),
"blender": (4, 1, 0),
"location": "File > Import-Export",
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",

View File

@ -1638,7 +1638,7 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementSmoothing')
if fbx_layer is None:
return False
return
# all should be valid
(fbx_layer_name,
@ -1651,13 +1651,13 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
# udk has 'Direct' mapped, with no Smoothing, not sure why, but ignore these
if fbx_layer_data is None:
return False
return
if fbx_layer_mapping == b'ByEdge':
# some models have bad edge data, we can't use this info...
if not mesh.edges:
print("warning skipping sharp edges data, no valid edges...")
return False
return
blen_data = MESH_ATTRIBUTE_SHARP_EDGE.ensure(mesh.attributes).data
fbx_item_size = 1
@ -1669,21 +1669,23 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
1, fbx_item_size, layer_id,
xform=np.logical_not, # in FBX, 0 (False) is sharp, but in Blender True is sharp.
)
return False
elif fbx_layer_mapping == b'ByPolygon':
blen_data = MESH_ATTRIBUTE_SHARP_FACE.ensure(mesh.attributes).data
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.ensure(mesh.attributes)
blen_data = sharp_face.data
fbx_item_size = 1
assert(fbx_item_size == MESH_ATTRIBUTE_SHARP_FACE.item_size)
return blen_read_geom_array_mapped_polygon(
sharp_face_set_successfully = blen_read_geom_array_mapped_polygon(
mesh, blen_data, MESH_ATTRIBUTE_SHARP_FACE.foreach_attribute, MESH_ATTRIBUTE_SHARP_FACE.dtype,
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
1, fbx_item_size, layer_id,
xform=lambda s: (s == 0), # smoothgroup bitflags, treat as booleans for now
)
if not sharp_face_set_successfully:
mesh.attributes.remove(sharp_face)
else:
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping))
return False
def blen_read_geom_layer_edge_crease(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementEdgeCrease')
@ -1883,7 +1885,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
print("ERROR: No polygons, but edges exist. Ignoring the edges!")
# must be after edge, face loading.
ok_smooth = blen_read_geom_layer_smooth(fbx_obj, mesh)
blen_read_geom_layer_smooth(fbx_obj, mesh)
blen_read_geom_layer_edge_crease(fbx_obj, mesh)
@ -1905,23 +1907,12 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
clnors = np.empty(len(mesh.loops) * 3, dtype=bl_nors_dtype)
mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors)
if not ok_smooth:
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)
if sharp_face:
attributes.remove(sharp_face)
ok_smooth = True
# Iterating clnors into a nested tuple first is faster than passing clnors.reshape(-1, 3) directly into
# normals_split_custom_set. We use clnors.data since it is a memoryview, which is faster to iterate than clnors.
mesh.normals_split_custom_set(tuple(zip(*(iter(clnors.data),) * 3)))
if settings.use_custom_normals:
mesh.attributes.remove(mesh.attributes["temp_custom_normals"])
if not ok_smooth:
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)
if sharp_face:
attributes.remove(sharp_face)
if settings.use_custom_props:
blen_read_custom_properties(fbx_obj, mesh, settings)
@ -2779,7 +2770,13 @@ class FbxImportHelperNode:
pose_bone = arm.bl_obj.pose.bones[self.bl_bone]
pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix()
if settings.use_custom_props:
# `self.fbx_elem` can be `None` in cases where the imported hierarchy contains a mix of bone and non-bone FBX
# Nodes parented to one another, e.g. "bone1"->"mesh1"->"bone2". In Blender, an Armature can only consist of
# bones, so to maintain the imported hierarchy, a placeholder bone with the same name as "mesh1" is inserted
# into the Armature and then the imported "mesh1" Object is parented to the placeholder bone. The placeholder
# bone won't have a `self.fbx_elem` because it belongs to the "mesh1" Object instead.
# See FbxImportHelperNode.find_fake_bones().
if settings.use_custom_props and self.fbx_elem:
blen_read_custom_properties(self.fbx_elem, pose_bone, settings)
for child in self.children:

View File

@ -5,7 +5,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (4, 1, 36),
"version": (4, 1, 38),
'blender': (4, 1, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',

View File

@ -96,6 +96,7 @@ def __gather_gltf(exporter, export_settings):
exporter.add_scene(scene, idx==active_scene_idx, export_settings=export_settings)
for animation in animations:
exporter.add_animation(animation)
exporter.manage_gpu_instancing_nodes(export_settings)
exporter.traverse_unused_skins(unused_skins)
exporter.traverse_additional_textures()
exporter.traverse_additional_images()

View File

@ -276,57 +276,44 @@ class GlTF2Exporter:
self.nodes_idx_to_remove.extend(insts)
def add_scene(self, scene: gltf2_io.Scene, active: bool = False, export_settings=None):
"""
Add a scene to the glTF.
The scene should be built up with the generated glTF classes
:param scene: gltf2_io.Scene type. Root node of the scene graph
:param active: If true, sets the glTD.scene index to the added scene
:return: nothing
"""
if self.__finalized:
raise RuntimeError("Tried to add scene to finalized glTF file")
scene_num = self.__traverse(scene)
if active:
self.__gltf.scene = scene_num
def manage_gpu_instancing_nodes(self, export_settings):
if export_settings['gltf_gpu_instances'] is True:
# Modify the scene data in case of EXT_mesh_gpu_instancing export
for scene_num in range(len(self.__gltf.scenes)):
# Modify the scene data in case of EXT_mesh_gpu_instancing export
self.nodes_idx_to_remove = []
for node_idx in self.__gltf.scenes[scene_num].nodes:
node = self.__gltf.nodes[node_idx]
if node.mesh is None:
self.manage_gpu_instancing(node)
else:
self.manage_gpu_instancing(node, also_mesh=True)
for child_idx in node.children:
child = self.__gltf.nodes[child_idx]
self.manage_gpu_instancing(child, also_mesh=child.mesh is not None)
self.nodes_idx_to_remove = []
for node_idx in self.__gltf.scenes[scene_num].nodes:
node = self.__gltf.nodes[node_idx]
if node.mesh is None:
self.manage_gpu_instancing(node)
else:
self.manage_gpu_instancing(node, also_mesh=True)
for child_idx in node.children:
child = self.__gltf.nodes[child_idx]
self.manage_gpu_instancing(child, also_mesh=child.mesh is not None)
# Slides other nodes index
# Slides other nodes index
self.nodes_idx_to_remove.sort()
for node_idx in self.__gltf.scenes[scene_num].nodes:
self.recursive_slide_node_idx(node_idx)
self.nodes_idx_to_remove.sort()
for node_idx in self.__gltf.scenes[scene_num].nodes:
self.recursive_slide_node_idx(node_idx)
new_node_list = []
for node_idx in self.__gltf.scenes[scene_num].nodes:
len_ = len([i for i in self.nodes_idx_to_remove if i < node_idx])
new_node_list.append(node_idx - len_)
self.__gltf.scenes[scene_num].nodes = new_node_list
for skin in self.__gltf.skins:
new_joint_list = []
for node_idx in skin.joints:
new_node_list = []
for node_idx in self.__gltf.scenes[scene_num].nodes:
len_ = len([i for i in self.nodes_idx_to_remove if i < node_idx])
new_joint_list.append(node_idx - len_)
skin.joints = new_joint_list
if skin.skeleton is not None:
len_ = len([i for i in self.nodes_idx_to_remove if i < skin.skeleton])
skin.skeleton = skin.skeleton - len_
new_node_list.append(node_idx - len_)
self.__gltf.scenes[scene_num].nodes = new_node_list
for skin in self.__gltf.skins:
new_joint_list = []
for node_idx in skin.joints:
len_ = len([i for i in self.nodes_idx_to_remove if i < node_idx])
new_joint_list.append(node_idx - len_)
skin.joints = new_joint_list
if skin.skeleton is not None:
len_ = len([i for i in self.nodes_idx_to_remove if i < skin.skeleton])
skin.skeleton = skin.skeleton - len_
# Remove animation channels that was targeting a node that will be removed
new_animation_list = []
@ -345,6 +332,23 @@ class GlTF2Exporter:
# And now really remove nodes
self.__gltf.nodes = [node for idx, node in enumerate(self.__gltf.nodes) if idx not in self.nodes_idx_to_remove]
def add_scene(self, scene: gltf2_io.Scene, active: bool = False, export_settings=None):
"""
Add a scene to the glTF.
The scene should be built up with the generated glTF classes
:param scene: gltf2_io.Scene type. Root node of the scene graph
:param active: If true, sets the glTD.scene index to the added scene
:return: nothing
"""
if self.__finalized:
raise RuntimeError("Tried to add scene to finalized glTF file")
scene_num = self.__traverse(scene)
if active:
self.__gltf.scene = scene_num
def recursive_slide_node_idx(self, node_idx):
node = self.__gltf.nodes[node_idx]

View File

@ -82,7 +82,6 @@ def export_anisotropy(blender_material, export_settings):
anisotropy_texture, uvmap_info , udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
anisotropy_data['tex_socket'],
(anisotropy_data['tex_socket'],),
(),
export_settings,
)
anisotropy_extension['anisotropyTexture'] = anisotropy_texture
@ -109,7 +108,6 @@ def export_anisotropy_from_grayscale_textures(blender_material, export_settings)
anisotropyTexture, uvmap_info, _, _ = gltf2_blender_gather_texture_info.gather_texture_info(
primary_socket,
sockets,
(),
export_settings,
filter_type='ANY')

View File

@ -56,7 +56,6 @@ def export_clearcoat(blender_material, export_settings):
clearcoat_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_socket,
clearcoat_roughness_slots,
(),
export_settings,
)
clearcoat_extension['clearcoatTexture'] = clearcoat_texture
@ -67,7 +66,6 @@ def export_clearcoat(blender_material, export_settings):
clearcoat_roughness_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_roughness_socket,
clearcoat_roughness_slots,
(),
export_settings,
)
clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture

View File

@ -58,7 +58,7 @@ def export_emission_texture(blender_material, export_settings):
emissive = get_socket(blender_material, "Emissive")
if emissive.socket is None:
emissive = get_socket_from_gltf_material_node(blender_material, "Emissive")
emissive_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), (), export_settings)
emissive_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
return emissive_texture, {'emissiveTexture': uvmap_info}, {'emissiveTexture': udim_info} if len(udim_info.keys()) > 0 else {}
def export_emission_strength_extension(emissive_factor, export_settings):

View File

@ -50,7 +50,6 @@ def export_sheen(blender_material, export_settings):
original_sheenColor_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sheenTint_socket,
(sheenTint_socket,),
(),
export_settings,
)
sheen_extension['sheenColorTexture'] = original_sheenColor_texture
@ -74,7 +73,6 @@ def export_sheen(blender_material, export_settings):
original_sheenRoughness_texture, uvmap_info , udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sheenRoughness_socket,
(sheenRoughness_socket,),
(),
export_settings,
)
sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture

View File

@ -56,7 +56,6 @@ def export_specular(blender_material, export_settings):
specular_texture, uvmap_info, udim_info, _ = gather_texture_info(
specular_socket,
(specular_socket,),
(),
export_settings,
)
specular_extension['specularTexture'] = specular_texture
@ -88,7 +87,6 @@ def export_specular(blender_material, export_settings):
specularcolor_texture, uvmap_info, udim_info, _ = gather_texture_info(
speculartint_socket,
(speculartint_socket,),
(),
export_settings,
)
specular_extension['specularColorTexture'] = specularcolor_texture

View File

@ -42,7 +42,6 @@ def export_transmission(blender_material, export_settings):
combined_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
transmission_socket,
transmission_slots,
(),
export_settings,
)
if has_transmission_texture:

View File

@ -71,7 +71,6 @@ def export_volume(blender_material, export_settings):
combined_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
thickness_socket,
thickness_slots,
(),
export_settings,
)
if has_thickness_texture:

View File

@ -18,13 +18,12 @@ from .gltf2_blender_search_node_tree import get_texture_node_from_socket, detect
@cached
def gather_image(
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets: typing.Tuple[bpy.types.NodeSocket],
use_tile: bool,
export_settings):
if not __filter_image(blender_shader_sockets, export_settings):
return None, None, None, None
image_data, udim_image = __get_image_data(blender_shader_sockets, default_sockets, use_tile, export_settings)
image_data, udim_image = __get_image_data(blender_shader_sockets, use_tile, export_settings)
if udim_image is not None:
# We are in a UDIM case, so we return no image data
@ -194,7 +193,7 @@ def __gather_uri(image_data, mime_type, name, export_settings):
return None, None
def __get_image_data(sockets, default_sockets, use_tile, export_settings) -> ExportImage:
def __get_image_data(sockets, use_tile, export_settings) -> ExportImage:
# For shared resources, such as images, we just store the portion of data that is needed in the glTF property
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
# resources.
@ -236,22 +235,15 @@ def __get_image_data(sockets, default_sockets, use_tile, export_settings) -> Exp
# We are not in complex node setup, so we can try to get the image data from grayscale textures
return __get_image_data_grayscale_anisotropy(sockets, results, export_settings), None
return __get_image_data_mapping(sockets, default_sockets, results, use_tile, export_settings), None
return __get_image_data_mapping(sockets, results, use_tile, export_settings), None
def __get_image_data_mapping(sockets, default_sockets, results, use_tile, export_settings) -> ExportImage:
def __get_image_data_mapping(sockets, results, use_tile, export_settings) -> ExportImage:
"""
Simple mapping
Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
"""
composed_image = ExportImage()
default_metallic = None
default_roughness = None
if "Metallic" in [s.name for s in default_sockets]:
default_metallic = [s for s in default_sockets if s.name == "Metallic"][0].default_value
if "Roughness" in [s.name for s in default_sockets]:
default_roughness = [s for s in default_sockets if s.name == "Roughness"][0].default_value
for result, socket in zip(results, sockets):
# Assume that user know what he does, and that channels/images are already combined correctly for pbr
# If not, we are going to keep only the first texture found
@ -334,15 +326,9 @@ def __get_image_data_mapping(sockets, default_sockets, results, use_tile, export
# Since metal/roughness are always used together, make sure
# the other channel is filled.
if socket.socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
if default_roughness is not None:
composed_image.fill_with(Channel.G, default_roughness)
else:
composed_image.fill_white(Channel.G)
composed_image.fill_white(Channel.G)
elif socket.socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
if default_metallic is not None:
composed_image.fill_with(Channel.B, default_metallic)
else:
composed_image.fill_white(Channel.B)
composed_image.fill_white(Channel.B)
else:
# copy full image...eventually following sockets might overwrite things
if use_tile is None:

View File

@ -68,13 +68,13 @@ def gather_material(blender_material, export_settings):
export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
return mat_unlit, {"uv_info": uvmap_info, "vc_info": vc_info, "udim_info": udim_info}
orm_texture, default_sockets = __gather_orm_texture(blender_material, export_settings)
orm_texture = __gather_orm_texture(blender_material, export_settings)
emissive_factor = __gather_emissive_factor(blender_material, export_settings)
emissive_texture, uvmap_info_emissive, udim_info_emissive = __gather_emissive_texture(blender_material, export_settings)
extensions, uvmap_info_extensions, udim_info_extensions = __gather_extensions(blender_material, emissive_factor, export_settings)
normal_texture, uvmap_info_normal, udim_info_normal = __gather_normal_texture(blender_material, export_settings)
occlusion_texture, uvmap_info_occlusion, udim_occlusion = __gather_occlusion_texture(blender_material, orm_texture, default_sockets, export_settings)
occlusion_texture, uvmap_info_occlusion, udim_occlusion = __gather_occlusion_texture(blender_material, orm_texture, export_settings)
pbr_metallic_roughness, uvmap_info_pbr_metallic_roughness, vc_info, udim_info_prb_mr = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
if any([i>1.0 for i in emissive_factor or []]) is True:
@ -113,7 +113,7 @@ def gather_material(blender_material, export_settings):
continue
s = NodeSocket(node[0].outputs[0], node[1])
tex, uv_info_additional, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(s, (s,), (), export_settings)
tex, uv_info_additional, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(s, (s,), export_settings)
if tex is not None:
export_settings['exported_images'][node[0].image.name] = 1 # Fully used
uvmap_infos.update({'additional' + str(cpt_additional): uv_info_additional})
@ -302,7 +302,7 @@ def __gather_orm_texture(blender_material, export_settings):
if occlusion.socket is None or not has_image_node_from_socket(occlusion, export_settings):
occlusion = get_socket_from_gltf_material_node(blender_material, "Occlusion")
if occlusion.socket is None or not has_image_node_from_socket(occlusion, export_settings):
return None, None
return None
metallic_socket = get_socket(blender_material, "Metallic")
roughness_socket = get_socket(blender_material, "Roughness")
@ -310,38 +310,34 @@ def __gather_orm_texture(blender_material, export_settings):
hasMetal = metallic_socket.socket is not None and has_image_node_from_socket(metallic_socket, export_settings)
hasRough = roughness_socket.socket is not None and has_image_node_from_socket(roughness_socket, export_settings)
default_sockets = ()
# Warning: for default socket, do not use NodeSocket object, because it will break cache
# Using directlty the Blender socket object
if not hasMetal and not hasRough:
metallic_roughness = get_socket_from_gltf_material_node(blender_material, "MetallicRoughness")
if metallic_roughness.socket is None or not has_image_node_from_socket(metallic_roughness, export_settings):
return None, default_sockets
return None
result = (occlusion, metallic_roughness)
elif not hasMetal:
result = (occlusion, roughness_socket)
default_sockets = (metallic_socket.socket,)
elif not hasRough:
result = (occlusion, metallic_socket)
default_sockets = (roughness_socket.socket,)
else:
result = (occlusion, roughness_socket, metallic_socket)
default_sockets = ()
if not gltf2_blender_gather_texture_info.check_same_size_images(result, export_settings):
print_console("INFO",
"Occlusion and metal-roughness texture will be exported separately "
"(use same-sized images if you want them combined)")
return None, ()
return None
# Double-check this will past the filter in texture_info
info, _, _, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, default_sockets, export_settings)
info, _, _, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
if info is None:
return None, ()
return None
return result, default_sockets
return result
def __gather_occlusion_texture(blender_material, orm_texture, default_sockets, export_settings):
def __gather_occlusion_texture(blender_material, orm_texture, export_settings):
occlusion = get_socket(blender_material, "Occlusion")
if occlusion.socket is None:
occlusion = get_socket_from_gltf_material_node(blender_material, "Occlusion")
@ -350,7 +346,6 @@ def __gather_occlusion_texture(blender_material, orm_texture, default_sockets, e
occlusion_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
occlusion,
orm_texture or (occlusion,),
default_sockets,
export_settings)
return occlusion_texture, \
{"occlusionTexture" : uvmap_info}, {'occlusionTexture': udim_info } if len(udim_info.keys()) > 0 else {}

View File

@ -108,7 +108,7 @@ def __gather_base_color_texture(blender_material, export_settings):
if not inputs:
return None, {}, {}, None
tex, uvmap_info, udim_info, factor = gather_texture_info(inputs[0], inputs, (), export_settings)
tex, uvmap_info, udim_info, factor = gather_texture_info(inputs[0], inputs, export_settings)
return tex, {'baseColorTexture': uvmap_info}, {'baseColorTexture': udim_info} if len(udim_info.keys()) > 0 else {}, factor
@ -140,7 +140,6 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
hasMetal = metallic_socket.socket is not None and has_image_node_from_socket(metallic_socket, export_settings)
hasRough = roughness_socket.socket is not None and has_image_node_from_socket(roughness_socket, export_settings)
default_sockets = ()
# Warning: for default socket, do not use NodeSocket object, because it will break cache
# Using directlty the Blender socket object
if not hasMetal and not hasRough:
@ -149,18 +148,14 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
return None, {}, {}, None
elif not hasMetal:
texture_input = (roughness_socket,)
default_sockets = (metallic_socket.socket,)
elif not hasRough:
texture_input = (metallic_socket,)
default_sockets = (roughness_socket.socket,)
else:
texture_input = (metallic_socket, roughness_socket)
default_sockets = ()
tex, uvmap_info, udim_info, factor = gather_texture_info(
texture_input[0],
orm_texture or texture_input,
default_sockets,
export_settings,
)

View File

@ -138,7 +138,6 @@ def gather_base_color_texture(info, export_settings):
unlit_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sockets[0],
sockets,
(),
export_settings,
)

View File

@ -20,7 +20,6 @@ from . import gltf2_blender_gather_image
@cached
def gather_texture(
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets,
use_tile: bool,
export_settings):
"""
@ -34,7 +33,7 @@ def gather_texture(
if not __filter_texture(blender_shader_sockets, export_settings):
return None, None, False
source, webp_image, image_data, factor, udim_image = __gather_source(blender_shader_sockets, default_sockets, use_tile, export_settings)
source, webp_image, image_data, factor, udim_image = __gather_source(blender_shader_sockets, use_tile, export_settings)
exts, remove_source = __gather_extensions(blender_shader_sockets, source, webp_image, image_data, export_settings)
@ -197,8 +196,8 @@ def __gather_sampler(blender_shader_sockets, export_settings):
export_settings)
def __gather_source(blender_shader_sockets, default_sockets, use_tile, export_settings):
source, image_data, factor, udim_image = gltf2_blender_gather_image.gather_image(blender_shader_sockets, default_sockets, use_tile, export_settings)
def __gather_source(blender_shader_sockets, use_tile, export_settings):
source, image_data, factor, udim_image = gltf2_blender_gather_image.gather_image(blender_shader_sockets, use_tile, export_settings)
if export_settings['gltf_keep_original_textures'] is False \

View File

@ -24,25 +24,20 @@ from .gltf2_blender_search_node_tree import \
# occlusion the primary_socket would be the occlusion socket, and
# blender_shader_sockets would be the (O,R,M) sockets.
# Default socket parameter is used when there is a mapping between channels, and one of the channel is not a texture
# In that case, we will create a texture with one channel from texture, other from default socket value
# Example: MetallicRoughness
def gather_texture_info(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, default_sockets, 'DEFAULT', filter_type, export_settings)
def gather_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, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, (), 'NORMAL', filter_type, export_settings)
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', filter_type, export_settings)
def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, default_sockets, 'OCCLUSION', filter_type, export_settings)
def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
@cached
def __gather_texture_info_helper(
primary_socket: bpy.types.NodeSocket,
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets,
kind: str,
filter_type: str,
export_settings):
@ -51,7 +46,7 @@ def __gather_texture_info_helper(
tex_transform, uvmap_info = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
index, factor, udim_image = __gather_index(blender_shader_sockets, default_sockets, None, export_settings)
index, factor, udim_image = __gather_index(blender_shader_sockets, None, export_settings)
if udim_image is not None:
udim_info = {'udim': udim_image is not None, 'image': udim_image, 'sockets': blender_shader_sockets}
else:
@ -91,7 +86,7 @@ def gather_udim_texture_info(
tex_transform, _ = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
export_settings['current_udim_info'] = udim_info
index, _, _ = __gather_index(blender_shader_sockets, (), udim_info['image'].name + str(udim_info['tile']), export_settings)
index, _, _ = __gather_index(blender_shader_sockets, udim_info['image'].name + str(udim_info['tile']), export_settings)
export_settings['current_udim_info'] = {}
fields = {
@ -182,9 +177,9 @@ def __gather_occlusion_strength(primary_socket, export_settings):
return None
def __gather_index(blender_shader_sockets, default_sockets, use_tile, export_settings):
def __gather_index(blender_shader_sockets, use_tile, export_settings):
# We just put the actual shader into the 'index' member
return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets, default_sockets, use_tile, export_settings)
return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets, use_tile, export_settings)
def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):

View File

@ -20,8 +20,8 @@
bl_info = {
"name": "Material Utilities",
"author": "MichaleW, ChrisHinde",
"version": (2, 2, 0),
"blender": (2, 80, 0),
"version": (2, 2, 1),
"blender": (3, 0, 0),
"location": "View3D > Shift + Q key",
"description": "Menu of material tools (assign, select..) in the 3D View",
"warning": "Beta",

View File

@ -55,6 +55,7 @@ class VIEW3D_MT_materialutilities_assign_material(bpy.types.Menu):
layout.separator()
for material_name, material in materials:
material.preview_ensure()
op = layout.operator(bl_id,
text = material_name,
icon_value = material.preview.icon_id)
@ -119,6 +120,7 @@ class VIEW3D_MT_materialutilities_select_by_material(bpy.types.Menu):
# There's no point in showing materials with 0 users
# (It will still show materials with fake user though)
if material.users > 0:
material.preview_ensure()
op = layout.operator(bl_id,
text = material_name,
icon_value = material.preview.icon_id
@ -140,6 +142,7 @@ class VIEW3D_MT_materialutilities_select_by_material(bpy.types.Menu):
if material.name in materials_added:
continue
material.preview_ensure()
op = layout.operator(bl_id,
text = material.name,
icon_value = material.preview.icon_id

View File

@ -5,7 +5,7 @@
bl_info = {
"name": "Node Wrangler",
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
"version": (3, 47),
"version": (3, 48),
"blender": (4, 0, 0),
"location": "Node Editor Toolbar or Shift-W",
"description": "Various tools to enhance and speed up node-based workflow",

View File

@ -518,7 +518,7 @@ class NWPreviewNode(Operator, NWBase):
output_sockets = self.get_output_sockets(node.node_tree)
if len(output_sockets):
free_socket = None
for socket in output_sockets:
for i, socket in enumerate(output_sockets):
if is_viewer_socket(socket) and socket.socket_type == socket_type:
# if viewer output is already used but leads to the same socket we can still use it
is_used = self.is_socket_used_other_mats(socket)
@ -694,7 +694,6 @@ class NWPreviewNode(Operator, NWBase):
else:
out_i = valid_outputs[0]
make_links = [] # store sockets for new links
if active.outputs:
# If there is no 'GEOMETRY' output type - We can't preview the node
if out_i is None:
@ -708,26 +707,25 @@ class NWPreviewNode(Operator, NWBase):
break
if geometryoutindex is None:
# Create geometry socket
geometryoutput.inputs.new(socket_type, 'Geometry')
geometryoutindex = len(geometryoutput.inputs) - 1
geometry_output_socket = base_node_tree.interface.new_socket(
'Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry')
geometryoutindex = geometry_output_socket.index
make_links.append((active.outputs[out_i], geometryoutput.inputs[geometryoutindex]))
output_socket = geometryoutput.inputs[geometryoutindex]
for li_from, li_to in make_links:
connect_sockets(li_from, li_to)
# Create links through node groups until we reach the active node
tree = base_node_tree
link_end = output_socket
while tree.nodes.active != active:
node = tree.nodes.active
viewer_socket = self.ensure_viewer_socket(
node, 'NodeSocketGeometry', connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None)
link_start = node.outputs[viewer_socket_name]
node_socket = viewer_socket
if node_socket in delete_sockets:
delete_sockets.remove(node_socket)
link_start = node.outputs[viewer_socket.identifier]
if viewer_socket in delete_sockets:
delete_sockets.remove(viewer_socket)
connect_sockets(link_start, link_end)
# Iterate
link_end = self.ensure_group_output(node.node_tree).inputs[viewer_socket_name]
link_end = self.ensure_group_output(node.node_tree).inputs[viewer_socket.identifier]
tree = tree.nodes.active.node_tree
connect_sockets(active.outputs[out_i], link_end)
@ -778,14 +776,10 @@ class NWPreviewNode(Operator, NWBase):
else:
out_i = valid_outputs[0]
make_links = [] # store sockets for new links
if active.outputs:
socket_type = 'NodeSocketShader'
materialout_index = 1 if active.outputs[out_i].name == "Volume" else 0
make_links.append((active.outputs[out_i], materialout.inputs[materialout_index]))
output_socket = materialout.inputs[materialout_index]
for li_from, li_to in make_links:
connect_sockets(li_from, li_to)
# Create links through node groups until we reach the active node
tree = base_node_tree
@ -794,13 +788,12 @@ class NWPreviewNode(Operator, NWBase):
node = tree.nodes.active
viewer_socket = self.ensure_viewer_socket(
node, socket_type, connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None)
link_start = node.outputs[viewer_socket_name]
node_socket = viewer_socket
if node_socket in delete_sockets:
delete_sockets.remove(node_socket)
link_start = node.outputs[viewer_socket.identifier]
if viewer_socket in delete_sockets:
delete_sockets.remove(viewer_socket)
connect_sockets(link_start, link_end)
# Iterate
link_end = self.ensure_group_output(node.node_tree).inputs[viewer_socket_name]
link_end = self.ensure_group_output(node.node_tree).inputs[viewer_socket.identifier]
tree = tree.nodes.active.node_tree
connect_sockets(active.outputs[out_i], link_end)

View File

@ -6,8 +6,8 @@ bl_info = {
"name": "Real Snow",
"description": "Generate snow mesh",
"author": "Marco Pavanello, Drew Perttula",
"version": (1, 3),
"blender": (3, 1, 0),
"version": (1, 3, 2),
"blender": (4, 1, 0),
"location": "View 3D > Properties Panel",
"doc_url": "{BLENDER_MANUAL_URL}/addons/object/real_snow.html",
"tracker_url": "https://gitlab.com/marcopavanello/real-snow/-/issues",
@ -273,20 +273,19 @@ def add_material(obj: bpy.types.Object):
coord.location = (-1900, 0)
# Change node parameters
principled.distribution = "MULTI_GGX"
principled.subsurface_method = "RANDOM_WALK"
principled.inputs[0].default_value[0] = 0.904
principled.subsurface_method = "RANDOM_WALK_SKIN"
principled.inputs[0].default_value[0] = 0.904 # Base color
principled.inputs[0].default_value[1] = 0.904
principled.inputs[0].default_value[2] = 0.904
principled.inputs[1].default_value = 1
principled.inputs[2].default_value[0] = 0.36
principled.inputs[2].default_value[1] = 0.46
principled.inputs[2].default_value[2] = 0.6
principled.inputs[3].default_value[0] = 0.904
principled.inputs[3].default_value[1] = 0.904
principled.inputs[3].default_value[2] = 0.904
principled.inputs[7].default_value = 0.224
principled.inputs[9].default_value = 0.1
principled.inputs[15].default_value = 0.1
principled.inputs[7].default_value = 1 # Subsurface weight
principled.inputs[9].default_value = 1 # Subsurface scale
principled.inputs[8].default_value[0] = 0.36 # Subsurface radius
principled.inputs[8].default_value[1] = 0.46
principled.inputs[8].default_value[2] = 0.6
principled.inputs[12].default_value = 0.224 # Specular
principled.inputs[2].default_value = 0.1 # Roughness
principled.inputs[19].default_value = 0.1 # Coat roughness
principled.inputs[20].default_value = 1.2 # Coat IOR
vec_math.operation = "MULTIPLY"
vec_math.inputs[1].default_value[0] = 0.5
vec_math.inputs[1].default_value[1] = 0.5
@ -321,7 +320,7 @@ def add_material(obj: bpy.types.Object):
# Link nodes
link = mat.node_tree.links
link.new(principled.outputs[0], output.inputs[0])
link.new(vec_math.outputs[0], principled.inputs[2])
link.new(vec_math.outputs[0], principled.inputs[8])
link.new(com_xyz.outputs[0], vec_math.inputs[0])
link.new(dis.outputs[0], output.inputs[2])
link.new(mul1.outputs[0], dis.inputs[0])
@ -329,7 +328,7 @@ def add_material(obj: bpy.types.Object):
link.new(add2.outputs[0], add1.inputs[0])
link.new(mul2.outputs[0], add2.inputs[0])
link.new(mul3.outputs[0], add2.inputs[1])
link.new(range1.outputs[0], principled.inputs[14])
link.new(range1.outputs[0], principled.inputs[18])
link.new(range2.outputs[0], mul3.inputs[0])
link.new(range3.outputs[0], add1.inputs[1])
link.new(vor.outputs[4], range1.inputs[0])
@ -342,7 +341,7 @@ def add_material(obj: bpy.types.Object):
link.new(mapping.outputs[0], noise3.inputs[0])
link.new(coord.outputs[3], mapping.inputs[0])
# Set displacement and add material
mat.cycles.displacement_method = "DISPLACEMENT"
mat.displacement_method = "DISPLACEMENT"
obj.data.materials.append(mat)

View File

@ -229,7 +229,7 @@ class SunPosProperties(PropertyGroup):
object_collection_type: EnumProperty(
name="Display type",
description="Type of Sun motion to visualize.",
description="Type of Sun motion to visualize",
items=(
('ANALEMMA', "Analemma", "Trajectory of the Sun in the sky during the year, for a given time of the day"),
('DIURNAL', "Diurnal", "Trajectory of the Sun in the sky during a single day"),

View File

@ -32,7 +32,7 @@ translations_tuple = (
(False, ())),
),
(("*", "Daylight Savings"),
(("bpy.types.SunPosProperties.use_daylight_savings"),
(("bpy.types.SunPosProperties.use_daylight_savings",),
()),
("fr_FR", "Heure dété",
(False, ())),
@ -218,7 +218,7 @@ translations_tuple = (
("fr_FR", "Collection dobjets utilisée pour visualiser la trajectoire du Soleil",
(False, ())),
),
(("*", "Type of Sun motion to visualize."),
(("*", "Type of Sun motion to visualize"),
(("bpy.types.SunPosProperties.object_collection_type",),
()),
("fr_FR", "Type de trajectoire du Soleil à visualiser",
@ -530,7 +530,7 @@ translations_tuple = (
(("scripts/addons/sun_position/ui_sun.py:94",
"scripts/addons/sun_position/ui_sun.py:137"),
()),
("fr_FR", "Veuillez activer Utiliser nœuds dans le panneau Monde",
("fr_FR", "Veuillez activer Utiliser nœuds dans le panneau Monde.",
(False, ())),
),
(("*", "World > Sun Position"),