From bea39233eadc178e803bdbf65fe4c139d92d94c5 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Fri, 17 Jan 2025 10:41:14 +0200 Subject: [PATCH 01/37] Cleanup: Share interpolation enum between all nodes Share a single enum list and DNA type between all compositor nodes that use interpolation. --- source/blender/makesdna/DNA_node_types.h | 3 +-- .../blender/makesrna/intern/rna_nodetree.cc | 23 +++++++------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f5bcd5955c6..d660b0d1f75 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2798,8 +2798,7 @@ typedef enum CMPNodeKuwahara { CMP_NODE_KUWAHARA_ANISOTROPIC = 1, } CMPNodeKuwahara; -/* Stabilize 2D node. Stored in custom1 for Stabilize 2D node and in interpolation for Translate - * node. */ +/* Shared between nodes with interpolation option. */ typedef enum CMPNodeInterpolation { CMP_NODE_INTERPOLATION_NEAREST = 0, CMP_NODE_INTERPOLATION_BILINEAR = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 4f434c0ff8f..e62c516fd30 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -592,10 +592,10 @@ const EnumPropertyItem rna_enum_geometry_nodes_linear_gizmo_draw_style_items[] = }; #ifndef RNA_RUNTIME -static const EnumPropertyItem node_sampler_type_items[] = { - {0, "NEAREST", 0, "Nearest", ""}, - {1, "BILINEAR", 0, "Bilinear", ""}, - {2, "BICUBIC", 0, "Bicubic", ""}, +static const EnumPropertyItem cmp_interpolation_items[] = { + {CMP_NODE_INTERPOLATION_NEAREST, "NEAREST", 0, "Nearest", "Use Nearest interpolation"}, + {CMP_NODE_INTERPOLATION_BILINEAR, "BILINEAR", 0, "Bilinear", "Use Bilinear interpolation"}, + {CMP_NODE_INTERPOLATION_BICUBIC, "BICUBIC", 0, "Bicubic", "Use Cubic B-Spline interpolation"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -7449,7 +7449,7 @@ static void def_cmp_rotate(BlenderRNA * /*brna*/, StructRNA *srna) prop = RNA_def_property(srna, "filter_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "custom1"); - RNA_def_property_enum_items(prop, node_sampler_type_items); + RNA_def_property_enum_items(prop, cmp_interpolation_items); RNA_def_property_ui_text(prop, "Filter", "Method to use to filter rotation"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } @@ -8602,7 +8602,7 @@ static void def_cmp_stabilize2d(BlenderRNA * /*brna*/, StructRNA *srna) prop = RNA_def_property(srna, "filter_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "custom1"); - RNA_def_property_enum_items(prop, node_sampler_type_items); + RNA_def_property_enum_items(prop, cmp_interpolation_items); RNA_def_property_ui_text(prop, "Filter", "Method to use to filter stabilization"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); @@ -8709,7 +8709,7 @@ static void dev_cmd_transform(BlenderRNA * /*brna*/, StructRNA *srna) prop = RNA_def_property(srna, "filter_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "custom1"); - RNA_def_property_enum_items(prop, node_sampler_type_items); + RNA_def_property_enum_items(prop, cmp_interpolation_items); RNA_def_property_ui_text(prop, "Filter", "Method to use to filter transform"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } @@ -9325,20 +9325,13 @@ static void def_cmp_translate(BlenderRNA * /*brna*/, StructRNA *srna) {0, nullptr, 0, nullptr, nullptr}, }; - static const EnumPropertyItem interpolation_items[] = { - {CMP_NODE_INTERPOLATION_NEAREST, "Nearest", 0, "Nearest", "Use nearest interpolation"}, - {CMP_NODE_INTERPOLATION_BILINEAR, "Bilinear", 0, "Bilinear", "Use bilinear interpolation"}, - {CMP_NODE_INTERPOLATION_BICUBIC, "Bicubic", 0, "Bicubic", "Use bicubic interpolation"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - PropertyRNA *prop; RNA_def_struct_sdna_from(srna, "NodeTranslateData", "storage"); prop = RNA_def_property(srna, "interpolation", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "interpolation"); - RNA_def_property_enum_items(prop, interpolation_items); + RNA_def_property_enum_items(prop, cmp_interpolation_items); RNA_def_property_ui_text(prop, "", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); From ae6b47f3468f125f7334e2dfc5cbb74322cc8ce0 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 8 Jan 2025 11:39:54 +0100 Subject: [PATCH 02/37] MaterialX: Support linking color directly to surface shader Matching Cycles and EEVEE. Also changes some Color4 to Color3, since shader nodes don't use this alpha. Pull Request: https://projects.blender.org/blender/blender/pulls/132907 --- .../nodes/shader/materialx/node_item.cc | 68 +++++++++++++++++++ .../nodes/shader/materialx/node_item.h | 3 + .../nodes/shader/materialx/node_parser.cc | 9 +-- .../nodes/shader/nodes/node_shader_curves.cc | 2 +- .../nodes/shader/nodes/node_shader_gamma.cc | 2 +- .../nodes/shader/nodes/node_shader_mix.cc | 4 +- .../nodes/shader/nodes/node_shader_rgb.cc | 4 +- .../shader/nodes/node_shader_rgb_to_bw.cc | 4 +- .../shader/nodes/node_shader_tex_checker.cc | 4 +- tests/data | 2 +- 10 files changed, 87 insertions(+), 15 deletions(-) diff --git a/source/blender/nodes/shader/materialx/node_item.cc b/source/blender/nodes/shader/materialx/node_item.cc index e5dacb58647..96668cf681b 100644 --- a/source/blender/nodes/shader/materialx/node_item.cc +++ b/source/blender/nodes/shader/materialx/node_item.cc @@ -478,12 +478,80 @@ NodeItem NodeItem::exp() const return to_vector().arithmetic("exp", [](float a) { return std::exp(a); }); } +bool NodeItem::is_convertible(eNodeSocketDatatype from_type, Type to_type) +{ + switch (to_type) { + case Type::Any: + return true; + case Type::Empty: + case Type::Multioutput: + return false; + case Type::String: + case Type::Filename: + return from_type == SOCK_STRING; + case Type::Boolean: + return from_type == SOCK_BOOLEAN; + case Type::Integer: + return from_type == SOCK_INT; + case Type::Float: + case Type::Vector2: + case Type::Vector3: + case Type::Color3: + case Type::Vector4: + case Type::Color4: + case Type::DisplacementShader: + return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); + case Type::EDF: + return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER); + case Type::BSDF: + case Type::SurfaceShader: + case Type::Material: + case Type::SurfaceOpacity: + return from_type == SOCK_SHADER; + } + + return false; +} + NodeItem NodeItem::convert(Type to_type) const { Type from_type = type(); if (from_type == Type::Empty || from_type == to_type || to_type == Type::Any) { return *this; } + + switch (to_type) { + /* Link arithmetic types to shader as EDF. */ + case Type::EDF: + if (is_arithmetic(from_type)) { + return create_node("uniform_edf", NodeItem::Type::EDF, {{"color", convert(Type::Color3)}}); + } + return empty(); + /* Displacement shader from arithmetic types, when not using (Vector) Displacement node. */ + case Type::DisplacementShader: + if (is_arithmetic(from_type)) { + return create_node("displacement", + NodeItem::Type::DisplacementShader, + {{"displacement", convert(Type::Vector3)}}); + } + return empty(); + /* Surface opacity is just a float. */ + case Type::SurfaceOpacity: + to_type = Type::Float; + if (from_type == to_type || to_type == Type::Any) { + return *this; + } + break; + /* Material output will evaluate graph multiple times for different components, + * when linking arithmetic types we want to leave those empty. */ + case Type::BSDF: + case Type::SurfaceShader: + case Type::Material: + return empty(); + default: + break; + } + if (!is_arithmetic(from_type) || !is_arithmetic(to_type)) { CLOG_WARN(LOG_MATERIALX_SHADER, "Cannot convert: %s -> %s", diff --git a/source/blender/nodes/shader/materialx/node_item.h b/source/blender/nodes/shader/materialx/node_item.h index 2f45e2d0df1..d6b6f3d8401 100644 --- a/source/blender/nodes/shader/materialx/node_item.h +++ b/source/blender/nodes/shader/materialx/node_item.h @@ -4,6 +4,8 @@ #pragma once +#include "DNA_node_types.h" + #include namespace blender::nodes::materialx { @@ -67,6 +69,7 @@ class NodeItem { static Type type(const std::string &type_str); static std::string type(Type type); static bool is_arithmetic(Type type); + static bool is_convertible(eNodeSocketDatatype from_type, Type to_type); /* Operators */ operator bool() const; diff --git a/source/blender/nodes/shader/materialx/node_parser.cc b/source/blender/nodes/shader/materialx/node_parser.cc index 044584da819..56363a807fd 100644 --- a/source/blender/nodes/shader/materialx/node_parser.cc +++ b/source/blender/nodes/shader/materialx/node_parser.cc @@ -41,6 +41,10 @@ NodeItem NodeParser::compute_full() { NodeItem res = empty(); + if (socket_out_ && !NodeItem::is_convertible((eNodeSocketDatatype)socket_out_->type, to_type_)) { + return res; + } + /* Checking if node was already computed */ res.node = graph_->getNode(node_name()); if (!res.node) { @@ -56,10 +60,7 @@ NodeItem NodeParser::compute_full() res.node->setName(node_name()); } } - if (NodeItem::is_arithmetic(to_type_)) { - res = res.convert(to_type_); - } - return res; + return res.convert(to_type_); } std::string NodeParser::node_name(bool with_out_socket) const diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.cc b/source/blender/nodes/shader/nodes/node_shader_curves.cc index ddedaca2960..93ec89f8820 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.cc +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -283,7 +283,7 @@ NODE_SHADER_MATERIALX_BEGIN #ifdef WITH_MATERIALX { /* TODO: implement */ - return get_input_value("Color", NodeItem::Type::Color4); + return get_input_value("Color", NodeItem::Type::Color3); } #endif NODE_SHADER_MATERIALX_END diff --git a/source/blender/nodes/shader/nodes/node_shader_gamma.cc b/source/blender/nodes/shader/nodes/node_shader_gamma.cc index 7e5eeb24767..f9a561b7b3c 100644 --- a/source/blender/nodes/shader/nodes/node_shader_gamma.cc +++ b/source/blender/nodes/shader/nodes/node_shader_gamma.cc @@ -35,7 +35,7 @@ static int node_shader_gpu_gamma(GPUMaterial *mat, NODE_SHADER_MATERIALX_BEGIN #ifdef WITH_MATERIALX { - NodeItem color = get_input_value("Color", NodeItem::Type::Color4); + NodeItem color = get_input_value("Color", NodeItem::Type::Color3); NodeItem gamma = get_input_value("Gamma", NodeItem::Type::Float); return color ^ gamma; } diff --git a/source/blender/nodes/shader/nodes/node_shader_mix.cc b/source/blender/nodes/shader/nodes/node_shader_mix.cc index 0e345a0f23e..f625d931226 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mix.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mix.cc @@ -579,8 +579,8 @@ NODE_SHADER_MATERIALX_BEGIN case SOCK_RGBA: factor = get_input_value(0, NodeItem::Type::Float); - value1 = get_input_value(6, NodeItem::Type::Color4); - value2 = get_input_value(7, NodeItem::Type::Color4); + value1 = get_input_value(6, NodeItem::Type::Color3); + value2 = get_input_value(7, NodeItem::Type::Color3); break; default: diff --git a/source/blender/nodes/shader/nodes/node_shader_rgb.cc b/source/blender/nodes/shader/nodes/node_shader_rgb.cc index 7871c92b0f7..3f3e2ac211b 100644 --- a/source/blender/nodes/shader/nodes/node_shader_rgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_rgb.cc @@ -29,8 +29,8 @@ static int gpu_shader_rgb(GPUMaterial *mat, NODE_SHADER_MATERIALX_BEGIN #ifdef WITH_MATERIALX { - NodeItem color = get_output_default("Color", NodeItem::Type::Color4); - return create_node("constant", NodeItem::Type::Color4, {{"value", color}}); + NodeItem color = get_output_default("Color", NodeItem::Type::Color3); + return create_node("constant", NodeItem::Type::Color3, {{"value", color}}); } #endif NODE_SHADER_MATERIALX_END diff --git a/source/blender/nodes/shader/nodes/node_shader_rgb_to_bw.cc b/source/blender/nodes/shader/nodes/node_shader_rgb_to_bw.cc index c4bd77bd832..618a2608b3b 100644 --- a/source/blender/nodes/shader/nodes/node_shader_rgb_to_bw.cc +++ b/source/blender/nodes/shader/nodes/node_shader_rgb_to_bw.cc @@ -28,8 +28,8 @@ static int gpu_shader_rgbtobw(GPUMaterial *mat, NODE_SHADER_MATERIALX_BEGIN #ifdef WITH_MATERIALX { - NodeItem color = get_input_value("Color", NodeItem::Type::Color4); - return create_node("luminance", NodeItem::Type::Color4, {{"in", color}}); + NodeItem color = get_input_value("Color", NodeItem::Type::Color3); + return create_node("luminance", NodeItem::Type::Color3, {{"in", color}}); } #endif NODE_SHADER_MATERIALX_END diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_checker.cc b/source/blender/nodes/shader/nodes/node_shader_tex_checker.cc index 5d6b2ab06e4..d89e09ea325 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_checker.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_checker.cc @@ -119,8 +119,8 @@ NODE_SHADER_MATERIALX_BEGIN NodeItem value1 = val(1.0f); NodeItem value2 = val(0.0f); if (STREQ(socket_out_->name, "Color")) { - value1 = get_input_value("Color1", NodeItem::Type::Color4); - value2 = get_input_value("Color2", NodeItem::Type::Color4); + value1 = get_input_value("Color1", NodeItem::Type::Color3); + value2 = get_input_value("Color2", NodeItem::Type::Color3); } NodeItem scale = get_input_value("Scale", NodeItem::Type::Float); diff --git a/tests/data b/tests/data index 6a4b127dd5b..b104760992f 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 6a4b127dd5b6d7174d547bba7bb5168d4ab4d629 +Subproject commit b104760992f09e6af6f4ad69ae44e395831979b0 From a6b293daacb0259b9879b88e33afc0220306a578 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 17 Jan 2025 10:13:31 +0100 Subject: [PATCH 03/37] Python Module: Bundle VFX library python bindings When calling bpy.utils.expose_bundled_modules(), these modules are added to sys.path. This provides a solution/workaround to two problems: * Using bpy together with packages like usd-core is problematic. Besides crashing due to C++ symbol conflicts, it's just impossible to import different versions of the same module, or to have distinct environment variables for both. (#127132) * Blender add-ons using these VFX modules do not currently work with the bpy module. This adds about 15MB to the bpy package. Pull Request: https://projects.blender.org/blender/blender/pulls/133082 --- .../examples/bpy.types.HydraRenderEngine.py | 3 + doc/python_api/examples/bpy.types.USDHook.py | 6 +- scripts/modules/bpy/utils/__init__.py | 25 ++++ scripts/site/sitecustomize.py | 5 + source/blender/python/intern/bpy_app.cc | 6 + source/creator/CMakeLists.txt | 119 +++++++++++------- tests/blender_as_python_module/import_bpy.py | 9 ++ 7 files changed, 129 insertions(+), 44 deletions(-) diff --git a/doc/python_api/examples/bpy.types.HydraRenderEngine.py b/doc/python_api/examples/bpy.types.HydraRenderEngine.py index 185df2a032e..14a17d18135 100644 --- a/doc/python_api/examples/bpy.types.HydraRenderEngine.py +++ b/doc/python_api/examples/bpy.types.HydraRenderEngine.py @@ -22,6 +22,9 @@ class CustomHydraRenderEngine(bpy.types.HydraRenderEngine): # Register path to plugin. @classmethod def register(cls): + # Make pxr module available, for running as bpy pip package. + bpy.utils.expose_bundled_modules() + import pxr.Plug pxr.Plug.Registry().RegisterPlugins(['/path/to/plugin']) diff --git a/doc/python_api/examples/bpy.types.USDHook.py b/doc/python_api/examples/bpy.types.USDHook.py index caf4c54c6fb..5123527e3e8 100644 --- a/doc/python_api/examples/bpy.types.USDHook.py +++ b/doc/python_api/examples/bpy.types.USDHook.py @@ -185,11 +185,15 @@ import bpy import bpy.types +import textwrap + +# Make pxr module available, for running as bpy pip package. +bpy.utils.expose_bundled_modules() + import pxr.Gf as Gf import pxr.Sdf as Sdf import pxr.Usd as Usd import pxr.UsdShade as UsdShade -import textwrap class USDHookExample(bpy.types.USDHook): diff --git a/scripts/modules/bpy/utils/__init__.py b/scripts/modules/bpy/utils/__init__.py index 71f76e7f575..4dfeed548e0 100644 --- a/scripts/modules/bpy/utils/__init__.py +++ b/scripts/modules/bpy/utils/__init__.py @@ -46,6 +46,7 @@ "unregister_tool", "user_resource", "execfile", + "expose_bundled_modules", ) from _bpy import ( @@ -1355,3 +1356,27 @@ def make_rna_paths(struct_name, prop_name, enum_name): else: src = src_rna = struct_name return src, src_rna, src_enum + + +def expose_bundled_modules(): + """ + For Blender as a Python module, add bundled VFX library python bindings + to ``sys.path``. These may be used instead of dedicated packages, to ensure + the libraries are compatible with Blender. + """ + # For Blender executable there is nothing to do, already exposed. + if not _bpy.app.module: + return + # System installations do not bundle additional modules, + # these are expected to be installed on the system too. + if not _bpy.app.portable: + return + + version_dir = _os.path.normpath(_os.path.join(_bpy.__file__, "..", "..", "..", "..")) + packages_dir = _os.path.join(version_dir, "python", "lib") + if _sys.platform != "win32": + packages_dir = _os.path.join(packages_dir, "python{:d}.{:d}".format(*_sys.version_info[:2])) + packages_dir = _os.path.join(packages_dir, "site-packages") + + if packages_dir not in _sys.path: + _sys.path.insert(0, packages_dir) diff --git a/scripts/site/sitecustomize.py b/scripts/site/sitecustomize.py index e199df65133..217b2192eae 100644 --- a/scripts/site/sitecustomize.py +++ b/scripts/site/sitecustomize.py @@ -46,3 +46,8 @@ os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_dir else: os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_env + os.pathsep + materialx_libs_dir + + +def register(): + # To make this work as a startup script for Blender as a Python module. + pass diff --git a/source/blender/python/intern/bpy_app.cc b/source/blender/python/intern/bpy_app.cc index c773341342a..fce01a9e1f6 100644 --- a/source/blender/python/intern/bpy_app.cc +++ b/source/blender/python/intern/bpy_app.cc @@ -88,6 +88,7 @@ static PyStructSequence_Field app_info_fields[] = { {"version_cycle", "The release status of this build alpha/beta/rc/release"}, {"background", "Boolean, True when blender is running without a user interface (started with -b)"}, + {"module", "Boolean, True when running Blender as a python module"}, {"factory_startup", "Boolean, True when blender is running with --factory-startup)"}, {"portable", "Boolean, True unless blender was built to reference absolute paths (on UNIX)."}, @@ -159,6 +160,11 @@ static PyObject *make_app_info() SetStrItem(STRINGIFY(BLENDER_VERSION_CYCLE)); SetObjItem(PyBool_FromLong(G.background)); +#ifdef WITH_PYTHON_MODULE + SetObjItem(Py_NewRef(Py_True)); +#else + SetObjItem(Py_NewRef(Py_False)); +#endif SetObjItem(PyBool_FromLong(G.factory_startup)); #ifdef WITH_INSTALL_PORTABLE diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 8601ecd954c..fef82a5bada 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -402,7 +402,7 @@ if(UNIX AND NOT APPLE) set(TARGETDIR_TEXT "share/doc/blender") endif() endif() - + set(TARGETDIR_SITE_PACKAGES "${TARGETDIR_VER}/python/lib/python${PYTHON_VERSION}/site-packages") elseif(WIN32) if(WITH_PYTHON_MODULE) set(TARGETDIR_BPY ${CMAKE_INSTALL_PREFIX_WITH_CONFIG}/bpy) @@ -416,6 +416,7 @@ elseif(WIN32) set(TARGETDIR_LIB "blender.shared") set(TARGETDIR_EXE ".") endif() + set(TARGETDIR_SITE_PACKAGES "${TARGETDIR_VER}/python/lib/site-packages") elseif(APPLE) if(WITH_PYTHON_MODULE) if(WITH_INSTALL_PORTABLE) @@ -434,6 +435,7 @@ elseif(APPLE) set(TARGETDIR_LIB "Blender.app/Contents/Resources/lib") set(TARGETDIR_TEXT "Blender.app/Contents/Resources/text") endif() + set(TARGETDIR_SITE_PACKAGES "${TARGETDIR_VER}/python/lib/python${PYTHON_VERSION}/site-packages") # Skip re-linking on CPACK / install. set_target_properties(blender PROPERTIES BUILD_WITH_INSTALL_RPATH true) endif() @@ -465,18 +467,18 @@ if(WITH_PYTHON) PATTERN "${FREESTYLE_EXCLUDE_CONDITIONAL}" EXCLUDE ) - if(WITH_PYTHON_INSTALL) - if(WIN32) - install( - FILES ${CMAKE_SOURCE_DIR}/scripts/site/sitecustomize.py - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages - ) - else() - install( - FILES ${CMAKE_SOURCE_DIR}/scripts/site/sitecustomize.py - DESTINATION ${TARGETDIR_VER}/python/lib/python${PYTHON_VERSION}/site-packages - ) - endif() + if(WITH_PYTHON_MODULE) + install( + FILES ${CMAKE_SOURCE_DIR}/scripts/site/sitecustomize.py + DESTINATION ${TARGETDIR_VER}/scripts/startup + # Rename to avoid conflict with system sitecustomize.py + RENAME bpy_site_customize.py + ) + elseif(WITH_PYTHON_INSTALL) + install( + FILES ${CMAKE_SOURCE_DIR}/scripts/site/sitecustomize.py + DESTINATION ${TARGETDIR_SITE_PACKAGES} + ) endif() unset(FREESTYLE_EXCLUDE_CONDITIONAL) @@ -574,12 +576,12 @@ if(WIN32) ) install( FILES ${LIBDIR}/opencolorio/lib/site-packages-debug/PyOpenColorIO_d.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Debug ) install( FILES ${LIBDIR}/opencolorio/lib/site-packages/PyOpenColorIO.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel ) endif() @@ -594,12 +596,12 @@ if(WIN32) ) install( DIRECTORY ${LIBDIR}/opencolorio/lib/site-packages-debug/PyOpenColorIO - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Debug ) install( DIRECTORY ${LIBDIR}/opencolorio/lib/site-packages/PyOpenColorIO - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel ) endif() @@ -1280,11 +1282,30 @@ elseif(WIN32) CONFIGURATIONS Debug ) + if(WINDOWS_PYTHON_DEBUG) + install( + FILES + ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}.pdb + DESTINATION "." + CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel + ) + + install( + FILES + ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}_d.pdb + DESTINATION "." + CONFIGURATIONS Debug + ) + endif() + endif() + + # VFX libs are bundled with both Blender executable and Python module. + if(WITH_PYTHON_INSTALL OR WITH_PYTHON_MODULE) # This will only exist for 3.5+. if(EXISTS ${LIBDIR}/openimageio/lib/python${PYTHON_VERSION}/site-packages) install( DIRECTORY ${LIBDIR}/openimageio/lib/python${PYTHON_VERSION}/site-packages/ - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages/ + DESTINATION ${TARGETDIR_SITE_PACKAGES}/ CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * @@ -1294,7 +1315,7 @@ elseif(WIN32) if(EXISTS ${LIBDIR}/openimageio/lib/python${PYTHON_VERSION}_debug/site-packages) install( DIRECTORY ${LIBDIR}/openimageio/lib/python${PYTHON_VERSION}_debug/site-packages/ - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages/ + DESTINATION ${TARGETDIR_SITE_PACKAGES}/ CONFIGURATIONS Debug PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * @@ -1307,7 +1328,7 @@ elseif(WIN32) if(EXISTS ${USD_LIBRARY_DIR}/python/) install( DIRECTORY ${USD_LIBRARY_DIR}/python/ - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * @@ -1317,7 +1338,7 @@ elseif(WIN32) if(EXISTS ${USD_LIBRARY_DIR}/debug/python/) install( DIRECTORY ${USD_LIBRARY_DIR}/debug/python/ - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Debug PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * @@ -1330,12 +1351,12 @@ elseif(WIN32) if(EXISTS ${LIBDIR}/openvdb/python/pyopenvdb_d.pyd) install( FILES ${LIBDIR}/openvdb/python/pyopenvdb_d.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Debug ) install( FILES ${LIBDIR}/openvdb/python/pyopenvdb.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel ) endif() @@ -1350,12 +1371,12 @@ elseif(WIN32) if(EXISTS ${LIBDIR}/openvdb/python/pyopenvdb_d.cp${_PYTHON_VERSION_NO_DOTS}-win_${_openvdb_arch}.pyd) install( FILES ${LIBDIR}/openvdb/python/pyopenvdb_d.cp${_PYTHON_VERSION_NO_DOTS}-win_${_openvdb_arch}.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Debug ) install( FILES ${LIBDIR}/openvdb/python/pyopenvdb.cp${_PYTHON_VERSION_NO_DOTS}-win_${_openvdb_arch}.pyd - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages + DESTINATION ${TARGETDIR_SITE_PACKAGES} CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel ) endif() @@ -1363,7 +1384,7 @@ elseif(WIN32) # MaterialX python bindings install( DIRECTORY ${LIBDIR}/materialx/python/Release/MaterialX - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages/ + DESTINATION ${TARGETDIR_SITE_PACKAGES}/ CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * @@ -1371,28 +1392,12 @@ elseif(WIN32) ) install( DIRECTORY ${LIBDIR}/materialx/python/Debug/MaterialX - DESTINATION ${TARGETDIR_VER}/python/lib/site-packages/ + DESTINATION ${TARGETDIR_SITE_PACKAGES}/ CONFIGURATIONS Debug PATTERN "__pycache__" EXCLUDE # * any cache * PATTERN "*.pyc" EXCLUDE # * any cache * PATTERN "*.pyo" EXCLUDE # * any cache * ) - - if(WINDOWS_PYTHON_DEBUG) - install( - FILES - ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}.pdb - DESTINATION "." - CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel - ) - - install( - FILES - ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/libs/python${_PYTHON_VERSION_NO_DOTS}_d.pdb - DESTINATION "." - CONFIGURATIONS Debug - ) - endif() endif() endif() @@ -1672,6 +1677,34 @@ elseif(APPLE) endif() +# Windows already installs these, for Unix we need to pick just the VFX libs from +# the site-packages directory. +if(WITH_PYTHON_MODULE AND LIBDIR AND NOT WIN32) + install( + DIRECTORY ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/MaterialX + DESTINATION ${TARGETDIR_SITE_PACKAGES}) + install( + DIRECTORY ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/OpenImageIO + DESTINATION ${TARGETDIR_SITE_PACKAGES}) + install( + DIRECTORY ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/PyOpenColorIO + DESTINATION ${TARGETDIR_SITE_PACKAGES}) + install( + FILES ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/oslquery.so + DESTINATION ${TARGETDIR_SITE_PACKAGES} OPTIONAL) + install( + DIRECTORY ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/pxr + DESTINATION ${TARGETDIR_SITE_PACKAGES}) + if(APPLE) + set(_pyopenvdb_filename pyopenvdb.cpython-${PYTHON_VERSION_NO_DOTS}-darwin.so) + else() + set(_pyopenvdb_filename pyopenvdb.cpython-${PYTHON_VERSION_NO_DOTS}-${CMAKE_SYSTEM_PROCESSOR}-linux-gnu.so) + endif() + install( + FILES ${LIBDIR}/python/lib/python${PYTHON_VERSION}/site-packages/${_pyopenvdb_filename} + DESTINATION ${TARGETDIR_SITE_PACKAGES}) +endif() + # ----------------------------------------------------------------------------- # Generic Install, for all targets diff --git a/tests/blender_as_python_module/import_bpy.py b/tests/blender_as_python_module/import_bpy.py index 8dea7f52eec..9d424d7d693 100644 --- a/tests/blender_as_python_module/import_bpy.py +++ b/tests/blender_as_python_module/import_bpy.py @@ -8,3 +8,12 @@ # Just import bpy and see if there are any dynamic loader errors. import bpy + +# Try bundled libraries +bpy.utils.expose_bundled_modules() + +from pxr import Usd +import MaterialX +import OpenImageIO +import PyOpenColorIO +import pyopenvdb From 25286dd2cde4ed07896d3271bb2db12bd81a64e9 Mon Sep 17 00:00:00 2001 From: Pratik Borhade Date: Fri, 17 Jan 2025 10:16:39 +0100 Subject: [PATCH 04/37] Fix #132112: Grease Pencil: Draw overlays for active object `object_is_paint_mode()` returns true for objects that are not in active interaction mode. This results in drawing of overlays for each Grease Pencil object. Now fixed with adjusted condition. Pull Request: https://projects.blender.org/blender/blender/pulls/132159 --- source/blender/draw/engines/overlay/overlay_next_instance.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/draw/engines/overlay/overlay_next_instance.cc b/source/blender/draw/engines/overlay/overlay_next_instance.cc index 064d578d2d3..a0d5517d722 100644 --- a/source/blender/draw/engines/overlay/overlay_next_instance.cc +++ b/source/blender/draw/engines/overlay/overlay_next_instance.cc @@ -602,7 +602,7 @@ bool Instance::object_is_selected(const ObjectRef &ob_ref) bool Instance::object_is_paint_mode(const Object *object) { - if (object->type == OB_GREASE_PENCIL && (state.object_mode & OB_MODE_ALL_PAINT_GPENCIL)) { + if (object->type == OB_GREASE_PENCIL && (object->mode & OB_MODE_ALL_PAINT_GPENCIL)) { return true; } return (object == state.object_active) && (state.object_mode & OB_MODE_ALL_PAINT); From 963c638a5b9d2c2724f62460a87006a05a16bca0 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 17 Jan 2025 10:21:44 +0100 Subject: [PATCH 05/37] Fix #131132: Cycles reading packed multilayer EXR wrong Rewrite Blender image loading to avoid RNA API, which we are moving away from. This makes it easier to support multilayer EXR. Pull Request: https://projects.blender.org/blender/blender/pulls/133179 --- intern/cycles/blender/CMakeLists.txt | 1 + intern/cycles/blender/image.cpp | 317 ++++++++++++++------------- intern/cycles/blender/image.h | 13 +- intern/cycles/blender/shader.cpp | 20 +- intern/cycles/blender/util.h | 14 -- 5 files changed, 190 insertions(+), 175 deletions(-) diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index 986e49b627d..f66b183cbca 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -58,6 +58,7 @@ set(LIB PRIVATE bf::blenkernel PRIVATE bf::blenlib PRIVATE bf::dna + PRIVATE bf::imbuf PRIVATE bf::gpu PRIVATE bf::intern::guardedalloc PRIVATE bf::render diff --git a/intern/cycles/blender/image.cpp b/intern/cycles/blender/image.cpp index 93c955757a4..c771d16eaf5 100644 --- a/intern/cycles/blender/image.cpp +++ b/intern/cycles/blender/image.cpp @@ -2,71 +2,77 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "MEM_guardedalloc.h" +#include + +#include "DNA_image_types.h" + +#include "IMB_imbuf_types.hh" + +#include "BKE_image.hh" #include "blender/image.h" #include "blender/session.h" -#include "blender/util.h" #include "util/half.h" +#include "util/types_float4.h" CCL_NAMESPACE_BEGIN /* Packed Images */ -BlenderImageLoader::BlenderImageLoader(BL::Image b_image, +BlenderImageLoader::BlenderImageLoader(Image *b_image, + ImageUser *b_iuser, const int frame, const int tile_number, const bool is_preview_render) : b_image(b_image), - frame(frame), - tile_number(tile_number), + b_iuser(*b_iuser), /* Don't free cache for preview render to avoid race condition from #93560, to be fixed * properly later as we are close to release. */ - free_cache(!is_preview_render && !b_image.has_data()) + free_cache(!is_preview_render && !BKE_image_has_loaded_ibuf(b_image)) { + this->b_iuser.framenr = frame; + if (b_image->source != IMA_SRC_TILED) { + /* Image sequences currently not supported by this image loader. */ + assert(b_image->source != IMA_SRC_SEQUENCE); + } + else { + /* Set UDIM tile, each can have different resolution. */ + this->b_iuser.tile = tile_number; + } } bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/, ImageMetaData &metadata) { - if (b_image.source() != BL::Image::source_TILED) { - /* Image sequence might have different dimensions, and hence needs to be handled in a special - * manner. - * NOTE: Currently the sequences are not handled by this image loader. */ - assert(b_image.source() != BL::Image::source_SEQUENCE); - - metadata.width = b_image.size()[0]; - metadata.height = b_image.size()[1]; - metadata.channels = b_image.channels(); - } - else { - /* Different UDIM tiles might have different resolutions, so get resolution from the actual - * tile. */ - BL::UDIMTile b_udim_tile = b_image.tiles.get(tile_number); - if (b_udim_tile) { - metadata.width = b_udim_tile.size()[0]; - metadata.height = b_udim_tile.size()[1]; - metadata.channels = b_udim_tile.channels(); + bool is_float = false; + + { + void *lock; + ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock); + if (ibuf) { + is_float = ibuf->float_buffer.data != nullptr; + metadata.width = ibuf->x; + metadata.height = ibuf->y; + metadata.channels = (is_float) ? ibuf->channels : 4; } else { metadata.width = 0; metadata.height = 0; metadata.channels = 0; } + BKE_image_release_ibuf(b_image, ibuf, lock); } metadata.depth = 1; - if (b_image.is_float()) { + if (is_float) { if (metadata.channels == 1) { metadata.type = IMAGE_DATA_TYPE_FLOAT; } - else if (metadata.channels == 4) { - metadata.type = IMAGE_DATA_TYPE_FLOAT4; - } else { - return false; + metadata.channels = 4; + metadata.type = IMAGE_DATA_TYPE_FLOAT4; } /* Float images are already converted on the Blender side, @@ -76,177 +82,182 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/, else { /* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the * metadata queries in this function, so update the colorspace setting here. */ - PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr; - metadata.colorspace = get_enum_identifier(colorspace_ptr, "name"); + metadata.colorspace = b_image->colorspace_settings.name; + metadata.type = IMAGE_DATA_TYPE_BYTE4; + } - if (metadata.channels == 1) { - metadata.type = IMAGE_DATA_TYPE_BYTE; + return true; +} + +static void load_float_pixels(const ImBuf *ibuf, const ImageMetaData &metadata, float *out_pixels) +{ + const size_t num_pixels = ((size_t)metadata.width) * metadata.height; + const int out_channels = metadata.channels; + const int in_channels = ibuf->channels; + const float *in_pixels = ibuf->float_buffer.data; + + if (in_pixels && out_channels == in_channels) { + /* Straight copy pixel data. */ + memcpy(out_pixels, in_pixels, num_pixels * out_channels * sizeof(float)); + } + else if (in_pixels && out_channels == 4) { + /* Fill channels to 4. */ + float *out_pixel = out_pixels; + const float *in_pixel = in_pixels; + for (size_t i = 0; i < num_pixels; i++) { + out_pixel[0] = in_pixel[0]; + out_pixel[1] = (in_channels >= 2) ? in_pixel[1] : 0.0f; + out_pixel[2] = (in_channels >= 3) ? in_pixel[2] : 0.0f; + out_pixel[3] = (in_channels >= 4) ? in_pixel[3] : 1.0f; + out_pixel += out_channels; + in_pixel += in_channels; } - else if (metadata.channels == 4) { - metadata.type = IMAGE_DATA_TYPE_BYTE4; + } + else { + /* Missing or invalid pixel data. */ + if (out_channels == 1) { + std::fill(out_pixels, out_pixels + num_pixels, 0.0f); } else { - return false; + std::fill((float4 *)out_pixels, + (float4 *)out_pixels + num_pixels, + make_float4(1.0f, 0.0f, 1.0f, 1.0f)); } } - - return true; } -bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata, - void *out_pixels, - const size_t out_pixels_size, - const bool associate_alpha) +static void load_half_pixels(const ImBuf *ibuf, + const ImageMetaData &metadata, + half *out_pixels, + const bool associate_alpha) { + /* Half float. Blender does not have a half type, but in some cases + * we up-sample byte to half to avoid precision loss for colorspace + * conversion. */ const size_t num_pixels = ((size_t)metadata.width) * metadata.height; - const int channels = metadata.channels; - - if (metadata.type == IMAGE_DATA_TYPE_FLOAT || metadata.type == IMAGE_DATA_TYPE_FLOAT4) { - /* Float. */ - float *in_pixels = image_get_float_pixels_for_frame(b_image, frame, tile_number); - - if (in_pixels && num_pixels * channels == out_pixels_size) { - /* Straight copy pixel data. */ - memcpy(out_pixels, in_pixels, out_pixels_size * sizeof(float)); + const int out_channels = metadata.channels; + const int in_channels = 4; + const uchar *in_pixels = ibuf->byte_buffer.data; + + if (in_pixels) { + /* Convert uchar to half. */ + const uchar *in_pixel = in_pixels; + half *out_pixel = out_pixels; + if (associate_alpha && out_channels == in_channels) { + for (size_t i = 0; i < num_pixels; i++, in_pixel += in_channels, out_pixel += out_channels) { + const float alpha = util_image_cast_to_float(in_pixel[3]); + out_pixel[0] = float_to_half_image(util_image_cast_to_float(in_pixel[0]) * alpha); + out_pixel[1] = float_to_half_image(util_image_cast_to_float(in_pixel[1]) * alpha); + out_pixel[2] = float_to_half_image(util_image_cast_to_float(in_pixel[2]) * alpha); + out_pixel[3] = float_to_half_image(alpha); + } } else { - /* Missing or invalid pixel data. */ - if (channels == 1) { - memset(out_pixels, 0, num_pixels * sizeof(float)); - } - else { - const size_t num_pixels_safe = out_pixels_size / channels; - float *out_pixel = (float *)out_pixels; - for (int i = 0; i < num_pixels_safe; i++, out_pixel += channels) { - out_pixel[0] = 1.0f; - out_pixel[1] = 0.0f; - out_pixel[2] = 1.0f; - if (channels == 4) { - out_pixel[3] = 1.0f; - } + for (size_t i = 0; i < num_pixels; i++) { + for (int c = 0; c < out_channels; c++, in_pixel++, out_pixel++) { + *out_pixel = float_to_half_image(util_image_cast_to_float(*in_pixel)); } } } - - if (in_pixels) { - MEM_freeN(in_pixels); - } } - else if (metadata.type == IMAGE_DATA_TYPE_HALF || metadata.type == IMAGE_DATA_TYPE_HALF4) { - /* Half float. Blender does not have a half type, but in some cases - * we up-sample byte to half to avoid precision loss for colorspace - * conversion. */ - unsigned char *in_pixels = image_get_pixels_for_frame(b_image, frame, tile_number); - - if (in_pixels && num_pixels * channels == out_pixels_size) { - /* Convert uchar to half. */ - const uchar *in_pixel = in_pixels; - half *out_pixel = (half *)out_pixels; - if (associate_alpha && channels == 4) { - for (size_t i = 0; i < num_pixels; i++, in_pixel += 4, out_pixel += 4) { - const float alpha = util_image_cast_to_float(in_pixel[3]); - out_pixel[0] = float_to_half_image(util_image_cast_to_float(in_pixel[0]) * alpha); - out_pixel[1] = float_to_half_image(util_image_cast_to_float(in_pixel[1]) * alpha); - out_pixel[2] = float_to_half_image(util_image_cast_to_float(in_pixel[2]) * alpha); - out_pixel[3] = float_to_half_image(alpha); - } - } - else { - for (size_t i = 0; i < num_pixels; i++) { - for (int c = 0; c < channels; c++, in_pixel++, out_pixel++) { - *out_pixel = float_to_half_image(util_image_cast_to_float(*in_pixel)); - } - } - } + else { + /* Missing or invalid pixel data. */ + if (out_channels == 1) { + std::fill(out_pixels, out_pixels + num_pixels, float_to_half_image(0.0f)); } else { - /* Missing or invalid pixel data. */ - if (channels == 1) { - memset(out_pixels, 0, num_pixels * sizeof(half)); - } - else { - const size_t num_pixels_safe = out_pixels_size / channels; - half *out_pixel = (half *)out_pixels; - for (int i = 0; i < num_pixels_safe; i++, out_pixel += channels) { - out_pixel[0] = float_to_half_image(1.0f); - out_pixel[1] = float_to_half_image(0.0f); - out_pixel[2] = float_to_half_image(1.0f); - if (channels == 4) { - out_pixel[3] = float_to_half_image(1.0f); - } - } - } + std::fill((half4 *)out_pixels, + (half4 *)out_pixels + num_pixels, + float4_to_half4_display(make_float4(1.0f, 0.0f, 1.0f, 1.0f))); } + } +} - if (in_pixels) { - MEM_freeN(in_pixels); +static void load_byte_pixels(const ImBuf *ibuf, + const ImageMetaData &metadata, + uchar *out_pixels, + const bool associate_alpha) +{ + const size_t num_pixels = ((size_t)metadata.width) * metadata.height; + const int out_channels = metadata.channels; + const int in_channels = 4; + const uchar *in_pixels = ibuf->byte_buffer.data; + + if (in_pixels) { + /* Straight copy pixel data. */ + memcpy(out_pixels, in_pixels, num_pixels * in_channels * sizeof(unsigned char)); + + if (associate_alpha && out_channels == in_channels) { + /* Premultiply, byte images are always straight for Blender. */ + unsigned char *out_pixel = (unsigned char *)out_pixels; + for (size_t i = 0; i < num_pixels; i++, out_pixel += 4) { + out_pixel[0] = (out_pixel[0] * out_pixel[3]) / 255; + out_pixel[1] = (out_pixel[1] * out_pixel[3]) / 255; + out_pixel[2] = (out_pixel[2] * out_pixel[3]) / 255; + } } } else { - /* Byte. */ - unsigned char *in_pixels = image_get_pixels_for_frame(b_image, frame, tile_number); - - if (in_pixels && num_pixels * channels == out_pixels_size) { - /* Straight copy pixel data. */ - memcpy(out_pixels, in_pixels, out_pixels_size * sizeof(unsigned char)); - - if (associate_alpha && channels == 4) { - /* Premultiply, byte images are always straight for Blender. */ - unsigned char *out_pixel = (unsigned char *)out_pixels; - for (size_t i = 0; i < num_pixels; i++, out_pixel += 4) { - out_pixel[0] = (out_pixel[0] * out_pixel[3]) / 255; - out_pixel[1] = (out_pixel[1] * out_pixel[3]) / 255; - out_pixel[2] = (out_pixel[2] * out_pixel[3]) / 255; - } - } + /* Missing or invalid pixel data. */ + if (out_channels == 1) { + std::fill(out_pixels, out_pixels + num_pixels, 0.0f); } else { - /* Missing or invalid pixel data. */ - if (channels == 1) { - memset(out_pixels, 0, out_pixels_size * sizeof(unsigned char)); - } - else { - const size_t num_pixels_safe = out_pixels_size / channels; - unsigned char *out_pixel = (unsigned char *)out_pixels; - for (size_t i = 0; i < num_pixels_safe; i++, out_pixel += channels) { - out_pixel[0] = 255; - out_pixel[1] = 0; - out_pixel[2] = 255; - if (channels == 4) { - out_pixel[3] = 255; - } - } - } + std::fill( + (uchar4 *)out_pixels, (uchar4 *)out_pixels + num_pixels, make_uchar4(255, 0, 255, 255)); } + } +} + +bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata, + void *out_pixels, + const size_t /*out_pixels_size*/, + const bool associate_alpha) +{ + void *lock; + ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock); - if (in_pixels) { - MEM_freeN(in_pixels); + /* Image changed since we requested metadata, assume we'll get a signal to reload it later. */ + const bool mismatch = (ibuf == nullptr || ibuf->x != metadata.width || + ibuf->y != metadata.height); + + if (!mismatch) { + if (metadata.type == IMAGE_DATA_TYPE_FLOAT || metadata.type == IMAGE_DATA_TYPE_FLOAT4) { + load_float_pixels(ibuf, metadata, (float *)out_pixels); + } + else if (metadata.type == IMAGE_DATA_TYPE_HALF || metadata.type == IMAGE_DATA_TYPE_HALF4) { + load_half_pixels(ibuf, metadata, (half *)out_pixels, associate_alpha); + } + else { + load_byte_pixels(ibuf, metadata, (uchar *)out_pixels, associate_alpha); } } + BKE_image_release_ibuf(b_image, ibuf, lock); + /* Free image buffers to save memory during render. */ if (free_cache) { - b_image.buffers_free(); + BKE_image_free_buffers_ex(b_image, true); } - return true; + return !mismatch; } string BlenderImageLoader::name() const { - return BL::Image(b_image).name(); + return b_image->id.name + 2; } bool BlenderImageLoader::equals(const ImageLoader &other) const { const BlenderImageLoader &other_loader = (const BlenderImageLoader &)other; - return b_image == other_loader.b_image && frame == other_loader.frame && - tile_number == other_loader.tile_number; + return b_image == other_loader.b_image && b_iuser.framenr == other_loader.b_iuser.framenr && + b_iuser.tile == other_loader.b_iuser.tile; } int BlenderImageLoader::get_tile_number() const { - return tile_number; + return b_iuser.tile; } /* Point Density */ diff --git a/intern/cycles/blender/image.h b/intern/cycles/blender/image.h index b6a84dfff82..fc06f7eea9e 100644 --- a/intern/cycles/blender/image.h +++ b/intern/cycles/blender/image.h @@ -4,15 +4,21 @@ #pragma once +#include "DNA_image_types.h" + #include "RNA_blender_cpp.hh" #include "scene/image.h" +struct Image; +struct ImageUser; + CCL_NAMESPACE_BEGIN class BlenderImageLoader : public ImageLoader { public: - BlenderImageLoader(BL::Image b_image, + BlenderImageLoader(::Image *b_image, + ::ImageUser *b_iuser, const int frame, const int tile_number, const bool is_preview_render); @@ -27,9 +33,8 @@ class BlenderImageLoader : public ImageLoader { int get_tile_number() const override; - BL::Image b_image; - int frame; - int tile_number; + ::Image *b_image; + ::ImageUser b_iuser; bool free_cache; }; diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index fd43a8384a1..87809d0d951 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -864,15 +864,23 @@ static ShaderNode *add_node(Scene *scene, const int image_frame = image_user_frame_number(b_image_user, b_image, scene_frame); if (b_image_source != BL::Image::source_TILED) { image->handle = scene->image_manager->add_image( - make_unique(b_image, image_frame, 0, b_engine.is_preview()), + make_unique(static_cast<::Image *>(b_image.ptr.data), + static_cast<::ImageUser *>(b_image_user.ptr.data), + image_frame, + 0, + b_engine.is_preview()), image->image_params()); } else { vector> loaders; loaders.reserve(image->get_tiles().size()); for (const int tile_number : image->get_tiles()) { - loaders.push_back(make_unique( - b_image, image_frame, tile_number, b_engine.is_preview())); + loaders.push_back( + make_unique(static_cast<::Image *>(b_image.ptr.data), + static_cast<::ImageUser *>(b_image_user.ptr.data), + image_frame, + tile_number, + b_engine.is_preview())); } image->handle = scene->image_manager->add_image(std::move(loaders), @@ -911,7 +919,11 @@ static ShaderNode *add_node(Scene *scene, const int scene_frame = b_scene.frame_current(); const int image_frame = image_user_frame_number(b_image_user, b_image, scene_frame); env->handle = scene->image_manager->add_image( - make_unique(b_image, image_frame, 0, b_engine.is_preview()), + make_unique(static_cast<::Image *>(b_image.ptr.data), + static_cast<::ImageUser *>(b_image_user.ptr.data), + image_frame, + 0, + b_engine.is_preview()), env->image_params()); } else { diff --git a/intern/cycles/blender/util.h b/intern/cycles/blender/util.h index d98625c5831..bae8b4f1c0f 100644 --- a/intern/cycles/blender/util.h +++ b/intern/cycles/blender/util.h @@ -328,20 +328,6 @@ static inline int image_user_frame_number(BL::ImageUser &iuser, BL::Image &ima, return iuser.frame_current(); } -static inline unsigned char *image_get_pixels_for_frame(BL::Image &image, - const int frame, - const int tile) -{ - return BKE_image_get_pixels_for_frame(static_cast(image.ptr.data), frame, tile); -} - -static inline float *image_get_float_pixels_for_frame(BL::Image &image, - const int frame, - const int tile) -{ - return BKE_image_get_float_pixels_for_frame(static_cast(image.ptr.data), frame, tile); -} - static inline bool image_is_builtin(BL::Image &ima, BL::RenderEngine &engine) { const BL::Image::source_enum image_source = ima.source(); From 80ec04b4ef5bf27763a1cb008e516b752ed60a66 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 17 Jan 2025 10:28:22 +0100 Subject: [PATCH 06/37] Cleanup: Vulkan: Use full surface format GHOST_ContextVK used to pass only the surface texture format to the GPU backend, it didn't pass the color space. This PR also includes the color space. Pull Request: https://projects.blender.org/blender/blender/pulls/133185 --- intern/ghost/GHOST_Types.h | 4 ++-- intern/ghost/intern/GHOST_ContextVK.cc | 4 ++-- source/blender/gpu/vulkan/vk_context.cc | 22 +++++++++++++--------- source/blender/gpu/vulkan/vk_context.hh | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 9c097936114..e89256b3eef 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -724,8 +724,8 @@ typedef struct { typedef struct { /** Image handle to the image that will be presented to the user. */ VkImage image; - /** Format of the image. */ - VkFormat format; + /** Format of the swap chain. */ + VkSurfaceFormatKHR surface_format; /** Resolution of the image. */ VkExtent2D extent; } GHOST_VulkanSwapChainData; diff --git a/intern/ghost/intern/GHOST_ContextVK.cc b/intern/ghost/intern/GHOST_ContextVK.cc index edfa1259a26..33fd5f52221 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cc +++ b/intern/ghost/intern/GHOST_ContextVK.cc @@ -585,7 +585,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers() GHOST_VulkanSwapChainData swap_chain_data; swap_chain_data.image = m_swapchain_images[image_index]; - swap_chain_data.format = m_surface_format.format; + swap_chain_data.surface_format = m_surface_format; swap_chain_data.extent = m_render_extent; if (swap_buffers_pre_callback_) { @@ -636,7 +636,7 @@ GHOST_TSuccess GHOST_ContextVK::getVulkanSwapChainFormat( GHOST_VulkanSwapChainData *r_swap_chain_data) { r_swap_chain_data->image = VK_NULL_HANDLE; - r_swap_chain_data->format = m_surface_format.format; + r_swap_chain_data->surface_format = m_surface_format; r_swap_chain_data->extent = m_render_extent; return GHOST_kSuccess; diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index 51a65f5b971..b34c82bfe6f 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -70,7 +70,10 @@ void VKContext::sync_backbuffer(bool cycle_resource_pool) resource_pool.discard_pool.move_data(device.orphaned_data); } - const bool reset_framebuffer = swap_chain_format_ != swap_chain_data.format || + const bool reset_framebuffer = swap_chain_format_.format != + swap_chain_data.surface_format.format || + swap_chain_format_.colorSpace != + swap_chain_data.surface_format.colorSpace || vk_extent_.width != swap_chain_data.extent.width || vk_extent_.height != swap_chain_data.extent.height; if (reset_framebuffer) { @@ -81,13 +84,14 @@ void VKContext::sync_backbuffer(bool cycle_resource_pool) GPU_texture_free(surface_texture_); surface_texture_ = nullptr; } - surface_texture_ = GPU_texture_create_2d("back-left", - swap_chain_data.extent.width, - swap_chain_data.extent.height, - 1, - to_gpu_format(swap_chain_data.format), - GPU_TEXTURE_USAGE_ATTACHMENT, - nullptr); + surface_texture_ = GPU_texture_create_2d( + "back-left", + swap_chain_data.extent.width, + swap_chain_data.extent.height, + 1, + to_gpu_format(swap_chain_data.surface_format.format), + GPU_TEXTURE_USAGE_ATTACHMENT, + nullptr); back_left->attachment_set(GPU_FB_COLOR_ATTACHMENT0, GPU_ATTACHMENT_TEXTURE(surface_texture_)); @@ -96,7 +100,7 @@ void VKContext::sync_backbuffer(bool cycle_resource_pool) back_left->bind(false); - swap_chain_format_ = swap_chain_data.format; + swap_chain_format_ = swap_chain_data.surface_format; vk_extent_ = swap_chain_data.extent; } } diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index 33eadc51d4a..b31d11f2449 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -29,7 +29,7 @@ class VKThreadData; class VKContext : public Context, NonCopyable { private: VkExtent2D vk_extent_ = {}; - VkFormat swap_chain_format_ = {}; + VkSurfaceFormatKHR swap_chain_format_ = {}; GPUTexture *surface_texture_ = nullptr; void *ghost_context_; From 58003a91148107527e448c68fa9b8578bd16b35c Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 17 Jan 2025 10:34:56 +0100 Subject: [PATCH 07/37] Cleanup: Vulkan: Remove non-compatible surface selection This was useful in the beginning to ensure we supported most surface configurations. However it seems that there are not that many surface configurations to support. Pull Request: https://projects.blender.org/blender/blender/pulls/133186 --- intern/ghost/intern/GHOST_ContextVK.cc | 44 +++++++------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/intern/ghost/intern/GHOST_ContextVK.cc b/intern/ghost/intern/GHOST_ContextVK.cc index 33fd5f52221..7c482c9b250 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cc +++ b/intern/ghost/intern/GHOST_ContextVK.cc @@ -35,16 +35,6 @@ #include -/* - * Should we only select surfaces that are known to be compatible. Or should we in case no - * compatible surfaces have been found select the first one. - * - * Currently we also select incompatible surfaces as Vulkan is still experimental. Assuming we get - * reports of color differences between OpenGL and Vulkan to narrow down if there are other - * configurations we need to support. - */ -#define SELECT_COMPATIBLE_SURFACES_ONLY false - using namespace std; static const char *vulkan_error_as_string(VkResult result) @@ -776,21 +766,6 @@ GHOST_TSuccess GHOST_ContextVK::createGraphicsCommandBuffer() return GHOST_kSuccess; } -static bool surfaceFormatSupported(const VkSurfaceFormatKHR &surface_format) -{ - if (surface_format.colorSpace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return false; - } - - if (surface_format.format == VK_FORMAT_R8G8B8A8_UNORM || - surface_format.format == VK_FORMAT_B8G8R8A8_UNORM) - { - return true; - } - - return false; -} - /** * Select the surface format that we will use. * @@ -806,15 +781,22 @@ static bool selectSurfaceFormat(const VkPhysicalDevice physical_device, vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data()); for (const VkSurfaceFormatKHR &format : formats) { - if (surfaceFormatSupported(format)) { + if (format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && + format.format == VK_FORMAT_R8G8B8A8_UNORM) + { r_surfaceFormat = format; return true; } } -#if !SELECT_COMPATIBLE_SURFACES_ONLY - r_surfaceFormat = formats[0]; -#endif + for (const VkSurfaceFormatKHR &format : formats) { + if (format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && + format.format == VK_FORMAT_B8G8R8A8_UNORM) + { + r_surfaceFormat = format; + return true; + } + } return false; } @@ -826,13 +808,9 @@ GHOST_TSuccess GHOST_ContextVK::createSwapchain() VkPhysicalDevice physical_device = vulkan_device->physical_device; m_surface_format = {}; -#if SELECT_COMPATIBLE_SURFACES_ONLY if (!selectSurfaceFormat(physical_device, m_surface, m_surface_format)) { return GHOST_kFailure; } -#else - selectSurfaceFormat(physical_device, m_surface, m_surface_format); -#endif VkPresentModeKHR present_mode; if (!selectPresentMode(physical_device, m_surface, &present_mode)) { From 318bc5af77b8f284aece46bb2323112a624300f2 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Jan 2025 10:48:33 +0100 Subject: [PATCH 08/37] BKE: ID namemap: always report warning when checking integrity of the namemap. Using `CLOG_INFO` and then reporting critical error is not very coherent, nor practical, since INFO messages are not even displayed by default in the console. --- source/blender/blenkernel/intern/main_namemap.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc index b932e5514ee..f4a3a9cf06b 100644 --- a/source/blender/blenkernel/intern/main_namemap.cc +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -534,9 +534,8 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) if (!type_map->full_names.contains(key_namemap)) { is_valid = false; if (do_fix) { - CLOG_INFO( + CLOG_WARN( &LOG, - 3, "ID name '%s' (from library '%s') exists in current Main, but is not listed in " "the namemap", id_iter->name, @@ -573,9 +572,8 @@ static bool main_namemap_validate_and_fix(Main *bmain, const bool do_fix) if (!id_names_libs.contains(key)) { is_valid = false; if (do_fix) { - CLOG_INFO( + CLOG_WARN( &LOG, - 3, "ID name '%s' (from library '%s') is listed in the namemap, but does not " "exists in current Main", key.name, From 56f14a0083594c60d858eac8ea9b100948afaa79 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 17 Jan 2025 10:58:28 +0100 Subject: [PATCH 09/37] Vulkan: Add support for GPU_DATA_UBYTE to F16 data conversion This data conversion is needed to download a HDR framebuffer for color-picking and saving screenshots. Pull Request: https://projects.blender.org/blender/blender/pulls/133187 --- .../blender/gpu/vulkan/vk_data_conversion.cc | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/source/blender/gpu/vulkan/vk_data_conversion.cc b/source/blender/gpu/vulkan/vk_data_conversion.cc index fe0a2e65c74..7b913fca499 100644 --- a/source/blender/gpu/vulkan/vk_data_conversion.cc +++ b/source/blender/gpu/vulkan/vk_data_conversion.cc @@ -54,6 +54,10 @@ enum class ConversionType { I32_TO_I8, I8_TO_I32, + /** Convert device 16F to UINT */ + HALF_TO_UI8, + UI8_TO_HALF, + /** Convert device 16F to floats. */ HALF_TO_FLOAT, FLOAT_TO_HALF, @@ -306,6 +310,7 @@ static ConversionType type_of_conversion_uint(eGPUTextureFormat device_format) return ConversionType::UNORM32_TO_FLOAT; case GPU_DEPTH24_STENCIL8: return ConversionType::UINT_TO_DEPTH_COMPONENT24; + case GPU_RGBA8I: case GPU_RGBA8: case GPU_RGBA16I: @@ -449,10 +454,14 @@ static ConversionType type_of_conversion_ubyte(eGPUTextureFormat device_format) case GPU_SRGB8_A8: return ConversionType::PASS_THROUGH; + case GPU_RGBA16F: + case GPU_RG16F: + case GPU_R16F: + return ConversionType::UI8_TO_HALF; + case GPU_RGBA8I: case GPU_RGBA16UI: case GPU_RGBA16I: - case GPU_RGBA16F: case GPU_RGBA16: case GPU_RGBA32UI: case GPU_RGBA32I: @@ -460,7 +469,6 @@ static ConversionType type_of_conversion_ubyte(eGPUTextureFormat device_format) case GPU_RG8I: case GPU_RG16UI: case GPU_RG16I: - case GPU_RG16F: case GPU_RG16: case GPU_RG32UI: case GPU_RG32I: @@ -468,7 +476,6 @@ static ConversionType type_of_conversion_ubyte(eGPUTextureFormat device_format) case GPU_R8I: case GPU_R16UI: case GPU_R16I: - case GPU_R16F: case GPU_R16: case GPU_R32UI: case GPU_R32I: @@ -610,8 +617,6 @@ static ConversionType host_to_device(const eGPUDataFormat host_format, const eGPUTextureFormat host_texture_format, const eGPUTextureFormat device_format) { - BLI_assert(validate_data_format(device_format, host_format)); - switch (host_format) { case GPU_DATA_FLOAT: return type_of_conversion_float(host_texture_format, device_format); @@ -668,6 +673,7 @@ static ConversionType reversed(ConversionType type) CASE_PAIR(FLOAT3, FLOAT4) CASE_PAIR(UINT, DEPTH24_STENCIL8) CASE_PAIR(UINT, DEPTH32F_STENCIL8) + CASE_PAIR(UI8, HALF) case ConversionType::UNSUPPORTED: return ConversionType::UNSUPPORTED; @@ -718,6 +724,7 @@ using UI32 = ComponentValue; using I8 = ComponentValue; using I16 = ComponentValue; using I32 = ComponentValue; +using F16 = ComponentValue; using F32 = ComponentValue; using SRGBA8 = PixelValue>; using FLOAT3 = PixelValue; @@ -939,6 +946,24 @@ static void convert(FLOAT4 &dst, const FLOAT3 &src) dst.value.a = 1.0f; } +static void convert(F16 &dst, const UI8 &src) +{ + UnsignedNormalized un8; + un8.value = src.value; + F32 f32; + convert(f32, un8); + dst.value = math::float_to_half(f32.value); +} + +static void convert(UI8 &dst, const F16 &src) +{ + F32 f32; + f32.value = math::half_to_float(src.value); + UnsignedNormalized un8; + convert(un8, f32); + dst.value = un8.value; +} + constexpr uint32_t MASK_10_BITS = 0b1111111111; constexpr uint32_t MASK_11_BITS = 0b11111111111; constexpr uint8_t SHIFT_B = 22; @@ -1122,6 +1147,13 @@ static void convert_buffer(void *dst_memory, dst_memory, src_memory, buffer_size, device_format); break; + case ConversionType::UI8_TO_HALF: + convert_per_component(dst_memory, src_memory, buffer_size, device_format); + break; + case ConversionType::HALF_TO_UI8: + convert_per_component(dst_memory, src_memory, buffer_size, device_format); + break; + case ConversionType::FLOAT_TO_HALF: blender::math::float_to_half_array(static_cast(src_memory), static_cast(dst_memory), From d4d046a67334b7aacf85b5e7267b95dda10c76b2 Mon Sep 17 00:00:00 2001 From: Pratik Borhade Date: Fri, 17 Jan 2025 11:02:44 +0100 Subject: [PATCH 10/37] Grease Pencil: Allow editing locked material properties As discussed in #132721, properties of locked materials should be editable (similar to locked layers) Pull Request: https://projects.blender.org/blender/blender/pulls/132724 --- .../bl_ui/properties_material_gpencil.py | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/scripts/startup/bl_ui/properties_material_gpencil.py b/scripts/startup/bl_ui/properties_material_gpencil.py index 1a87cbb74c1..0906c87411c 100644 --- a/scripts/startup/bl_ui/properties_material_gpencil.py +++ b/scripts/startup/bl_ui/properties_material_gpencil.py @@ -49,27 +49,24 @@ def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_p if (ma is not None) and (ma.grease_pencil is not None): gpcolor = ma.grease_pencil - if self.layout_type in {'DEFAULT', 'COMPACT'}: - if gpcolor.lock: - layout.active = False + if self.layout_type in {'DEFAULT', 'COMPACT'}: + row = layout.row(align=True) + row.enabled = not gpcolor.lock + row.prop(ma, "name", text="", emboss=False, icon_value=icon) - row = layout.row(align=True) - row.enabled = not gpcolor.lock - row.prop(ma, "name", text="", emboss=False, icon_value=icon) + row = layout.row(align=True) - row = layout.row(align=True) + if gpcolor.ghost is True: + icon = 'ONIONSKIN_OFF' + else: + icon = 'ONIONSKIN_ON' + row.prop(gpcolor, "ghost", text="", icon=icon, emboss=False) + row.prop(gpcolor, "hide", text="", emboss=False) + row.prop(gpcolor, "lock", text="", emboss=False) - if gpcolor.ghost is True: - icon = 'ONIONSKIN_OFF' - else: - icon = 'ONIONSKIN_ON' - row.prop(gpcolor, "ghost", text="", icon=icon, emboss=False) - row.prop(gpcolor, "hide", text="", emboss=False) - row.prop(gpcolor, "lock", text="", emboss=False) - - elif self.layout_type == 'GRID': - layout.alignment = 'CENTER' - layout.label(text="", icon_value=icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) class GPMaterialButtonsPanel: @@ -118,7 +115,6 @@ def draw_header(self, context): ma = context.material if ma is not None and ma.grease_pencil is not None: gpcolor = ma.grease_pencil - self.layout.enabled = not gpcolor.lock self.layout.prop(gpcolor, "show_stroke", text="") def draw(self, context): @@ -130,7 +126,6 @@ def draw(self, context): gpcolor = ma.grease_pencil col = layout.column() - col.enabled = not gpcolor.lock col.prop(gpcolor, "mode") @@ -141,7 +136,6 @@ def draw(self, context): if gpcolor.stroke_style == 'TEXTURE': row = col.row() - row.enabled = not gpcolor.lock col = row.column(align=True) col.template_ID(gpcolor, "stroke_image", open="image.open") @@ -166,7 +160,6 @@ class MATERIAL_PT_gpencil_fillcolor(GPMaterialButtonsPanel, Panel): def draw_header(self, context): ma = context.material gpcolor = ma.grease_pencil - self.layout.enabled = not gpcolor.lock self.layout.prop(gpcolor, "show_fill", text="") def draw(self, context): @@ -178,7 +171,6 @@ def draw(self, context): # color settings col = layout.column() - col.enabled = not gpcolor.lock col.prop(gpcolor, "fill_style", text="Style") if gpcolor.fill_style == 'SOLID': From 2f18e4fe2975d00f4fd0cbee7449dd910670ecd9 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 17 Jan 2025 11:40:11 +0100 Subject: [PATCH 11/37] Vulkan: Add debug group for swapchain Improves debugging swapchains when using renderdoc. Pull Request: https://projects.blender.org/blender/blender/pulls/133190 --- source/blender/gpu/intern/gpu_backend.hh | 3 +++ source/blender/gpu/vulkan/vk_context.cc | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/source/blender/gpu/intern/gpu_backend.hh b/source/blender/gpu/intern/gpu_backend.hh index 9e7d55398e1..e70d623565f 100644 --- a/source/blender/gpu/intern/gpu_backend.hh +++ b/source/blender/gpu/intern/gpu_backend.hh @@ -89,6 +89,9 @@ static inline ColorTheme4f get_debug_group_color(StringRefNull name) else if (name == "Cycles") { return ColorTheme4f(0.0, 0.5, 1.0, 1.0); } + else if (name == "BackBuffer.Blit") { + return ColorTheme4f(0.5, 0.7, 1.0, 1.0); + } else { return GPU_DEBUG_GROUP_COLOR_DEFAULT; } diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index b34c82bfe6f..b9c1eb04c1d 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -5,10 +5,12 @@ /** \file * \ingroup gpu */ -#include "vk_context.hh" -#include "vk_debug.hh" + +#include "GPU_debug.hh" #include "vk_backend.hh" +#include "vk_context.hh" +#include "vk_debug.hh" #include "vk_framebuffer.hh" #include "vk_immediate.hh" #include "vk_shader.hh" @@ -321,6 +323,8 @@ void VKContext::swap_buffers_post_callback() void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_chain_data) { + GPU_debug_group_begin("BackBuffer.Blit"); + VKFrameBuffer &framebuffer = *unwrap(back_left); VKTexture *color_attachment = unwrap(unwrap(framebuffer.color_tex(0))); @@ -357,9 +361,9 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c framebuffer.rendering_end(*this); render_graph.add_node(blit_image); + GPU_debug_group_end(); descriptor_set_get().upload_descriptor_sets(); render_graph.submit_for_present(swap_chain_data.image); - device.resources.remove_image(swap_chain_data.image); #if 0 device.debug_print(); From 9081674e8c6c34265584d4d681882da561389188 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 17 Jan 2025 12:10:46 +0100 Subject: [PATCH 12/37] Fix: UI: Use title case for items in status bar Missed these in the recent pull request review. --- source/blender/editors/mesh/editmesh_tools.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index 6b664175a16..e107a348f05 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -8124,7 +8124,7 @@ static void point_normals_update_statusbar(bContext *C, wmOperator *op) op->type, EDBM_CLNOR_MODAL_POINTTO_SPHERIZE, RNA_boolean_get(op->ptr, "spherize")); - status.opmodal(IFACE_("align"), + status.opmodal(IFACE_("Align"), op->type, EDBM_CLNOR_MODAL_POINTTO_ALIGN, RNA_boolean_get(op->ptr, "align")); @@ -8134,8 +8134,8 @@ static void point_normals_update_statusbar(bContext *C, wmOperator *op) EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE, RNA_enum_get(op->ptr, "mode") == EDBM_CLNOR_POINTTO_MODE_MOUSE); - status.opmodal(IFACE_("Use pivot"), op->type, EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT); - status.opmodal(IFACE_("Use object"), op->type, EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT); + status.opmodal(IFACE_("Use Pivot"), op->type, EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT); + status.opmodal(IFACE_("Use Object"), op->type, EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT); status.opmodal( IFACE_("Set and use 3D cursor"), op->type, EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR); status.opmodal( From 09e4747fc4bb6181bdd6286ae8528d7c3279dd2c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:14:37 +0100 Subject: [PATCH 13/37] Fix: inconsistent UI in Copy on Duplicate panel in User Preferences Generally, checkbox labels are not grayed out when they are not checked. Better be consistent with other parts of the UI here. When I saw this, I was first confused because it looked like I can't enable the values that are not checked. This was introduced in #112393. Pull Request: https://projects.blender.org/blender/blender/pulls/132180 --- scripts/startup/bl_ui/space_userpref.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 85e0a879fa5..804c469f90e 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -450,8 +450,6 @@ def draw_centered(self, context, layout): row_label = row.row() row_label.label(text=type_name, icon=type_icon) - row_label.active = getattr(edit, prop) - class USERPREF_PT_edit_cursor(EditingPanel, CenterAlignMixIn, Panel): bl_label = "3D Cursor" From 1151d82df3fa94a3ad59b3c58b0f816671353c1e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:16:04 +0100 Subject: [PATCH 14/37] Cleanup: move BlendFileData to C++ This simplifies potentially adding more data to the struct. Pull Request: https://projects.blender.org/blender/blender/pulls/133050 --- source/blender/blenloader/BLO_readfile.hh | 21 ++++++++++--------- .../blenloader/intern/readblenentry.cc | 2 +- source/blender/blenloader/intern/readfile.cc | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/source/blender/blenloader/BLO_readfile.hh b/source/blender/blenloader/BLO_readfile.hh index cc6e79bf3ea..42af18ed336 100644 --- a/source/blender/blenloader/BLO_readfile.hh +++ b/source/blender/blenloader/BLO_readfile.hh @@ -5,6 +5,7 @@ #include "BLI_listbase.h" #include "BLI_sys_types.h" +#include "BLI_utility_mixins.hh" /** \file * \ingroup blenloader @@ -51,26 +52,26 @@ enum eBlenFileType { // BLENFILETYPE_RUNTIME = 3, /* UNUSED */ }; -struct BlendFileData { - Main *main; - UserDef *user; +struct BlendFileData : blender::NonCopyable, blender::NonMovable { + Main *main = nullptr; + UserDef *user = nullptr; - int fileflags; - int globalf; + int fileflags = 0; + int globalf = 0; /** Typically the actual filepath of the read blend-file, except when recovering * save-on-exit/autosave files. In the latter case, it will be the path of the file that * generated the auto-saved one being recovered. * * NOTE: Currently expected to be the same path as #BlendFileData.filepath. */ - char filepath[1024]; /* 1024 = FILE_MAX */ + char filepath[1024] = {}; /* 1024 = FILE_MAX */ /** TODO: think this isn't needed anymore? */ - bScreen *curscreen; - Scene *curscene; + bScreen *curscreen = nullptr; + Scene *curscene = nullptr; /** Layer to activate in workspaces when reading without UI. */ - ViewLayer *cur_view_layer; + ViewLayer *cur_view_layer = nullptr; - eBlenFileType type; + eBlenFileType type = eBlenFileType(0); }; /** diff --git a/source/blender/blenloader/intern/readblenentry.cc b/source/blender/blenloader/intern/readblenentry.cc index ec44af22f0e..e379259a19b 100644 --- a/source/blender/blenloader/intern/readblenentry.cc +++ b/source/blender/blenloader/intern/readblenentry.cc @@ -401,7 +401,7 @@ void BLO_blendfiledata_free(BlendFileData *bfd) MEM_freeN(bfd->user); } - MEM_freeN(bfd); + MEM_delete(bfd); } void BLO_read_do_version_after_setup(Main *new_bmain, diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 8ab59876bcf..8d5a0f509ee 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -3645,7 +3645,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) * non-BLO functions (e.g. ID deletion) can indirectly trigger it. */ BKE_layer_collection_resync_forbid(); - bfd = static_cast(MEM_callocN(sizeof(BlendFileData), "blendfiledata")); + bfd = MEM_new(__func__); bfd->main = BKE_main_new(); bfd->main->versionfile = fd->fileversion; From 7408d0d8b1f504ca7ca0d364028a481447c0a066 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:17:17 +0100 Subject: [PATCH 15/37] Nodes: improve getting owner id during tree update Using the `BKE_id_owner_get` during the node tree update can avoid having to iterate over lots of data-blocks in common cases. Previously, the code had to iterate over all potential owners of node group to find the correct one. Pull Request: https://projects.blender.org/blender/blender/pulls/132903 --- .../blenkernel/intern/node_tree_update.cc | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 3b1bec5275e..345b432d7d5 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -19,6 +19,7 @@ #include "BKE_anim_data.hh" #include "BKE_image.hh" +#include "BKE_lib_id.hh" #include "BKE_main.hh" #include "BKE_node.hh" #include "BKE_node_enum.hh" @@ -196,7 +197,6 @@ struct NodeTreeRelations { private: Main *bmain_; std::optional> all_trees_; - std::optional> owner_ids_; std::optional> group_node_users_; std::optional> modifiers_users_; @@ -209,25 +209,16 @@ struct NodeTreeRelations { return; } all_trees_.emplace(); - owner_ids_.emplace(); if (bmain_ == nullptr) { return; } FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { all_trees_->append(ntree); - if (&ntree->id != id) { - owner_ids_->add_new(ntree, id); - } } FOREACH_NODETREE_END; } - void ensure_owner_ids() - { - this->ensure_all_trees(); - } - void ensure_group_node_users() { if (group_node_users_.has_value()) { @@ -287,12 +278,6 @@ struct NodeTreeRelations { BLI_assert(group_node_users_.has_value()); return group_node_users_->lookup(ntree); } - - ID &get_owner_id(bNodeTree *ntree) - { - BLI_assert(owner_ids_.has_value()); - return *owner_ids_->lookup_default(ntree, &ntree->id); - } }; struct TreeUpdateResult { @@ -395,13 +380,13 @@ class NodeTreeMainUpdater { ntree->runtime->geometry_nodes_lazy_function_graph_info.reset(); } - relations_.ensure_owner_ids(); - ID &owner_id = relations_.get_owner_id(ntree); + ID *owner_id = BKE_id_owner_get(&ntree->id); + ID &owner_or_self_id = owner_id ? *owner_id : ntree->id; if (params_.tree_changed_fn) { - params_.tree_changed_fn(*ntree, owner_id); + params_.tree_changed_fn(*ntree, owner_or_self_id); } if (params_.tree_output_changed_fn && result.output_changed) { - params_.tree_output_changed_fn(*ntree, owner_id); + params_.tree_output_changed_fn(*ntree, owner_or_self_id); } } From 987003d4568a8450b554d6eb546174a7f541421f Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:17:49 +0100 Subject: [PATCH 16/37] Nodes: replace some node checks with accessor method calls This uses the following accessor methods in more places in more places: `is_group()`, `is_group_input()`, `is_group_output()`, `is_muted()`, `is_frame()` and `is_reroute()`. This results in simpler code and reduces the use of `bNode.type_legacy`. Pull Request: https://projects.blender.org/blender/blender/pulls/132899 --- source/blender/blenkernel/intern/material.cc | 4 +- source/blender/blenkernel/intern/node.cc | 18 ++++----- .../blender/blenkernel/intern/node_runtime.cc | 2 +- .../intern/node_tree_field_inferencing.cc | 12 +++--- source/blender/blenkernel/intern/texture.cc | 2 +- .../blenloader/intern/versioning_400.cc | 8 ++-- source/blender/compositor/intern/scheduler.cc | 8 ++-- .../intern/builder/deg_builder_nodes.cc | 2 +- .../intern/builder/deg_builder_relations.cc | 2 +- .../blender/editors/animation/anim_filter.cc | 3 +- .../editors/space_buttons/buttons_texture.cc | 4 +- source/blender/editors/space_node/drawnode.cc | 6 +-- .../editors/space_node/link_drag_search.cc | 2 +- .../blender/editors/space_node/node_draw.cc | 37 ++++++++++--------- .../blender/editors/space_node/node_edit.cc | 4 +- .../blender/editors/space_node/node_group.cc | 8 ++-- .../editors/space_node/node_relationships.cc | 8 ++-- .../editors/space_node/node_templates.cc | 4 +- .../blender/editors/space_node/node_view.cc | 2 +- .../blender/makesrna/intern/rna_nodetree.cc | 8 ++-- .../nodes/composite/node_composite_tree.cc | 2 +- source/blender/nodes/intern/node_exec.cc | 2 +- .../nodes/shader/materialx/node_parser.cc | 2 +- .../blender/nodes/shader/node_shader_tree.cc | 25 +++++-------- .../nodes/shader/nodes/node_shader_common.cc | 2 +- .../nodes/texture/node_texture_tree.cc | 4 +- .../nodes/texture/node_texture_util.cc | 2 +- .../texture/nodes/node_texture_common.cc | 2 +- source/blender/render/intern/pipeline.cc | 10 ++--- 29 files changed, 94 insertions(+), 101 deletions(-) diff --git a/source/blender/blenkernel/intern/material.cc b/source/blender/blenkernel/intern/material.cc index bf976f4a53a..295f8bcf7ea 100644 --- a/source/blender/blenkernel/intern/material.cc +++ b/source/blender/blenkernel/intern/material.cc @@ -290,7 +290,7 @@ static void nodetree_mark_previews_dirty_reccursive(bNodeTree *tree) } tree->runtime->previews_refresh_state++; for (bNode *node : tree->all_nodes()) { - if (node->type_legacy == NODE_GROUP) { + if (node->is_group()) { bNodeTree *nested_tree = reinterpret_cast(node->id); nodetree_mark_previews_dirty_reccursive(nested_tree); } @@ -1478,7 +1478,7 @@ static bool ntree_foreach_texnode_recursive(bNodeTree *nodetree, return false; } } - else if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) { + else if (node->is_group() && node->id) { /* recurse into the node group and see if it contains any textures */ if (!ntree_foreach_texnode_recursive((bNodeTree *)node->id, callback, userdata, slot_filter)) { diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 5557c415c41..5593f57aad1 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -2167,9 +2167,9 @@ bNodeSocket *node_add_socket(bNodeTree *ntree, const StringRefNull identifier, const StringRefNull name) { - BLI_assert(node->type_legacy != NODE_FRAME); - BLI_assert(!(in_out == SOCK_IN && node->type_legacy == NODE_GROUP_INPUT)); - BLI_assert(!(in_out == SOCK_OUT && node->type_legacy == NODE_GROUP_OUTPUT)); + BLI_assert(!node->is_frame()); + BLI_assert(!(in_out == SOCK_IN && node->is_group_input())); + BLI_assert(!(in_out == SOCK_OUT && node->is_group_output())); ListBase *lb = (in_out == SOCK_IN ? &node->inputs : &node->outputs); bNodeSocket *sock = make_socket(ntree, node, in_out, lb, idname, identifier, name); @@ -2552,7 +2552,7 @@ bNode *node_find_root_parent(bNode *node) while (parent_iter->parent != nullptr) { parent_iter = parent_iter->parent; } - if (parent_iter->type_legacy != NODE_FRAME) { + if (!parent_iter->is_frame()) { return nullptr; } return parent_iter; @@ -2922,7 +2922,7 @@ void node_socket_move_default_value(Main & /*bmain*/, /* Multi input sockets no have value. */ return; } - if (ELEM(NODE_REROUTE, dst_node.type_legacy, src_node.type_legacy)) { + if (dst_node.is_reroute() || src_node.is_reroute()) { /* Reroute node can't have ownership of socket value directly. */ return; } @@ -3148,7 +3148,7 @@ void node_internal_relink(bNodeTree *ntree, bNode *node) void node_attach_node(bNodeTree *ntree, bNode *node, bNode *parent) { - BLI_assert(parent->type_legacy == NODE_FRAME); + BLI_assert(parent->is_frame()); BLI_assert(!node_is_parent_and_child(parent, node)); node->parent = parent; BKE_ntree_update_tag_parent_change(ntree, node); @@ -3157,7 +3157,7 @@ void node_attach_node(bNodeTree *ntree, bNode *node, bNode *parent) void node_detach_node(bNodeTree *ntree, bNode *node) { if (node->parent) { - BLI_assert(node->parent->type_legacy == NODE_FRAME); + BLI_assert(node->parent->is_frame()); node->parent = nullptr; BKE_ntree_update_tag_parent_change(ntree, node); } @@ -3700,10 +3700,10 @@ void node_tree_set_output(bNodeTree *ntree) } /* group node outputs use this flag too */ - if (node->type_legacy == NODE_GROUP_OUTPUT) { + if (node->is_group_output()) { int output = 0; LISTBASE_FOREACH (bNode *, tnode, &ntree->nodes) { - if (tnode->type_legacy != NODE_GROUP_OUTPUT) { + if (!tnode->is_group_output()) { continue; } if (tnode->flag & NODE_DO_OUTPUT) { diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index 1718d3e9bc0..aedf11e62a2 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -178,7 +178,7 @@ static void find_logical_origins_for_socket_recursive( /* Non available sockets are ignored. */ continue; } - if (origin_node.type_legacy == NODE_REROUTE) { + if (origin_node.is_reroute()) { bNodeSocket &reroute_input = *origin_node.runtime->inputs[0]; bNodeSocket &reroute_output = *origin_node.runtime->outputs[0]; r_skipped_origins.append(&reroute_input); diff --git a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc index 02d9fa0736b..6f0b04b1390 100644 --- a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc @@ -44,10 +44,10 @@ static InputSocketFieldType get_interface_input_field_type(const bNode &node, if (!is_field_socket_type(socket)) { return InputSocketFieldType::None; } - if (node.type_legacy == NODE_REROUTE) { + if (node.is_reroute()) { return InputSocketFieldType::IsSupported; } - if (node.type_legacy == NODE_GROUP_OUTPUT) { + if (node.is_group_output()) { /* Outputs always support fields when the data type is correct. */ return InputSocketFieldType::IsSupported; } @@ -77,11 +77,11 @@ static OutputFieldDependency get_interface_output_field_dependency(const bNode & /* Non-field sockets always output data. */ return OutputFieldDependency::ForDataSource(); } - if (node.type_legacy == NODE_REROUTE) { + if (node.is_reroute()) { /* The reroute just forwards what is passed in. */ return OutputFieldDependency::ForDependentField(); } - if (node.type_legacy == NODE_GROUP_INPUT) { + if (node.is_group_input()) { /* Input nodes get special treatment in #determine_group_input_states. */ return OutputFieldDependency::ForDependentField(); } @@ -234,7 +234,7 @@ static OutputFieldDependency find_group_output_dependencies( field_state_by_socket_id[origin_socket->index_in_tree()]; if (origin_state.is_field_source) { - if (origin_node.type_legacy == NODE_GROUP_INPUT) { + if (origin_node.is_group_input()) { /* Found a group input that the group output depends on. */ linked_input_indices.append_non_duplicates(origin_socket->index()); } @@ -577,7 +577,7 @@ static void propagate_field_status_from_left_to_right( bool need_update = false; for (const bNode *node : toposort_result) { - if (node->type_legacy == NODE_GROUP_INPUT) { + if (node->is_group_input()) { continue; } diff --git a/source/blender/blenkernel/intern/texture.cc b/source/blender/blenkernel/intern/texture.cc index b41ea560ed1..a9700e590da 100644 --- a/source/blender/blenkernel/intern/texture.cc +++ b/source/blender/blenkernel/intern/texture.cc @@ -728,7 +728,7 @@ static void texture_nodes_fetch_images_for_pool(Tex *texture, bNodeTree *ntree, Image *image = (Image *)node->id; BKE_image_pool_acquire_ibuf(image, &texture->iuser, pool); } - else if (node->type_legacy == NODE_GROUP && node->id != nullptr) { + else if (node->is_group() && node->id != nullptr) { /* TODO(sergey): Do we need to control recursion here? */ bNodeTree *nested_tree = (bNodeTree *)node->id; texture_nodes_fetch_images_for_pool(texture, nested_tree, pool); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 7877af59bb8..cdc191bfff1 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -2420,7 +2420,7 @@ static void change_input_socket_to_rotation_type(bNodeTree &ntree, continue; } if (ELEM(link->fromsock->type, SOCK_ROTATION, SOCK_VECTOR, SOCK_FLOAT) && - link->fromnode->type_legacy != NODE_REROUTE) + !link->fromnode->is_reroute()) { /* No need to add the conversion node when implicit conversions will work. */ continue; @@ -2453,9 +2453,7 @@ static void change_output_socket_to_rotation_type(bNodeTree &ntree, if (link->fromsock != &socket) { continue; } - if (ELEM(link->tosock->type, SOCK_ROTATION, SOCK_VECTOR) && - link->tonode->type_legacy != NODE_REROUTE) - { + if (ELEM(link->tosock->type, SOCK_ROTATION, SOCK_VECTOR) && !link->tonode->is_reroute()) { /* No need to add the conversion node when implicit conversions will work. */ continue; } @@ -3568,7 +3566,7 @@ static void rename_mesh_uv_seam_attribute(Mesh &mesh) static void version_group_input_socket_data_block_reference(bNodeTree &ntree) { LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { - if (node->type_legacy != NODE_GROUP_INPUT) { + if (!node->is_group_input()) { continue; } LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { diff --git a/source/blender/compositor/intern/scheduler.cc b/source/blender/compositor/intern/scheduler.cc index 91bdba8665d..23cd02f8143 100644 --- a/source/blender/compositor/intern/scheduler.cc +++ b/source/blender/compositor/intern/scheduler.cc @@ -27,7 +27,7 @@ using namespace nodes::derived_node_tree_types; static bool add_viewer_nodes_in_context(const DTreeContext *context, Stack &node_stack) { for (const bNode *node : context->btree().nodes_by_type("CompositorNodeViewer")) { - if (node->flag & NODE_DO_OUTPUT && !(node->flag & NODE_MUTED)) { + if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { node_stack.push(DNode(context, node)); return true; } @@ -41,7 +41,7 @@ static bool add_viewer_nodes_in_context(const DTreeContext *context, Stackbtree().nodes_by_type("CompositorNodeComposite")) { - if (node->flag & NODE_DO_OUTPUT && !(node->flag & NODE_MUTED)) { + if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { node_stack.push(DNode(context, node)); return true; } @@ -65,7 +65,7 @@ static void add_output_nodes(const Context &context, /* Only add File Output nodes if the context supports them. */ if (context.use_file_output()) { for (const bNode *node : root_context.btree().nodes_by_type("CompositorNodeOutputFile")) { - if (!(node->flag & NODE_MUTED)) { + if (!node->is_muted()) { node_stack.push(DNode(&root_context, node)); } } @@ -75,7 +75,7 @@ static void add_output_nodes(const Context &context, * Composite node may still be added as a fallback viewer output below. */ if (context.use_composite_output()) { for (const bNode *node : root_context.btree().nodes_by_type("CompositorNodeComposite")) { - if (node->flag & NODE_DO_OUTPUT && !(node->flag & NODE_MUTED)) { + if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { node_stack.push(DNode(&root_context, node)); break; } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index cb0025abf62..d58cbc2f980 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -2001,7 +2001,7 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) else if (id_type == ID_VF) { build_vfont((VFont *)id); } - else if (ELEM(bnode->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP)) { + else if (bnode->is_group()) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 3413050639b..b9884fb6fad 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -3051,7 +3051,7 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) ComponentKey vfont_key(id, NodeType::GENERIC_DATABLOCK); add_relation(vfont_key, ntree_output_key, "VFont -> Node"); } - else if (ELEM(bnode->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP)) { + else if (bnode->is_group()) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); ComponentKey group_output_key(&group_ntree->id, NodeType::NTREE_OUTPUT); diff --git a/source/blender/editors/animation/anim_filter.cc b/source/blender/editors/animation/anim_filter.cc index 7a0e04195e8..d62c48e1b57 100644 --- a/source/blender/editors/animation/anim_filter.cc +++ b/source/blender/editors/animation/anim_filter.cc @@ -81,6 +81,7 @@ #include "BKE_material.hh" #include "BKE_modifier.hh" #include "BKE_node.hh" +#include "BKE_node_runtime.hh" #include "ED_anim_api.hh" #include "ED_markers.hh" @@ -2517,7 +2518,7 @@ static size_t animdata_filter_ds_nodetree(bAnimContext *ac, items += animdata_filter_ds_nodetree_group(ac, anim_data, owner_id, ntree, filter_mode); LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type_legacy == NODE_GROUP) { + if (node->is_group()) { if (node->id) { if ((ac->ads->filterflag & ADS_FILTER_ONLYSEL) && (node->flag & NODE_SELECT) == 0) { continue; diff --git a/source/blender/editors/space_buttons/buttons_texture.cc b/source/blender/editors/space_buttons/buttons_texture.cc index abe58207ed3..21bf41461a3 100644 --- a/source/blender/editors/space_buttons/buttons_texture.cc +++ b/source/blender/editors/space_buttons/buttons_texture.cc @@ -158,7 +158,7 @@ static void buttons_texture_users_find_nodetree(ListBase *users, RNA_struct_ui_icon(ptr.type), node->name); } - else if (node->type_legacy == NODE_GROUP && node->id) { + else if (node->is_group() && node->id) { buttons_texture_users_find_nodetree(users, id, (bNodeTree *)node->id, category); } } @@ -175,7 +175,7 @@ static void buttons_texture_modifier_geonodes_users_add( PropertyRNA *prop; for (bNode *node : node_tree->all_nodes()) { - if (node->type_legacy == NODE_GROUP && node->id) { + if (node->is_group() && node->id) { if (handled_groups.add(reinterpret_cast(node->id))) { /* Recurse into the node group */ buttons_texture_modifier_geonodes_users_add( diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 58852947f3a..6095fbf7585 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -228,7 +228,7 @@ NodeResizeDirection node_get_resize_direction(const SpaceNode &snode, { const float size = NODE_RESIZE_MARGIN * math::max(snode.runtime->aspect, 1.0f); - if (node->type_legacy == NODE_FRAME) { + if (node->is_frame()) { NodeFrame *data = (NodeFrame *)node->storage; /* shrinking frame size is determined by child nodes */ @@ -2371,8 +2371,8 @@ static NodeLinkDrawConfig nodelink_get_draw_config(const bContext &C, (field_link ? 0.7f : 1.0f); draw_config.has_back_link = gizmo_link; draw_config.highlighted = link.flag & NODE_LINK_TEMP_HIGHLIGHT; - draw_config.drawarrow = ((link.tonode && (link.tonode->type_legacy == NODE_REROUTE)) && - (link.fromnode && (link.fromnode->type_legacy == NODE_REROUTE))); + draw_config.drawarrow = ((link.tonode && link.tonode->is_reroute()) && + (link.fromnode && link.fromnode->is_reroute())); draw_config.drawmuted = (link.flag & NODE_LINK_MUTED); UI_GetThemeColor4fv(th_col3, draw_config.outline_color); diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index 5df877f3fb2..1d3bd268728 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -104,7 +104,7 @@ static void add_group_input_node_fn(nodes::LinkSearchOpParams ¶ms) /* Hide the new input in all other group input nodes, to avoid making them taller. */ for (bNode *node : params.node_tree.all_nodes()) { - if (node->type_legacy == NODE_GROUP_INPUT) { + if (node->is_group_input()) { bNodeSocket *new_group_input_socket = bke::node_find_socket( node, SOCK_OUT, socket_iface->identifier); if (new_group_input_socket) { diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index f181a22a71a..8bb0d5dbbd9 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -423,7 +423,7 @@ static bool node_update_basis_buttons(const bContext &C, 0, UI_style_get_dpi()); - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { uiLayoutSetActive(layout, false); } @@ -506,7 +506,7 @@ static bool node_update_basis_socket(const bContext &C, 0, UI_style_get_dpi()); - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { uiLayoutSetActive(layout, false); } @@ -1104,7 +1104,7 @@ static void node_update_basis_from_declaration( 0, 0, UI_style_get_dpi()); - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { uiLayoutSetActive(layout, false); } PointerRNA node_ptr = RNA_pointer_create(&ntree.id, &RNA_Node, &node); @@ -2476,7 +2476,7 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block 0, 0, ""); - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_but_flag_enable(label_but, UI_BUT_INACTIVE); } @@ -2646,7 +2646,7 @@ static std::optional geo_node_get_execution_time( if (tree_log == nullptr) { return std::nullopt; } - if (node.type_legacy == NODE_GROUP_OUTPUT) { + if (node.is_group_output()) { return tree_log->execution_time; } if (node.is_frame()) { @@ -3104,7 +3104,7 @@ static void node_draw_extra_info_row(const bNode &node, but_text, extra_info_row.tooltip_fn, extra_info_row.tooltip_fn_arg, nullptr); } - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_but_flag_enable(but_text, UI_BUT_INACTIVE); UI_but_flag_enable(but_icon, UI_BUT_INACTIVE); } @@ -3120,7 +3120,7 @@ static void node_draw_extra_info_panel_back(const bNode &node, const rctf &extra } ColorTheme4f color; - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_GetThemeColorBlend4f(TH_BACK, TH_NODE, 0.2f, color); } else { @@ -3306,7 +3306,7 @@ static void node_draw_basis(const bContext &C, float color_header[4]; /* Muted nodes get a mix of the background with the node color. */ - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.1f, color_header); } else { @@ -3320,7 +3320,8 @@ static void node_draw_basis(const bContext &C, /* Show/hide icons. */ float iconofs = rct.xmax - 0.35f * U.widget_unit; - /* Group edit. This icon should be the first for the node groups. */ + /* Group edit. This icon should be the first for the node groups. Note that we intentionally + * don't check for NODE_GROUP_CUSTOM here. */ if (node.type_legacy == NODE_GROUP) { iconofs -= iconbutw; UI_block_emboss_set(&block, UI_EMBOSS_NONE); @@ -3472,12 +3473,12 @@ static void node_draw_basis(const bContext &C, const_cast(&node), nullptr); - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_but_flag_enable(but, UI_BUT_INACTIVE); } /* Wire across the node when muted/disabled. */ - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { node_draw_mute_line(C, v2d, snode, node); } @@ -3489,7 +3490,7 @@ static void node_draw_basis(const bContext &C, UI_GetThemeColorBlend4f(TH_REDALERT, TH_NODE, 0.4f, color); } /* Muted nodes get a mix of the background with the node color. */ - else if (node.flag & NODE_MUTED) { + else if (node.is_muted()) { UI_GetThemeColorBlend4f(TH_BACK, TH_NODE, 0.2f, color); } else if (node.flag & NODE_CUSTOM_COLOR) { @@ -3505,7 +3506,7 @@ static void node_draw_basis(const bContext &C, } /* Draw muted nodes slightly transparent so the wires inside are visible. */ - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { color[3] -= 0.2f; } @@ -3529,7 +3530,7 @@ static void node_draw_basis(const bContext &C, { float color_underline[4]; - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.05f, color_underline); color_underline[3] = 1.0f; } @@ -3620,7 +3621,7 @@ static void node_draw_hidden(const bContext &C, node_draw_shadow(snode, node, hiddenrad, 1.0f); /* Wire across the node when muted/disabled. */ - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { node_draw_mute_line(C, v2d, snode, node); } @@ -3631,7 +3632,7 @@ static void node_draw_hidden(const bContext &C, /* Use warning color to indicate undefined types. */ UI_GetThemeColorBlend4f(TH_REDALERT, TH_NODE, 0.4f, color); } - else if (node.flag & NODE_MUTED) { + else if (node.is_muted()) { /* Muted nodes get a mix of the background with the node color. */ UI_GetThemeColorBlendShade4fv(TH_BACK, color_id, 0.1f, 0, color); } @@ -3648,7 +3649,7 @@ static void node_draw_hidden(const bContext &C, } /* Draw muted nodes slightly transparent so the wires inside are visible. */ - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { color[3] -= 0.2f; } @@ -3740,7 +3741,7 @@ static void node_draw_hidden(const bContext &C, UI_draw_roundbox_4fv(&rect, false, hiddenrad + outline_width, color_outline); } - if (node.flag & NODE_MUTED) { + if (node.is_muted()) { UI_but_flag_enable(but, UI_BUT_INACTIVE); } diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index fd3641ba4c4..63d8f2e61c3 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -695,9 +695,9 @@ void ED_node_set_active( bool do_update = false; /* Generic node group output: set node as active output. */ - if (node->type_legacy == NODE_GROUP_OUTPUT) { + if (node->is_group_output()) { for (bNode *node_iter : ntree->all_nodes()) { - if (node_iter->type_legacy == NODE_GROUP_OUTPUT) { + if (node_iter->is_group_output()) { node_iter->flag &= ~NODE_DO_OUTPUT; } } diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 8aa15287966..e4bdbdea05f 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -290,7 +290,7 @@ static void node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) /* Remove interface nodes. * This also removes remaining links to and from interface nodes. */ - if (ELEM(node->type_legacy, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) { + if (node->is_group_input() || node->is_group_output()) { /* We must delay removal since sockets will reference this node. see: #52092 */ nodes_delayed_free.append(node); } @@ -377,7 +377,7 @@ static void node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) /* input links */ if (glinks_first != nullptr) { for (bNodeLink *link = glinks_first->next; link != glinks_last->next; link = link->next) { - if (link->fromnode->type_legacy == NODE_GROUP_INPUT) { + if (link->fromnode->is_group_input()) { const char *identifier = link->fromsock->identifier; int num_external_links = 0; @@ -423,9 +423,7 @@ static void node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) tlink = tlink->next) { /* only use active output node */ - if (tlink->tonode->type_legacy == NODE_GROUP_OUTPUT && - (tlink->tonode->flag & NODE_DO_OUTPUT)) - { + if (tlink->tonode->is_group_output() && (tlink->tonode->flag & NODE_DO_OUTPUT)) { if (STREQ(tlink->tosock->identifier, identifier)) { bke::node_add_link( ntree, tlink->fromnode, tlink->fromsock, link->tonode, link->tosock); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 99502402cfd..92ebc271c8d 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -218,7 +218,7 @@ static bNodeSocket *best_socket_output(bNodeTree *ntree, /* Always allow linking to an reroute node. The socket type of the reroute sockets might change * after the link has been created. */ - if (node->type_legacy == NODE_REROUTE) { + if (node->is_reroute()) { return (bNodeSocket *)node->outputs.first; } @@ -1914,7 +1914,7 @@ static int node_parent_set_exec(bContext *C, wmOperator * /*op*/) SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; bNode *frame = bke::node_get_active(&ntree); - if (!frame || frame->type_legacy != NODE_FRAME) { + if (!frame || !frame->is_frame()) { return OPERATOR_CANCELLED; } @@ -2437,7 +2437,7 @@ void node_insert_on_link_flags(Main &bmain, SpaceNode &snode, bool is_new_node) best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT); } - if (node_to_insert->type_legacy != NODE_REROUTE) { + if (!node_to_insert->is_reroute()) { /* Ignore main sockets when the types don't match. */ if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr && !ntree.typeinfo->validate_link(static_cast(old_link->fromsock->type), @@ -2719,7 +2719,7 @@ static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, /* check nodes front to back */ for (bNode *frame : tree_draw_order_calc_nodes_reversed(*ntree)) { /* skip selected, those are the nodes we want to attach */ - if ((frame->type_legacy != NODE_FRAME) || (frame->flag & NODE_SELECT)) { + if (!frame->is_frame() || (frame->flag & NODE_SELECT)) { continue; } diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 95032c6a20e..45a59eb93e7 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -77,7 +77,7 @@ static void node_link_item_init(NodeLinkItem &item) */ static bool node_link_item_compare(bNode *node, NodeLinkItem *item) { - if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP)) { + if (node->is_group()) { return (node->id == (ID *)item->ngroup); } return true; @@ -85,7 +85,7 @@ static bool node_link_item_compare(bNode *node, NodeLinkItem *item) static void node_link_item_apply(bNodeTree *ntree, bNode *node, NodeLinkItem *item) { - if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP)) { + if (node->is_group()) { node->id = (ID *)item->ngroup; BKE_ntree_update_tag_node_property(ntree, node); } diff --git a/source/blender/editors/space_node/node_view.cc b/source/blender/editors/space_node/node_view.cc index 83f3e004d4f..4daa8b9fb5e 100644 --- a/source/blender/editors/space_node/node_view.cc +++ b/source/blender/editors/space_node/node_view.cc @@ -92,7 +92,7 @@ bool space_node_view_flag( BLI_rctf_union(&cur_new, &node->runtime->draw_bounds); tot++; - if (node->type_legacy == NODE_FRAME) { + if (node->is_frame()) { has_frame = true; } } diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index e62c516fd30..43ef380e8eb 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -2397,7 +2397,7 @@ static void rna_Node_parent_set(PointerRNA *ptr, PointerRNA value, ReportList * /* XXX only Frame node allowed for now, * in the future should have a poll function or so to test possible attachment. */ - if (parent->type_legacy != NODE_FRAME) { + if (!parent->is_frame()) { return; } @@ -2487,11 +2487,11 @@ static bool rna_Node_parent_poll(PointerRNA *ptr, PointerRNA value) /* XXX only Frame node allowed for now, * in the future should have a poll function or so to test possible attachment. */ - if (parent->type_legacy != NODE_FRAME) { + if (!parent->is_frame()) { return false; } - if (node->type_legacy == NODE_FRAME && blender::bke::node_is_parent_and_child(node, parent)) { + if (node->is_frame() && blender::bke::node_is_parent_and_child(node, parent)) { return false; } @@ -4364,7 +4364,7 @@ static void rna_GroupOutput_is_active_output_set(PointerRNA *ptr, bool value) if (value) { /* Make sure that no other group output is active at the same time. */ LISTBASE_FOREACH (bNode *, other_node, &ntree->nodes) { - if (other_node->type_legacy == NODE_GROUP_OUTPUT) { + if (other_node->is_group_output()) { other_node->flag &= ~NODE_DO_OUTPUT; } } diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index 66719488a2f..7421c71f0ed 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -227,7 +227,7 @@ void ntreeCompositClearTags(bNodeTree *ntree) for (bNode *node : ntree->all_nodes()) { node->runtime->need_exec = 0; - if (node->type_legacy == NODE_GROUP) { + if (node->is_group()) { ntreeCompositClearTags((bNodeTree *)node->id); } } diff --git a/source/blender/nodes/intern/node_exec.cc b/source/blender/nodes/intern/node_exec.cc index e0fe3260b1d..84e5c262de3 100644 --- a/source/blender/nodes/intern/node_exec.cc +++ b/source/blender/nodes/intern/node_exec.cc @@ -173,7 +173,7 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, node_init_input_index(sock, &index); } - if (node->flag & NODE_MUTED || node->type_legacy == NODE_REROUTE) { + if (node->is_muted() || node->is_reroute()) { LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { node_init_output_index_muted(sock, &index, node->runtime->internal_links); } diff --git a/source/blender/nodes/shader/materialx/node_parser.cc b/source/blender/nodes/shader/materialx/node_parser.cc index 56363a807fd..abedf172535 100644 --- a/source/blender/nodes/shader/materialx/node_parser.cc +++ b/source/blender/nodes/shader/materialx/node_parser.cc @@ -244,7 +244,7 @@ NodeItem NodeParser::get_input_link(const bNodeSocket &socket, const bNode *from_node = link->fromnode; - /* Passing NODE_REROUTE nodes */ + /* Passing reroute nodes. */ while (from_node->is_reroute()) { link = from_node->input_socket(0).link; if (!(link && link->is_used())) { diff --git a/source/blender/nodes/shader/node_shader_tree.cc b/source/blender/nodes/shader/node_shader_tree.cc index b0425749fdc..3e6ae100166 100644 --- a/source/blender/nodes/shader/node_shader_tree.cc +++ b/source/blender/nodes/shader/node_shader_tree.cc @@ -129,7 +129,7 @@ static void localize(bNodeTree *localtree, bNodeTree * /*ntree*/) { /* replace muted nodes and reroute nodes by internal links */ LISTBASE_FOREACH_MUTABLE (bNode *, node, &localtree->nodes) { - if (node->flag & NODE_MUTED || node->type_legacy == NODE_REROUTE) { + if (node->is_muted() || node->is_reroute()) { if (node->is_group() && node->id) { /* Free the group like in #ntree_shader_groups_flatten. */ bNodeTree *group = reinterpret_cast(node->id); @@ -351,15 +351,14 @@ static void ntree_shader_unlink_hidden_value_sockets(bNode *group_node, bNodeSoc bool removed_link = false; LISTBASE_FOREACH (bNode *, node, &group_ntree->nodes) { - const bool is_group = ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP) && - (node->id != nullptr); + const bool is_group = node->is_group() && (node->id != nullptr); LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { if (!is_group && (sock->flag & SOCK_HIDE_VALUE) == 0) { continue; } /* If socket is linked to a group input node and sockets id match. */ - if (sock && sock->link && sock->link->fromnode->type_legacy == NODE_GROUP_INPUT) { + if (sock && sock->link && sock->link->fromnode->is_group_input()) { if (STREQ(isock->identifier, sock->link->fromsock->identifier)) { if (is_group) { /* Recursively unlink sockets within the nested group. */ @@ -386,10 +385,8 @@ static void ntree_shader_groups_expand_inputs(bNodeTree *localtree) bool link_added = false; LISTBASE_FOREACH (bNode *, node, &localtree->nodes) { - const bool is_group = ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP) && - (node->id != nullptr); - const bool is_group_output = node->type_legacy == NODE_GROUP_OUTPUT && - (node->flag & NODE_DO_OUTPUT); + const bool is_group = node->is_group() && (node->id != nullptr); + const bool is_group_output = node->is_group_output() && (node->flag & NODE_DO_OUTPUT); if (is_group) { /* Do it recursively. */ @@ -452,7 +449,7 @@ static void ntree_shader_groups_expand_inputs(bNodeTree *localtree) static void ntree_shader_groups_remove_muted_links(bNodeTree *ntree) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type_legacy == NODE_GROUP) { + if (node->is_group()) { if (node->id != nullptr) { ntree_shader_groups_remove_muted_links(reinterpret_cast(node->id)); } @@ -475,7 +472,7 @@ static void flatten_group_do(bNodeTree *ntree, bNode *gnode) /* Remove interface nodes. * This also removes remaining links to and from interface nodes. * We must delay removal since sockets will reference this node. see: #52092 */ - if (ELEM(node->type_legacy, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) { + if (node->is_group_input() || node->is_group_output()) { BLI_linklist_prepend(&group_interface_nodes, node); } /* migrate node */ @@ -503,7 +500,7 @@ static void flatten_group_do(bNodeTree *ntree, bNode *gnode) if (glinks_first != nullptr) { /* input links */ for (bNodeLink *link = glinks_first->next; link != glinks_last->next; link = link->next) { - if (link->fromnode->type_legacy == NODE_GROUP_INPUT) { + if (link->fromnode->is_group_input()) { const char *identifier = link->fromsock->identifier; /* find external links to this input */ for (bNodeLink *tlink = static_cast(ntree->links.first); @@ -529,9 +526,7 @@ static void flatten_group_do(bNodeTree *ntree, bNode *gnode) /* find internal links to this output */ for (bNodeLink *link = glinks_first->next; link != glinks_last->next; link = link->next) { /* only use active output node */ - if (link->tonode->type_legacy == NODE_GROUP_OUTPUT && - (link->tonode->flag & NODE_DO_OUTPUT)) - { + if (link->tonode->is_group_output() && (link->tonode->flag & NODE_DO_OUTPUT)) { if (STREQ(link->tosock->identifier, identifier)) { blender::bke::node_add_link( ntree, link->fromnode, link->fromsock, tlink->tonode, tlink->tosock); @@ -558,7 +553,7 @@ static void ntree_shader_groups_flatten(bNodeTree *localtree) for (bNode *node = static_cast(localtree->nodes.first), *node_next; node; node = node_next) { - if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id != nullptr) { + if (node->is_group() && node->id != nullptr) { flatten_group_do(localtree, node); /* Continue even on new flattened nodes. */ node_next = node->next; diff --git a/source/blender/nodes/shader/nodes/node_shader_common.cc b/source/blender/nodes/shader/nodes/node_shader_common.cc index 4ccbe396b9f..4f7c74d274f 100644 --- a/source/blender/nodes/shader/nodes/node_shader_common.cc +++ b/source/blender/nodes/shader/nodes/node_shader_common.cc @@ -28,7 +28,7 @@ static void group_gpu_copy_inputs(bNode *gnode, GPUNodeStack *in, bNodeStack *gs bNodeTree *ngroup = (bNodeTree *)gnode->id; for (bNode *node : ngroup->all_nodes()) { - if (node->type_legacy == NODE_GROUP_INPUT) { + if (node->is_group_input()) { int a; LISTBASE_FOREACH_INDEX (bNodeSocket *, sock, &node->outputs, a) { bNodeStack *ns = node_get_socket_stack(gstack, sock); diff --git a/source/blender/nodes/texture/node_texture_tree.cc b/source/blender/nodes/texture/node_texture_tree.cc index 1f8ae4de503..f8b329722cf 100644 --- a/source/blender/nodes/texture/node_texture_tree.cc +++ b/source/blender/nodes/texture/node_texture_tree.cc @@ -106,7 +106,7 @@ static void localize(bNodeTree *localtree, bNodeTree * /*ntree*/) for (node = static_cast(localtree->nodes.first); node; node = node_next) { node_next = node->next; - if (node->flag & NODE_MUTED || node->type_legacy == NODE_REROUTE) { + if (node->is_muted() || node->is_reroute()) { blender::bke::node_internal_relink(localtree, node); blender::bke::node_tree_free_local_node(localtree, node); } @@ -200,7 +200,7 @@ bool ntreeExecThreadNodes(bNodeTreeExec *exec, bNodeThreadStack *nts, void *call * If the mute func is not set, assume the node should never be muted, * and hence execute it! */ - if (node->typeinfo->exec_fn && !(node->flag & NODE_MUTED)) { + if (node->typeinfo->exec_fn && !node->is_muted()) { node->typeinfo->exec_fn(callerdata, thread, node, &nodeexec->data, nsin, nsout); } } diff --git a/source/blender/nodes/texture/node_texture_util.cc b/source/blender/nodes/texture/node_texture_util.cc index a69cb361b8e..2f75fd94a84 100644 --- a/source/blender/nodes/texture/node_texture_util.cc +++ b/source/blender/nodes/texture/node_texture_util.cc @@ -120,7 +120,7 @@ void tex_output(bNode *node, { TexDelegate *dg; - if (node->flag & NODE_MUTED) { + if (node->is_muted()) { /* do not add a delegate if the node is muted */ return; } diff --git a/source/blender/nodes/texture/nodes/node_texture_common.cc b/source/blender/nodes/texture/nodes/node_texture_common.cc index 9ae3594a598..8f56f7b1f5a 100644 --- a/source/blender/nodes/texture/nodes/node_texture_common.cc +++ b/source/blender/nodes/texture/nodes/node_texture_common.cc @@ -68,7 +68,7 @@ static void group_copy_inputs(bNode *gnode, bNodeStack **in, bNodeStack *gstack) int a; LISTBASE_FOREACH (bNode *, node, &ngroup->nodes) { - if (node->type_legacy == NODE_GROUP_INPUT) { + if (node->is_group_input()) { for (sock = static_cast(node->outputs.first), a = 0; sock; sock = sock->next, a++) { diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index 3faf3af9622..890e650e842 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -1185,7 +1185,7 @@ static void do_render_compositor_scene(Render *re, Scene *sce, int cfra) * otherwise. */ static Scene *get_scene_referenced_by_node(const bNode *node) { - if (node->flag & NODE_MUTED) { + if (node->is_muted()) { return nullptr; } @@ -1236,13 +1236,13 @@ static bool node_tree_has_composite_output(const bNodeTree *node_tree) } for (const bNode *node : node_tree->all_nodes()) { - if (node->flag & NODE_MUTED) { + if (node->is_muted()) { continue; } if (node->type_legacy == CMP_NODE_COMPOSITE && node->flag & NODE_DO_OUTPUT) { return true; } - if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) { + if (node->is_group() && node->id) { if (node_tree_has_composite_output(reinterpret_cast(node->id))) { return true; } @@ -1628,7 +1628,7 @@ static bool check_valid_compositing_camera(Scene *scene, { if (scene->r.scemode & R_DOCOMP && scene->use_nodes) { for (bNode *node : scene->nodetree->all_nodes()) { - if (node->type_legacy == CMP_NODE_R_LAYERS && (node->flag & NODE_MUTED) == 0) { + if (node->type_legacy == CMP_NODE_R_LAYERS && !node->is_muted()) { Scene *sce = node->id ? (Scene *)node->id : scene; if (sce->camera == nullptr) { sce->camera = BKE_view_layer_camera_find(sce, BKE_view_layer_default_render(sce)); @@ -1742,7 +1742,7 @@ static bool node_tree_has_any_compositor_output(const bNodeTree *ntree) if (ELEM(node->type_legacy, CMP_NODE_COMPOSITE, CMP_NODE_OUTPUT_FILE)) { return true; } - if (ELEM(node->type_legacy, NODE_GROUP, NODE_CUSTOM_GROUP)) { + if (node->is_group()) { if (node->id) { if (node_tree_has_any_compositor_output((const bNodeTree *)node->id)) { return true; From aec72c0a9ae5e4e72c35a7a48af684eb0d9722ca Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:20:42 +0100 Subject: [PATCH 17/37] Fix #97685: very inaccurate mean computation in Attribute Statistic node Numerical instability mainly comes from adding values together which have very different magnitude. There are algorithms to keep the error small like "Kahan Summation", however those are also slower because of additional code in the hot loop. This patch implements a simpler approach that is slightly less accurate, but still seems to solve the cases that people commonly run into while being simpler and faster. The approach is to simply compute a couple partial sums first, and to add those up in the end. The individual partial sums can also be computed in parallel. Care has to be taken to maintain determinism with floating point values. If accuracy is still not enough for some use cases, we can revisit this later and e.g. use doubles or a better summation algorithm. Pull Request: https://projects.blender.org/blender/blender/pulls/132759 --- .../nodes/node_geo_attribute_statistic.cc | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index 8a8e7b7c958..0afbcd766d3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -103,7 +103,25 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) template static T compute_sum(const Span data) { - return std::accumulate(data.begin(), data.end(), T()); + /* Explicitly splitting work into chunks for a couple of reasons: + * - Improve numerical stability. While there are even more stable algorithms (e.g. Kahan + * summation), they also add more complexity to the hot code path. So far, this simple approach + * seems to solve the common issues people run into. + * - Support computing the sum using multiple threads. + * - Ensure deterministic results even with floating point numbers. + */ + constexpr int64_t chunk_size = 1024; + const int64_t chunks_num = divide_ceil_ul(data.size(), chunk_size); + Array partial_sums(chunks_num); + threading::parallel_for(partial_sums.index_range(), 1, [&](const IndexRange range) { + for (const int64_t i : range) { + const int64_t start = i * chunk_size; + const Span chunk = data.slice_safe(start, chunk_size); + const T partial_sum = std::accumulate(chunk.begin(), chunk.end(), T()); + partial_sums[i] = partial_sum; + } + }); + return std::accumulate(partial_sums.begin(), partial_sums.end(), T()); } static float compute_variance(const Span data, const float mean) From 58c923c0bdfbdf9b7b68514be6509d41f2b71ac1 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:10:43 +0100 Subject: [PATCH 18/37] Fix #133177: incorrect handling of shared layers in mesh validation --- source/blender/blenkernel/BKE_customdata.hh | 7 +++++++ source/blender/blenkernel/intern/mesh_validate.cc | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/source/blender/blenkernel/BKE_customdata.hh b/source/blender/blenkernel/BKE_customdata.hh index a4f83343d02..93e3bdd1efe 100644 --- a/source/blender/blenkernel/BKE_customdata.hh +++ b/source/blender/blenkernel/BKE_customdata.hh @@ -329,6 +329,13 @@ void CustomData_set_only_copy(const CustomData *data, eCustomDataMask mask); * Copies data from one CustomData object to another * objects need not be compatible, each source layer is copied to the * first dest layer of correct type (if there is none, the layer is skipped). + * + * NOTE: It's expected that the destination layers are mutable + * (#CustomData_ensure_layers_are_mutable). These copy-functions could ensure that internally, but + * that would cause additional overhead when copying few elements at a time. It would also be + * necessary to pass the total size of the destination layers as parameter if to make them mutable + * though. In most cases, these functions are used right after creating a new geometry, in which + * case there are no shared layers anyway. */ void CustomData_copy_data( const CustomData *source, CustomData *dest, int source_index, int dest_index, int count); diff --git a/source/blender/blenkernel/intern/mesh_validate.cc b/source/blender/blenkernel/intern/mesh_validate.cc index 23d339ff391..b73d5933a0f 100644 --- a/source/blender/blenkernel/intern/mesh_validate.cc +++ b/source/blender/blenkernel/intern/mesh_validate.cc @@ -1164,6 +1164,9 @@ bool BKE_mesh_validate_material_indices(Mesh *mesh) void strip_loose_faces_corners(Mesh *mesh, blender::BitSpan faces_to_remove) { + /* Ensure layers are mutable so that #CustomData_copy_data can be used. */ + CustomData_ensure_layers_are_mutable(&mesh->face_data, mesh->faces_num); + MutableSpan face_offsets = mesh->face_offsets_for_write(); MutableSpan corner_edges = mesh->corner_edges_for_write(); @@ -1238,6 +1241,9 @@ void strip_loose_faces_corners(Mesh *mesh, blender::BitSpan faces_to_remove) void mesh_strip_edges(Mesh *mesh) { + /* Ensure layers are mutable so that #CustomData_copy_data can be used. */ + CustomData_ensure_layers_are_mutable(&mesh->edge_data, mesh->edges_num); + blender::int2 *e; int a, b; uint *new_idx = (uint *)MEM_mallocN(sizeof(int) * mesh->edges_num, __func__); From 947330e529de0ab6630d7ccd5aad46b0f3237ca5 Mon Sep 17 00:00:00 2001 From: Jaime Torres Date: Fri, 17 Jan 2025 12:30:47 +0100 Subject: [PATCH 19/37] Fix #129849: Remove redundant 'GreasePencil' references in PaintMode As established in issue #129849 , there are references to older versions of Grease Pencil that aren't needed. This PR is meant to remove such references or, in places where otherwise `GreasePencil` is referenced but `GPencil` is not, it changes the reference as for it to reference the v3 implementations in PaintMode. It also removes `GreasePencil` from the list of options when declaring `PaintMode`. Pull Request: https://projects.blender.org/blender/blender/pulls/131065 --- source/blender/blenkernel/BKE_paint.hh | 4 +--- source/blender/blenkernel/intern/paint.cc | 17 ++--------------- .../grease_pencil/intern/grease_pencil_modes.cc | 4 ++-- .../sculpt_paint/grease_pencil_draw_ops.cc | 3 ++- .../editors/sculpt_paint/paint_cursor.cc | 1 - .../blender/editors/sculpt_paint/paint_curve.cc | 3 --- .../editors/sculpt_paint/paint_stroke.cc | 2 +- source/blender/makesrna/intern/rna_brush.cc | 1 - 8 files changed, 8 insertions(+), 27 deletions(-) diff --git a/source/blender/blenkernel/BKE_paint.hh b/source/blender/blenkernel/BKE_paint.hh index e7204b4f0d3..6cdc37d0c0e 100644 --- a/source/blender/blenkernel/BKE_paint.hh +++ b/source/blender/blenkernel/BKE_paint.hh @@ -105,11 +105,9 @@ enum class PaintMode : int8_t { WeightGPencil = 9, /** Curves. */ SculptCurves = 10, - /** Grease Pencil. */ - SculptGreasePencil = 11, /** Keep last. */ - Invalid = 12, + Invalid = 11, }; /* overlay invalidation */ diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index a164663babd..681a1d212f7 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -355,9 +355,6 @@ bool BKE_paint_ensure_from_paintmode(Scene *sce, PaintMode mode) case PaintMode::SculptCurves: paint_ptr = (Paint **)&ts->curves_sculpt; break; - case PaintMode::SculptGreasePencil: - paint_ptr = (Paint **)&ts->gp_sculptpaint; - break; case PaintMode::Invalid: break; } @@ -393,8 +390,6 @@ Paint *BKE_paint_get_active_from_paintmode(Scene *sce, PaintMode mode) return &ts->gp_weightpaint->paint; case PaintMode::SculptCurves: return &ts->curves_sculpt->paint; - case PaintMode::SculptGreasePencil: - return &ts->gp_sculptpaint->paint; case PaintMode::Invalid: return nullptr; default: @@ -426,8 +421,6 @@ const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(const PaintMode m case PaintMode::WeightGPencil: return rna_enum_brush_gpencil_weight_types_items; case PaintMode::SculptCurves: - return rna_enum_brush_curves_sculpt_brush_type_items; - case PaintMode::SculptGreasePencil: return rna_enum_brush_gpencil_sculpt_types_items; case PaintMode::Invalid: break; @@ -529,7 +522,7 @@ PaintMode BKE_paintmode_get_active_from_context(const bContext *C) return PaintMode::Sculpt; case OB_MODE_SCULPT_GREASE_PENCIL: if (obact->type == OB_GREASE_PENCIL) { - return PaintMode::SculptGreasePencil; + return PaintMode::SculptGPencil; } return PaintMode::Invalid; case OB_MODE_PAINT_GREASE_PENCIL: @@ -586,7 +579,7 @@ PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref) case CTX_MODE_PAINT_GREASE_PENCIL: return PaintMode::GPencil; case CTX_MODE_SCULPT_GREASE_PENCIL: - return PaintMode::SculptGreasePencil; + return PaintMode::SculptGPencil; } } else if (tref->space_type == SPACE_IMAGE) { @@ -1266,8 +1259,6 @@ uint BKE_paint_get_brush_type_offset_from_paintmode(const PaintMode mode) return offsetof(Brush, gpencil_weight_brush_type); case PaintMode::SculptCurves: return offsetof(Brush, curves_sculpt_brush_type); - case PaintMode::SculptGreasePencil: - return offsetof(Brush, gpencil_sculpt_brush_type); case PaintMode::Invalid: break; /* We don't use these yet. */ } @@ -1325,8 +1316,6 @@ std::optional BKE_paint_get_brush_type_from_paintmode(const Brush *brush, return brush->gpencil_weight_brush_type; case PaintMode::SculptCurves: return brush->curves_sculpt_brush_type; - case PaintMode::SculptGreasePencil: - return brush->gpencil_sculpt_brush_type; case PaintMode::Invalid: default: return {}; @@ -1666,8 +1655,6 @@ eObjectMode BKE_paint_object_mode_from_paintmode(const PaintMode mode) return OB_MODE_SCULPT_CURVES; case PaintMode::GPencil: return OB_MODE_PAINT_GREASE_PENCIL; - case PaintMode::SculptGreasePencil: - return OB_MODE_SCULPT_GREASE_PENCIL; case PaintMode::Invalid: default: return OB_MODE_OBJECT; diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_modes.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_modes.cc index 3fa787b25bd..0c2d61479f8 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_modes.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_modes.cc @@ -179,8 +179,8 @@ static int sculptmode_toggle_exec(bContext *C, wmOperator *op) } else { Scene *scene = CTX_data_scene(C); - BKE_paint_init(bmain, scene, PaintMode::SculptGreasePencil, PAINT_CURSOR_SCULPT_GREASE_PENCIL); - Paint *paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::SculptGreasePencil); + BKE_paint_init(bmain, scene, PaintMode::SculptGPencil, PAINT_CURSOR_SCULPT_GREASE_PENCIL); + Paint *paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::SculptGPencil); ED_paint_cursor_start(paint, sculpt_poll_view3d); mode = OB_MODE_SCULPT_GREASE_PENCIL; } diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc index 5ab88859f17..fdeb91ff74d 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -116,7 +116,8 @@ static std::unique_ptr get_stroke_operation(bContex return greasepencil::new_tint_operation(); } } - else if (mode == PaintMode::SculptGreasePencil) { + else if (mode == PaintMode::SculptGPencil) { + if (stroke_mode == BRUSH_STROKE_SMOOTH) { return greasepencil::new_smooth_operation(stroke_mode, true); } diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index 8a509d9ea56..7996c2290c9 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -1229,7 +1229,6 @@ static bool paint_use_2d_cursor(PaintMode mode) case PaintMode::SculptGPencil: case PaintMode::WeightGPencil: case PaintMode::SculptCurves: - case PaintMode::SculptGreasePencil: case PaintMode::GPencil: return true; case PaintMode::Invalid: diff --git a/source/blender/editors/sculpt_paint/paint_curve.cc b/source/blender/editors/sculpt_paint/paint_curve.cc index 7ab812d2d71..080919e7898 100644 --- a/source/blender/editors/sculpt_paint/paint_curve.cc +++ b/source/blender/editors/sculpt_paint/paint_curve.cc @@ -702,9 +702,6 @@ static int paintcurve_draw_exec(bContext *C, wmOperator * /*op*/) case PaintMode::GPencil: name = "GREASE_PENCIL_OT_brush_stroke"; break; - case PaintMode::SculptGreasePencil: - name = "GREASE_PENCIL_OT_sculpt_paint"; - break; default: return OPERATOR_PASS_THROUGH; } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.cc b/source/blender/editors/sculpt_paint/paint_stroke.cc index 1b73ec18899..0a8efd2f530 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.cc +++ b/source/blender/editors/sculpt_paint/paint_stroke.cc @@ -1058,7 +1058,7 @@ bool paint_space_stroke_enabled(const Brush &br, const PaintMode mode) return false; } - if (ELEM(mode, PaintMode::GPencil, PaintMode::SculptGreasePencil)) { + if (ELEM(mode, PaintMode::GPencil, PaintMode::SculptGPencil)) { /* No spacing needed for now. */ return false; } diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index 9226d1e6c8e..689c3e8f6d8 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -964,7 +964,6 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C, return rna_enum_dummy_DEFAULT_items; } case PaintMode::SculptGPencil: - case PaintMode::SculptGreasePencil: switch (me->gpencil_sculpt_brush_type) { case GPSCULPT_BRUSH_TYPE_THICKNESS: case GPSCULPT_BRUSH_TYPE_STRENGTH: From e3d83806fdecc2e4736060ad43ae03a1c1d5090e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 12:52:02 +0100 Subject: [PATCH 20/37] Fix: use delete instead of free --- source/blender/blenkernel/intern/blendfile.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index c8496cc7305..9a82450d6fa 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -1427,7 +1427,7 @@ UserDef *BKE_blendfile_userdef_read(const char *filepath, ReportList *reports) userdef = bfd->user; } BKE_main_free(bfd->main); - MEM_freeN(bfd); + MEM_delete(bfd); } return userdef; From 782a4c9d85cea1c2d856606d0276af6a7412a98f Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Fri, 17 Jan 2025 13:39:26 +0100 Subject: [PATCH 21/37] Fix #133194: Grease Pencil Vertex Weight Angle influence vertexgroup has no effect The weights were just not used at all. Stumbled over this checking on #133055 Pull Request: https://projects.blender.org/blender/blender/pulls/133195 --- .../modifiers/intern/MOD_grease_pencil_weight_angle.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_weight_angle.cc b/source/blender/modifiers/intern/MOD_grease_pencil_weight_angle.cc index 8f8211c2d89..33d42c6755a 100644 --- a/source/blender/modifiers/intern/MOD_grease_pencil_weight_angle.cc +++ b/source/blender/modifiers/intern/MOD_grease_pencil_weight_angle.cc @@ -135,7 +135,7 @@ static void write_weights_for_drawing(const ModifierData &md, BLI_assert(!dst_weights.span.is_empty()); - const VArray input_weights = modifier::greasepencil::get_influence_vertex_weights( + const VArray influence_weights = modifier::greasepencil::get_influence_vertex_weights( curves, mmd.influence); /* Use default Z up. */ @@ -164,6 +164,11 @@ static void write_weights_for_drawing(const ModifierData &md, return; } for (const int point : points.drop_front(1)) { + const float influence_weight = influence_weights[point]; + if (influence_weight <= 0.0f) { + continue; + } + const float3 p1 = math::transform_point(obmat3x3, positions[point]); const float3 p2 = math::transform_point(obmat3x3, positions[point - 1]); const float3 vec = p2 - p1; @@ -177,6 +182,7 @@ static void write_weights_for_drawing(const ModifierData &md, dst_weights.span[point] = (mmd.flag & MOD_GREASE_PENCIL_WEIGHT_ANGLE_MULTIPLY_DATA) ? dst_weights.span[point] * weight : weight; + dst_weights.span[point] *= influence_weight; dst_weights.span[point] = math::clamp(dst_weights.span[point], mmd.min_weight, 1.0f); } /* First point has the same weight as the second one. */ From 43b41984ae84733472ecbb8cc2d58f60d3477779 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 17 Jan 2025 14:13:14 +0100 Subject: [PATCH 22/37] Fix: use MEM_delete instead of MEM_free This was missing in 1151d82df3fa94a3. --- source/blender/blenkernel/intern/blendfile.cc | 4 ++-- source/blender/blenloader/intern/undofile.cc | 2 +- source/blender/editors/render/render_preview.cc | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 9a82450d6fa..0dd9a3fefb0 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -1447,7 +1447,7 @@ UserDef *BKE_blendfile_userdef_read_from_memory(const void *file_buf, userdef = bfd->user; } BKE_main_free(bfd->main); - MEM_freeN(bfd); + MEM_delete(bfd); } else { BKE_reports_prepend(reports, "Loading failed: "); @@ -1684,7 +1684,7 @@ WorkspaceConfigFileData *BKE_blendfile_workspace_config_read(const char *filepat workspace_config->workspaces = bfd->main->workspaces; } - MEM_freeN(bfd); + MEM_delete(bfd); } return workspace_config; diff --git a/source/blender/blenloader/intern/undofile.cc b/source/blender/blenloader/intern/undofile.cc index bd26f4468ac..505260dc031 100644 --- a/source/blender/blenloader/intern/undofile.cc +++ b/source/blender/blenloader/intern/undofile.cc @@ -180,7 +180,7 @@ Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene) *r_scene = bfd->curscene; } - MEM_freeN(bfd); + MEM_delete(bfd); } return bmain_undo; diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc index bf0fa5ae886..b9d6628b08c 100644 --- a/source/blender/editors/render/render_preview.cc +++ b/source/blender/editors/render/render_preview.cc @@ -179,8 +179,7 @@ static Main *load_main_from_memory(const void *blend, int blend_size) bfd = BLO_read_from_memory(blend, blend_size, BLO_READ_SKIP_NONE, nullptr); if (bfd) { bmain = bfd->main; - - MEM_freeN(bfd); + MEM_delete(bfd); } G.fileflags = fileflags; From 390ca01685b6c074610354ed7395b2e38e371885 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 17 Jan 2025 14:46:22 +0100 Subject: [PATCH 23/37] Cleanup: Vulkan: Remove resource ownership Images used to be tracked with ownership in order to reset swap chain images to its original layout. This isn't used anymore as we always mark them in VK_IMAGE_LAYOUT_UNDEFINED to make the first pipeline barrier a nop. This change reduces unneeded complexity and safe a few CPU cycles. Pull Request: https://projects.blender.org/blender/blender/pulls/133197 --- .../tests/vk_render_graph_test_present.cc | 9 ++- .../tests/vk_render_graph_test_render.cc | 6 +- .../tests/vk_render_graph_test_scheduler.cc | 30 ++++----- .../tests/vk_render_graph_test_transfer.cc | 8 +-- .../vulkan/render_graph/vk_command_builder.cc | 2 - .../vulkan/render_graph/vk_render_graph.hh | 2 +- .../render_graph/vk_resource_state_tracker.cc | 19 +----- .../render_graph/vk_resource_state_tracker.hh | 64 +------------------ source/blender/gpu/vulkan/vk_context.cc | 6 +- source/blender/gpu/vulkan/vk_texture.cc | 6 +- 10 files changed, 29 insertions(+), 123 deletions(-) diff --git a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_present.cc b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_present.cc index eea72d9b34c..3eb14b62749 100644 --- a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_present.cc +++ b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_present.cc @@ -14,8 +14,7 @@ TEST_F(VKRenderGraphTestPresent, transfer_and_present) { VkHandle back_buffer(1u); - resources.add_image( - back_buffer, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, ResourceOwner::SWAP_CHAIN); + resources.add_image(back_buffer, 1); render_graph->submit_for_present(back_buffer); @@ -25,7 +24,7 @@ TEST_F(VKRenderGraphTestPresent, transfer_and_present) "dst_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT" + endl() + " - image_barrier(src_access_mask=, dst_access_mask=, " - "old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " @@ -38,7 +37,7 @@ TEST_F(VKRenderGraphTestPresent, clear_and_present) { VkHandle back_buffer(1u); - resources.add_image(back_buffer, 1, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ResourceOwner::SWAP_CHAIN); + resources.add_image(back_buffer, 1); VKClearColorImageNode::CreateInfo clear_color_image = {}; clear_color_image.vk_image = back_buffer; @@ -53,7 +52,7 @@ TEST_F(VKRenderGraphTestPresent, clear_and_present) "dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" + endl() + " - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, " - "old_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " diff --git a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_render.cc b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_render.cc index d5c60fcb922..19118f41d9a 100644 --- a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_render.cc +++ b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_render.cc @@ -16,7 +16,7 @@ TEST_P(VKRenderGraphTestRender, begin_clear_attachments_end_read_back) VkHandle image_view(2u); VkHandle buffer(3u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(image, 1); resources.add_buffer(buffer); { @@ -143,7 +143,7 @@ TEST_P(VKRenderGraphTestRender, begin_draw_end) VkHandle pipeline_layout(4u); VkHandle pipeline(3u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(image, 1); { VKResourceAccessInfo access_info = {}; @@ -224,7 +224,7 @@ TEST_P(VKRenderGraphTestRender, begin_draw_end__layered) VkHandle pipeline_layout(4u); VkHandle pipeline(3u); - resources.add_image(image, 2, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(image, 2); { VKResourceAccessInfo access_info = {}; diff --git a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_scheduler.cc b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_scheduler.cc index 794c7d3962d..9dea535d63f 100644 --- a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_scheduler.cc +++ b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_scheduler.cc @@ -18,7 +18,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_rendering_copy_buffer_end_rendering) VkHandle buffer_src(3u); VkHandle buffer_dst(4u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ResourceOwner::SWAP_CHAIN); + resources.add_image(image, 1); resources.add_buffer(buffer_src); resources.add_buffer(buffer_dst); @@ -74,7 +74,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_rendering_copy_buffer_end_rendering) endl() + " - image_barrier(src_access_mask=, " "dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, " - "old_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=" + color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " @@ -117,7 +117,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_clear_attachments_copy_buffer_end) VkHandle buffer_src(3u); VkHandle buffer_dst(4u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ResourceOwner::SWAP_CHAIN); + resources.add_image(image, 1); resources.add_buffer(buffer_src); resources.add_buffer(buffer_dst); @@ -188,7 +188,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_clear_attachments_copy_buffer_end) endl() + " - image_barrier(src_access_mask=, " "dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, " - "old_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=" + color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " @@ -238,7 +238,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_copy_buffer_clear_attachments_end) VkHandle buffer_src(3u); VkHandle buffer_dst(4u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ResourceOwner::SWAP_CHAIN); + resources.add_image(image, 1); resources.add_buffer(buffer_src); resources.add_buffer(buffer_dst); @@ -309,7 +309,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_copy_buffer_clear_attachments_end) endl() + " - image_barrier(src_access_mask=, " "dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, " - "old_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=" + color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " @@ -359,7 +359,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_clear_attachments_copy_buffer_clear_att VkHandle buffer_src(3u); VkHandle buffer_dst(4u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ResourceOwner::SWAP_CHAIN); + resources.add_image(image, 1); resources.add_buffer(buffer_src); resources.add_buffer(buffer_dst); @@ -447,7 +447,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_clear_attachments_copy_buffer_clear_att endl() + " - image_barrier(src_access_mask=, " "dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, " - "old_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, " + "old_layout=VK_IMAGE_LAYOUT_UNDEFINED, " "new_layout=" + color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() + " aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, " @@ -512,10 +512,8 @@ TEST_P(VKRenderGraphTestScheduler, begin_draw_copy_framebuffer_draw_end) VkHandle pipeline_layout_background(6u); VkHandle pipeline_background(7u); - resources.add_image( - image_attachment, 1, VK_IMAGE_LAYOUT_UNDEFINED, render_graph::ResourceOwner::APPLICATION); - resources.add_image( - image_feedback, 1, VK_IMAGE_LAYOUT_UNDEFINED, render_graph::ResourceOwner::APPLICATION); + resources.add_image(image_attachment, 1); + resources.add_image(image_feedback, 1); { VKResourceAccessInfo access_info = {}; @@ -698,7 +696,7 @@ TEST_P(VKRenderGraphTestScheduler, begin_update_draw_update_draw_update_draw_end VkHandle pipeline_layout(5u); VkHandle pipeline(6u); - resources.add_image(image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(image, 1); resources.add_buffer(buffer_a); resources.add_buffer(buffer_b); @@ -926,10 +924,8 @@ TEST_P(VKRenderGraphTestScheduler, begin_draw_copy_to_attachment_draw_end) VkHandle pipeline_layout(4u); VkHandle pipeline(5u); - resources.add_image( - image_attachment, 1, VK_IMAGE_LAYOUT_UNDEFINED, render_graph::ResourceOwner::APPLICATION); - resources.add_image( - image_editor, 1, VK_IMAGE_LAYOUT_UNDEFINED, render_graph::ResourceOwner::APPLICATION); + resources.add_image(image_attachment, 1); + resources.add_image(image_editor, 1); { VKResourceAccessInfo access_info = {}; diff --git a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_transfer.cc b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_transfer.cc index 5fd49420f80..1570e696899 100644 --- a/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_transfer.cc +++ b/source/blender/gpu/vulkan/render_graph/tests/vk_render_graph_test_transfer.cc @@ -104,8 +104,8 @@ TEST_F(VKRenderGraphTestTransfer, clear_clear_copy_and_read_back) VkHandle dst_image(2u); VkHandle staging_buffer(3u); - resources.add_image(src_image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); - resources.add_image(dst_image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(src_image, 1); + resources.add_image(dst_image, 1); resources.add_buffer(staging_buffer); VkClearColorValue color_white = {}; color_white.float32[0] = 1.0f; @@ -245,8 +245,8 @@ TEST_F(VKRenderGraphTestTransfer, clear_blit_copy_and_read_back) VkHandle dst_image(2u); VkHandle staging_buffer(3u); - resources.add_image(src_image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); - resources.add_image(dst_image, 1, VK_IMAGE_LAYOUT_UNDEFINED, ResourceOwner::APPLICATION); + resources.add_image(src_image, 1); + resources.add_image(dst_image, 1); resources.add_buffer(staging_buffer); VkClearColorValue color_black = {}; color_black.float32[0] = 0.0f; diff --git a/source/blender/gpu/vulkan/render_graph/vk_command_builder.cc b/source/blender/gpu/vulkan/render_graph/vk_command_builder.cc index 8973c174c27..e66f9b66e01 100644 --- a/source/blender/gpu/vulkan/render_graph/vk_command_builder.cc +++ b/source/blender/gpu/vulkan/render_graph/vk_command_builder.cc @@ -23,8 +23,6 @@ void VKCommandBuilder::build_nodes(VKRenderGraph &render_graph, VKCommandBufferInterface &command_buffer, Span node_handles) { - /* Swap chain images layouts needs to be reset as the image layouts are changed externally. */ - render_graph.resources_.reset_image_layouts(); groups_init(render_graph, node_handles); groups_extract_barriers( render_graph, node_handles, command_buffer.use_dynamic_rendering_local_read); diff --git a/source/blender/gpu/vulkan/render_graph/vk_render_graph.hh b/source/blender/gpu/vulkan/render_graph/vk_render_graph.hh index ba848ce84c7..c66d37f4c7c 100644 --- a/source/blender/gpu/vulkan/render_graph/vk_render_graph.hh +++ b/source/blender/gpu/vulkan/render_graph/vk_render_graph.hh @@ -230,7 +230,7 @@ class VKRenderGraph : public NonCopyable { * swap chain swap. * * Pre conditions: - * - `vk_swapchain_image` needs to be a created using ResourceOwner::SWAP_CHAIN`. + * - `vk_swapchain_image` needs to be registered in VKResourceStateTracker. * * Post conditions: * - `vk_swapchain_image` layout is transitioned to `VK_IMAGE_LAYOUT_SRC_PRESENT`. diff --git a/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.cc b/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.cc index 5c95d83d543..7098ddc53c0 100644 --- a/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.cc +++ b/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.cc @@ -30,11 +30,7 @@ ResourceHandle VKResourceStateTracker::create_resource_slot() return handle; } -void VKResourceStateTracker::add_image(VkImage vk_image, - uint32_t layer_count, - VkImageLayout vk_image_layout, - ResourceOwner owner, - const char *name) +void VKResourceStateTracker::add_image(VkImage vk_image, uint32_t layer_count, const char *name) { UNUSED_VARS_NDEBUG(name); BLI_assert_msg(!image_resources_.contains(vk_image), @@ -45,10 +41,8 @@ void VKResourceStateTracker::add_image(VkImage vk_image, image_resources_.add_new(vk_image, handle); resource.type = VKResourceType::IMAGE; - resource.owner = owner; resource.image.vk_image = vk_image; resource.image.layer_count = layer_count; - resource.image.vk_image_layout = vk_image_layout; resource.stamp = 0; #ifndef NDEBUG resource.name = name; @@ -70,7 +64,6 @@ void VKResourceStateTracker::add_buffer(VkBuffer vk_buffer, const char *name) buffer_resources_.add_new(vk_buffer, handle); resource.type = VKResourceType::BUFFER; - resource.owner = ResourceOwner::APPLICATION; resource.buffer.vk_buffer = vk_buffer; resource.stamp = 0; #ifndef NDEBUG @@ -159,16 +152,6 @@ ResourceWithStamp VKResourceStateTracker::get_image(VkImage vk_image) const return get_stamp(handle, resource); } -void VKResourceStateTracker::reset_image_layouts() -{ - for (ResourceHandle image_handle : image_resources_.values()) { - VKResourceStateTracker::Resource &resource = resources_.lookup(image_handle); - if (resource.owner == ResourceOwner::SWAP_CHAIN) { - resource.reset_image_layout(); - } - } -} - #ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION void VKResourceStateTracker::validate() const { diff --git a/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.hh b/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.hh index bb673ae0893..82c2d94ea5d 100644 --- a/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.hh +++ b/source/blender/gpu/vulkan/render_graph/vk_resource_state_tracker.hh @@ -69,28 +69,6 @@ struct ResourceWithStamp { enum class VKResourceType { NONE = (0 << 0), IMAGE = (1 << 0), BUFFER = (1 << 1) }; ENUM_OPERATORS(VKResourceType, VKResourceType::BUFFER); -/** - * Resources can have deviations in its lifetime based on who owns it. - */ -enum class ResourceOwner { - /** - * Resource is owned by Blender. - * - * These resources can be destroyed internally by Blender. - * - * NOTE: Most resources are application owned. - */ - APPLICATION, - - /** - * Resource is owned by a swap chain. - * - * These resources cannot be destroyed, could be recreated externally and its layout can be - * modified outside our context. - */ - SWAP_CHAIN, -}; - /** * State being tracked for a resource. */ @@ -146,23 +124,12 @@ class VKResourceStateTracker { VkImage vk_image = VK_NULL_HANDLE; /** Number of layers that the resource has. */ uint32_t layer_count = 0; - - /** - * Original image layout when the resource was added to the state tracker. - * - * It is used to reset the state tracker to its original state when working with swap chain - * images. See `reset_image_layout`. - */ - VkImageLayout vk_image_layout = VK_IMAGE_LAYOUT_UNDEFINED; } image; }; /** Current modification stamp of the resource. */ ModificationStamp stamp = 0; - /** Who owns the resource. */ - ResourceOwner owner = ResourceOwner::APPLICATION; - /** * State tracking to ensure correct pipeline barriers and command creation. */ @@ -172,20 +139,6 @@ class VKResourceStateTracker { const char *name; #endif - /** - * Reset the image layout to its original layout. - * - * The layout of swap chain images are externally managed. When they are used again we need to - * ensure the correct state. - * - * NOTE: Also needed when for other external images (Cycles, OpenXR, multi device). - */ - void reset_image_layout() - { - BLI_assert(type == VKResourceType::IMAGE); - barrier_state.image_layout = image.vk_image_layout; - } - /** * Check if the given resource handle has multiple layers. * @@ -236,11 +189,7 @@ class VKResourceStateTracker { * When an image is created in VKTexture, it needs to be registered in the device resources so * the resource state can be tracked during its lifetime. */ - void add_image(VkImage vk_image, - uint32_t layer_count, - VkImageLayout vk_image_layout, - ResourceOwner owner, - const char *name = nullptr); + void add_image(VkImage vk_image, uint32_t layer_count, const char *name = nullptr); /** * Remove an registered image. @@ -302,17 +251,6 @@ class VKResourceStateTracker { */ ResourceWithStamp get_image(VkImage vk_image) const; - /** - * Reset the swap chain image layouts to its original layout. - * - * The layout of swap chain images are externally managed. When they are reused we need to - * ensure the correct state. - * - * NOTE: This is also needed when working with external memory (Cycles, OpenXR, multi device - * rendering). - */ - void reset_image_layouts(); - /** Get the resource type for the given handle. */ VKResourceType resource_type_get(ResourceHandle resource_handle) const { diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index b9c1eb04c1d..3f17f3c2a77 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -353,11 +353,7 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c * chain image as device resources. When we move towards GPU swap chain synchronization we need * to keep track of the swap chain image between frames. */ VKDevice &device = VKBackend::get().device; - device.resources.add_image(swap_chain_data.image, - 1, - VK_IMAGE_LAYOUT_UNDEFINED, - render_graph::ResourceOwner::SWAP_CHAIN, - "SwapchainImage"); + device.resources.add_image(swap_chain_data.image, 1, "SwapchainImage"); framebuffer.rendering_end(*this); render_graph.add_node(blit_image); diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 4cb41ea93b4..028ff4636c8 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -553,11 +553,7 @@ bool VKTexture::allocate() } debug::object_label(vk_image_, name_); - device.resources.add_image(vk_image_, - image_info.arrayLayers, - VK_IMAGE_LAYOUT_UNDEFINED, - render_graph::ResourceOwner::APPLICATION, - name_); + device.resources.add_image(vk_image_, image_info.arrayLayers, name_); return result == VK_SUCCESS; } From 063267f459564c981a58583e0b69ffd2cf977800 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Jan 2025 14:49:09 +0100 Subject: [PATCH 24/37] Fix #132900: Blender error after editing and saving a brush asset. The handling of library for IDs added to a PartiaWriteContext in 'make local' mode (i.e. to make them local in the written blendfile) was flacky, leading to invalid removal of the ID name from the library ID namemap in G_MAIN. Now simply esure there is a local copy of the library too when adding an ID to the context, even if it will be made local there. --- source/blender/blenkernel/intern/blendfile.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 0dd9a3fefb0..f0f1efd57b0 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -1797,6 +1797,11 @@ ID *PartialWriteContext::id_add_copy(const ID *id, const bool regenerate_session LIB_ID_COPY_ASSET_METADATA); ctx_root_id = BKE_id_copy_in_lib(nullptr, id->lib, id, std::nullopt, nullptr, copy_flags); ctx_root_id->tag |= ID_TAG_TEMP_MAIN; + /* Ensure that the newly copied ID has a library in temp local bmain if it was linked. + * While this could be optimized out in case the ID is made local in the context, this adds + * complexity as default ID management code like 'make local' code will create invalid bmain + * namemap data. */ + this->ensure_library(ctx_root_id); if (regenerate_session_uid) { /* Calling #BKE_lib_libblock_session_uid_renew is not needed here, copying already generated a * new one. */ @@ -2019,9 +2024,6 @@ ID *PartialWriteContext::id_add( if (do_make_local) { this->make_local(ctx_id, make_local_flags); } - else { - this->ensure_library(ctx_id); - } } return ctx_root_id; From 14e788e42e4fab4c780ff89d3401de6a617654b5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Jan 2025 15:20:20 +0100 Subject: [PATCH 25/37] BKE: PartialWriteContext: Add concept of library duplicates. Check done by PartiaWriteContext writing code to ensure there is no library ID written which filepath is the same as the destination blendfile path of the context, was flacky in case there would be multiple library IDs with that same path. While this is not expected situation currently, it will likely change in the future, so handle that properly, and generate a CLOG warning. --- source/blender/blenkernel/intern/blendfile.cc | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index f0f1efd57b0..665ecc6a912 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -32,6 +32,7 @@ #include "BLI_system.h" #include "BLI_time.h" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "BLI_vector_set.hh" #include "BLT_translation.hh" @@ -2164,17 +2165,26 @@ bool PartialWriteContext::write(const char *write_filepath, /* In case the write path is the same as one of the libraries used by this context, make this * library local, and delete it (and all of its potentially remaining linked data). */ - Library *make_local_lib = nullptr; + blender::Vector make_local_libs; LISTBASE_FOREACH (Library *, library, &this->bmain.libraries) { if (STREQ(write_filepath, library->runtime.filepath_abs)) { - make_local_lib = library; + make_local_libs.append(library); } } - if (make_local_lib) { - BKE_library_make_local(&this->bmain, make_local_lib, nullptr, false, false, false); - BKE_id_delete(&this->bmain, make_local_lib); - make_local_lib = nullptr; + /* Will likely change in the near future (embedded linked IDs, virtual libraries...), but + * currently this should never happen. */ + if (make_local_libs.size() > 1) { + CLOG_WARN(&LOG_PARTIALWRITE, + "%ld libraries found using the same filepath as destination one ('%s'), should " + "never happen.", + make_local_libs.size(), + write_filepath); } + for (Library *lib : make_local_libs) { + BKE_library_make_local(&this->bmain, lib, nullptr, false, false, false); + BKE_id_delete(&this->bmain, lib); + } + make_local_libs.clear(); BLI_assert(this->is_valid()); From d9536fe878f0e2d7923f30fbd744d214a5bb0bfc Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Fri, 17 Jan 2025 16:27:28 +0100 Subject: [PATCH 26/37] Cleanup: anim_motion_paths.cc No functional changes. This patch just changes the comments to start with a capital letter and end with a dot. Pull Request: https://projects.blender.org/blender/blender/pulls/133209 --- .../editors/animation/anim_motion_paths.cc | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/source/blender/editors/animation/anim_motion_paths.cc b/source/blender/editors/animation/anim_motion_paths.cc index 0940af0e6e0..080c5821020 100644 --- a/source/blender/editors/animation/anim_motion_paths.cc +++ b/source/blender/editors/animation/anim_motion_paths.cc @@ -40,26 +40,26 @@ static CLG_LogRef LOG = {"ed.anim.motion_paths"}; -/* Motion path needing to be baked (mpt) */ +/* Motion path needing to be baked (mpt). */ struct MPathTarget { MPathTarget *next, *prev; - bMotionPath *mpath; /* motion path in question */ + bMotionPath *mpath; /* Motion path in question. */ - AnimKeylist *keylist; /* temp, to know where the keyframes are */ + AnimKeylist *keylist; /* Temp, to know where the keyframes are. */ /* Original (Source Objects) */ - Object *ob; /* source object */ - bPoseChannel *pchan; /* source pose-channel (if applicable) */ + Object *ob; /* Source Object */ + bPoseChannel *pchan; /* Source pose-channel (if applicable). */ /* "Evaluated" Copies (these come from the background evaluated copy * that provide all the coordinates we want to save off). */ - Object *ob_eval; /* evaluated object */ + Object *ob_eval; /* Evaluated Object. */ }; /* ........ */ -/* update scene for current frame */ +/* Update scene for current frame. */ static void motionpaths_calc_update_scene(Depsgraph *depsgraph) { BKE_scene_graph_update_for_newframe(depsgraph); @@ -97,9 +97,9 @@ void animviz_get_object_motionpaths(Object *ob, ListBase *targets) MPathTarget *mpt; - /* object itself first */ + /* Object itself first. */ if ((ob->avs.recalc & ANIMVIZ_RECALC_PATHS) && (ob->mpath)) { - /* new target for object */ + /* New target for object. */ mpt = static_cast(MEM_callocN(sizeof(MPathTarget), "MPathTarget Ob")); BLI_addtail(targets, mpt); @@ -107,12 +107,12 @@ void animviz_get_object_motionpaths(Object *ob, ListBase *targets) mpt->ob = ob; } - /* bones */ + /* Bones. */ if ((ob->pose) && (ob->pose->avs.recalc & ANIMVIZ_RECALC_PATHS)) { bArmature *arm = static_cast(ob->data); LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { if ((pchan->bone) && ANIM_bonecoll_is_visible_pchan(arm, pchan) && (pchan->mpath)) { - /* new target for bone */ + /* New target for bone. */ mpt = static_cast(MEM_callocN(sizeof(MPathTarget), "MPathTarget PoseBone")); BLI_addtail(targets, mpt); @@ -126,25 +126,25 @@ void animviz_get_object_motionpaths(Object *ob, ListBase *targets) /* ........ */ -/* perform baking for the targets on the current frame */ +/* Perform baking for the targets on the current frame. */ static void motionpaths_calc_bake_targets(ListBase *targets, int cframe, Depsgraph *depsgraph, Object *camera) { using namespace blender; - /* for each target, check if it can be baked on the current frame */ + /* For each target, check if it can be baked on the current frame. */ LISTBASE_FOREACH (MPathTarget *, mpt, targets) { bMotionPath *mpath = mpt->mpath; - /* current frame must be within the range the cache works for - * - is inclusive of the first frame, but not the last otherwise we get buffer overruns + /* Current frame must be within the range the cache works for. + * - is inclusive of the first frame, but not the last otherwise we get buffer overruns. */ if ((cframe < mpath->start_frame) || (cframe >= mpath->end_frame)) { continue; } - /* get the relevant cache vert to write to */ + /* Get the relevant cache vert to write to. */ bMotionPathVert *mpv = mpath->points + (cframe - mpath->start_frame); Object *ob_eval = mpt->ob_eval; @@ -156,9 +156,9 @@ static void motionpaths_calc_bake_targets(ListBase *targets, pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, mpt->pchan->name); } - /* pose-channel or object path baking? */ + /* Pose-channel or object path baking? */ if (pchan_eval) { - /* heads or tails */ + /* Heads or tails. */ if (mpath->flag & MOTIONPATH_FLAG_BHEAD) { copy_v3_v3(mpv->co, pchan_eval->pose_head); } @@ -183,7 +183,7 @@ static void motionpaths_calc_bake_targets(ListBase *targets, float mframe = float(cframe); - /* Tag if it's a keyframe */ + /* Tag if it's a keyframe. */ if (ED_keylist_find_exact(mpt->keylist, mframe)) { mpv->flag |= MOTIONPATH_VERT_KEY; } @@ -433,8 +433,8 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, break; } - /* get copies of objects/bones to get the calculated results from - * (for copy-on-evaluation), so that we actually get some results + /* Get copies of objects/bones to get the calculated results from + * (for copy-on-evaluation), so that we actually get some results. */ /* TODO: Create a copy of background depsgraph that only contain these entities, @@ -455,16 +455,16 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, AnimData *adt = BKE_animdata_from_id(&mpt->ob_eval->id); - /* build list of all keyframes in active action for object or pchan */ + /* Build list of all keyframes in active action for object or pchan. */ mpt->keylist = ED_keylist_create(); ListBase *fcurve_list = nullptr; if (adt) { - /* get pointer to animviz settings for each target */ + /* Get pointer to animviz settings for each target. */ bAnimVizSettings *avs = animviz_target_settings_get(mpt); - /* it is assumed that keyframes for bones are all grouped in a single group - * unless an option is set to always use the whole action + /* It is assumed that keyframes for bones are all grouped in a single group + * unless an option is set to always use the whole action. */ if ((mpt->pchan) && (avs->path_viewflag & MOTIONPATH_VIEW_KFACT) == 0) { bActionGroup *agrp = BKE_action_group_find_name(adt->action, mpt->pchan->name); @@ -496,7 +496,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, return; } - /* calculate path over requested range */ + /* Calculate path over requested range. */ CLOG_INFO(&LOG, 1, "Calculating MotionPaths between frames %d - %d (%d frames)", @@ -513,11 +513,11 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, motionpaths_calc_update_scene(depsgraph); } - /* perform baking for targets */ + /* Perform baking for targets. */ motionpaths_calc_bake_targets(targets, scene->r.cfra, depsgraph, scene->camera); } - /* reset original environment */ + /* Reset original environment. */ /* NOTE: We don't always need to reevaluate the main scene, as the depsgraph * may be a temporary one that works on a subset of the data. * We always have to restore the current frame though. */ @@ -530,17 +530,17 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, DEG_make_active(depsgraph); } - /* clear recalc flags from targets */ + /* Clear recalc flags from targets. */ LISTBASE_FOREACH (MPathTarget *, mpt, targets) { bMotionPath *mpath = mpt->mpath; - /* get pointer to animviz settings for each target */ + /* Get pointer to animviz settings for each target. */ bAnimVizSettings *avs = animviz_target_settings_get(mpt); - /* clear the flag requesting recalculation of targets */ + /* Clear the flag requesting recalculation of targets. */ avs->recalc &= ~ANIMVIZ_RECALC_PATHS; - /* Clean temp data */ + /* Clean temp data. */ ED_keylist_free(mpt->keylist); /* Free previous batches to force update. */ From 20332e89a9b8e6aff0a204ddbcfc4f732bbe5ff8 Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Fri, 17 Jan 2025 16:30:32 +0100 Subject: [PATCH 27/37] Selection: Improve Display as Bounds/Wires selection behavior Make the selection behavior more visually consistent in Render mode. Pull Request: https://projects.blender.org/blender/blender/pulls/133007 --- source/blender/draw/engines/overlay/overlay_next_prepass.hh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/overlay/overlay_next_prepass.hh b/source/blender/draw/engines/overlay/overlay_next_prepass.hh index 7319dde5afd..24d43689397 100644 --- a/source/blender/draw/engines/overlay/overlay_next_prepass.hh +++ b/source/blender/draw/engines/overlay/overlay_next_prepass.hh @@ -190,7 +190,11 @@ class Prepass : Overlay { Resources &res, const State &state) final { - if (!enabled_ || ob_ref.object->dt < OB_SOLID) { + bool is_solid = ob_ref.object->dt >= OB_SOLID || + (state.v3d->shading.type == OB_RENDER && + !(ob_ref.object->visibility_flag & OB_HIDE_CAMERA)); + + if (!enabled_ || !is_solid) { return; } From 8117e1981a65cdb487abfd9a60b72827918baf51 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 17 Jan 2025 16:56:21 +0100 Subject: [PATCH 28/37] Cleanup: Fix invalid doxygen format in RNA path comment Some doxygen outputs would become invalid with this, e.g. XML output would be `index to use when index_dim > 0`, so the closing tags were placed in invalid order. --- source/blender/makesrna/RNA_path.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesrna/RNA_path.hh b/source/blender/makesrna/RNA_path.hh index 552bf6e8f8c..8bfa7338ad5 100644 --- a/source/blender/makesrna/RNA_path.hh +++ b/source/blender/makesrna/RNA_path.hh @@ -259,7 +259,7 @@ std::string RNA_path_from_ptr_to_property_index(const PointerRNA *ptr, int index); /** * \param index_dim: The dimension to show, 0 disables. 1 for 1d array, 2 for 2d. etc. - * \param index: The *flattened* index to use when \a `index_dim > 0`, + * \param index: The *flattened* index to use when \a index_dim > 0, * this is expanded when used with multi-dimensional arrays. */ std::optional RNA_path_from_ID_to_property_index(const PointerRNA *ptr, From 3f763521495849a9189ac4cc39225586e5095625 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Fri, 17 Jan 2025 17:11:03 +0100 Subject: [PATCH 29/37] Refactor: extract utility functions from brush asset code No functional changes intended. Some functionality from the brush asset system will be reused by the pose library. To avoid duplicating code, the relevant functions are extracted to a common place. All functions are moved as is, except for `visit_library_catalogs_catalog_for_search`. For that I changed the `bUserAssetLibrary` argument to a `AssetLibraryReference`. That is because in the follow up PR I am using this function with non user libraries as well. This is a refactor PR extracted from #132747. To get a full picture of the use case see that PR. Part of #131840 Pull Request: https://projects.blender.org/blender/blender/pulls/132857 --- source/blender/editors/asset/CMakeLists.txt | 1 + .../blender/editors/asset/ED_asset_library.hh | 28 ++++ .../asset/intern/asset_library_utils.cc | 93 +++++++++++++ .../editors/asset/intern/asset_ui_utils.cc | 36 +++++ source/blender/editors/include/ED_asset.hh | 16 +++ .../editors/sculpt_paint/brush_asset_ops.cc | 130 +++--------------- 6 files changed, 190 insertions(+), 114 deletions(-) create mode 100644 source/blender/editors/asset/intern/asset_library_utils.cc diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index d8c6a98b6d2..360fba5a6f5 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -21,6 +21,7 @@ set(SRC intern/asset_handle.cc intern/asset_import.cc intern/asset_indexer.cc + intern/asset_library_utils.cc intern/asset_library_reference_enum.cc intern/asset_list.cc intern/asset_mark_clear.cc diff --git a/source/blender/editors/asset/ED_asset_library.hh b/source/blender/editors/asset/ED_asset_library.hh index 6566c907c25..61a207520c5 100644 --- a/source/blender/editors/asset/ED_asset_library.hh +++ b/source/blender/editors/asset/ED_asset_library.hh @@ -10,7 +10,16 @@ #include "DNA_asset_types.h" +struct bUserAssetLibrary; +struct bContext; +struct AssetLibraryReference; struct EnumPropertyItem; +struct StringPropertySearchVisitParams; + +namespace blender::asset_system { +class AssetCatalog; +class AssetCatalogPath; +} // namespace blender::asset_system namespace blender::ed::asset { @@ -38,4 +47,23 @@ AssetLibraryReference library_reference_from_enum_value(int value); */ const EnumPropertyItem *library_reference_to_rna_enum_itemf(bool include_generated); +/** + * Find the catalog with the given path in the library. Creates it in case it doesn't exist. + */ +blender::asset_system::AssetCatalog &library_ensure_catalogs_in_path( + blender::asset_system::AssetLibrary &library, + const blender::asset_system::AssetCatalogPath &path); + +/** + * May return a nullptr if the given AssetLibraryReference is not a user library. + */ +const bUserAssetLibrary *library_ref_to_user_library(const AssetLibraryReference &library_ref); +AssetLibraryReference user_library_to_library_ref(const bUserAssetLibrary &user_library); + +/** + * Call after changes to an asset library have been made to reflect the changes in the UI. + */ +void refresh_asset_library(const bContext *C, const AssetLibraryReference &library_ref); +void refresh_asset_library(const bContext *C, const bUserAssetLibrary &user_library); + } // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_library_utils.cc b/source/blender/editors/asset/intern/asset_library_utils.cc new file mode 100644 index 00000000000..59819ff0f6c --- /dev/null +++ b/source/blender/editors/asset/intern/asset_library_utils.cc @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#include "BKE_context.hh" +#include "BKE_preferences.h" + +#include "ED_asset_library.hh" +#include "ED_asset_list.hh" +#include "ED_asset_shelf.hh" + +#include "BLI_listbase.h" +#include "BLI_string_ref.hh" + +#include "DNA_userdef_types.h" +#include "RNA_access.hh" +#include "UI_resources.hh" +#include "WM_api.hh" + +#include "AS_asset_catalog.hh" +#include "AS_asset_catalog_tree.hh" +#include "AS_asset_library.hh" + +namespace blender::ed::asset { + +static asset_system::AssetCatalog &library_ensure_catalog( + asset_system::AssetLibrary &library, const asset_system::AssetCatalogPath &path) +{ + if (asset_system::AssetCatalog *catalog = library.catalog_service().find_catalog_by_path(path)) { + return *catalog; + } + return *library.catalog_service().create_catalog(path); +} + +/* Suppress warning for GCC-14.2. This isn't a dangling reference + * because the #asset_system::AssetLibrary owns the returned value. */ +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdangling-reference" +#endif + +blender::asset_system::AssetCatalog &library_ensure_catalogs_in_path( + asset_system::AssetLibrary &library, const blender::asset_system::AssetCatalogPath &path) +{ + /* Adding multiple catalogs in a path at a time with #AssetCatalogService::create_catalog() + * doesn't work; add each potentially new catalog in the hierarchy manually here. */ + asset_system::AssetCatalogPath parent = ""; + path.iterate_components([&](StringRef component_name, bool /*is_last_component*/) { + library_ensure_catalog(library, parent / component_name); + parent = parent / component_name; + }); + return *library.catalog_service().find_catalog_by_path(path); +} + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + +AssetLibraryReference user_library_to_library_ref(const bUserAssetLibrary &user_library) +{ + AssetLibraryReference library_ref{}; + library_ref.custom_library_index = BLI_findindex(&U.asset_libraries, &user_library); + library_ref.type = ASSET_LIBRARY_CUSTOM; + return library_ref; +} + +const bUserAssetLibrary *library_ref_to_user_library(const AssetLibraryReference &library_ref) +{ + if (library_ref.type != ASSET_LIBRARY_CUSTOM) { + return nullptr; + } + return static_cast( + BLI_findlink(&U.asset_libraries, library_ref.custom_library_index)); +} + +void refresh_asset_library(const bContext *C, const AssetLibraryReference &library_ref) +{ + asset::list::clear(&library_ref, C); + /* TODO: Should the all library reference be automatically cleared? */ + AssetLibraryReference all_lib_ref = asset_system::all_library_reference(); + asset::list::clear(&all_lib_ref, C); +} + +void refresh_asset_library(const bContext *C, const bUserAssetLibrary &user_library) +{ + refresh_asset_library(C, user_library_to_library_ref(user_library)); +} + +} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_ui_utils.cc b/source/blender/editors/asset/intern/asset_ui_utils.cc index 30fac75e90e..7189136725d 100644 --- a/source/blender/editors/asset/intern/asset_ui_utils.cc +++ b/source/blender/editors/asset/intern/asset_ui_utils.cc @@ -8,9 +8,14 @@ #include +#include "AS_asset_library.hh" #include "AS_asset_representation.hh" +#include "BKE_preferences.h" +#include "DNA_userdef_types.h" #include "ED_asset.hh" +#include "RNA_access.hh" +#include "UI_resources.hh" namespace blender::ed::asset { @@ -30,4 +35,35 @@ std::string asset_tooltip(const asset_system::AssetRepresentation &asset, const return complete_string; } +const bUserAssetLibrary *get_asset_library_from_opptr(PointerRNA &ptr) +{ + const int enum_value = RNA_enum_get(&ptr, "asset_library_reference"); + const AssetLibraryReference lib_ref = asset::library_reference_from_enum_value(enum_value); + return BKE_preferences_asset_library_find_index(&U, lib_ref.custom_library_index); +} + +void visit_library_catalogs_catalog_for_search( + const Main &bmain, + const AssetLibraryReference lib, + const StringRef edit_text, + const FunctionRef visit_fn) +{ + const asset_system::AssetLibrary *library = AS_asset_library_load(&bmain, lib); + if (!library) { + return; + } + + if (!edit_text.is_empty()) { + const asset_system::AssetCatalogPath edit_path = edit_text; + if (!library->catalog_service().find_catalog_by_path(edit_path)) { + visit_fn(StringPropertySearchVisitParams{edit_path.str(), std::nullopt, ICON_ADD}); + } + } + + const asset_system::AssetCatalogTree &full_tree = library->catalog_service().catalog_tree(); + full_tree.foreach_item([&](const asset_system::AssetCatalogTreeItem &item) { + visit_fn(StringPropertySearchVisitParams{item.catalog_path().str(), std::nullopt}); + }); +} + } // namespace blender::ed::asset diff --git a/source/blender/editors/include/ED_asset.hh b/source/blender/editors/include/ED_asset.hh index c90ddd6a5e8..8df563573dc 100644 --- a/source/blender/editors/include/ED_asset.hh +++ b/source/blender/editors/include/ED_asset.hh @@ -27,6 +27,8 @@ #include "../asset/ED_asset_import.hh" // IWYU pragma: export #include "../asset/ED_asset_list.hh" // IWYU pragma: export +struct PointerRNA; + namespace blender::ed::asset { std::string asset_tooltip(const asset_system::AssetRepresentation &asset, @@ -34,4 +36,18 @@ std::string asset_tooltip(const asset_system::AssetRepresentation &asset, void operatortypes_asset(); +/** + * The PointerRNA is expected to have an enum called "asset_library_reference". + */ +const bUserAssetLibrary *get_asset_library_from_opptr(PointerRNA &ptr); + +/** + * For each catalog of the given bUserAssetLibrary call `visit_fn`. + */ +void visit_library_catalogs_catalog_for_search( + const Main &bmain, + const AssetLibraryReference lib, + const StringRef edit_text, + const FunctionRef visit_fn); + } // namespace blender::ed::asset diff --git a/source/blender/editors/sculpt_paint/brush_asset_ops.cc b/source/blender/editors/sculpt_paint/brush_asset_ops.cc index 00ec87b5064..aaf4a25227a 100644 --- a/source/blender/editors/sculpt_paint/brush_asset_ops.cc +++ b/source/blender/editors/sculpt_paint/brush_asset_ops.cc @@ -29,6 +29,7 @@ #include "RNA_access.hh" #include "RNA_define.hh" +#include "ED_asset.hh" #include "ED_asset_handle.hh" #include "ED_asset_library.hh" #include "ED_asset_list.hh" @@ -37,7 +38,6 @@ #include "ED_asset_shelf.hh" #include "UI_interface_icons.hh" -#include "UI_resources.hh" #include "BLT_translation.hh" @@ -106,37 +106,6 @@ static std::optional library_to_library_ref( return std::nullopt; } -static AssetLibraryReference user_library_to_library_ref(const bUserAssetLibrary &user_library) -{ - AssetLibraryReference library_ref{}; - library_ref.custom_library_index = BLI_findindex(&U.asset_libraries, &user_library); - library_ref.type = ASSET_LIBRARY_CUSTOM; - return library_ref; -} - -static const bUserAssetLibrary *library_ref_to_user_library( - const AssetLibraryReference &library_ref) -{ - if (library_ref.type != ASSET_LIBRARY_CUSTOM) { - return nullptr; - } - return static_cast( - BLI_findlink(&U.asset_libraries, library_ref.custom_library_index)); -} - -static void refresh_asset_library(const bContext *C, const AssetLibraryReference &library_ref) -{ - asset::list::clear(&library_ref, C); - /* TODO: Should the all library reference be automatically cleared? */ - AssetLibraryReference all_lib_ref = asset_system::all_library_reference(); - asset::list::clear(&all_lib_ref, C); -} - -static void refresh_asset_library(const bContext *C, const bUserAssetLibrary &user_library) -{ - refresh_asset_library(C, user_library_to_library_ref(user_library)); -} - static bool brush_asset_save_as_poll(bContext *C) { Paint *paint = BKE_paint_get_active_from_context(C); @@ -158,45 +127,6 @@ static bool brush_asset_save_as_poll(bContext *C) return true; } -static const bUserAssetLibrary *get_asset_library_from_prop(PointerRNA &ptr) -{ - const int enum_value = RNA_enum_get(&ptr, "asset_library_reference"); - const AssetLibraryReference lib_ref = asset::library_reference_from_enum_value(enum_value); - return BKE_preferences_asset_library_find_index(&U, lib_ref.custom_library_index); -} - -static asset_system::AssetCatalog &asset_library_ensure_catalog( - asset_system::AssetLibrary &library, const asset_system::AssetCatalogPath &path) -{ - if (asset_system::AssetCatalog *catalog = library.catalog_service().find_catalog_by_path(path)) { - return *catalog; - } - return *library.catalog_service().create_catalog(path); -} - -/* Suppress warning for GCC-14.2. This isn't a dangling reference - * because the #asset_system::AssetLibrary owns the returned value. */ -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdangling-reference" -#endif - -static asset_system::AssetCatalog &asset_library_ensure_catalogs_in_path( - asset_system::AssetLibrary &library, const asset_system::AssetCatalogPath &path) -{ - /* Adding multiple catalogs in a path at a time with #AssetCatalogService::create_catalog() - * doesn't work; add each potentially new catalog in the hierarchy manually here. */ - asset_system::AssetCatalogPath parent = ""; - path.iterate_components([&](StringRef component_name, bool /*is_last_component*/) { - asset_library_ensure_catalog(library, parent / component_name); - parent = parent / component_name; - }); - return *library.catalog_service().find_catalog_by_path(path); -} -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif - static int brush_asset_save_as_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -213,13 +143,13 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op) STRNCPY(name, brush->id.name + 2); } - const bUserAssetLibrary *user_library = get_asset_library_from_prop(*op->ptr); + const bUserAssetLibrary *user_library = asset::get_asset_library_from_opptr(*op->ptr); if (!user_library) { return OPERATOR_CANCELLED; } asset_system::AssetLibrary *library = AS_asset_library_load( - bmain, user_library_to_library_ref(*user_library)); + bmain, asset::user_library_to_library_ref(*user_library)); if (!library) { BKE_report(op->reports, RPT_ERROR, "Failed to load asset library"); return OPERATOR_CANCELLED; @@ -238,7 +168,7 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op) AssetMetaData &meta_data = *brush->id.asset_data; if (catalog_path[0]) { - const asset_system::AssetCatalog &catalog = asset_library_ensure_catalogs_in_path( + const asset_system::AssetCatalog &catalog = asset::library_ensure_catalogs_in_path( *library, catalog_path); BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str()); } @@ -262,7 +192,7 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_WARNING, "Unable to activate just-saved brush asset"); } - refresh_asset_library(C, *user_library); + asset::refresh_asset_library(C, *user_library); WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_ADDED, nullptr); WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush); @@ -304,7 +234,7 @@ static int brush_asset_save_as_invoke(bContext *C, wmOperator *op, const wmEvent asset::library_reference_to_enum_value(&*library_ref)); } else { - const AssetLibraryReference first_library = user_library_to_library_ref( + const AssetLibraryReference first_library = asset::user_library_to_library_ref( *static_cast(U.asset_libraries.first)); RNA_enum_set(op->ptr, "asset_library_reference", @@ -338,31 +268,6 @@ static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext * /*C* return items; } -static void visit_library_catalogs_catalog_for_search( - const Main &bmain, - const bUserAssetLibrary &user_library, - const StringRef edit_text, - const FunctionRef visit_fn) -{ - const asset_system::AssetLibrary *library = AS_asset_library_load( - &bmain, user_library_to_library_ref(user_library)); - if (!library) { - return; - } - - if (!edit_text.is_empty()) { - const asset_system::AssetCatalogPath edit_path = edit_text; - if (!library->catalog_service().find_catalog_by_path(edit_path)) { - visit_fn(StringPropertySearchVisitParams{edit_path.str(), std::nullopt, ICON_ADD}); - } - } - - const asset_system::AssetCatalogTree &full_tree = library->catalog_service().catalog_tree(); - full_tree.foreach_item([&](const asset_system::AssetCatalogTreeItem &item) { - visit_fn(StringPropertySearchVisitParams{item.catalog_path().str(), std::nullopt}); - }); -} - static void visit_library_prop_catalogs_catalog_for_search_fn( const bContext *C, PointerRNA *ptr, @@ -371,9 +276,9 @@ static void visit_library_prop_catalogs_catalog_for_search_fn( FunctionRef visit_fn) { /* NOTE: Using the all library would also be a valid choice. */ - if (const bUserAssetLibrary *user_library = get_asset_library_from_prop(*ptr)) { - visit_library_catalogs_catalog_for_search( - *CTX_data_main(C), *user_library, edit_text, visit_fn); + if (const bUserAssetLibrary *user_library = asset::get_asset_library_from_opptr(*ptr)) { + asset::visit_library_catalogs_catalog_for_search( + *CTX_data_main(C), asset::user_library_to_library_ref(*user_library), edit_text, visit_fn); } } @@ -428,7 +333,7 @@ static int brush_asset_edit_metadata_exec(bContext *C, wmOperator *op) meta_data.description = RNA_string_get_alloc(op->ptr, "description", nullptr, 0, nullptr); if (catalog_path[0]) { - const asset_system::AssetCatalog &catalog = asset_library_ensure_catalogs_in_path( + const asset_system::AssetCatalog &catalog = asset::library_ensure_catalogs_in_path( *library, catalog_path); BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str()); } @@ -448,7 +353,7 @@ static int brush_asset_edit_metadata_exec(bContext *C, wmOperator *op) library->catalog_service().write_to_disk(file_path); - refresh_asset_library(C, library_ref); + asset::refresh_asset_library(C, library_ref); WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr); return OPERATOR_FINISHED; @@ -499,11 +404,8 @@ static void visit_active_library_catalogs_catalog_for_search_fn( const asset_system::AssetLibrary &library = asset->owner_asset_library(); /* NOTE: Using the all library would also be a valid choice. */ - visit_library_catalogs_catalog_for_search( - *CTX_data_main(C), - *library_ref_to_user_library(*library_to_library_ref(library)), - edit_text, - visit_fn); + asset::visit_library_catalogs_catalog_for_search( + *CTX_data_main(C), *library_to_library_ref(library), edit_text, visit_fn); } static bool brush_asset_edit_metadata_poll(bContext *C) @@ -590,7 +492,7 @@ static int brush_asset_load_preview_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - refresh_asset_library(C, library_ref); + asset::refresh_asset_library(C, library_ref); WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr); return OPERATOR_FINISHED; @@ -658,7 +560,7 @@ static int brush_asset_delete_exec(bContext *C, wmOperator *op) BKE_paint_brush_set_default(bmain, paint); if (library) { - refresh_asset_library(C, *library); + asset::refresh_asset_library(C, *library); } WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_REMOVED, nullptr); @@ -737,7 +639,7 @@ static int brush_asset_save_exec(bContext *C, wmOperator *op) bke::asset_edit_id_save(*bmain, brush->id, *op->reports); brush->has_unsaved_changes = false; - refresh_asset_library(C, *user_library); + asset::refresh_asset_library(C, *user_library); WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr); WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush); From 0fd3f3c2168e0614c776ace0dc00990109f90431 Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Fri, 17 Jan 2025 17:56:49 +0100 Subject: [PATCH 30/37] Fix #133207: Grease Pencil Opacity modifier "Use Weight As Factor" wrong Two things not behaving as in GPv2: - points outside the influence vertexgroup were getting zero opacity (as opposed to 1.0 in GPv2) - Opacity Factor was multiplied in (even though it shouldnt and is rightfully greyed out) I assume the a misunderstanding in c02f3c94d948. Pull Request: https://projects.blender.org/blender/blender/pulls/133208 --- .../modifiers/intern/MOD_grease_pencil_opacity.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_opacity.cc b/source/blender/modifiers/intern/MOD_grease_pencil_opacity.cc index c92c1153cd6..b4386bb3919 100644 --- a/source/blender/modifiers/intern/MOD_grease_pencil_opacity.cc +++ b/source/blender/modifiers/intern/MOD_grease_pencil_opacity.cc @@ -89,6 +89,11 @@ static void modify_stroke_color(const GreasePencilOpacityModifierData &omd, curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) { const IndexRange points = points_by_curve[curve_i]; for (const int64_t point_i : points) { + const float vgroup_weight = vgroup_weights[point_i]; + if (vgroup_weight <= 0.0f) { + continue; + } + const float curve_input = points.size() >= 2 ? (float(point_i - points.first()) / float(points.size() - 1)) : 0.0f; @@ -101,12 +106,10 @@ static void modify_stroke_color(const GreasePencilOpacityModifierData &omd, } else if (use_weight_as_factor) { /* Use vertex group weights as opacity factors. */ - opacities.span[point_i] = std::clamp( - omd.color_factor * curve_factor * vgroup_weights[point_i], 0.0f, 1.0f); + opacities.span[point_i] = std::clamp(curve_factor * vgroup_weight, 0.0f, 1.0f); } else { /* Use vertex group weights as influence factors. */ - const float vgroup_weight = vgroup_weights[point_i]; const float vgroup_influence = invert_vertex_group ? 1.0f - vgroup_weight : vgroup_weight; opacities.span[point_i] = std::clamp( opacities.span[point_i] + (omd.color_factor * curve_factor - 1.0f) * vgroup_influence, @@ -141,7 +144,11 @@ static void modify_fill_color(const GreasePencilOpacityModifierData &omd, if (use_vgroup_opacity) { /* Use the first stroke point as vertex weight. */ const IndexRange points = points_by_curve[curve_i]; - const float stroke_weight = points.is_empty() ? 1.0f : vgroup_weights[points.first()]; + const float vgroup_weight_first = vgroup_weights[points.first()]; + float stroke_weight = vgroup_weight_first; + if (points.is_empty() || (vgroup_weight_first <= 0.0f)) { + stroke_weight = 1.0f; + } const float stroke_influence = invert_vertex_group ? 1.0f - stroke_weight : stroke_weight; fill_opacities.span[curve_i] = std::clamp(stroke_influence, 0.0f, 1.0f); From 434f94b2542ce5ab4722472facde8010ec3f3205 Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Fri, 17 Jan 2025 18:00:43 +0100 Subject: [PATCH 31/37] Fix #133211: Grease pencil Smooth modifier ignores influence vertexgroup To resolve, multiply the modifier `Factor` by influence vertex groups weights (if a vertexgroup is used). Pull Request: https://projects.blender.org/blender/blender/pulls/133213 --- .../intern/MOD_grease_pencil_smooth.cc | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_smooth.cc b/source/blender/modifiers/intern/MOD_grease_pencil_smooth.cc index 9fefca3a707..a7becbae355 100644 --- a/source/blender/modifiers/intern/MOD_grease_pencil_smooth.cc +++ b/source/blender/modifiers/intern/MOD_grease_pencil_smooth.cc @@ -128,6 +128,24 @@ static void deform_drawing(const ModifierData &md, const VArray cyclic = curves.cyclic(); const VArray point_selection = VArray::ForSingle(true, curves.points_num()); + VArray influences; + const bool use_influence_vertex_group = mmd.influence.vertex_group_name[0] != '\0'; + if (use_influence_vertex_group) { + const VArray vgroup_weights = modifier::greasepencil::get_influence_vertex_weights( + curves, mmd.influence); + Array vgroup_weights_factored(vgroup_weights.size()); + threading::parallel_for( + vgroup_weights_factored.index_range(), 4096, [&](const IndexRange range) { + for (const int i : range) { + vgroup_weights_factored[i] = vgroup_weights[i] * influence; + } + }); + influences = VArray::ForContainer(vgroup_weights_factored); + } + else { + influences = VArray::ForSingle(influence, curves.points_num()); + } + if (smooth_position) { bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position"); geometry::smooth_curve_attribute(strokes, @@ -135,7 +153,7 @@ static void deform_drawing(const ModifierData &md, point_selection, cyclic, iterations, - influence, + influences, smooth_ends, keep_shape, positions.span); @@ -149,7 +167,7 @@ static void deform_drawing(const ModifierData &md, point_selection, cyclic, iterations, - influence, + influences, smooth_ends, false, opacities.span); @@ -162,7 +180,7 @@ static void deform_drawing(const ModifierData &md, point_selection, cyclic, iterations, - influence, + influences, smooth_ends, false, radii.span); @@ -176,7 +194,7 @@ static void deform_drawing(const ModifierData &md, point_selection, cyclic, iterations, - influence, + influences, smooth_ends, false, rotation.span); From 89efa94a2d8d14bfa29f6f8bb094f8e18b68b0f1 Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Fri, 17 Jan 2025 18:45:26 +0100 Subject: [PATCH 32/37] Fix #132198: Remove license dialog from the msi installer This also removes the component selection, made sense when you could install both blender and the game-engine player individually but since the game-engine got removed the dialog is a bit strange, as if one were to turn any of the components off you'd end up with a non-functional blender. The wix documentation [1] has the details on how/why the license dialog skip works. [1] https://wixtoolset.org/docs/v3/wixui/wixui_customizations/#changing-the-ui-sequence-of-a-built-in-dialog-set Pull Request: https://projects.blender.org/blender/blender/pulls/132308 --- build_files/cmake/packaging.cmake | 7 +- .../windows/installer_wix/WixUI_Blender.wxs | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 release/windows/installer_wix/WixUI_Blender.wxs diff --git a/build_files/cmake/packaging.cmake b/build_files/cmake/packaging.cmake index 244e1110625..03dffd98085 100644 --- a/build_files/cmake/packaging.cmake +++ b/build_files/cmake/packaging.cmake @@ -91,7 +91,9 @@ if(WIN32) set(CPACK_NSIS_MUI_ICON ${CMAKE_SOURCE_DIR}/release/windows/icons/winblender.ico) set(CPACK_NSIS_COMPRESSOR "/SOLID lzma") - + + # Eventhough we no longer display this, we still need to set it otherwise it'll throw an error + # during the msi build. set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/release/license/spdx/GPL-3.0-or-later.txt) set(CPACK_WIX_PRODUCT_ICON ${CMAKE_SOURCE_DIR}/release/windows/icons/winblender.ico) @@ -106,7 +108,8 @@ if(WIN32) set(CPACK_WIX_TEMPLATE ${CMAKE_SOURCE_DIR}/release/windows/installer_wix/WIX.template) set(CPACK_WIX_UI_BANNER ${CMAKE_SOURCE_DIR}/release/windows/installer_wix/WIX_UI_BANNER.bmp) set(CPACK_WIX_UI_DIALOG ${CMAKE_SOURCE_DIR}/release/windows/installer_wix/WIX_UI_DIALOG.bmp) - + set(CPACK_WIX_EXTRA_SOURCES ${CMAKE_SOURCE_DIR}/release/windows/installer_wix/WixUI_Blender.wxs) + set(CPACK_WIX_UI_REF "WixUI_Blender") set(CPACK_WIX_LIGHT_EXTRA_FLAGS -dcl:medium) endif() diff --git a/release/windows/installer_wix/WixUI_Blender.wxs b/release/windows/installer_wix/WixUI_Blender.wxs new file mode 100644 index 00000000000..cb34be5d878 --- /dev/null +++ b/release/windows/installer_wix/WixUI_Blender.wxs @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + "1"]]> + + 1 + + NOT Installed + Installed AND PATCH + + 1 + 1 + NOT WIXUI_DONTVALIDATEPATH + "1"]]> + WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" + 1 + 1 + + NOT Installed + Installed AND NOT PATCH + Installed AND PATCH + + 1 + + 1 + 1 + 1 + + + + + + + From b802c328b54387c782216c404f728010b4f0f18a Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Jan 2025 19:52:45 +0100 Subject: [PATCH 33/37] Refactor: Tests: bl_blendfile: Use unittest module instead of asserts. Pull Request: https://projects.blender.org/blender/blender/pulls/133217 --- tests/python/bl_blendfile_io.py | 66 +- tests/python/bl_blendfile_liblink.py | 641 +++++++++--------- .../python/bl_blendfile_library_overrides.py | 436 ++++++------ tests/python/bl_blendfile_relationships.py | 45 +- tests/python/bl_blendfile_utils.py | 9 +- 5 files changed, 603 insertions(+), 594 deletions(-) diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py index 0f1a738b702..537630ba8ae 100644 --- a/tests/python/bl_blendfile_io.py +++ b/tests/python/bl_blendfile_io.py @@ -14,7 +14,7 @@ class TestBlendFileSaveLoadBasic(TestHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_save_load(self): bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) @@ -35,7 +35,7 @@ def test_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data 1") # We have orphaned data, which should be removed by file reading, so there should not be equality here. - assert orig_data != read_data + self.assertNotEqual(orig_data, read_data) bpy.data.orphans_purge() @@ -46,7 +46,7 @@ def test_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data 2") - assert orig_data == read_data + self.assertEqual(orig_data, read_data) class TestBlendFileSavePartial(TestHelper): @@ -56,7 +56,7 @@ class TestBlendFileSavePartial(TestHelper): UNUSED_MESH_NAME = "UnusedMesh" def __init__(self, args): - self.args = args + super().__init__(args) def test_save_load(self): bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) @@ -70,10 +70,10 @@ def test_save_load(self): unused_mesh = bpy.data.meshes.new(self.UNUSED_MESH_NAME) unused_mesh.materials.append(ob_material) - assert ob_mesh.users == 1 - assert ob_material.users == 2 - assert ob.users == 1 - assert unused_mesh.users == 0 + self.assertEqual(ob_mesh.users, 1) + self.assertEqual(ob_material.users, 2) + self.assertEqual(ob.users, 1) + self.assertEqual(unused_mesh.users, 0) output_dir = self.args.output_dir self.ensure_path(output_dir) @@ -84,15 +84,15 @@ def test_save_load(self): bpy.data.libraries.write(filepath=output_path, datablocks={ob, unused_mesh}, fake_user=False) bpy.ops.wm.open_mainfile(filepath=output_path, load_ui=False) - assert self.OBJECT_MESH_NAME in bpy.data.meshes - assert self.OBJECT_MATERIAL_NAME in bpy.data.materials - assert self.OBJECT_NAME in bpy.data.objects - assert self.UNUSED_MESH_NAME in bpy.data.meshes + self.assertIn(self.OBJECT_MESH_NAME, bpy.data.meshes) + self.assertIn(self.OBJECT_MATERIAL_NAME, bpy.data.materials) + self.assertIn(self.OBJECT_NAME, bpy.data.objects) + self.assertIn(self.UNUSED_MESH_NAME, bpy.data.meshes) - assert bpy.data.meshes[self.OBJECT_MESH_NAME].users == 1 - assert bpy.data.materials[self.OBJECT_MATERIAL_NAME].users == 2 - assert bpy.data.objects[self.OBJECT_NAME].users == 0 - assert bpy.data.meshes[self.UNUSED_MESH_NAME].users == 0 + self.assertEqual(bpy.data.meshes[self.OBJECT_MESH_NAME].users, 1) + self.assertEqual(bpy.data.materials[self.OBJECT_MATERIAL_NAME].users, 2) + self.assertEqual(bpy.data.objects[self.OBJECT_NAME].users, 0) + self.assertEqual(bpy.data.meshes[self.UNUSED_MESH_NAME].users, 0) # NOTE: Technically this should rather be in `bl_id_management.py` test, but that file uses `unittest` module, @@ -101,7 +101,7 @@ def test_save_load(self): class TestIdRuntimeTag(TestHelper): def __init__(self, args): - self.args = args + super().__init__(args) def unique_blendfile_name(self, base_name): return base_name + self.__class__.__name__ + ".blend" @@ -112,26 +112,26 @@ def test_basics(self): bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data is False - assert bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data + self.assertFalse(obj.is_runtime_data) + self.assertTrue(bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data) output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile")) bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data is False + self.assertFalse(obj.is_runtime_data) obj.is_runtime_data = True - assert obj.is_runtime_data + self.assertTrue(obj.is_runtime_data) bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) - assert 'Cube' not in bpy.data.objects + self.assertNotIn('Cube', bpy.data.objects) mesh = bpy.data.meshes['Cube'] - assert mesh.is_runtime_data is False - assert mesh.users == 0 + self.assertFalse(mesh.is_runtime_data) + self.assertEqual(mesh.users, 0) def test_linking(self): output_dir = self.args.output_dir @@ -151,21 +151,21 @@ def test_linking(self): bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data is False + self.assertFalse(obj.is_runtime_data) obj.is_runtime_data = True link_dir = os.path.join(output_lib_path, "Material") bpy.ops.wm.link(directory=link_dir, filename="LibMaterial") linked_material = bpy.data.materials['LibMaterial'] - assert linked_material.is_library_indirect is False + self.assertFalse(linked_material.is_library_indirect) link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=False) linked_mesh = bpy.data.meshes['LibMesh'] - assert linked_mesh.is_library_indirect is False - assert linked_mesh.use_fake_user is False + self.assertFalse(linked_mesh.is_library_indirect) + self.assertFalse(linked_mesh.use_fake_user) obj.data = linked_mesh obj.material_slots[0].link = 'OBJECT' @@ -176,17 +176,17 @@ def test_linking(self): # Only usage of this linked material is a runtime ID (object), # so writing .blend file will have properly reset its tag to indirectly linked data. - assert linked_material.is_library_indirect + self.assertTrue(linked_material.is_library_indirect) # Only usage of this linked mesh is a runtime ID (object), # so writing .blend file will have properly reset its tag to indirectly linked data. - assert linked_mesh.is_library_indirect + self.assertTrue(linked_mesh.is_library_indirect) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) - assert 'Cube' not in bpy.data.objects - assert 'LibMaterial' not in bpy.data.materials - assert 'libMesh' not in bpy.data.meshes + self.assertNotIn('Cube', bpy.data.objects) + self.assertNotIn('LibMaterial', bpy.data.materials) + self.assertNotIn('libMesh', bpy.data.meshes) TESTS = ( diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index d590b6020c4..cd189c8adef 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -20,7 +20,7 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_link_save_load(self): output_dir = self.args.output_dir @@ -32,9 +32,9 @@ def test_link_save_load(self): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=False) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 0 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 0) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -45,8 +45,8 @@ def test_link_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data") # Since there is no usage of linked mesh, it is lost during save/reload. - assert len(bpy.data.meshes) == 0 - assert orig_data != read_data + self.assertEqual(len(bpy.data.meshes), 0) + self.assertNotEqual(orig_data, read_data) # Simple link of a single ObData with obdata instantiation. self.reset_blender() @@ -54,9 +54,9 @@ def test_link_save_load(self): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # Instance created for the mesh ObData. - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # Instance created for the mesh ObData. + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -65,7 +65,7 @@ def test_link_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data") - assert orig_data == read_data + self.assertEqual(orig_data, read_data) # Simple link of a single Object. self.reset_blender() @@ -73,9 +73,9 @@ def test_link_save_load(self): link_dir = os.path.join(output_lib_path, "Object") bpy.ops.wm.link(directory=link_dir, filename="LibMesh") - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -84,7 +84,7 @@ def test_link_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data") - assert orig_data == read_data + self.assertEqual(orig_data, read_data) # Simple link of a single Collection, with Empty-instantiation. self.reset_blender() @@ -92,9 +92,9 @@ def test_link_save_load(self): link_dir = os.path.join(output_lib_path, "Collection") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_collections=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 2 # linked object and local empty instancing the collection - assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 2) # linked object and local empty instancing the collection + self.assertEqual(len(bpy.data.collections), 1) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -103,7 +103,7 @@ def test_link_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data") - assert orig_data == read_data + self.assertEqual(orig_data, read_data) # Simple link of a single Collection, with ViewLayer-instantiation. self.reset_blender() @@ -111,11 +111,11 @@ def test_link_save_load(self): link_dir = os.path.join(output_lib_path, "Collection") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_collections=False) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 1) # Scene's master collection is not listed here # Linked collection should have been added to the scene's master collection children. - assert bpy.data.collections[0] in set(bpy.data.scenes[0].collection.children) + self.assertIn(bpy.data.collections[0], set(bpy.data.scenes[0].collection.children)) orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -124,13 +124,13 @@ def test_link_save_load(self): read_data = self.blender_data_to_tuple(bpy.data, "read_data") - assert orig_data == read_data + self.assertEqual(orig_data, read_data) class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_append(self): output_dir = self.args.output_dir @@ -142,102 +142,102 @@ def test_append(self): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=False) - assert len(bpy.data.images) == 1 - assert len(bpy.data.materials) == 1 - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 0 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertEqual(len(bpy.data.materials), 1) + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 0) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here mesh = bpy.data.meshes[0] material = bpy.data.materials[0] image = bpy.data.images[0] - assert image.library is not None - assert image.use_fake_user is False # Fake user is cleared when linking. - assert image.users == 1 - assert image.is_library_indirect is True - assert len(image.pixels) - assert image.has_data - - assert material.library is not None - assert material.use_fake_user is False # Fake user is cleared when linking. - assert material.users == 1 - assert material.is_library_indirect is True - - assert mesh.library is not None - assert mesh.use_fake_user is False - assert mesh.users == 0 + self.assertIsNotNone(image.library) + self.assertFalse(image.use_fake_user) # Fake user is cleared when linking. + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) + self.assertNotEqual(len(image.pixels), 0) + self.assertTrue(image.has_data) + + self.assertIsNotNone(material.library) + self.assertFalse(material.use_fake_user) # Fake user is cleared when linking. + self.assertEqual(material.users, 1) + self.assertTrue(material.is_library_indirect) + + self.assertIsNotNone(mesh.library) + self.assertFalse(mesh.use_fake_user) + self.assertEqual(mesh.users, 0) # IDs explicitly linked by the user are forcefully considered directly linked. - assert mesh.is_library_indirect is False + self.assertFalse(mesh.is_library_indirect) ob = bpy.data.objects.new("LocalMesh", mesh) coll = bpy.data.collections.new("LocalMesh") coll.objects.link(ob) bpy.context.scene.collection.children.link(coll) - assert image.users == 1 - assert image.is_library_indirect is True - assert material.users == 1 - assert material.is_library_indirect is True - assert mesh.users == 1 - assert mesh.is_library_indirect is False + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) + self.assertEqual(material.users, 1) + self.assertTrue(material.is_library_indirect) + self.assertEqual(mesh.users, 1) + self.assertFalse(mesh.is_library_indirect) ob.material_slots[0].link = 'OBJECT' ob.material_slots[0].material = material - assert image.users == 1 - assert image.is_library_indirect is True - assert material.users == 2 - assert material.is_library_indirect is False + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) + self.assertEqual(material.users, 2) + self.assertFalse(material.is_library_indirect) ob.material_slots[0].material = None - assert image.users == 1 - assert image.is_library_indirect is True - assert material.users == 1 + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) + self.assertEqual(material.users, 1) # This is not properly updated whene removing a local user of linked data. - assert material.is_library_indirect is False + self.assertFalse(material.is_library_indirect) output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile")) bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False) - assert image.users == 1 - assert image.is_library_indirect is True - assert material.users == 1 - assert material.is_library_indirect is True + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) + self.assertEqual(material.users, 1) + self.assertTrue(material.is_library_indirect) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) - assert len(bpy.data.images) == 1 - assert len(bpy.data.materials) == 1 - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertEqual(len(bpy.data.materials), 1) + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 1) # Scene's master collection is not listed here mesh = bpy.data.meshes[0] material = bpy.data.materials[0] image = bpy.data.images[0] - assert image.library is not None - assert image.use_fake_user is False # Fake user is cleared when linking. - assert image.users == 1 - assert image.is_library_indirect is True + self.assertIsNotNone(image.library) + self.assertFalse(image.use_fake_user) # Fake user is cleared when linking. + self.assertEqual(image.users, 1) + self.assertTrue(image.is_library_indirect) - assert material.library is not None - assert material.use_fake_user is False # Fake user is cleared when linking. - assert material.users == 1 - assert material.is_library_indirect is True + self.assertIsNotNone(material.library) + self.assertFalse(material.use_fake_user) # Fake user is cleared when linking. + self.assertEqual(material.users, 1) + self.assertTrue(material.is_library_indirect) - assert mesh.library is not None - assert mesh.use_fake_user is False - assert mesh.users == 1 - assert mesh.is_library_indirect is False + self.assertIsNotNone(mesh.library) + self.assertFalse(mesh.use_fake_user) + self.assertEqual(mesh.users, 1) + self.assertFalse(mesh.is_library_indirect) class TestBlendLibLinkAnimation(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_link(self): output_dir = self.args.output_dir @@ -249,42 +249,37 @@ def test_link(self): link_dir = os.path.join(output_lib_path, "Collection") bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_collections=False, instance_object_data=False) - assert bpy.data.meshes[0].library - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 2 - assert bpy.data.objects[0].library - assert bpy.data.objects[0].users == 1 - assert bpy.data.objects[1].library - assert bpy.data.objects[1].users == 1 - assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here - assert bpy.data.collections[0].library - assert bpy.data.collections[0].users == 1 - assert len(bpy.data.actions) == 2 - assert bpy.data.actions[0].library - assert bpy.data.actions[0].users == 1 - assert bpy.data.actions[1].library - assert bpy.data.actions[1].users == 1 + self.assertIsNotNone(bpy.data.meshes[0].library) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 2) + self.assertIsNotNone(bpy.data.objects[0].library) + self.assertEqual(bpy.data.objects[0].users, 1) + self.assertIsNotNone(bpy.data.objects[1].library) + self.assertEqual(bpy.data.objects[1].users, 1) + self.assertEqual(len(bpy.data.collections), 1) # Scene's master collection is not listed here + self.assertIsNotNone(bpy.data.collections[0].library) + self.assertEqual(bpy.data.collections[0].users, 1) + self.assertEqual(len(bpy.data.actions), 2) + self.assertIsNotNone(bpy.data.actions[0].library) + self.assertEqual(bpy.data.actions[0].users, 1) + self.assertIsNotNone(bpy.data.actions[1].library) + self.assertEqual(bpy.data.actions[1].users, 1) # Validate animation evaluation. - bpy.context.scene.frame_set(10) - print(bpy.data.objects["LibController"].location) - print(bpy.data.objects["LibMesh"].location) bpy.context.scene.frame_set(1) - print(bpy.data.objects["LibController"].location) - print(bpy.data.objects["LibMesh"].location) - assert bpy.data.objects["LibController"].location[0] == 0.0 - assert bpy.data.objects["LibMesh"].location[0] == bpy.data.objects["LibController"].location[0] - assert bpy.data.objects["LibMesh"].location[1] == 0.0 + self.assertEqual(bpy.data.objects["LibController"].location[0], 0.0) + self.assertEqual(bpy.data.objects["LibMesh"].location[0], bpy.data.objects["LibController"].location[0]) + self.assertEqual(bpy.data.objects["LibMesh"].location[1], 0.0) bpy.context.scene.frame_set(10) - assert bpy.data.objects["LibController"].location[0] == 5.0 - assert bpy.data.objects["LibMesh"].location[0] == bpy.data.objects["LibController"].location[0] - assert bpy.data.objects["LibMesh"].location[1] == -5.0 + self.assertEqual(bpy.data.objects["LibController"].location[0], 5.0) + self.assertEqual(bpy.data.objects["LibMesh"].location[0], bpy.data.objects["LibController"].location[0]) + self.assertEqual(bpy.data.objects["LibMesh"].location[1], -5.0) class TestBlendLibAppendBasic(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_append(self): output_dir = self.args.output_dir @@ -297,20 +292,20 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is not None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].use_fake_user is False - assert bpy.data.meshes[0].users == 0 - assert len(bpy.data.objects) == 0 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNotNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNotNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when linking. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertFalse(bpy.data.meshes[0].use_fake_user) + self.assertEqual(bpy.data.meshes[0].users, 0) + self.assertEqual(len(bpy.data.objects), 0) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here # Simple append of a single ObData with obdata instantiation. self.reset_blender() @@ -319,21 +314,21 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=True, set_fake=False, use_recursive=False, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is not None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].use_fake_user is False - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 1 # Instance created for the mesh ObData. - assert bpy.data.objects[0].library is None - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNotNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNotNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when linking. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertFalse(bpy.data.meshes[0].use_fake_user) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 1) # Instance created for the mesh ObData. + self.assertIsNone(bpy.data.objects[0].library) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here # Simple append of a single ObData with fake user. self.reset_blender() @@ -342,20 +337,20 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=True, use_recursive=False, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is not None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].use_fake_user is True - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 0 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNotNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNotNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when linking. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertTrue(bpy.data.meshes[0].use_fake_user) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 0) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here # Simple append of a single Object. self.reset_blender() @@ -364,21 +359,21 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is not None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 1 - assert bpy.data.objects[0].library is None - assert bpy.data.objects[0].users == 1 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNotNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNotNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when linking. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertIsNone(bpy.data.objects[0].library) + self.assertEqual(bpy.data.objects[0].users, 1) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here # Simple recursive append of a single Object. self.reset_blender() @@ -387,21 +382,21 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when appending. - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 1 - assert bpy.data.objects[0].library is None - assert bpy.data.objects[0].users == 1 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when appending. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertIsNone(bpy.data.objects[0].library) + self.assertEqual(bpy.data.objects[0].users, 1) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here # Simple recursive append of a single Collection. self.reset_blender() @@ -410,28 +405,28 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is None - assert bpy.data.images[0].users == 1 - assert len(bpy.data.images[0].pixels) - assert bpy.data.images[0].has_data - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is None - assert bpy.data.materials[0].users == 1 # Fake user is cleared when appending. - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].users == 1 - assert len(bpy.data.objects) == 1 - assert bpy.data.objects[0].library is None - assert bpy.data.objects[0].users == 1 - assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here - assert bpy.data.collections[0].library is None - assert bpy.data.collections[0].users == 1 + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNone(bpy.data.images[0].library) + self.assertEqual(bpy.data.images[0].users, 1) + self.assertNotEqual(len(bpy.data.images[0].pixels), 0) + self.assertTrue(bpy.data.images[0].has_data) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNone(bpy.data.materials[0].library) + self.assertEqual(bpy.data.materials[0].users, 1) # Fake user is cleared when appending. + self.assertIsNone(bpy.data.meshes[0].library) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertIsNone(bpy.data.objects[0].library) + self.assertEqual(bpy.data.objects[0].users, 1) + self.assertEqual(len(bpy.data.collections), 1) # Scene's master collection is not listed here + self.assertIsNone(bpy.data.collections[0].library) + self.assertEqual(bpy.data.collections[0].users, 1) class TestBlendLibAppendReuseID(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_append(self): output_dir = self.args.output_dir @@ -444,57 +439,57 @@ def test_append(self): bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].use_fake_user is False - assert bpy.data.meshes[0].users == 1 - assert bpy.data.meshes[0].library_weak_reference is not None - assert bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path - assert bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh" - assert len(bpy.data.objects) == 1 + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertFalse(bpy.data.meshes[0].use_fake_user) + self.assertEqual(bpy.data.meshes[0].users, 1) + self.assertIsNotNone(bpy.data.meshes[0].library_weak_reference) + self.assertEqual(bpy.data.meshes[0].library_weak_reference.filepath, output_lib_path) + self.assertEqual(bpy.data.meshes[0].library_weak_reference.id_name, "MELibMesh") + self.assertEqual(len(bpy.data.objects), 1) for ob in bpy.data.objects: - assert ob.library is None - assert ob.library_weak_reference is None - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertIsNone(ob.library) + self.assertIsNone(ob.library_weak_reference) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=True) - assert len(bpy.data.meshes) == 1 - assert bpy.data.meshes[0].library is None - assert bpy.data.meshes[0].use_fake_user is False - assert bpy.data.meshes[0].users == 2 - assert bpy.data.meshes[0].library_weak_reference is not None - assert bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path - assert bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh" - assert len(bpy.data.objects) == 2 + self.assertEqual(len(bpy.data.meshes), 1) + self.assertIsNone(bpy.data.meshes[0].library) + self.assertFalse(bpy.data.meshes[0].use_fake_user) + self.assertEqual(bpy.data.meshes[0].users, 2) + self.assertIsNotNone(bpy.data.meshes[0].library_weak_reference) + self.assertEqual(bpy.data.meshes[0].library_weak_reference.filepath, output_lib_path) + self.assertEqual(bpy.data.meshes[0].library_weak_reference.id_name, "MELibMesh") + self.assertEqual(len(bpy.data.objects), 2) for ob in bpy.data.objects: - assert ob.library is None - assert ob.library_weak_reference is None - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertIsNone(ob.library) + self.assertIsNone(ob.library_weak_reference) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here bpy.ops.wm.append(directory=link_dir, filename="LibMesh", instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) - assert len(bpy.data.meshes) == 2 - assert bpy.data.meshes[0].library_weak_reference is None - assert bpy.data.meshes[1].library is None - assert bpy.data.meshes[1].use_fake_user is False - assert bpy.data.meshes[1].users == 1 - assert bpy.data.meshes[1].library_weak_reference is not None - assert bpy.data.meshes[1].library_weak_reference.filepath == output_lib_path - assert bpy.data.meshes[1].library_weak_reference.id_name == "MELibMesh" - assert len(bpy.data.objects) == 3 + self.assertEqual(len(bpy.data.meshes), 2) + self.assertIsNone(bpy.data.meshes[0].library_weak_reference) + self.assertIsNone(bpy.data.meshes[1].library) + self.assertFalse(bpy.data.meshes[1].use_fake_user) + self.assertEqual(bpy.data.meshes[1].users, 1) + self.assertIsNotNone(bpy.data.meshes[1].library_weak_reference) + self.assertEqual(bpy.data.meshes[1].library_weak_reference.filepath, output_lib_path) + self.assertEqual(bpy.data.meshes[1].library_weak_reference.id_name, "MELibMesh") + self.assertEqual(len(bpy.data.objects), 3) for ob in bpy.data.objects: - assert ob.library is None - assert ob.library_weak_reference is None - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertIsNone(ob.library) + self.assertIsNone(ob.library_weak_reference) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here class TestBlendLibLibraryReload(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_link_reload(self): output_dir = self.args.output_dir @@ -506,9 +501,9 @@ def test_link_reload(self): link_dir = os.path.join(output_lib_path, "Object") bpy.ops.wm.link(directory=link_dir, filename="LibMesh") - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -516,15 +511,13 @@ def test_link_reload(self): reload_data = self.blender_data_to_tuple(bpy.data, "reload_data") - print(orig_data) - print(reload_data) - assert orig_data == reload_data + self.assertEqual(orig_data, reload_data) class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_link_relocate(self): output_dir = self.args.output_dir @@ -536,9 +529,9 @@ def test_link_relocate(self): link_dir = os.path.join(output_lib_path, "Object") bpy.ops.wm.link(directory=link_dir, filename="LibMesh") - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 0) # Scene's master collection is not listed here orig_data = self.blender_data_to_tuple(bpy.data, "orig_data") @@ -550,16 +543,14 @@ def test_link_relocate(self): relocate_data = self.blender_data_to_tuple(bpy.data, "relocate_data") - print(orig_data) - print(relocate_data) - assert orig_data == relocate_data + self.assertEqual(orig_data, relocate_data) # Python library loader context manager. class TestBlendLibDataLibrariesLoad(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def do_libload_init(self): output_dir = self.args.output_dir @@ -574,13 +565,13 @@ def do_libload(self, **load_kwargs): with bpy.data.libraries.load(**load_kwargs) as lib_ctx: lib_src, lib_link = lib_ctx - assert len(lib_src.meshes) == 1 - assert len(lib_src.objects) == 1 - assert len(lib_src.collections) == 1 + self.assertEqual(len(lib_src.meshes), 1) + self.assertEqual(len(lib_src.objects), 1) + self.assertEqual(len(lib_src.collections), 1) - assert len(lib_link.meshes) == 0 - assert len(lib_link.objects) == 0 - assert len(lib_link.collections) == 0 + self.assertEqual(len(lib_link.meshes), 0) + self.assertEqual(len(lib_link.objects), 0) + self.assertEqual(len(lib_link.collections), 0) lib_link.collections.append(lib_src.collections[0]) @@ -593,14 +584,14 @@ def test_libload_append(self): output_lib_path = self.do_libload_init() self.do_libload(filepath=output_lib_path, link=False, create_liboverrides=False) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 1 + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 1) # Append, so all data should have been made local. - assert bpy.data.meshes[0].library is None - assert bpy.data.objects[0].library is None - assert bpy.data.collections[0].library is None + self.assertIsNone(bpy.data.meshes[0].library) + self.assertIsNone(bpy.data.objects[0].library) + self.assertIsNone(bpy.data.collections[0].library) class TestBlendLibDataLibrariesLoadLink(TestBlendLibDataLibrariesLoad): @@ -609,14 +600,14 @@ def test_libload_link(self): output_lib_path = self.do_libload_init() self.do_libload(filepath=output_lib_path, link=True, create_liboverrides=False) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 1 + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 1) # Link, so all data should have remained linked. - assert bpy.data.meshes[0].library is not None - assert bpy.data.objects[0].library is not None - assert bpy.data.collections[0].library is not None + self.assertIsNotNone(bpy.data.meshes[0].library) + self.assertIsNotNone(bpy.data.objects[0].library) + self.assertIsNotNone(bpy.data.collections[0].library) class TestBlendLibDataLibrariesLoadLibOverride(TestBlendLibDataLibrariesLoad): @@ -625,56 +616,56 @@ def test_libload_liboverride(self): output_lib_path = self.do_libload_init() self.do_libload(filepath=output_lib_path, link=True, create_liboverrides=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 2 # The linked one and its local liboverride. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 2) # The linked one and its local liboverride. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[0].library is None - assert bpy.data.collections[0].is_runtime_data is False - assert bpy.data.collections[0].override_library is not None - assert bpy.data.collections[0].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[0].library) + self.assertFalse(bpy.data.collections[0].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[0].override_library) + self.assertEqual(bpy.data.collections[0].override_library.reference, bpy.data.collections[-1]) # Should create another liboverride for the linked collection. self.do_libload(filepath=output_lib_path, link=True, create_liboverrides=True, reuse_liboverrides=False) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 3 # The linked one and its two local liboverrides. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 3) # The linked one and its two local liboverrides. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[1].library is None - assert bpy.data.collections[1].is_runtime_data is False - assert bpy.data.collections[1].override_library is not None - assert bpy.data.collections[1].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[1].library) + self.assertFalse(bpy.data.collections[1].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[1].override_library) + self.assertEqual(bpy.data.collections[1].override_library.reference, bpy.data.collections[-1]) # This call should not change anything, first liboverrides should be found and 'reused'. self.do_libload(filepath=output_lib_path, link=True, create_liboverrides=True, reuse_liboverrides=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 3 # The linked one and its two local liboverrides. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 3) # The linked one and its two local liboverrides. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[1].library is None - assert bpy.data.collections[1].is_runtime_data is False - assert bpy.data.collections[1].override_library is not None - assert bpy.data.collections[1].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[1].library) + self.assertFalse(bpy.data.collections[1].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[1].override_library) + self.assertEqual(bpy.data.collections[1].override_library.reference, bpy.data.collections[-1]) def test_libload_liboverride_runtime(self): output_lib_path = self.do_libload_init() @@ -682,20 +673,20 @@ def test_libload_liboverride_runtime(self): create_liboverrides=True, create_liboverrides_runtime=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 2 # The linked one and its local liboverride. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 2) # The linked one and its local liboverride. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[0].library is None - assert bpy.data.collections[0].is_runtime_data is True - assert bpy.data.collections[0].override_library is not None - assert bpy.data.collections[0].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[0].library) + self.assertTrue(bpy.data.collections[0].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[0].override_library) + self.assertEqual(bpy.data.collections[0].override_library.reference, bpy.data.collections[-1]) # This call should not change anything, first liboverrides should be found and 'reused'. self.do_libload(filepath=output_lib_path, @@ -704,20 +695,20 @@ def test_libload_liboverride_runtime(self): create_liboverrides_runtime=True, reuse_liboverrides=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 2 # The linked one and its local liboverride. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 2) # The linked one and its local liboverride. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[0].library is None - assert bpy.data.collections[0].is_runtime_data is True - assert bpy.data.collections[0].override_library is not None - assert bpy.data.collections[0].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[0].library) + self.assertTrue(bpy.data.collections[0].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[0].override_library) + self.assertEqual(bpy.data.collections[0].override_library.reference, bpy.data.collections[-1]) # Should create another liboverride for the linked collection, since this time we request a non-runtime one. self.do_libload(filepath=output_lib_path, @@ -726,20 +717,20 @@ def test_libload_liboverride_runtime(self): create_liboverrides_runtime=False, reuse_liboverrides=True) - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 # This code does no instantiation. - assert len(bpy.data.collections) == 3 # The linked one and its two local liboverrides. + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) # This code does no instantiation. + self.assertEqual(len(bpy.data.collections), 3) # The linked one and its two local liboverrides. # Link + LibOverride, so linked data should have remained linked. - assert bpy.data.meshes[-1].library is not None - assert bpy.data.objects[-1].library is not None - assert bpy.data.collections[-1].library is not None + self.assertIsNotNone(bpy.data.meshes[-1].library) + self.assertIsNotNone(bpy.data.objects[-1].library) + self.assertIsNotNone(bpy.data.collections[-1].library) # Only explicitly linked data gets a liboverride, without any handling of hierarchy/dependencies. - assert bpy.data.collections[1].library is None - assert bpy.data.collections[1].is_runtime_data is False - assert bpy.data.collections[1].override_library is not None - assert bpy.data.collections[1].override_library.reference == bpy.data.collections[-1] + self.assertIsNone(bpy.data.collections[1].library) + self.assertFalse(bpy.data.collections[1].is_runtime_data) + self.assertIsNotNone(bpy.data.collections[1].override_library) + self.assertEqual(bpy.data.collections[1].override_library.reference, bpy.data.collections[-1]) TESTS = ( diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index 735e347112f..d095a315a1a 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -7,13 +7,12 @@ import bpy import sys import os -import unittest sys.path.append(os.path.dirname(os.path.realpath(__file__))) from bl_blendfile_utils import TestHelper -class TestLibraryOverrides(TestHelper, unittest.TestCase): +class TestLibraryOverrides(TestHelper): MESH_LIBRARY_PARENT = "LibMeshParent" OBJECT_LIBRARY_PARENT = "LibMeshParent" MESH_LIBRARY_CHILD = "LibMeshChild" @@ -22,7 +21,7 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase): OBJECT_LIBRARY_PERMISSIVE = "LibMeshPermissive" def __init__(self, args): - self.args = args + super().__init__(args) output_dir = pathlib.Path(self.args.output_dir) self.ensure_path(str(output_dir)) @@ -56,49 +55,49 @@ def test_link_and_override_property(self): local_id = obj.override_create() self.assertIsNotNone(local_id.override_library) self.assertIsNone(local_id.data.override_library) - assert len(local_id.override_library.properties) == 0 + self.assertEqual(len(local_id.override_library.properties), 0) # #### Generate an override property & operation automatically by editing the local override data. local_id.location.y = 1.0 local_id.override_library.operations_update() - assert len(local_id.override_library.properties) == 1 + self.assertEqual(len(local_id.override_library.properties), 1) override_prop = local_id.override_library.properties[0] - assert override_prop.rna_path == "location" - assert len(override_prop.operations) == 1 + self.assertEqual(override_prop.rna_path, "location") + self.assertEqual(len(override_prop.operations), 1) override_operation = override_prop.operations[0] - assert override_operation.operation == 'REPLACE' + self.assertEqual(override_operation.operation, 'REPLACE') # Setting location.y overrode all elements in the location array. -1 is a wildcard. - assert override_operation.subitem_local_index == -1 + self.assertEqual(override_operation.subitem_local_index, -1) # #### Reset the override to its linked reference data. local_id.override_library.reset() - assert len(local_id.override_library.properties) == 0 - assert local_id.location == local_id.override_library.reference.location + self.assertEqual(len(local_id.override_library.properties), 0) + self.assertEqual(local_id.location, local_id.override_library.reference.location) # #### Generate an override property & operation manually using the API. override_property = local_id.override_library.properties.add(rna_path="location") override_property.operations.add(operation='REPLACE') - assert len(local_id.override_library.properties) == 1 + self.assertEqual(len(local_id.override_library.properties), 1) override_prop = local_id.override_library.properties[0] - assert override_prop.rna_path == "location" - assert len(override_prop.operations) == 1 + self.assertEqual(override_prop.rna_path, "location") + self.assertEqual(len(override_prop.operations), 1) override_operation = override_prop.operations[0] - assert override_operation.operation == 'REPLACE' + self.assertEqual(override_operation.operation, 'REPLACE') # Setting location.y overrode all elements in the location array. -1 is a wildcard. - assert override_operation.subitem_local_index == -1 + self.assertEqual(override_operation.subitem_local_index, -1) override_property = local_id.override_library.properties[0] override_property.operations.remove(override_property.operations[0]) local_id.override_library.properties.remove(override_property) - assert len(local_id.override_library.properties) == 0 + self.assertEqual(len(local_id.override_library.properties), 0) # #### Delete the override. local_id_name = local_id.name - assert bpy.data.objects.get((local_id_name, None), None) == local_id + self.assertEqual(bpy.data.objects.get((local_id_name, None), None), local_id) local_id.override_library.destroy() - assert bpy.data.objects.get((local_id_name, None), None) is None + self.assertIsNone(bpy.data.objects.get((local_id_name, None), None)) def test_link_permissive(self): bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) @@ -112,23 +111,23 @@ def test_link_permissive(self): local_id = obj.override_create() self.assertIsNotNone(local_id.override_library) self.assertIsNone(local_id.data.override_library) - assert len(local_id.override_library.properties) == 0 + self.assertEqual(len(local_id.override_library.properties), 0) local_id.location.y = 1.0 - assert local_id.location.y == 1.0 + self.assertEqual(local_id.location.y, 1.0) local_id.override_library.operations_update() - assert local_id.location.y == 1.0 + self.assertEqual(local_id.location.y, 1.0) - assert len(local_id.override_library.properties) == 1 + self.assertEqual(len(local_id.override_library.properties), 1) override_prop = local_id.override_library.properties[0] - assert override_prop.rna_path == "location" - assert len(override_prop.operations) == 1 + self.assertEqual(override_prop.rna_path, "location") + self.assertEqual(len(override_prop.operations), 1) override_operation = override_prop.operations[0] - assert override_operation.operation == 'REPLACE' - assert override_operation.subitem_local_index == -1 + self.assertEqual(override_operation.operation, 'REPLACE') + self.assertEqual(override_operation.subitem_local_index, -1) -class TestLibraryOverridesComplex(TestHelper, unittest.TestCase): +class TestLibraryOverridesComplex(TestHelper): # Test resync, recursive resync, overrides of overrides, ID names collision handling, and multiple overrides. DATA_NAME_CONTAINER = "LibCollection" @@ -143,7 +142,7 @@ class TestLibraryOverridesComplex(TestHelper, unittest.TestCase): DATA_NAME_SAMENAME_3 = "LibCube.003" def __init__(self, args): - self.args = args + super().__init__(args) output_dir = pathlib.Path(self.args.output_dir) self.ensure_path(str(output_dir)) @@ -217,16 +216,16 @@ def link_lib_data(self, num_collections, num_objects, num_meshes, num_armatures) linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert linked_collection_container.library is not None - assert linked_collection_container.override_library is None - assert len(bpy.data.collections) == num_collections - assert all(id_.library is not None for id_ in bpy.data.collections) - assert len(bpy.data.objects) == num_objects - assert all(id_.library is not None for id_ in bpy.data.objects) - assert len(bpy.data.meshes) == num_meshes - assert all(id_.library is not None for id_ in bpy.data.meshes) - assert len(bpy.data.armatures) == num_armatures - assert all(id_.library is not None for id_ in bpy.data.armatures) + self.assertIsNotNone(linked_collection_container.library) + self.assertIsNone(linked_collection_container.override_library) + self.assertEqual(len(bpy.data.collections), num_collections) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.collections)) + self.assertEqual(len(bpy.data.objects), num_objects) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.objects)) + self.assertEqual(len(bpy.data.meshes), num_meshes) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.meshes)) + self.assertEqual(len(bpy.data.armatures), num_armatures) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.armatures)) return linked_collection_container @@ -241,37 +240,36 @@ def link_liboverride_data(self, num_collections, num_objects, num_meshes, num_ar linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str( self.test_output_path)] - assert linked_collection_container.library is not None - assert linked_collection_container.override_library is not None - assert len(bpy.data.collections) == num_collections - assert all(id_.library is not None for id_ in bpy.data.collections) - assert len(bpy.data.objects) == num_objects - assert all(id_.library is not None for id_ in bpy.data.objects) - assert len(bpy.data.meshes) == num_meshes - assert all(id_.library is not None for id_ in bpy.data.meshes) - assert len(bpy.data.armatures) == num_armatures - assert all(id_.library is not None for id_ in bpy.data.armatures) + self.assertIsNotNone(linked_collection_container.library) + self.assertIsNotNone(linked_collection_container.override_library) + self.assertEqual(len(bpy.data.collections), num_collections) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.collections)) + self.assertEqual(len(bpy.data.objects), num_objects) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.objects)) + self.assertEqual(len(bpy.data.meshes), num_meshes) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.meshes)) + self.assertEqual(len(bpy.data.armatures), num_armatures) + self.assertTrue(all(id_.library is not None for id_ in bpy.data.armatures)) self.liboverride_hierarchy_validate(linked_collection_container) return linked_collection_container - @staticmethod - def liboverride_hierarchy_validate(root_collection): + def liboverride_hierarchy_validate(self, root_collection): def liboverride_systemoverrideonly_hierarchy_validate(id_, id_root): if not id_.override_library: return - assert id_.override_library.hierarchy_root == id_root + self.assertEqual(id_.override_library.hierarchy_root, id_root) for op in id_.override_library.properties: for opop in op.operations: - assert 'IDPOINTER_MATCH_REFERENCE' in opop.flag + self.assertIn('IDPOINTER_MATCH_REFERENCE', opop.flag) for coll_ in root_collection.children_recursive: liboverride_systemoverrideonly_hierarchy_validate(coll_, root_collection) if coll_.override_library: for op in coll_.override_library.properties: for opop in op.operations: - assert 'IDPOINTER_ITEM_USE_ID' in opop.flag + self.assertIn('IDPOINTER_ITEM_USE_ID', opop.flag) print( coll_, opop.flag, @@ -279,8 +277,9 @@ def liboverride_systemoverrideonly_hierarchy_validate(id_, id_root): opop.subitem_reference_id, opop.subitem_local_name, opop.subitem_local_id) - assert opop.subitem_reference_id.library is not None - assert opop.subitem_local_id.library is None if coll_.library is None else opop.subitem_local_id.library is not None + self.assertIsNotNone(opop.subitem_reference_id.library) + self.assertTrue(opop.subitem_local_id.library is None if coll_.library is None + else opop.subitem_local_id.library is not None) for ob_ in root_collection.all_objects: liboverride_systemoverrideonly_hierarchy_validate(ob_, root_collection) @@ -299,16 +298,17 @@ def test_link_and_override_resync(self): bpy.context.scene, bpy.context.view_layer, ) - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) self.liboverride_hierarchy_validate(override_collection_container) @@ -350,20 +350,23 @@ def test_link_and_override_resync(self): bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path)) override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert override_collection_container.library is None - assert override_collection_container.override_library is not None - assert len(bpy.data.collections) == 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) + self.assertEqual(len(bpy.data.collections), 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2] - assert obj_armature.library is None and obj_armature.override_library is not None - assert obj_ctrl2.library is None and obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNone(obj_armature.library) + self.assertIsNotNone(obj_armature.override_library) + self.assertIsNone(obj_ctrl2.library) + self.assertIsNotNone(obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) self.liboverride_hierarchy_validate(override_collection_container) @@ -374,23 +377,23 @@ def test_link_and_override_resync(self): override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str( self.test_output_path)] - assert override_collection_container.library is not None - assert override_collection_container.override_library is not None + self.assertIsNotNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) test_output_path_lib = override_collection_container.library - assert len(bpy.data.collections) == 0 + 5 - assert all((id_.override_library is not None) - for id_ in bpy.data.collections if id_.library == test_output_path_lib) - assert len(bpy.data.objects) == 0 + 8 - assert all((id_.override_library is not None) - for id_ in bpy.data.objects if id_.library == test_output_path_lib) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 0 + 5) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.collections if id_.library == test_output_path_lib)) + self.assertEqual(len(bpy.data.objects), 0 + 8) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.objects if id_.library == test_output_path_lib)) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG, str(self.test_output_path)] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2, str(self.test_output_path)] - assert obj_armature.override_library is not None - assert obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNotNone(obj_armature.override_library) + self.assertIsNotNone(obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) self.liboverride_hierarchy_validate(override_collection_container) @@ -410,18 +413,20 @@ def test_link_and_override_multiple(self): bpy.context.view_layer, ) for i in range(3)] for override_container in override_collection_containers: - assert override_container.library is None - assert override_container.override_library is not None + self.assertIsNone(override_container.library) + self.assertIsNotNone(override_container.override_library) self.liboverride_hierarchy_validate(override_container) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 * 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3 * 2]) - assert len(bpy.data.objects) == 3 * 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:3 * 4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 3 * 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:3 * 2])) + self.assertEqual(len(bpy.data.objects), 3 * 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.objects[:3 * 4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) bpy.ops.wm.save_as_mainfile( filepath=str(self.test_output_path), @@ -459,21 +464,23 @@ def edit_lib_cb(self): bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path)) override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 * 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3 * 2]) - assert len(bpy.data.objects) == 3 * 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:3 * 4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 3 * 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:3 * 2])) + self.assertEqual(len(bpy.data.objects), 3 * 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.objects[:3 * 4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2] - assert obj_armature.library is None and obj_armature.override_library is not None - assert obj_ctrl2.library is None and obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNotNone(obj_armature.library is None and obj_armature.override_library) + self.assertIsNotNone(obj_ctrl2.library is None and obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) override_collection_containers = [ bpy.data.collections[self.__class__.DATA_NAME_CONTAINER], @@ -481,8 +488,8 @@ def edit_lib_cb(self): bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".002"], ] for override_container in override_collection_containers: - assert override_container.library is None - assert override_container.override_library is not None + self.assertIsNone(override_container.library) + self.assertIsNotNone(override_container.override_library) self.liboverride_hierarchy_validate(override_container) # Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly, @@ -492,24 +499,24 @@ def edit_lib_cb(self): linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str( self.test_output_path)] - assert linked_collection_container.library is not None - assert linked_collection_container.override_library is not None + self.assertIsNotNone(linked_collection_container.library) + self.assertIsNotNone(linked_collection_container.override_library) test_output_path_lib = linked_collection_container.library # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 0 + 5 - assert all((id_.override_library is not None) - for id_ in bpy.data.collections if id_.library == test_output_path_lib) - assert len(bpy.data.objects) == 0 + 8 - assert all((id_.override_library is not None) - for id_ in bpy.data.objects if id_.library == test_output_path_lib) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 0 + 5) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.collections if id_.library == test_output_path_lib)) + self.assertEqual(len(bpy.data.objects), 0 + 8) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.objects if id_.library == test_output_path_lib)) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG, str(self.test_output_path)] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2, str(self.test_output_path)] - assert obj_armature.override_library is not None - assert obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNotNone(obj_armature.override_library) + self.assertIsNotNone(obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) self.liboverride_hierarchy_validate(linked_collection_container) @@ -528,17 +535,18 @@ def test_link_and_override_of_override(self): bpy.context.scene, bpy.context.view_layer, ) - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) self.liboverride_hierarchy_validate(override_collection_container) @@ -562,17 +570,18 @@ def test_link_and_override_of_override(self): bpy.context.scene, bpy.context.view_layer, ) - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 2 + 5 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 8 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 2 + 5) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 8) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) self.liboverride_hierarchy_validate(override_collection_container) @@ -598,21 +607,24 @@ def test_link_and_override_of_override(self): bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path)) override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 2 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 4 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 2 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 4) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2] - assert obj_armature.library is None and obj_armature.override_library is not None - assert obj_ctrl2.library is None and obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNone(obj_armature.library) + self.assertIsNotNone(obj_armature.override_library) + self.assertIsNone(obj_ctrl2.library) + self.assertIsNotNone(obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) self.liboverride_hierarchy_validate(override_collection_container) @@ -622,21 +634,22 @@ def test_link_and_override_of_override(self): bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive)) override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 2 + 5 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2]) - assert len(bpy.data.objects) == 4 + 8 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 2 + 5) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:2])) + self.assertEqual(len(bpy.data.objects), 4 + 8) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG] obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2] - assert obj_armature.override_library is not None - assert obj_ctrl2.override_library is not None - assert obj_armature.constraints[0].target == obj_ctrl2 + self.assertIsNotNone(obj_armature.override_library) + self.assertIsNotNone(obj_ctrl2.override_library) + self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2) self.liboverride_hierarchy_validate(override_collection_container) @@ -663,25 +676,31 @@ def init_lib_cb(self): bpy.context.view_layer, ) for i in range(3)] for override_container in override_collection_containers: - assert override_container.library is None - assert override_container.override_library is not None + self.assertIsNone(override_container.library) + self.assertIsNotNone(override_container.override_library) self.liboverride_hierarchy_validate(override_container) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 * 3 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3 * 3]) - assert len(bpy.data.objects) == 3 * 6 + 6 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:3 * 6]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 - - bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_0].override_library.reference.name == self.__class__.DATA_NAME_SAMENAME_0 - bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_3].override_library.reference.name == self.__class__.DATA_NAME_SAMENAME_3 + self.assertEqual(len(bpy.data.collections), 3 * 3 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:3 * 3])) + self.assertEqual(len(bpy.data.objects), 3 * 6 + 6) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.objects[:3 * 6])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) + + self.assertEqual( + bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_0].override_library.reference.name, self.__class__.DATA_NAME_SAMENAME_0) + self.assertEqual( + bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_3].override_library.reference.name, self.__class__.DATA_NAME_SAMENAME_3) # These names will be used by the second created liboverride, due to how # naming is currently handled when original name is already used. - bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_1].override_library.reference.name == self.__class__.DATA_NAME_SAMENAME_0 - bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_2].override_library.reference.name == self.__class__.DATA_NAME_SAMENAME_3 + self.assertEqual( + bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_1].override_library.reference.name, self.__class__.DATA_NAME_SAMENAME_0) + self.assertEqual( + bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_2].override_library.reference.name, self.__class__.DATA_NAME_SAMENAME_3) bpy.ops.wm.save_as_mainfile( filepath=str(self.test_output_path), @@ -703,17 +722,18 @@ def init_lib_cb(self): bpy.context.scene, bpy.context.view_layer, ) - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides (except for empty collection), # but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 + 6 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3]) - assert len(bpy.data.objects) == 6 + 12 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:6]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.collections), 3 + 6) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:3])) + self.assertEqual(len(bpy.data.objects), 6 + 12) + self.assertTrue(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:6])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) self.liboverride_hierarchy_validate(override_collection_container) @@ -737,16 +757,18 @@ def edit_lib_cb(self): bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path)) override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER] - assert override_collection_container.library is None - assert override_collection_container.override_library is not None + self.assertIsNone(override_collection_container.library) + self.assertIsNotNone(override_collection_container.override_library) # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 * 3 + 3 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:3 * 3]) + self.assertEqual(len(bpy.data.collections), 3 * 3 + 3) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.collections[:3 * 3])) # Note that the 'missing' renamed objects from the library are now cleared as part of the resync process. - assert len(bpy.data.objects) == 3 * 6 + 6 - assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:3 * 6]) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.objects), 3 * 6 + 6) + self.assertTrue(all((id_.library is None and id_.override_library is not None) + for id_ in bpy.data.objects[:3 * 6])) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) override_collection_containers = [ bpy.data.collections[self.__class__.DATA_NAME_CONTAINER], @@ -754,8 +776,8 @@ def edit_lib_cb(self): bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".002"], ] for override_container in override_collection_containers: - assert override_container.library is None - assert override_container.override_library is not None + self.assertIsNone(override_container.library) + self.assertIsNotNone(override_container.override_library) self.liboverride_hierarchy_validate(override_container) # Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly, @@ -765,36 +787,36 @@ def edit_lib_cb(self): linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str( self.test_output_path)] - assert linked_collection_container.library is not None - assert linked_collection_container.override_library is not None + self.assertIsNotNone(linked_collection_container.library) + self.assertIsNotNone(linked_collection_container.override_library) test_output_path_lib = linked_collection_container.library # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. - assert len(bpy.data.collections) == 3 + 6 - assert all((id_.override_library is not None) - for id_ in bpy.data.collections if id_.library == test_output_path_lib) + self.assertEqual(len(bpy.data.collections), 3 + 6) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.collections if id_.library == test_output_path_lib)) # Note that the 'missing' renamed objects from the library are now cleared as part of the resync process. - assert len(bpy.data.objects) == 6 + 12 - assert all((id_.override_library is not None) - for id_ in bpy.data.objects if id_.library == test_output_path_lib) - assert len(bpy.data.meshes) == 0 + 1 - assert len(bpy.data.armatures) == 0 + 1 + self.assertEqual(len(bpy.data.objects), 6 + 12) + self.assertTrue(all((id_.override_library is not None) + for id_ in bpy.data.objects if id_.library == test_output_path_lib)) + self.assertEqual(len(bpy.data.meshes), 0 + 1) + self.assertEqual(len(bpy.data.armatures), 0 + 1) self.liboverride_hierarchy_validate(linked_collection_container) -class TestLibraryOverridesFromProxies(TestHelper, unittest.TestCase): +class TestLibraryOverridesFromProxies(TestHelper): # Very basic test, could be improved/extended. # NOTE: Tests way more than only liboverride proxy conversion actually, since this is a fairly old .blend file. MAIN_BLEND_FILE = "library_test_scene.blend" def __init__(self, args): - self.args = args + super().__init__(args) self.test_dir = pathlib.Path(self.args.test_dir) self.assertTrue(self.test_dir.exists(), - 'Test dir {0} should exist'.format(self.test_dir)) + msg='Test dir {0} should exist'.format(self.test_dir)) bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) @@ -803,10 +825,10 @@ def test_open_linked_proxy_file(self): # Check stability of 'same name' fixing for IDs. direct_linked_A = bpy.data.libraries["lib.002"] - assert direct_linked_A.filepath == os.path.join("//libraries", "direct_linked_A.blend") + self.assertEqual(direct_linked_A.filepath, os.path.join("//libraries", "direct_linked_A.blend")) - assert bpy.data.objects['HairCubeArmatureGroup_proxy'].library == direct_linked_A - assert bpy.data.objects['HairCubeArmatureGroup_proxy'].override_library is not None + self.assertEqual(bpy.data.objects['HairCubeArmatureGroup_proxy'].library, direct_linked_A) + self.assertIsNotNone(bpy.data.objects['HairCubeArmatureGroup_proxy'].override_library) TESTS = ( diff --git a/tests/python/bl_blendfile_relationships.py b/tests/python/bl_blendfile_relationships.py index fba7e4018a8..e69be00b50f 100644 --- a/tests/python/bl_blendfile_relationships.py +++ b/tests/python/bl_blendfile_relationships.py @@ -22,7 +22,7 @@ class TestBlendUserMap(TestBlendLibLinkHelper): def __init__(self, args): - self.args = args + super().__init__(args) def test_user_map(self): output_dir = self.args.output_dir @@ -33,13 +33,13 @@ def test_user_map(self): bpy.ops.wm.open_mainfile(filepath=output_blendfile_path) - assert len(bpy.data.images) == 1 - assert bpy.data.images[0].library is not None - assert len(bpy.data.materials) == 1 - assert bpy.data.materials[0].library is not None - assert len(bpy.data.meshes) == 1 - assert len(bpy.data.objects) == 1 - assert len(bpy.data.collections) == 1 + self.assertEqual(len(bpy.data.images), 1) + self.assertIsNotNone(bpy.data.images[0].library) + self.assertEqual(len(bpy.data.materials), 1) + self.assertIsNotNone(bpy.data.materials[0].library) + self.assertEqual(len(bpy.data.meshes), 1) + self.assertEqual(len(bpy.data.objects), 1) + self.assertEqual(len(bpy.data.collections), 1) user_map = bpy.data.user_map() # Note: Workspaces and screens are ignored here. @@ -55,8 +55,8 @@ def test_user_map(self): bpy.data.window_managers[0]: set(), } for k, v in expected_map.items(): - assert k in user_map - assert user_map[k] == v + self.assertIn(k, user_map) + self.assertEqual(user_map[k], v) user_map = bpy.data.user_map(subset=[bpy.data.objects[0], bpy.data.meshes[0]]) expected_map = { @@ -65,12 +65,12 @@ def test_user_map(self): bpy.data.collections[0]}, } for k, v in expected_map.items(): - assert k in user_map - assert user_map[k] == v + self.assertIn(k, user_map) + self.assertEqual(user_map[k], v) user_map = bpy.data.user_map(key_types={'OBJECT', 'MESH'}) for k, v in expected_map.items(): - assert k in user_map - assert user_map[k] == v + self.assertIn(k, user_map) + self.assertEqual(user_map[k], v) user_map = bpy.data.user_map(value_types={'SCENE'}) expected_map = { @@ -78,21 +78,12 @@ def test_user_map(self): bpy.data.objects[0]: {bpy.data.scenes[0]}, } for k, v in expected_map.items(): - assert k in user_map - assert user_map[k] == v + self.assertIn(k, user_map) + self.assertEqual(user_map[k], v) # Test handling of invalid parameters - try: - user_map = bpy.data.user_map(value_types={'FOOBAR'}) - assert 0 - except ValueError: - pass - - try: - user_map = bpy.data.user_map(subset=[bpy.data.objects[0], bpy.data.meshes[0], "FooBar"]) - assert 0 - except TypeError: - pass + self.assertRaises(ValueError, bpy.data.user_map, value_types={'FOOBAR'}) + self.assertRaises(TypeError, bpy.data.user_map, subset=[bpy.data.objects[0], bpy.data.meshes[0], "FooBar"]) TESTS = ( diff --git a/tests/python/bl_blendfile_utils.py b/tests/python/bl_blendfile_utils.py index 145080b0a22..0b410f9b798 100644 --- a/tests/python/bl_blendfile_utils.py +++ b/tests/python/bl_blendfile_utils.py @@ -5,9 +5,14 @@ import bpy import os import pprint +import unittest -class TestHelper: +class TestHelper(unittest.TestCase): + + def __init__(self, args): + super().__init__() + self.args = args @staticmethod def id_to_uid(id_data): @@ -48,7 +53,7 @@ class TestBlendLibLinkHelper(TestHelper): def __init__(self, args): assert hasattr(args, "src_test_dir") assert hasattr(args, "output_dir") - self.args = args + super().__init__(args) @staticmethod def reset_blender(): From 64bdc245b2499309999b7c43b7df46f8a3995f1f Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 17 Jan 2025 21:07:11 +0100 Subject: [PATCH 34/37] Fix warning in some compilers. Compilers do not seem to agree on valid printf format for `int64_t`, recent clang 19 on linux requires `%ld`, while older compilers like the buildbot ones of clang 15 on OSX ask for `%lld`. So instead, cast the value to `int32_t`. Other solution may have been to use `PRId64`, but this is fairly bad for readability. --- source/blender/blenkernel/intern/blendfile.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 665ecc6a912..ca93ee620d3 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -2175,9 +2175,9 @@ bool PartialWriteContext::write(const char *write_filepath, * currently this should never happen. */ if (make_local_libs.size() > 1) { CLOG_WARN(&LOG_PARTIALWRITE, - "%ld libraries found using the same filepath as destination one ('%s'), should " + "%d libraries found using the same filepath as destination one ('%s'), should " "never happen.", - make_local_libs.size(), + int32_t(make_local_libs.size()), write_filepath); } for (Library *lib : make_local_libs) { From 7ba732a9119396ee703f724717a8618dcddce6a6 Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Fri, 17 Jan 2025 21:30:22 +0100 Subject: [PATCH 35/37] Assets: Update submodule hash Related PR: blender/blender-assets#19 Ref: #131122 Pull Request: https://projects.blender.org/blender/blender/pulls/133219 --- release/datafiles/assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/datafiles/assets b/release/datafiles/assets index 60e97519eb4..16e30a5f673 160000 --- a/release/datafiles/assets +++ b/release/datafiles/assets @@ -1 +1 @@ -Subproject commit 60e97519eb4e9cc5edbaa93444497277eadaa7e5 +Subproject commit 16e30a5f6732f3d465774914ca341c70bb56467c From e8bc7ec68b7614c93f0abb005ea2a976bb2331dd Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Fri, 17 Jan 2025 22:44:21 +0100 Subject: [PATCH 36/37] Fix #83589: Remove Tooltips on Gizmo Mouse Down Currently tooltips on gizmo parts can pop up while holding your mouse down, unlike other UI elements. And they don't disappear until a few pixels into a drag. This PR solves both by removing tooltips on mouse down on a gizmo part. Pull Request: https://projects.blender.org/blender/blender/pulls/132576 --- source/blender/windowmanager/intern/wm_event_system.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 6638a286ca4..d2d206d50bb 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -3183,6 +3183,11 @@ static eHandlerActionFlag wm_handlers_do_gizmo_handler(bContext *C, BLI_assert(gzmap != nullptr); wmGizmo *gz = wm_gizmomap_highlight_get(gzmap); + if (gz && ISMOUSE(event->type) && event->val == KM_PRESS) { + /* Remove any tooltips on mouse down. #83589 */ + WM_tooltip_clear(C, CTX_wm_window(C)); + } + /* Needed so UI blocks over gizmos don't let events fall through to the gizmos, * noticeable for the node editor - where dragging on a node should move it, see: #73212. * note we still allow for starting the gizmo drag outside, then travel 'inside' the node. */ From 0a72db9f948f8e642dc303d28805089888be86cd Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Fri, 17 Jan 2025 22:59:39 +0100 Subject: [PATCH 37/37] Fix #133198: Overlay-Next: Texture Paint workspace crash Regression from 1406b9e656 Ensure it matches the Overlay Legacy behavior. --- .../draw/engines/overlay/overlay_next_mesh.hh | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/source/blender/draw/engines/overlay/overlay_next_mesh.hh b/source/blender/draw/engines/overlay/overlay_next_mesh.hh index 801c2919020..ad0e8cdd677 100644 --- a/source/blender/draw/engines/overlay/overlay_next_mesh.hh +++ b/source/blender/draw/engines/overlay/overlay_next_mesh.hh @@ -718,49 +718,51 @@ class MeshUVs : Overlay { Object &ob = *ob_ref.object; Mesh &mesh = *static_cast(ob.data); - const bool has_active_edit_uvmap = - (CustomData_get_active_layer(&mesh.runtime->edit_mesh->bm->ldata, CD_PROP_FLOAT2) != -1); - - if (!has_active_edit_uvmap) { - return; - } + const bool is_edit_object = DRW_object_is_in_edit_mode(&ob); + const bool has_active_object_uvmap = CustomData_get_active_layer(&mesh.corner_data, + CD_PROP_FLOAT2) != -1; + const bool has_active_edit_uvmap = is_edit_object && (CustomData_get_active_layer( + &mesh.runtime->edit_mesh->bm->ldata, + CD_PROP_FLOAT2) != -1); ResourceHandle res_handle = manager.unique_handle(ob_ref); - if (show_uv_edit) { - gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_edges(ob, mesh); - edges_ps_.draw_expand(geom, GPU_PRIM_TRIS, 2, 1, res_handle); - } - if (show_vert_) { - gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_verts(ob, mesh); - verts_ps_.draw(geom, res_handle); - } - if (show_face_dots_) { - gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_facedots(ob, mesh); - facedots_ps_.draw(geom, res_handle); - } - if (show_face_) { - gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_faces(ob, mesh); - faces_ps_.draw(geom, res_handle); - } - - if (show_mesh_analysis_) { - int index_3d, index_2d; - if (mesh_analysis_type_ == SI_UVDT_STRETCH_AREA) { - index_3d = per_mesh_area_3d_.append_and_get_index(nullptr); - index_2d = per_mesh_area_2d_.append_and_get_index(nullptr); + if (has_active_edit_uvmap) { + if (show_uv_edit) { + gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_edges(ob, mesh); + edges_ps_.draw_expand(geom, GPU_PRIM_TRIS, 2, 1, res_handle); + } + if (show_vert_) { + gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_verts(ob, mesh); + verts_ps_.draw(geom, res_handle); + } + if (show_face_dots_) { + gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_facedots(ob, mesh); + facedots_ps_.draw(geom, res_handle); + } + if (show_face_) { + gpu::Batch *geom = DRW_mesh_batch_cache_get_edituv_faces(ob, mesh); + faces_ps_.draw(geom, res_handle); } - gpu::Batch *geom = - mesh_analysis_type_ == SI_UVDT_STRETCH_ANGLE ? - DRW_mesh_batch_cache_get_edituv_faces_stretch_angle(ob, mesh) : - DRW_mesh_batch_cache_get_edituv_faces_stretch_area( - ob, mesh, &per_mesh_area_3d_[index_3d], &per_mesh_area_2d_[index_2d]); + if (show_mesh_analysis_) { + int index_3d, index_2d; + if (mesh_analysis_type_ == SI_UVDT_STRETCH_AREA) { + index_3d = per_mesh_area_3d_.append_and_get_index(nullptr); + index_2d = per_mesh_area_2d_.append_and_get_index(nullptr); + } + + gpu::Batch *geom = + mesh_analysis_type_ == SI_UVDT_STRETCH_ANGLE ? + DRW_mesh_batch_cache_get_edituv_faces_stretch_angle(ob, mesh) : + DRW_mesh_batch_cache_get_edituv_faces_stretch_area( + ob, mesh, &per_mesh_area_3d_[index_3d], &per_mesh_area_2d_[index_2d]); - analysis_ps_.draw(geom, res_handle); + analysis_ps_.draw(geom, res_handle); + } } - if (show_wireframe_) { + if (show_wireframe_ && (has_active_object_uvmap || has_active_edit_uvmap)) { gpu::Batch *geom = DRW_mesh_batch_cache_get_uv_edges(ob, mesh); wireframe_ps_.draw_expand(geom, GPU_PRIM_TRIS, 2, 1, res_handle); }