Merge branch 'blender-v4.2-release'

This commit is contained in:
Brecht Van Lommel 2024-06-06 15:29:35 +02:00
commit 5e1812dbb8
5 changed files with 119 additions and 99 deletions

@ -378,7 +378,13 @@ def script_paths_pref():
return paths
def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True):
def script_paths_system_environment():
"""Returns a list of system script directories from environment variables."""
if env_system_path := _os.environ.get("BLENDER_SYSTEM_SCRIPTS"):
return [_os.path.normpath(env_system_path)]
return []
def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True, use_system_environment=True):
"""
Returns a list of valid script paths.
@ -388,6 +394,10 @@ def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True)
:type user_pref: bool
:arg check_all: Include local, user and system paths rather just the paths Blender uses.
:type check_all: bool
:arg use_user: Include user paths
:type use_user: bool
:arg use_system_environment: Include BLENDER_SYSTEM_SCRIPTS variable path
:type use_system_environment: bool
:return: script paths.
:rtype: list
"""
@ -419,6 +429,9 @@ def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True)
if user_pref:
base_paths.extend(script_paths_pref())
if use_system_environment:
base_paths.extend(script_paths_system_environment())
scripts = []
for path in base_paths:
if not path:
@ -473,16 +486,22 @@ def app_template_paths(*, path=None):
"""
subdir_args = (path,) if path is not None else ()
# Note: keep in sync with: Blender's 'BKE_appdir_app_template_any'.
# Uses 'BLENDER_USER_SCRIPTS', 'BLENDER_SYSTEM_SCRIPTS'
# ... in this case 'system' accounts for 'local' too.
for resource_fn, module_name in (
(_user_resource, "bl_app_templates_user"),
(system_resource, "bl_app_templates_system"),
):
path_test = resource_fn('SCRIPTS', path=_os.path.join("startup", module_name, *subdir_args))
if path_test and _os.path.isdir(path_test):
# Uses BLENDER_USER_SCRIPTS
path_test = _user_resource('SCRIPTS', path=_os.path.join("startup", "bl_app_templates_user", *subdir_args))
if path_test and _os.path.isdir(path_test):
yield path_test
# Uses BLENDER_SYSTTEM_SCRIPTS
for path in script_paths_system_environment():
path_test = _os.path.join(path, "startup", "bl_app_templates_system", *subdir_args)
if _os.path.isdir(path_test):
yield path_test
# Uses default local or system location.
path_test = system_resource('SCRIPTS', path=_os.path.join("startup", "bl_app_templates_system", *subdir_args))
if path_test and _os.path.isdir(path_test):
yield path_test
def preset_paths(subdir):
"""
@ -494,7 +513,7 @@ def preset_paths(subdir):
:rtype: list
"""
dirs = []
for path in script_paths(subdir="presets", check_all=True):
for path in script_paths(subdir="presets"):
directory = _os.path.join(path, subdir)
if not directory.startswith(path):
raise Exception("invalid subdir given {!r}".format(subdir))

@ -5,7 +5,8 @@
import bpy
from bpy.types import Operator
from bpy.props import BoolProperty
from bpy_extras.node_utils import connect_sockets
from bpy_extras.node_utils import find_base_socket_type, connect_sockets
from bpy.app.translations import pgettext_data as data_
from .node_editor.node_functions import (
NodeEditorBase,
@ -15,9 +16,7 @@ from .node_editor.node_functions import (
get_output_location,
get_internal_socket,
is_visible_socket,
is_viewer_socket,
is_viewer_link,
viewer_socket_name,
force_update,
)
@ -65,7 +64,7 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
output_sockets = self.get_output_sockets(node_tree)
if len(output_sockets):
for i, socket in enumerate(output_sockets):
if is_viewer_socket(socket) and socket.socket_type == socket_type:
if socket.is_inspect_output:
# If viewer output is already used but leads to the same socket we can still use it.
is_used = self.has_socket_other_users(socket)
if is_used:
@ -82,7 +81,7 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
if viewer_socket is None:
# Create viewer socket.
viewer_socket = node_tree.interface.new_socket(
viewer_socket_name, in_out='OUTPUT', socket_type=socket_type)
data_("(Viewer)"), in_out='OUTPUT', socket_type=socket_type)
viewer_socket.is_inspect_output = True
return viewer_socket
@ -101,9 +100,9 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
return groupout
@classmethod
def search_sockets(cls, node, r_sockets, index=None):
"""Recursively scan nodes for viewer sockets and store them in a list"""
for i, input_socket in enumerate(node.inputs):
def search_connected_viewer_sockets(cls, output_node, r_sockets, index=None):
"""From an output node, recursively scan node tree for connected viewer sockets"""
for i, input_socket in enumerate(output_node.inputs):
if index and i != index:
continue
if len(input_socket.links):
@ -112,25 +111,26 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
external_socket = link.from_socket
if hasattr(next_node, "node_tree"):
for socket_index, socket in enumerate(next_node.node_tree.interface.items_tree):
# Find inside socket matching outside one.
if socket.identifier == external_socket.identifier:
break
if is_viewer_socket(socket) and socket not in r_sockets:
if socket.is_inspect_output and socket not in r_sockets:
r_sockets.append(socket)
# continue search inside of node group but restrict socket to where we came from.
# Continue search inside of node group but restrict socket to where we came from.
groupout = get_group_output_node(next_node.node_tree)
cls.search_sockets(groupout, r_sockets, index=socket_index)
cls.search_connected_viewer_sockets(groupout, r_sockets, index=socket_index)
@classmethod
def scan_nodes(cls, tree, sockets):
"""Recursively get all viewer sockets in a material tree"""
def search_viewer_sockets_in_tree(cls, tree, r_sockets):
"""Recursively get all viewer sockets in a node tree"""
for node in tree.nodes:
if hasattr(node, "node_tree"):
if node.node_tree is None:
continue
for socket in cls.get_output_sockets(node.node_tree):
if is_viewer_socket(socket) and (socket not in sockets):
sockets.append(socket)
cls.scan_nodes(node.node_tree, sockets)
if socket.is_inspect_output and (socket not in r_sockets):
r_sockets.append(socket)
cls.search_viewer_sockets_in_tree(node.node_tree, r_sockets)
@staticmethod
def remove_socket(tree, socket):
@ -156,23 +156,16 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
output_node = get_group_output_node(node_tree, output_node_idname=self.shader_output_idname)
if output_node is not None:
self.search_sockets(output_node, self.used_viewer_sockets_active_mat)
self.search_connected_viewer_sockets(output_node, self.used_viewer_sockets_active_mat)
return socket in self.used_viewer_sockets_active_mat
def has_socket_other_users(self, socket):
"""List the other users for this socket (other materials or geometry nodes groups)"""
if not hasattr(self, "other_viewer_sockets_users"):
self.other_viewer_sockets_users = []
if socket.socket_type == 'NodeSocketShader':
for mat in bpy.data.materials:
if mat.node_tree == bpy.context.space_data.node_tree or not hasattr(mat.node_tree, "nodes"):
continue
# Get viewer node.
output_node = get_group_output_node(mat.node_tree,
output_node_idname=self.shader_output_idname)
if output_node is not None:
self.search_sockets(output_node, self.other_viewer_sockets_users)
elif socket.socket_type == 'NodeSocketGeometry':
if socket.socket_type == 'NodeSocketGeometry':
# This operator can only preview Geometry sockets for geometry nodes,
# so the rest of them are shader nodes.
for obj in bpy.data.objects:
for mod in obj.modifiers:
if mod.type != 'NODES' or mod.node_group == bpy.context.space_data.node_tree:
@ -180,7 +173,16 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
# Get viewer node.
output_node = get_group_output_node(mod.node_group)
if output_node is not None:
self.search_sockets(output_node, self.other_viewer_sockets_users)
self.search_connected_viewer_sockets(output_node, self.other_viewer_sockets_users)
else:
for mat in bpy.data.materials:
if mat.node_tree == bpy.context.space_data.node_tree or not hasattr(mat.node_tree, "nodes"):
continue
# Get viewer node.
output_node = get_group_output_node(mat.node_tree,
output_node_idname=self.shader_output_idname)
if output_node is not None:
self.search_connected_viewer_sockets(output_node, self.other_viewer_sockets_users)
return socket in self.other_viewer_sockets_users
def get_output_index(self, node, output_node, is_base_node_tree, socket_type, check_type=False):
@ -260,7 +262,7 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
# Scan through all nodes in tree including nodes inside of groups to find viewer sockets.
self.delete_sockets = []
self.scan_nodes(base_node_tree, self.delete_sockets)
self.search_viewer_sockets_in_tree(base_node_tree, self.delete_sockets)
if not active.outputs:
self.cleanup()
@ -268,7 +270,6 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
# For geometry node trees, we just connect to the group output.
if space.tree_type == 'GeometryNodeTree':
socket_type = 'NodeSocketGeometry'
# Find (or create if needed) the output of this node tree.
output_node = self.ensure_group_output(base_node_tree)
@ -286,13 +287,15 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
if inp.type == 'GEOMETRY':
output_node_socket_index = i
break
node_output = active.outputs[active_node_socket_index]
socket_type = find_base_socket_type(node_output)
if output_node_socket_index is None:
output_node_socket_index = self.ensure_viewer_socket(
base_node_tree, socket_type, connect_socket=None)
# For shader node trees, we connect to a material output.
elif space.tree_type == 'ShaderNodeTree':
socket_type = 'NodeSocketShader'
self.init_shader_variables(space, space.shader_type)
# Get or create material_output node.
@ -312,13 +315,14 @@ class NODE_OT_connect_to_output(Operator, NodeEditorBase):
if active_node_socket_index is None:
return {'CANCELLED'}
if active.outputs[active_node_socket_index].name == "Volume":
node_output = active.outputs[active_node_socket_index]
socket_type = find_base_socket_type(node_output)
if node_output.name == "Volume":
output_node_socket_index = 1
else:
output_node_socket_index = 0
# If there are no nested node groups, the link starts at the active node.
node_output = active.outputs[active_node_socket_index]
if len(path) > 1:
# Recursively connect inside nested node groups and get the one from base level.
node_output = self.create_links(path, active, active_node_socket_index, socket_type)

@ -4,8 +4,6 @@
import bpy
viewer_socket_name = "tmp_viewer"
def node_editor_poll(cls, context):
space = context.space_data
@ -76,17 +74,12 @@ def is_visible_socket(socket):
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
def is_viewer_socket(socket):
# checks if a internal socket is a valid viewer socket.
return socket.name == viewer_socket_name and socket.is_inspect_output
def is_viewer_link(link, output_node):
if link.to_node == output_node and link.to_socket == output_node.inputs[0]:
return True
if link.to_node.type == 'GROUP_OUTPUT':
socket = get_internal_socket(link.to_socket)
if is_viewer_socket(socket):
if socket.is_inspect_output:
return True
return False

@ -21,6 +21,7 @@
#include "BLI_string_utils.hh"
#include "BLI_tempfile.h"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BKE_appdir.hh" /* own include */
#include "BKE_blender_version.h"
@ -590,10 +591,10 @@ bool BKE_appdir_folder_id_ex(const int folder_id,
if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
break;
}
if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
break;
}
if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
break;
}
return false;
@ -638,9 +639,6 @@ bool BKE_appdir_folder_id_ex(const int folder_id,
return false;
case BLENDER_SYSTEM_SCRIPTS:
if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_SCRIPTS")) {
break;
}
if (get_path_system(path, path_maxncpy, "scripts", subfolder)) {
break;
}
@ -1005,43 +1003,57 @@ bool BKE_appdir_program_python_search(char *program_filepath,
/** \name Application Templates
* \{ */
/** Keep in sync with `bpy.utils.app_template_paths()` */
static const char *app_template_directory_search[2] = {
"startup" SEP_STR "bl_app_templates_user",
"startup" SEP_STR "bl_app_templates_system",
};
static blender::Vector<std::string> appdir_app_template_directories()
{
blender::Vector<std::string> directories;
static const int app_template_directory_id[2] = {
/* Only 'USER' */
BLENDER_USER_SCRIPTS,
/* Covers 'LOCAL' & 'SYSTEM'. */
BLENDER_SYSTEM_SCRIPTS,
};
/** Keep in sync with `bpy.utils.app_template_paths()` */
char temp_dir[FILE_MAX];
if (BKE_appdir_folder_id_ex(BLENDER_USER_SCRIPTS,
"startup" SEP_STR "bl_app_templates_user",
temp_dir,
sizeof(temp_dir)))
{
directories.append(temp_dir);
}
/* Environment variable. */
if (get_path_environment(temp_dir,
sizeof(temp_dir),
"startup" SEP_STR "bl_app_templates_system",
"BLENDER_SYSTEM_SCRIPTS"))
{
directories.append(temp_dir);
}
/* Local or system directory. */
if (BKE_appdir_folder_id_ex(BLENDER_SYSTEM_SCRIPTS,
"startup" SEP_STR "bl_app_templates_system",
temp_dir,
sizeof(temp_dir)))
{
directories.append(temp_dir);
}
return directories;
}
bool BKE_appdir_app_template_any()
{
char temp_dir[FILE_MAX];
for (int i = 0; i < ARRAY_SIZE(app_template_directory_id); i++) {
if (BKE_appdir_folder_id_ex(app_template_directory_id[i],
app_template_directory_search[i],
temp_dir,
sizeof(temp_dir)))
{
return true;
}
}
return false;
return !appdir_app_template_directories().is_empty();
}
bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_maxncpy)
{
for (int i = 0; i < ARRAY_SIZE(app_template_directory_id); i++) {
char subdir[FILE_MAX];
BLI_path_join(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
if (BKE_appdir_folder_id_ex(app_template_directory_id[i], subdir, path, path_maxncpy)) {
const blender::Vector<std::string> directories = appdir_app_template_directories();
for (const std::string &directory : directories) {
BLI_path_join(path, path_maxncpy, directory.c_str(), app_template);
if (BLI_is_dir(path)) {
return true;
}
}
return false;
}
@ -1069,18 +1081,11 @@ void BKE_appdir_app_templates(ListBase *templates)
{
BLI_listbase_clear(templates);
for (int i = 0; i < ARRAY_SIZE(app_template_directory_id); i++) {
char subdir[FILE_MAX];
if (!BKE_appdir_folder_id_ex(app_template_directory_id[i],
app_template_directory_search[i],
subdir,
sizeof(subdir)))
{
continue;
}
const blender::Vector<std::string> directories = appdir_app_template_directories();
for (const std::string &subdir : directories) {
direntry *dirs;
const uint dir_num = BLI_filelist_dir_contents(subdir, &dirs);
const uint dir_num = BLI_filelist_dir_contents(subdir.c_str(), &dirs);
for (int f = 0; f < dir_num; f++) {
if (!FILENAME_IS_CURRPAR(dirs[f].relname) && S_ISDIR(dirs[f].type)) {
char *app_template = BLI_strdup(dirs[f].relname);

@ -844,18 +844,17 @@ static void print_help(bArgs *ba, bool all)
PRINT("\n");
PRINT("Environment Variables:\n");
PRINT(" $BLENDER_USER_RESOURCES Top level directory for user files.\n");
PRINT(" (other 'BLENDER_USER_*' variables override when set).\n");
PRINT(" $BLENDER_USER_RESOURCES Replace default directory of all user files.\n");
PRINT(" Other 'BLENDER_USER_*' variables override when set.\n");
PRINT(" $BLENDER_USER_CONFIG Directory for user configuration files.\n");
PRINT(" $BLENDER_USER_SCRIPTS Directory for user scripts.\n");
PRINT(" $BLENDER_USER_EXTENSIONS Directory for user extensions.\n");
PRINT(" $BLENDER_USER_DATAFILES Directory for user data files (icons, translations, ..).\n");
PRINT("\n");
PRINT(" $BLENDER_SYSTEM_RESOURCES Top level directory for system files.\n");
PRINT(" (other 'BLENDER_SYSTEM_*' variables override when set).\n");
PRINT(" $BLENDER_SYSTEM_SCRIPTS Directory for system wide scripts.\n");
PRINT(" $BLENDER_SYSTEM_DATAFILES Directory for system wide data files.\n");
PRINT(" $BLENDER_SYSTEM_PYTHON Directory for system Python libraries.\n");
PRINT(" $BLENDER_SYSTEM_RESOURCES Replace default directory of all bundled resource files.\n");
PRINT(" $BLENDER_SYSTEM_SCRIPTS Directory to add more bundled scripts.\n");
PRINT(" $BLENDER_SYSTEM_DATAFILES Directory to replace bundled datafiles.\n");
PRINT(" $BLENDER_SYSTEM_PYTHON Directory to replace bundled Python libraries.\n");
if (defs.with_ocio) {
PRINT(" $OCIO Path to override the OpenColorIO configuration file.\n");