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 = { bl_info = {
"name": "3D-Coat Applink", "name": "3D-Coat Applink",
"author": "Kalle-Samuli Riihikoski (haikalle)", "author": "Kalle-Samuli Riihikoski (haikalle)",
"version": (4, 9, 34), "version": (4, 9, 35),
"blender": (2, 80, 0), "blender": (4, 1, 0),
"location": "Scene > 3D-Coat Applink", "location": "Scene > 3D-Coat Applink",
"description": "Transfer data between 3D-Coat/Blender", "description": "Transfer data between 3D-Coat/Blender",
"warning": "", "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(applink_tree.outputs[5], disp_node.inputs[0])
main_material.links.new(disp_node.outputs[0], out_mat.inputs[2]) main_material.links.new(disp_node.outputs[0], out_mat.inputs[2])
coatMat.cycles.displacement_method = 'BOTH' coatMat.displacement_method = 'BOTH'
else: else:
if (texcoat['alpha'] != []): 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(applink_tree.outputs[5], disp_node.inputs[0])
main_material.links.new(disp_node.outputs[0], out_mat.inputs[2]) main_material.links.new(disp_node.outputs[0], out_mat.inputs[2])
coatMat.cycles.displacement_method = 'BOTH' coatMat.displacement_method = 'BOTH'
else: else:
if (texcoat['alpha'] != []): if (texcoat['alpha'] != []):

View File

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

View File

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

View File

@ -1638,7 +1638,7 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementSmoothing') fbx_layer = elem_find_first(fbx_obj, b'LayerElementSmoothing')
if fbx_layer is None: if fbx_layer is None:
return False return
# all should be valid # all should be valid
(fbx_layer_name, (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 # udk has 'Direct' mapped, with no Smoothing, not sure why, but ignore these
if fbx_layer_data is None: if fbx_layer_data is None:
return False return
if fbx_layer_mapping == b'ByEdge': if fbx_layer_mapping == b'ByEdge':
# some models have bad edge data, we can't use this info... # some models have bad edge data, we can't use this info...
if not mesh.edges: if not mesh.edges:
print("warning skipping sharp edges data, no valid edges...") print("warning skipping sharp edges data, no valid edges...")
return False return
blen_data = MESH_ATTRIBUTE_SHARP_EDGE.ensure(mesh.attributes).data blen_data = MESH_ATTRIBUTE_SHARP_EDGE.ensure(mesh.attributes).data
fbx_item_size = 1 fbx_item_size = 1
@ -1669,21 +1669,23 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
1, fbx_item_size, layer_id, 1, fbx_item_size, layer_id,
xform=np.logical_not, # in FBX, 0 (False) is sharp, but in Blender True is sharp. xform=np.logical_not, # in FBX, 0 (False) is sharp, but in Blender True is sharp.
) )
return False
elif fbx_layer_mapping == b'ByPolygon': 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 fbx_item_size = 1
assert(fbx_item_size == MESH_ATTRIBUTE_SHARP_FACE.item_size) 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, mesh, blen_data, MESH_ATTRIBUTE_SHARP_FACE.foreach_attribute, MESH_ATTRIBUTE_SHARP_FACE.dtype,
fbx_layer_data, None, fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref, fbx_layer_mapping, fbx_layer_ref,
1, fbx_item_size, layer_id, 1, fbx_item_size, layer_id,
xform=lambda s: (s == 0), # smoothgroup bitflags, treat as booleans for now xform=lambda s: (s == 0), # smoothgroup bitflags, treat as booleans for now
) )
if not sharp_face_set_successfully:
mesh.attributes.remove(sharp_face)
else: else:
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping)) 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): def blen_read_geom_layer_edge_crease(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementEdgeCrease') 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!") print("ERROR: No polygons, but edges exist. Ignoring the edges!")
# must be after edge, face loading. # 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) 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) clnors = np.empty(len(mesh.loops) * 3, dtype=bl_nors_dtype)
mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors) 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 # 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. # 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))) mesh.normals_split_custom_set(tuple(zip(*(iter(clnors.data),) * 3)))
if settings.use_custom_normals: if settings.use_custom_normals:
mesh.attributes.remove(mesh.attributes["temp_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: if settings.use_custom_props:
blen_read_custom_properties(fbx_obj, mesh, settings) 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 = arm.bl_obj.pose.bones[self.bl_bone]
pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix() 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) blen_read_custom_properties(self.fbx_elem, pose_bone, settings)
for child in self.children: for child in self.children:

View File

@ -5,7 +5,7 @@
bl_info = { bl_info = {
'name': 'glTF 2.0 format', 'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (4, 1, 36), "version": (4, 1, 38),
'blender': (4, 1, 0), 'blender': (4, 1, 0),
'location': 'File > Import-Export', 'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0', '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) exporter.add_scene(scene, idx==active_scene_idx, export_settings=export_settings)
for animation in animations: for animation in animations:
exporter.add_animation(animation) exporter.add_animation(animation)
exporter.manage_gpu_instancing_nodes(export_settings)
exporter.traverse_unused_skins(unused_skins) exporter.traverse_unused_skins(unused_skins)
exporter.traverse_additional_textures() exporter.traverse_additional_textures()
exporter.traverse_additional_images() exporter.traverse_additional_images()

View File

@ -276,57 +276,44 @@ class GlTF2Exporter:
self.nodes_idx_to_remove.extend(insts) 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: 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 = [] self.nodes_idx_to_remove = []
for node_idx in self.__gltf.scenes[scene_num].nodes: for node_idx in self.__gltf.scenes[scene_num].nodes:
node = self.__gltf.nodes[node_idx] node = self.__gltf.nodes[node_idx]
if node.mesh is None: if node.mesh is None:
self.manage_gpu_instancing(node) self.manage_gpu_instancing(node)
else: else:
self.manage_gpu_instancing(node, also_mesh=True) self.manage_gpu_instancing(node, also_mesh=True)
for child_idx in node.children: for child_idx in node.children:
child = self.__gltf.nodes[child_idx] child = self.__gltf.nodes[child_idx]
self.manage_gpu_instancing(child, also_mesh=child.mesh is not None) 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() self.nodes_idx_to_remove.sort()
for node_idx in self.__gltf.scenes[scene_num].nodes: for node_idx in self.__gltf.scenes[scene_num].nodes:
self.recursive_slide_node_idx(node_idx) self.recursive_slide_node_idx(node_idx)
new_node_list = [] new_node_list = []
for node_idx in self.__gltf.scenes[scene_num].nodes: 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:
len_ = len([i for i in self.nodes_idx_to_remove if i < node_idx]) len_ = len([i for i in self.nodes_idx_to_remove if i < node_idx])
new_joint_list.append(node_idx - len_) new_node_list.append(node_idx - len_)
skin.joints = new_joint_list self.__gltf.scenes[scene_num].nodes = new_node_list
if skin.skeleton is not None:
len_ = len([i for i in self.nodes_idx_to_remove if i < skin.skeleton]) for skin in self.__gltf.skins:
skin.skeleton = skin.skeleton - len_ 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 # Remove animation channels that was targeting a node that will be removed
new_animation_list = [] new_animation_list = []
@ -345,6 +332,23 @@ class GlTF2Exporter:
# And now really remove nodes # 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] 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): def recursive_slide_node_idx(self, node_idx):
node = self.__gltf.nodes[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_texture, uvmap_info , udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
anisotropy_data['tex_socket'], anisotropy_data['tex_socket'],
(anisotropy_data['tex_socket'],), (anisotropy_data['tex_socket'],),
(),
export_settings, export_settings,
) )
anisotropy_extension['anisotropyTexture'] = anisotropy_texture 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( anisotropyTexture, uvmap_info, _, _ = gltf2_blender_gather_texture_info.gather_texture_info(
primary_socket, primary_socket,
sockets, sockets,
(),
export_settings, export_settings,
filter_type='ANY') 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_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_socket, clearcoat_socket,
clearcoat_roughness_slots, clearcoat_roughness_slots,
(),
export_settings, export_settings,
) )
clearcoat_extension['clearcoatTexture'] = clearcoat_texture clearcoat_extension['clearcoatTexture'] = clearcoat_texture
@ -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_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_roughness_socket, clearcoat_roughness_socket,
clearcoat_roughness_slots, clearcoat_roughness_slots,
(),
export_settings, export_settings,
) )
clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture

View File

@ -58,7 +58,7 @@ def export_emission_texture(blender_material, export_settings):
emissive = get_socket(blender_material, "Emissive") emissive = get_socket(blender_material, "Emissive")
if emissive.socket is None: if emissive.socket is None:
emissive = get_socket_from_gltf_material_node(blender_material, "Emissive") 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 {} 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): 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( original_sheenColor_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sheenTint_socket, sheenTint_socket,
(sheenTint_socket,), (sheenTint_socket,),
(),
export_settings, export_settings,
) )
sheen_extension['sheenColorTexture'] = original_sheenColor_texture 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( original_sheenRoughness_texture, uvmap_info , udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sheenRoughness_socket, sheenRoughness_socket,
(sheenRoughness_socket,), (sheenRoughness_socket,),
(),
export_settings, export_settings,
) )
sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture

View File

@ -56,7 +56,6 @@ def export_specular(blender_material, export_settings):
specular_texture, uvmap_info, udim_info, _ = gather_texture_info( specular_texture, uvmap_info, udim_info, _ = gather_texture_info(
specular_socket, specular_socket,
(specular_socket,), (specular_socket,),
(),
export_settings, export_settings,
) )
specular_extension['specularTexture'] = specular_texture 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( specularcolor_texture, uvmap_info, udim_info, _ = gather_texture_info(
speculartint_socket, speculartint_socket,
(speculartint_socket,), (speculartint_socket,),
(),
export_settings, export_settings,
) )
specular_extension['specularColorTexture'] = specularcolor_texture 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( combined_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
transmission_socket, transmission_socket,
transmission_slots, transmission_slots,
(),
export_settings, export_settings,
) )
if has_transmission_texture: if has_transmission_texture:

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( combined_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
thickness_socket, thickness_socket,
thickness_slots, thickness_slots,
(),
export_settings, export_settings,
) )
if has_thickness_texture: if has_thickness_texture:

View File

@ -18,13 +18,12 @@ from .gltf2_blender_search_node_tree import get_texture_node_from_socket, detect
@cached @cached
def gather_image( def gather_image(
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket], blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets: typing.Tuple[bpy.types.NodeSocket],
use_tile: bool, use_tile: bool,
export_settings): export_settings):
if not __filter_image(blender_shader_sockets, export_settings): if not __filter_image(blender_shader_sockets, export_settings):
return None, None, None, None 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: if udim_image is not None:
# We are in a UDIM case, so we return no image data # 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 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 # For shared resources, such as images, we just store the portion of data that is needed in the glTF property
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
# resources. # resources.
@ -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 # 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_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 Simple mapping
Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ... Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
""" """
composed_image = ExportImage() composed_image = ExportImage()
default_metallic = None
default_roughness = None
if "Metallic" in [s.name for s in default_sockets]:
default_metallic = [s for s in default_sockets if s.name == "Metallic"][0].default_value
if "Roughness" in [s.name for s in default_sockets]:
default_roughness = [s for s in default_sockets if s.name == "Roughness"][0].default_value
for result, socket in zip(results, sockets): for result, socket in zip(results, sockets):
# Assume that user know what he does, and that channels/images are already combined correctly for pbr # Assume that user know what he does, and that channels/images are already combined correctly for pbr
# If not, we are going to keep only the first texture found # If not, we are going to keep only the first texture found
@ -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 # Since metal/roughness are always used together, make sure
# the other channel is filled. # the other channel is filled.
if socket.socket.name == 'Metallic' and not composed_image.is_filled(Channel.G): if socket.socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
if default_roughness is not None: composed_image.fill_white(Channel.G)
composed_image.fill_with(Channel.G, default_roughness)
else:
composed_image.fill_white(Channel.G)
elif socket.socket.name == 'Roughness' and not composed_image.is_filled(Channel.B): elif socket.socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
if default_metallic is not None: composed_image.fill_white(Channel.B)
composed_image.fill_with(Channel.B, default_metallic)
else:
composed_image.fill_white(Channel.B)
else: else:
# copy full image...eventually following sockets might overwrite things # copy full image...eventually following sockets might overwrite things
if use_tile is None: 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) 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} 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_factor = __gather_emissive_factor(blender_material, export_settings)
emissive_texture, uvmap_info_emissive, udim_info_emissive = __gather_emissive_texture(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) 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) 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) 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: 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 continue
s = NodeSocket(node[0].outputs[0], node[1]) 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: if tex is not None:
export_settings['exported_images'][node[0].image.name] = 1 # Fully used export_settings['exported_images'][node[0].image.name] = 1 # Fully used
uvmap_infos.update({'additional' + str(cpt_additional): uv_info_additional}) 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): 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") 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): 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") metallic_socket = get_socket(blender_material, "Metallic")
roughness_socket = get_socket(blender_material, "Roughness") 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) 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) 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 # Warning: for default socket, do not use NodeSocket object, because it will break cache
# Using directlty the Blender socket object # Using directlty the Blender socket object
if not hasMetal and not hasRough: if not hasMetal and not hasRough:
metallic_roughness = get_socket_from_gltf_material_node(blender_material, "MetallicRoughness") 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): 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) result = (occlusion, metallic_roughness)
elif not hasMetal: elif not hasMetal:
result = (occlusion, roughness_socket) result = (occlusion, roughness_socket)
default_sockets = (metallic_socket.socket,)
elif not hasRough: elif not hasRough:
result = (occlusion, metallic_socket) result = (occlusion, metallic_socket)
default_sockets = (roughness_socket.socket,)
else: else:
result = (occlusion, roughness_socket, metallic_socket) result = (occlusion, roughness_socket, metallic_socket)
default_sockets = ()
if not gltf2_blender_gather_texture_info.check_same_size_images(result, export_settings): if not gltf2_blender_gather_texture_info.check_same_size_images(result, export_settings):
print_console("INFO", print_console("INFO",
"Occlusion and metal-roughness texture will be exported separately " "Occlusion and metal-roughness texture will be exported separately "
"(use same-sized images if you want them combined)") "(use same-sized images if you want them combined)")
return None, () return None
# Double-check this will past the filter in texture_info # Double-check this will past the filter in texture_info
info, _, _, _ = 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: 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") occlusion = get_socket(blender_material, "Occlusion")
if occlusion.socket is None: if occlusion.socket is None:
occlusion = get_socket_from_gltf_material_node(blender_material, "Occlusion") 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_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
occlusion, occlusion,
orm_texture or (occlusion,), orm_texture or (occlusion,),
default_sockets,
export_settings) export_settings)
return occlusion_texture, \ return occlusion_texture, \
{"occlusionTexture" : uvmap_info}, {'occlusionTexture': udim_info } if len(udim_info.keys()) > 0 else {} {"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: if not inputs:
return None, {}, {}, None 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 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) 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) 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 # Warning: for default socket, do not use NodeSocket object, because it will break cache
# Using directlty the Blender socket object # Using directlty the Blender socket object
if not hasMetal and not hasRough: if not hasMetal and not hasRough:
@ -149,18 +148,14 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
return None, {}, {}, None return None, {}, {}, None
elif not hasMetal: elif not hasMetal:
texture_input = (roughness_socket,) texture_input = (roughness_socket,)
default_sockets = (metallic_socket.socket,)
elif not hasRough: elif not hasRough:
texture_input = (metallic_socket,) texture_input = (metallic_socket,)
default_sockets = (roughness_socket.socket,)
else: else:
texture_input = (metallic_socket, roughness_socket) texture_input = (metallic_socket, roughness_socket)
default_sockets = ()
tex, uvmap_info, udim_info, factor = gather_texture_info( tex, uvmap_info, udim_info, factor = gather_texture_info(
texture_input[0], texture_input[0],
orm_texture or texture_input, orm_texture or texture_input,
default_sockets,
export_settings, export_settings,
) )

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( unlit_texture, uvmap_info, udim_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
sockets[0], sockets[0],
sockets, sockets,
(),
export_settings, export_settings,
) )

View File

@ -20,7 +20,6 @@ from . import gltf2_blender_gather_image
@cached @cached
def gather_texture( def gather_texture(
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket], blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets,
use_tile: bool, use_tile: bool,
export_settings): export_settings):
""" """
@ -34,7 +33,7 @@ def gather_texture(
if not __filter_texture(blender_shader_sockets, export_settings): if not __filter_texture(blender_shader_sockets, export_settings):
return None, None, False 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) 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) export_settings)
def __gather_source(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, default_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 \ 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 # occlusion the primary_socket would be the occlusion socket, and
# blender_shader_sockets would be the (O,R,M) sockets. # blender_shader_sockets would be the (O,R,M) sockets.
# Default socket parameter is used when there is a mapping between channels, and one of the channel is not a texture def gather_texture_info(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
# In that case, we will create a texture with one channel from texture, other from default socket value return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', filter_type, export_settings)
# Example: MetallicRoughness
def gather_texture_info(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, default_sockets, 'DEFAULT', filter_type, export_settings)
def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'): def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
return __gather_texture_info_helper(primary_socket, blender_shader_sockets, (), 'NORMAL', filter_type, export_settings) return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', filter_type, export_settings)
def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, default_sockets, export_settings, filter_type='ALL'): 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, default_sockets, 'OCCLUSION', filter_type, export_settings) return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
@cached @cached
def __gather_texture_info_helper( def __gather_texture_info_helper(
primary_socket: bpy.types.NodeSocket, primary_socket: bpy.types.NodeSocket,
blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket], blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
default_sockets,
kind: str, kind: str,
filter_type: str, filter_type: str,
export_settings): 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) 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: if udim_image is not None:
udim_info = {'udim': udim_image is not None, 'image': udim_image, 'sockets': blender_shader_sockets} udim_info = {'udim': udim_image is not None, 'image': udim_image, 'sockets': blender_shader_sockets}
else: else:
@ -91,7 +86,7 @@ def gather_udim_texture_info(
tex_transform, _ = __gather_texture_transform_and_tex_coord(primary_socket, export_settings) tex_transform, _ = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
export_settings['current_udim_info'] = udim_info 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'] = {} export_settings['current_udim_info'] = {}
fields = { fields = {
@ -182,9 +177,9 @@ def __gather_occlusion_strength(primary_socket, export_settings):
return None 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 # 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): def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):

View File

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

View File

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

View File

@ -5,7 +5,7 @@
bl_info = { bl_info = {
"name": "Node Wrangler", "name": "Node Wrangler",
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer", "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
"version": (3, 47), "version": (3, 48),
"blender": (4, 0, 0), "blender": (4, 0, 0),
"location": "Node Editor Toolbar or Shift-W", "location": "Node Editor Toolbar or Shift-W",
"description": "Various tools to enhance and speed up node-based workflow", "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) output_sockets = self.get_output_sockets(node.node_tree)
if len(output_sockets): if len(output_sockets):
free_socket = None 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 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 # 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) is_used = self.is_socket_used_other_mats(socket)
@ -694,7 +694,6 @@ class NWPreviewNode(Operator, NWBase):
else: else:
out_i = valid_outputs[0] out_i = valid_outputs[0]
make_links = [] # store sockets for new links
if active.outputs: if active.outputs:
# If there is no 'GEOMETRY' output type - We can't preview the node # If there is no 'GEOMETRY' output type - We can't preview the node
if out_i is None: if out_i is None:
@ -708,26 +707,25 @@ class NWPreviewNode(Operator, NWBase):
break break
if geometryoutindex is None: if geometryoutindex is None:
# Create geometry socket # Create geometry socket
geometryoutput.inputs.new(socket_type, 'Geometry') geometry_output_socket = base_node_tree.interface.new_socket(
geometryoutindex = len(geometryoutput.inputs) - 1 '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] 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 tree = base_node_tree
link_end = output_socket link_end = output_socket
while tree.nodes.active != active: while tree.nodes.active != active:
node = tree.nodes.active node = tree.nodes.active
viewer_socket = self.ensure_viewer_socket( viewer_socket = self.ensure_viewer_socket(
node, 'NodeSocketGeometry', connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None) node, 'NodeSocketGeometry', connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None)
link_start = node.outputs[viewer_socket_name] link_start = node.outputs[viewer_socket.identifier]
node_socket = viewer_socket if viewer_socket in delete_sockets:
if node_socket in delete_sockets: delete_sockets.remove(viewer_socket)
delete_sockets.remove(node_socket)
connect_sockets(link_start, link_end) connect_sockets(link_start, link_end)
# Iterate # 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 tree = tree.nodes.active.node_tree
connect_sockets(active.outputs[out_i], link_end) connect_sockets(active.outputs[out_i], link_end)
@ -778,14 +776,10 @@ class NWPreviewNode(Operator, NWBase):
else: else:
out_i = valid_outputs[0] out_i = valid_outputs[0]
make_links = [] # store sockets for new links
if active.outputs: if active.outputs:
socket_type = 'NodeSocketShader' socket_type = 'NodeSocketShader'
materialout_index = 1 if active.outputs[out_i].name == "Volume" else 0 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] 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 # Create links through node groups until we reach the active node
tree = base_node_tree tree = base_node_tree
@ -794,13 +788,12 @@ class NWPreviewNode(Operator, NWBase):
node = tree.nodes.active node = tree.nodes.active
viewer_socket = self.ensure_viewer_socket( viewer_socket = self.ensure_viewer_socket(
node, socket_type, connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None) 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] link_start = node.outputs[viewer_socket.identifier]
node_socket = viewer_socket if viewer_socket in delete_sockets:
if node_socket in delete_sockets: delete_sockets.remove(viewer_socket)
delete_sockets.remove(node_socket)
connect_sockets(link_start, link_end) connect_sockets(link_start, link_end)
# Iterate # 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 tree = tree.nodes.active.node_tree
connect_sockets(active.outputs[out_i], link_end) connect_sockets(active.outputs[out_i], link_end)

View File

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

View File

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

View File

@ -32,7 +32,7 @@ translations_tuple = (
(False, ())), (False, ())),
), ),
(("*", "Daylight Savings"), (("*", "Daylight Savings"),
(("bpy.types.SunPosProperties.use_daylight_savings"), (("bpy.types.SunPosProperties.use_daylight_savings",),
()), ()),
("fr_FR", "Heure dété", ("fr_FR", "Heure dété",
(False, ())), (False, ())),
@ -218,7 +218,7 @@ translations_tuple = (
("fr_FR", "Collection dobjets utilisée pour visualiser la trajectoire du Soleil", ("fr_FR", "Collection dobjets utilisée pour visualiser la trajectoire du Soleil",
(False, ())), (False, ())),
), ),
(("*", "Type of Sun motion to visualize."), (("*", "Type of Sun motion to visualize"),
(("bpy.types.SunPosProperties.object_collection_type",), (("bpy.types.SunPosProperties.object_collection_type",),
()), ()),
("fr_FR", "Type de trajectoire du Soleil à visualiser", ("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:94",
"scripts/addons/sun_position/ui_sun.py:137"), "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, ())), (False, ())),
), ),
(("*", "World > Sun Position"), (("*", "World > Sun Position"),