diff options
author | Joseph Eagar <joeedh@gmail.com> | 2021-09-16 23:44:21 +0300 |
---|---|---|
committer | Joseph Eagar <joeedh@gmail.com> | 2021-09-16 23:44:21 +0300 |
commit | 627edd1efabb0baaed3127bd127215ffb0ddfbac (patch) | |
tree | d42cf9d0de78dbdeb21c11a95c0fde2d4caf5fee | |
parent | 445889676bfd900a237acbacbedeaadc30881cc7 (diff) | |
parent | db7fca3588aab72e49a74cbb2c236f86c0e0e6c1 (diff) |
Merge branch 'master' into temp_bmesh_multires
545 files changed, 20234 insertions, 5513 deletions
diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake index e3183fe5b7f..cb4d196d43f 100644 --- a/build_files/cmake/platform/platform_win32.cmake +++ b/build_files/cmake/platform/platform_win32.cmake @@ -259,7 +259,7 @@ if(NOT DEFINED LIBDIR) else() message(FATAL_ERROR "32 bit compiler detected, blender no longer provides pre-build libraries for 32 bit windows, please set the LIBDIR cmake variable to your own library folder") endif() - if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.29.30130) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.30.30423) message(STATUS "Visual Studio 2022 detected.") set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15) elseif(MSVC_VERSION GREATER 1919) diff --git a/build_files/utils/make_update.py b/build_files/utils/make_update.py index 2b8c7af98fb..a6399790bc9 100755 --- a/build_files/utils/make_update.py +++ b/build_files/utils/make_update.py @@ -31,6 +31,7 @@ def parse_arguments(): parser.add_argument("--no-submodules", action="store_true") parser.add_argument("--use-tests", action="store_true") parser.add_argument("--svn-command", default="svn") + parser.add_argument("--svn-branch", default=None) parser.add_argument("--git-command", default="git") parser.add_argument("--use-centos-libraries", action="store_true") return parser.parse_args() @@ -46,7 +47,7 @@ def svn_update(args, release_version): svn_non_interactive = [args.svn_command, '--non-interactive'] lib_dirpath = os.path.join(get_blender_git_root(), '..', 'lib') - svn_url = make_utils.svn_libraries_base_url(release_version) + svn_url = make_utils.svn_libraries_base_url(release_version, args.svn_branch) # Checkout precompiled libraries if sys.platform == 'darwin': @@ -170,26 +171,28 @@ def submodules_update(args, release_version, branch): sys.stderr.write("git not found, can't update code\n") sys.exit(1) - # Update submodules to latest master or appropriate release branch. - if not release_version: - branch = "master" + # Update submodules to appropriate given branch, + # falling back to master if none is given and/or found in a sub-repository. + branch_fallback = "master" + if not branch: + branch = branch_fallback submodules = [ - ("release/scripts/addons", branch), - ("release/scripts/addons_contrib", branch), - ("release/datafiles/locale", branch), - ("source/tools", branch), + ("release/scripts/addons", branch, branch_fallback), + ("release/scripts/addons_contrib", branch, branch_fallback), + ("release/datafiles/locale", branch, branch_fallback), + ("source/tools", branch, branch_fallback), ] # Initialize submodules only if needed. - for submodule_path, submodule_branch in submodules: + for submodule_path, submodule_branch, submodule_branch_fallback in submodules: if not os.path.exists(os.path.join(submodule_path, ".git")): call([args.git_command, "submodule", "update", "--init", "--recursive"]) break # Checkout appropriate branch and pull changes. skip_msg = "" - for submodule_path, submodule_branch in submodules: + for submodule_path, submodule_branch, submodule_branch_fallback in submodules: cwd = os.getcwd() try: os.chdir(submodule_path) @@ -201,6 +204,11 @@ def submodules_update(args, release_version, branch): call([args.git_command, "fetch", "origin"]) call([args.git_command, "checkout", submodule_branch]) call([args.git_command, "pull", "--rebase", "origin", submodule_branch]) + # If we cannot find the specified branch for this submodule, fallback to default one (aka master). + if make_utils.git_branch(args.git_command) != submodule_branch: + call([args.git_command, "fetch", "origin"]) + call([args.git_command, "checkout", submodule_branch_fallback]) + call([args.git_command, "pull", "--rebase", "origin", submodule_branch_fallback]) finally: os.chdir(cwd) @@ -214,6 +222,10 @@ if __name__ == "__main__": # Test if we are building a specific release version. branch = make_utils.git_branch(args.git_command) + if branch == 'HEAD': + sys.stderr.write('Blender git repository is in detached HEAD state, must be in a branch\n') + sys.exit(1) + tag = make_utils.git_tag(args.git_command) release_version = make_utils.git_branch_release_version(branch, tag) diff --git a/build_files/utils/make_utils.py b/build_files/utils/make_utils.py index 9f928bb524d..db352ff7e16 100755 --- a/build_files/utils/make_utils.py +++ b/build_files/utils/make_utils.py @@ -70,9 +70,11 @@ def git_branch_release_version(branch, tag): return release_version -def svn_libraries_base_url(release_version): +def svn_libraries_base_url(release_version, branch=None): if release_version: svn_branch = "tags/blender-" + release_version + "-release" + elif branch: + svn_branch = "branches/" + branch else: svn_branch = "trunk" return "https://svn.blender.org/svnroot/bf-blender/" + svn_branch + "/lib/" diff --git a/build_files/windows/icons.cmd b/build_files/windows/icons.cmd index 473a40885a8..d51b27d8953 100644 --- a/build_files/windows/icons.cmd +++ b/build_files/windows/icons.cmd @@ -1,4 +1,4 @@ -if EXIST %PYTHON% ( +if EXIST "%PYTHON%" ( goto detect_python_done ) diff --git a/doc/python_api/requirements.txt b/doc/python_api/requirements.txt index b5a9d15bf7b..51440046430 100644 --- a/doc/python_api/requirements.txt +++ b/doc/python_api/requirements.txt @@ -10,4 +10,4 @@ requests==2.26.0 # Only needed to match the theme used for the official documentation. # Without this theme, the default theme will be used. -sphinx_rtd_theme==1.0.0rc1 +sphinx_rtd_theme==1.0.0 diff --git a/extern/mantaflow/UPDATE.sh b/extern/mantaflow/UPDATE.sh index aed4e2a9b71..1158ff13455 100644 --- a/extern/mantaflow/UPDATE.sh +++ b/extern/mantaflow/UPDATE.sh @@ -8,7 +8,7 @@ # YOUR INSTALLATION PATHS GO HERE: MANTA_INSTALLATION=/Users/sebbas/Developer/Mantaflow/mantaflowDevelop -BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender/fluid-mantaflow +BLENDER_INSTALLATION=/Users/sebbas/Developer/Blender # Try to check out Mantaflow repository before building? CLEAN_REPOSITORY=0 diff --git a/extern/mantaflow/helper/pwrapper/pconvert.cpp b/extern/mantaflow/helper/pwrapper/pconvert.cpp index 7c66cdc7e72..5a7a32c5a73 100644 --- a/extern/mantaflow/helper/pwrapper/pconvert.cpp +++ b/extern/mantaflow/helper/pwrapper/pconvert.cpp @@ -28,11 +28,13 @@ extern PyTypeObject PbVec3Type; extern PyTypeObject PbVec4Type; struct PbVec3 { - PyObject_HEAD float data[3]; + PyObject_HEAD + float data[3]; }; struct PbVec4 { - PyObject_HEAD float data[4]; + PyObject_HEAD + float data[4]; }; PyObject *getPyNone() diff --git a/extern/mantaflow/helper/pwrapper/pvec3.cpp b/extern/mantaflow/helper/pwrapper/pvec3.cpp index 1dca44d5e5c..4d07a201cfe 100644 --- a/extern/mantaflow/helper/pwrapper/pvec3.cpp +++ b/extern/mantaflow/helper/pwrapper/pvec3.cpp @@ -25,7 +25,8 @@ namespace Manta { extern PyTypeObject PbVec3Type; struct PbVec3 { - PyObject_HEAD float data[3]; + PyObject_HEAD + float data[3]; }; static void PbVec3Dealloc(PbVec3 *self) @@ -293,7 +294,8 @@ inline PyObject *castPy(PyTypeObject *p) extern PyTypeObject PbVec4Type; struct PbVec4 { - PyObject_HEAD float data[4]; + PyObject_HEAD + float data[4]; }; static PyMethodDef PbVec4Methods[] = { diff --git a/extern/mantaflow/helper/pwrapper/registry.cpp b/extern/mantaflow/helper/pwrapper/registry.cpp index f88c2aa708e..5196c0409f8 100644 --- a/extern/mantaflow/helper/pwrapper/registry.cpp +++ b/extern/mantaflow/helper/pwrapper/registry.cpp @@ -76,7 +76,8 @@ struct ClassData { }; struct PbObject { - PyObject_HEAD Manta::PbClass *instance; + PyObject_HEAD + Manta::PbClass *instance; ClassData *classdef; }; diff --git a/extern/mantaflow/preprocessed/fastmarch.cpp b/extern/mantaflow/preprocessed/fastmarch.cpp index 956725e523c..31e43483b49 100644 --- a/extern/mantaflow/preprocessed/fastmarch.cpp +++ b/extern/mantaflow/preprocessed/fastmarch.cpp @@ -874,6 +874,136 @@ static const Vec3i nb[6] = {Vec3i(1, 0, 0), Vec3i(0, 0, 1), Vec3i(0, 0, -1)}; +struct knMarkSkipCells : public KernelBase { + knMarkSkipCells(Grid<Real> &phi, Grid<int> &tmp, bool inside) + : KernelBase(&phi, 1), phi(phi), tmp(tmp), inside(inside) + { + runMessage(); + run(); + } + inline void op(int i, int j, int k, Grid<Real> &phi, Grid<int> &tmp, bool inside) const + { + if (!inside && phi(i, j, k) < 0.) { + tmp(i, j, k) = 1; + } + if (inside && phi(i, j, k) > 0.) { + tmp(i, j, k) = 1; + } + } + inline Grid<Real> &getArg0() + { + return phi; + } + typedef Grid<Real> type0; + inline Grid<int> &getArg1() + { + return tmp; + } + typedef Grid<int> type1; + inline bool &getArg2() + { + return inside; + } + typedef bool type2; + void runMessage() + { + debMsg("Executing kernel knMarkSkipCells ", 3); + debMsg("Kernel range" + << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", + 4); + }; + void operator()(const tbb::blocked_range<IndexInt> &__r) const + { + const int _maxX = maxX; + const int _maxY = maxY; + if (maxZ > 1) { + for (int k = __r.begin(); k != (int)__r.end(); k++) + for (int j = 1; j < _maxY; j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, phi, tmp, inside); + } + else { + const int k = 0; + for (int j = __r.begin(); j != (int)__r.end(); j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, phi, tmp, inside); + } + } + void run() + { + if (maxZ > 1) + tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this); + else + tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this); + } + Grid<Real> φ + Grid<int> &tmp; + bool inside; +}; + +struct knSetFirstLayer : public KernelBase { + knSetFirstLayer(Grid<int> &tmp, int dim) : KernelBase(&tmp, 1), tmp(tmp), dim(dim) + { + runMessage(); + run(); + } + inline void op(int i, int j, int k, Grid<int> &tmp, int dim) const + { + Vec3i p(i, j, k); + if (tmp(p)) + return; + for (int n = 0; n < 2 * dim; ++n) { + if (tmp(p + nb[n]) == 1) { + tmp(i, j, k) = 2; + break; + } + } + } + inline Grid<int> &getArg0() + { + return tmp; + } + typedef Grid<int> type0; + inline int &getArg1() + { + return dim; + } + typedef int type1; + void runMessage() + { + debMsg("Executing kernel knSetFirstLayer ", 3); + debMsg("Kernel range" + << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", + 4); + }; + void operator()(const tbb::blocked_range<IndexInt> &__r) const + { + const int _maxX = maxX; + const int _maxY = maxY; + if (maxZ > 1) { + for (int k = __r.begin(); k != (int)__r.end(); k++) + for (int j = 1; j < _maxY; j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, tmp, dim); + } + else { + const int k = 0; + for (int j = __r.begin(); j != (int)__r.end(); j++) + for (int i = 1; i < _maxX; i++) + op(i, j, k, tmp, dim); + } + } + void run() + { + if (maxZ > 1) + tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this); + else + tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this); + } + Grid<int> &tmp; + int dim; +}; + template<class S> struct knExtrapolateLsSimple : public KernelBase { knExtrapolateLsSimple(Grid<S> &val, int distance, Grid<int> &tmp, const int d, S direction) : KernelBase(&val, 1), val(val), distance(distance), tmp(tmp), d(d), direction(direction) @@ -1043,39 +1173,12 @@ void extrapolateLsSimple(Grid<Real> &phi, int distance = 4, bool inside = false) tmp.clear(); const int dim = (phi.is3D() ? 3 : 2); - // by default, march outside - Real direction = 1.; - if (!inside) { - // mark all inside - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) < 0.) { - tmp(i, j, k) = 1; - } - } - } - else { - direction = -1.; - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) > 0.) { - tmp(i, j, k) = 1; - } - } - } + // by default, march outside (ie mark all inside to be skipped) + Real direction = (inside) ? -1. : 1.; + knMarkSkipCells(phi, tmp, inside); + // + first layer around - FOR_IJK_BND(phi, 1) - { - Vec3i p(i, j, k); - if (tmp(p)) - continue; - for (int n = 0; n < 2 * dim; ++n) { - if (tmp(p + nb[n]) == 1) { - tmp(i, j, k) = 2; - n = 2 * dim; - } - } - } + knSetFirstLayer(tmp, dim); // extrapolate for distance for (int d = 2; d < 1 + distance; ++d) { @@ -1126,37 +1229,12 @@ void extrapolateVec3Simple(Grid<Vec3> &vel, Grid<Real> &phi, int distance = 4, b tmp.clear(); const int dim = (vel.is3D() ? 3 : 2); - // mark initial cells, by default, march outside - if (!inside) { - // mark all inside - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) < 0.) { - tmp(i, j, k) = 1; - } - } - } - else { - FOR_IJK_BND(phi, 1) - { - if (phi(i, j, k) > 0.) { - tmp(i, j, k) = 1; - } - } - } + // mark initial cells, by default, march outside (ie mark all inside to be skipped) + Real direction = (inside) ? -1. : 1.; + knMarkSkipCells(phi, tmp, inside); + // + first layer next to initial cells - FOR_IJK_BND(vel, 1) - { - Vec3i p(i, j, k); - if (tmp(p)) - continue; - for (int n = 0; n < 2 * dim; ++n) { - if (tmp(p + nb[n]) == 1) { - tmp(i, j, k) = 2; - n = 2 * dim; - } - } - } + knSetFirstLayer(tmp, dim); for (int d = 2; d < 1 + distance; ++d) { knExtrapolateLsSimple<Vec3>(vel, distance, tmp, d, Vec3(0.)); diff --git a/extern/mantaflow/preprocessed/gitinfo.h b/extern/mantaflow/preprocessed/gitinfo.h index 6bc92278a33..6d367b764af 100644 --- a/extern/mantaflow/preprocessed/gitinfo.h +++ b/extern/mantaflow/preprocessed/gitinfo.h @@ -1,3 +1,3 @@ -#define MANTA_GIT_VERSION "commit 8fbebe02459b7f72575872c20961f7cb757db408" +#define MANTA_GIT_VERSION "commit d5d9a6c28daa8f21426d7a285f48639c0d8fd13f" diff --git a/extern/quadriflow/src/loader.cpp b/extern/quadriflow/src/loader.cpp index aa27066e6e4..1aa50a40fc3 100644 --- a/extern/quadriflow/src/loader.cpp +++ b/extern/quadriflow/src/loader.cpp @@ -10,6 +10,7 @@ #include <fstream> #include <unordered_map> +#include <functional> namespace qflow { @@ -69,7 +70,7 @@ void load(const char* filename, MatrixXd& V, MatrixXi& F) }; /// Hash function for obj_vertex - struct obj_vertexHash : std::unary_function<obj_vertex, size_t> { + struct obj_vertexHash : std::function<size_t(obj_vertex)> { std::size_t operator()(const obj_vertex &v) const { size_t hash = std::hash<uint32_t>()(v.p); hash = hash * 37 + std::hash<uint32_t>()(v.uv); diff --git a/intern/cycles/blender/blender_curves.cpp b/intern/cycles/blender/blender_curves.cpp index 85d886fd850..6fe5ea41fff 100644 --- a/intern/cycles/blender/blender_curves.cpp +++ b/intern/cycles/blender/blender_curves.cpp @@ -526,8 +526,13 @@ bool BlenderSync::object_has_particle_hair(BL::Object b_ob) /* Old particle hair. */ void BlenderSync::sync_particle_hair( - Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step) + Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step) { + if (!b_ob_info.is_real_object_data()) { + return; + } + BL::Object b_ob = b_ob_info.real_object; + /* obtain general settings */ if (b_ob.mode() == b_ob.mode_PARTICLE_EDIT || b_ob.mode() == b_ob.mode_EDIT) { return; @@ -788,10 +793,10 @@ static void export_hair_curves_motion(Hair *hair, BL::Hair b_hair, int motion_st } /* Hair object. */ -void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step) +void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) { /* Convert Blender hair to Cycles curves. */ - BL::Hair b_hair(b_ob.data()); + BL::Hair b_hair(b_ob_info.object_data); if (motion) { export_hair_curves_motion(hair, b_hair, motion_step); } @@ -800,16 +805,16 @@ void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motio } } #else -void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step) +void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) { (void)hair; - (void)b_ob; + (void)b_ob_info; (void)motion; (void)motion_step; } #endif -void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair) +void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair) { /* make a copy of the shaders as the caller in the main thread still need them for syncing the * attributes */ @@ -819,19 +824,19 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha new_hair.set_used_shaders(used_shaders); if (view_layer.use_hair) { - if (b_ob.type() == BL::Object::type_HAIR) { + if (b_ob_info.object_data.is_a(&RNA_Hair)) { /* Hair object. */ - sync_hair(&new_hair, b_ob, false); + sync_hair(&new_hair, b_ob_info, false); } else { /* Particle hair. */ bool need_undeformed = new_hair.need_attribute(scene, ATTR_STD_GENERATED); BL::Mesh b_mesh = object_to_mesh( - b_data, b_ob, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE); + b_data, b_ob_info, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE); if (b_mesh) { - sync_particle_hair(&new_hair, b_mesh, b_ob, false); - free_object_to_mesh(b_data, b_ob, b_mesh); + sync_particle_hair(&new_hair, b_mesh, b_ob_info, false); + free_object_to_mesh(b_data, b_ob_info, b_mesh); } } } @@ -859,7 +864,7 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *ha } void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, - BL::Object b_ob, + BObjectInfo &b_ob_info, Hair *hair, int motion_step) { @@ -869,18 +874,19 @@ void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, } /* Export deformed coordinates. */ - if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) { - if (b_ob.type() == BL::Object::type_HAIR) { + if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) { + if (b_ob_info.object_data.is_a(&RNA_Hair)) { /* Hair object. */ - sync_hair(hair, b_ob, true, motion_step); + sync_hair(hair, b_ob_info, true, motion_step); return; } else { /* Particle hair. */ - BL::Mesh b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE); + BL::Mesh b_mesh = object_to_mesh( + b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); if (b_mesh) { - sync_particle_hair(hair, b_mesh, b_ob, true, motion_step); - free_object_to_mesh(b_data, b_ob, b_mesh); + sync_particle_hair(hair, b_mesh, b_ob_info, true, motion_step); + free_object_to_mesh(b_data, b_ob_info, b_mesh); return; } } diff --git a/intern/cycles/blender/blender_geometry.cpp b/intern/cycles/blender/blender_geometry.cpp index a009018f357..b1de37dac10 100644 --- a/intern/cycles/blender/blender_geometry.cpp +++ b/intern/cycles/blender/blender_geometry.cpp @@ -29,13 +29,15 @@ CCL_NAMESPACE_BEGIN -static Geometry::Type determine_geom_type(BL::Object &b_ob, bool use_particle_hair) +static Geometry::Type determine_geom_type(BObjectInfo &b_ob_info, bool use_particle_hair) { - if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) { return Geometry::HAIR; } - if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { + if (b_ob_info.object_data.is_a(&RNA_Volume) || + (b_ob_info.object_data == b_ob_info.real_object.data() && + object_fluid_gas_domain_find(b_ob_info.real_object))) { return Geometry::VOLUME; } @@ -71,20 +73,17 @@ array<Node *> BlenderSync::find_used_shaders(BL::Object &b_ob) } Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, bool object_updated, bool use_particle_hair, TaskPool *task_pool) { /* Test if we can instance or if the object is modified. */ - BL::ID b_ob_data = b_ob.data(); - BL::ID b_key_id = (BKE_object_is_modified(b_ob)) ? b_ob_instance : b_ob_data; - Geometry::Type geom_type = determine_geom_type(b_ob, use_particle_hair); - GeometryKey key(b_key_id.ptr.data, geom_type); + Geometry::Type geom_type = determine_geom_type(b_ob_info, use_particle_hair); + GeometryKey key(b_ob_info.object_data, geom_type); /* Find shader indices. */ - array<Node *> used_shaders = find_used_shaders(b_ob); + array<Node *> used_shaders = find_used_shaders(b_ob_info.iter_object); /* Ensure we only sync instanced geometry once. */ Geometry *geom = geometry_map.find(key); @@ -111,7 +110,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, } else { /* Test if we need to update existing geometry. */ - sync = geometry_map.update(geom, b_key_id); + sync = geometry_map.update(geom, b_ob_info.object_data); } if (!sync) { @@ -144,7 +143,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, geometry_synced.insert(geom); - geom->name = ustring(b_ob_data.name().c_str()); + geom->name = ustring(b_ob_info.object_data.name().c_str()); /* Store the shaders immediately for the object attribute code. */ geom->set_used_shaders(used_shaders); @@ -153,19 +152,19 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, if (progress.get_cancel()) return; - progress.set_sync_status("Synchronizing object", b_ob.name()); + progress.set_sync_status("Synchronizing object", b_ob_info.real_object.name()); if (geom_type == Geometry::HAIR) { Hair *hair = static_cast<Hair *>(geom); - sync_hair(b_depsgraph, b_ob, hair); + sync_hair(b_depsgraph, b_ob_info, hair); } else if (geom_type == Geometry::VOLUME) { Volume *volume = static_cast<Volume *>(geom); - sync_volume(b_ob, volume); + sync_volume(b_ob_info, volume); } else { Mesh *mesh = static_cast<Mesh *>(geom); - sync_mesh(b_depsgraph, b_ob, mesh); + sync_mesh(b_depsgraph, b_ob_info, mesh); } }; @@ -181,7 +180,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, } void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Object *object, float motion_time, bool use_particle_hair, @@ -190,8 +189,10 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, /* Ensure we only sync instanced geometry once. */ Geometry *geom = object->get_geometry(); - if (geometry_motion_synced.find(geom) != geometry_motion_synced.end()) + if (geometry_motion_synced.find(geom) != geometry_motion_synced.end() || + geometry_motion_attribute_synced.find(geom) != geometry_motion_attribute_synced.end()) { return; + } geometry_motion_synced.insert(geom); @@ -210,16 +211,17 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, if (progress.get_cancel()) return; - if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + if (b_ob_info.object_data.is_a(&RNA_Hair) || use_particle_hair) { Hair *hair = static_cast<Hair *>(geom); - sync_hair_motion(b_depsgraph, b_ob, hair, motion_step); + sync_hair_motion(b_depsgraph, b_ob_info, hair, motion_step); } - else if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { + else if (b_ob_info.object_data.is_a(&RNA_Volume) || + object_fluid_gas_domain_find(b_ob_info.real_object)) { /* No volume motion blur support yet. */ } else { Mesh *mesh = static_cast<Mesh *>(geom); - sync_mesh_motion(b_depsgraph, b_ob, mesh, motion_step); + sync_mesh_motion(b_depsgraph, b_ob_info, mesh, motion_step); } }; diff --git a/intern/cycles/blender/blender_light.cpp b/intern/cycles/blender/blender_light.cpp index 50cd9e3db5c..542028f4b2f 100644 --- a/intern/cycles/blender/blender_light.cpp +++ b/intern/cycles/blender/blender_light.cpp @@ -27,15 +27,14 @@ CCL_NAMESPACE_BEGIN void BlenderSync::sync_light(BL::Object &b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, int random_id, Transform &tfm, bool *use_portal) { /* test if we need to sync */ - ObjectKey key(b_parent, persistent_id, b_ob_instance, false); - BL::Light b_light(b_ob.data()); + ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, false); + BL::Light b_light(b_ob_info.object_data); Light *light = light_map.find(key); @@ -44,7 +43,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, const bool tfm_updated = (light && light->get_tfm() != tfm); /* Update if either object or light data changed. */ - if (!light_map.add_or_update(&light, b_ob, b_parent, key) && !tfm_updated) { + if (!light_map.add_or_update(&light, b_ob_info.real_object, b_parent, key) && !tfm_updated) { Shader *shader; if (!shader_map.add_or_update(&shader, b_light)) { if (light->get_is_portal()) @@ -139,11 +138,11 @@ void BlenderSync::sync_light(BL::Object &b_parent, light->set_max_bounces(get_int(clight, "max_bounces")); - if (b_ob != b_ob_instance) { + if (b_ob_info.real_object != b_ob_info.iter_object) { light->set_random_id(random_id); } else { - light->set_random_id(hash_uint2(hash_string(b_ob.name().c_str()), 0)); + light->set_random_id(hash_uint2(hash_string(b_ob_info.real_object.name().c_str()), 0)); } if (light->get_light_type() == LIGHT_AREA) @@ -155,7 +154,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, *use_portal = true; /* visibility */ - uint visibility = object_ray_visibility(b_ob); + uint visibility = object_ray_visibility(b_ob_info.real_object); light->set_use_diffuse((visibility & PATH_RAY_DIFFUSE) != 0); light->set_use_glossy((visibility & PATH_RAY_GLOSSY) != 0); light->set_use_transmission((visibility & PATH_RAY_TRANSMIT) != 0); diff --git a/intern/cycles/blender/blender_mesh.cpp b/intern/cycles/blender/blender_mesh.cpp index ebba6981502..7ec430eb7fe 100644 --- a/intern/cycles/blender/blender_mesh.cpp +++ b/intern/cycles/blender/blender_mesh.cpp @@ -347,16 +347,57 @@ static void fill_generic_attribute(BL::Mesh &b_mesh, } } -static void attr_create_generic(Scene *scene, Mesh *mesh, BL::Mesh &b_mesh, bool subdivision) +static void attr_create_motion(Mesh *mesh, BL::Attribute &b_attribute, const float motion_scale) +{ + if (!(b_attribute.domain() == BL::Attribute::domain_POINT) && + (b_attribute.data_type() == BL::Attribute::data_type_FLOAT_VECTOR)) { + return; + } + + BL::FloatVectorAttribute b_vector_attribute(b_attribute); + const int numverts = mesh->get_verts().size(); + + /* Find or add attribute */ + float3 *P = &mesh->get_verts()[0]; + Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + + if (!attr_mP) { + attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); + } + + /* Only export previous and next frame, we don't have any in between data. */ + float motion_times[2] = {-1.0f, 1.0f}; + for (int step = 0; step < 2; step++) { + const float relative_time = motion_times[step] * 0.5f * motion_scale; + float3 *mP = attr_mP->data_float3() + step * numverts; + + for (int i = 0; i < numverts; i++) { + mP[i] = P[i] + get_float3(b_vector_attribute.data[i].vector()) * relative_time; + } + } +} + +static void attr_create_generic(Scene *scene, + Mesh *mesh, + BL::Mesh &b_mesh, + const bool subdivision, + const bool need_motion, + const float motion_scale) { if (subdivision) { /* TODO: Handle subdivision correctly. */ return; } AttributeSet &attributes = mesh->attributes; + static const ustring u_velocity("velocity"); for (BL::Attribute &b_attribute : b_mesh.attributes) { const ustring name{b_attribute.name().c_str()}; + + if (need_motion && name == u_velocity) { + attr_create_motion(mesh, b_attribute, motion_scale); + } + if (!mesh->need_attribute(scene, name)) { continue; } @@ -859,8 +900,10 @@ static void create_mesh(Scene *scene, Mesh *mesh, BL::Mesh &b_mesh, const array<Node *> &used_shaders, - bool subdivision = false, - bool subdivide_uvs = true) + const bool need_motion, + const float motion_scale, + const bool subdivision = false, + const bool subdivide_uvs = true) { /* count vertices and faces */ int numverts = b_mesh.vertices.length(); @@ -974,7 +1017,7 @@ static void create_mesh(Scene *scene, attr_create_vertex_color(scene, mesh, b_mesh, subdivision); attr_create_sculpt_vertex_color(scene, mesh, b_mesh, subdivision); attr_create_random_per_island(scene, mesh, b_mesh, subdivision); - attr_create_generic(scene, mesh, b_mesh, subdivision); + attr_create_generic(scene, mesh, b_mesh, subdivision, need_motion, motion_scale); if (subdivision) { attr_create_subd_uv_map(scene, mesh, b_mesh, subdivide_uvs); @@ -999,16 +1042,20 @@ static void create_mesh(Scene *scene, static void create_subd_mesh(Scene *scene, Mesh *mesh, - BL::Object &b_ob, + BObjectInfo &b_ob_info, BL::Mesh &b_mesh, const array<Node *> &used_shaders, + const bool need_motion, + const float motion_scale, float dicing_rate, int max_subdivisions) { + BL::Object b_ob = b_ob_info.real_object; + BL::SubsurfModifier subsurf_mod(b_ob.modifiers[b_ob.modifiers.length() - 1]); bool subdivide_uvs = subsurf_mod.uv_smooth() != BL::SubsurfModifier::uv_smooth_NONE; - create_mesh(scene, mesh, b_mesh, used_shaders, true, subdivide_uvs); + create_mesh(scene, mesh, b_mesh, used_shaders, need_motion, motion_scale, true, subdivide_uvs); /* export creases */ size_t num_creases = 0; @@ -1043,7 +1090,7 @@ static void create_subd_mesh(Scene *scene, * * NOTE: This code is run prior to object motion blur initialization. so can not access properties * set by `sync_object_motion_init()`. */ -static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) +static bool mesh_need_motion_attribute(BObjectInfo &b_ob_info, Scene *scene) { const Scene::MotionType need_motion = scene->need_motion(); if (need_motion == Scene::MOTION_NONE) { @@ -1060,7 +1107,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) * - Motion attribute expects non-zero time steps. * * Avoid adding motion attributes if the motion blur will enforce 0 motion steps. */ - PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles"); + PointerRNA cobject = RNA_pointer_get(&b_ob_info.real_object.ptr, "cycles"); const bool use_motion = get_boolean(cobject, "use_motion_blur"); if (!use_motion) { return false; @@ -1072,92 +1119,7 @@ static bool mesh_need_motion_attribute(BL::Object &b_ob, Scene *scene) return true; } -static void sync_mesh_cached_velocities(BL::Object &b_ob, Scene *scene, Mesh *mesh) -{ - if (!mesh_need_motion_attribute(b_ob, scene)) { - return; - } - - BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob, true, nullptr); - - if (!b_mesh_cache) { - return; - } - - if (!MeshSequenceCacheModifier_read_velocity_get(&b_mesh_cache.ptr)) { - return; - } - - const size_t numverts = mesh->get_verts().size(); - - if (b_mesh_cache.vertex_velocities.length() != numverts) { - return; - } - - /* Find or add attribute */ - float3 *P = &mesh->get_verts()[0]; - Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - - if (!attr_mP) { - attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); - } - - /* Only export previous and next frame, we don't have any in between data. */ - float motion_times[2] = {-1.0f, 1.0f}; - for (int step = 0; step < 2; step++) { - const float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f; - float3 *mP = attr_mP->data_float3() + step * numverts; - - BL::MeshSequenceCacheModifier::vertex_velocities_iterator vvi; - int i = 0; - - for (b_mesh_cache.vertex_velocities.begin(vvi); vvi != b_mesh_cache.vertex_velocities.end(); - ++vvi, ++i) { - mP[i] = P[i] + get_float3(vvi->velocity()) * relative_time; - } - } -} - -static void sync_mesh_fluid_motion(BL::Object &b_ob, Scene *scene, Mesh *mesh) -{ - if (!mesh_need_motion_attribute(b_ob, scene)) { - return; - } - - BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob); - - if (!b_fluid_domain) - return; - - /* If the mesh has modifiers following the fluid domain we can't export motion. */ - if (b_fluid_domain.mesh_vertices.length() != mesh->get_verts().size()) - return; - - /* Find or add attribute */ - float3 *P = &mesh->get_verts()[0]; - Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - - if (!attr_mP) { - attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); - } - - /* Only export previous and next frame, we don't have any in between data. */ - float motion_times[2] = {-1.0f, 1.0f}; - for (int step = 0; step < 2; step++) { - float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f; - float3 *mP = attr_mP->data_float3() + step * mesh->get_verts().size(); - - BL::FluidDomainSettings::mesh_vertices_iterator svi; - int i = 0; - - for (b_fluid_domain.mesh_vertices.begin(svi); svi != b_fluid_domain.mesh_vertices.end(); - ++svi, ++i) { - mP[i] = P[i] + get_float3(svi->velocity()) * relative_time; - } - } -} - -void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh) +void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh) { /* make a copy of the shaders as the caller in the main thread still need them for syncing the * attributes */ @@ -1170,37 +1132,47 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me /* Adaptive subdivision setup. Not for baking since that requires * exact mapping to the Blender mesh. */ if (!scene->bake_manager->get_baking()) { - new_mesh.set_subdivision_type(object_subdivision_type(b_ob, preview, experimental)); + new_mesh.set_subdivision_type( + object_subdivision_type(b_ob_info.real_object, preview, experimental)); } /* For some reason, meshes do not need this... */ bool need_undeformed = new_mesh.need_attribute(scene, ATTR_STD_GENERATED); BL::Mesh b_mesh = object_to_mesh( - b_data, b_ob, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type()); + b_data, b_ob_info, b_depsgraph, need_undeformed, new_mesh.get_subdivision_type()); if (b_mesh) { + /* Motion blur attribute is relative to seconds, we need it relative to frames. */ + const bool need_motion = mesh_need_motion_attribute(b_ob_info, scene); + const float motion_scale = (need_motion) ? + scene->motion_shutter_time() / + (b_scene.render().fps() / b_scene.render().fps_base()) : + 0.0f; + /* Sync mesh itself. */ if (new_mesh.get_subdivision_type() != Mesh::SUBDIVISION_NONE) create_subd_mesh(scene, &new_mesh, - b_ob, + b_ob_info, b_mesh, new_mesh.get_used_shaders(), + need_motion, + motion_scale, dicing_rate, max_subdivisions); else - create_mesh(scene, &new_mesh, b_mesh, new_mesh.get_used_shaders(), false); - - free_object_to_mesh(b_data, b_ob, b_mesh); + create_mesh(scene, + &new_mesh, + b_mesh, + new_mesh.get_used_shaders(), + need_motion, + motion_scale, + false); + + free_object_to_mesh(b_data, b_ob_info, b_mesh); } } - /* cached velocities (e.g. from alembic archive) */ - sync_mesh_cached_velocities(b_ob, scene, &new_mesh); - - /* mesh fluid motion mantaflow */ - sync_mesh_fluid_motion(b_ob, scene, &new_mesh); - /* update original sockets */ mesh->clear_non_sockets(); @@ -1230,22 +1202,10 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *me } void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, - BL::Object b_ob, + BObjectInfo &b_ob_info, Mesh *mesh, int motion_step) { - /* Fluid motion blur already exported. */ - BL::FluidDomainSettings b_fluid_domain = object_fluid_liquid_domain_find(b_ob); - if (b_fluid_domain) { - return; - } - - /* Cached motion blur already exported. */ - BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob, true, nullptr); - if (mesh_cache) { - return; - } - /* Skip if no vertices were exported. */ size_t numverts = mesh->get_verts().size(); if (numverts == 0) { @@ -1255,11 +1215,13 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, /* Skip objects without deforming modifiers. this is not totally reliable, * would need a more extensive check to see which objects are animated. */ BL::Mesh b_mesh(PointerRNA_NULL); - if (ccl::BKE_object_is_deform_modified(b_ob, b_scene, preview)) { + if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) { /* get derived mesh */ - b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE); + b_mesh = object_to_mesh(b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); } + const std::string ob_name = b_ob_info.real_object.name(); + /* TODO(sergey): Perform preliminary check for number of vertices. */ if (b_mesh) { /* Export deformed coordinates. */ @@ -1295,17 +1257,17 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, memcmp(mP, &mesh->get_verts()[0], sizeof(float3) * numverts) == 0) { /* no motion, remove attributes again */ if (b_mesh.vertices.length() != numverts) { - VLOG(1) << "Topology differs, disabling motion blur for object " << b_ob.name(); + VLOG(1) << "Topology differs, disabling motion blur for object " << ob_name; } else { - VLOG(1) << "No actual deformation motion for object " << b_ob.name(); + VLOG(1) << "No actual deformation motion for object " << ob_name; } mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION); if (attr_mN) mesh->attributes.remove(ATTR_STD_MOTION_VERTEX_NORMAL); } else if (motion_step > 0) { - VLOG(1) << "Filling deformation motion for object " << b_ob.name(); + VLOG(1) << "Filling deformation motion for object " << ob_name; /* motion, fill up previous steps that we might have skipped because * they had no motion, but we need them anyway now */ float3 *P = &mesh->get_verts()[0]; @@ -1319,8 +1281,8 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, } else { if (b_mesh.vertices.length() != numverts) { - VLOG(1) << "Topology differs, discarding motion blur for object " << b_ob.name() - << " at time " << motion_step; + VLOG(1) << "Topology differs, discarding motion blur for object " << ob_name << " at time " + << motion_step; memcpy(mP, &mesh->get_verts()[0], sizeof(float3) * numverts); if (mN != NULL) { memcpy(mN, attr_N->data_float3(), sizeof(float3) * numverts); @@ -1328,7 +1290,7 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph, } } - free_object_to_mesh(b_data, b_ob, b_mesh); + free_object_to_mesh(b_data, b_ob_info, b_mesh); return; } diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index 5d98b61b409..22d6edeb099 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -154,7 +154,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, const bool is_instance = b_instance.is_instance(); BL::Object b_ob = b_instance.object(); BL::Object b_parent = is_instance ? b_instance.parent() : b_instance.object(); - BL::Object b_ob_instance = is_instance ? b_instance.instance_object() : b_ob; + BObjectInfo b_ob_info{b_ob, is_instance ? b_instance.instance_object() : b_ob, b_ob.data()}; const bool motion = motion_time != 0.0f; /*const*/ Transform tfm = get_transform(b_ob.matrix_world()); int *persistent_id = NULL; @@ -178,8 +178,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, { sync_light(b_parent, persistent_id, - b_ob, - b_ob_instance, + b_ob_info, is_instance ? b_instance.random_id() : 0, tfm, use_portal); @@ -231,7 +230,7 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, TaskPool *object_geom_task_pool = (is_instance) ? NULL : geom_task_pool; /* key to lookup object */ - ObjectKey key(b_parent, persistent_id, b_ob_instance, use_particle_hair); + ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, use_particle_hair); Object *object; /* motion vector case */ @@ -249,12 +248,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, /* mesh deformation */ if (object->get_geometry()) - sync_geometry_motion(b_depsgraph, - b_ob_instance, - object, - motion_time, - use_particle_hair, - object_geom_task_pool); + sync_geometry_motion( + b_depsgraph, b_ob_info, object, motion_time, use_particle_hair, object_geom_task_pool); } return object; @@ -265,15 +260,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, (tfm != object->get_tfm()); /* mesh sync */ - /* b_ob is owned by the iterator and will go out of scope at the end of the block. - * b_ob_instance is the original object and will remain valid for deferred geometry - * sync. */ - Geometry *geometry = sync_geometry(b_depsgraph, - b_ob_instance, - b_ob_instance, - object_updated, - use_particle_hair, - object_geom_task_pool); + Geometry *geometry = sync_geometry( + b_depsgraph, b_ob_info, object_updated, use_particle_hair, object_geom_task_pool); object->set_geometry(geometry); /* special case not tracked by object update flags */ @@ -616,7 +604,7 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph, * only available in preview renders since currently do not have a good cache policy, the * data being loaded at once for all the frames. */ if (experimental && b_v3d) { - b_mesh_cache = object_mesh_cache_find(b_ob, false, &has_subdivision_modifier); + b_mesh_cache = object_mesh_cache_find(b_ob, &has_subdivision_modifier); use_procedural = b_mesh_cache && b_mesh_cache.cache_file().use_render_procedural(); } @@ -731,6 +719,14 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, } } + /* Check which geometry already has motion blur so it can be skipped. */ + geometry_motion_attribute_synced.clear(); + for (Geometry *geom : scene->geometry) { + if (geom->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION)) { + geometry_motion_attribute_synced.insert(geom); + } + } + /* note iteration over motion_times set happens in sorted order */ foreach (float relative_time, motion_times) { /* center time is already handled. */ @@ -761,6 +757,8 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, sync_objects(b_depsgraph, b_v3d, relative_time); } + geometry_motion_attribute_synced.clear(); + /* we need to set the python thread state again because this * function assumes it is being executed from python and will * try to save the thread state */ diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index 44322dda6b9..d25c0ce1bc3 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -23,6 +23,7 @@ #include "RNA_types.h" #include "blender/blender_id_map.h" +#include "blender/blender_util.h" #include "blender/blender_viewport.h" #include "render/scene.h" @@ -158,18 +159,24 @@ class BlenderSync { bool sync_object_attributes(BL::DepsgraphObjectInstance &b_instance, Object *object); /* Volume */ - void sync_volume(BL::Object &b_ob, Volume *volume); + void sync_volume(BObjectInfo &b_ob_info, Volume *volume); /* Mesh */ - void sync_mesh(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh); - void sync_mesh_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Mesh *mesh, int motion_step); + void sync_mesh(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Mesh *mesh); + void sync_mesh_motion(BL::Depsgraph b_depsgraph, + BObjectInfo &b_ob_info, + Mesh *mesh, + int motion_step); /* Hair */ - void sync_hair(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair); - void sync_hair_motion(BL::Depsgraph b_depsgraph, BL::Object b_ob, Hair *hair, int motion_step); - void sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step = 0); + void sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair); + void sync_hair_motion(BL::Depsgraph b_depsgraph, + BObjectInfo &b_ob_info, + Hair *hair, + int motion_step); + void sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step = 0); void sync_particle_hair( - Hair *hair, BL::Mesh &b_mesh, BL::Object &b_ob, bool motion, int motion_step = 0); + Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step = 0); bool object_has_particle_hair(BL::Object b_ob); /* Camera */ @@ -178,14 +185,13 @@ class BlenderSync { /* Geometry */ Geometry *sync_geometry(BL::Depsgraph &b_depsgrpah, - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, bool object_updated, bool use_particle_hair, TaskPool *task_pool); void sync_geometry_motion(BL::Depsgraph &b_depsgraph, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Object *object, float motion_time, bool use_particle_hair, @@ -194,8 +200,7 @@ class BlenderSync { /* Light */ void sync_light(BL::Object &b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], - BL::Object &b_ob, - BL::Object &b_ob_instance, + BObjectInfo &b_ob_info, int random_id, Transform &tfm, bool *use_portal); @@ -231,6 +236,7 @@ class BlenderSync { id_map<ParticleSystemKey, ParticleSystem> particle_system_map; set<Geometry *> geometry_synced; set<Geometry *> geometry_motion_synced; + set<Geometry *> geometry_motion_attribute_synced; set<float> motion_times; void *world_map; bool world_recalc; diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h index f6824f31b7b..04008d77d89 100644 --- a/intern/cycles/blender/blender_util.h +++ b/intern/cycles/blender/blender_util.h @@ -40,6 +40,28 @@ float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile); CCL_NAMESPACE_BEGIN +struct BObjectInfo { + /* Object directly provided by the depsgraph iterator. This object is only valid during one + * iteration and must not be accessed afterwards. Transforms and visibility should be checked on + * this object. */ + BL::Object iter_object; + + /* This object remains alive even after the object iterator is done. It corresponds to one + * original object. It is the object that owns the object data below. */ + BL::Object real_object; + + /* The object-data referenced by the iter object. This is still valid after the depsgraph + * iterator is done. It might have a different type compared to real_object.data(). */ + BL::ID object_data; + + /* True when the current geometry is the data of the referenced object. False when it is a + * geometry instance that does not have a 1-to-1 relationship with an object. */ + bool is_real_object_data() const + { + return const_cast<BL::Object &>(real_object).data() == object_data; + } +}; + typedef BL::ShaderNodeAttribute::attribute_type_enum BlenderAttributeType; BlenderAttributeType blender_attribute_name_split_type(ustring name, string *r_real_name); @@ -47,7 +69,7 @@ void python_thread_state_save(void **python_thread_state); void python_thread_state_restore(void **python_thread_state); static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, - BL::Object &object, + BObjectInfo &b_ob_info, BL::Depsgraph & /*depsgraph*/, bool /*calc_undeformed*/, Mesh::SubdivisionType subdivision_type) @@ -69,9 +91,9 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, #endif BL::Mesh mesh(PointerRNA_NULL); - if (object.type() == BL::Object::type_MESH) { + if (b_ob_info.object_data.is_a(&RNA_Mesh)) { /* TODO: calc_undeformed is not used. */ - mesh = BL::Mesh(object.data()); + mesh = BL::Mesh(b_ob_info.object_data); /* Make a copy to split faces if we use autosmooth, otherwise not needed. * Also in edit mode do we need to make a copy, to ensure data layers like @@ -79,12 +101,15 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, if (mesh.is_editmode() || (mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) { BL::Depsgraph depsgraph(PointerRNA_NULL); - mesh = object.to_mesh(false, depsgraph); + assert(b_ob_info.is_real_object_data()); + mesh = b_ob_info.real_object.to_mesh(false, depsgraph); } } else { BL::Depsgraph depsgraph(PointerRNA_NULL); - mesh = object.to_mesh(false, depsgraph); + if (b_ob_info.is_real_object_data()) { + mesh = b_ob_info.real_object.to_mesh(false, depsgraph); + } } #if 0 @@ -108,10 +133,14 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, } static inline void free_object_to_mesh(BL::BlendData & /*data*/, - BL::Object &object, + BObjectInfo &b_ob_info, BL::Mesh &mesh) { + if (!b_ob_info.is_real_object_data()) { + return; + } /* Free mesh if we didn't just use the existing one. */ + BL::Object object = b_ob_info.real_object; if (object.data().ptr.data != mesh.ptr.data) { object.to_mesh_clear(); } @@ -219,9 +248,13 @@ static inline bool BKE_object_is_modified(BL::Object &self, BL::Scene &scene, bo return self.is_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false; } -static inline bool BKE_object_is_deform_modified(BL::Object &self, BL::Scene &scene, bool preview) +static inline bool BKE_object_is_deform_modified(BObjectInfo &self, BL::Scene &scene, bool preview) { - return self.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false; + if (!self.is_real_object_data()) { + return false; + } + return self.real_object.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : + false; } static inline int render_resolution_x(BL::RenderSettings &b_render) @@ -540,22 +573,6 @@ static inline bool object_use_deform_motion(BL::Object &b_parent, BL::Object &b_ return use_deform_motion; } -static inline BL::FluidDomainSettings object_fluid_liquid_domain_find(BL::Object &b_ob) -{ - for (BL::Modifier &b_mod : b_ob.modifiers) { - if (b_mod.is_a(&RNA_FluidModifier)) { - BL::FluidModifier b_mmd(b_mod); - - if (b_mmd.fluid_type() == BL::FluidModifier::fluid_type_DOMAIN && - b_mmd.domain_settings().domain_type() == BL::FluidDomainSettings::domain_type_LIQUID) { - return b_mmd.domain_settings(); - } - } - } - - return BL::FluidDomainSettings(PointerRNA_NULL); -} - static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b_ob) { for (BL::Modifier &b_mod : b_ob.modifiers) { @@ -573,7 +590,6 @@ static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b } static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob, - bool check_velocity, bool *has_subdivision_modifier) { for (int i = b_ob.modifiers.length() - 1; i >= 0; --i) { @@ -581,13 +597,6 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) { BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod); - - if (check_velocity) { - if (!MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) { - return BL::MeshSequenceCacheModifier(PointerRNA_NULL); - } - } - return mesh_cache; } @@ -596,9 +605,7 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b continue; } - /* Only skip the subsurf modifier if we are not checking for the mesh sequence cache modifier - * for motion blur. */ - if (b_mod.type() == BL::Modifier::type_SUBSURF && !check_velocity) { + if (b_mod.type() == BL::Modifier::type_SUBSURF) { if (has_subdivision_modifier) { *has_subdivision_modifier = true; } diff --git a/intern/cycles/blender/blender_volume.cpp b/intern/cycles/blender/blender_volume.cpp index 772ab9f5c8a..0a5b19d7d4c 100644 --- a/intern/cycles/blender/blender_volume.cpp +++ b/intern/cycles/blender/blender_volume.cpp @@ -181,9 +181,12 @@ class BlenderSmokeLoader : public ImageLoader { AttributeStandard attribute; }; -static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, float frame) +static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame) { - BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob); + if (!b_ob_info.is_real_object_data()) { + return; + } + BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob_info.real_object); if (!b_domain) { return; } @@ -206,7 +209,7 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, fl Attribute *attr = volume->attributes.add(std); - ImageLoader *loader = new BlenderSmokeLoader(b_ob, std); + ImageLoader *loader = new BlenderSmokeLoader(b_ob_info.real_object, std); ImageParams params; params.frame = frame; @@ -244,11 +247,11 @@ class BlenderVolumeLoader : public VDBImageLoader { }; static void sync_volume_object(BL::BlendData &b_data, - BL::Object &b_ob, + BObjectInfo &b_ob_info, Scene *scene, Volume *volume) { - BL::Volume b_volume(b_ob.data()); + BL::Volume b_volume(b_ob_info.object_data); b_volume.grids.load(b_data.ptr.data); BL::VolumeRender b_render(b_volume.render()); @@ -296,19 +299,19 @@ static void sync_volume_object(BL::BlendData &b_data, } } -void BlenderSync::sync_volume(BL::Object &b_ob, Volume *volume) +void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume) { volume->clear(true); if (view_layer.use_volumes) { - if (b_ob.type() == BL::Object::type_VOLUME) { + if (b_ob_info.object_data.is_a(&RNA_Volume)) { /* Volume object. Create only attributes, bounding mesh will then * be automatically generated later. */ - sync_volume_object(b_data, b_ob, scene, volume); + sync_volume_object(b_data, b_ob_info, scene, volume); } else { /* Smoke domain. */ - sync_smoke_volume(scene, b_ob, volume, b_scene.frame_current()); + sync_smoke_volume(scene, b_ob_info, volume, b_scene.frame_current()); } } diff --git a/intern/cycles/render/hair.h b/intern/cycles/render/hair.h index 1a8f422e8c4..e4451d70767 100644 --- a/intern/cycles/render/hair.h +++ b/intern/cycles/render/hair.h @@ -89,10 +89,10 @@ class Hair : public Geometry { float4 r_keys[4]) const; }; - NODE_SOCKET_API(array<float3>, curve_keys) - NODE_SOCKET_API(array<float>, curve_radius) - NODE_SOCKET_API(array<int>, curve_first_key) - NODE_SOCKET_API(array<int>, curve_shader) + NODE_SOCKET_API_ARRAY(array<float3>, curve_keys) + NODE_SOCKET_API_ARRAY(array<float>, curve_radius) + NODE_SOCKET_API_ARRAY(array<int>, curve_first_key) + NODE_SOCKET_API_ARRAY(array<int>, curve_shader) /* BVH */ size_t curvekey_offset; diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 5290d68e75a..15aa4e047b5 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -410,38 +410,39 @@ void LightManager::device_update_distribution(Device *, } float trianglearea = totarea; - /* point lights */ - float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; bool use_lamp_mis = false; - int light_index = 0; - foreach (Light *light, scene->lights) { - if (!light->is_enabled) - continue; - distribution[offset].totarea = totarea; - distribution[offset].prim = ~light_index; - distribution[offset].lamp.pad = 1.0f; - distribution[offset].lamp.size = light->size; - totarea += lightarea; + if (num_lights > 0) { + float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; + foreach (Light *light, scene->lights) { + if (!light->is_enabled) + continue; - if (light->light_type == LIGHT_DISTANT) { - use_lamp_mis |= (light->angle > 0.0f && light->use_mis); - } - else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { - use_lamp_mis |= (light->size > 0.0f && light->use_mis); - } - else if (light->light_type == LIGHT_AREA) { - use_lamp_mis |= light->use_mis; - } - else if (light->light_type == LIGHT_BACKGROUND) { - num_background_lights++; - background_mis |= light->use_mis; - } + distribution[offset].totarea = totarea; + distribution[offset].prim = ~light_index; + distribution[offset].lamp.pad = 1.0f; + distribution[offset].lamp.size = light->size; + totarea += lightarea; - light_index++; - offset++; + if (light->light_type == LIGHT_DISTANT) { + use_lamp_mis |= (light->angle > 0.0f && light->use_mis); + } + else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { + use_lamp_mis |= (light->size > 0.0f && light->use_mis); + } + else if (light->light_type == LIGHT_AREA) { + use_lamp_mis |= light->use_mis; + } + else if (light->light_type == LIGHT_BACKGROUND) { + num_background_lights++; + background_mis |= light->use_mis; + } + + light_index++; + offset++; + } } /* normalize cumulative distribution functions */ diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index 99cb0b779b8..3013e9b1866 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -128,7 +128,7 @@ class ImageTextureNode : public ImageSlotTextureNode { NODE_SOCKET_API(float, projection_blend) NODE_SOCKET_API(bool, animated) NODE_SOCKET_API(float3, vector) - NODE_SOCKET_API(array<int>, tiles) + NODE_SOCKET_API_ARRAY(array<int>, tiles) protected: void cull_tiles(Scene *scene, ShaderGraph *graph); @@ -1554,7 +1554,7 @@ class CurvesNode : public ShaderNode { return NODE_GROUP_LEVEL_3; } - NODE_SOCKET_API(array<float3>, curves) + NODE_SOCKET_API_ARRAY(array<float3>, curves) NODE_SOCKET_API(float, min_x) NODE_SOCKET_API(float, max_x) NODE_SOCKET_API(float, fac) @@ -1588,8 +1588,8 @@ class RGBRampNode : public ShaderNode { return NODE_GROUP_LEVEL_1; } - NODE_SOCKET_API(array<float3>, ramp) - NODE_SOCKET_API(array<float>, ramp_alpha) + NODE_SOCKET_API_ARRAY(array<float3>, ramp) + NODE_SOCKET_API_ARRAY(array<float>, ramp_alpha) NODE_SOCKET_API(float, fac) NODE_SOCKET_API(bool, interpolate) }; diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index e46f712cb64..221fa140f70 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -652,6 +652,11 @@ typedef struct { enum { GHOST_kXrContextDebug = (1 << 0), GHOST_kXrContextDebugTime = (1 << 1), +# ifdef WIN32 + /* Needed to avoid issues with the SteamVR OpenGL graphics binding (use DirectX fallback + instead). */ + GHOST_kXrContextGpuNVIDIA = (1 << 2), +# endif }; typedef struct { diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index a7498e9f91f..fe8fec052fe 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -20,6 +20,7 @@ * Abstraction for XR (VR, AR, MR, ..) access via OpenXR. */ +#include <algorithm> #include <cassert> #include <sstream> #include <string> @@ -98,7 +99,7 @@ void GHOST_XrContext::initialize(const GHOST_XrContextCreateInfo *create_info) storeInstanceProperties(); /* Multiple bindings may be enabled. Now that we know the runtime in use, settle for one. */ - m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types); + m_gpu_binding_type = determineGraphicsBindingTypeToUse(graphics_binding_types, create_info); printInstanceInfo(); if (isDebugMode()) { @@ -135,7 +136,8 @@ void GHOST_XrContext::storeInstanceProperties() {"Monado(XRT) by Collabora et al", OPENXR_RUNTIME_MONADO}, {"Oculus", OPENXR_RUNTIME_OCULUS}, {"SteamVR/OpenXR", OPENXR_RUNTIME_STEAMVR}, - {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}}; + {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}, + {"Varjo OpenXR Runtime", OPENXR_RUNTIME_VARJO}}; decltype(runtime_map)::const_iterator runtime_map_iter; m_oxr->instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES; @@ -415,6 +417,12 @@ void GHOST_XrContext::getExtensionsToEnable( try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME); try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME); + /* Varjo quad view extension. */ + try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME); + + /* Varjo foveated extension. */ + try_ext.push_back(XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME); + r_ext_names.reserve(try_ext.size() + graphics_binding_types.size()); /* Add graphics binding extensions (may be multiple ones, we'll settle for one to use later, once @@ -465,16 +473,20 @@ std::vector<GHOST_TXrGraphicsBinding> GHOST_XrContext::determineGraphicsBindingT } GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( - const std::vector<GHOST_TXrGraphicsBinding> &enabled_types) + const std::vector<GHOST_TXrGraphicsBinding> &enabled_types, + const GHOST_XrContextCreateInfo *create_info) { /* Return the first working type. */ for (GHOST_TXrGraphicsBinding type : enabled_types) { #ifdef WIN32 - /* The SteamVR OpenGL backend fails currently. Disable it and allow falling back to the DirectX - * one. */ - if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL)) { + /* The SteamVR OpenGL backend currently fails for NVIDIA GPU's. Disable it and allow falling + * back to the DirectX one. */ + if ((m_runtime_id == OPENXR_RUNTIME_STEAMVR) && (type == GHOST_kXrGraphicsOpenGL) && + ((create_info->context_flag & GHOST_kXrContextGpuNVIDIA) != 0)) { continue; } +#else + ((void)create_info); #endif assert(type != GHOST_kXrGraphicsUnknown); @@ -613,4 +625,11 @@ bool GHOST_XrContext::isDebugTimeMode() const return m_debug_time; } +bool GHOST_XrContext::isExtensionEnabled(const char *ext) const +{ + bool contains = std::find(m_enabled_extensions.begin(), m_enabled_extensions.end(), ext) != + m_enabled_extensions.end(); + return contains; +} + /** \} */ /* Ghost Internal Accessors and Mutators */ diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index f29d7349f7e..479b50e1537 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -51,6 +51,7 @@ enum GHOST_TXrOpenXRRuntimeID { OPENXR_RUNTIME_OCULUS, OPENXR_RUNTIME_STEAMVR, OPENXR_RUNTIME_WMR, /* Windows Mixed Reality */ + OPENXR_RUNTIME_VARJO, OPENXR_RUNTIME_UNKNOWN }; @@ -94,6 +95,8 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isDebugMode() const; bool isDebugTimeMode() const; + bool isExtensionEnabled(const char *ext) const; + private: static GHOST_XrErrorHandlerFn s_error_handler; static void *s_error_handler_customdata; @@ -136,5 +139,6 @@ class GHOST_XrContext : public GHOST_IXrContext { std::vector<GHOST_TXrGraphicsBinding> determineGraphicsBindingTypesToEnable( const GHOST_XrContextCreateInfo *create_info); GHOST_TXrGraphicsBinding determineGraphicsBindingTypeToUse( - const std::vector<GHOST_TXrGraphicsBinding> &enabled_types); + const std::vector<GHOST_TXrGraphicsBinding> &enabled_types, + const GHOST_XrContextCreateInfo *create_info); }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a08b2d6045a..cd930c8328b 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -41,10 +41,13 @@ struct OpenXRSessionData { XrSession session = XR_NULL_HANDLE; XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; - /* Only stereo rendering supported now. */ - const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + /* Use stereo rendering by default. */ + XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + bool foveation_supported = false; + XrSpace reference_space; XrSpace view_space; + XrSpace combined_eye_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; @@ -58,6 +61,9 @@ struct GHOST_XrDrawInfo { std::chrono::high_resolution_clock::time_point frame_begin_time; /* Time previous frames took for rendering (in ms). */ std::list<double> last_frame_times; + + /* Whether foveation is active for the frame. */ + bool foveation_active; }; /* -------------------------------------------------------------------- */ @@ -82,6 +88,9 @@ GHOST_XrSession::~GHOST_XrSession() if (m_oxr->view_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space)); } + if (m_oxr->combined_eye_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_oxr->combined_eye_space)); + } if (m_oxr->session != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySession(m_oxr->session)); } @@ -189,6 +198,13 @@ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose & create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW; CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space), "Failed to create view reference space."); + + /* Foveation reference spaces. */ + if (oxr.foveation_supported) { + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO; + CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.combined_eye_space), + "Failed to create combined eye reference space."); + } } void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) @@ -292,9 +308,19 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( void GHOST_XrSession::prepareDrawing() { + assert(m_context->getInstance() != XR_NULL_HANDLE); + std::vector<XrViewConfigurationView> view_configs; uint32_t view_count; + /* Attempt to use quad view if supported. */ + if (m_context->isExtensionEnabled(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME)) { + m_oxr->view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO; + } + + m_oxr->foveation_supported = m_context->isExtensionEnabled( + XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME); + CHECK_XR( xrEnumerateViewConfigurationViews( m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr), @@ -306,7 +332,36 @@ void GHOST_XrSession::prepareDrawing() view_configs.size(), &view_count, view_configs.data()), - "Failed to get count of view configurations."); + "Failed to get view configurations."); + + /* If foveated rendering is used, query the foveated views. */ + if (m_oxr->foveation_supported) { + std::vector<XrFoveatedViewConfigurationViewVARJO> request_foveated_config{ + view_count, {XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO, nullptr, XR_TRUE}}; + + auto foveated_views = std::vector<XrViewConfigurationView>(view_count, + {XR_TYPE_VIEW_CONFIGURATION_VIEW}); + + for (uint32_t i = 0; i < view_count; i++) { + foveated_views[i].next = &request_foveated_config[i]; + } + CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(), + m_oxr->system_id, + m_oxr->view_type, + view_configs.size(), + &view_count, + foveated_views.data()), + "Failed to get foveated view configurations."); + + /* Ensure swapchains have correct size even when foveation is being used. */ + for (uint32_t i = 0; i < view_count; i++) { + view_configs[i].recommendedImageRectWidth = std::max( + view_configs[i].recommendedImageRectWidth, foveated_views[i].recommendedImageRectWidth); + view_configs[i].recommendedImageRectHeight = std::max( + view_configs[i].recommendedImageRectHeight, + foveated_views[i].recommendedImageRectHeight); + } + } for (const XrViewConfigurationView &view_config : view_configs) { m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config); @@ -327,6 +382,20 @@ void GHOST_XrSession::beginFrameDrawing() CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state), "Failed to synchronize frame rates between Blender and the device."); + /* Check if we have foveation available for the current frame. */ + m_draw_info->foveation_active = false; + if (m_oxr->foveation_supported) { + XrSpaceLocation render_gaze_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR(xrLocateSpace(m_oxr->combined_eye_space, + m_oxr->view_space, + frame_state.predictedDisplayTime, + &render_gaze_location), + "Failed to locate combined eye space."); + + m_draw_info->foveation_active = (render_gaze_location.locationFlags & + XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0; + } + CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info), "Failed to submit frame rendering start state."); @@ -442,6 +511,8 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata) { XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO}; + XrViewLocateFoveatedRenderingVARJO foveated_info{ + XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO, nullptr, true}; XrViewState view_state = {XR_TYPE_VIEW_STATE}; XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION}; XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION}; @@ -451,6 +522,10 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime; viewloc_info.space = m_oxr->reference_space; + if (m_draw_info->foveation_active) { + viewloc_info.next = &foveated_info; + } + CHECK_XR(xrLocateViews(m_oxr->session, &viewloc_info, &view_state, @@ -458,6 +533,7 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer( &view_count, m_oxr->views.data()), "Failed to query frame view and projection state."); + assert(m_oxr->swapchains.size() == view_count); CHECK_XR( diff --git a/release/datafiles/blender_icons.svg b/release/datafiles/blender_icons.svg index c3461fd1bea..70bd7dc8085 100644 --- a/release/datafiles/blender_icons.svg +++ b/release/datafiles/blender_icons.svg @@ -17321,6 +17321,58 @@ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" d="m 368.30892,141.58547 c -0.27613,4e-5 -0.49997,0.22388 -0.5,0.5 v 1.26473 h -4.76715 v 1.4911 h 4.76715 v 1.24417 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.63583,0.004 3.43318,-0.006 3.9995,-0.006 0.24106,0 0.46127,-0.0485 0.46127,-0.50967 4e-5,-0.85242 -8.9e-4,-2.98571 -8.9e-4,-3.95935 0,-0.30244 -0.19636,-0.51552 -0.46153,-0.51552 -0.82724,0 -3.36276,-0.009 -3.99823,-0.009 v 2e-5 z m 2.30359,-4.68113 -0.005,4.25868 c 0.48989,0.002 1.39549,0.005 1.88538,0.007 0.44541,0.0357 0.71675,0.47423 0.71675,0.85988 -6.6e-4,1.00616 -0.009,2.97018 -0.009,4.15122 0,0.46073 -0.24756,0.84994 -0.6533,0.84994 -0.48399,0.0143 -1.44986,-1.1e-4 -1.93405,-1.6e-4 v 3.87356 l -7.75691,-0.0669 v -14.00001 z" sodipodi:nodetypes="cccccccccccccccccccccccccc" /> + <g + transform="translate(230.76791,210.17135)" + style="display:inline;enable-background:new" + id="g4087_GP_lineart"> + <g + id="g4082"> + <path + sodipodi:nodetypes="cccccccccccccccccccc" + inkscape:connector-curvature="0" + id="path12456-6" + mask="none" + d="m 198.0253,98.27163 v 1.5 h 1 v -1.5 z m 0,2.5 v 2 h 1 v -2 z m 0,3 v 1.2793 l -2.58594,2.35156 0.67188,0.73828 2.60351,-2.36719 0.49027,-0.002 c 0.82475,0 0.82408,-1 0,-1 h -0.17972 v -1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + id="path4185" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.10423;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 207.2397,99.568306 c -0.33768,-0.02992 -0.70751,0.105959 -1.01625,0.406518 l -0.51139,0.495896 c -0.13287,0.12942 -0.13287,0.34092 0,0.47035 l 2.04339,1.98784 c 0.13292,0.12938 0.3479,0.12938 0.48082,0 l 0.50922,-0.49802 c 0.3087,-0.30067 0.44811,-0.65869 0.41741,-0.98755 -0.0307,-0.32884 -0.20718,-0.60186 -0.41741,-0.80663 l -0.67969,-0.661886 c -0.21026,-0.204768 -0.48842,-0.37662 -0.8261,-0.406518 z m -2.31222,1.800554 c -0.0883,9.4e-4 -0.17353,0.0367 -0.23603,0.0979 l -4.25293,4.14168 c -0.0434,0.0426 -0.0749,0.095 -0.0896,0.15324 l -0.67969,2.65189 c -0.0614,0.24217 0.16235,0.46285 0.41088,0.40225 l 2.72308,-0.66402 c 0.0599,-0.0144 0.11363,-0.0428 0.15735,-0.0851 l 4.2551,-4.14382 c 0.13286,-0.12943 0.13286,-0.33881 0,-0.46825 l -2.0434,-1.98784 c -0.0651,-0.0634 -0.15267,-0.0994 -0.24478,-0.0979 z" /> + <path + id="path12458-7" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 198.52539,94.771484 c -0.1326,2.7e-5 -0.25978,0.05272 -0.35351,0.146485 l -3,3 c -0.0938,0.09376 -0.14646,0.220915 -0.14649,0.353515 v 9.999996 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 2.50158 c 0.72806,0 0.76638,-1.01916 0,-1 h -2.00158 v -8.999996 h 9 v 0.186392 c 0,0.766385 1,0.767345 1,0 v -0.47936 l 2,-2 v 0.907841 c 0,0.708905 1,0.709935 1,0 v -2.114873 c -3e-5,-0.276131 -0.22387,-0.499972 -0.5,-0.5 z m 0.20703,1 h 8.58594 l -2,2 h -8.58594 z" + sodipodi:nodetypes="ccccccsccccssccsscccccccc" /> + </g> + </g> + <g + transform="translate(167.42608,209.69482)" + style="display:inline;enable-background:new" + id="g7880_GP_lenght"> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 224.38607,100.78271 c -0.15574,0.005 -0.30353,0.0699 -0.41211,0.18164 l -2.05673,2.00254 c -0.62065,0.56444 0.28322,1.46831 0.84766,0.84765 l 2.05673,-2.00254 c 0.39088,-0.38144 0.1104,-1.04428 -0.43555,-1.02929 z" + id="path15289-7-6" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccc" /> + <path + sodipodi:nodetypes="ccccccccccc" + inkscape:connector-curvature="0" + id="path15289-7-6-5" + d="m 225.6621,95.349988 c -0.67621,-0.0096 -0.67621,1.009611 0,1 h 2.79493 c -1.0479,1.117288 -1.7641,1.668027 -2.82812,2.732043 -0.62065,0.56444 0.28321,1.468319 0.84765,0.847657 1.06063,-1.101282 1.59202,-1.777197 2.68554,-2.870716 v 2.791016 c -0.01,0.676162 1.00956,0.676162 1,0 v -4 c -3e-5,-0.276131 -0.22387,-0.499973 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + sodipodi:nodetypes="ccccccccccc" + inkscape:connector-curvature="0" + id="path15289-7-6-5-2" + d="m 221.03217,109.33958 c 0.67621,0.01 0.67621,-1.00961 0,-1 h -2.79493 c 1.0479,-1.11729 1.7641,-1.66802 2.82812,-2.73204 0.62065,-0.56444 -0.28321,-1.46832 -0.84765,-0.84766 -1.06063,1.10128 -1.59202,1.7772 -2.68554,2.87072 v -2.79102 c 0.01,-0.67616 -1.00956,-0.67616 -1,0 v 4 c 3e-5,0.27613 0.22387,0.49998 0.5,0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + </g> + <path + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + d="m 417.92349,304.73964 c -0.7818,-0.0644 -0.86293,1.09626 -0.0796,1.1383 l 0.41758,0.0202 c 0.78182,0.0644 0.86296,-1.09626 0.0796,-1.13831 z m -7.87437,1.29265 c -0.65325,0.42724 0.0163,1.38626 0.65667,0.94062 l 0.34001,-0.23929 c 0.65327,-0.42727 -0.0163,-1.38631 -0.65662,-0.94061 z m 5.26412,-0.10772 c 0.785,-0.0185 0.73895,-1.18175 -0.0451,-1.14009 -0.6811,-0.0652 -1.43225,-0.0213 -2.22341,0.0851 -0.785,0.0185 -0.73896,1.18176 0.0451,1.14011 0.8585,-0.10954 1.60282,-0.14009 2.22342,-0.0852 z m -5.74172,5.34858 c -0.17789,-0.75187 -1.32618,-0.47161 -1.12597,0.27482 -0.008,0.72815 0.18352,1.43475 0.53595,2.12392 0.17789,0.75187 1.32617,0.47159 1.12598,-0.27483 -0.40688,-0.70818 -0.47775,-1.41605 -0.53596,-2.12391 z m 1.14987,4.81425 c 0.55238,0.5479 1.3799,-0.2833 0.81165,-0.81524 l -0.30437,-0.28193 c -0.55238,-0.54789 -1.37991,0.2833 -0.81163,0.81524 z m 2.55883,0.11471 c -0.78112,0.0716 -0.65484,1.22767 0.12391,1.13446 0.79706,0.0708 1.5429,0.0136 2.2124,-0.23372 0.7811,-0.0716 0.65482,-1.22768 -0.12391,-1.13445 -0.66955,0.35373 -1.42049,0.37687 -2.2124,0.23371 z m 4.35036,-1.24066 c 0.39775,-0.66505 -0.63058,-1.23994 -1.00859,-0.56384 l -0.19953,0.36135 c -0.39776,0.66506 0.63057,1.23995 1.00857,0.56383 z m -1.53457,-4.82813 c -0.44444,-0.63566 -1.409,0.0364 -0.94666,0.65956 0.53116,0.53126 0.99257,1.10609 1.28624,1.78569 0.44445,0.63565 1.40902,-0.0364 0.94667,-0.65956 -0.24301,-0.74231 -0.69323,-1.32054 -1.28625,-1.78569 z m -2.73483,-1.49223 c -0.72218,-0.30138 -1.16808,0.7761 -0.43732,1.05681 l 0.39025,0.14758 c 0.7222,0.30141 1.1681,-0.7761 0.43732,-1.0568 z m -7.60223,1.91562 c -0.52109,0.57678 0.37464,1.33651 0.87855,0.74515 l 0.26685,-0.31654 c 0.52111,-0.57679 -0.37465,-1.33654 -0.87854,-0.74516 z m 1.15912,7.09355 c -0.1906,-0.74845 -1.33363,-0.44917 -1.12109,0.29354 l 0.11543,0.39523 c 0.19062,0.74845 1.33365,0.44917 1.12109,-0.29354 z m -0.68592,-4.36328 c -0.0858,-0.76698 -1.25912,-0.62352 -1.15127,0.14077 -0.065,0.75431 -0.008,1.50847 0.28594,2.26232 0.0859,0.76696 1.25912,0.62352 1.15129,-0.14076 -0.28468,-0.81162 -0.29126,-1.53878 -0.28596,-2.26233 z m 1.97398,-4.7241 c -0.77314,0.13162 -0.55483,1.27463 0.21417,1.12135 0.7762,-0.30633 1.5005,-0.42412 2.18687,-0.40397 0.77313,-0.13163 0.55482,-1.27462 -0.21418,-1.12137 -0.74152,0.0229 -1.4733,0.13255 -2.18686,0.40399 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.15052;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:2.2;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path4101-2-6-9-1_GP_dotdash" /> </g> <g inkscape:groupmode="layer" diff --git a/release/datafiles/blender_icons16/icon16_mod_dash.dat b/release/datafiles/blender_icons16/icon16_mod_dash.dat Binary files differnew file mode 100644 index 00000000000..a8419db8c16 --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_dash.dat diff --git a/release/datafiles/blender_icons16/icon16_mod_length.dat b/release/datafiles/blender_icons16/icon16_mod_length.dat Binary files differnew file mode 100644 index 00000000000..0e1e25fcd71 --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_length.dat diff --git a/release/datafiles/blender_icons16/icon16_mod_lineart.dat b/release/datafiles/blender_icons16/icon16_mod_lineart.dat Binary files differnew file mode 100644 index 00000000000..3478b14fdab --- /dev/null +++ b/release/datafiles/blender_icons16/icon16_mod_lineart.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_dash.dat b/release/datafiles/blender_icons32/icon32_mod_dash.dat Binary files differnew file mode 100644 index 00000000000..cca56b0c9de --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_dash.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_length.dat b/release/datafiles/blender_icons32/icon32_mod_length.dat Binary files differnew file mode 100644 index 00000000000..0d1cf1f33aa --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_length.dat diff --git a/release/datafiles/blender_icons32/icon32_mod_lineart.dat b/release/datafiles/blender_icons32/icon32_mod_lineart.dat Binary files differnew file mode 100644 index 00000000000..a8e9c976a9f --- /dev/null +++ b/release/datafiles/blender_icons32/icon32_mod_lineart.dat diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index d51a82c482b..29288dcd8fd 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -33,8 +33,8 @@ const UserDef U_default = { .versionfile = BLENDER_FILE_VERSION, .subversionfile = BLENDER_FILE_SUBVERSION, - .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_SAVE_PREVIEWS | USER_RELPATHS | - USER_RELEASECONFIRM | USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES), + .flag = (USER_AUTOSAVE | USER_TOOLTIPS | USER_RELPATHS | USER_RELEASECONFIRM | + USER_SCRIPT_AUTOEXEC_DISABLE | USER_NONEGFRAMES), .dupflag = USER_DUP_MESH | USER_DUP_CURVE | USER_DUP_SURF | USER_DUP_FONT | USER_DUP_MBALL | USER_DUP_LAMP | USER_DUP_ARM | USER_DUP_ACT | USER_DUP_LIGHTPROBE | USER_DUP_GPENCIL, @@ -231,6 +231,7 @@ const UserDef U_default = { .collection_instance_empty_size = 1.0f, .statusbar_flag = STATUSBAR_SHOW_VERSION, + .file_preview_type = USER_FILE_PREVIEW_CAMERA, .runtime = { diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py index 14fc81821c4..0293d7143ec 100644 --- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py +++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py @@ -48,6 +48,7 @@ class SpellChecker: "equi", # equi-angular, etc. "fader", "globbing", + "gridded", "haptics", "hasn", # hasn't "hetero", @@ -64,12 +65,14 @@ class SpellChecker: "mplayer", "ons", # add-ons "pong", # ping pong + "resumable", "scalable", "shadeless", "shouldn", # shouldn't "smoothen", "spacings", "teleport", "teleporting", + "tangency", "vertices", "wasn", # wasn't @@ -173,11 +176,13 @@ class SpellChecker: "precalculate", "precomputing", "prefetch", + "preload", "premultiply", "premultiplied", "prepass", "prepend", - "preprocess", "preprocessing", "preprocessor", + "preprocess", "preprocessing", "preprocessor", "preprocessed", "preseek", + "preselect", "preselected", "promillage", "pushdown", "raytree", @@ -185,8 +190,10 @@ class SpellChecker: "realtime", "reinject", "reinjected", "rekey", + "relink", "remesh", "reprojection", "reproject", "reprojecting", + "resample", "resize", "restpose", "resync", "resynced", @@ -226,6 +233,7 @@ class SpellChecker: "todo", "tradeoff", "un", + "unadjust", "unadjusted", "unassociate", "unassociated", "unbake", "uncheck", @@ -388,6 +396,7 @@ class SpellChecker: "boid", "boids", "ceil", "compressibility", + "coplanar", "curvilinear", "equiangular", "equisolid", @@ -396,6 +405,7 @@ class SpellChecker: "gettext", "hashable", "hotspot", + "hydrostatic", "interocular", "intrinsics", "irradiance", @@ -495,6 +505,7 @@ class SpellChecker: "perlin", "phong", "pinlight", + "posterize", "qi", "radiosity", "raycast", "raycasting", diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index f9756811bde..24b5bb6b685 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -47,6 +47,7 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.sndparticle_potential_min_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-wavecrest"), ("bpy.types.movietrackingsettings.refine_intrinsics_principal_point*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-principal-point"), ("bpy.types.cyclesrenderlayersettings.denoising_optix_input_passes*", "render/layers/denoising.html#bpy-types-cyclesrenderlayersettings-denoising-optix-input-passes"), + ("bpy.types.sequencertoolsettings.use_snap_current_frame_to_strips*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-use-snap-current-frame-to-strips"), ("bpy.types.fluiddomainsettings.sndparticle_potential_max_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-energy"), ("bpy.types.fluiddomainsettings.sndparticle_potential_min_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-energy"), ("bpy.types.movietrackingsettings.refine_intrinsics_focal_length*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-focal-length"), @@ -56,6 +57,7 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.sndparticle_sampling_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-wavecrest"), ("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"), ("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"), + ("bpy.types.rendersettings.use_sequencer_override_scene_strip*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-use-sequencer-override-scene-strip"), ("bpy.types.toolsettings.use_transform_correct_keep_connected*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-keep-connected"), ("bpy.types.fluiddomainsettings.sndparticle_potential_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-radius"), ("bpy.types.fluiddomainsettings.openvdb_cache_compress_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-cache-compress-type"), @@ -65,6 +67,8 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.vector_scale_with_magnitude*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale-with-magnitude"), ("bpy.types.movietrackingstabilization.use_2d_stabilization*", "movie_clip/tracking/clip/sidebar/stabilization/panel.html#bpy-types-movietrackingstabilization-use-2d-stabilization"), ("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"), + ("bpy.types.toolsettings.annotation_stroke_placement_view2d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view2d"), + ("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"), ("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"), ("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"), ("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"), @@ -85,11 +89,15 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"), ("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"), ("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"), + ("bpy.types.sequencertoolsettings.snap_to_current_frame*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-current-frame"), ("bpy.types.colormanageddisplaysettings.display_device*", "render/color_management.html#bpy-types-colormanageddisplaysettings-display-device"), ("bpy.types.colormanagedviewsettings.use_curve_mapping*", "render/color_management.html#bpy-types-colormanagedviewsettings-use-curve-mapping"), ("bpy.types.fluiddomainsettings.color_ramp_field_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field-scale"), ("bpy.types.fluiddomainsettings.use_adaptive_timesteps*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-adaptive-timesteps"), ("bpy.types.fluiddomainsettings.use_dissolve_smoke_log*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke-log"), + ("bpy.types.gpencillayer.annotation_onion_before_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-color"), + ("bpy.types.gpencillayer.annotation_onion_before_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-range"), + ("bpy.types.gpencillayer.use_annotation_onion_skinning*", "interface/annotate_tool.html#bpy-types-gpencillayer-use-annotation-onion-skinning"), ("bpy.types.greasepencil.use_adaptive_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-adaptive-curve-resolution"), ("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"), ("bpy.types.toolsettings.use_gpencil_automerge_strokes*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-automerge-strokes"), @@ -102,10 +110,13 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.gridlines_lower_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-lower-bound"), ("bpy.types.fluiddomainsettings.gridlines_range_color*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-range-color"), ("bpy.types.fluiddomainsettings.gridlines_upper_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-upper-bound"), + ("bpy.types.gpencillayer.annotation_onion_after_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-color"), + ("bpy.types.gpencillayer.annotation_onion_after_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-range"), ("bpy.types.materialgpencilstyle.use_fill_texture_mix*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-texture-mix"), ("bpy.types.rendersettings_simplify_gpencil_shader_fx*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-shader-fx"), ("bpy.types.rendersettings_simplify_gpencil_view_fill*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-fill"), - ("bpy.types.spacesequenceeditor.waveform_display_type*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-waveform-display-type"), + ("bpy.types.sequencertoolsettings.snap_to_hold_offset*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-hold-offset"), + ("bpy.types.spacesequenceeditor.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-waveform-display-type"), ("bpy.types.toolsettings.use_mesh_automerge_and_split*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge-and-split"), ("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"), ("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"), @@ -145,9 +156,11 @@ url_manual_mapping = ( ("bpy.types.materialgpencilstyle.use_stroke_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-stroke-holdout"), ("bpy.types.movietrackingsettings.use_tripod_solver*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-tripod-solver"), ("bpy.types.rendersettings.use_high_quality_normals*", "render/eevee/render_settings/performance.html#bpy-types-rendersettings-use-high-quality-normals"), + ("bpy.types.sequencertoolsettings.snap_ignore_muted*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-muted"), + ("bpy.types.sequencertoolsettings.snap_ignore_sound*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-sound"), ("bpy.types.spaceoutliner.use_filter_case_sensitive*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-case-sensitive"), ("bpy.types.spaceoutliner.use_filter_object_content*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-content"), - ("bpy.types.spacesequenceeditor.show_strip_duration*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-duration"), + ("bpy.types.spacesequenceeditor.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-duration"), ("bpy.types.toolsettings.use_proportional_connected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-connected"), ("bpy.types.toolsettings.use_proportional_projected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-projected"), ("bpy.types.view3doverlay.vertex_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-vertex-paint-mode-opacity"), @@ -156,6 +169,7 @@ url_manual_mapping = ( ("bpy.ops.gpencil.vertex_color_brightness_contrast*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-brightness-contrast"), ("bpy.ops.view3d.edit_mesh_extrude_individual_move*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-individual-move"), ("bpy.ops.view3d.edit_mesh_extrude_manifold_normal*", "modeling/meshes/tools/extrude_manifold.html#bpy-ops-view3d-edit-mesh-extrude-manifold-normal"), + ("bpy.types.cyclesobjectsettings.use_distance_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-distance-cull"), ("bpy.types.cyclesrendersettings.ao_bounces_render*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces-render"), ("bpy.types.cyclesrendersettings.use_distance_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-distance-cull"), ("bpy.types.fluiddomainsettings.cache_frame_offset*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-offset"), @@ -180,6 +194,7 @@ url_manual_mapping = ( ("bpy.types.spacedopesheeteditor.show_pose_markers*", "animation/markers.html#bpy-types-spacedopesheeteditor-show-pose-markers"), ("bpy.types.spaceoutliner.use_filter_object_camera*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-camera"), ("bpy.types.spaceoutliner.use_filter_object_others*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-others"), + ("bpy.types.spacesequenceeditor.show_strip_overlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-overlay"), ("bpy.types.toolsettings.proportional_edit_falloff*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-proportional-edit-falloff"), ("bpy.types.toolsettings.use_edge_path_live_unwrap*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-edge-path-live-unwrap"), ("bpy.types.toolsettings.use_gpencil_draw_additive*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-additive"), @@ -214,8 +229,8 @@ url_manual_mapping = ( ("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"), ("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"), ("bpy.types.spacesequenceeditor.proxy_render_size*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"), - ("bpy.types.spacesequenceeditor.show_strip_offset*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-offset"), - ("bpy.types.spacesequenceeditor.show_strip_source*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-source"), + ("bpy.types.spacesequenceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-offset"), + ("bpy.types.spacesequenceeditor.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-source"), ("bpy.types.spacespreadsheetrowfilter.column_name*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-column-name"), ("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"), ("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"), @@ -223,6 +238,7 @@ url_manual_mapping = ( ("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"), ("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"), ("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"), + ("bpy.types.cyclesobjectsettings.use_camera_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-camera-cull"), ("bpy.types.cyclesrendersettings.use_camera_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-camera-cull"), ("bpy.types.editbone.bbone_handle_use_ease_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-start"), ("bpy.types.fluiddomainsettings.color_ramp_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field"), @@ -232,7 +248,7 @@ url_manual_mapping = ( ("bpy.types.linestylegeometrymodifier_tipremover*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/tip_remover.html#bpy-types-linestylegeometrymodifier-tipremover"), ("bpy.types.movieclipuser.use_render_undistorted*", "editors/clip/display/clip_display.html#bpy-types-movieclipuser-use-render-undistorted"), ("bpy.types.movietrackingcamera.distortion_model*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-distortion-model"), - ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-percentage"), + ("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-percentage"), ("bpy.types.rendersettings_simplify_gpencil_tint*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-tint"), ("bpy.types.spaceoutliner.use_filter_object_mesh*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-mesh"), ("bpy.types.spaceoutliner.use_filter_view_layers*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-view-layers"), @@ -267,6 +283,7 @@ url_manual_mapping = ( ("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/blueprint.html#bpy-types-linestylegeometrymodifier-blueprint"), ("bpy.types.materialgpencilstyle.alignment_mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-alignment-mode"), ("bpy.types.particlesettings.use_modifier_stack*", "physics/particles/emitter/emission.html#bpy-types-particlesettings-use-modifier-stack"), + ("bpy.types.rendersettings.sequencer_gl_preview*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-sequencer-gl-preview"), ("bpy.types.rendersettings.simplify_subdivision*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-subdivision"), ("bpy.types.spacegrapheditor.show_extrapolation*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-extrapolation"), ("bpy.types.spaceoutliner.use_filter_collection*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-collection"), @@ -274,7 +291,7 @@ url_manual_mapping = ( ("bpy.types.spacesequenceeditor.show_annotation*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-annotation"), ("bpy.types.spacesequenceeditor.show_region_hud*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-region-hud"), ("bpy.types.spacesequenceeditor.show_safe_areas*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-safe-areas"), - ("bpy.types.spacesequenceeditor.show_strip_name*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-name"), + ("bpy.types.spacesequenceeditor.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-name"), ("bpy.types.spacespreadsheet.show_only_selected*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-show-only-selected"), ("bpy.types.spacespreadsheetrowfilter.operation*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-operation"), ("bpy.types.spacespreadsheetrowfilter.threshold*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-threshold"), @@ -313,6 +330,7 @@ url_manual_mapping = ( ("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sampling.html#bpy-types-linestylegeometrymodifier-sampling"), ("bpy.types.nodesocketinterface*.default_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-default-value"), ("bpy.types.rendersettings.use_persistent_data*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-persistent-data"), + ("bpy.types.sequencertoolsettings.overlap_mode*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-overlap-mode"), ("bpy.types.spaceclipeditor.show_green_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-green-channel"), ("bpy.types.spaceoutliner.show_restrict_column*", "editors/outliner/interface.html#bpy-types-spaceoutliner-show-restrict-column"), ("bpy.types.spacespreadsheet.object_eval_state*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-object-eval-state"), @@ -339,10 +357,10 @@ url_manual_mapping = ( ("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"), ("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-crease-override"), ("bpy.types.rendersettings.preview_pixel_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-preview-pixel-size"), - ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-crop-to-border"), + ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-crop-to-border"), ("bpy.types.rendersettings.use_file_extension*", "render/output/properties/output.html#bpy-types-rendersettings-use-file-extension"), ("bpy.types.sculpt.constant_detail_resolution*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-constant-detail-resolution"), - ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/annotation.html#bpy-types-spaceclipeditor-annotation-source"), + ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/view.html#bpy-types-spaceclipeditor-annotation-source"), ("bpy.types.spaceclipeditor.show_blue_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-blue-channel"), ("bpy.types.spaceoutliner.use_filter_children*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-children"), ("bpy.types.spaceoutliner.use_filter_complete*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-complete"), @@ -388,7 +406,7 @@ url_manual_mapping = ( ("bpy.types.spaceclipeditor.show_red_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-red-channel"), ("bpy.types.spaceclipeditor.use_mute_footage*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-mute-footage"), ("bpy.types.spacesequenceeditor.overlay_type*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-overlay-type"), - ("bpy.types.spacesequenceeditor.show_fcurves*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-fcurves"), + ("bpy.types.spacesequenceeditor.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-fcurves"), ("bpy.types.spaceuveditor.sticky_select_mode*", "editors/uv/selecting.html#bpy-types-spaceuveditor-sticky-select-mode"), ("bpy.types.spaceview3d.show_object_viewport*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-viewport"), ("bpy.types.view3doverlay.show_fade_inactive*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-fade-inactive"), @@ -396,6 +414,7 @@ url_manual_mapping = ( ("bpy.ops.clip.stabilize_2d_rotation_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-rotation-select"), ("bpy.ops.constraint.disable_keep_transform*", "animation/constraints/interface/common.html#bpy-ops-constraint-disable-keep-transform"), ("bpy.ops.gpencil.stroke_reset_vertex_color*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-stroke-reset-vertex-color"), + ("bpy.ops.object.modifier_apply_as_shapekey*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply-as-shapekey"), ("bpy.ops.object.vertex_group_normalize_all*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize-all"), ("bpy.ops.outliner.collection_color_tag_set*", "editors/outliner/editing.html#bpy-ops-outliner-collection-color-tag-set"), ("bpy.ops.outliner.collection_enable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable-render"), @@ -417,6 +436,7 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.flow_behavior*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-behavior"), ("bpy.types.fluidflowsettings.noise_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-noise-texture"), ("bpy.types.geometrynodeattributevectormath*", "modeling/geometry_nodes/attribute/attribute_vector_math.html#bpy-types-geometrynodeattributevectormath"), + ("bpy.types.gpencillayer.annotation_opacity*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-opacity"), ("bpy.types.gpencillayer.use_onion_skinning*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-onion-skinning"), ("bpy.types.gpencilsculptguide.use_snapping*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-use-snapping"), ("bpy.types.gpencilsculptsettings.lock_axis*", "grease_pencil/modes/draw/drawing_planes.html#bpy-types-gpencilsculptsettings-lock-axis"), @@ -443,6 +463,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.gpencil_selectmode*", "grease_pencil/selecting.html#bpy-types-toolsettings-gpencil-selectmode"), ("bpy.types.toolsettings.use_auto_normalize*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-auto-normalize"), ("bpy.types.toolsettings.use_mesh_automerge*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge"), + ("bpy.types.toolsettings.use_snap_sequencer*", "video_editing/sequencer/editing.html#bpy-types-toolsettings-use-snap-sequencer"), ("bpy.types.toolsettings.use_snap_translate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-translate"), ("bpy.types.toolsettings.use_uv_select_sync*", "editors/uv/selecting.html#bpy-types-toolsettings-use-uv-select-sync"), ("bpy.types.view3doverlay.wireframe_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-opacity"), @@ -469,7 +490,6 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"), ("bpy.types.fluiddomainsettings.guide_beta*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-beta"), ("bpy.types.fluiddomainsettings.mesh_scale*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-scale"), - ("bpy.types.fluiddomainsettings.noise_type*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-type"), ("bpy.types.fluiddomainsettings.slice_axis*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-axis"), ("bpy.types.fluiddomainsettings.time_scale*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-time-scale"), ("bpy.types.fluidflowsettings.texture_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-size"), @@ -534,7 +554,7 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.smoke_color*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-smoke-color"), ("bpy.types.fluidflowsettings.temperature*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-temperature"), ("bpy.types.fluidflowsettings.use_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-texture"), - ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), + ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), ("bpy.types.geometrynodeattributecurvemap*", "modeling/geometry_nodes/attribute/attribute_curve_map.html#bpy-types-geometrynodeattributecurvemap"), ("bpy.types.geometrynodeattributemaprange*", "modeling/geometry_nodes/attribute/attribute_map_range.html#bpy-types-geometrynodeattributemaprange"), ("bpy.types.geometrynodeattributetransfer*", "modeling/geometry_nodes/attribute/attribute_transfer.html#bpy-types-geometrynodeattributetransfer"), @@ -543,13 +563,15 @@ url_manual_mapping = ( ("bpy.types.material.use_sss_translucency*", "render/eevee/materials/settings.html#bpy-types-material-use-sss-translucency"), ("bpy.types.movietrackingcamera.principal*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-principal"), ("bpy.types.object.use_camera_lock_parent*", "scene_layout/object/properties/relations.html#bpy-types-object-use-camera-lock-parent"), - ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-x"), - ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-pixel-aspect-y"), + ("bpy.types.object.visible_volume_scatter*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-volume-scatter"), + ("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-x"), + ("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-y"), ("bpy.types.rigidbodyconstraint.use_limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-limit"), ("bpy.types.sceneeevee.taa_render_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-render-samples"), ("bpy.types.spaceoutliner.use_sync_select*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-sync-select"), ("bpy.types.spaceproperties.outliner_sync*", "editors/properties_editor.html#bpy-types-spaceproperties-outliner-sync"), ("bpy.types.spaceproperties.search_filter*", "editors/properties_editor.html#bpy-types-spaceproperties-search-filter"), + ("bpy.types.spacesequenceeditor.view_type*", "editors/video_sequencer/introduction.html#bpy-types-spacesequenceeditor-view-type"), ("bpy.types.spacetexteditor.margin_column*", "editors/text_editor.html#bpy-types-spacetexteditor-margin-column"), ("bpy.types.spacetexteditor.use_find_wrap*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-wrap"), ("bpy.types.spaceuveditor.pixel_snap_mode*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-pixel-snap-mode"), @@ -586,9 +608,10 @@ url_manual_mapping = ( ("bpy.types.material.preview_render_type*", "render/materials/preview.html#bpy-types-material-preview-render-type"), ("bpy.types.materialgpencilstyle.pattern*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pattern"), ("bpy.types.materialgpencilstyle.texture*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-texture"), + ("bpy.types.modifier.use_apply_on_spline*", "modeling/modifiers/introduction.html#bpy-types-modifier-use-apply-on-spline"), ("bpy.types.object.use_empty_image_alpha*", "modeling/empties.html#bpy-types-object-use-empty-image-alpha"), - ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-new"), - ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/dimensions.html#bpy-types-rendersettings-frame-map-old"), + ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-new"), + ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-old"), ("bpy.types.rendersettings.use_overwrite*", "render/output/properties/output.html#bpy-types-rendersettings-use-overwrite"), ("bpy.types.rendersettings.use_sequencer*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-sequencer"), ("bpy.types.sceneeevee.volumetric_shadow*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-shadow"), @@ -613,6 +636,7 @@ url_manual_mapping = ( ("bpy.ops.mesh.primitive_ico_sphere_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-ico-sphere-add"), ("bpy.ops.object.gpencil_modifier_apply*", "grease_pencil/modifiers/introduction.html#bpy-ops-object-gpencil-modifier-apply"), ("bpy.ops.object.material_slot_deselect*", "render/materials/assignment.html#bpy-ops-object-material-slot-deselect"), + ("bpy.ops.object.modifier_move_to_index*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-move-to-index"), ("bpy.ops.object.multires_external_save*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-external-save"), ("bpy.ops.object.vertex_group_normalize*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize"), ("bpy.ops.object.visual_transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-visual-transform-apply"), @@ -645,11 +669,12 @@ url_manual_mapping = ( ("bpy.types.linestyle*modifier_material*", "render/freestyle/parameter_editor/line_style/modifiers/color/material.html#bpy-types-linestyle-modifier-material"), ("bpy.types.movietrackingcamera.brown_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-k"), ("bpy.types.movietrackingcamera.brown_p*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-p"), + ("bpy.types.object.visible_transmission*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-transmission"), ("bpy.types.particlesettingstextureslot*", "physics/particles/texture_influence.html#bpy-types-particlesettingstextureslot"), ("bpy.types.posebone.ik_rotation_weight*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-ik-rotation-weight"), ("bpy.types.regionview3d.show_sync_view*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-show-sync-view"), - ("bpy.types.rendersettings.resolution_x*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-x"), - ("bpy.types.rendersettings.resolution_y*", "render/output/properties/dimensions.html#bpy-types-rendersettings-resolution-y"), + ("bpy.types.rendersettings.resolution_x*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-x"), + ("bpy.types.rendersettings.resolution_y*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-y"), ("bpy.types.rendersettings.threads_mode*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads-mode"), ("bpy.types.rigidbodyconstraint.enabled*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-enabled"), ("bpy.types.rigidbodyconstraint.object1*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-object1"), @@ -700,7 +725,7 @@ url_manual_mapping = ( ("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"), ("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"), ("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"), - ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierfunctiongenerator"), + ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"), ("bpy.types.geometrynodeattributeclamp*", "modeling/geometry_nodes/attribute/attribute_clamp.html#bpy-types-geometrynodeattributeclamp"), ("bpy.types.geometrynodecollectioninfo*", "modeling/geometry_nodes/input/collection_info.html#bpy-types-geometrynodecollectioninfo"), ("bpy.types.geometrynodecurveendpoints*", "modeling/geometry_nodes/curve/curve_endpoints.html#bpy-types-geometrynodecurveendpoints"), @@ -710,6 +735,7 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointstovolume*", "modeling/geometry_nodes/volume/points_to_volume.html#bpy-types-geometrynodepointstovolume"), ("bpy.types.geometrynodepointtranslate*", "modeling/geometry_nodes/point/point_translate.html#bpy-types-geometrynodepointtranslate"), ("bpy.types.greasepencil.use_multiedit*", "grease_pencil/multiframe.html#bpy-types-greasepencil-use-multiedit"), + ("bpy.types.keyframe.handle_right_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right-type"), ("bpy.types.materialgpencilstyle.color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-color"), ("bpy.types.movietrackingcamera.nuke_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-nuke-k"), ("bpy.types.movietrackingstabilization*", "movie_clip/tracking/clip/sidebar/stabilization/index.html#bpy-types-movietrackingstabilization"), @@ -728,6 +754,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.use_snap_self*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-self"), ("bpy.types.viewlayer.active_aov_index*", "render/layers/passes.html#bpy-types-viewlayer-active-aov-index"), ("bpy.ops.anim.channels_enable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-enable-toggle"), + ("bpy.ops.constraint.copy_to_selected*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy-to-selected"), ("bpy.ops.gpencil.bake_mesh_animation*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-bake-mesh-animation"), ("bpy.ops.gpencil.select_vertex_color*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-vertex-color"), ("bpy.ops.gpencil.set_active_material*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-set-active-material"), @@ -778,13 +805,15 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointinstance*", "modeling/geometry_nodes/point/point_instance.html#bpy-types-geometrynodepointinstance"), ("bpy.types.geometrynodepointseparate*", "modeling/geometry_nodes/point/point_separate.html#bpy-types-geometrynodepointseparate"), ("bpy.types.geometrynoderesamplecurve*", "modeling/geometry_nodes/curve/resample_curve.html#bpy-types-geometrynoderesamplecurve"), + ("bpy.types.keyframe.handle_left_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left-type"), ("bpy.types.light.use_custom_distance*", "render/eevee/lighting.html#bpy-types-light-use-custom-distance"), ("bpy.types.materialgpencilstyle.flip*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-flip"), ("bpy.types.materialgpencilstyle.mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mode"), + ("bpy.types.modifier.show_in_editmode*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-in-editmode"), ("bpy.types.object.empty_display_size*", "modeling/empties.html#bpy-types-object-empty-display-size"), ("bpy.types.object.empty_display_type*", "modeling/empties.html#bpy-types-object-empty-display-type"), ("bpy.types.regionview3d.use_box_clip*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-use-box-clip"), - ("bpy.types.rendersettings.use_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-border"), + ("bpy.types.rendersettings.use_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-border"), ("bpy.types.rigidbodyconstraint.limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-limit"), ("bpy.types.rigidbodyobject.kinematic*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-kinematic"), ("bpy.types.scene.audio_doppler_speed*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-speed"), @@ -848,7 +877,6 @@ url_manual_mapping = ( ("bpy.types.copytransformsconstraint*", "animation/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"), ("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"), ("bpy.types.curve.bevel_factor_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-start"), - ("bpy.types.cyclesvisibilitysettings*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesvisibilitysettings"), ("bpy.types.fluiddomainsettings.beta*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-beta"), ("bpy.types.fluidmodifier.fluid_type*", "physics/fluid/type/index.html#bpy-types-fluidmodifier-fluid-type"), ("bpy.types.functionnodefloatcompare*", "modeling/geometry_nodes/utilities/float_compare.html#bpy-types-functionnodefloatcompare"), @@ -865,6 +893,7 @@ url_manual_mapping = ( ("bpy.types.mesh.use_mirror_topology*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-topology"), ("bpy.types.movieclip.display_aspect*", "editors/clip/display/clip_display.html#bpy-types-movieclip-display-aspect"), ("bpy.types.nodesocketinterface.name*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-name"), + ("bpy.types.object.is_shadow_catcher*", "render/cycles/object_settings/object_data.html#bpy-types-object-is-shadow-catcher"), ("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"), ("bpy.types.sequencetransform.offset*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"), ("bpy.types.shadernodebrightcontrast*", "render/shader_nodes/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"), @@ -955,7 +984,7 @@ url_manual_mapping = ( ("bpy.types.limitrotationconstraint*", "animation/constraints/transform/limit_rotation.html#bpy-types-limitrotationconstraint"), ("bpy.types.multiplygpencilmodifier*", "grease_pencil/modifiers/generate/multiple_strokes.html#bpy-types-multiplygpencilmodifier"), ("bpy.types.rendersettings.filepath*", "render/output/properties/output.html#bpy-types-rendersettings-filepath"), - ("bpy.types.rendersettings.fps_base*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps-base"), + ("bpy.types.rendersettings.fps_base*", "render/output/properties/format.html#bpy-types-rendersettings-fps-base"), ("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"), ("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"), ("bpy.types.sequencetransform.scale*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"), @@ -965,13 +994,13 @@ url_manual_mapping = ( ("bpy.types.shadernodevolumescatter*", "render/shader_nodes/shader/volume_scatter.html#bpy-types-shadernodevolumescatter"), ("bpy.types.simplifygpencilmodifier*", "grease_pencil/modifiers/generate/simplify.html#bpy-types-simplifygpencilmodifier"), ("bpy.types.spacegrapheditor.cursor*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-cursor"), - ("bpy.types.toolsettings.annotation*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation"), ("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"), ("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer.html#bpy-types-viewlayer-use-freestyle"), ("bpy.types.volumedisplay.use_slice*", "modeling/volumes/properties.html#bpy-types-volumedisplay-use-slice"), ("bpy.ops.armature.armature_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-armature-layers"), ("bpy.ops.armature.select_linked()*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-linked"), ("bpy.ops.clip.stabilize_2d_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-select"), + ("bpy.ops.constraint.move_to_index*", "animation/constraints/interface/header.html#bpy-ops-constraint-move-to-index"), ("bpy.ops.gpencil.frame_clean_fill*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-fill"), ("bpy.ops.gpencil.stroke_subdivide*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-subdivide"), ("bpy.ops.gpencil.vertex_color_hsv*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-hsv"), @@ -994,7 +1023,7 @@ url_manual_mapping = ( ("bpy.ops.outliner.collection_hide*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide"), ("bpy.ops.outliner.collection_show*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show"), ("bpy.ops.paint.mask_lasso_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-lasso-gesture"), - ("bpy.ops.rigidbody.mass_calculate*", "physics/rigid_body/editing.html#bpy-ops-rigidbody-mass-calculate"), + ("bpy.ops.rigidbody.mass_calculate*", "scene_layout/object/editing/rigid_body.html#bpy-ops-rigidbody-mass-calculate"), ("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"), ("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"), ("bpy.ops.sequencer.duplicate_move*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-duplicate-move"), @@ -1045,9 +1074,12 @@ url_manual_mapping = ( ("bpy.types.geometrynodepointscale*", "modeling/geometry_nodes/point/point_scale.html#bpy-types-geometrynodepointscale"), ("bpy.types.imagepaint.use_occlude*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-occlude"), ("bpy.types.imagesequence.use_flip*", "video_editing/sequencer/sidebar/strip.html#bpy-types-imagesequence-use-flip"), + ("bpy.types.keyframe.interpolation*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-interpolation"), ("bpy.types.latticegpencilmodifier*", "grease_pencil/modifiers/deform/lattice.html#bpy-types-latticegpencilmodifier"), ("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier"), ("bpy.types.mesh.auto_smooth_angle*", "modeling/meshes/structure.html#bpy-types-mesh-auto-smooth-angle"), + ("bpy.types.modifier.show_viewport*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-viewport"), + ("bpy.types.object.visible_diffuse*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-diffuse"), ("bpy.types.objectsolverconstraint*", "animation/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"), ("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"), ("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"), @@ -1143,6 +1175,7 @@ url_manual_mapping = ( ("bpy.types.dopesheet.filter_text*", "editors/graph_editor/channels.html#bpy-types-dopesheet-filter-text"), ("bpy.types.editbone.bbone_easein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-easein"), ("bpy.types.editbone.bbone_rollin*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollin"), + ("bpy.types.fcurve.auto_smoothing*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-auto-smoothing"), ("bpy.types.fluideffectorsettings*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings"), ("bpy.types.followtrackconstraint*", "animation/constraints/motion_tracking/follow_track.html#bpy-types-followtrackconstraint"), ("bpy.types.geometrynodecurveline*", "modeling/geometry_nodes/curve_primitives/line.html#bpy-types-geometrynodecurveline"), @@ -1151,12 +1184,17 @@ url_manual_mapping = ( ("bpy.types.geometrynodeedgesplit*", "modeling/geometry_nodes/mesh/edge_split.html#bpy-types-geometrynodeedgesplit"), ("bpy.types.geometrynodetransform*", "modeling/geometry_nodes/geometry/transform.html#bpy-types-geometrynodetransform"), ("bpy.types.gpencilsculptsettings*", "grease_pencil/properties/index.html#bpy-types-gpencilsculptsettings"), + ("bpy.types.keyframe.handle_right*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right"), ("bpy.types.light.cutoff_distance*", "render/eevee/lighting.html#bpy-types-light-cutoff-distance"), ("bpy.types.lockedtrackconstraint*", "animation/constraints/tracking/locked_track.html#bpy-types-lockedtrackconstraint"), ("bpy.types.material.blend_method*", "render/eevee/materials/settings.html#bpy-types-material-blend-method"), ("bpy.types.mirrorgpencilmodifier*", "grease_pencil/modifiers/generate/mirror.html#bpy-types-mirrorgpencilmodifier"), + ("bpy.types.modifier.show_on_cage*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-on-cage"), ("bpy.types.movietrackingcamera.k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-k"), ("bpy.types.node.use_custom_color*", "interface/controls/nodes/sidebar.html#bpy-types-node-use-custom-color"), + ("bpy.types.object.visible_camera*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-camera"), + ("bpy.types.object.visible_glossy*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-glossy"), + ("bpy.types.object.visible_shadow*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-shadow"), ("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"), ("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"), ("bpy.types.rendersettings.tile_x*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-x"), @@ -1192,6 +1230,7 @@ url_manual_mapping = ( ("bpy.ops.mesh.vert_connect_path*", "modeling/meshes/editing/vertex/connect_vertex_path.html#bpy-ops-mesh-vert-connect-path"), ("bpy.ops.nla.action_sync_length*", "editors/nla/editing.html#bpy-ops-nla-action-sync-length"), ("bpy.ops.object.make_links_data*", "scene_layout/object/editing/link_transfer/link_data.html#bpy-ops-object-make-links-data"), + ("bpy.ops.object.modifier_remove*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-remove"), ("bpy.ops.object.paths_calculate*", "animation/motion_paths.html#bpy-ops-object-paths-calculate"), ("bpy.ops.object.transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-transform-apply"), ("bpy.ops.outliner.lib_operation*", "files/linked_libraries/link_append.html#bpy-ops-outliner-lib-operation"), @@ -1232,11 +1271,13 @@ url_manual_mapping = ( ("bpy.types.geometrynodemeshline*", "modeling/geometry_nodes/mesh_primitives/line.html#bpy-types-geometrynodemeshline"), ("bpy.types.gpencillayer.opacity*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-opacity"), ("bpy.types.image.display_aspect*", "editors/image/sidebar.html#bpy-types-image-display-aspect"), + ("bpy.types.keyframe.handle_left*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left"), ("bpy.types.keyingsetsall.active*", "editors/timeline.html#bpy-types-keyingsetsall-active"), ("bpy.types.limitscaleconstraint*", "animation/constraints/transform/limit_scale.html#bpy-types-limitscaleconstraint"), ("bpy.types.materialgpencilstyle*", "grease_pencil/materials/index.html#bpy-types-materialgpencilstyle"), ("bpy.types.mesh.use_auto_smooth*", "modeling/meshes/structure.html#bpy-types-mesh-use-auto-smooth"), ("bpy.types.meshtovolumemodifier*", "modeling/modifiers/generate/mesh_to_volume.html#bpy-types-meshtovolumemodifier"), + ("bpy.types.modifier.show_render*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-render"), ("bpy.types.noisegpencilmodifier*", "grease_pencil/modifiers/deform/noise.html#bpy-types-noisegpencilmodifier"), ("bpy.types.object.hide_viewport*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-viewport"), ("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"), @@ -1291,6 +1332,7 @@ url_manual_mapping = ( ("bpy.ops.node.tree_socket_move*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-move"), ("bpy.ops.object.duplicate_move*", "scene_layout/object/editing/duplicate.html#bpy-ops-object-duplicate-move"), ("bpy.ops.object.hook_add_selob*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook-add-selob"), + ("bpy.ops.object.modifier_apply*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply"), ("bpy.ops.object.select_by_type*", "scene_layout/object/selecting.html#bpy-ops-object-select-by-type"), ("bpy.ops.object.select_grouped*", "scene_layout/object/selecting.html#bpy-ops-object-select-grouped"), ("bpy.ops.object.select_pattern*", "scene_layout/object/selecting.html#bpy-ops-object-select-pattern"), @@ -1402,6 +1444,7 @@ url_manual_mapping = ( ("bpy.ops.node.read_viewlayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-viewlayers"), ("bpy.ops.node.tree_socket_add*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-add"), ("bpy.ops.object.data_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data.html#bpy-ops-object-data-transfer"), + ("bpy.ops.object.modifier_copy*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy"), ("bpy.ops.object.select_camera*", "scene_layout/object/selecting.html#bpy-ops-object-select-camera"), ("bpy.ops.object.select_linked*", "scene_layout/object/selecting.html#bpy-ops-object-select-linked"), ("bpy.ops.object.select_mirror*", "scene_layout/object/selecting.html#bpy-ops-object-select-mirror"), @@ -1436,13 +1479,15 @@ url_manual_mapping = ( ("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"), ("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"), ("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"), + ("bpy.types.constraint.enabled*", "animation/constraints/interface/header.html#bpy-types-constraint-enabled"), ("bpy.types.curve.bevel_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-object"), ("bpy.types.curve.resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-resolution-u"), ("bpy.types.curve.resolution_v*", "modeling/surfaces/properties/shape.html#bpy-types-curve-resolution-v"), ("bpy.types.curve.taper_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-taper-object"), ("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"), ("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"), - ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiergenerator"), + ("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"), + ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), ("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"), ("bpy.types.gammacrosssequence*", "video_editing/sequencer/strips/transitions/gamma_cross.html#bpy-types-gammacrosssequence"), ("bpy.types.geometrynodeswitch*", "modeling/geometry_nodes/utilities/switch.html#bpy-types-geometrynodeswitch"), @@ -1459,7 +1504,7 @@ url_manual_mapping = ( ("bpy.types.object.hide_select*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-select"), ("bpy.types.object.parent_type*", "scene_layout/object/properties/relations.html#bpy-types-object-parent-type"), ("bpy.types.object.show_bounds*", "scene_layout/object/properties/display.html#bpy-types-object-show-bounds"), - ("bpy.types.rendersettings.fps*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps"), + ("bpy.types.rendersettings.fps*", "render/output/properties/format.html#bpy-types-rendersettings-fps"), ("bpy.types.scene.audio_volume*", "scene_layout/scene/properties.html#bpy-types-scene-audio-volume"), ("bpy.types.sculpt.detail_size*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-size"), ("bpy.types.shadernodebsdfhair*", "render/shader_nodes/shader/hair.html#bpy-types-shadernodebsdfhair"), @@ -1558,8 +1603,9 @@ url_manual_mapping = ( ("bpy.types.curve.bevel_depth*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-depth"), ("bpy.types.curve.use_stretch*", "modeling/curves/properties/shape.html#bpy-types-curve-use-stretch"), ("bpy.types.edgesplitmodifier*", "modeling/modifiers/generate/edge_split.html#bpy-types-edgesplitmodifier"), + ("bpy.types.fcurve.color_mode*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-color-mode"), ("bpy.types.fluidflowsettings*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings"), - ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelope"), + ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelope"), ("bpy.types.freestylesettings*", "render/freestyle/view_layer.html#bpy-types-freestylesettings"), ("bpy.types.geometrynodegroup*", "modeling/geometry_nodes/group.html#bpy-types-geometrynodegroup"), ("bpy.types.gpencillayer.hide*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-hide"), @@ -1573,12 +1619,13 @@ url_manual_mapping = ( ("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"), ("bpy.types.movieclipsequence*", "video_editing/sequencer/strips/clip.html#bpy-types-movieclipsequence"), ("bpy.types.object.dimensions*", "scene_layout/object/properties/transforms.html#bpy-types-object-dimensions"), + ("bpy.types.object.is_holdout*", "scene_layout/object/properties/visibility.html#bpy-types-object-is-holdout"), ("bpy.types.object.pass_index*", "scene_layout/object/properties/relations.html#bpy-types-object-pass-index"), ("bpy.types.object.track_axis*", "scene_layout/object/properties/relations.html#bpy-types-object-track-axis"), ("bpy.types.pose.use_mirror_x*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-mirror-x"), ("bpy.types.preferencessystem*", "editors/preferences/system.html#bpy-types-preferencessystem"), ("bpy.types.scene.active_clip*", "scene_layout/scene/properties.html#bpy-types-scene-active-clip"), - ("bpy.types.scene.frame_start*", "render/output/properties/dimensions.html#bpy-types-scene-frame-start"), + ("bpy.types.scene.frame_start*", "render/output/properties/frame_range.html#bpy-types-scene-frame-start"), ("bpy.types.sceneeevee.shadow*", "render/eevee/render_settings/shadows.html#bpy-types-sceneeevee-shadow"), ("bpy.types.screen.use_follow*", "editors/timeline.html#bpy-types-screen-use-follow"), ("bpy.types.sequencetransform*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform"), @@ -1661,7 +1708,8 @@ url_manual_mapping = ( ("bpy.types.displaysafeareas*", "render/cameras.html#bpy-types-displaysafeareas"), ("bpy.types.editbone.bbone_x*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-x"), ("bpy.types.editbone.bbone_z*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-z"), - ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierstepped"), + ("bpy.types.fcurve.data_path*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-data-path"), + ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"), ("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"), ("bpy.types.mask.frame_start*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-start"), ("bpy.types.mesh.*customdata*", "modeling/meshes/properties/custom_data.html#bpy-types-mesh-customdata"), @@ -1675,7 +1723,7 @@ url_manual_mapping = ( ("bpy.types.pose.use_auto_ik*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-auto-ik"), ("bpy.types.preferencesinput*", "editors/preferences/input.html#bpy-types-preferencesinput"), ("bpy.types.rigifyparameters*", "addons/rigging/rigify/rig_types/index.html#bpy-types-rigifyparameters"), - ("bpy.types.scene.frame_step*", "render/output/properties/dimensions.html#bpy-types-scene-frame-step"), + ("bpy.types.scene.frame_step*", "render/output/properties/frame_range.html#bpy-types-scene-frame-step"), ("bpy.types.sceneeevee.bloom*", "render/eevee/render_settings/bloom.html#bpy-types-sceneeevee-bloom"), ("bpy.types.sculpt.show_mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-sculpt-show-mask"), ("bpy.types.sequence.channel*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequence-channel"), @@ -1708,6 +1756,7 @@ url_manual_mapping = ( ("bpy.ops.clip.clean_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-clean-tracks"), ("bpy.ops.clip.delete_track*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-delete-track"), ("bpy.ops.clip.solve_camera*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-solve-camera"), + ("bpy.ops.constraint.delete*", "animation/constraints/interface/header.html#bpy-ops-constraint-delete"), ("bpy.ops.curve.smooth_tilt*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-tilt"), ("bpy.ops.fluid.bake_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-bake-guides"), ("bpy.ops.fluid.free_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-free-guides"), @@ -1751,15 +1800,16 @@ url_manual_mapping = ( ("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"), ("bpy.types.brush.mask_tool*", "sculpt_paint/sculpting/tools/mask.html#bpy-types-brush-mask-tool"), ("bpy.types.constraint.mute*", "animation/constraints/interface/header.html#bpy-types-constraint-mute"), + ("bpy.types.constraint.name*", "animation/constraints/interface/header.html#bpy-types-constraint-name"), ("bpy.types.curve.eval_time*", "modeling/curves/properties/path_animation.html#bpy-types-curve-eval-time"), ("bpy.types.curve.fill_mode*", "modeling/curves/properties/shape.html#bpy-types-curve-fill-mode"), ("bpy.types.editbone.layers*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-layers"), ("bpy.types.editbone.parent*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-parent"), ("bpy.types.explodemodifier*", "modeling/modifiers/physics/explode.html#bpy-types-explodemodifier"), - ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fcurvemodifiers"), + ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"), ("bpy.types.floorconstraint*", "animation/constraints/relationship/floor.html#bpy-types-floorconstraint"), - ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiercycles"), - ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierlimits"), + ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiercycles"), + ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"), ("bpy.types.imagepaint.mode*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-mode"), ("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"), ("bpy.types.musgravetexture*", "render/materials/legacy_textures/types/musgrave.html#bpy-types-musgravetexture"), @@ -1770,7 +1820,7 @@ url_manual_mapping = ( ("bpy.types.preferencesedit*", "editors/preferences/editing.html#bpy-types-preferencesedit"), ("bpy.types.preferencesview*", "editors/preferences/interface.html#bpy-types-preferencesview"), ("bpy.types.rigidbodyobject*", "physics/rigid_body/index.html#bpy-types-rigidbodyobject"), - ("bpy.types.scene.frame_end*", "render/output/properties/dimensions.html#bpy-types-scene-frame-end"), + ("bpy.types.scene.frame_end*", "render/output/properties/frame_range.html#bpy-types-scene-frame-end"), ("bpy.types.sceneeevee.gtao*", "render/eevee/render_settings/ambient_occlusion.html#bpy-types-sceneeevee-gtao"), ("bpy.types.screen.use_play*", "editors/timeline.html#bpy-types-screen-use-play"), ("bpy.types.shadernodebevel*", "render/shader_nodes/input/bevel.html#bpy-types-shadernodebevel"), @@ -1786,6 +1836,7 @@ url_manual_mapping = ( ("bpy.types.texturenodemath*", "editors/texture_node/types/converter/math.html#bpy-types-texturenodemath"), ("bpy.types.volume.filepath*", "modeling/volumes/properties.html#bpy-types-volume-filepath"), ("bpy.ops.clip.join_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-join-tracks"), + ("bpy.ops.constraint.apply*", "animation/constraints/interface/header.html#bpy-ops-constraint-apply"), ("bpy.ops.curve.select_row*", "modeling/surfaces/selecting.html#bpy-ops-curve-select-row"), ("bpy.ops.curve.tilt_clear*", "modeling/curves/editing/control_points.html#bpy-ops-curve-tilt-clear"), ("bpy.ops.curve.vertex_add*", "modeling/curves/editing/other.html#bpy-ops-curve-vertex-add"), @@ -1829,7 +1880,8 @@ url_manual_mapping = ( ("bpy.types.brush.hardness*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-hardness"), ("bpy.types.curvesmodifier*", "video_editing/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"), ("bpy.types.ffmpegsettings*", "render/output/properties/output.html#bpy-types-ffmpegsettings"), - ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifiernoise"), + ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"), + ("bpy.types.keyframe.co_ui*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-co-ui"), ("bpy.types.mask.frame_end*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-end"), ("bpy.types.material.paint*", "sculpt_paint/texture_paint/index.html#bpy-types-material-paint"), ("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"), @@ -1861,6 +1913,7 @@ url_manual_mapping = ( ("bpy.ops.clip.select_all*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-all"), ("bpy.ops.clip.select_box*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-box"), ("bpy.ops.clip.set_origin*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-origin"), + ("bpy.ops.constraint.copy*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy"), ("bpy.ops.curve.subdivide*", "modeling/curves/editing/segments.html#bpy-ops-curve-subdivide"), ("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"), ("bpy.ops.file.unpack_all*", "files/blend/packed_data.html#bpy-ops-file-unpack-all"), @@ -1924,6 +1977,7 @@ url_manual_mapping = ( ("bpy.types.fieldsettings*", "physics/forces/force_fields/index.html#bpy-types-fieldsettings"), ("bpy.types.imagesequence*", "video_editing/sequencer/strips/image.html#bpy-types-imagesequence"), ("bpy.types.marbletexture*", "render/materials/legacy_textures/types/marble.html#bpy-types-marbletexture"), + ("bpy.types.modifier.name*", "modeling/modifiers/introduction.html#bpy-types-modifier-name"), ("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"), ("bpy.types.moviesequence*", "video_editing/sequencer/strips/movie.html#bpy-types-moviesequence"), ("bpy.types.movietracking*", "movie_clip/tracking/index.html#bpy-types-movietracking"), @@ -2106,6 +2160,7 @@ url_manual_mapping = ( ("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"), ("bpy.types.constraint*", "animation/constraints/index.html#bpy-types-constraint"), ("bpy.types.imagepaint*", "sculpt_paint/texture_paint/index.html#bpy-types-imagepaint"), + ("bpy.types.keymapitem*", "editors/preferences/keymap.html#bpy-types-keymapitem"), ("bpy.types.lightprobe*", "render/eevee/light_probes/index.html#bpy-types-lightprobe"), ("bpy.types.maskparent*", "movie_clip/masking/sidebar.html#bpy-types-maskparent"), ("bpy.types.maskspline*", "movie_clip/masking/sidebar.html#bpy-types-maskspline"), @@ -2142,7 +2197,7 @@ url_manual_mapping = ( ("bpy.types.bone.hide*", "animation/armatures/bones/properties/display.html#bpy-types-bone-hide"), ("bpy.types.colorramp*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp"), ("bpy.types.dopesheet*", "editors/dope_sheet/index.html#bpy-types-dopesheet"), - ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifier"), + ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifier"), ("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"), ("bpy.types.masklayer*", "movie_clip/masking/sidebar.html#bpy-types-masklayer"), ("bpy.types.movieclip*", "movie_clip/index.html#bpy-types-movieclip"), diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 6a9306c2eab..44b77ab2aac 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2606,8 +2606,7 @@ def km_sequencer(params): for i in range(10) ) ), - ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, - {"properties": [("deselect_all", True)]}), + ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None), ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "shift": True}, {"properties": [("extend", True)]}), ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "alt": True}, @@ -6997,8 +6996,7 @@ def km_sequencer_editor_tool_select(params): "Sequencer Tool: Select", {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, - {"properties": [("deselect_all", not params.legacy)]}), + ("sequencer.select", {"type": params.select_mouse, "value": 'PRESS'}, None), *_template_items_change_frame(params), ]}, ) diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index dba94d71a43..dbe351eb10c 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -1814,8 +1814,7 @@ def km_sequencer(params): for i in range(10) ) ), - ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, - {"properties": [("deselect_all", True)]}), + ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("extend", True)]}), ("sequencer.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py index be47890a002..c8328f5ee42 100644 --- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py +++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py @@ -46,6 +46,7 @@ def update_factory_startup_screens(): def update_factory_startup_scenes(): for scene in bpy.data.scenes: scene.tool_settings.use_keyframe_insert_auto = True + scene.tool_settings.gpencil_sculpt.use_scale_thickness = True def update_factory_startup_grease_pencils(): diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py index 8c76018b7a1..b241e537c38 100644 --- a/release/scripts/startup/bl_operators/assets.py +++ b/release/scripts/startup/bl_operators/assets.py @@ -85,9 +85,9 @@ class ASSET_OT_open_containing_blend_file(Operator): @classmethod def poll(cls, context): asset_file_handle = getattr(context, 'asset_file_handle', None) - asset_library = getattr(context, 'asset_library', None) + asset_library_ref = getattr(context, 'asset_library_ref', None) - if not asset_library: + if not asset_library_ref: cls.poll_message_set("No asset library selected") return False if not asset_file_handle: @@ -100,13 +100,13 @@ class ASSET_OT_open_containing_blend_file(Operator): def execute(self, context): asset_file_handle = context.asset_file_handle - asset_library = context.asset_library + asset_library_ref = context.asset_library_ref if asset_file_handle.local_id: self.report({'WARNING'}, "This asset is stored in the current blend file") return {'CANCELLED'} - asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library) + asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library_ref) self.open_in_new_blender(asset_lib_path) wm = context.window_manager diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py index ec2887a1a74..258a73bd70b 100644 --- a/release/scripts/startup/bl_operators/geometry_nodes.py +++ b/release/scripts/startup/bl_operators/geometry_nodes.py @@ -42,8 +42,8 @@ def geometry_node_group_empty_new(): def geometry_modifier_poll(context): ob = context.object - # Test object support for geometry node modifier (No curve, or hair object support yet) - if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME'}: + # Test object support for geometry node modifier (No hair object support yet) + if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT'}: return False return True diff --git a/release/scripts/startup/bl_ui/properties_data_curve.py b/release/scripts/startup/bl_ui/properties_data_curve.py index 85f672cd50f..e5b675db2c5 100644 --- a/release/scripts/startup/bl_ui/properties_data_curve.py +++ b/release/scripts/startup/bl_ui/properties_data_curve.py @@ -120,7 +120,6 @@ class DATA_PT_shape_curve(CurveButtonsPanel, Panel): sub = col.column() sub.active = (curve.dimensions == '2D' or (curve.bevel_mode != 'OBJECT' and curve.dimensions == '3D')) sub.prop(curve, "fill_mode") - col.prop(curve, "use_fill_deform") if is_curve: col = layout.column() diff --git a/release/scripts/startup/bl_ui/properties_freestyle.py b/release/scripts/startup/bl_ui/properties_freestyle.py index fd12747e2fa..3c765c10154 100644 --- a/release/scripts/startup/bl_ui/properties_freestyle.py +++ b/release/scripts/startup/bl_ui/properties_freestyle.py @@ -22,7 +22,6 @@ from bpy.types import Menu, Panel, UIList # Render properties - class RenderFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -49,20 +48,17 @@ class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout layout.use_property_split = True - layout.use_property_decorate = False # No animation. - + layout.use_property_decorate = False rd = context.scene.render - layout.active = rd.use_freestyle - - layout.prop(rd, "line_thickness_mode", expand=True) - + layout.row().prop(rd, "line_thickness_mode", expand=True, text="Line Thickness Mode") if rd.line_thickness_mode == 'ABSOLUTE': layout.prop(rd, "line_thickness") # Render layer properties + class ViewLayerFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -76,8 +72,12 @@ class ViewLayerFreestyleButtonsPanel: rd = scene.render with_freestyle = bpy.app.build_options.freestyle - return (scene and with_freestyle and rd.use_freestyle and - (context.engine in cls.COMPAT_ENGINES)) + return ( + scene + and with_freestyle + and rd.use_freestyle + and (context.engine in cls.COMPAT_ENGINES) + ) class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel): @@ -88,7 +88,35 @@ class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel): if not super().poll(context): return False view_layer = context.view_layer - return view_layer and view_layer.freestyle_settings.mode == 'EDITOR' + return ( + view_layer + and view_layer.use_freestyle + and view_layer.freestyle_settings.mode == 'EDITOR' + ) + + +class ViewLayerFreestyleLineStyle(ViewLayerFreestyleEditorButtonsPanel): + # Freestyle Linestyle Panels + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + if not super().poll(context): + return False + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + if lineset is None: + return False + linestyle = lineset.linestyle + if linestyle is None: + return False + + return True + + +class ViewLayerFreestyleLinestyleStrokesSubPanel(ViewLayerFreestyleLineStyle): + # Freestyle Linestyle Strokes sub panels + bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes" class VIEWLAYER_UL_linesets(UIList): @@ -126,54 +154,85 @@ class VIEWLAYER_PT_freestyle(ViewLayerFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False view_layer = context.view_layer freestyle = view_layer.freestyle_settings layout.active = view_layer.use_freestyle - row = layout.row() - layout.prop(freestyle, "mode", text="Control Mode") - layout.prop(freestyle, "use_view_map_cache", text="View Map Cache") - layout.prop(freestyle, "as_render_pass", text="As Render Pass") - layout.label(text="Edge Detection Options:") + col = layout.column(align=True) + col.prop(freestyle, "mode", text="Control Mode") + col.prop(freestyle, "use_view_map_cache", text="View Map Cache") + col.prop(freestyle, "as_render_pass", text="As Render Pass") + + +class VIEWLAYER_PT_freestyle_edge_detection(ViewLayerFreestyleButtonsPanel, Panel): + bl_label = "Edge Detection" + bl_parent_id = "VIEWLAYER_PT_freestyle" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False - split = layout.split() + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + + layout.active = view_layer.use_freestyle - col = split.column() + col = layout.column() col.prop(freestyle, "crease_angle") col.prop(freestyle, "use_culling") - col.prop(freestyle, "use_advanced_options") - - col = split.column() col.prop(freestyle, "use_smoothness") + if freestyle.mode == 'SCRIPT': col.prop(freestyle, "use_material_boundaries") - # Advanced options are hidden by default to warn new users - if freestyle.use_advanced_options: - if freestyle.mode == 'SCRIPT': - row = layout.row() - row.prop(freestyle, "use_ridges_and_valleys") - row.prop(freestyle, "use_suggestive_contours") - row = layout.row() - row.prop(freestyle, "sphere_radius") - row.prop(freestyle, "kr_derivative_epsilon") - if freestyle.mode == 'SCRIPT': - row = layout.row() - row.label(text="Style Modules:") - row.operator("scene.freestyle_module_add", text="Add") - for module in freestyle.modules: - box = layout.box() - box.context_pointer_set("freestyle_module", module) - row = box.row(align=True) - row.prop(module, "use", text="") - row.prop(module, "script", text="") - row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="") - row.operator("scene.freestyle_module_remove", icon='X', text="") - row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP' - row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + col.prop(freestyle, "use_ridges_and_valleys") + col.prop(freestyle, "use_suggestive_contours") + col.prop(freestyle, "sphere_radius") + col.prop(freestyle, "kr_derivative_epsilon") + + +class VIEWLAYER_PT_freestyle_style_modules(ViewLayerFreestyleButtonsPanel, Panel): + bl_label = "Style Modules" + bl_parent_id = "VIEWLAYER_PT_freestyle" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + return freestyle.mode == 'SCRIPT' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + + layout.active = view_layer.use_freestyle + + col = layout.column() + col.use_property_split = False + row = col.row() + row.operator("scene.freestyle_module_add", text="Add") + for module in freestyle.modules: + box = col.box() + box.context_pointer_set("freestyle_module", module) + row = box.row(align=True) + row.prop(module, "use", text="") + row.prop(module, "script", text="") + row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="") + row.operator("scene.freestyle_module_remove", icon='X', text="") + row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP' + row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN' class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel): @@ -198,10 +257,13 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel freestyle = view_layer.freestyle_settings lineset = freestyle.linesets.active - layout.active = view_layer.use_freestyle - row = layout.row() - rows = 4 if lineset else 2 + + is_sortable = len(freestyle.linesets) > 1 + rows = 3 + if is_sortable: + rows = 5 + row.template_list( "VIEWLAYER_UL_linesets", "", @@ -212,157 +274,504 @@ class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel rows=rows, ) - sub = row.column(align=True) - sub.operator("scene.freestyle_lineset_add", icon='ADD', text="") - sub.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="") - sub.menu("RENDER_MT_lineset_context_menu", icon='DOWNARROW_HLT', text="") + col = row.column(align=True) + col.operator("scene.freestyle_lineset_add", icon='ADD', text="") + col.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="") + + col.separator() + + col.menu("RENDER_MT_lineset_context_menu", icon="DOWNARROW_HLT", text="") + + if is_sortable: + col.separator() + col.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP' + col.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + if lineset: - sub.separator() - sub.separator() - sub.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP' - sub.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN' - - col = layout.column() - col.label(text="Selection By:") - row = col.row(align=True) - row.prop(lineset, "select_by_visibility", text="Visibility", toggle=True) - row.prop(lineset, "select_by_edge_types", text="Edge Types", toggle=True) - row.prop(lineset, "select_by_face_marks", text="Face Marks", toggle=True) - row.prop(lineset, "select_by_collection", text="Collection", toggle=True) - row.prop(lineset, "select_by_image_border", text="Image Border", toggle=True) - - if lineset.select_by_visibility: - col.label(text="Visibility:") - row = col.row(align=True) - row.prop(lineset, "visibility", expand=True) - if lineset.visibility == 'RANGE': - row = col.row(align=True) - row.prop(lineset, "qi_start") - row.prop(lineset, "qi_end") - - if lineset.select_by_edge_types: - col.label(text="Edge Types:") - row = col.row() - row.prop(lineset, "edge_type_negation", expand=True) - row.prop(lineset, "edge_type_combination", expand=True) - - split = col.split() - - sub = split.column() - self.draw_edge_type_buttons(sub, lineset, "silhouette") - self.draw_edge_type_buttons(sub, lineset, "border") - self.draw_edge_type_buttons(sub, lineset, "contour") - self.draw_edge_type_buttons(sub, lineset, "suggestive_contour") - self.draw_edge_type_buttons(sub, lineset, "ridge_valley") - - sub = split.column() - self.draw_edge_type_buttons(sub, lineset, "crease") - self.draw_edge_type_buttons(sub, lineset, "edge_mark") - self.draw_edge_type_buttons(sub, lineset, "external_contour") - self.draw_edge_type_buttons(sub, lineset, "material_boundary") - - if lineset.select_by_face_marks: - col.label(text="Face Marks:") - row = col.row() - row.prop(lineset, "face_mark_negation", expand=True) - row.prop(lineset, "face_mark_condition", expand=True) - - if lineset.select_by_collection: - col.label(text="Collection:") - row = col.row() - row.prop(lineset, "collection", text="") - row.prop(lineset, "collection_negation", expand=True) - - -class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Panel): - bl_label = "Freestyle Line Style" + layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new") + layout.separator() + col = layout.column(heading="Select by") + col.use_property_split = True + col.use_property_decorate = False + col.prop(lineset, "select_by_image_border", text="Image Border") + + +# Freestyle Lineset Sub Panels +class VIEWLAYER_PT_freestyle_lineset_visibilty(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Visibility" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_visibility", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_visibility + col = layout.column(align=True, heading="Type") + col.prop(lineset, "visibility", text="Type", expand=False) + + if lineset.visibility == 'RANGE': + col = layout.column(align=True) + col.use_property_split = True + col.prop(lineset, "qi_start") + col.prop(lineset, "qi_end") + + +class VIEWLAYER_PT_freestyle_lineset_edgetype(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Edge Type" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_edge_types", text="") + + def draw_edge_type_buttons(self, box, lineset, edge_type): + # property names + select_edge_type = "select_" + edge_type + exclude_edge_type = "exclude_" + edge_type + # draw edge type buttons + row = box.row(align=True) + row.prop(lineset, select_edge_type) + sub = row.column(align=True) + sub.prop(lineset, exclude_edge_type, text="") + sub.active = getattr(lineset, select_edge_type) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_edge_types + layout.row().prop(lineset, "edge_type_negation", expand=True, text="Negation") + layout.row().prop(lineset, "edge_type_combination", expand=True, text="Combination") + + col = layout.column(heading="Type") + self.draw_edge_type_buttons(col, lineset, "silhouette") + self.draw_edge_type_buttons(col, lineset, "crease") + self.draw_edge_type_buttons(col, lineset, "border") + self.draw_edge_type_buttons(col, lineset, "edge_mark") + self.draw_edge_type_buttons(col, lineset, "contour") + self.draw_edge_type_buttons(col, lineset, "external_contour") + self.draw_edge_type_buttons(col, lineset, "material_boundary") + self.draw_edge_type_buttons(col, lineset, "suggestive_contour") + self.draw_edge_type_buttons(col, lineset, "ridge_valley") + + +class VIEWLAYER_PT_freestyle_lineset_facemarks(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Face Marks" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_face_marks", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_face_marks + layout.row().prop(lineset, "face_mark_negation", expand=True, text="Negation") + layout.row().prop(lineset, "face_mark_condition", expand=True, text="Condition") + + +class VIEWLAYER_PT_freestyle_lineset_collection(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Collection" + bl_parent_id = "VIEWLAYER_PT_freestyle_lineset" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + bl_options = {'DEFAULT_CLOSED'} - def draw_modifier_box_header(self, box, modifier): - row = box.row() - row.context_pointer_set("modifier", modifier) - if modifier.expanded: - icon = 'TRIA_DOWN' - else: - icon = 'TRIA_RIGHT' - row.prop(modifier, "expanded", text="", icon=icon, emboss=False) - # TODO: Use icons rather than text label, would save some room! - row.label(text=modifier.rna_type.name) - row.prop(modifier, "name", text="") - if modifier.use: - icon = 'RESTRICT_RENDER_OFF' - else: - icon = 'RESTRICT_RENDER_ON' - row.prop(modifier, "use", text="", icon=icon) - sub = row.row(align=True) - sub.operator("scene.freestyle_modifier_copy", icon='NONE', text="Copy") - sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP' - sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN' - sub.operator("scene.freestyle_modifier_remove", icon='X', text="") - - def draw_modifier_box_error(self, box, _modifier, message): - row = box.row() - row.label(text=message, icon='ERROR') - - def draw_modifier_common(self, box, modifier): - row = box.row() - row.prop(modifier, "blend", text="") - row.prop(modifier, "influence") - - def draw_modifier_color_ramp_common(self, box, modifier, has_range): - box.template_color_ramp(modifier, "color_ramp", expand=True) - if has_range: - row = box.row(align=True) - row.prop(modifier, "range_min") - row.prop(modifier, "range_max") - - def draw_modifier_curve_common(self, box, modifier, has_range, has_value): - row = box.row() - row.prop(modifier, "mapping", text="") - sub = row.column() - sub.prop(modifier, "invert") - if modifier.mapping == 'CURVE': - sub.active = False - box.template_curve_mapping(modifier, "curve") - if has_range: - row = box.row(align=True) - row.prop(modifier, "range_min") - row.prop(modifier, "range_max") - if has_value: - row = box.row(align=True) - row.prop(modifier, "value_min") - row.prop(modifier, "value_max") + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.prop(lineset, "select_by_collection", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + layout.active = lineset.select_by_collection + layout.row().prop(lineset, "collection", text="Line Set Collection") + layout.row().prop(lineset, "collection_negation", expand=True, text="Negation") + + +# Linestyle Modifier Drawing code +def draw_modifier_box_header(box, modifier): + row = box.row() + row.use_property_split = False + row.context_pointer_set("modifier", modifier) + if modifier.expanded: + icon = 'TRIA_DOWN' + else: + icon = 'TRIA_RIGHT' + row.prop(modifier, "expanded", text="", icon=icon, emboss=False) + + sub = row.row(align=True) + sub.prop(modifier, "name", text="") + if modifier.use: + icon = 'RESTRICT_RENDER_OFF' + else: + icon = 'RESTRICT_RENDER_ON' + sub.prop(modifier, "use", text="", icon=icon) + sub.operator("scene.freestyle_modifier_copy", icon='DUPLICATE', text="") + + sub = row.row(align=True) + sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP' + sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + + row.operator("scene.freestyle_modifier_remove", icon='X', text="", emboss=False) + + +def draw_modifier_box_error(box, _modifier, message): + row = box.row() + row.label(text=message, icon='ERROR') + + +def draw_modifier_common(box, modifier): + col = box.column() + col.prop(modifier, "blend", text="Blend Mode") + col.prop(modifier, "influence") + + +def draw_modifier_color_ramp_common(box, modifier, has_range): + box.template_color_ramp(modifier, "color_ramp", expand=True) + if has_range: + col = box.column(align=True) + col.prop(modifier, "range_min", text="Range Min") + col.prop(modifier, "range_max", text="Max") + + +def draw_modifier_curve_common(box, modifier, has_range, has_value): + row = box.row() + row.prop(modifier, "mapping", text="Mapping") + if modifier.mapping == 'LINEAR': + box.prop(modifier, "invert") + if has_range: + col = box.column(align=True) + col.prop(modifier, "range_min", text="Range Min") + col.prop(modifier, "range_max", text="Max") + if has_value: + col = box.column(align=True) + col.prop(modifier, "value_min", text="Value Min") + col.prop(modifier, "value_max", text="Max") + if modifier.mapping == 'CURVE': + box.template_curve_mapping(modifier, "curve") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Strokes" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column(align=True) + col.prop(linestyle, "caps", expand=False) + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_chaining(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Chaining" + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_chaining", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_chaining + layout.row().prop(linestyle, "chaining", expand=True, text="Method") + if linestyle.chaining == 'SKETCHY': + layout.prop(linestyle, "rounds") + layout.prop(linestyle, "use_same_object") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Splitting" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + row = layout.row(align=False, heading="Min 2D Angle") + row.prop(linestyle, "use_angle_min", text="") + sub = row.row() + sub.active = linestyle.use_angle_min + sub.prop(linestyle, "angle_min", text="") + + row = layout.row(align=False, heading="Max 2D Angle") + row.prop(linestyle, "use_angle_max", text="") + sub = row.row() + sub.active = linestyle.use_angle_max + sub.prop(linestyle, "angle_max", text="") + + row = layout.row(align=False, heading="2D Length") + row.prop(linestyle, "use_split_length", text="") + sub = row.row() + sub.active = linestyle.use_split_length + sub.prop(linestyle, "split_length", text="") + + layout.prop(linestyle, "material_boundary") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Split Pattern" + bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes_splitting" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_split_pattern", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_split_pattern + + col = layout.column(align=True) + col.prop(linestyle, "split_dash1", text="Dash 1") + col.prop(linestyle, "split_dash2", text="2") + col.prop(linestyle, "split_dash3", text="3") + col = layout.column(align=True) + col.prop(linestyle, "split_gap1", text="Gap 1") + col.prop(linestyle, "split_gap2", text="2") + col.prop(linestyle, "split_gap3", text="3") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_sorting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Sorting" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_sorting", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + + layout.active = linestyle.use_sorting + + layout.prop(linestyle, "sort_key") + + row = layout.row() + row.active = linestyle.sort_key in { + 'DISTANCE_FROM_CAMERA', + 'PROJECTED_X', + 'PROJECTED_Y', + } + row.prop(linestyle, "integration_type") + layout.row().prop(linestyle, "sort_order", expand=True, text="Sort Order") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_selection(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Selection" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + row = layout.row(align=False, heading="Min 2D Length") + row.prop(linestyle, "use_length_min", text="") + sub = row.row() + sub.active = linestyle.use_length_min + sub.prop(linestyle, "length_min", text="") + + row = layout.row(align=False, heading="Max 2D Length") + row.prop(linestyle, "use_length_max", text="") + sub = row.row() + sub.active = linestyle.use_length_max + sub.prop(linestyle, "length_max", text="") + + row = layout.row(align=False, heading="Chain Count") + row.prop(linestyle, "use_chain_count", text="") + sub = row.row() + sub.active = linestyle.use_chain_count + sub.prop(linestyle, "chain_count", text="") + + +class VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel): + bl_label = "Dashed Line" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, context): + layout = self.layout + + view_layer = context.view_layer + freestyle = view_layer.freestyle_settings + lineset = freestyle.linesets.active + + linestyle = lineset.linestyle + layout.prop(linestyle, "use_dashed_line", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + linestyle = lineset.linestyle + + layout.active = linestyle.use_dashed_line + + col = layout.column(align=True) + col.prop(linestyle, "dash1", text="Dash 1") + col.prop(linestyle, "dash2", text="2") + col.prop(linestyle, "dash3", text="3") + col = layout.column(align=True) + col.prop(linestyle, "gap1", text="Gap 1") + col.prop(linestyle, "gap2", text="2") + col.prop(linestyle, "gap3", text="3") + + +class VIEWLAYER_PT_freestyle_linestyle_color(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Color" + bl_options = {'DEFAULT_CLOSED'} def draw_color_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_color_ramp_common(box, modifier, True) + draw_modifier_color_ramp_common(box, modifier, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'COLOR' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_color_ramp_common(box, modifier, True) + draw_modifier_color_ramp_common(box, modifier, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'COLOR' prop.name = modifier.name elif modifier.type == 'MATERIAL': row = box.row() - row.prop(modifier, "material_attribute", text="") - sub = row.column() + row.prop(modifier, "material_attribute", + text="Material Attribute") + sub = box.column() sub.prop(modifier, "use_ramp") if modifier.material_attribute in {'LINE', 'DIFF', 'SPEC'}: sub.active = True @@ -371,166 +780,292 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan sub.active = False show_ramp = True if show_ramp: - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'TANGENT': - self.draw_modifier_color_ramp_common(box, modifier, False) + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'NOISE': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row.prop(modifier, "seed") + subcol = box.column(align=True) + subcol.prop(modifier, "amplitude") + subcol.prop(modifier, "period") + subcol.prop(modifier, "seed") + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + subcol = box.column(align=True) + subcol.prop(modifier, "angle_min", text="Angle Min") + subcol.prop(modifier, "angle_max", text="Max") + draw_modifier_color_ramp_common(box, modifier, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_color_ramp_common(box, modifier, False) - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min", text="Curvature Min") + subcol.prop(modifier, "curvature_max", text="Max") + + draw_modifier_color_ramp_common(box, modifier, False) + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "color", text="Base Color") + col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.color_modifiers: + self.draw_color_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_alpha(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Alpha" + bl_options = {'DEFAULT_CLOSED'} def draw_alpha_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_curve_common(box, modifier, False, False) + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_curve_common(box, modifier, True, False) + draw_modifier_curve_common(box, modifier, True, False) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'ALPHA' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_curve_common(box, modifier, True, False) + draw_modifier_curve_common(box, modifier, True, False) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'ALPHA' prop.name = modifier.name elif modifier.type == 'MATERIAL': - box.prop(modifier, "material_attribute", text="") - self.draw_modifier_curve_common(box, modifier, False, False) + box.prop(modifier, "material_attribute", text="Material Attribute") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'TANGENT': - self.draw_modifier_curve_common(box, modifier, False, False) + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'NOISE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row.prop(modifier, "seed") + col = box.column(align=True) + col.prop(modifier, "amplitude") + col.prop(modifier, "period") + col.prop(modifier, "seed") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + col = box.column(align=True) + col.prop(modifier, "angle_min", text="Angle Min") + col.prop(modifier, "angle_max", text="Max") + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + draw_modifier_curve_common(box, modifier, False, False) + + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min", text="Curvature Min") + subcol.prop(modifier, "curvature_max", text="Max") + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "alpha", text="Base Transparency") + col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.alpha_modifiers: + self.draw_alpha_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_thickness(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Thickness" + bl_options = {'DEFAULT_CLOSED'} def draw_thickness_modifier(self, context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() - self.draw_modifier_common(box, modifier) + draw_modifier_common(box, modifier) if modifier.type == 'ALONG_STROKE': - self.draw_modifier_curve_common(box, modifier, False, True) + draw_modifier_curve_common(box, modifier, False, True) elif modifier.type == 'DISTANCE_FROM_OBJECT': box.prop(modifier, "target") - self.draw_modifier_curve_common(box, modifier, True, True) + draw_modifier_curve_common(box, modifier, True, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'THICKNESS' prop.name = modifier.name elif modifier.type == 'DISTANCE_FROM_CAMERA': - self.draw_modifier_curve_common(box, modifier, True, True) + draw_modifier_curve_common(box, modifier, True, True) prop = box.operator("scene.freestyle_fill_range_by_selection") prop.type = 'THICKNESS' prop.name = modifier.name elif modifier.type == 'MATERIAL': - box.prop(modifier, "material_attribute", text="") - self.draw_modifier_curve_common(box, modifier, False, True) + box.prop(modifier, "material_attribute", text="Material Attribute") + draw_modifier_curve_common(box, modifier, False, True) elif modifier.type == 'CALLIGRAPHY': box.prop(modifier, "orientation") - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") elif modifier.type == 'TANGENT': - self.draw_modifier_curve_common(box, modifier, False, False) self.mapping = 'CURVE' - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") + + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'NOISE': - row = box.row(align=False) - row.prop(modifier, "amplitude") - row.prop(modifier, "period") - row = box.row(align=False) - row.prop(modifier, "seed") - row.prop(modifier, "use_asymmetric") + col = box.column(align=True) + col.prop(modifier, "amplitude") + col.prop(modifier, "period") + + col = box.column(align=True) + col.prop(modifier, "seed") + col.prop(modifier, "use_asymmetric") elif modifier.type == 'CREASE_ANGLE': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") - row = box.row(align=True) - row.prop(modifier, "angle_min") - row.prop(modifier, "angle_max") + col = box.column(align=True) + col.prop(modifier, "thickness_min", text="Thickness Min") + col.prop(modifier, "thickness_max", text="Max") + + col = box.column(align=True) + col.prop(modifier, "angle_min", text="Angle Min") + col.prop(modifier, "angle_max", text="Max") + + draw_modifier_curve_common(box, modifier, False, False) elif modifier.type == 'CURVATURE_3D': - self.draw_modifier_curve_common(box, modifier, False, False) - row = box.row(align=True) - row.prop(modifier, "thickness_min") - row.prop(modifier, "thickness_max") - row = box.row(align=True) - row.prop(modifier, "curvature_min") - row.prop(modifier, "curvature_max") + subcol = box.column(align=True) + subcol.prop(modifier, "thickness_min", text="Thickness Min") + subcol.prop(modifier, "thickness_max", text="Max") + + subcol = box.column(align=True) + subcol.prop(modifier, "curvature_min") + subcol.prop(modifier, "curvature_max") + + draw_modifier_curve_common(box, modifier, False, False) + freestyle = context.view_layer.freestyle_settings if not freestyle.use_smoothness: message = "Enable Face Smoothness to use this modifier" - self.draw_modifier_box_error(col.box(), modifier, message) + draw_modifier_box_error(col.box(), modifier, message) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + col = layout.column() + row = col.row() + row.prop(linestyle, "thickness", text="Base Thickness") + subcol = col.column() + subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object + row = subcol.row() + row.prop(linestyle, "thickness_position", expand=False) + + if linestyle.thickness_position == 'RELATIVE': + row = subcol.row() + row.prop(linestyle, "thickness_ratio") + + col = layout.column() + col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.thickness_modifiers: + self.draw_thickness_modifier(context, modifier) + + +class VIEWLAYER_PT_freestyle_linestyle_geometry(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Geometry" + bl_options = {'DEFAULT_CLOSED'} def draw_geometry_modifier(self, _context, modifier): layout = self.layout col = layout.column(align=True) - self.draw_modifier_box_header(col.box(), modifier) + draw_modifier_box_header(col.box(), modifier) if modifier.expanded: box = col.box() @@ -541,40 +1076,40 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan box.prop(modifier, "error") elif modifier.type == 'SINUS_DISPLACEMENT': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "wavelength") col.prop(modifier, "amplitude") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "phase") elif modifier.type == 'SPATIAL_NOISE': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "amplitude") col.prop(modifier, "scale") col.prop(modifier, "octaves") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "smooth") col.prop(modifier, "use_pure_random") elif modifier.type == 'PERLIN_NOISE_1D': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "frequency") col.prop(modifier, "amplitude") col.prop(modifier, "seed") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "octaves") col.prop(modifier, "angle") elif modifier.type == 'PERLIN_NOISE_2D': - split = box.split() - col = split.column() + col = box.column(align=True) col.prop(modifier, "frequency") col.prop(modifier, "amplitude") col.prop(modifier, "seed") - col = split.column() + + col = box.column(align=True) col.prop(modifier, "octaves") col.prop(modifier, "angle") @@ -594,33 +1129,34 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan row = box.row() row.prop(modifier, "shape", expand=True) box.prop(modifier, "rounds") - row = box.row() + subcol = box.column(align=True) if modifier.shape in {'CIRCLES', 'ELLIPSES'}: - row.prop(modifier, "random_radius") - row.prop(modifier, "random_center") + subcol.prop(modifier, "random_radius", text="Random Radius") + subcol.prop(modifier, "random_center", text="Center") elif modifier.shape == 'SQUARES': - row.prop(modifier, "backbone_length") - row.prop(modifier, "random_backbone") + subcol.prop(modifier, "backbone_length", text="Backbone Length") + subcol.prop(modifier, "random_backbone", text="Randomness") elif modifier.type == '2D_OFFSET': - row = box.row(align=True) - row.prop(modifier, "start") - row.prop(modifier, "end") - row = box.row(align=True) - row.prop(modifier, "x") - row.prop(modifier, "y") + subcol = box.column(align=True) + subcol.prop(modifier, "start") + subcol.prop(modifier, "end") + + subcol = box.column(align=True) + subcol.prop(modifier, "x") + subcol.prop(modifier, "y") elif modifier.type == '2D_TRANSFORM': box.prop(modifier, "pivot") if modifier.pivot == 'PARAM': box.prop(modifier, "pivot_u") elif modifier.pivot == 'ABSOLUTE': - row = box.row(align=True) - row.prop(modifier, "pivot_x") - row.prop(modifier, "pivot_y") - row = box.row(align=True) - row.prop(modifier, "scale_x") - row.prop(modifier, "scale_y") + subcol = box.column(align=True) + subcol.prop(modifier, "pivot_x", text="Pivot X") + subcol.prop(modifier, "pivot_y", text="Y") + subcol = box.column(align=True) + subcol.prop(modifier, "scale_x", text="Scale X") + subcol.prop(modifier, "scale_y", text="Y") box.prop(modifier, "angle") elif modifier.type == 'SIMPLIFICATION': @@ -628,6 +1164,8 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False view_layer = context.view_layer lineset = view_layer.freestyle_settings.linesets.active @@ -638,181 +1176,61 @@ class VIEWLAYER_PT_freestyle_linestyle(ViewLayerFreestyleEditorButtonsPanel, Pan return linestyle = lineset.linestyle - layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new") if linestyle is None: return + row = layout.row(align=True) - row.prop(linestyle, "panel", expand=True) - if linestyle.panel == 'STROKES': - # Chaining - layout.prop(linestyle, "use_chaining", text="Chaining:") - split = layout.split(align=True) - split.active = linestyle.use_chaining - # First column - col = split.column() - col.active = linestyle.use_chaining - col.prop(linestyle, "chaining", text="") - if linestyle.chaining == 'SKETCHY': - col.prop(linestyle, "rounds") - # Second column - col = split.column() - col.prop(linestyle, "use_same_object") - - # Splitting - layout.label(text="Splitting:") - split = layout.split(align=True) - # First column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_angle_min", text="") - sub = row.row() - sub.active = linestyle.use_angle_min - sub.prop(linestyle, "angle_min") - row = col.row(align=True) - row.prop(linestyle, "use_angle_max", text="") - sub = row.row() - sub.active = linestyle.use_angle_max - sub.prop(linestyle, "angle_max") - # Second column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_split_length", text="") - sub = row.row() - sub.active = linestyle.use_split_length - sub.prop(linestyle, "split_length", text="2D Length") - row = col.row(align=True) - row.prop(linestyle, "material_boundary") - # End of columns - row = layout.row(align=True) - row.prop(linestyle, "use_split_pattern", text="") - sub = row.row(align=True) - sub.active = linestyle.use_split_pattern - sub.prop(linestyle, "split_dash1", text="D1") - sub.prop(linestyle, "split_gap1", text="G1") - sub.prop(linestyle, "split_dash2", text="D2") - sub.prop(linestyle, "split_gap2", text="G2") - sub.prop(linestyle, "split_dash3", text="D3") - sub.prop(linestyle, "split_gap3", text="G3") - - # Sorting - layout.prop(linestyle, "use_sorting", text="Sorting:") - col = layout.column() - col.active = linestyle.use_sorting - row = col.row(align=True) - row.prop(linestyle, "sort_key", text="") - sub = row.row() - sub.active = linestyle.sort_key in {'DISTANCE_FROM_CAMERA', - 'PROJECTED_X', - 'PROJECTED_Y'} - sub.prop(linestyle, "integration_type", text="") - row = col.row(align=True) - row.prop(linestyle, "sort_order", expand=True) - - # Selection - layout.label(text="Selection:") - split = layout.split(align=True) - # First column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_length_min", text="") - sub = row.row() - sub.active = linestyle.use_length_min - sub.prop(linestyle, "length_min") - row = col.row(align=True) - row.prop(linestyle, "use_length_max", text="") - sub = row.row() - sub.active = linestyle.use_length_max - sub.prop(linestyle, "length_max") - # Second column - col = split.column() - row = col.row(align=True) - row.prop(linestyle, "use_chain_count", text="") - sub = row.row() - sub.active = linestyle.use_chain_count - sub.prop(linestyle, "chain_count") - - # Caps - layout.label(text="Caps:") - row = layout.row(align=True) - row.prop(linestyle, "caps", expand=True) - - # Dashed lines - layout.prop(linestyle, "use_dashed_line", text="Dashed Line:") - row = layout.row(align=True) - row.active = linestyle.use_dashed_line - row.prop(linestyle, "dash1", text="D1") - row.prop(linestyle, "gap1", text="G1") - row.prop(linestyle, "dash2", text="D2") - row.prop(linestyle, "gap2", text="G2") - row.prop(linestyle, "dash3", text="D3") - row.prop(linestyle, "gap3", text="G3") - - elif linestyle.panel == 'COLOR': - col = layout.column() - row = col.row() - row.label(text="Base Color:") - row.prop(linestyle, "color", text="") - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.color_modifiers: - self.draw_color_modifier(context, modifier) - - elif linestyle.panel == 'ALPHA': - col = layout.column() - row = col.row() - row.label(text="Base Transparency:") - row.prop(linestyle, "alpha") - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.alpha_modifiers: - self.draw_alpha_modifier(context, modifier) - - elif linestyle.panel == 'THICKNESS': - col = layout.column() - row = col.row() - row.label(text="Base Thickness:") - row.prop(linestyle, "thickness") - subcol = col.column() - subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object - row = subcol.row() - row.prop(linestyle, "thickness_position", expand=True) - row = subcol.row() - row.prop(linestyle, "thickness_ratio") - row.active = (linestyle.thickness_position == 'RELATIVE') - col = layout.column() - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.thickness_modifiers: - self.draw_thickness_modifier(context, modifier) - - elif linestyle.panel == 'GEOMETRY': - col = layout.column() - col.label(text="Modifiers:") - col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier") - for modifier in linestyle.geometry_modifiers: - self.draw_geometry_modifier(context, modifier) - - elif linestyle.panel == 'TEXTURE': - layout.separator() + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) - row = layout.row() - row.prop(linestyle, "use_nodes") - row.prop(linestyle, "texture_spacing", text="Spacing Along Stroke") + col = layout.column() + col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.geometry_modifiers: + self.draw_geometry_modifier(context, modifier) - row = layout.row() - props = row.operator( - "wm.properties_context_change", - text="Go to Linestyle Textures Properties", - icon='TEXTURE', - ) - props.context = 'TEXTURE' - elif linestyle.panel == 'MISC': - pass +class VIEWLAYER_PT_freestyle_linestyle_texture(ViewLayerFreestyleLineStyle, Panel): + bl_label = "Freestyle Texture" + bl_options = {'DEFAULT_CLOSED'} + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False -# Material properties + view_layer = context.view_layer + lineset = view_layer.freestyle_settings.linesets.active + layout.active = view_layer.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + if linestyle is None: + return + + row = layout.row(align=True) + row.alignment = 'LEFT' + row.label(text=lineset.name, icon='LINE_DATA') + row.label(text="", icon='SMALL_TRI_RIGHT_VEC') + row.label(text=linestyle.name) + + layout.prop(linestyle, "use_nodes") + layout.prop(linestyle, "texture_spacing", text="Spacing Along Stroke") + + row = layout.row() + props = row.operator( + "wm.properties_context_change", + text="Go to Linestyle Textures Properties", + icon='TEXTURE', + ) + props.context = 'TEXTURE' + + +# Material properties class MaterialFreestyleButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -825,8 +1243,11 @@ class MaterialFreestyleButtonsPanel: material = context.material with_freestyle = bpy.app.build_options.freestyle return ( - with_freestyle and material and scene and scene.render.use_freestyle and - (context.engine in cls.COMPAT_ENGINES) + with_freestyle + and material + and scene + and scene.render.use_freestyle + and (context.engine in cls.COMPAT_ENGINES) ) @@ -837,12 +1258,14 @@ class MATERIAL_PT_freestyle_line(MaterialFreestyleButtonsPanel, Panel): def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False mat = context.material - row = layout.row() - row.prop(mat, "line_color", text="") - row.prop(mat, "line_priority", text="Priority") + col = layout.column() + col.prop(mat, "line_color") + col.prop(mat, "line_priority", text="Priority") classes = ( @@ -850,12 +1273,30 @@ classes = ( VIEWLAYER_UL_linesets, RENDER_MT_lineset_context_menu, VIEWLAYER_PT_freestyle, + VIEWLAYER_PT_freestyle_edge_detection, + VIEWLAYER_PT_freestyle_style_modules, VIEWLAYER_PT_freestyle_lineset, - VIEWLAYER_PT_freestyle_linestyle, + VIEWLAYER_PT_freestyle_lineset_visibilty, + VIEWLAYER_PT_freestyle_lineset_edgetype, + VIEWLAYER_PT_freestyle_lineset_facemarks, + VIEWLAYER_PT_freestyle_lineset_collection, + VIEWLAYER_PT_freestyle_linestyle_strokes, + VIEWLAYER_PT_freestyle_linestyle_strokes_chaining, + VIEWLAYER_PT_freestyle_linestyle_strokes_splitting, + VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern, + VIEWLAYER_PT_freestyle_linestyle_strokes_sorting, + VIEWLAYER_PT_freestyle_linestyle_strokes_selection, + VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline, + VIEWLAYER_PT_freestyle_linestyle_color, + VIEWLAYER_PT_freestyle_linestyle_alpha, + VIEWLAYER_PT_freestyle_linestyle_thickness, + VIEWLAYER_PT_freestyle_linestyle_geometry, + VIEWLAYER_PT_freestyle_linestyle_texture, MATERIAL_PT_freestyle_line, ) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class + for cls in classes: register_class(cls) diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index f87f5351d6d..f01e75dbab8 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -85,9 +85,6 @@ class GreasePencilSculptOptionsPanel: layout.prop(gp_settings, "use_edit_strength", text="Affect Strength") layout.prop(gp_settings, "use_edit_thickness", text="Affect Thickness") - if tool == 'SMOOTH': - layout.prop(gp_settings, "use_edit_pressure") - layout.prop(gp_settings, "use_edit_uv", text="Affect UV") diff --git a/release/scripts/startup/bl_ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py index 52af4fafd09..81a641a20cf 100644 --- a/release/scripts/startup/bl_ui/properties_object.py +++ b/release/scripts/startup/bl_ui/properties_object.py @@ -267,7 +267,8 @@ class OBJECT_PT_instancing(ObjectButtonsPanel, Panel): @classmethod def poll(cls, context): ob = context.object - return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD'}) + # FONT objects need (vertex) instancing for the 'Object Font' feature + return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD', 'FONT'}) def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/bl_ui/properties_output.py b/release/scripts/startup/bl_ui/properties_output.py index 0c1a26ceec1..d96a53f6ab8 100644 --- a/release/scripts/startup/bl_ui/properties_output.py +++ b/release/scripts/startup/bl_ui/properties_output.py @@ -25,8 +25,8 @@ from bl_ui.utils import PresetPanel from bpy.app.translations import pgettext_tip as tip_ -class RENDER_PT_presets(PresetPanel, Panel): - bl_label = "Render Presets" +class RENDER_PT_format_presets(PresetPanel, Panel): + bl_label = "Format Presets" preset_subdir = "render" preset_operator = "script.execute_preset" preset_add_operator = "render.preset_add" @@ -56,21 +56,21 @@ class RenderOutputButtonsPanel: return (context.engine in cls.COMPAT_ENGINES) -class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): - bl_label = "Dimensions" +class RENDER_PT_format(RenderOutputButtonsPanel, Panel): + bl_label = "Format" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} _frame_rate_args_prev = None _preset_class = None def draw_header_preset(self, _context): - RENDER_PT_presets.draw_panel_header(self.layout) + RENDER_PT_format_presets.draw_panel_header(self.layout) @staticmethod def _draw_framerate_label(*args): # avoids re-creating text string each draw - if RENDER_PT_dimensions._frame_rate_args_prev == args: - return RENDER_PT_dimensions._frame_rate_ret + if RENDER_PT_format._frame_rate_args_prev == args: + return RENDER_PT_format._frame_rate_ret fps, fps_base, preset_label = args @@ -89,17 +89,17 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): fps_label_text = tip_("%.4g fps") % fps_rate show_framerate = (preset_label == "Custom") - RENDER_PT_dimensions._frame_rate_args_prev = args - RENDER_PT_dimensions._frame_rate_ret = args = (fps_label_text, show_framerate) + RENDER_PT_format._frame_rate_args_prev = args + RENDER_PT_format._frame_rate_ret = args = (fps_label_text, show_framerate) return args @staticmethod def draw_framerate(layout, rd): - if RENDER_PT_dimensions._preset_class is None: - RENDER_PT_dimensions._preset_class = bpy.types.RENDER_MT_framerate_presets + if RENDER_PT_format._preset_class is None: + RENDER_PT_format._preset_class = bpy.types.RENDER_MT_framerate_presets - args = rd.fps, rd.fps_base, RENDER_PT_dimensions._preset_class.bl_label - fps_label_text, show_framerate = RENDER_PT_dimensions._draw_framerate_label(*args) + args = rd.fps, rd.fps_base, RENDER_PT_format._preset_class.bl_label + fps_label_text, show_framerate = RENDER_PT_format._draw_framerate_label(*args) layout.menu("RENDER_MT_framerate_presets", text=fps_label_text) @@ -113,8 +113,7 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): layout.use_property_split = True layout.use_property_decorate = False # No animation. - scene = context.scene - rd = scene.render + rd = context.scene.render col = layout.column(align=True) col.prop(rd, "resolution_x", text="Resolution X") @@ -131,18 +130,30 @@ class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel): sub.active = rd.use_border sub.prop(rd, "use_crop_to_border") + col = layout.column(heading="Frame Rate") + self.draw_framerate(col, rd) + + +class RENDER_PT_frame_range(RenderOutputButtonsPanel, Panel): + bl_label = "Frame Range" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + col = layout.column(align=True) col.prop(scene, "frame_start", text="Frame Start") col.prop(scene, "frame_end", text="End") col.prop(scene, "frame_step", text="Step") - col = layout.column(heading="Frame Rate") - self.draw_framerate(col, rd) - -class RENDER_PT_frame_remapping(RenderOutputButtonsPanel, Panel): - bl_label = "Time Remapping" - bl_parent_id = "RENDER_PT_dimensions" +class RENDER_PT_time_stretching(RenderOutputButtonsPanel, Panel): + bl_label = "Time Stretching" + bl_parent_id = "RENDER_PT_frame_range" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} @@ -481,11 +492,12 @@ class RENDER_PT_stereoscopy(RenderOutputButtonsPanel, Panel): classes = ( - RENDER_PT_presets, + RENDER_PT_format_presets, RENDER_PT_ffmpeg_presets, RENDER_MT_framerate_presets, - RENDER_PT_dimensions, - RENDER_PT_frame_remapping, + RENDER_PT_format, + RENDER_PT_frame_range, + RENDER_PT_time_stretching, RENDER_PT_stereoscopy, RENDER_PT_output, RENDER_PT_output_views, diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index ca018216a5a..8ba82a7d407 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -267,7 +267,7 @@ class FILEBROWSER_PT_bookmarks_system(Panel): @classmethod def poll(cls, context): return ( - not context.preferences.filepaths.hide_system_bookmarks and + context.preferences.filepaths.show_system_bookmarks and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) ) @@ -345,7 +345,7 @@ class FILEBROWSER_PT_bookmarks_recents(Panel): @classmethod def poll(cls, context): return ( - not context.preferences.filepaths.hide_recent_locations and + context.preferences.filepaths.show_recent_locations and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) ) diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index dcb0ab2e9e5..3ee668888f3 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -1453,7 +1453,7 @@ class IMAGE_PT_udim_grid(Panel): def poll(cls, context): sima = context.space_data - return sima.show_uvedit and sima.image is None + return sima.show_uvedit def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 30115618f3d..258797c18da 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -191,16 +191,6 @@ class SEQUENCER_PT_overlay(Panel): pass -class SEQUENCER_PT_overlay(Panel): - bl_space_type = 'SEQUENCE_EDITOR' - bl_region_type = 'HEADER' - bl_label = "Overlays" - bl_ui_units_x = 7 - - def draw(self, _context): - pass - - class SEQUENCER_PT_preview_overlay(Panel): bl_space_type = 'SEQUENCE_EDITOR' bl_region_type = 'HEADER' @@ -1659,7 +1649,7 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel): def draw(self, context): layout = self.layout - layout.use_property_split = True + layout.use_property_split = False st = context.space_data strip = context.active_sequence_strip @@ -1667,20 +1657,39 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel): layout.active = not strip.mute - col = layout.column() - - col.prop(strip, "volume", text="Volume") - col.prop(strip, "pitch") - - col = layout.column() - col.prop(strip, "pan") - col.enabled = sound is not None and sound.use_mono - if sound is not None: col = layout.column() + + split = col.split(factor=0.4) + split.label(text="") + split.prop(sound, "use_mono") if st.waveform_display_type == 'DEFAULT_WAVEFORMS': - col.prop(strip, "show_waveform") - col.prop(sound, "use_mono") + split = col.split(factor=0.4) + split.label(text="") + split.prop(strip, "show_waveform") + + col = layout.column() + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Volume") + split.prop(strip, "volume", text="") + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Pitch") + split.prop(strip, "pitch", text="") + + split = col.split(factor=0.4) + split.alignment = 'RIGHT' + split.label(text="Pan") + audio_channels = context.scene.render.ffmpeg.audio_channels + pan_text = "" + if audio_channels != 'MONO' and audio_channels != 'STEREO': + pan_text = "%.2f°" % (strip.pan * 90) + split.prop(strip, "pan", text=pan_text) + split.enabled = sound.use_mono and audio_channels != 'MONO' + class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel): diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index bacca6dedc2..b409e9079be 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -209,9 +209,9 @@ class TOPBAR_MT_editor_menus(Menu): # Allow calling this menu directly (this might not be a header area). if getattr(context.area, "show_menus", False): - layout.menu("TOPBAR_MT_app", text="", icon='BLENDER') + layout.menu("TOPBAR_MT_blender", text="", icon='BLENDER') else: - layout.menu("TOPBAR_MT_app", text="Blender") + layout.menu("TOPBAR_MT_blender", text="Blender") layout.menu("TOPBAR_MT_file") layout.menu("TOPBAR_MT_edit") @@ -222,7 +222,7 @@ class TOPBAR_MT_editor_menus(Menu): layout.menu("TOPBAR_MT_help") -class TOPBAR_MT_app(Menu): +class TOPBAR_MT_blender(Menu): bl_label = "Blender" def draw(self, _context): @@ -238,7 +238,7 @@ class TOPBAR_MT_app(Menu): layout.separator() - layout.menu("TOPBAR_MT_app_system") + layout.menu("TOPBAR_MT_blender_system") class TOPBAR_MT_file_cleanup(Menu): @@ -430,7 +430,7 @@ class TOPBAR_MT_file_defaults(Menu): # Include technical operators here which would otherwise have no way for users to access. -class TOPBAR_MT_app_system(Menu): +class TOPBAR_MT_blender_system(Menu): bl_label = "System" def draw(self, _context): @@ -854,8 +854,8 @@ classes = ( TOPBAR_MT_file_context_menu, TOPBAR_MT_workspace_menu, TOPBAR_MT_editor_menus, - TOPBAR_MT_app, - TOPBAR_MT_app_system, + TOPBAR_MT_blender, + TOPBAR_MT_blender_system, TOPBAR_MT_file, TOPBAR_MT_file_new, TOPBAR_MT_file_recover, diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 1130c3b80e6..be16179fdff 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -267,7 +267,6 @@ class USERPREF_PT_interface_editors(InterfacePanel, CenterAlignMixIn, Panel): col = layout.column() col.prop(system, "use_region_overlap") - col.prop(view, "show_layout_ui", text="Corner Splitting") col.prop(view, "show_navigate_ui") col.prop(view, "color_picker_type") col.row().prop(view, "header_align") @@ -1414,7 +1413,7 @@ class USERPREF_PT_saveload_blend(SaveLoadPanel, CenterAlignMixIn, Panel): col = layout.column(heading="Save") col.prop(view, "use_save_prompt") - col.prop(paths, "use_save_preview_images") + col.prop(paths, "file_preview_type") col = layout.column(heading="Default To") col.prop(paths, "use_relative_paths") @@ -1455,13 +1454,11 @@ class USERPREF_PT_saveload_file_browser(SaveLoadPanel, CenterAlignMixIn, Panel): prefs = context.preferences paths = prefs.filepaths - col = layout.column() + col = layout.column(heading="Defaults") col.prop(paths, "use_filter_files") - - col = layout.column(heading="Hide") - col.prop(paths, "show_hidden_files_datablocks", text="Dot File & Data-Blocks") - col.prop(paths, "hide_recent_locations", text="Recent Locations") - col.prop(paths, "hide_system_bookmarks", text="System Bookmarks") + col.prop(paths, "show_hidden_files_datablocks") + col.prop(paths, "show_recent_locations") + col.prop(paths, "show_system_bookmarks") # ----------------------------------------------------------------------------- @@ -2255,6 +2252,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_sculpt_tools_tilt"}, "T82877"), ({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")), ({"property": "use_override_templates"}, ("T73318", "Milestone 4")), + ({"property": "use_geometry_nodes_fields"}, "T91274"), ), ) diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index d78023b4e0e..569c5291576 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -180,6 +180,13 @@ def object_eevee_cycles_shader_nodes_poll(context): eevee_cycles_shader_nodes_poll(context)) +def geometry_nodes_fields_poll(context): + return context.preferences.experimental.use_geometry_nodes_fields + +def geometry_nodes_fields_legacy_poll(context): + return not context.preferences.experimental.use_geometry_nodes_fields + + # All standard node categories currently used in nodes. shader_node_categories = [ @@ -333,6 +340,7 @@ compositor_node_categories = [ NodeItem("CompositorNodeGamma"), NodeItem("CompositorNodeExposure"), NodeItem("CompositorNodeColorCorrection"), + NodeItem("CompositorNodePosterize"), NodeItem("CompositorNodeTonemap"), NodeItem("CompositorNodeZcombine"), ]), @@ -475,24 +483,26 @@ texture_node_categories = [ geometry_node_categories = [ # Geometry Nodes GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[ - NodeItem("GeometryNodeAttributeRandomize"), - NodeItem("GeometryNodeAttributeMath"), - NodeItem("GeometryNodeAttributeClamp"), - NodeItem("GeometryNodeAttributeCompare"), - NodeItem("GeometryNodeAttributeConvert"), - NodeItem("GeometryNodeAttributeCurveMap"), - NodeItem("GeometryNodeAttributeFill"), - NodeItem("GeometryNodeAttributeMix"), - NodeItem("GeometryNodeAttributeProximity"), - NodeItem("GeometryNodeAttributeColorRamp"), - NodeItem("GeometryNodeAttributeVectorMath"), - NodeItem("GeometryNodeAttributeVectorRotate"), - NodeItem("GeometryNodeAttributeSampleTexture"), - NodeItem("GeometryNodeAttributeCombineXYZ"), - NodeItem("GeometryNodeAttributeSeparateXYZ"), + NodeItem("GeometryNodeLegacyAttributeRandomize", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMath", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeClamp", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCompare", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeConvert", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCurveMap", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeFill", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMix", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeProximity", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeColorRamp", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorMath", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorRotate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSampleTexture", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCombineXYZ", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSeparateXYZ", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMapRange", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeTransfer", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeAttributeRemove"), - NodeItem("GeometryNodeAttributeMapRange"), - NodeItem("GeometryNodeAttributeTransfer"), + NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_COLOR", "Color", items=[ NodeItem("ShaderNodeMixRGB"), @@ -502,19 +512,20 @@ geometry_node_categories = [ NodeItem("ShaderNodeCombineRGB"), ]), GeometryNodeCategory("GEO_CURVE", "Curve", items=[ - NodeItem("GeometryNodeCurveSubdivide"), + NodeItem("GeometryNodeLegacyCurveSubdivide", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveReverse", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSplineType", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSetHandles", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSelectHandles", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyMeshToCurve", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeCurveToMesh"), NodeItem("GeometryNodeCurveResample"), - NodeItem("GeometryNodeMeshToCurve"), NodeItem("GeometryNodeCurveToPoints"), NodeItem("GeometryNodeCurveEndpoints"), NodeItem("GeometryNodeCurveFill"), NodeItem("GeometryNodeCurveTrim"), NodeItem("GeometryNodeCurveLength"), - NodeItem("GeometryNodeCurveReverse"), - NodeItem("GeometryNodeCurveSplineType"), - NodeItem("GeometryNodeCurveSetHandles"), - NodeItem("GeometryNodeCurveSelectHandles"), ]), GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[ NodeItem("GeometryNodeCurvePrimitiveLine"), @@ -526,13 +537,15 @@ geometry_node_categories = [ NodeItem("GeometryNodeCurvePrimitiveBezierSegment"), ]), GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[ + NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeBoundBox"), NodeItem("GeometryNodeConvexHull"), - NodeItem("GeometryNodeDeleteGeometry"), NodeItem("GeometryNodeTransform"), NodeItem("GeometryNodeJoinGeometry"), NodeItem("GeometryNodeSeparateComponents"), - NodeItem("GeometryNodeRaycast"), + NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_INPUT", "Input", items=[ NodeItem("GeometryNodeObjectInfo"), @@ -543,10 +556,16 @@ geometry_node_categories = [ NodeItem("FunctionNodeInputVector"), NodeItem("GeometryNodeInputMaterial"), NodeItem("GeometryNodeIsViewport"), + NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll), ]), GeometryNodeCategory("GEO_MATERIAL", "Material", items=[ - NodeItem("GeometryNodeMaterialAssign"), - NodeItem("GeometryNodeSelectByMaterial"), + NodeItem("GeometryNodeLegacyMaterialAssign", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacySelectByMaterial", poll=geometry_nodes_fields_legacy_poll), + + NodeItem("GeometryNodeMaterialAssign", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeMaterialSelection", poll=geometry_nodes_fields_poll), NodeItem("GeometryNodeMaterialReplace"), ]), GeometryNodeCategory("GEO_MESH", "Mesh", items=[ @@ -566,15 +585,14 @@ geometry_node_categories = [ NodeItem("GeometryNodeMeshLine"), NodeItem("GeometryNodeMeshUVSphere"), ]), - GeometryNodeCategory("GEO_POINT", "Point", items=[ - NodeItem("GeometryNodePointDistribute"), - NodeItem("GeometryNodePointInstance"), - NodeItem("GeometryNodePointSeparate"), - NodeItem("GeometryNodePointScale"), - NodeItem("GeometryNodePointTranslate"), - NodeItem("GeometryNodeRotatePoints"), - NodeItem("GeometryNodeAlignRotationToVector"), + NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointSeparate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointScale", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointTranslate", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_fields_legacy_poll), ]), GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[ NodeItem("ShaderNodeMapRange"), @@ -596,7 +614,8 @@ geometry_node_categories = [ NodeItem("GeometryNodeViewer"), ]), GeometryNodeCategory("GEO_VOLUME", "Volume", items=[ - NodeItem("GeometryNodePointsToVolume"), + NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeVolumeToMesh"), ]), GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items), diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.h b/source/blender/blenkernel/BKE_anonymous_attribute.h new file mode 100644 index 00000000000..ebdb0b05160 --- /dev/null +++ b/source/blender/blenkernel/BKE_anonymous_attribute.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup bke + * + * An #AnonymousAttributeID is used to identify attributes that are not explicitly named. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct AnonymousAttributeID AnonymousAttributeID; + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name); +AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name); +bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id); +void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id); +const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id); +const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.hh b/source/blender/blenkernel/BKE_anonymous_attribute.hh new file mode 100644 index 00000000000..56e6335c7c0 --- /dev/null +++ b/source/blender/blenkernel/BKE_anonymous_attribute.hh @@ -0,0 +1,169 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <atomic> +#include <string> + +#include "BLI_hash.hh" +#include "BLI_string_ref.hh" + +#include "BKE_anonymous_attribute.h" + +namespace blender::bke { + +/** + * Wrapper for #AnonymousAttributeID with RAII semantics. + * This class should typically not be used directly. Instead use #StrongAnonymousAttributeID or + * #WeakAnonymousAttributeID. + */ +template<bool IsStrongReference> class OwnedAnonymousAttributeID { + private: + const AnonymousAttributeID *data_ = nullptr; + + template<bool OtherIsStrongReference> friend class OwnedAnonymousAttributeID; + + public: + OwnedAnonymousAttributeID() = default; + + /** Create a new anonymous attribute id. */ + explicit OwnedAnonymousAttributeID(StringRefNull debug_name) + { + if constexpr (IsStrongReference) { + data_ = BKE_anonymous_attribute_id_new_strong(debug_name.c_str()); + } + else { + data_ = BKE_anonymous_attribute_id_new_weak(debug_name.c_str()); + } + } + + /** + * This transfers ownership, so no incref is necessary. + * The caller has to make sure that it owned the anonymous id. + */ + explicit OwnedAnonymousAttributeID(const AnonymousAttributeID *anonymous_id) + : data_(anonymous_id) + { + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID<OtherIsStrong> &other) + { + data_ = other.data_; + this->incref(); + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID(OwnedAnonymousAttributeID<OtherIsStrong> &&other) + { + data_ = other.data_; + this->incref(); + other.decref(); + other.data_ = nullptr; + } + + ~OwnedAnonymousAttributeID() + { + this->decref(); + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID<OtherIsStrong> &other) + { + if (this == &other) { + return *this; + } + this->~OwnedAnonymousAttributeID(); + new (this) OwnedAnonymousAttributeID(other); + return *this; + } + + template<bool OtherIsStrong> + OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID<OtherIsStrong> &&other) + { + if (this == &other) { + return *this; + } + this->~OwnedAnonymousAttributeID(); + new (this) OwnedAnonymousAttributeID(std::move(other)); + return *this; + } + + operator bool() const + { + return data_ != nullptr; + } + + StringRefNull debug_name() const + { + BLI_assert(data_ != nullptr); + return BKE_anonymous_attribute_id_debug_name(data_); + } + + bool has_strong_references() const + { + BLI_assert(data_ != nullptr); + return BKE_anonymous_attribute_id_has_strong_references(data_); + } + + /** Extract the ownership of the currently wrapped anonymous id. */ + const AnonymousAttributeID *extract() + { + const AnonymousAttributeID *extracted_data = data_; + /* Don't decref because the caller becomes the new owner. */ + data_ = nullptr; + return extracted_data; + } + + /** Get the wrapped anonymous id, without taking ownership. */ + const AnonymousAttributeID *get() const + { + return data_; + } + + private: + void incref() + { + if (data_ == nullptr) { + return; + } + if constexpr (IsStrongReference) { + BKE_anonymous_attribute_id_increment_strong(data_); + } + else { + BKE_anonymous_attribute_id_increment_weak(data_); + } + } + + void decref() + { + if (data_ == nullptr) { + return; + } + if constexpr (IsStrongReference) { + BKE_anonymous_attribute_id_decrement_strong(data_); + } + else { + BKE_anonymous_attribute_id_decrement_weak(data_); + } + } +}; + +using StrongAnonymousAttributeID = OwnedAnonymousAttributeID<true>; +using WeakAnonymousAttributeID = OwnedAnonymousAttributeID<false>; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index 5fda30224c6..7476474258b 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -66,6 +66,11 @@ bool BKE_id_attribute_remove(struct ID *id, struct CustomDataLayer *layer, struct ReportList *reports); +struct CustomDataLayer *BKE_id_attribute_find(const struct ID *id, + const char *name, + const int type, + const AttributeDomain domain); + AttributeDomain BKE_id_attribute_domain(struct ID *id, struct CustomDataLayer *layer); int BKE_id_attribute_data_length(struct ID *id, struct CustomDataLayer *layer); bool BKE_id_attribute_required(struct ID *id, struct CustomDataLayer *layer); diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index c3f7dbd4bd9..cf54e7efa0d 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -22,6 +22,7 @@ #include "FN_generic_span.hh" #include "FN_generic_virtual_array.hh" +#include "BKE_anonymous_attribute.hh" #include "BKE_attribute.h" #include "BLI_color.hh" @@ -29,6 +30,81 @@ #include "BLI_float3.hh" #include "BLI_function_ref.hh" +namespace blender::bke { + +/** + * Identifies an attribute that is either named or anonymous. + * It does not own the identifier, so it is just a reference. + */ +class AttributeIDRef { + private: + StringRef name_; + const AnonymousAttributeID *anonymous_id_ = nullptr; + + public: + AttributeIDRef() = default; + + AttributeIDRef(StringRef name) : name_(name) + { + } + + AttributeIDRef(StringRefNull name) : name_(name) + { + } + + AttributeIDRef(const char *name) : name_(name) + { + } + + AttributeIDRef(const std::string &name) : name_(name) + { + } + + /* The anonymous id is only borrowed, the caller has to keep a reference to it. */ + AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id) + { + } + + operator bool() const + { + return this->is_named() || this->is_anonymous(); + } + + friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) + { + return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; + } + + uint64_t hash() const + { + return get_default_hash_2(name_, anonymous_id_); + } + + bool is_named() const + { + return !name_.is_empty(); + } + + bool is_anonymous() const + { + return anonymous_id_ != nullptr; + } + + StringRef name() const + { + BLI_assert(this->is_named()); + return name_; + } + + const AnonymousAttributeID &anonymous_id() const + { + BLI_assert(this->is_anonymous()); + return *anonymous_id_; + } +}; + +} // namespace blender::bke + /** * Contains information about an attribute in a geometry component. * More information can be added in the future. E.g. whether the attribute is builtin and how it is @@ -104,8 +180,8 @@ struct AttributeInitMove : public AttributeInit { }; /* Returns false when the iteration should be stopped. */ -using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name, - const AttributeMetaData &meta_data)>; +using AttributeForeachCallback = blender::FunctionRef<bool( + const blender::bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data)>; namespace blender::bke { @@ -171,7 +247,7 @@ class OutputAttribute { GVMutableArrayPtr varray_; AttributeDomain domain_; SaveFn save_; - std::optional<fn::GVMutableArray_GSpan> optional_span_varray_; + std::unique_ptr<fn::GVMutableArray_GSpan> optional_span_varray_; bool ignore_old_values_ = false; bool save_has_been_called_ = false; @@ -230,9 +306,10 @@ class OutputAttribute { fn::GMutableSpan as_span() { - if (!optional_span_varray_.has_value()) { + if (!optional_span_varray_) { const bool materialize_old_values = !ignore_old_values_; - optional_span_varray_.emplace(*varray_, materialize_old_values); + optional_span_varray_ = std::make_unique<fn::GVMutableArray_GSpan>(*varray_, + materialize_old_values); } fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_; return span_varray; @@ -333,26 +410,28 @@ class CustomDataAttributes { void reallocate(const int size); - std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const; + std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const; - blender::fn::GVArrayPtr get_for_read(const StringRef name, + blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id, const CustomDataType data_type, const void *default_value) const; template<typename T> - blender::fn::GVArray_Typed<T> get_for_read(const blender::StringRef name, + blender::fn::GVArray_Typed<T> get_for_read(const AttributeIDRef &attribute_id, const T &default_value) const { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - GVArrayPtr varray = this->get_for_read(name, type, &default_value); + GVArrayPtr varray = this->get_for_read(attribute_id, type, &default_value); return blender::fn::GVArray_Typed<T>(std::move(varray)); } - std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name); - bool create(const blender::StringRef name, const CustomDataType data_type); - bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer); - bool remove(const blender::StringRef name); + std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id); + bool create(const AttributeIDRef &attribute_id, const CustomDataType data_type); + bool create_by_move(const AttributeIDRef &attribute_id, + const CustomDataType data_type, + void *buffer); + bool remove(const AttributeIDRef &attribute_id); bool foreach_attribute(const AttributeForeachCallback callback, const AttributeDomain domain) const; diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index d36991c4444..f7e1b7f0d81 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -33,6 +33,7 @@ extern "C" { #endif +struct AnonymousAttributeID; struct BMesh; struct BlendDataReader; struct BlendWriter; @@ -199,6 +200,12 @@ void *CustomData_add_layer_named(struct CustomData *data, void *layer, int totelem, const char *name); +void *CustomData_add_layer_anonymous(struct CustomData *data, + int type, + eCDAllocType alloctype, + void *layer, + int totelem, + const struct AnonymousAttributeID *anonymous_id); /* frees the active or first data layer with the give type. * returns 1 on success, 0 if no layer with the given type is found @@ -237,6 +244,11 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data, const int type, const char *name, const int totelem); +void *CustomData_duplicate_referenced_layer_anonymous( + CustomData *data, + const int type, + const struct AnonymousAttributeID *anonymous_id, + const int totelem); bool CustomData_is_referenced_layer(struct CustomData *data, int type); /* Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. */ diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h index 0f37ba6c4ce..db5663fcc94 100644 --- a/source/blender/blenkernel/BKE_displist.h +++ b/source/blender/blenkernel/BKE_displist.h @@ -82,7 +82,6 @@ DispList *BKE_displist_find(struct ListBase *lb, int type); void BKE_displist_normals_add(struct ListBase *lb); void BKE_displist_count(const struct ListBase *lb, int *totvert, int *totface, int *tottri); void BKE_displist_free(struct ListBase *lb); -bool BKE_displist_has_faces(const struct ListBase *lb); void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph, const struct Scene *scene, @@ -94,12 +93,8 @@ void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph, struct ListBase *dispbase, struct Mesh **r_final); void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); -void BKE_displist_make_mball_forRender(struct Depsgraph *depsgraph, - struct Scene *scene, - struct Object *ob, - struct ListBase *dispbase); -bool BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, +void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *ob, struct ListBase *source_nurb, diff --git a/source/blender/blenkernel/BKE_duplilist.h b/source/blender/blenkernel/BKE_duplilist.h index c142d5338d1..989b68f4ccb 100644 --- a/source/blender/blenkernel/BKE_duplilist.h +++ b/source/blender/blenkernel/BKE_duplilist.h @@ -31,6 +31,7 @@ struct ListBase; struct Object; struct ParticleSystem; struct Scene; +struct ID; /* ---------------------------------------------------- */ /* Dupli-Geometry */ @@ -42,7 +43,10 @@ void free_object_duplilist(struct ListBase *lb); typedef struct DupliObject { struct DupliObject *next, *prev; + /* Object whose geometry is instanced. */ struct Object *ob; + /* Data owned by the object above that is instanced. This might not be the same as `ob->data`. */ + struct ID *ob_data; float mat[4][4]; float orco[3], uv[2]; diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h index ffd8ac42c63..2c24b1a5487 100644 --- a/source/blender/blenkernel/BKE_editmesh.h +++ b/source/blender/blenkernel/BKE_editmesh.h @@ -79,6 +79,11 @@ typedef struct BMEditMesh { int mirror_cdlayer; /** + * Enable for evaluated copies, causes the edit-mesh to free the memory, not it's contents. + */ + char is_shallow_copy; + + /** * ID data is older than edit-mode data. * Set #Main.is_memfile_undo_flush_needed when enabling. */ diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index 5f6a9ec7b91..17cdb9d6a42 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -41,7 +41,7 @@ typedef enum GeometryComponentType { void BKE_geometry_set_free(struct GeometrySet *geometry_set); -bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set); +bool BKE_object_has_geometry_set_instances(const struct Object *ob); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 42e9ce82278..bf38294257a 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -31,9 +31,12 @@ #include "BLI_user_counter.hh" #include "BLI_vector_set.hh" +#include "BKE_anonymous_attribute.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.h" +#include "FN_field.hh" + struct Collection; struct Curve; struct CurveEval; @@ -88,11 +91,11 @@ class GeometryComponent { GeometryComponentType type() const; /* Return true when any attribute with this name exists, including built in attributes. */ - bool attribute_exists(const blender::StringRef attribute_name) const; + bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const; /* Return the data type and domain of an attribute with the given name if it exists. */ std::optional<AttributeMetaData> attribute_get_meta_data( - const blender::StringRef attribute_name) const; + const blender::bke::AttributeIDRef &attribute_id) const; /* Returns true when the geometry component supports this attribute domain. */ bool attribute_domain_supported(const AttributeDomain domain) const; @@ -100,16 +103,17 @@ class GeometryComponent { virtual int attribute_domain_size(const AttributeDomain domain) const; bool attribute_is_builtin(const blender::StringRef attribute_name) const; + bool attribute_is_builtin(const blender::bke::AttributeIDRef &attribute_id) const; /* Get read-only access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::StringRef attribute_name) const; + const blender::bke::AttributeIDRef &attribute_id) const; /* Get read and write access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ blender::bke::WriteAttributeLookup attribute_try_get_for_write( - const blender::StringRef attribute_name); + const blender::bke::AttributeIDRef &attribute_id); /* Get a read-only attribute for the domain based on the given attribute. This can be used to * interpolate from one domain to another. @@ -120,10 +124,10 @@ class GeometryComponent { const AttributeDomain to_domain) const; /* Returns true when the attribute has been deleted. */ - bool attribute_try_delete(const blender::StringRef attribute_name); + bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id); /* Returns true when the attribute has been created. */ - bool attribute_try_create(const blender::StringRef attribute_name, + bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer); @@ -133,7 +137,7 @@ class GeometryComponent { bool attribute_try_create_builtin(const blender::StringRef attribute_name, const AttributeInit &initializer); - blender::Set<std::string> attribute_names() const; + blender::Set<blender::bke::AttributeIDRef> attribute_ids() const; bool attribute_foreach(const AttributeForeachCallback callback) const; virtual bool is_empty() const; @@ -142,7 +146,7 @@ class GeometryComponent { * Returns null when the attribute does not exist or cannot be converted to the requested domain * and data type. */ std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) const; @@ -150,18 +154,18 @@ class GeometryComponent { * left unchanged. Returns null when the attribute does not exist or cannot be adapted to the * requested domain. */ std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( - const blender::StringRef attribute_name, const AttributeDomain domain) const; + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const; /* Get a virtual array to read data of an attribute with the given data type. The domain is * left unchanged. Returns null when the attribute does not exist or cannot be converted to the * requested data type. */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::StringRef attribute_name, const CustomDataType data_type) const; + const blender::bke::AttributeIDRef &attribute_id, const CustomDataType data_type) const; /* Get a virtual array to read the data of an attribute. If that is not possible, the returned * virtual array will contain a default value. This never returns null. */ std::unique_ptr<blender::fn::GVArray> attribute_get_for_read( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value = nullptr) const; @@ -169,14 +173,15 @@ class GeometryComponent { /* Should be used instead of the method above when the requested data type is known at compile * time for better type safety. */ template<typename T> - blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name, - const AttributeDomain domain, - const T &default_value) const + blender::fn::GVArray_Typed<T> attribute_get_for_read( + const blender::bke::AttributeIDRef &attribute_id, + const AttributeDomain domain, + const T &default_value) const { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); std::unique_ptr varray = this->attribute_get_for_read( - attribute_name, domain, type, &default_value); + attribute_id, domain, type, &default_value); return blender::fn::GVArray_Typed<T>(std::move(varray)); } @@ -191,7 +196,7 @@ class GeometryComponent { * is created that will overwrite the existing attribute in the end. */ blender::bke::OutputAttribute attribute_try_get_for_output( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value = nullptr); @@ -200,28 +205,30 @@ class GeometryComponent { * attributes are not read, i.e. the attribute is used only for output. Since values are not read * from this attribute, no default value is necessary. */ blender::bke::OutputAttribute attribute_try_get_for_output_only( - const blender::StringRef attribute_name, + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type); /* Statically typed method corresponding to the equally named generic one. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output( - const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value) + const blender::bke::AttributeIDRef &attribute_id, + const AttributeDomain domain, + const T default_value) { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value); + return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value); } /* Statically typed method corresponding to the equally named generic one. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only( - const blender::StringRef attribute_name, const AttributeDomain domain) + const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output_only(attribute_name, domain, data_type); + return this->attribute_try_get_for_output_only(attribute_id, domain, data_type); } private: @@ -283,6 +290,7 @@ struct GeometrySet { void clear(); + bool owns_direct_data() const; void ensure_owns_direct_data(); /* Utility methods for creation. */ @@ -447,12 +455,14 @@ class InstanceReference { None, Object, Collection, + GeometrySet, }; private: Type type_ = Type::None; /** Depending on the type this is either null, an Object or Collection pointer. */ void *data_ = nullptr; + std::unique_ptr<GeometrySet> geometry_set_; public: InstanceReference() = default; @@ -465,6 +475,19 @@ class InstanceReference { { } + InstanceReference(GeometrySet geometry_set) + : type_(Type::GeometrySet), + geometry_set_(std::make_unique<GeometrySet>(std::move(geometry_set))) + { + } + + InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_) + { + if (src.type_ == Type::GeometrySet) { + geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_); + } + } + Type type() const { return type_; @@ -482,14 +505,37 @@ class InstanceReference { return *(Collection *)data_; } + const GeometrySet &geometry_set() const + { + BLI_assert(type_ == Type::GeometrySet); + return *geometry_set_; + } + + bool owns_direct_data() const + { + if (type_ != Type::GeometrySet) { + /* The object and collection instances are not direct data. */ + return true; + } + return geometry_set_->owns_direct_data(); + } + + void ensure_owns_direct_data() + { + if (type_ != Type::GeometrySet) { + return; + } + geometry_set_->ensure_owns_direct_data(); + } + uint64_t hash() const { - return blender::get_default_hash(data_); + return blender::get_default_hash_2(data_, geometry_set_.get()); } friend bool operator==(const InstanceReference &a, const InstanceReference &b) { - return a.data_ == b.data_; + return a.data_ == b.data_ && a.geometry_set_.get() == b.geometry_set_.get(); } }; @@ -529,7 +575,7 @@ class InstancesComponent : public GeometryComponent { void reserve(int min_capacity); void resize(int capacity); - int add_reference(InstanceReference reference); + int add_reference(const InstanceReference &reference); void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); blender::Span<InstanceReference> references() const; @@ -577,3 +623,78 @@ class VolumeComponent : public GeometryComponent { static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME; }; + +namespace blender::bke { + +class GeometryComponentFieldContext : public fn::FieldContext { + private: + const GeometryComponent &component_; + const AttributeDomain domain_; + + public: + GeometryComponentFieldContext(const GeometryComponent &component, const AttributeDomain domain) + : component_(component), domain_(domain) + { + } + + const GeometryComponent &geometry_component() const + { + return component_; + } + + AttributeDomain domain() const + { + return domain_; + } +}; + +class AttributeFieldInput : public fn::FieldInput { + private: + std::string name_; + + public: + AttributeFieldInput(std::string name, const CPPType &type) + : fn::FieldInput(type, name), name_(std::move(name)) + { + } + + StringRefNull attribute_name() const + { + return name_; + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const override; + + std::string socket_inspection_name() const override; + + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + +class AnonymousAttributeFieldInput : public fn::FieldInput { + private: + /** + * A strong reference is required to make sure that the referenced attribute is not removed + * automatically. + */ + StrongAnonymousAttributeID anonymous_id_; + + public: + AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, const CPPType &type) + : fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id)) + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const override; + + std::string socket_inspection_name() const override; + + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index 25876296a47..44a0ee30c4c 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -59,9 +59,10 @@ struct AttributeKind { * will contain the highest complexity data type and the highest priority domain among every * attribute with the given name on all of the input components. */ -void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, - Span<GeometryComponentType> component_types, - const Set<std::string> &ignored_attributes, - Map<std::string, AttributeKind> &r_attributes); +void geometry_set_gather_instances_attribute_info( + Span<GeometryInstanceGroup> set_groups, + Span<GeometryComponentType> component_types, + const Set<std::string> &ignored_attributes, + Map<AttributeIDRef, AttributeKind> &r_attributes); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index 89713e9ad0a..7696b5c0189 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -124,7 +124,10 @@ enum { /** Don't overwrite these flags when reading a file. */ #define G_FLAG_ALL_RUNTIME \ (G_FLAG_SCRIPT_AUTOEXEC | G_FLAG_SCRIPT_OVERRIDE_PREF | G_FLAG_EVENT_SIMULATE | \ - G_FLAG_USERPREF_NO_SAVE_ON_EXIT) + G_FLAG_USERPREF_NO_SAVE_ON_EXIT | \ +\ + /* #BPY_python_reset is responsible for resetting these flags on file load. */ \ + G_FLAG_SCRIPT_AUTOEXEC_FAIL | G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET) /** Flags to read from blend file. */ #define G_FLAG_ALL_READFILE 0 diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index 29e3a74b1b2..d472fd6f02b 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -101,7 +101,7 @@ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd, struct bGPDstroke *gps, const float dist, const bool select); -bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf); +bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf); bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); @@ -151,7 +151,8 @@ void BKE_gpencil_stroke_set_random_color(struct bGPDstroke *gps); void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a, struct bGPDstroke *gps_b, const bool leave_gaps, - const bool fit_thickness); + const bool fit_thickness, + const bool smooth); void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd, struct bGPDlayer *gpl, struct bGPDframe *gpf, diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index b0939ec884d..7136a3fd7af 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -45,8 +45,10 @@ enum { IDTYPE_FLAGS_NO_COPY = 1 << 0, /** Indicates that the given IDType does not support linking/appending from a library file. */ IDTYPE_FLAGS_NO_LIBLINKING = 1 << 1, - /** Indicates that the given IDType does not support making a library-linked ID local. */ - IDTYPE_FLAGS_NO_MAKELOCAL = 1 << 2, + /** Indicates that the given IDType should not be directly linked from a library file, but may be + * appended. + * NOTE: Mutually exclusive with `IDTYPE_FLAGS_NO_LIBLINKING`. */ + IDTYPE_FLAGS_ONLY_APPEND = 1 << 2, /** Indicates that the given IDType does not have animation data. */ IDTYPE_FLAGS_NO_ANIMDATA = 1 << 3, }; @@ -283,9 +285,14 @@ const struct IDTypeInfo *BKE_idtype_get_info_from_id(const struct ID *id); const char *BKE_idtype_idcode_to_name(const short idcode); const char *BKE_idtype_idcode_to_name_plural(const short idcode); const char *BKE_idtype_idcode_to_translation_context(const short idcode); -bool BKE_idtype_idcode_is_linkable(const short idcode); + bool BKE_idtype_idcode_is_valid(const short idcode); +bool BKE_idtype_idcode_is_linkable(const short idcode); +bool BKE_idtype_idcode_is_only_appendable(const short idcode); +/* Macro currently, since any linkable IDtype should be localizable. */ +#define BKE_idtype_idcode_is_localizable BKE_idtype_idcode_is_linkable + short BKE_idtype_idcode_from_name(const char *idtype_name); uint64_t BKE_idtype_idcode_to_idfilter(const short idcode); diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index a50faedcc3c..36f57209e33 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -230,20 +230,25 @@ void id_us_plus(struct ID *id); void id_us_min(struct ID *id); void id_fake_user_set(struct ID *id); void id_fake_user_clear(struct ID *id); -void BKE_id_clear_newpoin(struct ID *id); +void BKE_id_newptr_and_tag_clear(struct ID *id); /** Flags to control make local code behavior. */ enum { /** Making that ID local is part of making local a whole library. */ LIB_ID_MAKELOCAL_FULL_LIBRARY = 1 << 0, + /** In case caller code already knows this ID should be made local without copying. */ + LIB_ID_MAKELOCAL_FORCE_LOCAL = 1 << 1, + /** In case caller code already knows this ID should be made local using copying. */ + LIB_ID_MAKELOCAL_FORCE_COPY = 1 << 2, + /* Special type-specific options. */ /** For Objects, do not clear the proxy pointers while making the data-block local. */ LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING = 1 << 16, }; void BKE_lib_id_make_local_generic(struct Main *bmain, struct ID *id, const int flags); -bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const bool test, const int flags); +bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, const int flags); bool id_single_user(struct bContext *C, struct ID *id, struct PointerRNA *ptr, diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h index c90a284c204..c70521f9593 100644 --- a/source/blender/blenkernel/BKE_lib_remap.h +++ b/source/blender/blenkernel/BKE_lib_remap.h @@ -112,6 +112,7 @@ void BKE_libblock_relink_ex(struct Main *bmain, const short remap_flags) ATTR_NONNULL(1, 2); void BKE_libblock_relink_to_newid(struct ID *id) ATTR_NONNULL(); +void BKE_libblock_relink_to_newid_new(struct Main *bmain, struct ID *id) ATTR_NONNULL(); typedef void (*BKE_library_free_notifier_reference_cb)(const void *); typedef void (*BKE_library_remap_editor_id_reference_cb)(struct ID *, struct ID *); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 5e0526ab262..a57281e4478 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -731,6 +731,8 @@ void nodeSetSocketAvailability(struct bNodeSocket *sock, bool is_available); int nodeSocketLinkLimit(const struct bNodeSocket *sock); +void nodeDeclarationEnsure(struct bNodeTree *ntree, struct bNode *node); + /* Node Clipboard */ void BKE_node_clipboard_init(const struct bNodeTree *ntree); void BKE_node_clipboard_clear(void); @@ -1255,6 +1257,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, #define CMP_NODE_DENOISE 324 #define CMP_NODE_EXPOSURE 325 #define CMP_NODE_CRYPTOMATTE 326 +#define CMP_NODE_POSTERIZE 327 /* channel toggles */ #define CMP_CHAN_RGB 1 @@ -1411,34 +1414,34 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_EDGE_SPLIT 1001 #define GEO_NODE_TRANSFORM 1002 #define GEO_NODE_BOOLEAN 1003 -#define GEO_NODE_POINT_DISTRIBUTE 1004 -#define GEO_NODE_POINT_INSTANCE 1005 +#define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004 +#define GEO_NODE_LEGACY_POINT_INSTANCE 1005 #define GEO_NODE_SUBDIVISION_SURFACE 1006 #define GEO_NODE_OBJECT_INFO 1007 -#define GEO_NODE_ATTRIBUTE_RANDOMIZE 1008 -#define GEO_NODE_ATTRIBUTE_MATH 1009 +#define GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE 1008 +#define GEO_NODE_LEGACY_ATTRIBUTE_MATH 1009 #define GEO_NODE_JOIN_GEOMETRY 1010 -#define GEO_NODE_ATTRIBUTE_FILL 1011 -#define GEO_NODE_ATTRIBUTE_MIX 1012 -#define GEO_NODE_ATTRIBUTE_COLOR_RAMP 1013 -#define GEO_NODE_POINT_SEPARATE 1014 -#define GEO_NODE_ATTRIBUTE_COMPARE 1015 -#define GEO_NODE_POINT_ROTATE 1016 -#define GEO_NODE_ATTRIBUTE_VECTOR_MATH 1017 -#define GEO_NODE_ALIGN_ROTATION_TO_VECTOR 1018 -#define GEO_NODE_POINT_TRANSLATE 1019 -#define GEO_NODE_POINT_SCALE 1020 -#define GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE 1021 -#define GEO_NODE_POINTS_TO_VOLUME 1022 +#define GEO_NODE_LEGACY_ATTRIBUTE_FILL 1011 +#define GEO_NODE_LEGACY_ATTRIBUTE_MIX 1012 +#define GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP 1013 +#define GEO_NODE_LEGACY_POINT_SEPARATE 1014 +#define GEO_NODE_LEGACY_ATTRIBUTE_COMPARE 1015 +#define GEO_NODE_LEGACY_POINT_ROTATE 1016 +#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH 1017 +#define GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR 1018 +#define GEO_NODE_LEGACY_POINT_TRANSLATE 1019 +#define GEO_NODE_LEGACY_POINT_SCALE 1020 +#define GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE 1021 +#define GEO_NODE_LEGACY_POINTS_TO_VOLUME 1022 #define GEO_NODE_COLLECTION_INFO 1023 #define GEO_NODE_IS_VIEWPORT 1024 -#define GEO_NODE_ATTRIBUTE_PROXIMITY 1025 +#define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025 #define GEO_NODE_VOLUME_TO_MESH 1026 -#define GEO_NODE_ATTRIBUTE_COMBINE_XYZ 1027 -#define GEO_NODE_ATTRIBUTE_SEPARATE_XYZ 1028 +#define GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ 1027 +#define GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ 1028 #define GEO_NODE_MESH_SUBDIVIDE 1029 #define GEO_NODE_ATTRIBUTE_REMOVE 1030 -#define GEO_NODE_ATTRIBUTE_CONVERT 1031 +#define GEO_NODE_LEGACY_ATTRIBUTE_CONVERT 1031 #define GEO_NODE_MESH_PRIMITIVE_CUBE 1032 #define GEO_NODE_MESH_PRIMITIVE_CIRCLE 1033 #define GEO_NODE_MESH_PRIMITIVE_UV_SPHERE 1034 @@ -1447,28 +1450,28 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_MESH_PRIMITIVE_CONE 1037 #define GEO_NODE_MESH_PRIMITIVE_LINE 1038 #define GEO_NODE_MESH_PRIMITIVE_GRID 1039 -#define GEO_NODE_ATTRIBUTE_MAP_RANGE 1040 -#define GEO_NODE_ATTRIBUTE_CLAMP 1041 +#define GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE 1040 +#define GEO_NODE_LECAGY_ATTRIBUTE_CLAMP 1041 #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 -#define GEO_NODE_ATTRIBUTE_TRANSFER 1044 +#define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 -#define GEO_NODE_ATTRIBUTE_CURVE_MAP 1046 +#define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046 #define GEO_NODE_CURVE_RESAMPLE 1047 #define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 -#define GEO_NODE_MATERIAL_ASSIGN 1049 +#define GEO_NODE_LEGACY_MATERIAL_ASSIGN 1049 #define GEO_NODE_INPUT_MATERIAL 1050 #define GEO_NODE_MATERIAL_REPLACE 1051 -#define GEO_NODE_MESH_TO_CURVE 1052 -#define GEO_NODE_DELETE_GEOMETRY 1053 +#define GEO_NODE_LEGACY_MESH_TO_CURVE 1052 +#define GEO_NODE_LEGACY_DELETE_GEOMETRY 1053 #define GEO_NODE_CURVE_LENGTH 1054 -#define GEO_NODE_SELECT_BY_MATERIAL 1055 +#define GEO_NODE_LEGACY_SELECT_BY_MATERIAL 1055 #define GEO_NODE_CONVEX_HULL 1056 #define GEO_NODE_CURVE_TO_POINTS 1057 -#define GEO_NODE_CURVE_REVERSE 1058 +#define GEO_NODE_LEGACY_CURVE_REVERSE 1058 #define GEO_NODE_SEPARATE_COMPONENTS 1059 -#define GEO_NODE_CURVE_SUBDIVIDE 1060 -#define GEO_NODE_RAYCAST 1061 +#define GEO_NODE_LEGACY_CURVE_SUBDIVIDE 1060 +#define GEO_NODE_LEGACY_RAYCAST 1061 #define GEO_NODE_CURVE_PRIMITIVE_STAR 1062 #define GEO_NODE_CURVE_PRIMITIVE_SPIRAL 1063 #define GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER 1064 @@ -1479,10 +1482,17 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_ENDPOINTS 1069 #define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070 #define GEO_NODE_CURVE_TRIM 1071 -#define GEO_NODE_CURVE_SET_HANDLES 1072 -#define GEO_NODE_CURVE_SPLINE_TYPE 1073 -#define GEO_NODE_CURVE_SELECT_HANDLES 1074 +#define GEO_NODE_LEGACY_CURVE_SET_HANDLES 1072 +#define GEO_NODE_LEGACY_CURVE_SPLINE_TYPE 1073 +#define GEO_NODE_LEGACY_CURVE_SELECT_HANDLES 1074 #define GEO_NODE_CURVE_FILL 1075 +#define GEO_NODE_INPUT_POSITION 1076 +#define GEO_NODE_SET_POSITION 1077 +#define GEO_NODE_INPUT_INDEX 1078 +#define GEO_NODE_INPUT_NORMAL 1079 +#define GEO_NODE_ATTRIBUTE_CAPTURE 1080 +#define GEO_NODE_MATERIAL_SELECTION 1081 +#define GEO_NODE_MATERIAL_ASSIGN 1082 /** \} */ diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 1d57886f93d..597096bdff9 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -460,9 +460,13 @@ void BKE_object_modifiers_lib_link_common(void *userData, struct ID **idpoin, int cb_flag); +void BKE_object_replace_data_on_shallow_copy(struct Object *ob, struct ID *new_data); + struct PartEff; struct PartEff *BKE_object_do_version_give_parteff_245(struct Object *ob); +bool BKE_object_supports_material_slots(struct Object *ob); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_packedFile.h b/source/blender/blenkernel/BKE_packedFile.h index c45a0bc857d..8cb0c78d9aa 100644 --- a/source/blender/blenkernel/BKE_packedFile.h +++ b/source/blender/blenkernel/BKE_packedFile.h @@ -74,6 +74,12 @@ char *BKE_packedfile_unpack_to_file(struct ReportList *reports, const char *local_name, struct PackedFile *pf, enum ePF_FileStatus how); +char *BKE_packedfile_unpack(struct Main *bmain, + struct ReportList *reports, + struct ID *id, + const char *orig_file_path, + struct PackedFile *pf, + enum ePF_FileStatus how); int BKE_packedfile_unpack_vfont(struct Main *bmain, struct ReportList *reports, struct VFont *vfont, diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index fc145f1ddf1..0fbf39a52fa 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -131,6 +131,11 @@ class Spline { virtual void transform(const blender::float4x4 &matrix); /** + * Change the direction of the spline (switch the start and end) without changing its shape. + */ + void reverse(); + + /** * Mark all caches for re-computation. This must be called after any operation that would * change the generated positions, tangents, normals, mapping, etc. of the evaluated points. */ @@ -210,6 +215,7 @@ class Spline { virtual void correct_end_tangents() const = 0; virtual void copy_settings(Spline &dst) const = 0; virtual void copy_data(Spline &dst) const = 0; + virtual void reverse_impl() = 0; }; /** @@ -353,6 +359,9 @@ class BezierSpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + + protected: + void reverse_impl() override; }; /** @@ -469,6 +478,7 @@ class NURBSpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + void reverse_impl() override; void calculate_knots() const; blender::Span<BasisCache> calculate_basis_cache() const; @@ -519,6 +529,7 @@ class PolySpline final : public Spline { void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; + void reverse_impl() override; }; /** diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index c18ac9c0519..c652632aca8 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC intern/anim_path.c intern/anim_sys.c intern/anim_visualization.c + intern/anonymous_attribute.cc intern/appdir.c intern/armature.c intern/armature_deform.c @@ -297,6 +298,8 @@ set(SRC BKE_anim_path.h BKE_anim_visualization.h BKE_animsys.h + BKE_anonymous_attribute.h + BKE_anonymous_attribute.hh BKE_appdir.h BKE_armature.h BKE_armature.hh diff --git a/source/blender/blenkernel/intern/action_bones.cc b/source/blender/blenkernel/intern/action_bones.cc index b8d185e6a81..1f2b7360b70 100644 --- a/source/blender/blenkernel/intern/action_bones.cc +++ b/source/blender/blenkernel/intern/action_bones.cc @@ -28,6 +28,7 @@ #include "DNA_action_types.h" #include "DNA_anim_types.h" +#include "DNA_armature_types.h" #include "MEM_guardedalloc.h" @@ -36,12 +37,11 @@ namespace blender::bke { void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback) { LISTBASE_FOREACH (FCurve *, fcu, &action->curves) { - char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); - if (!bone_name) { + char bone_name[MAXBONENAME]; + if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) { continue; } callback(fcu, bone_name); - MEM_freeN(bone_name); } } diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc new file mode 100644 index 00000000000..67611053d83 --- /dev/null +++ b/source/blender/blenkernel/intern/anonymous_attribute.cc @@ -0,0 +1,118 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_anonymous_attribute.hh" + +using namespace blender::bke; + +/** + * A struct that identifies an attribute. It's lifetime is managed by an atomic reference count. + * + * Additionally, this struct can be strongly or weakly owned. The difference is that strong + * ownership means that attributes with this id will be kept around. Weak ownership just makes sure + * that the struct itself stays alive, but corresponding attributes might still be removed + * automatically. + */ +struct AnonymousAttributeID { + /** + * Total number of references to this attribute id. Once this reaches zero, the struct can be + * freed. This includes strong and weak references. + */ + mutable std::atomic<int> refcount_tot = 0; + + /** + * Number of strong references to this attribute id. When this is zero, the corresponding + * attributes can be removed from geometries automatically. + */ + mutable std::atomic<int> refcount_strong = 0; + + /** + * Only used to identify this struct in a debugging session. + */ + std::string debug_name; + + /** + * Unique name of the this attribute id during the current session. + */ + std::string internal_name; +}; + +/** Every time this function is called, it outputs a different name. */ +static std::string get_new_internal_name() +{ + static std::atomic<int> index = 0; + const int next_index = index.fetch_add(1); + return "anonymous_attribute_" + std::to_string(next_index); +} + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name) +{ + AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); + anonymous_id->debug_name = debug_name; + anonymous_id->internal_name = get_new_internal_name(); + anonymous_id->refcount_tot.store(1); + return anonymous_id; +} + +AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name) +{ + AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); + anonymous_id->debug_name = debug_name; + anonymous_id->internal_name = get_new_internal_name(); + anonymous_id->refcount_tot.store(1); + anonymous_id->refcount_strong.store(1); + return anonymous_id; +} + +bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->refcount_strong.load() >= 1; +} + +void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_tot.fetch_add(1); +} + +void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_tot.fetch_add(1); + anonymous_id->refcount_strong.fetch_add(1); +} + +void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id) +{ + const int new_refcount = anonymous_id->refcount_tot.fetch_sub(1) - 1; + if (new_refcount == 0) { + delete anonymous_id; + } +} + +void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id) +{ + anonymous_id->refcount_strong.fetch_sub(1); + BKE_anonymous_attribute_id_decrement_weak(anonymous_id); +} + +const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->debug_name.c_str(); +} + +const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id) +{ + return anonymous_id->internal_name.c_str(); +} diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c index e9444cf88a6..ee8ef5e97f7 100644 --- a/source/blender/blenkernel/intern/attribute.c +++ b/source/blender/blenkernel/intern/attribute.c @@ -51,7 +51,7 @@ typedef struct DomainInfo { int length; } DomainInfo; -static void get_domains(ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) +static void get_domains(const ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) { memset(info, 0, sizeof(DomainInfo) * ATTR_DOMAIN_NUM); @@ -223,6 +223,29 @@ bool BKE_id_attribute_remove(ID *id, CustomDataLayer *layer, ReportList *reports return true; } +CustomDataLayer *BKE_id_attribute_find(const ID *id, + const char *name, + const int type, + const AttributeDomain domain) +{ + DomainInfo info[ATTR_DOMAIN_NUM]; + get_domains(id, info); + + CustomData *customdata = info[domain].customdata; + if (customdata == NULL) { + return NULL; + } + + for (int i = 0; i < customdata->totlayer; i++) { + CustomDataLayer *layer = &customdata->layers[i]; + if (layer->type == type && STREQ(layer->name, name)) { + return layer; + } + } + + return NULL; +} + int BKE_id_attributes_length(ID *id, const CustomDataMask mask) { DomainInfo info[ATTR_DOMAIN_NUM]; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index aa0af294bc3..ee0477faefe 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -32,6 +32,8 @@ #include "BLI_float2.hh" #include "BLI_span.hh" +#include "BLT_translation.h" + #include "CLG_log.h" #include "NOD_type_conversions.hh" @@ -44,6 +46,8 @@ using blender::float3; using blender::Set; using blender::StringRef; using blender::StringRefNull; +using blender::bke::AttributeIDRef; +using blender::bke::OutputAttribute; using blender::fn::GMutableSpan; using blender::fn::GSpan; using blender::fn::GVArray_For_GSpan; @@ -186,7 +190,7 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) void OutputAttribute::save() { save_has_been_called_ = true; - if (optional_span_varray_.has_value()) { + if (optional_span_varray_) { optional_span_varray_->save(); } if (save_) { @@ -334,8 +338,20 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) return data != nullptr; } +static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer, + const AttributeIDRef &attribute_id) +{ + if (!attribute_id) { + return false; + } + if (attribute_id.is_anonymous()) { + return layer.anonymous_id == &attribute_id.anonymous_id(); + } + return layer.name == attribute_id.name(); +} + ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( - const GeometryComponent &component, const StringRef attribute_name) const + const GeometryComponent &component, const AttributeIDRef &attribute_id) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); if (custom_data == nullptr) { @@ -343,7 +359,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( } const int domain_size = component.attribute_domain_size(domain_); for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { - if (layer.name != attribute_name) { + if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } const CustomDataType data_type = (CustomDataType)layer.type; @@ -368,7 +384,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( } WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( - GeometryComponent &component, const StringRef attribute_name) const + GeometryComponent &component, const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -376,10 +392,17 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( } const int domain_size = component.attribute_domain_size(domain_); for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { - if (layer.name != attribute_name) { + if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } - CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size); + if (attribute_id.is_named()) { + CustomData_duplicate_referenced_layer_named( + custom_data, layer.type, layer.name, domain_size); + } + else { + CustomData_duplicate_referenced_layer_anonymous( + custom_data, layer.type, &attribute_id.anonymous_id(), domain_size); + } const CustomDataType data_type = (CustomDataType)layer.type; switch (data_type) { case CD_PROP_FLOAT: @@ -402,7 +425,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( } bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -411,7 +434,8 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, const int domain_size = component.attribute_domain_size(domain_); for (const int i : IndexRange(custom_data->totlayer)) { const CustomDataLayer &layer = custom_data->layers[i]; - if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) { + if (this->type_is_supported((CustomDataType)layer.type) && + custom_data_layer_matches_attribute_id(layer, attribute_id)) { CustomData_free_layer(custom_data, layer.type, domain_size, i); return true; } @@ -419,24 +443,39 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, return false; } -static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name, - CustomData &custom_data, - const CustomDataType data_type, - const int domain_size, - const AttributeInit &initializer) +static void *add_generic_custom_data_layer(CustomData &custom_data, + const CustomDataType data_type, + const eCDAllocType alloctype, + void *layer_data, + const int domain_size, + const AttributeIDRef &attribute_id) { - char attribute_name_c[MAX_NAME]; - attribute_name.copy(attribute_name_c); + if (attribute_id.is_named()) { + char attribute_name_c[MAX_NAME]; + attribute_id.name().copy(attribute_name_c); + return CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + } + const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); + return CustomData_add_layer_anonymous( + &custom_data, data_type, alloctype, layer_data, domain_size, &anonymous_id); +} +static bool add_custom_data_layer_from_attribute_init(const AttributeIDRef &attribute_id, + CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ switch (initializer.type) { case AttributeInit::Type::Default: { - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id); return data != nullptr; } case AttributeInit::Type::VArray: { - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id); if (data == nullptr) { return false; } @@ -446,8 +485,8 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr } case AttributeInit::Type::MoveArray: { void *source_data = static_cast<const AttributeInitMove &>(initializer).data; - void *data = CustomData_add_layer_named( - &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c); + void *data = add_generic_custom_data_layer( + custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_id); if (data == nullptr) { MEM_freeN(source_data); return false; @@ -461,7 +500,7 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr } bool CustomDataAttributeProvider::try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const @@ -477,13 +516,13 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, return false; } for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { return false; } } const int domain_size = component.attribute_domain_size(domain_); - add_named_custom_data_layer_from_attribute_init( - attribute_name, *custom_data, data_type, domain_size, initializer); + add_custom_data_layer_from_attribute_init( + attribute_id, *custom_data, data_type, domain_size, initializer); return true; } @@ -498,7 +537,14 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com const CustomDataType data_type = (CustomDataType)layer.type; if (this->type_is_supported(data_type)) { AttributeMetaData meta_data{domain_, data_type}; - if (!callback(layer.name, meta_data)) { + AttributeIDRef attribute_id; + if (layer.anonymous_id != nullptr) { + attribute_id = layer.anonymous_id; + } + else { + attribute_id = layer.name; + } + if (!callback(attribute_id, meta_data)) { return false; } } @@ -507,7 +553,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com } ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( - const GeometryComponent &component, const StringRef attribute_name) const + const GeometryComponent &component, const AttributeIDRef &attribute_id) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); if (custom_data == nullptr) { @@ -515,7 +561,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( } for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); return {as_read_attribute_(layer.data, domain_size), domain_}; } @@ -525,7 +571,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( } WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( - GeometryComponent &component, const StringRef attribute_name) const + GeometryComponent &component, const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -533,7 +579,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( } for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); void *data_old = layer.data; void *data_new = CustomData_duplicate_referenced_layer_named( @@ -549,7 +595,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( } bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); if (custom_data == nullptr) { @@ -558,7 +604,7 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, for (const int i : IndexRange(custom_data->totlayer)) { const CustomDataLayer &layer = custom_data->layers[i]; if (layer.type == stored_type_) { - if (layer.name == attribute_name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const int domain_size = component.attribute_domain_size(domain_); CustomData_free_layer(custom_data, stored_type_, domain_size, i); custom_data_access_.update_custom_data_pointers(component); @@ -627,11 +673,11 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes return *this; } -std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const +std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const { BLI_assert(size_ != 0); for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); BLI_assert(cpp_type != nullptr); return GSpan(*cpp_type, layer.data, size_); @@ -645,13 +691,13 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co * value if the attribute doesn't exist. If no default value is provided, the default value for the * type will be used. */ -GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name, +GVArrayPtr CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id, const CustomDataType data_type, const void *default_value) const { const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); - std::optional<GSpan> attribute = this->get_for_read(name); + std::optional<GSpan> attribute = this->get_for_read(attribute_id); if (!attribute) { const int domain_size = this->size_; return std::make_unique<GVArray_For_SingleValue>( @@ -666,12 +712,12 @@ GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name, return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type); } -std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id) { /* If this assert hits, it most likely means that #reallocate was not called at some point. */ BLI_assert(size_ != 0); for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); BLI_assert(cpp_type != nullptr); return GMutableSpan(*cpp_type, layer.data, size_); @@ -680,30 +726,29 @@ std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef return {}; } -bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type) +bool CustomDataAttributes::create(const AttributeIDRef &attribute_id, + const CustomDataType data_type) { - char name_c[MAX_NAME]; - name.copy(name_c); - void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c); + void *result = add_generic_custom_data_layer( + data, data_type, CD_DEFAULT, nullptr, size_, attribute_id); return result != nullptr; } -bool CustomDataAttributes::create_by_move(const blender::StringRef name, +bool CustomDataAttributes::create_by_move(const AttributeIDRef &attribute_id, const CustomDataType data_type, void *buffer) { - char name_c[MAX_NAME]; - name.copy(name_c); - void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c); + void *result = add_generic_custom_data_layer( + data, data_type, CD_ASSIGN, buffer, size_, attribute_id); return result != nullptr; } -bool CustomDataAttributes::remove(const blender::StringRef name) +bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id) { bool result = false; for (const int i : IndexRange(data.totlayer)) { const CustomDataLayer &layer = data.layers[i]; - if (layer.name == name) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { CustomData_free_layer(&data, layer.type, size_, i); result = true; } @@ -722,7 +767,14 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call { for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; - if (!callback(layer.name, meta_data)) { + AttributeIDRef attribute_id; + if (layer.anonymous_id != nullptr) { + attribute_id = layer.anonymous_id; + } + else { + attribute_id = layer.name; + } + if (!callback(attribute_id, meta_data)) { return false; } } @@ -765,22 +817,30 @@ bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_ return providers->builtin_attribute_providers().contains_as(attribute_name); } +bool GeometryComponent::attribute_is_builtin(const AttributeIDRef &attribute_id) const +{ + /* Anonymous attributes cannot be built-in. */ + return attribute_id.is_named() && this->attribute_is_builtin(attribute_id.name()); +} + blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; + } } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name); + ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id); if (attribute) { return attribute; } @@ -800,21 +860,23 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_dom } blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write( - const StringRef attribute_name) + const AttributeIDRef &attribute_id) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; + } } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name); + WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id); if (attribute) { return attribute; } @@ -822,53 +884,57 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ return {}; } -bool GeometryComponent::attribute_try_delete(const StringRef attribute_name) +bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return {}; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - return builtin_provider->try_delete(*this); + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + return builtin_provider->try_delete(*this); + } } bool success = false; for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - success = dynamic_provider->try_delete(*this, attribute_name) || success; + success = dynamic_provider->try_delete(*this, attribute_id) || success; } return success; } -bool GeometryComponent::attribute_try_create(const StringRef attribute_name, +bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) { using namespace blender::bke; - if (attribute_name.is_empty()) { + if (!attribute_id) { return false; } const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return false; } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider != nullptr) { - if (builtin_provider->domain() != domain) { - return false; - } - if (builtin_provider->data_type() != data_type) { - return false; + if (attribute_id.is_named()) { + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); + if (builtin_provider != nullptr) { + if (builtin_provider->domain() != domain) { + return false; + } + if (builtin_provider->data_type() != data_type) { + return false; + } + return builtin_provider->try_create(*this, initializer); } - return builtin_provider->try_create(*this, initializer); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) { + if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) { return true; } } @@ -894,13 +960,14 @@ bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef at return builtin_provider->try_create(*this, initializer); } -Set<std::string> GeometryComponent::attribute_names() const +Set<AttributeIDRef> GeometryComponent::attribute_ids() const { - Set<std::string> attributes; - this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - attributes.add(name); - return true; - }); + Set<AttributeIDRef> attributes; + this->attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + attributes.add(attribute_id); + return true; + }); return attributes; } @@ -931,9 +998,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac } for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) { const bool continue_loop = provider->foreach_attribute( - *this, [&](StringRefNull name, const AttributeMetaData &meta_data) { - if (handled_attribute_names.add(name)) { - return callback(name, meta_data); + *this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) { + return callback(attribute_id, meta_data); } return true; }); @@ -945,9 +1012,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac return true; } -bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const +bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (attribute) { return true; } @@ -955,16 +1022,17 @@ bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name } std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data( - const StringRef attribute_name) const + const AttributeIDRef &attribute_id) const { std::optional<AttributeMetaData> result{std::nullopt}; - this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (attribute_name == name) { - result = meta_data; - return false; - } - return true; - }); + this->attribute_foreach( + [&](const AttributeIDRef ¤t_attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id == current_attribute_id) { + result = meta_data; + return false; + } + return true; + }); return result; } @@ -977,11 +1045,11 @@ static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type( } std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1007,13 +1075,13 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r } std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read( - const StringRef attribute_name, const AttributeDomain domain) const + const AttributeIDRef &attribute_id, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { return {}; } - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1026,9 +1094,9 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_ } blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const blender::StringRef attribute_name, const CustomDataType data_type) const + const AttributeIDRef &attribute_id, const CustomDataType data_type) const { - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); if (!attribute) { return {}; } @@ -1043,13 +1111,13 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( } std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read( - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const void *default_value) const { std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read( - attribute_name, domain, data_type); + attribute_id, domain, data_type); if (varray) { return varray; } @@ -1065,15 +1133,22 @@ class GVMutableAttribute_For_OutputAttribute : public blender::fn::GVMutableArray_For_GMutableSpan { public: GeometryComponent *component; - std::string final_name; + std::string attribute_name; + blender::bke::WeakAnonymousAttributeID anonymous_attribute_id; GVMutableAttribute_For_OutputAttribute(GMutableSpan data, GeometryComponent &component, - std::string final_name) - : blender::fn::GVMutableArray_For_GMutableSpan(data), - component(&component), - final_name(std::move(final_name)) + const AttributeIDRef &attribute_id) + : blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component) { + if (attribute_id.is_named()) { + this->attribute_name = attribute_id.name(); + } + else { + const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id(); + BKE_anonymous_attribute_id_increment_weak(anonymous_id); + this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id}; + } } ~GVMutableAttribute_For_OutputAttribute() override @@ -1083,7 +1158,7 @@ class GVMutableAttribute_For_OutputAttribute } }; -static void save_output_attribute(blender::bke::OutputAttribute &output_attribute) +static void save_output_attribute(OutputAttribute &output_attribute) { using namespace blender; using namespace blender::fn; @@ -1093,21 +1168,28 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray()); GeometryComponent &component = *varray.component; - const StringRefNull name = varray.final_name; + AttributeIDRef attribute_id; + if (!varray.attribute_name.empty()) { + attribute_id = varray.attribute_name; + } + else { + attribute_id = varray.anonymous_attribute_id.extract(); + } const AttributeDomain domain = output_attribute.domain(); const CustomDataType data_type = output_attribute.custom_data_type(); const CPPType &cpp_type = output_attribute.cpp_type(); - component.attribute_try_delete(name); - if (!component.attribute_try_create( - varray.final_name, domain, data_type, AttributeInitDefault())) { - CLOG_WARN(&LOG, - "Could not create the '%s' attribute with type '%s'.", - name.c_str(), - cpp_type.name().c_str()); + component.attribute_try_delete(attribute_id); + if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) { + if (!varray.attribute_name.empty()) { + CLOG_WARN(&LOG, + "Could not create the '%s' attribute with type '%s'.", + varray.attribute_name.c_str(), + cpp_type.name().c_str()); + } return; } - WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name); + WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id); BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer); for (const int i : IndexRange(varray.size())) { varray.get(i, buffer); @@ -1115,19 +1197,18 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut } } -static blender::bke::OutputAttribute create_output_attribute( - GeometryComponent &component, - const blender::StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const bool ignore_old_values, - const void *default_value) +static OutputAttribute create_output_attribute(GeometryComponent &component, + const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const CustomDataType data_type, + const bool ignore_old_values, + const void *default_value) { using namespace blender; using namespace blender::fn; using namespace blender::bke; - if (attribute_name.is_empty()) { + if (!attribute_id) { return {}; } @@ -1135,7 +1216,8 @@ static blender::bke::OutputAttribute create_output_attribute( BLI_assert(cpp_type != nullptr); const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); - if (component.attribute_is_builtin(attribute_name)) { + if (component.attribute_is_builtin(attribute_id)) { + const StringRef attribute_name = attribute_id.name(); WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); if (!attribute) { if (default_value) { @@ -1169,18 +1251,18 @@ static blender::bke::OutputAttribute create_output_attribute( const int domain_size = component.attribute_domain_size(domain); - WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id); if (!attribute) { if (default_value) { const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; component.attribute_try_create( - attribute_name, domain, data_type, AttributeInitVArray(&default_varray)); + attribute_id, domain, data_type, AttributeInitVArray(&default_varray)); } else { - component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault()); + component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault()); } - attribute = component.attribute_try_get_for_write(attribute_name); + attribute = component.attribute_try_get_for_write(attribute_id); if (!attribute) { /* Can't create the attribute. */ return {}; @@ -1202,28 +1284,102 @@ static blender::bke::OutputAttribute create_output_attribute( else { /* Fill the temporary array with values from the existing attribute. */ GVArrayPtr old_varray = component.attribute_get_for_read( - attribute_name, domain, data_type, default_value); + attribute_id, domain, data_type, default_value); old_varray->materialize_to_uninitialized(IndexRange(domain_size), data); } GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>( - GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name); + GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id); return OutputAttribute(std::move(varray), domain, save_output_attribute, true); } -blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output( - const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) +OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) { - return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value); + return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value); } -blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only( - const blender::StringRef attribute_name, +OutputAttribute GeometryComponent::attribute_try_get_for_output_only( + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) { - return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr); + return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr); } + +namespace blender::bke { + +const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const +{ + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type); + return scope.add(std::move(attribute)); + } + return nullptr; +} + +std::string AttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Attribute: ") << name_; + return ss.str(); +} + +uint64_t AttributeFieldInput::hash() const +{ + return get_default_hash_2(name_, type_); +} + +bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + if (const AttributeFieldInput *other_typed = dynamic_cast<const AttributeFieldInput *>(&other)) { + return name_ == other_typed->name_ && type_ == other_typed->type_; + } + return false; +} + +const GVArray *AnonymousAttributeFieldInput::get_varray_for_context( + const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const +{ + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + GVArrayPtr attribute = component.attribute_try_get_for_read( + anonymous_id_.get(), domain, data_type); + return scope.add(std::move(attribute)); + } + return nullptr; +} + +std::string AnonymousAttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Anonymous Attribute: ") << debug_name_; + return ss.str(); +} + +uint64_t AnonymousAttributeFieldInput::hash() const +{ + return get_default_hash_2(anonymous_id_.get(), type_); +} + +bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + if (const AnonymousAttributeFieldInput *other_typed = + dynamic_cast<const AnonymousAttributeFieldInput *>(&other)) { + return anonymous_id_.get() == other_typed->anonymous_id_.get() && type_ == other_typed->type_; + } + return false; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index b3a795faa30..261cb26d4e5 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -116,12 +116,13 @@ class BuiltinAttributeProvider { class DynamicAttributesProvider { public: virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const = 0; + const AttributeIDRef &attribute_id) const = 0; virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const = 0; - virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0; + const AttributeIDRef &attribute_id) const = 0; + virtual bool try_delete(GeometryComponent &component, + const AttributeIDRef &attribute_id) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component), - const StringRef UNUSED(attribute_name), + const AttributeIDRef &UNUSED(attribute_id), const AttributeDomain UNUSED(domain), const CustomDataType UNUSED(data_type), const AttributeInit &UNUSED(initializer)) const @@ -154,15 +155,15 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { } ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; bool try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const final; @@ -231,10 +232,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { } ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; + const AttributeIDRef &attribute_id) const final; + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final; diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index 97a54f289ee..97f8bddc043 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -362,7 +362,9 @@ void BKE_blender_userdef_app_template_data_swap(UserDef *userdef_a, UserDef *use DATA_SWAP(app_flag); /* We could add others. */ - FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT); + FLAG_SWAP(uiflag, int, USER_SAVE_PROMPT | USER_SPLASH_DISABLE | USER_SHOW_GIZMO_NAVIGATE); + + DATA_SWAP(ui_scale); #undef SWAP_TYPELESS #undef DATA_SWAP diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 78fbe439ac9..7d7b13fe872 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -142,8 +142,16 @@ static void brush_free_data(ID *id) static void brush_make_local(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + Brush *brush = (Brush *)id; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -151,36 +159,46 @@ static void brush_make_local(Main *bmain, ID *id, const int flags) * - mixed: make copy */ - if (!ID_IS_LINKED(brush)) { - return; - } - if (brush->clone.image) { /* Special case: ima always local immediately. Clone image should only have one user anyway. */ - BKE_lib_id_make_local(bmain, &brush->clone.image->id, false, 0); + /* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided + * in IDType callbacks. Higher-level ID management code usually does not expect such things and + * does not deal properly with it. */ + /* NOTE: assert below ensures that the comment above is valid, and that that exception is + * acceptable for the time being. */ + BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0); + BLI_assert(brush->clone.image->id.lib == NULL && brush->clone.image->id.newid == NULL); + } + + if (!force_local && !force_copy) { + BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib); + if (force_local) { + BKE_lib_id_clear_library_data(bmain, &brush->id); + BKE_lib_id_expand_local(bmain, &brush->id); - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, &brush->id); - BKE_lib_id_expand_local(bmain, &brush->id); - - /* enable fake user by default */ - id_fake_user_set(&brush->id); - } - else { - Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ + /* enable fake user by default */ + id_fake_user_set(&brush->id); + } + else if (force_copy) { + Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ - brush_new->id.us = 0; + brush_new->id.us = 0; - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(brush, brush_new); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(brush, brush_new); - if (!lib_local) { - BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); - } + if (!lib_local) { + BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); } } } @@ -1157,7 +1175,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_strength = 0.3f; brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAG_SMOOTH_PRESSURE; + brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAGMODE_APPLY_THICKNESS; brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; break; @@ -1171,7 +1189,6 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_strength = 0.3f; brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAG_SMOOTH_PRESSURE; brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; break; diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 72f14d94833..b9b15eba6a4 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -3900,7 +3900,11 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar unit_m4(targetMatrix); INIT_MINMAX(curveMin, curveMax); - /* XXX(campbell): don't think this is good calling this here. */ + /* XXX(@campbellbarton): don't think this is good calling this here because + * the other object's data is lazily initializing bounding-box information. + * This could cause issues when evaluating from a thread. + * If the depsgraph ensures the bound-box is always available, a code-path could + * be used that doesn't lazy initialize to avoid thread safety issues in the future. */ BKE_object_minmax(ct->tar, curveMin, curveMax, true); /* get targetmatrix */ diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index 397838e6904..f22c3b13efc 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -5269,6 +5269,8 @@ void BKE_curve_transform_ex(Curve *cu, BezTriple *bezt; int i; + const bool is_uniform_scaled = is_uniform_scaled_m4(mat); + LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { if (nu->type == CU_BEZIER) { i = nu->pntsu; @@ -5279,6 +5281,11 @@ void BKE_curve_transform_ex(Curve *cu, if (do_props) { bezt->radius *= unit_scale; } + if (!is_uniform_scaled) { + if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) || ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) { + bezt->h1 = bezt->h2 = HD_ALIGN; + } + } } BKE_nurb_handles_calc(nu); } diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 5c18f6f3807..1c4f9c5a6ab 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -25,6 +25,7 @@ #include "DNA_curve_types.h" +#include "BKE_anonymous_attribute.hh" #include "BKE_curve.h" #include "BKE_spline.hh" @@ -37,6 +38,7 @@ using blender::MutableSpan; using blender::Span; using blender::StringRefNull; using blender::Vector; +using blender::bke::AttributeIDRef; blender::Span<SplinePtr> CurveEval::splines() const { @@ -330,13 +332,13 @@ void CurveEval::assert_valid_point_attributes() const return; } const int layer_len = splines_.first()->attributes.data.totlayer; - Map<StringRefNull, AttributeMetaData> map; + Map<AttributeIDRef, AttributeMetaData> map; for (const SplinePtr &spline : splines_) { BLI_assert(spline->attributes.data.totlayer == layer_len); spline->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { map.add_or_modify( - name, + attribute_id, [&](AttributeMetaData *map_data) { /* All unique attribute names should be added on the first spline. */ BLI_assert(spline == splines_.first()); diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index 7e7ecbf101e..f78a322dd24 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -47,6 +47,7 @@ #include "BLT_translation.h" +#include "BKE_anonymous_attribute.h" #include "BKE_customdata.h" #include "BKE_customdata_file.h" #include "BKE_deform.h" @@ -2286,6 +2287,11 @@ bool CustomData_merge(const struct CustomData *source, if (flag & CD_FLAG_NOCOPY) { continue; } + if (layer->anonymous_id && + !BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) { + /* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */ + continue; + } if (!(mask & CD_TYPE_AS_MASK(type))) { continue; } @@ -2325,6 +2331,11 @@ bool CustomData_merge(const struct CustomData *source, newlayer->active_mask = lastmask; newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY); changed = true; + + if (layer->anonymous_id != NULL) { + BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id); + newlayer->anonymous_id = layer->anonymous_id; + } } } @@ -2365,6 +2376,10 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem) { const LayerTypeInfo *typeInfo; + if (layer->anonymous_id != NULL) { + BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id); + layer->anonymous_id = NULL; + } if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) { typeInfo = layerType_getInfo(layer->type); @@ -2808,6 +2823,27 @@ void *CustomData_add_layer_named(CustomData *data, return NULL; } +void *CustomData_add_layer_anonymous(struct CustomData *data, + int type, + eCDAllocType alloctype, + void *layerdata, + int totelem, + const AnonymousAttributeID *anonymous_id) +{ + const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id); + CustomDataLayer *layer = customData_add_layer__internal( + data, type, alloctype, layerdata, totelem, name); + CustomData_update_typemap(data); + + if (layer == NULL) { + return NULL; + } + + BKE_anonymous_attribute_id_increment_weak(anonymous_id); + layer->anonymous_id = anonymous_id; + return layer->data; +} + bool CustomData_free_layer(CustomData *data, int type, int totelem, int index) { const int index_first = CustomData_get_layer_index(data, type); @@ -2971,6 +3007,20 @@ void *CustomData_duplicate_referenced_layer_named(CustomData *data, return customData_duplicate_referenced_layer_index(data, layer_index, totelem); } +void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data, + const int UNUSED(type), + const AnonymousAttributeID *anonymous_id, + const int totelem) +{ + for (int i = 0; i < data->totlayer; i++) { + if (data->layers[i].anonymous_id == anonymous_id) { + return customData_duplicate_referenced_layer_index(data, i, totelem); + } + } + BLI_assert_unreachable(); + return NULL; +} + void CustomData_duplicate_referenced_layers(CustomData *data, int totelem) { for (int i = 0; i < data->totlayer; i++) { @@ -4525,7 +4575,8 @@ void CustomData_blend_write_prepare(CustomData *data, for (i = 0, j = 0; i < totlayer; i++) { CustomDataLayer *layer = &data->layers[i]; - if (layer->flag & CD_FLAG_NOCOPY) { /* Layers with this flag set are not written to file. */ + /* Layers with this flag set are not written to file. */ + if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) { data->totlayer--; // CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name); } diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 58509e95de6..f37978d14bb 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -40,6 +40,7 @@ #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_scanfill.h" +#include "BLI_span.hh" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -47,6 +48,7 @@ #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_font.h" +#include "BKE_geometry_set.hh" #include "BKE_key.h" #include "BKE_lattice.h" #include "BKE_lib_id.h" @@ -55,6 +57,7 @@ #include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_object.h" +#include "BKE_spline.hh" #include "BLI_sys_types.h" /* For #intptr_t support. */ @@ -101,17 +104,6 @@ DispList *BKE_displist_find(ListBase *lb, int type) return nullptr; } -bool BKE_displist_has_faces(const ListBase *lb) -{ - LISTBASE_FOREACH (const DispList *, dl, lb) { - if (ELEM(dl->type, DL_INDEX3, DL_INDEX4, DL_SURF)) { - return true; - } - } - - return false; -} - void BKE_displist_copy(ListBase *lbn, const ListBase *lb) { BKE_displist_free(lbn); @@ -688,23 +680,9 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) BKE_mball_texspace_calc(ob); object_deform_mball(ob, &ob->runtime.curve_cache->disp); - - /* No-op for MBALLs anyway... */ - boundbox_displist_object(ob); } } -void BKE_displist_make_mball_forRender(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - ListBase *dispbase) -{ - BKE_mball_polygonize(depsgraph, scene, ob, dispbase); - BKE_mball_texspace_calc(ob); - - object_deform_mball(ob, dispbase); -} - static ModifierData *curve_get_tessellate_point(const Scene *scene, const Object *ob, const bool for_render, @@ -745,10 +723,7 @@ static ModifierData *curve_get_tessellate_point(const Scene *scene, return pretessellatePoint; } -/** - * \return True if any modifier was applied. - */ -bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, +void BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, const Scene *scene, Object *ob, ListBase *source_nurb, @@ -793,7 +768,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, const ModifierEvalContext mectx = {depsgraph, ob, apply_flag}; ModifierData *pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); - bool modified = false; if (pretessellatePoint) { VirtualModifierData virtualModifierData; @@ -813,7 +787,6 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, } mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts); - modified = true; if (md == pretessellatePoint) { break; @@ -832,48 +805,59 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, if (keyVerts) { MEM_freeN(keyVerts); } - return modified; } -static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[3] +/** + * \return True if the deformed curve control point data should be implicitly + * converted directly to a mesh, or false if it can be left as curve data via #CurveEval. + */ +static bool do_curve_implicit_mesh_conversion(const Curve *curve, + ModifierData *first_modifier, + const Scene *scene, + const ModifierMode required_mode) { - *r_vert_len = 0; + /* Skip implicit filling and conversion to mesh when using "fast text editing". */ + if (curve->flag & CU_FAST) { + return false; + } - LISTBASE_FOREACH (DispList *, dl, dispbase) { - *r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr; + /* Do implicit conversion to mesh with the object bevel mode. */ + if (curve->bevel_mode == CU_BEV_MODE_OBJECT && curve->bevobj != nullptr) { + return true; } - float(*allverts)[3] = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__); - float *fp = (float *)allverts; - LISTBASE_FOREACH (DispList *, dl, dispbase) { - const int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); - memcpy(fp, dl->verts, sizeof(float) * ofs); - fp += ofs; + /* 2D curves are sometimes implicitly filled and converted to a mesh. */ + if (CU_DO_2DFILL(curve)) { + return true; } - return allverts; -} + /* Curve objects with implicit "tube" meshes should convert implicitly to a mesh. */ + if (curve->ext1 != 0.0f || curve->ext2 != 0.0f) { + return true; + } -static void displist_vert_coords_apply(ListBase *dispbase, const float (*allverts)[3]) -{ - const float *fp = (float *)allverts; - LISTBASE_FOREACH (DispList *, dl, dispbase) { - int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); - memcpy(dl->verts, fp, sizeof(float) * ofs); - fp += ofs; + /* If a non-geometry-nodes modifier is enabled before a nodes modifier, + * force conversion to mesh, since only the nodes modifier supports curve data. */ + ModifierData *md = first_modifier; + for (; md; md = md->next) { + if (BKE_modifier_is_enabled(scene, md, required_mode)) { + if (md->type == eModifierType_Nodes) { + break; + } + return true; + } } + + return false; } -static void curve_calc_modifiers_post(Depsgraph *depsgraph, - const Scene *scene, - Object *ob, - ListBase *dispbase, - const bool for_render, - const bool force_mesh_conversion, - Mesh **r_final) +static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + const ListBase *dispbase, + const bool for_render) { const Curve *cu = (const Curve *)ob->data; - const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); const bool use_cache = !for_render; @@ -897,166 +881,64 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData) : pretessellatePoint->next; - if (r_final && *r_final) { - BKE_id_free(nullptr, *r_final); + GeometrySet geometry_set; + if (ob->type == OB_SURF || do_curve_implicit_mesh_conversion(cu, md, scene, required_mode)) { + Mesh *mesh = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); + geometry_set.replace_mesh(mesh); + } + else { + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve( + *cu, ob->runtime.curve_cache->deformed_nurbs); + geometry_set.replace_curve(curve_eval.release()); } - Mesh *modified = nullptr; - float(*vertCos)[3] = nullptr; - int totvert = 0; for (; md; md = md->next) { const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); - if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; } - /* If we need normals, no choice, have to convert to mesh now. */ - const bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md); - /* XXX 2.8 : now that batch cache is stored inside the ob->data - * we need to create a Mesh for each curve that uses modifiers. */ - if (modified == nullptr /* && need_normal */) { - if (vertCos != nullptr) { - displist_vert_coords_apply(dispbase, vertCos); - } - - if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, dispbase); - } + if (md->type == eModifierType_Nodes) { + mti->modifyGeometrySet(md, &mectx_apply, &geometry_set); + continue; + } - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); + if (!geometry_set.has_mesh()) { + geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0)); } + Mesh *mesh = geometry_set.get_mesh_for_write(); - if (mti->type == eModifierTypeType_OnlyDeform || - (mti->type == eModifierTypeType_DeformOrConstruct && !modified)) { - if (modified) { - if (!vertCos) { - vertCos = BKE_mesh_vert_coords_alloc(modified, &totvert); - } - if (need_normal) { - BKE_mesh_ensure_normals(modified); - } - mti->deformVerts(md, &mectx_deform, modified, vertCos, totvert); - } - else { - if (!vertCos) { - vertCos = displist_vert_coords_alloc(dispbase, &totvert); - } - mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert); + if (mti->type == eModifierTypeType_OnlyDeform) { + int totvert; + float(*vertex_coords)[3] = BKE_mesh_vert_coords_alloc(mesh, &totvert); + if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) { + BKE_mesh_ensure_normals(mesh); } + mti->deformVerts(md, &mectx_deform, mesh, vertex_coords, totvert); + BKE_mesh_vert_coords_apply(mesh, vertex_coords); + MEM_freeN(vertex_coords); } else { - if (!r_final) { - /* makeDisplistCurveTypes could be used for beveling, where mesh - * is totally unnecessary, so we could stop modifiers applying - * when we found constructive modifier but mesh is unwanted. */ - break; - } - - if (modified) { - if (vertCos) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); - BKE_id_free(nullptr, modified); - modified = temp_mesh; - - BKE_mesh_vert_coords_apply(modified, vertCos); - } - } - else { - if (vertCos) { - displist_vert_coords_apply(dispbase, vertCos); - } - - if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, dispbase); - } - - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); - } - - if (vertCos) { - /* Vertex coordinates were applied to necessary data, could free it */ - MEM_freeN(vertCos); - vertCos = nullptr; - } - - if (need_normal) { - BKE_mesh_ensure_normals(modified); + if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) { + BKE_mesh_ensure_normals(mesh); } - Mesh *mesh_applied = mti->modifyMesh(md, &mectx_apply, modified); - - if (mesh_applied) { - if (modified && modified != mesh_applied) { - BKE_id_free(nullptr, modified); - } - modified = mesh_applied; + Mesh *output_mesh = mti->modifyMesh(md, &mectx_apply, mesh); + if (mesh != output_mesh) { + geometry_set.replace_mesh(output_mesh); } } } - if (vertCos) { - if (modified) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); - BKE_id_free(nullptr, modified); - modified = temp_mesh; + if (geometry_set.has_mesh()) { + Mesh *final_mesh = geometry_set.get_mesh_for_write(); - BKE_mesh_vert_coords_apply(modified, vertCos); - BKE_mesh_calc_normals(modified); + BKE_mesh_calc_normals(final_mesh); - MEM_freeN(vertCos); - } - else { - displist_vert_coords_apply(dispbase, vertCos); - MEM_freeN(vertCos); - vertCos = nullptr; - } + BLI_strncpy(final_mesh->id.name, cu->id.name, sizeof(final_mesh->id.name)); + *((short *)final_mesh->id.name) = ID_ME; } - if (r_final) { - if (force_mesh_conversion && !modified) { - /* XXX 2.8 : This is a workaround for by some deeper technical debts: - * - DRW Batch cache is stored inside the ob->data. - * - Curve data is not COWed for instances that use different modifiers. - * This can causes the modifiers to be applied on all user of the same data-block - * (see T71055) - * - * The easy workaround is to force to generate a Mesh that will be used for display data - * since a Mesh output is already used for generative modifiers. - * However it does not fix problems with actual edit data still being shared. - * - * The right solution would be to COW the Curve data block at the input of the modifier - * stack just like what the mesh modifier does. - */ - modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); - } - - if (modified) { - - /* XXX2.8(Sybren): make sure the face normals are recalculated as well */ - BKE_mesh_ensure_normals(modified); - - /* Special tweaks, needed since neither BKE_mesh_new_nomain_from_template() nor - * BKE_mesh_new_nomain_from_curve_displist() properly duplicate mat info... */ - BLI_strncpy(modified->id.name, cu->id.name, sizeof(modified->id.name)); - *((short *)modified->id.name) = ID_ME; - MEM_SAFE_FREE(modified->mat); - /* Set flag which makes it easier to see what's going on in a debugger. */ - modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT; - modified->mat = (Material **)MEM_dupallocN(cu->mat); - modified->totcol = cu->totcol; - - (*r_final) = modified; - } - else { - (*r_final) = nullptr; - } - } - else if (modified != nullptr) { - /* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */ - BKE_id_free(nullptr, modified); - } + return geometry_set; } static void displist_surf_indices(DispList *dl) @@ -1109,8 +991,7 @@ static void evaluate_surface_object(Depsgraph *depsgraph, BKE_nurbList_duplicate(deformed_nurbs, &cu->nurb); } - bool force_mesh_conversion = BKE_curve_calc_modifiers_pre( - depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); + BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); LISTBASE_FOREACH (const Nurb *, nu, deformed_nurbs) { if (!(for_render || nu->hide == 0) || !BKE_nurb_check_valid_uv(nu)) { @@ -1173,8 +1054,14 @@ static void evaluate_surface_object(Depsgraph *depsgraph, } } - curve_calc_modifiers_post( - depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final); + curve_to_filledpoly(cu, r_dispbase); + GeometrySet geometry_set = curve_calc_modifiers_post( + depsgraph, scene, ob, r_dispbase, for_render); + if (!geometry_set.has_mesh()) { + geometry_set.replace_mesh(BKE_mesh_new_nomain(0, 0, 0, 0, 0)); + } + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + *r_final = mesh_component.release(); } static void rotateBevelPiece(const Curve *cu, @@ -1394,12 +1281,11 @@ static void calc_bevfac_mapping(const Curve *cu, } } -static void evaluate_curve_type_object(Depsgraph *depsgraph, - const Scene *scene, - Object *ob, - const bool for_render, - ListBase *r_dispbase, - Mesh **r_final) +static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + const bool for_render, + ListBase *r_dispbase) { BLI_assert(ELEM(ob->type, OB_CURVE, OB_FONT)); const Curve *cu = (const Curve *)ob->data; @@ -1413,8 +1299,7 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph, BKE_nurbList_duplicate(deformed_nurbs, BKE_curve_nurbs_get_for_read(cu)); } - bool force_mesh_conversion = BKE_curve_calc_modifiers_pre( - depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); + BKE_curve_calc_modifiers_pre(depsgraph, scene, ob, deformed_nurbs, deformed_nurbs, for_render); BKE_curve_bevelList_make(ob, deformed_nurbs, for_render); @@ -1603,16 +1488,8 @@ static void evaluate_curve_type_object(Depsgraph *depsgraph, BKE_displist_free(&dlbev); - if (!(cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, r_dispbase); - } - - curve_calc_modifiers_post( - depsgraph, scene, ob, r_dispbase, for_render, force_mesh_conversion, r_final); - - if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) { - curve_to_filledpoly(cu, r_dispbase); - } + curve_to_filledpoly(cu, r_dispbase); + return curve_calc_modifiers_post(depsgraph, scene, ob, r_dispbase, for_render); } void BKE_displist_make_curveTypes(Depsgraph *depsgraph, @@ -1621,25 +1498,43 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, const bool for_render) { BLI_assert(ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)); + Curve &cow_curve = *(Curve *)ob->data; BKE_object_free_derived_caches(ob); + cow_curve.curve_eval = nullptr; - if (!ob->runtime.curve_cache) { - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); - } - + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); ListBase *dispbase = &(ob->runtime.curve_cache->disp); - Mesh *mesh_eval = nullptr; if (ob->type == OB_SURF) { + Mesh *mesh_eval; evaluate_surface_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval); + BKE_object_eval_assign_data(ob, &mesh_eval->id, true); } else { - evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase, &mesh_eval); - } + GeometrySet geometry = evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase); + + if (geometry.has_curve()) { + /* Assign the evaluated curve to the object's "data_eval". In addition to the curve_eval + * added to the curve here, it will also contain a copy of the original curve's data. This is + * essential, because it maintains the expected behavior for evaluated curve data from before + * the CurveEval data type was introduced, when an evaluated object's curve data was just a + * copy of the original curve and everything else ended up in #CurveCache. */ + CurveComponent &curve_component = geometry.get_component_for_write<CurveComponent>(); + cow_curve.curve_eval = curve_component.get_for_write(); + BKE_object_eval_assign_data(ob, &cow_curve.id, false); + } + else if (geometry.has_mesh()) { + /* Most areas of Blender don't yet know how to look in #geometry_set_eval for evaluated mesh + * data, and look in #data_eval instead. When the object evaluates to a curve, that field + * must be used for the evaluated curve data, but otherwise we can use the field to store a + * pointer to the mesh, so more areas can retrieve the mesh. */ + MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>(); + Mesh *mesh_eval = mesh_component.get_for_write(); + BKE_object_eval_assign_data(ob, &mesh_eval->id, false); + } - if (mesh_eval != nullptr) { - BKE_object_eval_assign_data(ob, &mesh_eval->id, true); + ob->runtime.geometry_set_eval = new GeometrySet(std::move(geometry)); } boundbox_displist_object(ob); @@ -1656,7 +1551,9 @@ void BKE_displist_make_curveTypes_forRender( evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final); } else { - evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase, r_final); + GeometrySet geometry_set = evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase); + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + *r_final = mesh_component.release(); } } @@ -1684,27 +1581,26 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) /* this is confusing, there's also min_max_object, applying the obmat... */ static void boundbox_displist_object(Object *ob) { - if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { - /* Curve's BB is already calculated as a part of modifier stack, - * here we only calculate object BB based on final display list. */ + BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)); + /* Curve's BB is already calculated as a part of modifier stack, + * here we only calculate object BB based on final display list. */ - /* object's BB is calculated from final displist */ - if (ob->runtime.bb == nullptr) { - ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); - } + /* object's BB is calculated from final displist */ + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); + } - const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval) { - BKE_object_boundbox_calc_from_mesh(ob, mesh_eval); - } - else { - float min[3], max[3]; + const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); + if (mesh_eval) { + BKE_object_boundbox_calc_from_mesh(ob, mesh_eval); + } + else { + float min[3], max[3]; - INIT_MINMAX(min, max); - BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max); - BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + INIT_MINMAX(min, max); + BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); - ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; - } + ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; } } diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 0dc4f64cec1..d75b3259148 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -2064,7 +2064,7 @@ static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object * } if (update_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } /* make a copy of mesh to use as brush data */ diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index fb6cd5871f4..8e9c504dcbf 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -346,30 +346,30 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con return 0; } + const size_t quotedName_size = strlen(dataName) + 1; + char *quotedName = alloca(quotedName_size); + /* Search each F-Curve one by one. */ for (fcu = src->first; fcu; fcu = fcu->next) { /* Check if quoted string matches the path. */ if (fcu->rna_path == NULL) { continue; } - - char *quotedName = BLI_str_quoted_substrN(fcu->rna_path, dataPrefix); - if (quotedName == NULL) { + /* Skipping names longer than `quotedName_size` is OK since we're after an exact match. */ + if (!BLI_str_quoted_substr(fcu->rna_path, dataPrefix, quotedName, quotedName_size)) { + continue; + } + if (!STREQ(quotedName, dataName)) { continue; } /* Check if the quoted name matches the required name. */ - if (STREQ(quotedName, dataName)) { - LinkData *ld = MEM_callocN(sizeof(LinkData), __func__); - - ld->data = fcu; - BLI_addtail(dst, ld); + LinkData *ld = MEM_callocN(sizeof(LinkData), __func__); - matches++; - } + ld->data = fcu; + BLI_addtail(dst, ld); - /* Always free the quoted string, since it needs freeing. */ - MEM_freeN(quotedName); + matches++; } /* Return the number of matches. */ return matches; diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 5a5e1208ff0..1324b37f39c 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -39,6 +39,7 @@ #include "DNA_object_types.h" #include "DNA_rigidbody_types.h" +#include "BKE_attribute.h" #include "BKE_effect.h" #include "BKE_fluid.h" #include "BKE_global.h" @@ -529,8 +530,7 @@ static bool BKE_fluid_modifier_init( copy_v3_v3_int(fds->res_max, res); /* Set time, frame length = 0.1 is at 25fps. */ - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; /* Initially dt is equal to frame length (dt can change with adaptive-time stepping though). */ fds->dt = fds->frame_length; fds->time_per_frame = 0; @@ -3256,7 +3256,10 @@ static void update_effectors( BKE_effectors_free(effectors); } -static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Object *ob) +static Mesh *create_liquid_geometry(FluidDomainSettings *fds, + Scene *scene, + Mesh *orgmesh, + Object *ob) { Mesh *me; MVert *mverts; @@ -3303,22 +3306,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj /* Normals are per vertex, so these must match. */ BLI_assert(num_verts == num_normals); - /* If needed, vertex velocities will be read too. */ - bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS; - FluidDomainVertexVelocity *velarray = NULL; - float time_mult = 25.0f * DT_DEFAULT; - - if (use_speedvectors) { - if (fds->mesh_velocities) { - MEM_freeN(fds->mesh_velocities); - } - - fds->mesh_velocities = MEM_calloc_arrayN( - num_verts, sizeof(FluidDomainVertexVelocity), "fluid_mesh_vertvelocities"); - fds->totvert = num_verts; - velarray = fds->mesh_velocities; - } - me = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 3, num_faces); if (!me) { return NULL; @@ -3350,6 +3337,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj /* Normals. */ normals = MEM_callocN(sizeof(short[3]) * num_normals, "Fluidmesh_tmp_normals"); + /* Velocities. */ + /* If needed, vertex velocities will be read too. */ + bool use_speedvectors = fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS; + float(*velarray)[3] = NULL; + float time_mult = fds->dx / (DT_DEFAULT * (25.0f / FPS)); + + if (use_speedvectors) { + CustomDataLayer *velocity_layer = BKE_id_attribute_new( + &me->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, NULL); + velarray = velocity_layer->data; + } + /* Loop for vertices and normals. */ for (i = 0, no_s = normals; i < num_verts && i < num_normals; i++, mverts++, no_s += 3) { @@ -3389,18 +3388,18 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obj # endif if (use_speedvectors) { - velarray[i].vel[0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * (fds->dx / time_mult); - velarray[i].vel[1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * (fds->dx / time_mult); - velarray[i].vel[2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * (fds->dx / time_mult); + velarray[i][0] = manta_liquid_get_vertvel_x_at(fds->fluid, i) * time_mult; + velarray[i][1] = manta_liquid_get_vertvel_y_at(fds->fluid, i) * time_mult; + velarray[i][2] = manta_liquid_get_vertvel_z_at(fds->fluid, i) * time_mult; # ifdef DEBUG_PRINT /* Debugging: Print velocities of vertices. */ - printf("velarray[%d].vel[0]: %f, velarray[%d].vel[1]: %f, velarray[%d].vel[2]: %f\n", + printf("velarray[%d][0]: %f, velarray[%d][1]: %f, velarray[%d][2]: %f\n", i, - velarray[i].vel[0], + velarray[i][0], i, - velarray[i].vel[1], + velarray[i][1], i, - velarray[i].vel[2]); + velarray[i][2]); # endif } } @@ -3574,7 +3573,7 @@ static Mesh *create_smoke_geometry(FluidDomainSettings *fds, Mesh *orgmesh, Obje } BKE_mesh_calc_edges(result, false, false); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -3670,8 +3669,7 @@ static void manta_guiding( Depsgraph *depsgraph, Scene *scene, Object *ob, FluidModifierData *fmd, int frame) { FluidDomainSettings *fds = fmd->domain; - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - float dt = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + float dt = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; BLI_mutex_lock(&object_update_lock); @@ -3842,8 +3840,7 @@ static void BKE_fluid_modifier_processDomain(FluidModifierData *fmd, copy_v3_v3_int(o_shift, fds->shift); /* Ensure that time parameters are initialized correctly before every step. */ - float fps = scene->r.frs_sec / scene->r.frs_sec_base; - fds->frame_length = DT_DEFAULT * (25.0f / fps) * fds->time_scale; + fds->frame_length = DT_DEFAULT * (25.0f / FPS) * fds->time_scale; fds->dt = fds->frame_length; fds->time_per_frame = 0; @@ -4216,7 +4213,7 @@ struct Mesh *BKE_fluid_modifier_do( if (needs_viewport_update) { /* Return generated geometry depending on domain type. */ if (fmd->domain->type == FLUID_DOMAIN_TYPE_LIQUID) { - result = create_liquid_geometry(fmd->domain, me, ob); + result = create_liquid_geometry(fmd->domain, scene, me, ob); } if (fmd->domain->type == FLUID_DOMAIN_TYPE_GAS) { result = create_smoke_geometry(fmd->domain, me, ob); @@ -4773,8 +4770,6 @@ static void BKE_fluid_modifier_freeDomain(FluidModifierData *fmd) fmd->domain->point_cache[0] = NULL; } - MEM_SAFE_FREE(fmd->domain->mesh_velocities); - if (fmd->domain->coba) { MEM_freeN(fmd->domain->coba); } @@ -5010,16 +5005,12 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, tfds->viscosity_exponent = fds->viscosity_exponent; /* mesh options */ - if (fds->mesh_velocities) { - tfds->mesh_velocities = MEM_dupallocN(fds->mesh_velocities); - } tfds->mesh_concave_upper = fds->mesh_concave_upper; tfds->mesh_concave_lower = fds->mesh_concave_lower; tfds->mesh_particle_radius = fds->mesh_particle_radius; tfds->mesh_smoothen_pos = fds->mesh_smoothen_pos; tfds->mesh_smoothen_neg = fds->mesh_smoothen_neg; tfds->mesh_scale = fds->mesh_scale; - tfds->totvert = fds->totvert; tfds->mesh_generator = fds->mesh_generator; /* secondary particle options */ diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index c1765967238..842a701f525 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -719,6 +719,9 @@ typedef struct VFontToCurveIter { * * Currently only disabled when scale-to-fit is enabled, * so floating-point error doesn't cause unexpected wrapping, see T89241. + * + * \note This should only be set once, in the #VFONT_TO_CURVE_INIT pass + * otherwise iterations wont behave predictably, see T91401. */ bool word_wrap; int status; @@ -750,8 +753,15 @@ enum { * * The em_height here is relative to FT_Face->bbox. */ -#define ASCENT(vfd) ((vfd)->ascender * (vfd)->em_height) -#define DESCENT(vfd) ((vfd)->em_height - ASCENT(vfd)) + +static float vfont_ascent(const VFontData *vfd) +{ + return vfd->ascender * vfd->em_height; +} +static float vfont_descent(const VFontData *vfd) +{ + return vfd->em_height - vfont_ascent(vfd); +} static bool vfont_to_curve(Object *ob, Curve *cu, @@ -1234,17 +1244,17 @@ static bool vfont_to_curve(Object *ob, case CU_ALIGN_Y_TOP_BASELINE: break; case CU_ALIGN_Y_TOP: - yoff = textbox_y_origin - ASCENT(vfd); + yoff = textbox_y_origin - vfont_ascent(vfd); break; case CU_ALIGN_Y_CENTER: - yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - ASCENT(vfd)) - + yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - vfont_ascent(vfd)) - (tb_scale.h * 0.5f) + textbox_y_origin); break; case CU_ALIGN_Y_BOTTOM_BASELINE: yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h; break; case CU_ALIGN_Y_BOTTOM: - yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + DESCENT(vfd); + yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + vfont_descent(vfd); break; } @@ -1265,16 +1275,16 @@ static bool vfont_to_curve(Object *ob, case CU_ALIGN_Y_TOP_BASELINE: break; case CU_ALIGN_Y_TOP: - yoff = -ASCENT(vfd); + yoff = -vfont_ascent(vfd); break; case CU_ALIGN_Y_CENTER: - yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - ASCENT(vfd); + yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - vfont_ascent(vfd); break; case CU_ALIGN_Y_BOTTOM_BASELINE: yoff = (lnr - 1) * linedist; break; case CU_ALIGN_Y_BOTTOM: - yoff = (lnr - 1) * linedist + DESCENT(vfd); + yoff = (lnr - 1) * linedist + vfont_descent(vfd); break; } @@ -1640,7 +1650,6 @@ static bool vfont_to_curve(Object *ob, else { iter_data->scale_to_fit = iter_data->bisect.min; iter_data->status = VFONT_TO_CURVE_SCALE_ONCE; - iter_data->word_wrap = false; } } } diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 0b6ba966974..7d0537178ef 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -64,6 +64,8 @@ void CurveComponent::clear() delete curve_; } if (curve_for_render_ != nullptr) { + /* The curve created by this component should not have any edit mode data. */ + BLI_assert(curve_for_render_->editfont == nullptr && curve_for_render_->editnurb == nullptr); BKE_id_free(nullptr, curve_for_render_); curve_for_render_ = nullptr; } @@ -220,6 +222,37 @@ static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, mixer.finalize(); } +/** + * A spline is selected if all of its control points were selected. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<> +void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + const int splines_len = curve.splines().size(); + Array<int> offsets = curve.control_point_offsets(); + BLI_assert(r_values.size() == splines_len); + + r_values.fill(true); + + for (const int i_spline : IndexRange(splines_len)) { + const int spline_offset = offsets[i_spline]; + const int spline_point_len = offsets[i_spline + 1] - spline_offset; + + for (const int i_point : IndexRange(spline_point_len)) { + if (!old_values[spline_offset + i_point]) { + r_values[i_spline] = false; + break; + } + } + } +} + static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -892,7 +925,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { public: ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr || curve->splines().size() == 0) { @@ -902,13 +935,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { Span<SplinePtr> splines = curve->splines(); Vector<GSpan> spans; /* GSpan has no default constructor. */ spans.reserve(splines.size()); - std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name); + std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_id); if (!first_span) { return {}; } spans.append(*first_span); for (const int i : IndexRange(1, splines.size() - 1)) { - std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name); + std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_id); if (!span) { /* All splines should have the same set of data layers. It would be possible to recover * here and return partial data instead, but that would add a lot of complexity for a @@ -945,7 +978,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* This function is almost the same as #try_get_for_read, but without const. */ WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr || curve->splines().size() == 0) { @@ -955,13 +988,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { MutableSpan<SplinePtr> splines = curve->splines(); Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */ spans.reserve(splines.size()); - std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name); + std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_id); if (!first_span) { return {}; } spans.append(*first_span); for (const int i : IndexRange(1, splines.size() - 1)) { - std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name); + std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_id); if (!span) { /* All splines should have the same set of data layers. It would be possible to recover * here and return partial data instead, but that would add a lot of complexity for a @@ -996,7 +1029,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return attribute; } - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { @@ -1006,7 +1039,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* Reuse the boolean for all splines; we expect all splines to have the same attributes. */ bool layer_freed = false; for (SplinePtr &spline : curve->splines()) { - layer_freed = spline->attributes.remove(attribute_name); + layer_freed = spline->attributes.remove(attribute_id); } return layer_freed; } @@ -1034,7 +1067,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { } bool try_create(GeometryComponent &component, - const StringRef attribute_name, + const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const final @@ -1053,7 +1086,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* First check the one case that allows us to avoid copying the input data. */ if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) { void *source_data = static_cast<const AttributeInitMove &>(initializer).data; - if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) { + if (!splines[0]->attributes.create_by_move(attribute_id, data_type, source_data)) { MEM_freeN(source_data); return false; } @@ -1062,7 +1095,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* Otherwise just create a custom data layer on each of the splines. */ for (const int i : splines.index_range()) { - if (!splines[i]->attributes.create(attribute_name, data_type)) { + if (!splines[i]->attributes.create(attribute_id, data_type)) { /* If attribute creation fails on one of the splines, we cannot leave the custom data * layers in the previous splines around, so delete them before returning. However, * this is not an expected case. */ @@ -1076,7 +1109,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return true; } - WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name); + WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_id); /* We just created the attribute, it should exist. */ BLI_assert(write_attribute); diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 3b1b7456162..26ef827d36d 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -122,7 +122,7 @@ blender::Span<int> InstancesComponent::instance_ids() const * If the reference exists already, the handle of the existing reference is returned. * Otherwise a new handle is added. */ -int InstancesComponent::add_reference(InstanceReference reference) +int InstancesComponent::add_reference(const InstanceReference &reference) { return references_.index_of_or_add_as(reference); } @@ -144,14 +144,23 @@ bool InstancesComponent::is_empty() const bool InstancesComponent::owns_direct_data() const { - /* The object and collection instances are not direct data. Instance transforms are direct data - * and are always owned. Therefore, instance components always own all their direct data. */ + for (const InstanceReference &reference : references_) { + if (!reference.owns_direct_data()) { + return false; + } + } return true; } void InstancesComponent::ensure_owns_direct_data() { BLI_assert(this->is_mutable()); + for (const InstanceReference &const_reference : references_) { + /* Const cast is fine because we are not changing anything that would change the hash of the + * reference. */ + InstanceReference &reference = const_cast<InstanceReference &>(const_reference); + reference.ensure_owns_direct_data(); + } } static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index ef93a3f9b3f..0c98aa5551b 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -175,6 +175,34 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if all connected face corners were selected and it is not loose. */ +template<> +void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + Array<bool> loose_verts(mesh.totvert, true); + + r_values.fill(true); + for (const int loop_index : IndexRange(mesh.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int point_index = loop.v; + + loose_verts[point_index] = false; + if (!old_values[loop_index]) { + r_values[point_index] = false; + } + } + + /* Deselect loose vertices without corners that are still selected from the 'true' default. */ + for (const int vert_index : IndexRange(mesh.totvert)) { + if (loose_verts[vert_index]) { + r_values[vert_index] = false; + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -191,6 +219,13 @@ static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr return new_varray; } +/** + * Each corner's value is simply a copy of the value at its vertex. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ template<typename T> static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, const VArray<T> &old_values, @@ -209,10 +244,6 @@ static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr GVArrayPtr new_varray; attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); - /* It is not strictly necessary to compute the value for all corners here. Instead one could - * lazily lookup the mesh topology when a specific index accessed. This can be more efficient - * when an algorithm only accesses very few of the corner values. However, for the algorithms - * we currently have, precomputing the array is fine. Also, it is easier to implement. */ Array<T> values(mesh.totloop); adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values); new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); @@ -244,6 +275,26 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its corners were selected. */ +template<> +void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + if (!old_values[loop_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -282,6 +333,41 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if all corners on adjacent faces were selected. */ +template<> +void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + /* It may be possible to rely on the #ME_LOOSEEDGE flag, but that seems error-prone. */ + Array<bool> loose_edges(mesh.totedge, true); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const int loop_index_next = (loop_index == poly.totloop) ? poly.loopstart : (loop_index + 1); + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + loose_edges[edge_index] = false; + + if (!old_values[loop_index] || !old_values[loop_index_next]) { + r_values[edge_index] = false; + } + } + } + + /* Deselect loose edges without corners that are still selected from the 'true' default. */ + for (const int edge_index : IndexRange(mesh.totedge)) { + if (loose_edges[edge_index]) { + r_values[edge_index] = false; + } + } +} + static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -317,6 +403,27 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if any of the connected faces were selected. */ +template<> +void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + + r_values.fill(false); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + if (old_values[poly_index]) { + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int vert_index = loop.v; + r_values[vert_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -331,6 +438,7 @@ static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr v return new_varray; } +/* Each corner's value is simply a copy of the value at its face. */ template<typename T> void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, const VArray<T> &old_values, @@ -378,6 +486,27 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if any connected face was selected. */ +template<> +void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + r_values.fill(false); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + if (old_values[poly_index]) { + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + r_values[edge_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -416,6 +545,28 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its vertices were selected too. */ +template<> +void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + MLoop &loop = mesh.mloop[loop_index]; + const int vert_index = loop.v; + if (!old_values[vert_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -452,6 +603,20 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, mixer.finalize(); } +/* An edge is selected if both of its vertices were selected. */ +template<> +void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totedge); + + for (const int edge_index : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[edge_index]; + r_values[edge_index] = old_values[edge.v1] && old_values[edge.v2]; + } +} + static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -490,6 +655,29 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, mixer.finalize(); } +/* A corner is selected if its two adjacent edges were selected. */ +template<> +void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totloop); + + r_values.fill(false); + + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; + const MLoop &loop = mesh.mloop[loop_index]; + const MLoop &loop_prev = mesh.mloop[loop_index_prev]; + if (old_values[loop.e] && old_values[loop_prev.e]) { + r_values[loop_index] = true; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -522,6 +710,24 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, mixer.finalize(); } +/* A vertex is selected if any connected edge was selected. */ +template<> +void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totvert); + + r_values.fill(false); + for (const int edge_index : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[edge_index]; + if (old_values[edge_index]) { + r_values[edge.v1] = true; + r_values[edge.v2] = true; + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -560,6 +766,28 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, mixer.finalize(); } +/* A face is selected if all of its edges are selected. */ +template<> +void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + BLI_assert(r_values.size() == mesh.totpoly); + + r_values.fill(true); + for (const int poly_index : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int edge_index = loop.e; + if (!old_values[edge_index]) { + r_values[poly_index] = false; + break; + } + } + } +} + static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; @@ -698,7 +926,7 @@ static void tag_normals_dirty_when_writing_position(GeometryComponent &component { Mesh *mesh = get_mesh_from_component_for_write(component); if (mesh != nullptr) { - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } } @@ -818,16 +1046,20 @@ class VArray_For_VertexWeights final : public VArray<float> { class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return {}; + } const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); const Mesh *mesh = mesh_component.get_for_read(); if (mesh == nullptr) { return {}; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return {}; } @@ -843,17 +1075,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return {}; + } MeshComponent &mesh_component = static_cast<MeshComponent &>(component); Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return {}; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return {}; } @@ -872,17 +1108,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { ATTR_DOMAIN_POINT}; } - bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); + if (!attribute_id.is_named()) { + return false; + } MeshComponent &mesh_component = static_cast<MeshComponent &>(component); Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return true; } + const std::string name = attribute_id.name(); const int vertex_group_index = BLI_findstringindex( - &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name)); + &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name)); if (vertex_group_index < 0) { return false; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 07b4e715ea9..e717d289894 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -218,6 +218,16 @@ void GeometrySet::ensure_owns_direct_data() } } +bool GeometrySet::owns_direct_data() const +{ + for (const GeometryComponentPtr &component : components_.values()) { + if (!component->owns_direct_data()) { + return false; + } + } + return true; +} + /* Returns a read-only mesh or null. */ const Mesh *GeometrySet::get_mesh_for_read() const { @@ -376,9 +386,32 @@ void BKE_geometry_set_free(GeometrySet *geometry_set) delete geometry_set; } -bool BKE_geometry_set_has_instances(const GeometrySet *geometry_set) +bool BKE_object_has_geometry_set_instances(const Object *ob) { - return geometry_set->get_component_for_read<InstancesComponent>() != nullptr; + const GeometrySet *geometry_set = ob->runtime.geometry_set_eval; + if (geometry_set == nullptr) { + return false; + } + if (geometry_set->has_instances()) { + return true; + } + const bool has_mesh = geometry_set->has_mesh(); + const bool has_pointcloud = geometry_set->has_pointcloud(); + const bool has_volume = geometry_set->has_volume(); + const bool has_curve = geometry_set->has_curve(); + if (ob->type == OB_MESH) { + return has_pointcloud || has_volume || has_curve; + } + if (ob->type == OB_POINTCLOUD) { + return has_mesh || has_volume || has_curve; + } + if (ob->type == OB_VOLUME) { + return has_mesh || has_pointcloud || has_curve; + } + if (ELEM(ob->type, OB_CURVE, OB_FONT)) { + return has_mesh || has_pointcloud || has_volume; + } + return false; } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 32a65ab47bf..9dca2c2907e 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -51,16 +51,6 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS } } -static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set) -{ - BLI_assert(object.type == OB_CURVE); - if (object.data != nullptr) { - std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(const Curve *)object.data); - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - curve_component.replace(curve.release(), GeometryOwnershipType::Owned); - } -} - /** * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ @@ -84,9 +74,6 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) if (object.type == OB_MESH) { add_final_mesh_as_geometry_component(object, geometry_set); } - else if (object.type == OB_CURVE) { - add_curve_data_as_geometry_component(object, geometry_set); - } /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the * #geometry_set_eval case above. */ @@ -168,6 +155,11 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, collection, instance_transform, r_sets); break; } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &geometry_set = reference.geometry_set(); + geometry_set_collect_recursive(geometry_set, instance_transform, r_sets); + break; + } case InstanceReference::Type::None: { break; } @@ -290,6 +282,13 @@ static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_se } break; } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &geometry_set = reference.geometry_set(); + if (!instances_attribute_foreach_recursive(geometry_set, callback, limit, count)) { + return false; + } + break; + } case InstanceReference::Type::None: { break; } @@ -319,7 +318,7 @@ void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, const Set<std::string> &ignored_attributes, - Map<std::string, AttributeKind> &r_attributes) + Map<AttributeIDRef, AttributeKind> &r_attributes) { for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; @@ -329,23 +328,24 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se } const GeometryComponent &component = *set.get_component_for_read(component_type); - component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (ignored_attributes.contains(name)) { - return true; - } - auto add_info = [&](AttributeKind *attribute_kind) { - attribute_kind->domain = meta_data.domain; - attribute_kind->data_type = meta_data.data_type; - }; - auto modify_info = [&](AttributeKind *attribute_kind) { - attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */ - attribute_kind->data_type = bke::attribute_data_type_highest_complexity( - {attribute_kind->data_type, meta_data.data_type}); - }; - - r_attributes.add_or_modify(name, add_info, modify_info); - return true; - }); + component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { + return true; + } + auto add_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = meta_data.domain; + attribute_kind->data_type = meta_data.data_type; + }; + auto modify_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */ + attribute_kind->data_type = bke::attribute_data_type_highest_complexity( + {attribute_kind->data_type, meta_data.data_type}); + }; + + r_attributes.add_or_modify(attribute_id, add_info, modify_info); + return true; + }); } } } @@ -500,11 +500,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou static void join_attributes(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, - const Map<std::string, AttributeKind> &attribute_info, + const Map<AttributeIDRef, AttributeKind> &attribute_info, GeometryComponent &result) { - for (Map<std::string, AttributeKind>::Item entry : attribute_info.items()) { - StringRef name = entry.key; + for (Map<AttributeIDRef, AttributeKind>::Item entry : attribute_info.items()) { + const AttributeIDRef attribute_id = entry.key; const AttributeDomain domain_output = entry.value.domain; const CustomDataType data_type_output = entry.value.data_type; const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output); @@ -512,7 +512,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, result.attribute_try_create( entry.key, domain_output, data_type_output, AttributeInitDefault()); - WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name); + WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(attribute_id); if (!write_attribute || &write_attribute.varray->type() != cpp_type || write_attribute.domain != domain_output) { continue; @@ -531,7 +531,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, continue; /* Domain size is 0, so no need to increment the offset. */ } GVArrayPtr source_attribute = component.attribute_try_get_for_read( - name, domain_output, data_type_output); + attribute_id, domain_output, data_type_output); if (source_attribute) { fn::GVArray_GSpan src_span{*source_attribute}; @@ -641,7 +641,7 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, } /* Don't copy attributes that are stored directly in the mesh data structs. */ - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, component_types, @@ -662,7 +662,7 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); dst_component.replace(new_pointcloud); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes); join_attributes(set_groups, @@ -696,7 +696,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); dst_component.replace(curve); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_CURVE}, diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index 5bca20ecd44..8ff026231f5 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -800,7 +800,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo * \param i: Point index * \param inf: Amount of smoothing to apply */ -bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf) +bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf) { bGPDspoint *pt = &gps->points[i]; float sco[3] = {0.0f}; @@ -3248,7 +3248,8 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps, void BKE_gpencil_stroke_join(bGPDstroke *gps_a, bGPDstroke *gps_b, const bool leave_gaps, - const bool fit_thickness) + const bool fit_thickness, + const bool smooth) { bGPDspoint point; bGPDspoint *pt; @@ -3326,16 +3327,51 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, deltatime); } + /* Ratio to apply in the points to keep the same thickness in the joined stroke using the + * destination stroke thickness. */ const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ? (float)gps_b->thickness / (float)gps_a->thickness : 1.0f; /* 3rd: add all points */ + const int totpoints_a = gps_a->totpoints; for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : nullptr; gpencil_stroke_copy_point( gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime); } + /* Smooth the join to avoid hard thickness changes. */ + if (smooth) { + const int sample_points = 8; + /* Get the segment to smooth using n points on each side of the join. */ + int start = MAX2(0, totpoints_a - sample_points); + int end = MIN2(gps_a->totpoints - 1, start + (sample_points * 2)); + const int len = (end - start); + float step = 1.0f / ((len / 2) + 1); + + /* Calc the average pressure. */ + float avg_pressure = 0.0f; + for (i = start; i < end; i++) { + pt = &gps_a->points[i]; + avg_pressure += pt->pressure; + } + avg_pressure = avg_pressure / len; + + /* Smooth segment thickness and position. */ + float ratio = step; + for (i = start; i < end; i++) { + pt = &gps_a->points[i]; + pt->pressure += (avg_pressure - pt->pressure) * ratio; + BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f); + + ratio += step; + /* In the center, reverse the ratio. */ + if (ratio > 1.0f) { + ratio = ratio - step - step; + step *= -1.0f; + } + } + } } /* Copy the stroke of the frame to all frames selected (except current). */ diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index a30376b9bad..6be03bffb3c 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -938,6 +938,11 @@ void BKE_gpencil_modifier_blend_write(BlendWriter *writer, ListBase *modbase) BKE_curvemapping_blend_write(writer, gpmd->curve_intensity); } } + else if (md->type == eGpencilModifierType_Dash) { + DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md; + BLO_write_struct_array( + writer, DashGpencilModifierSegment, gpmd->segments_len, gpmd->segments); + } } } @@ -1017,6 +1022,13 @@ void BKE_gpencil_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb) BKE_curvemapping_init(gpmd->curve_intensity); } } + else if (md->type == eGpencilModifierType_Dash) { + DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md; + BLO_read_data_address(reader, &gpmd->segments); + for (int i = 0; i < gpmd->segments_len; i++) { + gpmd->segments[i].dmd = gpmd; + } + } } } diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc index 5a4b2448a73..ac45e57f413 100644 --- a/source/blender/blenkernel/intern/icons.cc +++ b/source/blender/blenkernel/intern/icons.cc @@ -634,7 +634,7 @@ void BKE_previewimg_blend_write(BlendWriter *writer, const PreviewImage *prv) PreviewImage prv_copy = *prv; /* don't write out large previews if not requested */ - if (!(U.flag & USER_SAVE_PREVIEWS)) { + if (U.file_preview_type == USER_FILE_PREVIEW_NONE) { prv_copy.w[1] = 0; prv_copy.h[1] = 0; prv_copy.rect[1] = nullptr; diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index fee70922570..b2efccc53c4 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -224,10 +224,10 @@ bool BKE_idtype_idcode_is_valid(const short idcode) } /** - * Return non-zero when an ID type is linkable. + * Check if an ID type is linkable. * - * \param idcode: The code to check. - * \return Boolean, 0 when non linkable. + * \param idcode: The IDType code to check. + * \return Boolean, false when non linkable, true otherwise. */ bool BKE_idtype_idcode_is_linkable(const short idcode) { @@ -237,6 +237,24 @@ bool BKE_idtype_idcode_is_linkable(const short idcode) } /** + * Check if an ID type is only appendable. + * + * \param idcode: The IDType code to check. + * \return Boolean, false when also linkable, true when only appendable. + */ +bool BKE_idtype_idcode_is_only_appendable(const short idcode) +{ + const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); + BLI_assert(id_type != NULL); + if (id_type != NULL && (id_type->flags & IDTYPE_FLAGS_ONLY_APPEND) != 0) { + /* Only appendable ID types should also always be linkable. */ + BLI_assert((id_type->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0); + return true; + } + return false; +} + +/** * Convert an \a idcode into an \a idfilter (e.g. ID_OB -> FILTER_ID_OB). */ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index d87290e1eb4..33f007c6dee 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -155,7 +155,9 @@ static void image_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - image_dst->gputexture[i][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + image_dst->gputexture[i][eye][resolution] = NULL; + } } } @@ -208,9 +210,11 @@ static void image_foreach_cache(ID *id, for (int eye = 0; eye < 2; eye++) { for (int a = 0; a < TEXTARGET_COUNT; a++) { - key.offset_in_ID = offsetof(Image, gputexture[a][eye]); - key.cache_v = image->gputexture[a][eye]; - function_callback(id, &key, (void **)&image->gputexture[a][eye], 0, user_data); + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + key.offset_in_ID = offsetof(Image, gputexture[a][eye][resolution]); + key.cache_v = image->gputexture[a][eye]; + function_callback(id, &key, (void **)&image->gputexture[a][eye][resolution], 0, user_data); + } } } @@ -239,7 +243,9 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres BLI_listbase_clear(&ima->gpu_refresh_areas); for (int i = 0; i < 3; i++) { for (int j = 0; j < 2; j++) { - ima->gputexture[i][j] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + ima->gputexture[i][j][resolution] = NULL; + } } } @@ -677,8 +683,10 @@ bool BKE_image_has_opengl_texture(Image *ima) { for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - if (ima->gputexture[i][eye] != NULL) { - return true; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + return true; + } } } } @@ -3531,9 +3539,11 @@ static void image_free_tile(Image *ima, ImageTile *tile) } for (int eye = 0; eye < 2; eye++) { - if (ima->gputexture[i][eye] != NULL) { - GPU_texture_free(ima->gputexture[i][eye]); - ima->gputexture[i][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + ima->gputexture[i][eye][resolution] = NULL; + } } } } @@ -3801,14 +3811,16 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la } for (int eye = 0; eye < 2; eye++) { - /* Reallocate GPU tile array. */ - if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]); - ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL; - } - if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]); - ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + /* Reallocate GPU tile array. */ + if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]); + ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL; + } + if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]); + ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL; + } } } @@ -3863,14 +3875,17 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu } for (int eye = 0; eye < 2; eye++) { - /* Reallocate GPU tile array. */ - if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]); - ima->gputexture[TEXTARGET_2D_ARRAY][eye] = NULL; - } - if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != NULL) { - GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]); - ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = NULL; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + + /* Reallocate GPU tile array. */ + if (ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]); + ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution] = NULL; + } + if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution]); + ima->gputexture[TEXTARGET_TILE_MAPPING][eye][resolution] = NULL; + } } } } diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c index d179dd40c33..9712e912bed 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.c @@ -49,6 +49,7 @@ /* Prototypes. */ static void gpu_free_unused_buffers(void); static void image_free_gpu(Image *ima, const bool immediate); +static void image_free_gpu_limited_scale(Image *ima); static void image_update_gputexture_ex( Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h); @@ -97,9 +98,11 @@ static int smaller_power_of_2_limit(int num, bool limit_gl_texture_size) return power_of_2_min_i(GPU_texture_size_with_limit(num, limit_gl_texture_size)); } -static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multiview_eye) +static GPUTexture *gpu_texture_create_tile_mapping( + Image *ima, const int multiview_eye, const eImageTextureResolution texture_resolution) { - GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye]; + const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0; + GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye][resolution]; if (tilearray == NULL) { return 0; @@ -121,13 +124,14 @@ static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multivi } LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { int i = tile->tile_number - 1001; - data[4 * i] = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + data[4 * i] = tile_runtime->tilearray_layer; float *tile_info = &data[4 * width + 4 * i]; - tile_info[0] = tile->runtime.tilearray_offset[0] / array_w; - tile_info[1] = tile->runtime.tilearray_offset[1] / array_h; - tile_info[2] = tile->runtime.tilearray_size[0] / array_w; - tile_info[3] = tile->runtime.tilearray_size[1] / array_h; + tile_info[0] = tile_runtime->tilearray_offset[0] / array_w; + tile_info[1] = tile_runtime->tilearray_offset[1] / array_h; + tile_info[2] = tile_runtime->tilearray_size[0] / array_w; + tile_info[3] = tile_runtime->tilearray_size[1] / array_h; } GPUTexture *tex = GPU_texture_create_1d_array(ima->id.name + 2, width, 2, 1, GPU_RGBA32F, data); @@ -152,9 +156,12 @@ static int compare_packtile(const void *a, const void *b) return tile_a->pack_score < tile_b->pack_score; } -static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) +static GPUTexture *gpu_texture_create_tile_array(Image *ima, + ImBuf *main_ibuf, + const eImageTextureResolution texture_resolution) { - const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0; + const bool limit_gl_texture_size = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED; + const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0; int arraywidth = 0, arrayheight = 0; ListBase boxes = {NULL}; @@ -200,14 +207,15 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) LISTBASE_FOREACH (PackTile *, packtile, &packed) { ImageTile *tile = packtile->tile; - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; tileoffset[0] = packtile->boxpack.x; tileoffset[1] = packtile->boxpack.y; tilesize[0] = packtile->boxpack.w; tilesize[1] = packtile->boxpack.h; - tile->runtime.tilearray_layer = arraylayers; + tile_runtime->tilearray_layer = arraylayers; } BLI_freelistN(&packed); @@ -221,9 +229,10 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) /* Upload each tile one by one. */ LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { - int tilelayer = tile->runtime.tilearray_layer; - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int tilelayer = tile_runtime->tilearray_layer; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; if (tilesize[0] == 0 || tilesize[1] == 0) { continue; @@ -268,16 +277,33 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf) /** \name Regular gpu texture * \{ */ +static bool image_max_resolution_texture_fits_in_limited_scale(Image *ima, + eGPUTextureTarget textarget, + const int multiview_eye) +{ + BLI_assert_msg(U.glreslimit != 0, + "limited scale function called without limited scale being set."); + GPUTexture *max_resolution_texture = + ima->gputexture[textarget][multiview_eye][IMA_TEXTURE_RESOLUTION_FULL]; + if (max_resolution_texture && GPU_texture_width(max_resolution_texture) <= U.glreslimit && + GPU_texture_height(max_resolution_texture) <= U.glreslimit) { + return true; + } + return false; +} + static GPUTexture **get_image_gpu_texture_ptr(Image *ima, eGPUTextureTarget textarget, - const int multiview_eye) + const int multiview_eye, + const eImageTextureResolution texture_resolution) { const bool in_range = (textarget >= 0) && (textarget < TEXTARGET_COUNT); BLI_assert(in_range); BLI_assert(multiview_eye == 0 || multiview_eye == 1); + const int resolution = (texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED) ? 1 : 0; if (in_range) { - return &(ima->gputexture[textarget][multiview_eye]); + return &(ima->gputexture[textarget][multiview_eye][resolution]); } return NULL; } @@ -296,6 +322,21 @@ static GPUTexture *image_gpu_texture_error_create(eGPUTextureTarget textarget) } } +static void image_update_reusable_textures(Image *ima, + eGPUTextureTarget textarget, + const int multiview_eye) +{ + if ((ima->gpuflag & IMA_GPU_HAS_LIMITED_SCALE_TEXTURES) == 0) { + return; + } + + if (ELEM(textarget, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { + if (image_max_resolution_texture_fits_in_limited_scale(ima, textarget, multiview_eye)) { + image_free_gpu_limited_scale(ima); + } + } +} + static GPUTexture *image_get_gpu_texture(Image *ima, ImageUser *iuser, ImBuf *ibuf, @@ -315,24 +356,17 @@ static GPUTexture *image_get_gpu_texture(Image *ima, short requested_pass = iuser ? iuser->pass : 0; short requested_layer = iuser ? iuser->layer : 0; short requested_view = iuser ? iuser->multi_index : 0; - const bool limit_resolution = U.glreslimit != 0 && - ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) || - (iuser == NULL)); - short requested_gpu_flags = limit_resolution ? 0 : IMA_GPU_MAX_RESOLUTION; -#define GPU_FLAGS_TO_CHECK (IMA_GPU_MAX_RESOLUTION) /* There is room for 2 multiview textures. When a higher number is requested we should always * target the first view slot. This is fine as multi view images aren't used together. */ if (requested_view < 2) { requested_view = 0; } if (ima->gpu_pass != requested_pass || ima->gpu_layer != requested_layer || - ima->gpu_view != requested_view || - ((ima->gpuflag & GPU_FLAGS_TO_CHECK) != requested_gpu_flags)) { + ima->gpu_view != requested_view) { ima->gpu_pass = requested_pass; ima->gpu_layer = requested_layer; ima->gpu_view = requested_view; - ima->gpuflag &= ~GPU_FLAGS_TO_CHECK; - ima->gpuflag |= requested_gpu_flags | IMA_GPU_REFRESH; + ima->gpuflag |= IMA_GPU_REFRESH; } #undef GPU_FLAGS_TO_CHECK @@ -369,7 +403,14 @@ static GPUTexture *image_get_gpu_texture(Image *ima, if (current_view >= 2) { current_view = 0; } - GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view); + const bool limit_resolution = U.glreslimit != 0 && + ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) || + (iuser == NULL)) && + ((ima->gpuflag & IMA_GPU_REUSE_MAX_RESOLUTION) == 0); + const eImageTextureResolution texture_resolution = limit_resolution ? + IMA_TEXTURE_RESOLUTION_LIMITED : + IMA_TEXTURE_RESOLUTION_FULL; + GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view, texture_resolution); if (*tex) { return *tex; } @@ -392,22 +433,19 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } if (textarget == TEXTARGET_2D_ARRAY) { - *tex = gpu_texture_create_tile_array(ima, ibuf_intern); + *tex = gpu_texture_create_tile_array(ima, ibuf_intern, texture_resolution); } else if (textarget == TEXTARGET_TILE_MAPPING) { - *tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0); + *tex = gpu_texture_create_tile_mapping( + ima, iuser ? iuser->multiview_eye : 0, texture_resolution); } else { const bool use_high_bitdepth = (ima->flag & IMA_HIGH_BITDEPTH); const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf_intern); - const bool limit_gl_texture_size = (ima->gpuflag & IMA_GPU_MAX_RESOLUTION) == 0; - *tex = IMB_create_gpu_texture(ima->id.name + 2, - ibuf_intern, - use_high_bitdepth, - store_premultiplied, - limit_gl_texture_size); + *tex = IMB_create_gpu_texture( + ima->id.name + 2, ibuf_intern, use_high_bitdepth, store_premultiplied, limit_resolution); if (*tex) { GPU_texture_wrap_mode(*tex, true, false); @@ -425,6 +463,20 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } } + switch (texture_resolution) { + case IMA_TEXTURE_RESOLUTION_LIMITED: + ima->gpuflag |= IMA_GPU_HAS_LIMITED_SCALE_TEXTURES; + break; + + case IMA_TEXTURE_RESOLUTION_FULL: + image_update_reusable_textures(ima, textarget, current_view); + break; + + case IMA_TEXTURE_RESOLUTION_LEN: + BLI_assert_unreachable(); + break; + } + /* if `ibuf` was given, we don't own the `ibuf_intern` */ if (ibuf == NULL) { BKE_image_release_ibuf(ima, ibuf_intern, NULL); @@ -497,22 +549,39 @@ static void image_free_gpu(Image *ima, const bool immediate) { for (int eye = 0; eye < 2; eye++) { for (int i = 0; i < TEXTARGET_COUNT; i++) { - if (ima->gputexture[i][eye] != NULL) { - if (immediate) { - GPU_texture_free(ima->gputexture[i][eye]); - } - else { - BLI_mutex_lock(&gpu_texture_queue_mutex); - BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye]); - BLI_mutex_unlock(&gpu_texture_queue_mutex); + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + if (immediate) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + } + else { + BLI_mutex_lock(&gpu_texture_queue_mutex); + BLI_linklist_prepend(&gpu_texture_free_queue, ima->gputexture[i][eye][resolution]); + BLI_mutex_unlock(&gpu_texture_queue_mutex); + } + + ima->gputexture[i][eye][resolution] = NULL; } + } + } + } + + ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES); +} - ima->gputexture[i][eye] = NULL; +static void image_free_gpu_limited_scale(Image *ima) +{ + const eImageTextureResolution resolution = IMA_TEXTURE_RESOLUTION_LIMITED; + for (int eye = 0; eye < 2; eye++) { + for (int i = 0; i < TEXTARGET_COUNT; i++) { + if (ima->gputexture[i][eye][resolution] != NULL) { + GPU_texture_free(ima->gputexture[i][eye][resolution]); + ima->gputexture[i][eye][resolution] = NULL; } } } - ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE; + ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_HAS_LIMITED_SCALE_TEXTURES); } void BKE_image_free_gputextures(Image *ima) @@ -689,12 +758,21 @@ static void gpu_texture_update_unscaled(GPUTexture *tex, GPU_unpack_row_length_set(0); } -static void gpu_texture_update_from_ibuf( - GPUTexture *tex, Image *ima, ImBuf *ibuf, ImageTile *tile, int x, int y, int w, int h) +static void gpu_texture_update_from_ibuf(GPUTexture *tex, + Image *ima, + ImBuf *ibuf, + ImageTile *tile, + int x, + int y, + int w, + int h, + eImageTextureResolution texture_resolution) { + const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0; bool scaled; if (tile != NULL) { - int *tilesize = tile->runtime.tilearray_size; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tilesize = tile_runtime->tilearray_size; scaled = (ibuf->x != tilesize[0]) || (ibuf->y != tilesize[1]); } else { @@ -758,9 +836,10 @@ static void gpu_texture_update_from_ibuf( if (scaled) { /* Slower update where we first have to scale the input pixels. */ if (tile != NULL) { - int *tileoffset = tile->runtime.tilearray_offset; - int *tilesize = tile->runtime.tilearray_size; - int tilelayer = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int *tilesize = tile_runtime->tilearray_size; + int tilelayer = tile_runtime->tilearray_layer; gpu_texture_update_scaled( tex, rect, rect_float, ibuf->x, ibuf->y, x, y, tilelayer, tileoffset, tilesize, w, h); } @@ -772,8 +851,9 @@ static void gpu_texture_update_from_ibuf( else { /* Fast update at same resolution. */ if (tile != NULL) { - int *tileoffset = tile->runtime.tilearray_offset; - int tilelayer = tile->runtime.tilearray_layer; + ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; + int *tileoffset = tile_runtime->tilearray_offset; + int tilelayer = tile_runtime->tilearray_layer; gpu_texture_update_unscaled( tex, rect, rect_float, x, y, tilelayer, tileoffset, w, h, tex_stride, tex_offset); } @@ -804,16 +884,20 @@ static void gpu_texture_update_from_ibuf( static void image_update_gputexture_ex( Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h) { - GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0]; - /* Check if we need to update the main gputexture. */ - if (tex != NULL && tile == ima->tiles.first) { - gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h); - } + const int eye = 0; + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + GPUTexture *tex = ima->gputexture[TEXTARGET_2D][eye][resolution]; + eImageTextureResolution texture_resolution = resolution; + /* Check if we need to update the main gputexture. */ + if (tex != NULL && tile == ima->tiles.first) { + gpu_texture_update_from_ibuf(tex, ima, ibuf, NULL, x, y, w, h, texture_resolution); + } - /* Check if we need to update the array gputexture. */ - tex = ima->gputexture[TEXTARGET_2D_ARRAY][0]; - if (tex != NULL) { - gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h); + /* Check if we need to update the array gputexture. */ + tex = ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]; + if (tex != NULL) { + gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h, texture_resolution); + } } } @@ -917,12 +1001,14 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap) LISTBASE_FOREACH (Image *, ima, &bmain->images) { if (BKE_image_has_opengl_texture(ima)) { if (ima->gpuflag & IMA_GPU_MIPMAP_COMPLETE) { - for (int eye = 0; eye < 2; eye++) { - for (int a = 0; a < TEXTARGET_COUNT; a++) { - if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { - GPUTexture *tex = ima->gputexture[a][eye]; - if (tex != NULL) { - GPU_texture_mipmap_mode(tex, mipmap, true); + for (int a = 0; a < TEXTARGET_COUNT; a++) { + if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) { + for (int eye = 0; eye < 2; eye++) { + for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { + GPUTexture *tex = ima->gputexture[a][eye][resolution]; + if (tex != NULL) { + GPU_texture_mipmap_mode(tex, mipmap, true); + } } } } diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index aac081991e3..9b72a2d1a72 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -184,8 +184,7 @@ IDTypeInfo IDType_ID_IP = { .name = "Ipo", .name_plural = "ipos", .translation_context = "", - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index f79058dcf21..44fc86877a7 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -212,7 +212,7 @@ IDTypeInfo IDType_ID_KE = { .name = "Key", .name_plural = "shape_keys", .translation_context = BLT_I18NCONTEXT_ID_SHAPEKEY, - .flags = IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL, + .flags = IDTYPE_FLAGS_NO_LIBLINKING, .init_data = NULL, .copy_data = shapekey_copy_data, diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 5e3589c9e68..3b4d7845ad7 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -98,7 +98,7 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { .name = "LinkPlaceholder", .name_plural = "link_placeholders", .translation_context = BLT_I18NCONTEXT_ID_ID, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING, .init_data = NULL, .copy_data = NULL, @@ -336,12 +336,34 @@ void id_fake_user_clear(ID *id) } } -void BKE_id_clear_newpoin(ID *id) +void BKE_id_newptr_and_tag_clear(ID *id) { - if (id->newid) { - id->newid->tag &= ~LIB_TAG_NEW; + /* We assume that if this ID has no new ID, its embedded data has not either. */ + if (id->newid == NULL) { + return; } + + id->newid->tag &= ~LIB_TAG_NEW; id->newid = NULL; + + /* Deal with embedded data too. */ + /* NOTE: even though ShapeKeys are not technically embedded data currently, they behave as such + * in most cases, so for sake of consistency treat them as such here. Also mirrors the behavior + * in `BKE_lib_id_make_local`. */ + Key *key = BKE_key_from_id(id); + if (key != NULL) { + BKE_id_newptr_and_tag_clear(&key->id); + } + bNodeTree *ntree = ntreeFromID(id); + if (ntree != NULL) { + BKE_id_newptr_and_tag_clear(&ntree->id); + } + if (GS(id->name) == ID_SCE) { + Collection *master_collection = ((Scene *)id)->master_collection; + if (master_collection != NULL) { + BKE_id_newptr_and_tag_clear(&master_collection->id); + } + } } static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data) @@ -406,7 +428,15 @@ static void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id) */ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -416,47 +446,51 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) * we always want to localize, and we skip remapping (done later). */ - if (!ID_IS_LINKED(id)) { - return; + if (!force_copy && !force_local) { + BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + if (force_local) { + BKE_lib_id_clear_library_data(bmain, id); + BKE_lib_id_expand_local(bmain, id); + } + else if (force_copy) { + ID *id_new = BKE_id_copy(bmain, id); - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, id); - BKE_lib_id_expand_local(bmain, id); - } - else { - ID *id_new = BKE_id_copy(bmain, id); - - /* Should not fail in expected use cases, - * but a few ID types cannot be copied (LIB, WM, SCR...). */ - if (id_new != NULL) { - id_new->us = 0; - - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(id, id_new); - Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id); - if (key && key_new) { - ID_NEW_SET(key, key_new); - } - bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new); - if (ntree && ntree_new) { - ID_NEW_SET(ntree, ntree_new); - } - if (GS(id->name) == ID_SCE) { - Collection *master_collection = ((Scene *)id)->master_collection, - *master_collection_new = ((Scene *)id_new)->master_collection; - if (master_collection && master_collection_new) { - ID_NEW_SET(master_collection, master_collection_new); - } - } + /* Should not fail in expected use cases, + * but a few ID types cannot be copied (LIB, WM, SCR...). */ + if (id_new != NULL) { + id_new->us = 0; - if (!lib_local) { - BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(id, id_new); + Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id); + if (key && key_new) { + ID_NEW_SET(key, key_new); + } + bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new); + if (ntree && ntree_new) { + ID_NEW_SET(ntree, ntree_new); + } + if (GS(id->name) == ID_SCE) { + Collection *master_collection = ((Scene *)id)->master_collection, + *master_collection_new = ((Scene *)id_new)->master_collection; + if (master_collection && master_collection_new) { + ID_NEW_SET(master_collection, master_collection_new); } } + + if (!lib_local) { + BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); + } } } } @@ -468,10 +502,9 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) * * \param flags: Special flag used when making a whole library's content local, * it needs specific handling. - * - * \return true if the block can be made local. + * \return true is the ID has successfully been made local. */ -bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags) +bool BKE_lib_id_make_local(Main *bmain, ID *id, const int flags) { const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; @@ -483,23 +516,21 @@ bool BKE_lib_id_make_local(Main *bmain, ID *id, const bool test, const int flags const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id); - if (idtype_info != NULL) { - if ((idtype_info->flags & IDTYPE_FLAGS_NO_MAKELOCAL) == 0) { - if (!test) { - if (idtype_info->make_local != NULL) { - idtype_info->make_local(bmain, id, flags); - } - else { - BKE_lib_id_make_local_generic(bmain, id, flags); - } - } - return true; - } + if (idtype_info == NULL) { + BLI_assert_msg(0, "IDType Missing IDTypeInfo"); return false; } - BLI_assert_msg(0, "IDType Missing IDTypeInfo"); - return false; + BLI_assert((idtype_info->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0); + + if (idtype_info->make_local != NULL) { + idtype_info->make_local(bmain, id, flags); + } + else { + BKE_lib_id_make_local_generic(bmain, id, flags); + } + + return true; } struct IDCopyLibManagementData { @@ -1694,7 +1725,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ * * Only for local IDs (linked ones already have a unique ID in their library). * - * \param do_linked_data if true, also ensure a unique name in case the given \a id is linked + * \param do_linked_data: if true, also ensure a unique name in case the given \a id is linked * (otherwise, just ensure that it is properly sorted). * * \return true if a new name had to be created. @@ -1754,8 +1785,7 @@ void BKE_main_id_newptr_and_tag_clear(Main *bmain) ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { - id->newid = NULL; - id->tag &= ~LIB_TAG_NEW; + BKE_id_newptr_and_tag_clear(id); } FOREACH_MAIN_ID_END; } @@ -2022,11 +2052,8 @@ void BKE_library_make_local(Main *bmain, * Note that for objects, we don't want proxy pointers to be cleared yet. This will happen * down the road in this function. */ - BKE_lib_id_make_local(bmain, - id, - false, - LIB_ID_MAKELOCAL_FULL_LIBRARY | - LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BKE_lib_id_make_local( + bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); if (id->newid) { if (GS(id->newid->name) == ID_OB) { diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 8c1e04838df..3fead8b0f39 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -333,11 +333,11 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, * main. You can add more local IDs to be remapped to use new overriding ones by setting their * LIB_TAG_DOIT tag. * - * \param reference_library the library from which the linked data being overridden come from + * \param reference_library: the library from which the linked data being overridden come from * (i.e. the library of the linked reference ID). * - * \param do_no_main Create the new override data outside of Main database. Used for resyncing of - * linked overrides. + * \param do_no_main: Create the new override data outside of Main database. + * Used for resyncing of linked overrides. * * \return \a true on success, \a false otherwise. */ @@ -901,7 +901,7 @@ static void lib_override_library_create_post_process(Main *bmain, * \param id_reference: Some reference ID used to do some post-processing after overrides have been * created, may be NULL. Typically, the Empty object instantiating the linked collection we * override, currently. - * \param r_id_root_override if not NULL, the override generated for the given \a id_root. + * \param r_id_root_override: if not NULL, the override generated for the given \a id_root. * \return true if override was successfully created. */ bool BKE_lib_override_library_create(Main *bmain, diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index 36cbf35b251..2ac92828cec 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -720,9 +720,9 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain, * Valid usages here are defined as ref-counting usages, which are not towards embedded or * loop-back data. * - * \param r_num_tagged If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers. - * Number of tagged-as-unused IDs is then set for each type, and as total in - * #INDEX_ID_NULL item. + * \param r_num_tagged: If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers. + * Number of tagged-as-unused IDs is then set for each type, and as total in + * #INDEX_ID_NULL item. */ void BKE_lib_query_unused_ids_tag(Main *bmain, const int tag, diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index bba15a3bcdf..250b8d4d515 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -699,6 +699,9 @@ static int id_relink_to_newid_looper(LibraryIDLinkCallbackData *cb_data) * * Very specific usage, not sure we'll keep it on the long run, * currently only used in Object/Collection duplication code... + * + * WARNING: This is a deprecated version of this function, should not be used by new code. See + * #BKE_libblock_relink_to_newid_new below. */ void BKE_libblock_relink_to_newid(ID *id) { @@ -708,3 +711,53 @@ void BKE_libblock_relink_to_newid(ID *id) BKE_library_foreach_ID_link(NULL, id, id_relink_to_newid_looper, NULL, 0); } + +/* ************************ + * FIXME: Port all usages of #BKE_libblock_relink_to_newid to this + * #BKE_libblock_relink_to_newid_new new code and remove old one. + ************************** */ +static int id_relink_to_newid_looper_new(LibraryIDLinkCallbackData *cb_data) +{ + const int cb_flag = cb_data->cb_flag; + if (cb_flag & IDWALK_CB_EMBEDDED) { + return IDWALK_RET_NOP; + } + + Main *bmain = cb_data->bmain; + ID *id_owner = cb_data->id_owner; + ID **id_pointer = cb_data->id_pointer; + ID *id = *id_pointer; + if (id) { + /* See: NEW_ID macro */ + if (id->newid != NULL) { + BKE_libblock_relink_ex(bmain, id_owner, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE); + id = id->newid; + } + if (id->tag & LIB_TAG_NEW) { + id->tag &= ~LIB_TAG_NEW; + BKE_libblock_relink_to_newid_new(bmain, id); + } + } + return IDWALK_RET_NOP; +} + +/** + * Remaps ID usages of given ID to their `id->newid` pointer if not None, and proceeds recursively + * in the dependency tree of IDs for all data-blocks tagged with `LIB_TAG_NEW`. + * + * NOTE: `LIB_TAG_NEW` is cleared + * + * Very specific usage, not sure we'll keep it on the long run, + * currently only used in Object/Collection duplication code... + */ +void BKE_libblock_relink_to_newid_new(Main *bmain, ID *id) +{ + if (ID_IS_LINKED(id)) { + return; + } + /* We do not want to have those cached relationship data here. */ + BLI_assert(bmain->relations == NULL); + + id->tag &= ~LIB_TAG_NEW; + BKE_library_foreach_ID_link(bmain, id, id_relink_to_newid_looper_new, NULL, 0); +} diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 07a3396ad5f..36958e36004 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -68,8 +68,7 @@ IDTypeInfo IDType_ID_LI = { .name = "Library", .name_plural = "libraries", .translation_context = BLT_I18NCONTEXT_ID_LIBRARY, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index bb33f5f9f87..981f1d4a623 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -405,11 +405,8 @@ ImBuf *BKE_main_thumbnail_to_imbuf(Main *bmain, BlendThumbnail *data) } if (data) { - /* NOTE: we cannot use IMB_allocFromBuffer(), since it tries to dupalloc passed buffer, - * which will fail here (we do not want to pass the first two ints!). */ - img = IMB_allocImBuf( - (unsigned int)data->width, (unsigned int)data->height, 32, IB_rect | IB_metadata); - memcpy(img->rect, data->rect, BLEN_THUMB_MEMSIZE(data->width, data->height) - sizeof(*data)); + img = IMB_allocFromBuffer( + (const uint *)data->rect, NULL, (uint)data->width, (uint)data->height, 4); } return img; diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c index a24b17794c6..11291cf2b03 100644 --- a/source/blender/blenkernel/intern/mesh.c +++ b/source/blender/blenkernel/intern/mesh.c @@ -160,6 +160,14 @@ static void mesh_free_data(ID *id) BLI_freelistN(&mesh->vertex_group_names); + if (mesh->edit_mesh) { + if (mesh->edit_mesh->is_shallow_copy == false) { + BKE_editmesh_free_data(mesh->edit_mesh); + } + MEM_freeN(mesh->edit_mesh); + mesh->edit_mesh = NULL; + } + BKE_mesh_runtime_clear_cache(mesh); mesh_clear_geometry(mesh); MEM_SAFE_FREE(mesh->mat); @@ -880,6 +888,18 @@ void BKE_mesh_free_data_for_undo(Mesh *me) mesh_free_data(&me->id); } +/** + * \note on data that this function intentionally doesn't free: + * + * - Materials and shape keys are not freed here (#Mesh.mat & #Mesh.key). + * As freeing shape keys requires tagging the depsgraph for updated relations, + * which is expensive. + * Material slots should be kept in sync with the object. + * + * - Edit-Mesh (#Mesh.edit_mesh) + * Since edit-mesh is tied to the objects mode, + * which crashes when called in edit-mode, see: T90972. + */ static void mesh_clear_geometry(Mesh *mesh) { CustomData_free(&mesh->vdata, mesh->totvert); @@ -889,11 +909,6 @@ static void mesh_clear_geometry(Mesh *mesh) CustomData_free(&mesh->pdata, mesh->totpoly); MEM_SAFE_FREE(mesh->mselect); - MEM_SAFE_FREE(mesh->edit_mesh); - - /* Note that materials and shape keys are not freed here. This is intentional, as freeing - * shape keys requires tagging the depsgraph for updated relations, which is expensive. - * Material slots should be kept in sync with the object. */ mesh->totvert = 0; mesh->totedge = 0; @@ -1851,7 +1866,7 @@ void BKE_mesh_vert_coords_apply(Mesh *mesh, const float (*vert_coords)[3]) for (int i = 0; i < mesh->totvert; i++, mv++) { copy_v3_v3(mv->co, vert_coords[i]); } - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, @@ -1864,7 +1879,7 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, for (int i = 0; i < mesh->totvert; i++, mv++) { mul_v3_m4v3(mv->co, mat, vert_coords[i]); } - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } void BKE_mesh_vert_normals_apply(Mesh *mesh, const short (*vert_normals)[3]) diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index d5524312612..50c80f8d0a4 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -483,8 +483,24 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, return 0; } +/** + * Copy evaluated texture space from curve to mesh. + * + * \note We disable auto texture space feature since that will cause texture space to evaluate + * differently for curve and mesh, since curves use control points and handles to calculate the + * bounding box, and mesh uses the tessellated curve. + */ +static void mesh_copy_texture_space_from_curve_type(const Curve *cu, Mesh *me) +{ + me->texflag = cu->texflag & ~CU_AUTOSPACE; + copy_v3_v3(me->loc, cu->loc); + copy_v3_v3(me->size, cu->size); + BKE_mesh_texspace_calc(me); +} + Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase *dispbase) { + const Curve *cu = ob->data; Mesh *mesh; MVert *allvert; MEdge *alledge; @@ -493,7 +509,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * MLoopUV *alluv = NULL; int totvert, totedge, totloop, totpoly; - if (mesh_nurbs_displist_to_mdata(ob->data, + if (mesh_nurbs_displist_to_mdata(cu, dispbase, &allvert, &totvert, @@ -509,7 +525,7 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * } mesh = BKE_mesh_new_nomain(totvert, totedge, 0, totloop, totpoly); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); if (totvert != 0) { memcpy(mesh->mvert, allvert, totvert * sizeof(MVert)); @@ -529,6 +545,12 @@ Mesh *BKE_mesh_new_nomain_from_curve_displist(const Object *ob, const ListBase * CustomData_add_layer_named(&mesh->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, totloop, uvname); } + mesh_copy_texture_space_from_curve_type(cu, mesh); + + /* Copy curve materials. */ + mesh->mat = (Material **)MEM_dupallocN(cu->mat); + mesh->totcol = cu->totcol; + MEM_freeN(allvert); MEM_freeN(alledge); MEM_freeN(allloop); @@ -612,17 +634,7 @@ static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char me->totcol = cu->totcol; me->mat = cu->mat; - /* Copy evaluated texture space from curve to mesh. - * - * Note that we disable auto texture space feature since that will cause - * texture space to evaluate differently for curve and mesh, since curve - * uses CV to calculate bounding box, and mesh uses what is coming from - * tessellated curve. - */ - me->texflag = cu->texflag & ~CU_AUTOSPACE; - copy_v3_v3(me->loc, cu->loc); - copy_v3_v3(me->size, cu->size); - BKE_mesh_texspace_calc(me); + mesh_copy_texture_space_from_curve_type(cu, me); cu->mat = NULL; cu->totcol = 0; diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c index fe6af432314..bc1ffeb8cf4 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.c +++ b/source/blender/blenkernel/intern/mesh_wrapper.c @@ -69,7 +69,9 @@ Mesh *BKE_mesh_wrapper_from_editmesh_with_coords(BMEditMesh *em, /* Use edit-mesh directly where possible. */ me->runtime.is_original = true; + me->edit_mesh = MEM_dupallocN(em); + me->edit_mesh->is_shallow_copy = true; /* Make sure, we crash if these are ever used. */ #ifdef DEBUG diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index b328a31cda5..b55b02c7bf2 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -1473,7 +1473,6 @@ void BKE_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb, Object fmd->domain->tex_velocity_y = NULL; fmd->domain->tex_velocity_z = NULL; fmd->domain->tex_wt = NULL; - fmd->domain->mesh_velocities = NULL; BLO_read_data_address(reader, &fmd->domain->coba); BLO_read_data_address(reader, &fmd->domain->effector_weights); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 48d747e2bf3..0d691854c1d 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -511,7 +511,8 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } - else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) { + else if ((ntree->type == NTREE_GEOMETRY) && + (node->type == GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP)) { BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec); @@ -652,6 +653,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BLO_read_list(reader, &ntree->nodes); LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { node->typeinfo = nullptr; + node->declaration = nullptr; BLO_read_list(reader, &node->inputs); BLO_read_list(reader, &node->outputs); @@ -689,7 +691,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage); break; } - case GEO_NODE_ATTRIBUTE_CURVE_MAP: { + case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: { NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; BLO_read_data_address(reader, &data->curve_vec); if (data->curve_vec) { @@ -1013,10 +1015,8 @@ IDTypeInfo IDType_ID_NT = { static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype) { if (ntype->declare != nullptr) { - blender::nodes::NodeDeclaration node_decl; - blender::nodes::NodeDeclarationBuilder builder{node_decl}; - ntype->declare(builder); - node_decl.build(*ntree, *node); + nodeDeclarationEnsure(ntree, node); + node->declaration->build(*ntree, *node); return; } bNodeSocketTemplate *sockdef; @@ -2215,6 +2215,10 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, bNodeLink *link_dst, *link_src; *node_dst = *node_src; + + /* Reset the declaration of the new node. */ + node_dst->declaration = nullptr; + /* can be called for nodes outside a node tree (e.g. clipboard) */ if (ntree) { if (unique_name) { @@ -3102,6 +3106,8 @@ static void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node->prop); } + delete node->declaration; + MEM_freeN(node); if (ntree) { @@ -3888,7 +3894,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) } } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || - (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) { + (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { tnode->flag &= ~NODE_ACTIVE_TEXTURE; } } @@ -3898,7 +3904,7 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) node->flag |= NODE_ACTIVE_ID; } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || - (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) { + (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { node->flag |= NODE_ACTIVE_TEXTURE; } } @@ -3932,6 +3938,21 @@ int nodeSocketLinkLimit(const bNodeSocket *sock) return sock->limit; } +/** + * If the node implements a `declare` function, this function makes sure that `node->declaration` + * is up to date. + */ +void nodeDeclarationEnsure(bNodeTree *UNUSED(ntree), bNode *node) +{ + if (node->typeinfo->declare == nullptr) { + return; + } + + node->declaration = new blender::nodes::NodeDeclaration(); + blender::nodes::NodeDeclarationBuilder builder{*node->declaration}; + node->typeinfo->declare(builder); +} + /* ************** Node Clipboard *********** */ #define USE_NODE_CB_VALIDATE @@ -4919,6 +4940,7 @@ static void registerCompositNodes() register_node_type_cmp_inpaint(); register_node_type_cmp_despeckle(); register_node_type_cmp_defocus(); + register_node_type_cmp_posterize(); register_node_type_cmp_sunbeams(); register_node_type_cmp_denoise(); register_node_type_cmp_antialiasing(); @@ -5131,6 +5153,9 @@ static void registerGeometryNodes() { register_node_type_geo_group(); + register_node_type_geo_legacy_material_assign(); + register_node_type_geo_legacy_select_by_material(); + register_node_type_geo_align_rotation_to_vector(); register_node_type_geo_attribute_clamp(); register_node_type_geo_attribute_color_ramp(); @@ -5139,6 +5164,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_convert(); register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); + register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); register_node_type_geo_attribute_mix(); @@ -5173,7 +5199,10 @@ static void registerGeometryNodes() register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); register_node_type_geo_edge_split(); + register_node_type_geo_input_index(); register_node_type_geo_input_material(); + register_node_type_geo_input_normal(); + register_node_type_geo_input_position(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_assign(); @@ -5199,8 +5228,9 @@ static void registerGeometryNodes() register_node_type_geo_raycast(); register_node_type_geo_sample_texture(); register_node_type_geo_select_by_handle_type(); - register_node_type_geo_select_by_material(); + register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); + register_node_type_geo_set_position(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); register_node_type_geo_transform(); diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index c91cf6ed926..465ec9dc665 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -324,9 +324,17 @@ static void object_free_data(ID *id) static void object_make_local(Main *bmain, ID *id, const int flags) { + if (!ID_IS_LINKED(id)) { + return; + } + Object *ob = (Object *)id; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; const bool clear_proxy = (flags & LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING) == 0; + bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; + bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; + BLI_assert(force_copy == false || force_copy != force_local); + bool is_local = false, is_lib = false; /* - only lib users: do nothing (unless force_local is set) @@ -336,36 +344,40 @@ static void object_make_local(Main *bmain, ID *id, const int flags) * we always want to localize, and we skip remapping (done later). */ - if (!ID_IS_LINKED(ob)) { - return; + if (!force_local && !force_copy) { + BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib); + if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; + } + } } - BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib); - - if (lib_local || is_local) { - if (!is_lib) { - BKE_lib_id_clear_library_data(bmain, &ob->id); - BKE_lib_id_expand_local(bmain, &ob->id); - if (clear_proxy) { - if (ob->proxy_from != NULL) { - ob->proxy_from->proxy = NULL; - ob->proxy_from->proxy_group = NULL; - } - ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + if (force_local) { + BKE_lib_id_clear_library_data(bmain, &ob->id); + BKE_lib_id_expand_local(bmain, &ob->id); + if (clear_proxy) { + if (ob->proxy_from != NULL) { + ob->proxy_from->proxy = NULL; + ob->proxy_from->proxy_group = NULL; } + ob->proxy = ob->proxy_from = ob->proxy_group = NULL; } - else { - Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id); - id_us_min(&ob_new->id); + } + else if (force_copy) { + Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id); + id_us_min(&ob_new->id); - ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL; + ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL; - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(ob, ob_new); + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(ob, ob_new); - if (!lib_local) { - BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE); - } + if (!lib_local) { + BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE); } } } @@ -1329,6 +1341,11 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) { const ModifierTypeInfo *mti = BKE_modifier_get_info(modifier_type); + /* Surface and lattice objects don't output geometry sets. */ + if (mti->modifyGeometrySet != NULL && ELEM(ob->type, OB_SURF, OB_LATTICE)) { + return false; + } + /* Only geometry objects should be able to get modifiers T25291. */ if (ob->type == OB_HAIR) { return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); @@ -1979,8 +1996,7 @@ int BKE_object_visibility(const Object *ob, const int dag_eval_mode) visibility |= OB_VISIBLE_INSTANCES; } - if (ob->runtime.geometry_set_eval != NULL && - BKE_geometry_set_has_instances(ob->runtime.geometry_set_eval)) { + if (BKE_object_has_geometry_set_instances(ob)) { visibility |= OB_VISIBLE_INSTANCES; } @@ -3307,8 +3323,8 @@ static void ob_parbone(Object *ob, Object *par, float r_mat[4][4]) /* Make sure the bone is still valid */ bPoseChannel *pchan = BKE_pose_channel_find_name(par->pose, ob->parsubstr); if (!pchan || !pchan->bone) { - CLOG_ERROR( - &LOG, "Object %s with Bone parent: bone %s doesn't exist", ob->id.name + 2, ob->parsubstr); + CLOG_WARN( + &LOG, "Parent Bone: '%s' for Object: '%s' doesn't exist", ob->parsubstr, ob->id.name + 2); unit_m4(r_mat); return; } @@ -5307,7 +5323,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot) unsigned int i; Mesh *me_eval = ob->runtime.mesh_deform_eval ? ob->runtime.mesh_deform_eval : - ob->runtime.mesh_deform_eval; + BKE_object_get_evaluated_mesh(ob); const int *index; if (me_eval && (index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) { @@ -5737,3 +5753,21 @@ void BKE_object_modifiers_lib_link_common(void *userData, id_us_plus_no_lib(*idpoin); } } + +void BKE_object_replace_data_on_shallow_copy(Object *ob, ID *new_data) +{ + ob->type = BKE_object_obdata_to_type(new_data); + ob->data = new_data; + ob->runtime.geometry_set_eval = NULL; + ob->runtime.data_eval = NULL; + if (ob->runtime.bb != NULL) { + ob->runtime.bb->flag |= BOUNDBOX_DIRTY; + } + ob->id.py_instance = NULL; +} + +bool BKE_object_supports_material_slots(struct Object *ob) +{ + return ELEM( + ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_MBALL, OB_HAIR, OB_POINTCLOUD, OB_VOLUME); +} diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index a46ac4b1175..04739ec19d3 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -194,6 +194,7 @@ static DupliObject *make_dupli(const DupliContext *ctx, } dob->ob = ob; + dob->ob_data = (ID *)ob->data; mul_m4_m4m4(dob->mat, (float(*)[4])ctx->space_mat, mat); dob->type = ctx->gen->type; @@ -834,14 +835,59 @@ static const DupliGenerator gen_dupli_verts_pointcloud = { /** \name Instances Geometry Component Implementation * \{ */ -static void make_duplis_instances_component(const DupliContext *ctx) +static void make_duplis_geometry_set_impl(const DupliContext *ctx, + const GeometrySet &geometry_set, + const float parent_transform[4][4], + bool geometry_set_is_instance) { - const InstancesComponent *component = - ctx->object->runtime.geometry_set_eval->get_component_for_read<InstancesComponent>(); + int component_index = 0; + if (ctx->object->type != OB_MESH || geometry_set_is_instance) { + const Mesh *mesh = geometry_set.get_mesh_for_read(); + if (mesh != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)mesh; + } + } + if (ctx->object->type != OB_VOLUME || geometry_set_is_instance) { + const Volume *volume = geometry_set.get_volume_for_read(); + if (volume != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)volume; + } + } + if (!ELEM(ctx->object->type, OB_CURVE, OB_FONT) || geometry_set_is_instance) { + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + if (curve_component != nullptr) { + const Curve *curve = curve_component->get_curve_for_render(); + if (curve != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)curve; + } + } + } + if (ctx->object->type != OB_POINTCLOUD || geometry_set_is_instance) { + const PointCloud *pointcloud = geometry_set.get_pointcloud_for_read(); + if (pointcloud != nullptr) { + DupliObject *dupli = make_dupli(ctx, ctx->object, parent_transform, component_index++); + dupli->ob_data = (ID *)pointcloud; + } + } + const bool creates_duplis_for_components = component_index >= 1; + + const InstancesComponent *component = geometry_set.get_component_for_read<InstancesComponent>(); if (component == nullptr) { return; } + const DupliContext *instances_ctx = ctx; + /* Create a sub-context if some duplis were created above. This is to avoid dupli id collisions + * between the instances component below and the other components above. */ + DupliContext new_instances_ctx; + if (creates_duplis_for_components) { + copy_dupli_context(&new_instances_ctx, ctx, ctx->object, nullptr, component_index); + instances_ctx = &new_instances_ctx; + } + Span<float4x4> instance_offset_matrices = component->instance_transforms(); Span<int> instance_reference_handles = component->instance_reference_handles(); Span<int> almost_unique_ids = component->almost_unique_ids(); @@ -855,13 +901,13 @@ static void make_duplis_instances_component(const DupliContext *ctx) case InstanceReference::Type::Object: { Object &object = reference.object(); float matrix[4][4]; - mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values); - make_dupli(ctx, &object, matrix, id); + mul_m4_m4m4(matrix, parent_transform, instance_offset_matrices[i].values); + make_dupli(instances_ctx, &object, matrix, id); float space_matrix[4][4]; mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat); - mul_m4_m4_pre(space_matrix, ctx->object->obmat); - make_recursive_duplis(ctx, &object, space_matrix, id); + mul_m4_m4_pre(space_matrix, parent_transform); + make_recursive_duplis(instances_ctx, &object, space_matrix, id); break; } case InstanceReference::Type::Collection: { @@ -870,23 +916,36 @@ static void make_duplis_instances_component(const DupliContext *ctx) unit_m4(collection_matrix); sub_v3_v3(collection_matrix[3], collection.instance_offset); mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values); - mul_m4_m4_pre(collection_matrix, ctx->object->obmat); + mul_m4_m4_pre(collection_matrix, parent_transform); + + DupliContext sub_ctx; + copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id); - eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); + eEvaluationMode mode = DEG_get_mode(instances_ctx->depsgraph); + int object_id = 0; FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) { - if (object == ctx->object) { + if (object == instances_ctx->object) { continue; } float instance_matrix[4][4]; mul_m4_m4m4(instance_matrix, collection_matrix, object->obmat); - make_dupli(ctx, object, instance_matrix, id); - make_recursive_duplis(ctx, object, collection_matrix, id); + make_dupli(&sub_ctx, object, instance_matrix, object_id++); + make_recursive_duplis(&sub_ctx, object, collection_matrix, object_id++); } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; break; } + case InstanceReference::Type::GeometrySet: { + float new_transform[4][4]; + mul_m4_m4m4(new_transform, parent_transform, instance_offset_matrices[i].values); + + DupliContext sub_ctx; + copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id); + make_duplis_geometry_set_impl(&sub_ctx, reference.geometry_set(), new_transform, true); + break; + } case InstanceReference::Type::None: { break; } @@ -894,9 +953,15 @@ static void make_duplis_instances_component(const DupliContext *ctx) } } -static const DupliGenerator gen_dupli_instances_component = { +static void make_duplis_geometry_set(const DupliContext *ctx) +{ + const GeometrySet *geometry_set = ctx->object->runtime.geometry_set_eval; + make_duplis_geometry_set_impl(ctx, *geometry_set, ctx->object->obmat, false); +} + +static const DupliGenerator gen_dupli_geometry_set = { 0, - make_duplis_instances_component, + make_duplis_geometry_set, }; /** \} */ @@ -1567,8 +1632,8 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) } if (ctx->object->runtime.geometry_set_eval != nullptr) { - if (BKE_geometry_set_has_instances(ctx->object->runtime.geometry_set_eval)) { - return &gen_dupli_instances_component; + if (BKE_object_has_geometry_set_instances(ctx->object)) { + return &gen_dupli_geometry_set; } } diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c index 78e7e11c248..baff1bb47cc 100644 --- a/source/blender/blenkernel/intern/packedFile.c +++ b/source/blender/blenkernel/intern/packedFile.c @@ -576,26 +576,42 @@ static void unpack_generate_paths(const char *name, } } +char *BKE_packedfile_unpack(Main *bmain, + ReportList *reports, + ID *id, + const char *orig_file_path, + PackedFile *pf, + enum ePF_FileStatus how) +{ + char localname[FILE_MAX], absname[FILE_MAX]; + char *new_name = NULL; + + if (id != NULL) { + unpack_generate_paths( + orig_file_path, id, absname, localname, sizeof(absname), sizeof(localname)); + new_name = BKE_packedfile_unpack_to_file( + reports, BKE_main_blendfile_path(bmain), absname, localname, pf, how); + } + + return new_name; +} + int BKE_packedfile_unpack_vfont(Main *bmain, ReportList *reports, VFont *vfont, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; int ret_value = RET_ERROR; + if (vfont) { + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)vfont, vfont->filepath, vfont->packedfile, how); - if (vfont != NULL) { - unpack_generate_paths( - vfont->filepath, (ID *)vfont, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, vfont->packedfile, how); - if (newname != NULL) { + if (new_file_path != NULL) { ret_value = RET_OK; BKE_packedfile_free(vfont->packedfile); vfont->packedfile = NULL; - BLI_strncpy(vfont->filepath, newname, sizeof(vfont->filepath)); - MEM_freeN(newname); + BLI_strncpy(vfont->filepath, new_file_path, sizeof(vfont->filepath)); + MEM_freeN(new_file_path); } } @@ -607,18 +623,14 @@ int BKE_packedfile_unpack_sound(Main *bmain, bSound *sound, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; int ret_value = RET_ERROR; if (sound != NULL) { - unpack_generate_paths( - sound->filepath, (ID *)sound, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, sound->packedfile, how); - if (newname != NULL) { - BLI_strncpy(sound->filepath, newname, sizeof(sound->filepath)); - MEM_freeN(newname); + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)sound, sound->filepath, sound->packedfile, how); + if (new_file_path != NULL) { + BLI_strncpy(sound->filepath, new_file_path, sizeof(sound->filepath)); + MEM_freeN(new_file_path); BKE_packedfile_free(sound->packedfile); sound->packedfile = NULL; @@ -641,16 +653,11 @@ int BKE_packedfile_unpack_image(Main *bmain, if (ima != NULL) { while (ima->packedfiles.last) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newname; ImagePackedFile *imapf = ima->packedfiles.last; + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)ima, imapf->filepath, imapf->packedfile, how); - unpack_generate_paths( - imapf->filepath, (ID *)ima, absname, localname, sizeof(absname), sizeof(localname)); - newname = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, imapf->packedfile, how); - - if (newname != NULL) { + if (new_file_path != NULL) { ImageView *iv; ret_value = ret_value == RET_ERROR ? RET_ERROR : RET_OK; @@ -660,14 +667,14 @@ int BKE_packedfile_unpack_image(Main *bmain, /* update the new corresponding view filepath */ iv = BLI_findstring(&ima->views, imapf->filepath, offsetof(ImageView, filepath)); if (iv) { - BLI_strncpy(iv->filepath, newname, sizeof(imapf->filepath)); + BLI_strncpy(iv->filepath, new_file_path, sizeof(imapf->filepath)); } /* keep the new name in the image for non-pack specific reasons */ if (how != PF_REMOVE) { - BLI_strncpy(ima->filepath, newname, sizeof(imapf->filepath)); + BLI_strncpy(ima->filepath, new_file_path, sizeof(imapf->filepath)); } - MEM_freeN(newname); + MEM_freeN(new_file_path); } else { ret_value = RET_ERROR; @@ -690,18 +697,14 @@ int BKE_packedfile_unpack_volume(Main *bmain, Volume *volume, enum ePF_FileStatus how) { - char localname[FILE_MAX], absname[FILE_MAX]; - char *newfilepath; int ret_value = RET_ERROR; if (volume != NULL) { - unpack_generate_paths( - volume->filepath, (ID *)volume, absname, localname, sizeof(absname), sizeof(localname)); - newfilepath = BKE_packedfile_unpack_to_file( - reports, BKE_main_blendfile_path(bmain), absname, localname, volume->packedfile, how); - if (newfilepath != NULL) { - BLI_strncpy(volume->filepath, newfilepath, sizeof(volume->filepath)); - MEM_freeN(newfilepath); + char *new_file_path = BKE_packedfile_unpack( + bmain, reports, (ID *)volume, volume->filepath, volume->packedfile, how); + if (new_file_path != NULL) { + BLI_strncpy(volume->filepath, new_file_path, sizeof(volume->filepath)); + MEM_freeN(new_file_path); BKE_packedfile_free(volume->packedfile); volume->packedfile = NULL; diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 065240bddbc..73e25a22225 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -312,7 +312,7 @@ IDTypeInfo IDType_ID_SCR = { .name = "Screen", .name_plural = "screens", .translation_context = BLT_I18NCONTEXT_ID_SCREEN, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 732fabc6582..a8871777420 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -19,6 +19,8 @@ #include "BLI_task.hh" #include "BLI_timeit.hh" +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" #include "BKE_spline.hh" #include "FN_generic_virtual_array.hh" @@ -28,6 +30,8 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::attribute_math::convert_to_static_type; +using blender::bke::AttributeIDRef; using blender::fn::GMutableSpan; using blender::fn::GSpan; using blender::fn::GVArray; @@ -110,6 +114,31 @@ void Spline::transform(const blender::float4x4 &matrix) this->mark_cache_invalid(); } +void Spline::reverse() +{ + this->positions().reverse(); + this->radii().reverse(); + this->tilts().reverse(); + + this->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + std::optional<blender::fn::GMutableSpan> attribute = this->attributes.get_for_write(id); + if (!attribute) { + BLI_assert_unreachable(); + return false; + } + convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + attribute->typed<T>().reverse(); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + this->reverse_impl(); + this->mark_cache_invalid(); +} + int Spline::evaluated_edges_size() const { const int eval_size = this->evaluated_points_size(); diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index b6764f65631..79d2137ee84 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -166,6 +166,17 @@ MutableSpan<float3> BezierSpline::handle_positions_right() return handle_positions_right_; } +void BezierSpline::reverse_impl() +{ + this->handle_positions_left().reverse(); + this->handle_positions_right().reverse(); + std::swap(this->handle_positions_left_, this->handle_positions_right_); + + this->handle_types_left().reverse(); + this->handle_types_right().reverse(); + std::swap(this->handle_types_left_, this->handle_types_right_); +} + static float3 previous_position(Span<float3> positions, const bool cyclic, const int i) { if (i == 0) { diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index ac6f1bd082c..6d30d8ba916 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -142,6 +142,11 @@ Span<float> NURBSpline::weights() const return weights_; } +void NURBSpline::reverse_impl() +{ + this->weights().reverse(); +} + void NURBSpline::mark_cache_invalid() { basis_cache_dirty_ = true; diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index dfd24b2566e..338b5d0ac9e 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -91,6 +91,10 @@ Span<float> PolySpline::tilts() const return tilts_; } +void PolySpline::reverse_impl() +{ +} + void PolySpline::mark_cache_invalid() { tangent_cache_dirty_ = true; diff --git a/source/blender/blenkernel/intern/studiolight.c b/source/blender/blenkernel/intern/studiolight.c index 95436372a65..29f726ece71 100644 --- a/source/blender/blenkernel/intern/studiolight.c +++ b/source/blender/blenkernel/intern/studiolight.c @@ -439,17 +439,15 @@ static void studiolight_load_equirect_image(StudioLight *sl) if (ctx.diffuse_pass != NULL) { float *converted_pass = studiolight_multilayer_convert_pass( ibuf, ctx.diffuse_pass, ctx.num_diffuse_channels); - diffuse_ibuf = IMB_allocFromBuffer( + diffuse_ibuf = IMB_allocFromBufferOwn( NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_diffuse_channels); - MEM_freeN(converted_pass); } if (ctx.specular_pass != NULL) { float *converted_pass = studiolight_multilayer_convert_pass( ibuf, ctx.specular_pass, ctx.num_specular_channels); - specular_ibuf = IMB_allocFromBuffer( + specular_ibuf = IMB_allocFromBufferOwn( NULL, converted_pass, ibuf->x, ibuf->y, ctx.num_specular_channels); - MEM_freeN(converted_pass); } IMB_exr_close(ibuf->userdata); @@ -1148,12 +1146,11 @@ static void studiolight_calculate_irradiance_equirect_image(StudioLight *sl) } ITER_PIXELS_END; - sl->equirect_irradiance_buffer = IMB_allocFromBuffer(NULL, - colbuf, - STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH, - STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT, - 4); - MEM_freeN(colbuf); + sl->equirect_irradiance_buffer = IMB_allocFromBufferOwn(NULL, + colbuf, + STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH, + STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT, + 4); } sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED; } diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index da6ee8d8779..e9cd0b70019 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -1232,7 +1232,7 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv, // BKE_mesh_validate(result, true, true); BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH); if (!subdiv_context.can_evaluate_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } /* Free used memory. */ subdiv_mesh_context_free(&subdiv_context); diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c index 0ca2b97b4ef..db5184edfd2 100644 --- a/source/blender/blenkernel/intern/undo_system.c +++ b/source/blender/blenkernel/intern/undo_system.c @@ -744,16 +744,15 @@ static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStep /** * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. * - * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` will become the - * active step. + * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` + * will become the active step. * - * \note In case `use_skip` is true, the final target will always be **beyond** the given one (if - * the given one has to be skipped). + * \note In case `use_skip` is true, the final target will always be **beyond** the given one + * (if the given one has to be skipped). * - * \param us_reference If NULL, will be set to current active step in the undo stack. Otherwise, it - * is assumed to match the current state, and will be used as basis for the - * undo/redo process (i.e. all steps in-between `us_reference` and `us_target` - * will be processed). + * \param us_reference: If NULL, will be set to current active step in the undo stack. Otherwise, + * it is assumed to match the current state, and will be used as basis for the undo/redo process + * (i.e. all steps in-between `us_reference` and `us_target` will be processed). */ bool BKE_undosys_step_load_data_ex(UndoStack *ustack, bContext *C, diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c index 329633c6759..3c168a6c7b2 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -186,7 +186,7 @@ IDTypeInfo IDType_ID_WS = { .name = "WorkSpace", .name_plural = "workspaces", .translation_context = BLT_I18NCONTEXT_ID_WORKSPACE, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_MAKELOCAL | IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = workspace_init_data, .copy_data = NULL, diff --git a/source/blender/blenlib/BLI_float4.hh b/source/blender/blenlib/BLI_float4.hh new file mode 100644 index 00000000000..b1feee3121b --- /dev/null +++ b/source/blender/blenlib/BLI_float4.hh @@ -0,0 +1,86 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +namespace blender { + +struct float4 { + float x, y, z, w; + + float4() = default; + + float4(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}, w{ptr[3]} + { + } + + explicit float4(float value) : x(value), y(value), z(value), w(value) + { + } + + explicit float4(int value) : x(value), y(value), z(value), w(value) + { + } + + float4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) + { + } + + operator float *() + { + return &x; + } + + operator const float *() const + { + return &x; + } + + float4 &operator+=(const float4 &other) + { + x += other.x; + y += other.y; + z += other.z; + w += other.w; + return *this; + } + + friend float4 operator+(const float4 &a, const float4 &b) + { + return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; + } + + float4 &operator*=(float factor) + { + x *= factor; + y *= factor; + z *= factor; + w *= factor; + return *this; + } + + friend float4 operator*(const float4 &a, float b) + { + return {a.x * b, a.y * b, a.z * b, a.w * b}; + } + + friend float4 operator*(float a, const float4 &b) + { + return b * a; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_hash.hh b/source/blender/blenlib/BLI_hash.hh index fbed321534c..11ff7d040aa 100644 --- a/source/blender/blenlib/BLI_hash.hh +++ b/source/blender/blenlib/BLI_hash.hh @@ -250,6 +250,20 @@ template<typename T> struct DefaultHash<std::unique_ptr<T>> { } }; +template<typename T> struct DefaultHash<std::shared_ptr<T>> { + uint64_t operator()(const std::shared_ptr<T> &value) const + { + return get_default_hash(value.get()); + } +}; + +template<typename T> struct DefaultHash<std::reference_wrapper<T>> { + uint64_t operator()(const std::reference_wrapper<T> &value) const + { + return get_default_hash(value.get()); + } +}; + template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> { uint64_t operator()(const std::pair<T1, T2> &value) const { diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh index 7a3169520ca..ad030e127fe 100644 --- a/source/blender/blenlib/BLI_index_mask.hh +++ b/source/blender/blenlib/BLI_index_mask.hh @@ -39,6 +39,7 @@ #include "BLI_index_range.hh" #include "BLI_span.hh" +#include "BLI_vector.hh" namespace blender { @@ -221,6 +222,8 @@ class IndexMask { { return indices_.is_empty(); } + + IndexMask slice_and_offset(IndexRange slice, Vector<int64_t> &r_new_indices) const; }; } // namespace blender diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh new file mode 100644 index 00000000000..760ff082d06 --- /dev/null +++ b/source/blender/blenlib/BLI_noise.hh @@ -0,0 +1,72 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "BLI_float2.hh" +#include "BLI_float3.hh" +#include "BLI_float4.hh" + +namespace blender::noise { + +/* Perlin noise in the range [-1, 1]. */ + +float perlin_signed(float position); +float perlin_signed(float2 position); +float perlin_signed(float3 position); +float perlin_signed(float4 position); + +/* Perlin noise in the range [0, 1]. */ + +float perlin(float position); +float perlin(float2 position); +float perlin(float3 position); +float perlin(float4 position); + +/* Fractal perlin noise in the range [0, 1]. */ + +float perlin_fractal(float position, float octaves, float roughness); +float perlin_fractal(float2 position, float octaves, float roughness); +float perlin_fractal(float3 position, float octaves, float roughness); +float perlin_fractal(float4 position, float octaves, float roughness); + +/* Positive distorted fractal perlin noise. */ + +float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion); +float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion); +float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion); +float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion); + +/* Positive distorted fractal perlin noise that outputs a float3. */ + +float3 perlin_float3_fractal_distorted(float position, + float octaves, + float roughness, + float distortion); +float3 perlin_float3_fractal_distorted(float2 position, + float octaves, + float roughness, + float distortion); +float3 perlin_float3_fractal_distorted(float3 position, + float octaves, + float roughness, + float distortion); +float3 perlin_float3_fractal_distorted(float4 position, + float octaves, + float roughness, + float distortion); + +} // namespace blender::noise diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh index 6a98c2dcc1c..edffb148477 100644 --- a/source/blender/blenlib/BLI_resource_scope.hh +++ b/source/blender/blenlib/BLI_resource_scope.hh @@ -50,11 +50,10 @@ class ResourceScope : NonCopyable, NonMovable { struct ResourceData { void *data; void (*free)(void *data); - const char *debug_name; }; - LinearAllocator<> m_allocator; - Vector<ResourceData> m_resources; + LinearAllocator<> allocator_; + Vector<ResourceData> resources_; public: ResourceScope() = default; @@ -62,8 +61,8 @@ class ResourceScope : NonCopyable, NonMovable { ~ResourceScope() { /* Free in reversed order. */ - for (int64_t i = m_resources.size(); i--;) { - ResourceData &data = m_resources[i]; + for (int64_t i = resources_.size(); i--;) { + ResourceData &data = resources_[i]; data.free(data.data); } } @@ -72,20 +71,17 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of the resource to the ResourceScope. It will be destructed and freed when * the collector is destructed. */ - template<typename T> T *add(std::unique_ptr<T> resource, const char *name) + template<typename T> T *add(std::unique_ptr<T> resource) { BLI_assert(resource.get() != nullptr); T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; } - this->add( - ptr, - [](void *data) { - T *typed_data = reinterpret_cast<T *>(data); - delete typed_data; - }, - name); + this->add(ptr, [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + delete typed_data; + }); return ptr; } @@ -93,7 +89,7 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of the resource to the ResourceScope. It will be destructed when the * collector is destructed. */ - template<typename T> T *add(destruct_ptr<T> resource, const char *name) + template<typename T> T *add(destruct_ptr<T> resource) { T *ptr = resource.release(); if (ptr == nullptr) { @@ -104,13 +100,10 @@ class ResourceScope : NonCopyable, NonMovable { return ptr; } - this->add( - ptr, - [](void *data) { - T *typed_data = reinterpret_cast<T *>(data); - typed_data->~T(); - }, - name); + this->add(ptr, [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + typed_data->~T(); + }); return ptr; } @@ -118,22 +111,31 @@ class ResourceScope : NonCopyable, NonMovable { * Pass ownership of some resource to the ResourceScope. The given free function will be * called when the collector is destructed. */ - void add(void *userdata, void (*free)(void *), const char *name) + void add(void *userdata, void (*free)(void *)) { ResourceData data; - data.debug_name = name; data.data = userdata; data.free = free; - m_resources.append(data); + resources_.append(data); } /** * Construct an object with the same value in the ResourceScope and return a reference to the * new value. */ - template<typename T> T &add_value(T &&value, const char *name) + template<typename T> T &add_value(T &&value) { - return this->construct<T>(name, std::forward<T>(value)); + return this->construct<T>(std::forward<T>(value)); + } + + /** + * The passed in function will be called when the scope is destructed. + */ + template<typename Func> void add_destruct_call(Func func) + { + void *buffer = allocator_.allocate(sizeof(Func), alignof(Func)); + new (buffer) Func(std::move(func)); + this->add(buffer, [](void *data) { (*(Func *)data)(); }); } /** @@ -142,37 +144,19 @@ class ResourceScope : NonCopyable, NonMovable { */ LinearAllocator<> &linear_allocator() { - return m_allocator; + return allocator_; } /** * Utility method to construct an instance of type T that will be owned by the ResourceScope. */ - template<typename T, typename... Args> T &construct(const char *name, Args &&...args) + template<typename T, typename... Args> T &construct(Args &&...args) { - destruct_ptr<T> value_ptr = m_allocator.construct<T>(std::forward<Args>(args)...); + destruct_ptr<T> value_ptr = allocator_.construct<T>(std::forward<Args>(args)...); T &value_ref = *value_ptr; - this->add(std::move(value_ptr), name); + this->add(std::move(value_ptr)); return value_ref; } - - /** - * Print the names of all the resources that are owned by this ResourceScope. This can be - * useful for debugging. - */ - void print(StringRef name) const - { - if (m_resources.size() == 0) { - std::cout << "\"" << name << "\" has no resources.\n"; - return; - } - else { - std::cout << "Resources for \"" << name << "\":\n"; - for (const ResourceData &data : m_resources) { - std::cout << " " << data.data << ": " << data.debug_name << '\n'; - } - } - } }; } // namespace blender diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh index e04295b0e51..5adb47ba0b0 100644 --- a/source/blender/blenlib/BLI_span.hh +++ b/source/blender/blenlib/BLI_span.hh @@ -644,6 +644,16 @@ template<typename T> class MutableSpan { } /** + * Reverse the data in the MutableSpan. + */ + constexpr void reverse() + { + for (const int i : IndexRange(size_ / 2)) { + std::swap(data_[size_ - 1 - i], data_[i]); + } + } + + /** * Returns an (immutable) Span that references the same array. This is usually not needed, * due to implicit conversions. However, sometimes automatic type deduction needs some help. */ diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index c20376c42b9..d3dc05edd9e 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -62,10 +62,15 @@ bool BLI_str_quoted_substr_range(const char *__restrict str, int *__restrict r_start, int *__restrict r_end) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 4); +#if 0 /* UNUSED */ char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict prefix) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL() ATTR_MALLOC; - +#endif +bool BLI_str_quoted_substr(const char *__restrict str, + const char *__restrict prefix, + char *result, + size_t result_maxlen); char *BLI_str_replaceN(const char *__restrict str, const char *__restrict substr_old, const char *__restrict substr_new) ATTR_WARN_UNUSED_RESULT @@ -97,8 +102,15 @@ char *BLI_sprintfN(const char *__restrict format, ...) ATTR_WARN_UNUSED_RESULT size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy) ATTR_NONNULL(); +size_t BLI_str_unescape_ex(char *__restrict dst, + const char *__restrict src, + const size_t src_maxncpy, + /* Additional arguments. */ + const size_t dst_maxncpy, + bool *r_is_complete) ATTR_NONNULL(); size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, const size_t src_maxncpy) ATTR_NONNULL(); + const char *BLI_str_escape_find_quote(const char *str) ATTR_NONNULL(); size_t BLI_str_format_int_grouped(char dst[16], int num) ATTR_NONNULL(); diff --git a/source/blender/blenlib/BLI_user_counter.hh b/source/blender/blenlib/BLI_user_counter.hh index 3e6d5af4c3f..8cebadeac4c 100644 --- a/source/blender/blenlib/BLI_user_counter.hh +++ b/source/blender/blenlib/BLI_user_counter.hh @@ -84,12 +84,24 @@ template<typename T> class UserCounter { return data_; } + const T *operator->() const + { + BLI_assert(data_ != nullptr); + return data_; + } + T &operator*() { BLI_assert(data_ != nullptr); return *data_; } + const T &operator*() const + { + BLI_assert(data_ != nullptr); + return *data_; + } + operator bool() const { return data_ != nullptr; diff --git a/source/blender/blenlib/BLI_utildefines.h b/source/blender/blenlib/BLI_utildefines.h index 5b84e050f82..dec8acd7549 100644 --- a/source/blender/blenlib/BLI_utildefines.h +++ b/source/blender/blenlib/BLI_utildefines.h @@ -683,12 +683,22 @@ extern bool BLI_memory_is_zero(const void *arr, const size_t arr_size); # define UNUSED(x) UNUSED_##x #endif +/** + * WARNING: this doesn't warn when returning pointer types (because of the placement of `*`). + * Use #UNUSED_FUNCTION_WITH_RETURN_TYPE instead in this case. + */ #if defined(__GNUC__) || defined(__clang__) # define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_##x #else # define UNUSED_FUNCTION(x) UNUSED_##x #endif +#if defined(__GNUC__) || defined(__clang__) +# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) __attribute__((__unused__)) rtype UNUSED_##x +#else +# define UNUSED_FUNCTION_WITH_RETURN_TYPE(rtype, x) rtype UNUSED_##x +#endif + /** * UNUSED_VARS#(a, ...): quiet unused warnings * diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 7c91447ab3e..22dbeb0b7cc 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -86,6 +86,7 @@ set(SRC intern/hash_md5.c intern/hash_mm2a.c intern/hash_mm3.c + intern/index_mask.cc intern/jitter_2d.c intern/kdtree_1d.c intern/kdtree_2d.c @@ -116,6 +117,7 @@ set(SRC intern/mesh_boolean.cc intern/mesh_intersect.cc intern/noise.c + intern/noise.cc intern/path_util.c intern/polyfill_2d.c intern/polyfill_2d_beautify.c @@ -204,6 +206,7 @@ set(SRC BLI_filereader.h BLI_float2.hh BLI_float3.hh + BLI_float4.hh BLI_float4x4.hh BLI_fnmatch.h BLI_function_ref.hh @@ -265,6 +268,7 @@ set(SRC BLI_mpq3.hh BLI_multi_value_map.hh BLI_noise.h + BLI_noise.hh BLI_path_util.h BLI_polyfill_2d.h BLI_polyfill_2d_beautify.h diff --git a/source/blender/blenlib/intern/freetypefont.c b/source/blender/blenlib/intern/freetypefont.c index e1e3aa273b5..34de8fe7f6d 100644 --- a/source/blender/blenlib/intern/freetypefont.c +++ b/source/blender/blenlib/intern/freetypefont.c @@ -369,36 +369,28 @@ static VFontData *objfnt_to_ftvfontdata(PackedFile *pf) return vfd; } -static int check_freetypefont(PackedFile *pf) +static bool check_freetypefont(PackedFile *pf) { - FT_Face face; - FT_GlyphSlot glyph; - FT_UInt glyph_index; - int success = 0; + FT_Face face = NULL; + FT_UInt glyph_index = 0; + bool success = false; err = FT_New_Memory_Face(library, pf->data, pf->size, 0, &face); if (err) { - success = 0; + return false; // XXX error("This is not a valid font"); } - else { - glyph_index = FT_Get_Char_Index(face, 'A'); + + FT_Get_First_Char(face, &glyph_index); + if (glyph_index) { err = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP); - if (err) { - success = 0; - } - else { - glyph = face->glyph; - if (glyph->format == ft_glyph_format_outline) { - success = 1; - } - else { - // XXX error("Selected Font has no outline data"); - success = 0; - } + if (!err) { + success = (face->glyph->format == ft_glyph_format_outline); } } + FT_Done_Face(face); + return success; } @@ -413,7 +405,6 @@ static int check_freetypefont(PackedFile *pf) VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf) { VFontData *vfd = NULL; - int success = 0; /* init Freetype */ err = FT_Init_FreeType(&library); @@ -422,9 +413,7 @@ VFontData *BLI_vfontdata_from_freetypefont(PackedFile *pf) return NULL; } - success = check_freetypefont(pf); - - if (success) { + if (check_freetypefont(pf)) { vfd = objfnt_to_ftvfontdata(pf); } diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc new file mode 100644 index 00000000000..cba985b8a44 --- /dev/null +++ b/source/blender/blenlib/intern/index_mask.cc @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_index_mask.hh" + +namespace blender { + +/** + * Create a sub-mask that is also shifted to the beginning. The shifting to the beginning allows + * code to work with smaller indices, which is more memory efficient. + * + * \return New index mask with the size of #slice. It is either empty or starts with 0. It might + * reference indices that have been appended to #r_new_indices. + * + * Example: + * this: [2, 3, 5, 7, 8, 9, 10] + * slice: ^--------^ + * output: [0, 2, 4, 5] + * + * All the indices in the sub-mask are shifted by 3 towards zero, so that the first index in the + * output is zero. + */ +IndexMask IndexMask::slice_and_offset(const IndexRange slice, Vector<int64_t> &r_new_indices) const +{ + const int slice_size = slice.size(); + if (slice_size == 0) { + return {}; + } + IndexMask sliced_mask{indices_.slice(slice)}; + if (sliced_mask.is_range()) { + return IndexMask(slice_size); + } + const int64_t offset = sliced_mask.indices().first(); + if (offset == 0) { + return sliced_mask; + } + r_new_indices.resize(slice_size); + for (const int i : IndexRange(slice_size)) { + r_new_indices[i] = sliced_mask[i] - offset; + } + return IndexMask(r_new_indices.as_span()); +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc new file mode 100644 index 00000000000..c057c12e543 --- /dev/null +++ b/source/blender/blenlib/intern/noise.cc @@ -0,0 +1,693 @@ +/* + * Adapted from Open Shading Language with this license: + * + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Modifications Copyright 2011, Blender Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sony Pictures Imageworks nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <cmath> +#include <cstdint> + +#include "BLI_float2.hh" +#include "BLI_float3.hh" +#include "BLI_float4.hh" +#include "BLI_noise.hh" +#include "BLI_utildefines.h" + +namespace blender::noise { +/* ------------------------------ + * Jenkins Lookup3 Hash Functions + * ------------------------------ + * + * https://burtleburtle.net/bob/c/lookup3.c + * + */ + +BLI_INLINE uint32_t hash_bit_rotate(uint32_t x, uint32_t k) +{ + return (x << k) | (x >> (32 - k)); +} + +BLI_INLINE void hash_bit_mix(uint32_t &a, uint32_t &b, uint32_t &c) +{ + a -= c; + a ^= hash_bit_rotate(c, 4); + c += b; + b -= a; + b ^= hash_bit_rotate(a, 6); + a += c; + c -= b; + c ^= hash_bit_rotate(b, 8); + b += a; + a -= c; + a ^= hash_bit_rotate(c, 16); + c += b; + b -= a; + b ^= hash_bit_rotate(a, 19); + a += c; + c -= b; + c ^= hash_bit_rotate(b, 4); + b += a; +} + +BLI_INLINE void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c) +{ + c ^= b; + c -= hash_bit_rotate(b, 14); + a ^= c; + a -= hash_bit_rotate(c, 11); + b ^= a; + b -= hash_bit_rotate(a, 25); + c ^= b; + c -= hash_bit_rotate(b, 16); + a ^= c; + a -= hash_bit_rotate(c, 4); + b ^= a; + b -= hash_bit_rotate(a, 14); + c ^= b; + c -= hash_bit_rotate(b, 24); +} + +BLI_INLINE uint32_t hash(uint32_t kx) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (1 << 2) + 13; + + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (2 << 2) + 13; + + b += ky; + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (3 << 2) + 13; + + c += kz; + b += ky; + a += kx; + hash_bit_final(a, b, c); + + return c; +} + +BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (4 << 2) + 13; + + a += kx; + b += ky; + c += kz; + hash_bit_mix(a, b, c); + + a += kw; + hash_bit_final(a, b, c); + + return c; +} + +/* Hashing a number of uint32_t into a float in the range [0, 1]. */ + +BLI_INLINE float hash_to_float(uint32_t kx) +{ + return static_cast<float>(hash(kx)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky) +{ + return static_cast<float>(hash(kx, ky)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +{ + return static_cast<float>(hash(kx, ky, kz)) / static_cast<float>(0xFFFFFFFFu); +} + +BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +{ + return static_cast<float>(hash(kx, ky, kz, kw)) / static_cast<float>(0xFFFFFFFFu); +} + +/* Hashing a number of floats into a float in the range [0, 1]. */ + +BLI_INLINE uint32_t float_as_uint(float f) +{ + union { + uint32_t i; + float f; + } u; + u.f = f; + return u.i; +} + +BLI_INLINE float hash_to_float(float k) +{ + return hash_to_float(float_as_uint(k)); +} + +BLI_INLINE float hash_to_float(float2 k) +{ + return hash_to_float(float_as_uint(k.x), float_as_uint(k.y)); +} + +BLI_INLINE float hash_to_float(float3 k) +{ + return hash_to_float(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); +} + +BLI_INLINE float hash_to_float(float4 k) +{ + return hash_to_float( + float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); +} + +/* ------------ + * Perlin Noise + * ------------ + * + * Perlin, Ken. "Improving noise." Proceedings of the 29th annual conference on Computer graphics + * and interactive techniques. 2002. + * + * This implementation is functionally identical to the implementations in EEVEE, OSL, and SVM. So + * any changes should be applied in all relevant implementations. + */ + +/* Linear Interpolation. */ +BLI_INLINE float mix(float v0, float v1, float x) +{ + return (1 - x) * v0 + x * v1; +} + +/* Bilinear Interpolation: + * + * v2 v3 + * @ + + + + @ y + * + + ^ + * + + | + * + + | + * @ + + + + @ @------> x + * v0 v1 + * + */ +BLI_INLINE float mix(float v0, float v1, float v2, float v3, float x, float y) +{ + float x1 = 1.0 - x; + return (1.0 - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x); +} + +/* Trilinear Interpolation: + * + * v6 v7 + * @ + + + + + + @ + * +\ +\ + * + \ + \ + * + \ + \ + * + \ v4 + \ v5 + * + @ + + + +++ + @ z + * + + + + y ^ + * v2 @ + +++ + + + @ v3 + \ | + * \ + \ + \ | + * \ + \ + \| + * \ + \ + +---------> x + * \+ \+ + * @ + + + + + + @ + * v0 v1 + */ +BLI_INLINE float mix(float v0, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6, + float v7, + float x, + float y, + float z) +{ + float x1 = 1.0 - x; + float y1 = 1.0 - y; + float z1 = 1.0 - z; + return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) + + z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x)); +} + +/* Quadrilinear Interpolation. */ +BLI_INLINE float mix(float v0, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6, + float v7, + float v8, + float v9, + float v10, + float v11, + float v12, + float v13, + float v14, + float v15, + float x, + float y, + float z, + float w) +{ + return mix(mix(v0, v1, v2, v3, v4, v5, v6, v7, x, y, z), + mix(v8, v9, v10, v11, v12, v13, v14, v15, x, y, z), + w); +} + +BLI_INLINE float fade(float t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +BLI_INLINE float negate_if(float value, uint32_t condition) +{ + return (condition != 0u) ? -value : value; +} + +BLI_INLINE float noise_grad(uint32_t hash, float x) +{ + uint32_t h = hash & 15u; + float g = 1u + (h & 7u); + return negate_if(g, h & 8u) * x; +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y) +{ + uint32_t h = hash & 7u; + float u = h < 4u ? x : y; + float v = 2.0 * (h < 4u ? y : x); + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z) +{ + uint32_t h = hash & 15u; + float u = h < 8u ? x : y; + float vt = ((h == 12u) || (h == 14u)) ? x : z; + float v = h < 4u ? y : vt; + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z, float w) +{ + uint32_t h = hash & 31u; + float u = h < 24u ? x : y; + float v = h < 16u ? y : z; + float s = h < 8u ? z : w; + return negate_if(u, h & 1u) + negate_if(v, h & 2u) + negate_if(s, h & 4u); +} + +BLI_INLINE float floor_fraction(float x, int &i) +{ + i = (int)x - ((x < 0) ? 1 : 0); + return x - i; +} + +BLI_INLINE float perlin_noise(float position) +{ + int X; + + float fx = floor_fraction(position, X); + + float u = fade(fx); + + float r = mix(noise_grad(hash(X), fx), noise_grad(hash(X + 1), fx - 1.0), u); + + return r; +} + +BLI_INLINE float perlin_noise(float2 position) +{ + int X, Y; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + + float u = fade(fx); + float v = fade(fy); + + float r = mix(noise_grad(hash(X, Y), fx, fy), + noise_grad(hash(X + 1, Y), fx - 1.0, fy), + noise_grad(hash(X, Y + 1), fx, fy - 1.0), + noise_grad(hash(X + 1, Y + 1), fx - 1.0, fy - 1.0), + u, + v); + + return r; +} + +BLI_INLINE float perlin_noise(float3 position) +{ + int X, Y, Z; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + float fz = floor_fraction(position.z, Z); + + float u = fade(fx); + float v = fade(fy); + float w = fade(fz); + + float r = mix(noise_grad(hash(X, Y, Z), fx, fy, fz), + noise_grad(hash(X + 1, Y, Z), fx - 1, fy, fz), + noise_grad(hash(X, Y + 1, Z), fx, fy - 1, fz), + noise_grad(hash(X + 1, Y + 1, Z), fx - 1, fy - 1, fz), + noise_grad(hash(X, Y, Z + 1), fx, fy, fz - 1), + noise_grad(hash(X + 1, Y, Z + 1), fx - 1, fy, fz - 1), + noise_grad(hash(X, Y + 1, Z + 1), fx, fy - 1, fz - 1), + noise_grad(hash(X + 1, Y + 1, Z + 1), fx - 1, fy - 1, fz - 1), + u, + v, + w); + + return r; +} + +BLI_INLINE float perlin_noise(float4 position) +{ + int X, Y, Z, W; + + float fx = floor_fraction(position.x, X); + float fy = floor_fraction(position.y, Y); + float fz = floor_fraction(position.z, Z); + float fw = floor_fraction(position.w, W); + + float u = fade(fx); + float v = fade(fy); + float t = fade(fz); + float s = fade(fw); + + float r = mix( + noise_grad(hash(X, Y, Z, W), fx, fy, fz, fw), + noise_grad(hash(X + 1, Y, Z, W), fx - 1.0, fy, fz, fw), + noise_grad(hash(X, Y + 1, Z, W), fx, fy - 1.0, fz, fw), + noise_grad(hash(X + 1, Y + 1, Z, W), fx - 1.0, fy - 1.0, fz, fw), + noise_grad(hash(X, Y, Z + 1, W), fx, fy, fz - 1.0, fw), + noise_grad(hash(X + 1, Y, Z + 1, W), fx - 1.0, fy, fz - 1.0, fw), + noise_grad(hash(X, Y + 1, Z + 1, W), fx, fy - 1.0, fz - 1.0, fw), + noise_grad(hash(X + 1, Y + 1, Z + 1, W), fx - 1.0, fy - 1.0, fz - 1.0, fw), + noise_grad(hash(X, Y, Z, W + 1), fx, fy, fz, fw - 1.0), + noise_grad(hash(X + 1, Y, Z, W + 1), fx - 1.0, fy, fz, fw - 1.0), + noise_grad(hash(X, Y + 1, Z, W + 1), fx, fy - 1.0, fz, fw - 1.0), + noise_grad(hash(X + 1, Y + 1, Z, W + 1), fx - 1.0, fy - 1.0, fz, fw - 1.0), + noise_grad(hash(X, Y, Z + 1, W + 1), fx, fy, fz - 1.0, fw - 1.0), + noise_grad(hash(X + 1, Y, Z + 1, W + 1), fx - 1.0, fy, fz - 1.0, fw - 1.0), + noise_grad(hash(X, Y + 1, Z + 1, W + 1), fx, fy - 1.0, fz - 1.0, fw - 1.0), + noise_grad(hash(X + 1, Y + 1, Z + 1, W + 1), fx - 1.0, fy - 1.0, fz - 1.0, fw - 1.0), + u, + v, + t, + s); + + return r; +} + +/* Signed versions of perlin noise in the range [-1, 1]. The scale values were computed + * experimentally by the OSL developers to remap the noise output to the correct range. */ + +float perlin_signed(float position) +{ + return perlin_noise(position) * 0.2500f; +} + +float perlin_signed(float2 position) +{ + return perlin_noise(position) * 0.6616f; +} + +float perlin_signed(float3 position) +{ + return perlin_noise(position) * 0.9820f; +} + +float perlin_signed(float4 position) +{ + return perlin_noise(position) * 0.8344f; +} + +/* Positive versions of perlin noise in the range [0, 1]. */ + +float perlin(float position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float2 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float3 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +float perlin(float4 position) +{ + return perlin_signed(position) / 2.0f + 0.5f; +} + +/* Positive fractal perlin noise. */ + +template<typename T> float perlin_fractal_template(T position, float octaves, float roughness) +{ + float fscale = 1.0f; + float amp = 1.0f; + float maxamp = 0.0f; + float sum = 0.0f; + octaves = CLAMPIS(octaves, 0.0f, 16.0f); + int n = static_cast<int>(octaves); + for (int i = 0; i <= n; i++) { + float t = perlin(fscale * position); + sum += t * amp; + maxamp += amp; + amp *= CLAMPIS(roughness, 0.0f, 1.0f); + fscale *= 2.0f; + } + float rmd = octaves - std::floor(octaves); + if (rmd == 0.0f) { + return sum / maxamp; + } + + float t = perlin(fscale * position); + float sum2 = sum + t * amp; + sum /= maxamp; + sum2 /= maxamp + amp; + return (1.0f - rmd) * sum + rmd * sum2; +} + +float perlin_fractal(float position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float2 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float3 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +float perlin_fractal(float4 position, float octaves, float roughness) +{ + return perlin_fractal_template(position, octaves, roughness); +} + +/* The following offset functions generate random offsets to be added to + * positions to act as a seed since the noise functions don't have seed values. + * The offset's components are in the range [100, 200], not too high to cause + * bad precision and not too small to be noticeable. We use float seed because + * OSL only support float hashes and we need to maintain compatibility with it. + */ + +BLI_INLINE float random_float_offset(float seed) +{ + return 100.0f + hash_to_float(seed) * 100.0f; +} + +BLI_INLINE float2 random_float2_offset(float seed) +{ + return float2(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f); +} + +BLI_INLINE float3 random_float3_offset(float seed) +{ + return float3(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f); +} + +BLI_INLINE float4 random_float4_offset(float seed) +{ + return float4(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f, + 100.0f + hash_to_float(float2(seed, 3.0f)) * 100.0f); +} + +/* Perlin noises to be added to the position to distort other noises. */ + +BLI_INLINE float perlin_distortion(float position, float strength) +{ + return perlin_signed(position + random_float_offset(0.0)) * strength; +} + +BLI_INLINE float2 perlin_distortion(float2 position, float strength) +{ + return float2(perlin_signed(position + random_float2_offset(0.0f)) * strength, + perlin_signed(position + random_float2_offset(1.0f)) * strength); +} + +BLI_INLINE float3 perlin_distortion(float3 position, float strength) +{ + return float3(perlin_signed(position + random_float3_offset(0.0f)) * strength, + perlin_signed(position + random_float3_offset(1.0f)) * strength, + perlin_signed(position + random_float3_offset(2.0f)) * strength); +} + +BLI_INLINE float4 perlin_distortion(float4 position, float strength) +{ + return float4(perlin_signed(position + random_float4_offset(0.0f)) * strength, + perlin_signed(position + random_float4_offset(1.0f)) * strength, + perlin_signed(position + random_float4_offset(2.0f)) * strength, + perlin_signed(position + random_float4_offset(3.0f)) * strength); +} + +/* Positive distorted fractal perlin noise. */ + +float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion) +{ + position += perlin_distortion(position, distortion); + return perlin_fractal(position, octaves, roughness); +} + +/* Positive distorted fractal perlin noise that outputs a float3. The arbitrary seeds are for + * compatibility with shading functions. */ + +float3 perlin_float3_fractal_distorted(float position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float_offset(1.0f), octaves, roughness), + perlin_fractal(position + random_float_offset(2.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float2 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float2_offset(2.0f), octaves, roughness), + perlin_fractal(position + random_float2_offset(3.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float3 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float3_offset(3.0f), octaves, roughness), + perlin_fractal(position + random_float3_offset(4.0f), octaves, roughness)); +} + +float3 perlin_float3_fractal_distorted(float4 position, + float octaves, + float roughness, + float distortion) +{ + position += perlin_distortion(position, distortion); + return float3(perlin_fractal(position, octaves, roughness), + perlin_fractal(position + random_float4_offset(4.0f), octaves, roughness), + perlin_fractal(position + random_float4_offset(5.0f), octaves, roughness)); +} + +} // namespace blender::noise diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index 0be8700810e..0ea784c95b0 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -360,6 +360,27 @@ size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const si return len; } +BLI_INLINE bool str_unescape_pair(char c_next, char *r_out) +{ +#define CASE_PAIR(value_src, value_dst) \ + case value_src: { \ + *r_out = value_dst; \ + return true; \ + } + switch (c_next) { + CASE_PAIR('"', '"'); /* Quote. */ + CASE_PAIR('\\', '\\'); /* Backslash. */ + CASE_PAIR('t', '\t'); /* Tab. */ + CASE_PAIR('n', '\n'); /* Newline. */ + CASE_PAIR('r', '\r'); /* Carriage return. */ + CASE_PAIR('a', '\a'); /* Bell. */ + CASE_PAIR('b', '\b'); /* Backspace. */ + CASE_PAIR('f', '\f'); /* Form-feed. */ + } +#undef CASE_PAIR + return false; +} + /** * This roughly matches C and Python's string escaping with double quotes - `"`. * @@ -368,31 +389,53 @@ size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const si * * \param dst: The destination string, at least the size of `strlen(src) + 1`. * \param src: The escaped source string. - * \param dst_maxncpy: The maximum number of bytes allowable to copy. + * \param src_maxncpy: The maximum number of bytes allowable to copy from `src`. + * \param dst_maxncpy: The maximum number of bytes allowable to copy into `dst`. + * \param r_is_complete: Set to true when + */ +size_t BLI_str_unescape_ex(char *__restrict dst, + const char *__restrict src, + const size_t src_maxncpy, + /* Additional arguments to #BLI_str_unescape */ + const size_t dst_maxncpy, + bool *r_is_complete) +{ + size_t len = 0; + bool is_complete = true; + for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) { + if (UNLIKELY(len == dst_maxncpy)) { + is_complete = false; + break; + } + char c = *src; + if (UNLIKELY(c == '\\') && (str_unescape_pair(*(src + 1), &c))) { + src++; + } + dst[len++] = c; + } + dst[len] = 0; + *r_is_complete = is_complete; + return len; +} + +/** + * See #BLI_str_unescape_ex doc-string. + * + * This function makes the assumption that `dst` always has + * at least `src_maxncpy` bytes available. + * + * Use #BLI_str_unescape_ex if `dst` has a smaller fixed size. * - * \note This is used for parsing animation paths in blend files. + * \note This is used for parsing animation paths in blend files (runs often). */ size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, const size_t src_maxncpy) { size_t len = 0; - for (size_t i = 0; i < src_maxncpy && (*src != '\0'); i++, src++) { + for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) { char c = *src; - if (c == '\\') { - char c_next = *(src + 1); - if (((c_next == '"') && ((void)(c = '"'), true)) || /* Quote. */ - ((c_next == '\\') && ((void)(c = '\\'), true)) || /* Backslash. */ - ((c_next == 't') && ((void)(c = '\t'), true)) || /* Tab. */ - ((c_next == 'n') && ((void)(c = '\n'), true)) || /* Newline. */ - ((c_next == 'r') && ((void)(c = '\r'), true)) || /* Carriage return. */ - ((c_next == 'a') && ((void)(c = '\a'), true)) || /* Bell. */ - ((c_next == 'b') && ((void)(c = '\b'), true)) || /* Backspace. */ - ((c_next == 'f') && ((void)(c = '\f'), true))) /* Form-feed. */ - { - i++; - src++; - } + if (UNLIKELY(c == '\\') && (str_unescape_pair(*(src + 1), &c))) { + src++; } - dst[len++] = c; } dst[len] = 0; @@ -466,8 +509,12 @@ bool BLI_str_quoted_substr_range(const char *__restrict str, return true; } +/* NOTE(@campbellbarton): in principal it should be possible to access a quoted string + * with an arbitrary size, currently all callers for this functionality + * happened to use a fixed size buffer, so only #BLI_str_quoted_substr is needed. */ +#if 0 /** - * Makes a copy of the text within the "" that appear after some text `blahblah`. + * Makes a copy of the text within the "" that appear after the contents of \a prefix. * i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`. * * \param str: is the entire string to chop. @@ -490,6 +537,38 @@ char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict } return result; } +#endif + +/** + * Fills \a result with text within "" that appear after some the contents of \a prefix. + * i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`. + * + * \param str: is the entire string to chop. + * \param prefix: is the part of the string to step over. + * \param result: The buffer to fill. + * \param result_maxlen: The maximum size of the buffer (including nil terminator). + * \return True if the prefix was found and the entire quoted string was copied into result. + * + * Assume that the strings returned must be freed afterwards, + * and that the inputs will contain data we want. + */ +bool BLI_str_quoted_substr(const char *__restrict str, + const char *__restrict prefix, + char *result, + size_t result_maxlen) +{ + int start_match_ofs, end_match_ofs; + if (!BLI_str_quoted_substr_range(str, prefix, &start_match_ofs, &end_match_ofs)) { + return false; + } + const size_t escaped_len = (size_t)(end_match_ofs - start_match_ofs); + bool is_complete; + BLI_str_unescape_ex(result, str + start_match_ofs, escaped_len, result_maxlen, &is_complete); + if (is_complete == false) { + *result = '\0'; + } + return is_complete; +} /** * string with all instances of substr_old replaced with substr_new, diff --git a/source/blender/blenlib/tests/BLI_color_test.cc b/source/blender/blenlib/tests/BLI_color_test.cc index 14796e6bf71..a91c743b133 100644 --- a/source/blender/blenlib/tests/BLI_color_test.cc +++ b/source/blender/blenlib/tests/BLI_color_test.cc @@ -128,6 +128,6 @@ TEST(color, SceneLinearByteDecoding) EXPECT_NEAR(0.5f, decoded.a, 0.01f); } -/* \} */ +/** \} */ } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_index_mask_test.cc b/source/blender/blenlib/tests/BLI_index_mask_test.cc index 4d6060e51c9..0778d71df01 100644 --- a/source/blender/blenlib/tests/BLI_index_mask_test.cc +++ b/source/blender/blenlib/tests/BLI_index_mask_test.cc @@ -40,4 +40,28 @@ TEST(index_mask, RangeConstructor) EXPECT_EQ(indices[2], 5); } +TEST(index_mask, SliceAndOffset) +{ + Vector<int64_t> indices; + { + IndexMask mask{IndexRange(10)}; + IndexMask new_mask = mask.slice_and_offset(IndexRange(3, 5), indices); + EXPECT_TRUE(new_mask.is_range()); + EXPECT_EQ(new_mask.size(), 5); + EXPECT_EQ(new_mask[0], 0); + EXPECT_EQ(new_mask[1], 1); + } + { + Vector<int64_t> original_indices = {2, 3, 5, 7, 8, 9, 10}; + IndexMask mask{original_indices.as_span()}; + IndexMask new_mask = mask.slice_and_offset(IndexRange(1, 4), indices); + EXPECT_FALSE(new_mask.is_range()); + EXPECT_EQ(new_mask.size(), 4); + EXPECT_EQ(new_mask[0], 0); + EXPECT_EQ(new_mask[1], 2); + EXPECT_EQ(new_mask[2], 4); + EXPECT_EQ(new_mask[3], 5); + } +} + } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc index 4d23a53c08a..fb88fb63e53 100644 --- a/source/blender/blenlib/tests/BLI_span_test.cc +++ b/source/blender/blenlib/tests/BLI_span_test.cc @@ -362,6 +362,29 @@ TEST(span, ReverseIterator) EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4); } +TEST(span, ReverseMutableSpan) +{ + std::array<int, 0> src0 = {}; + MutableSpan<int> span0 = src0; + span0.reverse(); + EXPECT_EQ_ARRAY(span0.data(), Span<int>({}).data(), 0); + + std::array<int, 1> src1 = {4}; + MutableSpan<int> span1 = src1; + span1.reverse(); + EXPECT_EQ_ARRAY(span1.data(), Span<int>({4}).data(), 1); + + std::array<int, 2> src2 = {4, 5}; + MutableSpan<int> span2 = src2; + span2.reverse(); + EXPECT_EQ_ARRAY(span2.data(), Span<int>({5, 4}).data(), 2); + + std::array<int, 5> src5 = {4, 5, 6, 7, 8}; + MutableSpan<int> span5 = src5; + span5.reverse(); + EXPECT_EQ_ARRAY(span5.data(), Span<int>({8, 7, 6, 5, 4}).data(), 5); +} + TEST(span, MutableReverseIterator) { std::array<int, 4> src = {4, 5, 6, 7}; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 3e9ea8db758..15653264211 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -4500,7 +4500,8 @@ static void add_loose_objects_to_scene(Main *mainvar, * or for a collection when *lib has been set. */ LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0; - if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { + if (do_it || + ((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { if (do_append) { if (ob->id.us == 0) { do_it = true; @@ -4560,6 +4561,17 @@ static void add_loose_object_data_to_scene(Main *mainvar, active_collection = lc->collection; } + /* Do not re-instantiate obdata IDs that are already instantiated by an object. */ + LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { + if ((ob->id.tag & LIB_TAG_PRE_EXISTING) == 0 && ob->data != NULL) { + ID *obdata = ob->data; + BLI_assert(ID_REAL_USERS(obdata) > 0); + if ((obdata->tag & LIB_TAG_PRE_EXISTING) == 0) { + obdata->tag &= ~LIB_TAG_DOIT; + } + } + } + /* Loop over all ID types, instancing object-data for ID types that have support for it. */ ListBase *lbarray[INDEX_ID_MAX]; int i = set_listbasepointers(mainvar, lbarray); @@ -4648,7 +4660,7 @@ static void add_collections_to_scene(Main *mainvar, LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { Object *ob = coll_ob->ob; if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 && - (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == 0)) { + (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == false)) { do_add_collection = true; break; } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index e87e201368e..7ad21b9f1ed 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3416,7 +3416,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) case SPACE_FILE: { SpaceFile *sfile = (SpaceFile *)sl; if (sfile->params) { - sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_6 | + sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE | FILE_OBDATA_INSTANCE); } break; diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 5860d60537f..7693dc5c8fb 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -831,33 +831,6 @@ static void do_versions_strip_cache_settings_recursive(const ListBase *seqbase) } } -static void version_node_socket_name(bNodeTree *ntree, - const int node_type, - const char *old_name, - const char *new_name) -{ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == node_type) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - } - } -} - static void version_node_join_geometry_for_multi_input_socket(bNodeTree *ntree) { LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { @@ -1121,8 +1094,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) if (md->type == eModifierType_MeshSequenceCache) { MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; mcmd->velocity_scale = 1.0f; - mcmd->vertex_velocities = NULL; - mcmd->num_vertices = 0; } } } @@ -1588,7 +1559,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_MATH && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH && node->storage == NULL) { const int old_use_attibute_a = (1 << 0); const int old_use_attibute_b = (1 << 1); NodeAttributeMath *data = MEM_callocN(sizeof(NodeAttributeMath), "NodeAttributeMath"); @@ -1749,7 +1720,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_POINT_INSTANCE && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_POINT_INSTANCE && node->storage == NULL) { NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( sizeof(NodeGeometryPointInstance), __func__); data->instance_type = node->custom1; @@ -1766,7 +1737,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_MATH) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_MATH) { NodeAttributeMath *data = (NodeAttributeMath *)node->storage; data->input_type_c = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; } @@ -1825,7 +1796,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_RANDOMIZE && node->storage == NULL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE && node->storage == NULL) { NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( sizeof(NodeAttributeRandomize), __func__); data->data_type = node->custom1; @@ -1861,7 +1832,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { - version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Result", "Distance"); + version_node_socket_name(ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Result", "Distance"); } } FOREACH_NODETREE_END; @@ -1870,7 +1841,8 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) if (!MAIN_VERSION_ATLEAST(bmain, 293, 10)) { FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { - version_node_socket_name(ntree, GEO_NODE_ATTRIBUTE_PROXIMITY, "Location", "Position"); + version_node_socket_name( + ntree, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Location", "Position"); } } FOREACH_NODETREE_END; @@ -1964,7 +1936,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_ATTRIBUTE_FILL) { + if (node->type == GEO_NODE_LEGACY_ATTRIBUTE_FILL) { node->custom2 = ATTR_DOMAIN_AUTO; } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 862b5bdb318..2ac98a11e18 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -37,6 +37,8 @@ #include "DNA_constraint_types.h" #include "DNA_curve_types.h" #include "DNA_genfile.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_lineart_types.h" #include "DNA_listBase.h" #include "DNA_material_types.h" #include "DNA_modifier_types.h" @@ -238,6 +240,16 @@ static void do_versions_idproperty_bones_recursive(Bone *bone) } } +static void do_versions_idproperty_seq_recursive(ListBase *seqbase) +{ + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + version_idproperty_ui_data(seq->prop); + if (seq->type == SEQ_TYPE_META) { + do_versions_idproperty_seq_recursive(&seq->seqbase); + } + } +} + /** * For every data block that supports them, initialize the new IDProperty UI data struct based on * the old more complicated storage. Assumes only the top level of IDProperties below the parent @@ -298,9 +310,7 @@ static void do_versions_idproperty_ui_data(Main *bmain) /* Sequences. */ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { if (scene->ed != NULL) { - LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) { - version_idproperty_ui_data(seq->prop); - } + do_versions_idproperty_seq_recursive(&scene->ed->seqbase); } } } @@ -447,7 +457,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) continue; } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) { + if (node->type != GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE) { continue; } if (node->id == NULL) { @@ -527,33 +537,6 @@ static void version_switch_node_input_prefix(Main *bmain) FOREACH_NODETREE_END; } -static void version_node_socket_name(bNodeTree *ntree, - const int node_type, - const char *old_name, - const char *new_name) -{ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == node_type) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { - if (STREQ(socket->name, old_name)) { - strcpy(socket->name, new_name); - } - if (STREQ(socket->identifier, old_name)) { - strcpy(socket->identifier, new_name); - } - } - } - } -} - static bool replace_bbone_len_scale_rnapath(char **p_old_path, int *p_index) { char *old_path = *p_old_path; @@ -658,6 +641,141 @@ static bNodeSocket *do_version_replace_float_size_with_vector(bNodeTree *ntree, return new_socket; } +static bool geometry_node_is_293_legacy(const short node_type) +{ + switch (node_type) { + /* Not legacy: No attribute inputs or outputs. */ + case GEO_NODE_TRIANGULATE: + case GEO_NODE_EDGE_SPLIT: + case GEO_NODE_TRANSFORM: + case GEO_NODE_BOOLEAN: + case GEO_NODE_SUBDIVISION_SURFACE: + case GEO_NODE_IS_VIEWPORT: + case GEO_NODE_MESH_SUBDIVIDE: + case GEO_NODE_MESH_PRIMITIVE_CUBE: + case GEO_NODE_MESH_PRIMITIVE_CIRCLE: + case GEO_NODE_MESH_PRIMITIVE_UV_SPHERE: + case GEO_NODE_MESH_PRIMITIVE_CYLINDER: + case GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE: + case GEO_NODE_MESH_PRIMITIVE_CONE: + case GEO_NODE_MESH_PRIMITIVE_LINE: + case GEO_NODE_MESH_PRIMITIVE_GRID: + case GEO_NODE_BOUNDING_BOX: + case GEO_NODE_CURVE_RESAMPLE: + case GEO_NODE_INPUT_MATERIAL: + case GEO_NODE_MATERIAL_REPLACE: + case GEO_NODE_CURVE_LENGTH: + case GEO_NODE_CONVEX_HULL: + case GEO_NODE_SEPARATE_COMPONENTS: + case GEO_NODE_CURVE_PRIMITIVE_STAR: + case GEO_NODE_CURVE_PRIMITIVE_SPIRAL: + case GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER: + case GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT: + case GEO_NODE_CURVE_PRIMITIVE_CIRCLE: + case GEO_NODE_VIEWER: + case GEO_NODE_CURVE_PRIMITIVE_LINE: + case GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL: + case GEO_NODE_CURVE_FILL: + case GEO_NODE_CURVE_TRIM: + case GEO_NODE_CURVE_TO_MESH: + return false; + + /* Not legacy: Newly added with fields patch. */ + case GEO_NODE_INPUT_POSITION: + case GEO_NODE_SET_POSITION: + case GEO_NODE_INPUT_INDEX: + case GEO_NODE_INPUT_NORMAL: + case GEO_NODE_ATTRIBUTE_CAPTURE: + return false; + + /* Maybe legacy: Might need special attribute handling, depending on design. */ + case GEO_NODE_SWITCH: + case GEO_NODE_JOIN_GEOMETRY: + case GEO_NODE_ATTRIBUTE_REMOVE: + case GEO_NODE_OBJECT_INFO: + case GEO_NODE_COLLECTION_INFO: + return false; + + /* Maybe legacy: Transferred *all* attributes before, will not transfer all built-ins now. */ + case GEO_NODE_CURVE_ENDPOINTS: + case GEO_NODE_CURVE_TO_POINTS: + return false; + + /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to generate + * a mesh for all grids in the volume. */ + case GEO_NODE_VOLUME_TO_MESH: + return false; + + /* Legacy: Attribute operation completely replaced by field nodes. */ + case GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE: + case GEO_NODE_LEGACY_ATTRIBUTE_MATH: + case GEO_NODE_LEGACY_ATTRIBUTE_FILL: + case GEO_NODE_LEGACY_ATTRIBUTE_MIX: + case GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_COMPARE: + case GEO_NODE_LEGACY_POINT_ROTATE: + case GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR: + case GEO_NODE_LEGACY_POINT_SCALE: + case GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE: + case GEO_NODE_ATTRIBUTE_VECTOR_ROTATE: + case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: + case GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE: + case GEO_NODE_LECAGY_ATTRIBUTE_CLAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH: + case GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ: + case GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ: + return true; + + /* Legacy: Replaced by field node depending on another geometry. */ + case GEO_NODE_LEGACY_RAYCAST: + case GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER: + case GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY: + return true; + + /* Legacy: Simple selection attribute input. */ + case GEO_NODE_LEGACY_MESH_TO_CURVE: + case GEO_NODE_LEGACY_POINT_SEPARATE: + case GEO_NODE_LEGACY_CURVE_SELECT_HANDLES: + case GEO_NODE_LEGACY_CURVE_SPLINE_TYPE: + case GEO_NODE_LEGACY_CURVE_REVERSE: + case GEO_NODE_LEGACY_MATERIAL_ASSIGN: + case GEO_NODE_LEGACY_CURVE_SET_HANDLES: + return true; + + /* Legacy: More complex attribute inputs or outputs. */ + case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */ + case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */ + case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */ + case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */ + case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */ + case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */ + return true; + } + return false; +} + +static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (geometry_node_is_293_legacy(node->type)) { + if (strstr(node->idname, "Legacy")) { + /* Make sure we haven't changed this idname already, better safe than sorry. */ + continue; + } + + char temp_idname[sizeof(node->idname)]; + BLI_strncpy(temp_idname, node->idname, sizeof(node->idname)); + + BLI_snprintf(node->idname, + sizeof(node->idname), + "GeometryNodeLegacy%s", + temp_idname + strlen("GeometryNode")); + } + } +} + /* NOLINTNEXTLINE: readability-function-size */ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) { @@ -1199,6 +1317,40 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 22)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_change_legacy_names(ntree); + } + } + if (!DNA_struct_elem_find( + fd->filesdna, "LineartGpencilModifierData", "bool", "use_crease_on_smooth")) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_GPENCIL) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + lmd->calculation_flags |= LRT_USE_CREASE_ON_SMOOTH_SURFACES; + } + } + } + } + } + + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_FILE) { + SpaceFile *sfile = (SpaceFile *)sl; + if (sfile->asset_params) { + sfile->asset_params->base_params.recursion_level = FILE_SELECT_MAX_RECURSIONS; + } + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 208c02b60d1..3f13d1ec12e 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -22,6 +22,7 @@ #include <cstring> +#include "DNA_node_types.h" #include "DNA_screen_types.h" #include "BLI_listbase.h" @@ -85,3 +86,30 @@ ID *do_versions_rename_id(Main *bmain, } return id; } + +void version_node_socket_name(bNodeTree *ntree, + const int node_type, + const char *old_name, + const char *new_name) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == node_type) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (STREQ(socket->name, old_name)) { + BLI_strncpy(socket->name, new_name, sizeof(socket->name)); + } + if (STREQ(socket->identifier, old_name)) { + BLI_strncpy(socket->identifier, new_name, sizeof(socket->name)); + } + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + if (STREQ(socket->name, old_name)) { + BLI_strncpy(socket->name, new_name, sizeof(socket->name)); + } + if (STREQ(socket->identifier, old_name)) { + BLI_strncpy(socket->identifier, new_name, sizeof(socket->name)); + } + } + } + } +} diff --git a/source/blender/blenloader/intern/versioning_common.h b/source/blender/blenloader/intern/versioning_common.h index 47e0b74a3e4..c1fe2b591cd 100644 --- a/source/blender/blenloader/intern/versioning_common.h +++ b/source/blender/blenloader/intern/versioning_common.h @@ -23,6 +23,7 @@ struct ARegion; struct ListBase; struct Main; +struct bNodeTree; #ifdef __cplusplus extern "C" { @@ -38,6 +39,11 @@ ID *do_versions_rename_id(Main *bmain, const char *name_src, const char *name_dst); +void version_node_socket_name(struct bNodeTree *ntree, + const int node_type, + const char *old_name, + const char *new_name); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 2cce4ed8f9d..f94b0020fe8 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -52,6 +52,7 @@ #include "BKE_brush.h" #include "BKE_colortools.h" #include "BKE_curveprofile.h" +#include "BKE_customdata.h" #include "BKE_gpencil.h" #include "BKE_layer.h" #include "BKE_lib_id.h" @@ -552,6 +553,11 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) mesh->flag |= ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME; BKE_mesh_smooth_flag_set(mesh, false); } + else { + /* Remove sculpt-mask data in default mesh objects for all non-sculpt templates. */ + CustomData_free_layers(&mesh->vdata, CD_PAINT_MASK, mesh->totvert); + CustomData_free_layers(&mesh->ldata, CD_GRID_PAINT_MASK, mesh->totloop); + } } for (Camera *camera = bmain->cameras.first; camera; camera = camera->id.next) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 0042ff29dc2..19f6c1cbbf6 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -885,6 +885,14 @@ void blo_do_versions_userdef(UserDef *userdef) BKE_addon_ensure(&userdef->addons, "pose_library"); } + if (!USER_VERSION_ATLEAST(300, 21)) { + /* Deprecated userdef->flag USER_SAVE_PREVIEWS */ + userdef->file_preview_type = (userdef->flag & USER_FLAG_UNUSED_5) ? USER_FILE_PREVIEW_CAMERA : + USER_FILE_PREVIEW_NONE; + /* Clear for reuse. */ + userdef->flag &= ~USER_FLAG_UNUSED_5; + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c index a25c644555c..bf53e84efa7 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.c +++ b/source/blender/bmesh/intern/bmesh_opdefines.c @@ -1602,7 +1602,7 @@ static BMOpDefine bmo_create_uvsphere_def = { /* slots_in */ {{"u_segments", BMO_OP_SLOT_INT}, /* number of u segments */ {"v_segments", BMO_OP_SLOT_INT}, /* number of v segment */ - {"diameter", BMO_OP_SLOT_FLT}, /* diameter */ + {"radius", BMO_OP_SLOT_FLT}, /* radius */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ {{'\0'}}, @@ -1625,7 +1625,7 @@ static BMOpDefine bmo_create_icosphere_def = { "create_icosphere", /* slots_in */ {{"subdivisions", BMO_OP_SLOT_INT}, /* how many times to recursively subdivide the sphere */ - {"diameter", BMO_OP_SLOT_FLT}, /* diameter */ + {"radius", BMO_OP_SLOT_FLT}, /* radius */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ {{'\0'}}, @@ -1671,8 +1671,8 @@ static BMOpDefine bmo_create_cone_def = { {{"cap_ends", BMO_OP_SLOT_BOOL}, /* whether or not to fill in the ends with faces */ {"cap_tris", BMO_OP_SLOT_BOOL}, /* fill ends with triangles instead of ngons */ {"segments", BMO_OP_SLOT_INT}, /* number of vertices in the base circle */ - {"diameter1", BMO_OP_SLOT_FLT}, /* diameter of one end */ - {"diameter2", BMO_OP_SLOT_FLT}, /* diameter of the opposite */ + {"radius1", BMO_OP_SLOT_FLT}, /* radius of one end */ + {"radius2", BMO_OP_SLOT_FLT}, /* radius of the opposite */ {"depth", BMO_OP_SLOT_FLT}, /* distance between ends */ {"matrix", BMO_OP_SLOT_MAT}, /* matrix to multiply the new geometry with */ {"calc_uvs", BMO_OP_SLOT_BOOL}, /* calculate default UVs */ diff --git a/source/blender/bmesh/intern/bmesh_query.c b/source/blender/bmesh/intern/bmesh_query.c index cb5764b1c91..795d8829ee7 100644 --- a/source/blender/bmesh/intern/bmesh_query.c +++ b/source/blender/bmesh/intern/bmesh_query.c @@ -2637,7 +2637,7 @@ int BM_mesh_calc_face_groups(BMesh *bm, STACK_DECLARE(stack); BMIter iter; - BMFace *f; + BMFace *f, *f_next; int i; STACK_INIT(group_array, bm->totface); @@ -2662,6 +2662,8 @@ int BM_mesh_calc_face_groups(BMesh *bm, /* detect groups */ stack = MEM_mallocN(sizeof(*stack) * tot_faces, __func__); + f_next = BM_iter_new(&iter, bm, BM_FACES_OF_MESH, NULL); + while (tot_touch != tot_faces) { int *group_item; bool ok = false; @@ -2670,10 +2672,10 @@ int BM_mesh_calc_face_groups(BMesh *bm, STACK_INIT(stack, tot_faces); - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_TAG) == false) { - BM_elem_flag_enable(f, BM_ELEM_TAG); - STACK_PUSH(stack, f); + for (; f_next; f_next = BM_iter_step(&iter)) { + if (BM_elem_flag_test(f_next, BM_ELEM_TAG) == false) { + BM_elem_flag_enable(f_next, BM_ELEM_TAG); + STACK_PUSH(stack, f_next); ok = true; break; } @@ -2799,9 +2801,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm, STACK_DECLARE(stack); BMIter iter; - BMEdge *e; + BMEdge *e, *e_next; int i; - STACK_INIT(group_array, bm->totedge); /* init the array */ @@ -2822,6 +2823,8 @@ int BM_mesh_calc_edge_groups(BMesh *bm, /* detect groups */ stack = MEM_mallocN(sizeof(*stack) * tot_edges, __func__); + e_next = BM_iter_new(&iter, bm, BM_EDGES_OF_MESH, NULL); + while (tot_touch != tot_edges) { int *group_item; bool ok = false; @@ -2830,10 +2833,10 @@ int BM_mesh_calc_edge_groups(BMesh *bm, STACK_INIT(stack, tot_edges); - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_TAG) == false) { - BM_elem_flag_enable(e, BM_ELEM_TAG); - STACK_PUSH(stack, e); + for (; e_next; e_next = BM_iter_step(&iter)) { + if (BM_elem_flag_test(e_next, BM_ELEM_TAG) == false) { + BM_elem_flag_enable(e_next, BM_ELEM_TAG); + STACK_PUSH(stack, e_next); ok = true; break; } diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c index e17a4b90478..7ff24c9f825 100644 --- a/source/blender/bmesh/operators/bmo_primitive.c +++ b/source/blender/bmesh/operators/bmo_primitive.c @@ -854,7 +854,7 @@ void BM_mesh_calc_uvs_grid(BMesh *bm, void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) { - const float dia = BMO_slot_float_get(op->slots_in, "diameter"); + const float rad = BMO_slot_float_get(op->slots_in, "radius"); const int seg = BMO_slot_int_get(op->slots_in, "u_segments"); const int tot = BMO_slot_int_get(op->slots_in, "v_segments"); @@ -881,8 +881,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) const float phi = M_PI * ((double)a / (double)tot); vec[0] = 0.0; - vec[1] = dia * sinf(phi); - vec[2] = dia * cosf(phi); + vec[1] = rad * sinf(phi); + vec[2] = rad * cosf(phi); eve = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); BMO_vert_flag_enable(bm, eve, VERT_MARK); @@ -921,12 +921,12 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) { float len, len2, vec2[3]; - len = 2 * dia * sinf(phid / 2.0f); + len = 2 * rad * sinf(phid / 2.0f); /* Length of one segment in shortest parallel. */ - vec[0] = dia * sinf(phid); + vec[0] = rad * sinf(phid); vec[1] = 0.0f; - vec[2] = dia * cosf(phid); + vec[2] = rad * cosf(phid); mul_v3_m3v3(vec2, cmat, vec); len2 = len_v3v3(vec, vec2); @@ -973,8 +973,8 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) { - const float dia = BMO_slot_float_get(op->slots_in, "diameter"); - const float dia_div = dia / 200.0f; + const float rad = BMO_slot_float_get(op->slots_in, "radius"); + const float rad_div = rad / 200.0f; const int subdiv = BMO_slot_int_get(op->slots_in, "subdivisions"); const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); @@ -994,9 +994,9 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) /* phi = 0.25f * (float)M_PI; */ /* UNUSED */ for (a = 0; a < 12; a++) { - vec[0] = dia_div * icovert[a][0]; - vec[1] = dia_div * icovert[a][1]; - vec[2] = dia_div * icovert[a][2]; + vec[0] = rad_div * icovert[a][0]; + vec[1] = rad_div * icovert[a][1]; + vec[2] = rad_div * icovert[a][2]; eva[a] = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); BMO_vert_flag_enable(bm, eva[a], VERT_MARK); @@ -1041,7 +1041,7 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op) "cuts=%i " "use_grid_fill=%b use_sphere=%b", EDGE_MARK, - dia, + rad, (1 << (subdiv - 1)) - 1, true, true); @@ -1392,8 +1392,8 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) BMVert *v1, *v2, *lastv1 = NULL, *lastv2 = NULL, *cent1, *cent2, *firstv1, *firstv2; BMFace *f; float vec[3], mat[4][4]; - const float dia1 = BMO_slot_float_get(op->slots_in, "diameter1"); - const float dia2 = BMO_slot_float_get(op->slots_in, "diameter2"); + const float rad1 = BMO_slot_float_get(op->slots_in, "radius1"); + const float rad2 = BMO_slot_float_get(op->slots_in, "radius2"); const float depth_half = 0.5f * BMO_slot_float_get(op->slots_in, "depth"); int segs = BMO_slot_int_get(op->slots_in, "segments"); const bool cap_ends = BMO_slot_bool_get(op->slots_in, "cap_ends"); @@ -1431,15 +1431,14 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) for (int i = 0; i < segs; i++) { /* Calculate with doubles for higher precision, see: T87779. */ const float phi = (2.0 * M_PI) * ((double)i / (double)segs); - - vec[0] = dia1 * sinf(phi); - vec[1] = dia1 * cosf(phi); + vec[0] = rad1 * sinf(phi); + vec[1] = rad1 * cosf(phi); vec[2] = -depth_half; mul_m4_v3(mat, vec); v1 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); - vec[0] = dia2 * sinf(phi); - vec[1] = dia2 * cosf(phi); + vec[0] = rad2 * sinf(phi); + vec[1] = rad2 * cosf(phi); vec[2] = depth_half; mul_m4_v3(mat, vec); v2 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); @@ -1497,11 +1496,11 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) } if (calc_uvs) { - BM_mesh_calc_uvs_cone(bm, mat, dia2, dia1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset); + BM_mesh_calc_uvs_cone(bm, mat, rad2, rad1, segs, cap_ends, FACE_MARK, cd_loop_uv_offset); } /* Collapse vertices at the first end. */ - if (dia1 == 0.0f) { + if (rad1 == 0.0f) { if (cap_ends) { BM_vert_kill(bm, cent1); } @@ -1513,7 +1512,7 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) } /* Collapse vertices at the second end. */ - if (dia2 == 0.0f) { + if (rad2 == 0.0f) { if (cap_ends) { BM_vert_kill(bm, cent2); } diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 8ddcf11602a..10e385e0187 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -317,6 +317,8 @@ set(SRC nodes/COM_FilterNode.h nodes/COM_InpaintNode.cc nodes/COM_InpaintNode.h + nodes/COM_PosterizeNode.cc + nodes/COM_PosterizeNode.h operations/COM_BlurBaseOperation.cc operations/COM_BlurBaseOperation.h @@ -346,6 +348,8 @@ set(SRC operations/COM_MovieClipAttributeOperation.h operations/COM_MovieDistortionOperation.cc operations/COM_MovieDistortionOperation.h + operations/COM_PosterizeOperation.cc + operations/COM_PosterizeOperation.h operations/COM_SMAAOperation.cc operations/COM_SMAAOperation.h operations/COM_VariableSizeBokehBlurOperation.cc @@ -647,6 +651,7 @@ if(WITH_GTESTS) tests/COM_BufferArea_test.cc tests/COM_BufferRange_test.cc tests/COM_BuffersIterator_test.cc + tests/COM_NodeOperation_test.cc ) set(TEST_INC ) diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 1983eb190e2..4b103c21c75 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -90,6 +90,7 @@ #include "COM_OutputFileNode.h" #include "COM_PixelateNode.h" #include "COM_PlaneTrackDeformNode.h" +#include "COM_PosterizeNode.h" #include "COM_RenderLayersNode.h" #include "COM_RotateNode.h" #include "COM_ScaleNode.h" @@ -424,6 +425,9 @@ Node *COM_convert_bnode(bNode *b_node) case CMP_NODE_ANTIALIASING: node = new AntiAliasingNode(b_node); break; + case CMP_NODE_POSTERIZE: + node = new PosterizeNode(b_node); + break; } return node; } diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index a0333cf96cf..007085ee528 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -425,7 +425,8 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma } const bool has_execution_groups = system->getContext().get_execution_model() == - eExecutionModel::Tiled; + eExecutionModel::Tiled && + system->m_groups.size() > 0; len += graphviz_legend(str + len, maxlen > len ? maxlen - len : 0, has_execution_groups); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "}\r\n"); diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index f3e15c2a495..f730d53acec 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -373,6 +373,12 @@ class MemoryBuffer { return this->m_buffer; } + float *release_ownership_buffer() + { + owns_data_ = false; + return this->m_buffer; + } + MemoryBuffer *inflate() const; inline void wrap_pixel(int &x, int &y, MemoryBufferExtend extend_x, MemoryBufferExtend extend_y) diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index 1b87cdf72fb..3bbd1b22d60 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -41,6 +41,53 @@ NodeOperation::NodeOperation() this->m_btree = nullptr; } +/** + * Generate a hash that identifies the operation result in the current execution. + * Requires `hash_output_params` to be implemented, otherwise `std::nullopt` is returned. + * If the operation parameters or its linked inputs change, the hash must be re-generated. + */ +std::optional<NodeOperationHash> NodeOperation::generate_hash() +{ + params_hash_ = get_default_hash_2(m_width, m_height); + + /* Hash subclasses params. */ + is_hash_output_params_implemented_ = true; + hash_output_params(); + if (!is_hash_output_params_implemented_) { + return std::nullopt; + } + + hash_param(getOutputSocket()->getDataType()); + NodeOperationHash hash; + hash.params_hash_ = params_hash_; + + hash.parents_hash_ = 0; + for (NodeOperationInput &socket : m_inputs) { + if (!socket.isConnected()) { + continue; + } + + NodeOperation &input = socket.getLink()->getOperation(); + const bool is_constant = input.get_flags().is_constant_operation; + combine_hashes(hash.parents_hash_, get_default_hash(is_constant)); + if (is_constant) { + const float *elem = ((ConstantOperation *)&input)->get_constant_elem(); + const int num_channels = COM_data_type_num_channels(socket.getDataType()); + for (const int i : IndexRange(num_channels)) { + combine_hashes(hash.parents_hash_, get_default_hash(elem[i])); + } + } + else { + combine_hashes(hash.parents_hash_, get_default_hash(input.get_id())); + } + } + + hash.type_hash_ = typeid(*this).hash_code(); + hash.operation_ = this; + + return hash; +} + NodeOperationOutput *NodeOperation::getOutputSocket(unsigned int index) { return &m_outputs[index]; diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index b402dc7f174..ef7cf319222 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -22,6 +22,8 @@ #include <sstream> #include <string> +#include "BLI_ghash.h" +#include "BLI_hash.hh" #include "BLI_math_color.h" #include "BLI_math_vector.h" #include "BLI_threads.h" @@ -269,6 +271,42 @@ struct NodeOperationFlags { } }; +/** Hash that identifies an operation output result in the current execution. */ +struct NodeOperationHash { + private: + NodeOperation *operation_; + size_t type_hash_; + size_t parents_hash_; + size_t params_hash_; + + friend class NodeOperation; + + public: + NodeOperation *get_operation() const + { + return operation_; + } + + bool operator==(const NodeOperationHash &other) const + { + return type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ && + params_hash_ == other.params_hash_; + } + + bool operator!=(const NodeOperationHash &other) const + { + return !(*this == other); + } + + bool operator<(const NodeOperationHash &other) const + { + return type_hash_ < other.type_hash_ || + (type_hash_ == other.type_hash_ && parents_hash_ < other.parents_hash_) || + (type_hash_ == other.type_hash_ && parents_hash_ == other.parents_hash_ && + params_hash_ < other.params_hash_); + } +}; + /** * \brief NodeOperation contains calculation logic * @@ -282,6 +320,9 @@ class NodeOperation { Vector<NodeOperationInput> m_inputs; Vector<NodeOperationOutput> m_outputs; + size_t params_hash_; + bool is_hash_output_params_implemented_; + /** * \brief the index of the input socket that will be used to determine the resolution */ @@ -363,6 +404,8 @@ class NodeOperation { return flags; } + std::optional<NodeOperationHash> generate_hash(); + unsigned int getNumberOfInputSockets() const { return m_inputs.size(); @@ -624,6 +667,33 @@ class NodeOperation { protected: NodeOperation(); + /* Overridden by subclasses to allow merging equal operations on compiling. Implementations must + * hash any subclass parameter that affects the output result using `hash_params` methods. */ + virtual void hash_output_params() + { + is_hash_output_params_implemented_ = false; + } + + static void combine_hashes(size_t &combined, size_t other) + { + combined = BLI_ghashutil_combine_hash(combined, other); + } + + template<typename T> void hash_param(T param) + { + combine_hashes(params_hash_, get_default_hash(param)); + } + + template<typename T1, typename T2> void hash_params(T1 param1, T2 param2) + { + combine_hashes(params_hash_, get_default_hash_2(param1, param2)); + } + + template<typename T1, typename T2, typename T3> void hash_params(T1 param1, T2 param2, T3 param3) + { + combine_hashes(params_hash_, get_default_hash_3(param1, param2, param3)); + } + void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center); void addOutputSocket(DataType datatype); diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index 10a91bbcd3e..b2cd76be2c3 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -101,16 +101,16 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) add_datatype_conversions(); if (m_context->get_execution_model() == eExecutionModel::FullFrame) { - /* Copy operations to system. Needed for graphviz. */ - system->set_operations(m_operations, {}); - - DebugInfo::graphviz(system, "compositor_prior_folding"); + save_graphviz("compositor_prior_folding"); ConstantFolder folder(*this); folder.fold_operations(); } determineResolutions(); + save_graphviz("compositor_prior_merging"); + merge_equal_operations(); + if (m_context->get_execution_model() == eExecutionModel::Tiled) { /* surround complex ops with read/write buffer */ add_complex_operation_buffers(); @@ -149,22 +149,28 @@ void NodeOperationBuilder::replace_operation_with_constant(NodeOperation *operat ConstantOperation *constant_operation) { BLI_assert(constant_operation->getNumberOfInputSockets() == 0); + unlink_inputs_and_relink_outputs(operation, constant_operation); + addOperation(constant_operation); +} + +void NodeOperationBuilder::unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op, + NodeOperation *linked_op) +{ int i = 0; while (i < m_links.size()) { Link &link = m_links[i]; - if (&link.to()->getOperation() == operation) { + if (&link.to()->getOperation() == unlinked_op) { link.to()->setLink(nullptr); m_links.remove(i); continue; } - if (&link.from()->getOperation() == operation) { - link.to()->setLink(constant_operation->getOutputSocket()); - m_links[i] = Link(constant_operation->getOutputSocket(), link.to()); + if (&link.from()->getOperation() == unlinked_op) { + link.to()->setLink(linked_op->getOutputSocket()); + m_links[i] = Link(linked_op->getOutputSocket(), link.to()); } i++; } - addOperation(constant_operation); } void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, @@ -456,6 +462,50 @@ void NodeOperationBuilder::determineResolutions() } } +static Vector<NodeOperationHash> generate_hashes(Span<NodeOperation *> operations) +{ + Vector<NodeOperationHash> hashes; + for (NodeOperation *op : operations) { + std::optional<NodeOperationHash> hash = op->generate_hash(); + if (hash) { + hashes.append(std::move(*hash)); + } + } + return hashes; +} + +/** Merge operations with same type, inputs and parameters that produce the same result. */ +void NodeOperationBuilder::merge_equal_operations() +{ + bool check_for_next_merge = true; + while (check_for_next_merge) { + /* Re-generate hashes with any change. */ + Vector<NodeOperationHash> hashes = generate_hashes(m_operations); + + /* Make hashes be consecutive when they are equal. */ + std::sort(hashes.begin(), hashes.end()); + + bool any_merged = false; + const NodeOperationHash *prev_hash = nullptr; + for (const NodeOperationHash &hash : hashes) { + if (prev_hash && *prev_hash == hash) { + merge_equal_operations(prev_hash->get_operation(), hash.get_operation()); + any_merged = true; + } + prev_hash = &hash; + } + + check_for_next_merge = any_merged; + } +} + +void NodeOperationBuilder::merge_equal_operations(NodeOperation *from, NodeOperation *into) +{ + unlink_inputs_and_relink_outputs(from, into); + m_operations.remove_first_occurrence_and_reorder(from); + delete from; +} + Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links( NodeOperationOutput *output) const { @@ -728,6 +778,14 @@ void NodeOperationBuilder::group_operations() } } +void NodeOperationBuilder::save_graphviz(StringRefNull name) +{ + if (COM_EXPORT_GRAPHVIZ) { + exec_system_->set_operations(m_operations, m_groups); + DebugInfo::graphviz(exec_system_, name); + } +} + /** Create a graphviz representation of the NodeOperationBuilder. */ std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder) { diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index 1f76765c846..aca4d043d41 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -169,7 +169,10 @@ class NodeOperationBuilder { private: PreviewOperation *make_preview_operation() const; - + void unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op, NodeOperation *linked_op); + void merge_equal_operations(); + void merge_equal_operations(NodeOperation *from, NodeOperation *into); + void save_graphviz(StringRefNull name = ""); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeCompilerImpl") #endif diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 8e49bf34b51..a08f9dd284c 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -298,7 +298,7 @@ static void opencl_deinitialize() g_work_scheduler.opencl.initialized = false; } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Single threaded Scheduling @@ -310,7 +310,7 @@ static void threading_model_single_thread_execute(WorkPackage *package) device.execute(package); } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Queue Scheduling @@ -388,7 +388,7 @@ static void threading_model_queue_deinitialize() } } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Task Scheduling @@ -426,7 +426,7 @@ static void threading_model_task_stop() BLI_thread_local_delete(g_thread_device); } -/* \} */ +/** \} */ /* -------------------------------------------------------------------- */ /** \name Public API @@ -587,6 +587,6 @@ int WorkScheduler::current_thread_id() return device->thread_id(); } -/* \} */ +/** \} */ } // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_CryptomatteNode.cc b/source/blender/compositor/nodes/COM_CryptomatteNode.cc index 5835f051ce3..c04d98d6a2b 100644 --- a/source/blender/compositor/nodes/COM_CryptomatteNode.cc +++ b/source/blender/compositor/nodes/COM_CryptomatteNode.cc @@ -33,7 +33,8 @@ namespace blender::compositor { -/** \name Cryptomatte base +/* -------------------------------------------------------------------- */ +/** \name Cryptomatte Base * \{ */ void CryptomatteBaseNode::convertToOperations(NodeConverter &converter, @@ -73,10 +74,12 @@ void CryptomatteBaseNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(output_pick_socket, extract_pick_operation->getOutputSocket(0)); } -/* \} */ +/** \} */ +/* -------------------------------------------------------------------- */ /** \name Cryptomatte V2 * \{ */ + static std::string prefix_from_node(const CompositorContext &context, const bNode &node) { char prefix[MAX_NAME]; @@ -247,9 +250,10 @@ CryptomatteOperation *CryptomatteNode::create_cryptomatte_operation( return operation; } -/* \} */ +/** \} */ -/** \name Cryptomatte legacy +/* -------------------------------------------------------------------- */ +/** \name Cryptomatte Legacy * \{ */ CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation( @@ -273,6 +277,6 @@ CryptomatteOperation *CryptomatteLegacyNode::create_cryptomatte_operation( return operation; } -/* \} */ +/** \} */ } // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.cc b/source/blender/compositor/nodes/COM_PosterizeNode.cc new file mode 100644 index 00000000000..9f5a69961a4 --- /dev/null +++ b/source/blender/compositor/nodes/COM_PosterizeNode.cc @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2020, Blender Foundation. + */ + +#include "COM_PosterizeNode.h" +#include "COM_ExecutionSystem.h" +#include "COM_PosterizeOperation.h" + +namespace blender::compositor { + +PosterizeNode::PosterizeNode(bNode *editorNode) : Node(editorNode) +{ + /* pass */ +} + +void PosterizeNode::convertToOperations(NodeConverter &converter, + const CompositorContext & /*context*/) const +{ + PosterizeOperation *operation = new PosterizeOperation(); + converter.addOperation(operation); + + converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); + converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); + converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0)); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_PosterizeNode.h b/source/blender/compositor/nodes/COM_PosterizeNode.h new file mode 100644 index 00000000000..bb9bef2bdd0 --- /dev/null +++ b/source/blender/compositor/nodes/COM_PosterizeNode.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_Node.h" + +namespace blender::compositor { + +/** + * \brief PosterizeNode + * \ingroup Node + */ +class PosterizeNode : public Node { + public: + PosterizeNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, + const CompositorContext &context) const override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc index a9c58b55d73..405ba03abf3 100644 --- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.cc @@ -116,4 +116,31 @@ void ConvertDepthToRadiusOperation::deinitExecution() this->m_inputOperation = nullptr; } +void ConvertDepthToRadiusOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float z = *it.in(0); + if (z == 0.0f) { + *it.out = 0.0f; + continue; + } + + const float inv_z = (1.0f / z); + + /* Bug T6656 part 2b, do not re-scale. */ +#if 0 + bcrad = 0.5f * fabs(aperture * (dof_sp * (cam_invfdist - iZ) - 1.0f)); + /* Scale crad back to original maximum and blend: + * `crad->rect[px] = bcrad + wts->rect[px] * (scf * crad->rect[px] - bcrad);` */ +#endif + const float radius = 0.5f * + fabsf(m_aperture * (m_dof_sp * (m_inverseFocalDistance - inv_z) - 1.0f)); + /* Bug T6615, limit minimum radius to 1 pixel, + * not really a solution, but somewhat mitigates the problem. */ + *it.out = CLAMPIS(radius, 0.0f, m_maxRadius); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h index 1f4e856b128..3d163843d06 100644 --- a/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h +++ b/source/blender/compositor/operations/COM_ConvertDepthToRadiusOperation.h @@ -19,7 +19,7 @@ #pragma once #include "COM_FastGaussianBlurOperation.h" -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_object_types.h" namespace blender::compositor { @@ -28,7 +28,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class ConvertDepthToRadiusOperation : public NodeOperation { +class ConvertDepthToRadiusOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -83,6 +83,10 @@ class ConvertDepthToRadiusOperation : public NodeOperation { { this->m_blurPostOperation = operation; } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc index d377903efea..9a3733dda5b 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertOperation.cc @@ -40,6 +40,10 @@ void ConvertBaseOperation::deinitExecution() this->m_inputOperation = nullptr; } +void ConvertBaseOperation::hash_output_params() +{ +} + void ConvertBaseOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) @@ -269,6 +273,12 @@ void ConvertRGBToYCCOperation::executePixelSampled(float output[4], output[3] = inputColor[3]; } +void ConvertRGBToYCCOperation::hash_output_params() +{ + ConvertBaseOperation::hash_output_params(); + hash_param(m_mode); +} + void ConvertRGBToYCCOperation::update_memory_buffer_partial(BuffersIterator<float> &it) { for (; !it.is_end(); ++it) { @@ -327,6 +337,12 @@ void ConvertYCCToRGBOperation::executePixelSampled(float output[4], output[3] = inputColor[3]; } +void ConvertYCCToRGBOperation::hash_output_params() +{ + ConvertBaseOperation::hash_output_params(); + hash_param(m_mode); +} + void ConvertYCCToRGBOperation::update_memory_buffer_partial(BuffersIterator<float> &it) { for (; !it.is_end(); ++it) { diff --git a/source/blender/compositor/operations/COM_ConvertOperation.h b/source/blender/compositor/operations/COM_ConvertOperation.h index 0334959ae7e..72864b3c5e2 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.h +++ b/source/blender/compositor/operations/COM_ConvertOperation.h @@ -37,6 +37,7 @@ class ConvertBaseOperation : public MultiThreadedOperation { Span<MemoryBuffer *> inputs) final; protected: + virtual void hash_output_params() override; virtual void update_memory_buffer_partial(BuffersIterator<float> &it) = 0; }; @@ -124,6 +125,7 @@ class ConvertRGBToYCCOperation : public ConvertBaseOperation { void setMode(int mode); protected: + void hash_output_params() override; void update_memory_buffer_partial(BuffersIterator<float> &it) override; }; @@ -141,6 +143,7 @@ class ConvertYCCToRGBOperation : public ConvertBaseOperation { void setMode(int mode); protected: + void hash_output_params() override; void update_memory_buffer_partial(BuffersIterator<float> &it) override; }; diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc index 5ead300a368..9127a871b04 100644 --- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.cc @@ -95,4 +95,81 @@ void ConvolutionEdgeFilterOperation::executePixel(float output[4], int x, int y, output[3] = MAX2(output[3], 0.0f); } +void ConvolutionEdgeFilterOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int left_offset = (it.x == 0) ? 0 : -image->elem_stride; + const int right_offset = (it.x == last_x) ? 0 : image->elem_stride; + const int down_offset = (it.y == 0) ? 0 : -image->row_stride; + const int up_offset = (it.y == last_y) ? 0 : image->row_stride; + + const float *center_color = it.in(IMAGE_INPUT_INDEX); + float res1[4] = {0}; + float res2[4] = {0}; + + const float *color = center_color + down_offset + left_offset; + madd_v3_v3fl(res1, color, m_filter[0]); + copy_v3_v3(res2, res1); + + color = center_color + down_offset; + madd_v3_v3fl(res1, color, m_filter[1]); + madd_v3_v3fl(res2, color, m_filter[3]); + + color = center_color + down_offset + right_offset; + madd_v3_v3fl(res1, color, m_filter[2]); + madd_v3_v3fl(res2, color, m_filter[6]); + + color = center_color + left_offset; + madd_v3_v3fl(res1, color, m_filter[3]); + madd_v3_v3fl(res2, color, m_filter[1]); + + { + float rgb_filtered[3]; + mul_v3_v3fl(rgb_filtered, center_color, m_filter[4]); + add_v3_v3(res1, rgb_filtered); + add_v3_v3(res2, rgb_filtered); + } + + color = center_color + right_offset; + madd_v3_v3fl(res1, color, m_filter[5]); + madd_v3_v3fl(res2, color, m_filter[7]); + + color = center_color + up_offset + left_offset; + madd_v3_v3fl(res1, color, m_filter[6]); + madd_v3_v3fl(res2, color, m_filter[2]); + + color = center_color + up_offset; + madd_v3_v3fl(res1, color, m_filter[7]); + madd_v3_v3fl(res2, color, m_filter[5]); + + { + color = center_color + up_offset + right_offset; + float rgb_filtered[3]; + mul_v3_v3fl(rgb_filtered, color, m_filter[8]); + add_v3_v3(res1, rgb_filtered); + add_v3_v3(res2, rgb_filtered); + } + + it.out[0] = sqrt(res1[0] * res1[0] + res2[0] * res2[0]); + it.out[1] = sqrt(res1[1] * res1[1] + res2[1] * res2[1]); + it.out[2] = sqrt(res1[2] * res1[2] + res2[2] * res2[2]); + + const float factor = *it.in(FACTOR_INPUT_INDEX); + const float m_factor = 1.0f - factor; + it.out[0] = it.out[0] * factor + center_color[0] * m_factor; + it.out[1] = it.out[1] * factor + center_color[1] * m_factor; + it.out[2] = it.out[2] * factor + center_color[2] * m_factor; + + it.out[3] = center_color[3]; + + /* Make sure we don't return negative color. */ + CLAMP4_MIN(it.out, 0.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h index 319b424bd4a..bd38e27165a 100644 --- a/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h +++ b/source/blender/compositor/operations/COM_ConvolutionEdgeFilterOperation.h @@ -25,6 +25,10 @@ namespace blender::compositor { class ConvolutionEdgeFilterOperation : public ConvolutionFilterOperation { public: void executePixel(float output[4], int x, int y, void *data) override; + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc index 72cbbf4283a..11a077229fd 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc @@ -127,4 +127,62 @@ bool ConvolutionFilterOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void ConvolutionFilterOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + switch (input_idx) { + case IMAGE_INPUT_INDEX: { + const int add_x = (m_filterWidth - 1) / 2 + 1; + const int add_y = (m_filterHeight - 1) / 2 + 1; + r_input_area.xmin = output_area.xmin - add_x; + r_input_area.xmax = output_area.xmax + add_x; + r_input_area.ymin = output_area.ymin - add_y; + r_input_area.ymax = output_area.ymax + add_y; + break; + } + case FACTOR_INPUT_INDEX: { + r_input_area = output_area; + break; + } + } +} + +void ConvolutionFilterOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int left_offset = (it.x == 0) ? 0 : -image->elem_stride; + const int right_offset = (it.x == last_x) ? 0 : image->elem_stride; + const int down_offset = (it.y == 0) ? 0 : -image->row_stride; + const int up_offset = (it.y == last_y) ? 0 : image->row_stride; + + const float *center_color = it.in(IMAGE_INPUT_INDEX); + zero_v4(it.out); + madd_v4_v4fl(it.out, center_color + down_offset + left_offset, m_filter[0]); + madd_v4_v4fl(it.out, center_color + down_offset, m_filter[1]); + madd_v4_v4fl(it.out, center_color + down_offset + right_offset, m_filter[2]); + madd_v4_v4fl(it.out, center_color + left_offset, m_filter[3]); + madd_v4_v4fl(it.out, center_color, m_filter[4]); + madd_v4_v4fl(it.out, center_color + right_offset, m_filter[5]); + madd_v4_v4fl(it.out, center_color + up_offset + left_offset, m_filter[6]); + madd_v4_v4fl(it.out, center_color + up_offset, m_filter[7]); + madd_v4_v4fl(it.out, center_color + up_offset + right_offset, m_filter[8]); + + const float factor = *it.in(FACTOR_INPUT_INDEX); + const float m_factor = 1.0f - factor; + it.out[0] = it.out[0] * factor + center_color[0] * m_factor; + it.out[1] = it.out[1] * factor + center_color[1] * m_factor; + it.out[2] = it.out[2] * factor + center_color[2] * m_factor; + it.out[3] = it.out[3] * factor + center_color[3] * m_factor; + + /* Make sure we don't return negative color. */ + CLAMP4_MIN(it.out, 0.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h index 16dee502929..7e12c7faa5c 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.h @@ -18,11 +18,15 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class ConvolutionFilterOperation : public NodeOperation { +class ConvolutionFilterOperation : public MultiThreadedOperation { + protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int FACTOR_INPUT_INDEX = 1; + private: int m_filterWidth; int m_filterHeight; @@ -43,6 +47,11 @@ class ConvolutionFilterOperation : public NodeOperation { void initExecution() override; void deinitExecution() override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc index ec11ad4d69a..e7f2d5a740a 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.cc +++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc @@ -35,6 +35,8 @@ DenoiseOperation::DenoiseOperation() this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); this->m_settings = nullptr; + flags.is_fullframe_operation = true; + output_rendered_ = false; } void DenoiseOperation::initExecution() { @@ -63,8 +65,7 @@ MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2) rect.xmax = getWidth(); rect.ymax = getHeight(); MemoryBuffer *result = new MemoryBuffer(DataType::Color, rect); - float *data = result->getBuffer(); - this->generateDenoise(data, tileColor, tileNormal, tileAlbedo, this->m_settings); + this->generateDenoise(result, tileColor, tileNormal, tileAlbedo, this->m_settings); return result; } @@ -84,23 +85,33 @@ bool DenoiseOperation::determineDependingAreaOfInterest(rcti * /*input*/, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } -void DenoiseOperation::generateDenoise(float *data, - MemoryBuffer *inputTileColor, - MemoryBuffer *inputTileNormal, - MemoryBuffer *inputTileAlbedo, +void DenoiseOperation::generateDenoise(MemoryBuffer *output, + MemoryBuffer *input_color, + MemoryBuffer *input_normal, + MemoryBuffer *input_albedo, NodeDenoise *settings) { - float *inputBufferColor = inputTileColor->getBuffer(); - BLI_assert(inputBufferColor); - if (!inputBufferColor) { + BLI_assert(input_color->getBuffer()); + if (!input_color->getBuffer()) { return; } + #ifdef WITH_OPENIMAGEDENOISE /* Always supported through Accelerate framework BNNS on macOS. */ # ifndef __APPLE__ if (BLI_cpu_support_sse41()) # endif { + /* OpenImageDenoise needs full buffers. */ + MemoryBuffer *buf_color = input_color->is_a_single_elem() ? input_color->inflate() : + input_color; + MemoryBuffer *buf_normal = input_normal && input_normal->is_a_single_elem() ? + input_normal->inflate() : + input_normal; + MemoryBuffer *buf_albedo = input_albedo && input_albedo->is_a_single_elem() ? + input_albedo->inflate() : + input_albedo; + /* Since it's memory intensive, it's better to run only one instance of OIDN at a time. * OpenImageDenoise is multithreaded internally and should use all available cores nonetheless. */ @@ -111,35 +122,35 @@ void DenoiseOperation::generateDenoise(float *data, oidn::FilterRef filter = device.newFilter("RT"); filter.setImage("color", - inputBufferColor, + buf_color->getBuffer(), oidn::Format::Float3, - inputTileColor->getWidth(), - inputTileColor->getHeight(), + buf_color->getWidth(), + buf_color->getHeight(), 0, sizeof(float[4])); - if (inputTileNormal && inputTileNormal->getBuffer()) { + if (buf_normal && buf_normal->getBuffer()) { filter.setImage("normal", - inputTileNormal->getBuffer(), + buf_normal->getBuffer(), oidn::Format::Float3, - inputTileNormal->getWidth(), - inputTileNormal->getHeight(), + buf_normal->getWidth(), + buf_normal->getHeight(), 0, sizeof(float[3])); } - if (inputTileAlbedo && inputTileAlbedo->getBuffer()) { + if (buf_albedo && buf_albedo->getBuffer()) { filter.setImage("albedo", - inputTileAlbedo->getBuffer(), + buf_albedo->getBuffer(), oidn::Format::Float3, - inputTileAlbedo->getWidth(), - inputTileAlbedo->getHeight(), + buf_albedo->getWidth(), + buf_albedo->getHeight(), 0, sizeof(float[4])); } filter.setImage("output", - data, + output->getBuffer(), oidn::Format::Float3, - inputTileColor->getWidth(), - inputTileColor->getHeight(), + buf_color->getWidth(), + buf_color->getHeight(), 0, sizeof(float[4])); @@ -153,19 +164,46 @@ void DenoiseOperation::generateDenoise(float *data, filter.execute(); BLI_mutex_unlock(&oidn_lock); - /* copy the alpha channel, OpenImageDenoise currently only supports RGB */ - size_t numPixels = inputTileColor->getWidth() * inputTileColor->getHeight(); - for (size_t i = 0; i < numPixels; i++) { - data[i * 4 + 3] = inputBufferColor[i * 4 + 3]; + /* Copy the alpha channel, OpenImageDenoise currently only supports RGB. */ + output->copy_from(input_color, input_color->get_rect(), 3, COM_DATA_TYPE_VALUE_CHANNELS, 3); + + /* Delete inflated buffers. */ + if (input_color->is_a_single_elem()) { + delete buf_color; + } + if (input_normal && input_normal->is_a_single_elem()) { + delete buf_normal; } + if (input_albedo && input_albedo->is_a_single_elem()) { + delete buf_albedo; + } + return; } #endif /* If built without OIDN or running on an unsupported CPU, just pass through. */ - UNUSED_VARS(inputTileAlbedo, inputTileNormal, settings); - ::memcpy(data, - inputBufferColor, - sizeof(float[4]) * inputTileColor->getWidth() * inputTileColor->getHeight()); + UNUSED_VARS(input_albedo, input_normal, settings); + output->copy_from(input_color, input_color->get_rect()); +} + +void DenoiseOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void DenoiseOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (!output_rendered_) { + this->generateDenoise(output, inputs[0], inputs[1], inputs[2], m_settings); + output_rendered_ = true; + } } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.h b/source/blender/compositor/operations/COM_DenoiseOperation.h index a9298c17e92..48209c3eacf 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.h +++ b/source/blender/compositor/operations/COM_DenoiseOperation.h @@ -37,6 +37,8 @@ class DenoiseOperation : public SingleThreadedOperation { */ NodeDenoise *m_settings; + bool output_rendered_; + public: DenoiseOperation(); /** @@ -57,11 +59,16 @@ class DenoiseOperation : public SingleThreadedOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + protected: - void generateDenoise(float *data, - MemoryBuffer *inputTileColor, - MemoryBuffer *inputTileNormal, - MemoryBuffer *inputTileAlbedo, + void generateDenoise(MemoryBuffer *output, + MemoryBuffer *input_color, + MemoryBuffer *input_normal, + MemoryBuffer *input_albedo, NodeDenoise *settings); MemoryBuffer *createMemoryBuffer(rcti *rect) override; diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.cc b/source/blender/compositor/operations/COM_DespeckleOperation.cc index fc8778c7d2e..19bd7b2af6f 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.cc +++ b/source/blender/compositor/operations/COM_DespeckleOperation.cc @@ -127,6 +127,11 @@ void DespeckleOperation::executePixel(float output[4], int x, int y, void * /*da else { copy_v4_v4(output, color_org); } + +#undef TOT_DIV_ONE +#undef TOT_DIV_CNR +#undef WTOT +#undef COLOR_ADD } bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input, @@ -144,4 +149,106 @@ bool DespeckleOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DespeckleOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + switch (input_idx) { + case IMAGE_INPUT_INDEX: { + const int add_x = 2; //(this->m_filterWidth - 1) / 2 + 1; + const int add_y = 2; //(this->m_filterHeight - 1) / 2 + 1; + r_input_area.xmin = output_area.xmin - add_x; + r_input_area.xmax = output_area.xmax + add_x; + r_input_area.ymin = output_area.ymin - add_y; + r_input_area.ymax = output_area.ymax + add_y; + break; + } + case FACTOR_INPUT_INDEX: { + r_input_area = output_area; + break; + } + } +} + +void DespeckleOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const int last_x = getWidth() - 1; + const int last_y = getHeight() - 1; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int x1 = MAX2(it.x - 1, 0); + const int x2 = it.x; + const int x3 = MIN2(it.x + 1, last_x); + const int y1 = MAX2(it.y - 1, 0); + const int y2 = it.y; + const int y3 = MIN2(it.y + 1, last_y); + + float w = 0.0f; + const float *color_org = it.in(IMAGE_INPUT_INDEX); + float color_mid[4]; + float color_mid_ok[4]; + const float *in1 = nullptr; + +#define TOT_DIV_ONE 1.0f +#define TOT_DIV_CNR (float)M_SQRT1_2 + +#define WTOT (TOT_DIV_ONE * 4 + TOT_DIV_CNR * 4) + +#define COLOR_ADD(fac) \ + { \ + madd_v4_v4fl(color_mid, in1, fac); \ + if (color_diff(in1, color_org, m_threshold)) { \ + w += fac; \ + madd_v4_v4fl(color_mid_ok, in1, fac); \ + } \ + } + + zero_v4(color_mid); + zero_v4(color_mid_ok); + + in1 = image->get_elem(x1, y1); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x2, y1); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x3, y1); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x1, y2); + COLOR_ADD(TOT_DIV_ONE) + +#if 0 + const float* in2 = image->get_elem(x2, y2); + madd_v4_v4fl(color_mid, in2, this->m_filter[4]); +#endif + + in1 = image->get_elem(x3, y2); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x1, y3); + COLOR_ADD(TOT_DIV_CNR) + in1 = image->get_elem(x2, y3); + COLOR_ADD(TOT_DIV_ONE) + in1 = image->get_elem(x3, y3); + COLOR_ADD(TOT_DIV_CNR) + + mul_v4_fl(color_mid, 1.0f / (4.0f + (4.0f * (float)M_SQRT1_2))); + // mul_v4_fl(color_mid, 1.0f / w); + + if ((w != 0.0f) && ((w / WTOT) > (m_threshold_neighbor)) && + color_diff(color_mid, color_org, m_threshold)) { + const float factor = *it.in(FACTOR_INPUT_INDEX); + mul_v4_fl(color_mid_ok, 1.0f / w); + interp_v4_v4v4(it.out, color_org, color_mid_ok, factor); + } + else { + copy_v4_v4(it.out, color_org); + } + +#undef TOT_DIV_ONE +#undef TOT_DIV_CNR +#undef WTOT +#undef COLOR_ADD + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.h b/source/blender/compositor/operations/COM_DespeckleOperation.h index e8d3461d2ec..70d6c2227f4 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.h +++ b/source/blender/compositor/operations/COM_DespeckleOperation.h @@ -18,12 +18,15 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DespeckleOperation : public NodeOperation { +class DespeckleOperation : public MultiThreadedOperation { private: + constexpr static int IMAGE_INPUT_INDEX = 0; + constexpr static int FACTOR_INPUT_INDEX = 1; + float m_threshold; float m_threshold_neighbor; @@ -52,6 +55,11 @@ class DespeckleOperation : public NodeOperation { void initExecution() override; void deinitExecution() override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.cc b/source/blender/compositor/operations/COM_DilateErodeOperation.cc index c459d09f02c..28b40021cd9 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.cc +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.cc @@ -35,9 +35,9 @@ DilateErodeThresholdOperation::DilateErodeThresholdOperation() this->m__switch = 0.5f; this->m_distance = 0.0f; } -void DilateErodeThresholdOperation::initExecution() + +void DilateErodeThresholdOperation::init_data() { - this->m_inputProgram = this->getInputSocketReader(0); if (this->m_distance < 0.0f) { this->m_scope = -this->m_distance + this->m_inset; } @@ -54,6 +54,11 @@ void DilateErodeThresholdOperation::initExecution() } } +void DilateErodeThresholdOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); +} + void *DilateErodeThresholdOperation::initializeTileData(rcti * /*rect*/) { void *buffer = this->m_inputProgram->initializeTileData(nullptr); @@ -160,6 +165,112 @@ bool DilateErodeThresholdOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DilateErodeThresholdOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_scope; + r_input_area.xmax = output_area.xmax + m_scope; + r_input_area.ymin = output_area.ymin - m_scope; + r_input_area.ymax = output_area.ymax + m_scope; +} + +struct DilateErodeThresholdOperation::PixelData { + int x; + int y; + int xmin; + int xmax; + int ymin; + int ymax; + const float *elem; + float distance; + int elem_stride; + int row_stride; + /** Switch. */ + float sw; +}; + +template<template<typename> typename TCompare> +static float get_min_distance(DilateErodeThresholdOperation::PixelData &p) +{ + /* TODO(manzanilla): bad performance, generate a table with relative offsets on operation + * initialization to loop from less to greater distance and break as soon as #compare is + * true. */ + const TCompare compare; + float min_dist = p.distance; + const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride + + ((intptr_t)p.xmin - p.x) * p.elem_stride; + for (int yi = p.ymin; yi < p.ymax; yi++) { + const float dy = yi - p.y; + const float dist_y = dy * dy; + const float *elem = row; + for (int xi = p.xmin; xi < p.xmax; xi++) { + if (compare(*elem, p.sw)) { + const float dx = xi - p.x; + const float dist = dx * dx + dist_y; + min_dist = MIN2(min_dist, dist); + } + elem += p.elem_stride; + } + row += p.row_stride; + } + return min_dist; +} + +void DilateErodeThresholdOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *input = inputs[0]; + const rcti &input_rect = input->get_rect(); + const float rd = m_scope * m_scope; + const float inset = m_inset; + + PixelData p; + p.sw = m__switch; + p.distance = rd * 2; + p.elem_stride = input->elem_stride; + p.row_stride = input->row_stride; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.x = it.x; + p.y = it.y; + p.xmin = MAX2(p.x - m_scope, input_rect.xmin); + p.ymin = MAX2(p.y - m_scope, input_rect.ymin); + p.xmax = MIN2(p.x + m_scope, input_rect.xmax); + p.ymax = MIN2(p.y + m_scope, input_rect.ymax); + p.elem = it.in(0); + + float pixel_value; + if (*p.elem > p.sw) { + pixel_value = -sqrtf(get_min_distance<std::less>(p)); + } + else { + pixel_value = sqrtf(get_min_distance<std::greater>(p)); + } + + if (m_distance > 0.0f) { + const float delta = m_distance - pixel_value; + if (delta >= 0.0f) { + *it.out = delta >= inset ? 1.0f : delta / inset; + } + else { + *it.out = 0.0f; + } + } + else { + const float delta = -m_distance + pixel_value; + if (delta < 0.0f) { + *it.out = delta < -inset ? 1.0f : (-delta) / inset; + } + else { + *it.out = 0.0f; + } + } + } +} + /* Dilate Distance. */ DilateDistanceOperation::DilateDistanceOperation() { @@ -170,15 +281,20 @@ DilateDistanceOperation::DilateDistanceOperation() flags.complex = true; flags.open_cl = true; } -void DilateDistanceOperation::initExecution() + +void DilateDistanceOperation::init_data() { - this->m_inputProgram = this->getInputSocketReader(0); this->m_scope = this->m_distance; if (this->m_scope < 3) { this->m_scope = 3; } } +void DilateDistanceOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); +} + void *DilateDistanceOperation::initializeTileData(rcti * /*rect*/) { void *buffer = this->m_inputProgram->initializeTileData(nullptr); @@ -258,6 +374,92 @@ void DilateDistanceOperation::executeOpenCL(OpenCLDevice *device, device->COM_clEnqueueRange(dilateKernel, outputMemoryBuffer, 7, this); } +void DilateDistanceOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_scope; + r_input_area.xmax = output_area.xmax + m_scope; + r_input_area.ymin = output_area.ymin - m_scope; + r_input_area.ymax = output_area.ymax + m_scope; +} + +struct DilateDistanceOperation::PixelData { + int x; + int y; + int xmin; + int xmax; + int ymin; + int ymax; + const float *elem; + float min_distance; + int scope; + int elem_stride; + int row_stride; + const rcti &input_rect; + + PixelData(MemoryBuffer *input, const int distance, const int scope) + : min_distance(distance * distance), + scope(scope), + elem_stride(input->elem_stride), + row_stride(input->row_stride), + input_rect(input->get_rect()) + { + } + + void update(BuffersIterator<float> &it) + { + x = it.x; + y = it.y; + xmin = MAX2(x - scope, input_rect.xmin); + ymin = MAX2(y - scope, input_rect.ymin); + xmax = MIN2(x + scope, input_rect.xmax); + ymax = MIN2(y + scope, input_rect.ymax); + elem = it.in(0); + } +}; + +template<template<typename> typename TCompare> +static float get_distance_value(DilateDistanceOperation::PixelData &p, const float start_value) +{ + /* TODO(manzanilla): bad performance, only loop elements within minimum distance removing + * coordinates and conditional if `dist <= min_dist`. May need to generate a table of offsets. */ + const TCompare compare; + const float min_dist = p.min_distance; + float value = start_value; + const float *row = p.elem + ((intptr_t)p.ymin - p.y) * p.row_stride + + ((intptr_t)p.xmin - p.x) * p.elem_stride; + for (int yi = p.ymin; yi < p.ymax; yi++) { + const float dy = yi - p.y; + const float dist_y = dy * dy; + const float *elem = row; + for (int xi = p.xmin; xi < p.xmax; xi++) { + const float dx = xi - p.x; + const float dist = dx * dx + dist_y; + if (dist <= min_dist) { + value = compare(*elem, value) ? *elem : value; + } + elem += p.elem_stride; + } + row += p.row_stride; + } + + return value; +} + +void DilateDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + PixelData p(inputs[0], m_distance, m_scope); + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.update(it); + *it.out = get_distance_value<std::greater>(p, 0.0f); + } +} + /* Erode Distance */ ErodeDistanceOperation::ErodeDistanceOperation() : DilateDistanceOperation() { @@ -318,6 +520,17 @@ void ErodeDistanceOperation::executeOpenCL(OpenCLDevice *device, device->COM_clEnqueueRange(erodeKernel, outputMemoryBuffer, 7, this); } +void ErodeDistanceOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + PixelData p(inputs[0], m_distance, m_scope); + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + p.update(it); + *it.out = get_distance_value<std::less>(p, 1.0f); + } +} + /* Dilate step */ DilateStepOperation::DilateStepOperation() { @@ -475,6 +688,126 @@ bool DilateStepOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DilateStepOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = output_area.xmin - m_iterations; + r_input_area.xmax = output_area.xmax + m_iterations; + r_input_area.ymin = output_area.ymin - m_iterations; + r_input_area.ymax = output_area.ymax + m_iterations; +} + +template<typename TCompareSelector> +static void step_update_memory_buffer(MemoryBuffer *output, + const MemoryBuffer *input, + const rcti &area, + const int num_iterations, + const float compare_min_value) +{ + TCompareSelector selector; + + const int width = output->getWidth(); + const int height = output->getHeight(); + + const int half_window = num_iterations; + const int window = half_window * 2 + 1; + + const int xmin = MAX2(0, area.xmin - half_window); + const int ymin = MAX2(0, area.ymin - half_window); + const int xmax = MIN2(width, area.xmax + half_window); + const int ymax = MIN2(height, area.ymax + half_window); + + const int bwidth = area.xmax - area.xmin; + const int bheight = area.ymax - area.ymin; + + /* NOTE: #result has area width, but new height. + * We have to calculate the additional rows in the first pass, + * to have valid data available for the second pass. */ + rcti result_area; + BLI_rcti_init(&result_area, area.xmin, area.xmax, ymin, ymax); + MemoryBuffer result(DataType::Value, result_area); + + /* #temp holds maxima for every step in the algorithm, #buf holds a + * single row or column of input values, padded with #limit values to + * simplify the logic. */ + float *temp = (float *)MEM_mallocN(sizeof(float) * (2 * window - 1), "dilate erode temp"); + float *buf = (float *)MEM_mallocN(sizeof(float) * (MAX2(bwidth, bheight) + 5 * half_window), + "dilate erode buf"); + + /* The following is based on the van Herk/Gil-Werman algorithm for morphology operations. */ + /* First pass, horizontal dilate/erode. */ + for (int y = ymin; y < ymax; y++) { + for (int x = 0; x < bwidth + 5 * half_window; x++) { + buf[x] = compare_min_value; + } + for (int x = xmin; x < xmax; x++) { + buf[x - area.xmin + window - 1] = input->get_value(x, y, 0); + } + + for (int i = 0; i < (bwidth + 3 * half_window) / window; i++) { + int start = (i + 1) * window - 1; + + temp[window - 1] = buf[start]; + for (int x = 1; x < window; x++) { + temp[window - 1 - x] = selector(temp[window - x], buf[start - x]); + temp[window - 1 + x] = selector(temp[window + x - 2], buf[start + x]); + } + + start = half_window + (i - 1) * window + 1; + for (int x = -MIN2(0, start); x < window - MAX2(0, start + window - bwidth); x++) { + result.get_value(start + x + area.xmin, y, 0) = selector(temp[x], temp[x + window - 1]); + } + } + } + + /* Second pass, vertical dilate/erode. */ + for (int x = 0; x < bwidth; x++) { + for (int y = 0; y < bheight + 5 * half_window; y++) { + buf[y] = compare_min_value; + } + for (int y = ymin; y < ymax; y++) { + buf[y - area.ymin + window - 1] = result.get_value(x + area.xmin, y, 0); + } + + for (int i = 0; i < (bheight + 3 * half_window) / window; i++) { + int start = (i + 1) * window - 1; + + temp[window - 1] = buf[start]; + for (int y = 1; y < window; y++) { + temp[window - 1 - y] = selector(temp[window - y], buf[start - y]); + temp[window - 1 + y] = selector(temp[window + y - 2], buf[start + y]); + } + + start = half_window + (i - 1) * window + 1; + for (int y = -MIN2(0, start); y < window - MAX2(0, start + window - bheight); y++) { + result.get_value(x, y + start + area.ymin, 0) = selector(temp[y], temp[y + window - 1]); + } + } + } + + MEM_freeN(temp); + MEM_freeN(buf); + + output->copy_from(&result, area); +} + +struct Max2Selector { + float operator()(float f1, float f2) const + { + return MAX2(f1, f2); + } +}; + +void DilateStepOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + step_update_memory_buffer<Max2Selector>(output, inputs[0], area, m_iterations, -FLT_MAX); +} + /* Erode step */ ErodeStepOperation::ErodeStepOperation() : DilateStepOperation() { @@ -571,4 +904,18 @@ void *ErodeStepOperation::initializeTileData(rcti *rect) return result; } +struct Min2Selector { + float operator()(float f1, float f2) const + { + return MIN2(f1, f2); + } +}; + +void ErodeStepOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + step_update_memory_buffer<Min2Selector>(output, inputs[0], area, m_iterations, FLT_MAX); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.h b/source/blender/compositor/operations/COM_DilateErodeOperation.h index a489e293e8e..9c32a5ac1fd 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.h +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.h @@ -18,11 +18,14 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DilateErodeThresholdOperation : public NodeOperation { +class DilateErodeThresholdOperation : public MultiThreadedOperation { + public: + struct PixelData; + private: /** * Cached reference to the inputProgram @@ -47,6 +50,7 @@ class DilateErodeThresholdOperation : public NodeOperation { */ void executePixel(float output[4], int x, int y, void *data) override; + void init_data() override; /** * Initialize the execution */ @@ -74,10 +78,17 @@ class DilateErodeThresholdOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; -class DilateDistanceOperation : public NodeOperation { - private: +class DilateDistanceOperation : public MultiThreadedOperation { + public: + struct PixelData; + protected: /** * Cached reference to the inputProgram @@ -94,6 +105,7 @@ class DilateDistanceOperation : public NodeOperation { */ void executePixel(float output[4], int x, int y, void *data) override; + void init_data() override; /** * Initialize the execution */ @@ -119,7 +131,13 @@ class DilateDistanceOperation : public NodeOperation { MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; + class ErodeDistanceOperation : public DilateDistanceOperation { public: ErodeDistanceOperation(); @@ -135,9 +153,13 @@ class ErodeDistanceOperation : public DilateDistanceOperation { MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) override; + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; -class DilateStepOperation : public NodeOperation { +class DilateStepOperation : public MultiThreadedOperation { protected: /** * Cached reference to the inputProgram @@ -174,6 +196,11 @@ class DilateStepOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) final; + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; class ErodeStepOperation : public DilateStepOperation { @@ -181,6 +208,9 @@ class ErodeStepOperation : public DilateStepOperation { ErodeStepOperation(); void *initializeTileData(rcti *rect) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc index 97bdc25af3b..102025ed915 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc @@ -146,4 +146,58 @@ bool DirectionalBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/ return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void DirectionalBlurOperation::get_area_of_interest(const int input_idx, + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void DirectionalBlurOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *input = inputs[0]; + const int iterations = pow(2.0f, this->m_data->iter); + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + float color_accum[4]; + input->read_elem_bilinear(x, y, color_accum); + + /* Blur pixel. */ + /* TODO(manzanilla): Many values used on iterations can be calculated beforehand. Create a + * table on operation initialization. */ + float ltx = this->m_tx; + float lty = this->m_ty; + float lsc = this->m_sc; + float lrot = this->m_rot; + for (int i = 0; i < iterations; i++) { + const float cs = cosf(lrot), ss = sinf(lrot); + const float isc = 1.0f / (1.0f + lsc); + + const float v = isc * (y - this->m_center_y_pix) + lty; + const float u = isc * (x - this->m_center_x_pix) + ltx; + + float color[4]; + input->read_elem_bilinear( + cs * u + ss * v + this->m_center_x_pix, cs * v - ss * u + this->m_center_y_pix, color); + add_v4_v4(color_accum, color); + + /* Double transformations. */ + ltx += this->m_tx; + lty += this->m_ty; + lrot += this->m_rot; + lsc += this->m_sc; + } + + mul_v4_v4fl(it.out, color_accum, 1.0f / (iterations + 1)); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h index 5555520462b..9a982bf6481 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.h +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.h @@ -18,12 +18,12 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "COM_QualityStepHelper.h" namespace blender::compositor { -class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper { +class DirectionalBlurOperation : public MultiThreadedOperation, public QualityStepHelper { private: SocketReader *m_inputProgram; NodeDBlurData *m_data; @@ -65,6 +65,11 @@ class DirectionalBlurOperation : public NodeOperation, public QualityStepHelper MemoryBuffer **inputMemoryBuffers, std::list<cl_mem> *clMemToCleanUp, std::list<cl_kernel> *clKernelsToCleanUp) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DotproductOperation.cc b/source/blender/compositor/operations/COM_DotproductOperation.cc index 07075ae1d9d..875b161e208 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.cc +++ b/source/blender/compositor/operations/COM_DotproductOperation.cc @@ -28,6 +28,7 @@ DotproductOperation::DotproductOperation() this->setResolutionInputSocketIndex(0); this->m_input1Operation = nullptr; this->m_input2Operation = nullptr; + flags.can_be_constant = true; } void DotproductOperation::initExecution() { @@ -55,4 +56,15 @@ void DotproductOperation::executePixelSampled(float output[4], output[0] = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]); } +void DotproductOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *input1 = it.in(0); + const float *input2 = it.in(1); + *it.out = -(input1[0] * input2[0] + input1[1] * input2[1] + input1[2] * input2[2]); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DotproductOperation.h b/source/blender/compositor/operations/COM_DotproductOperation.h index 728033bcf32..c3f39d43fff 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.h +++ b/source/blender/compositor/operations/COM_DotproductOperation.h @@ -18,11 +18,11 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class DotproductOperation : public NodeOperation { +class DotproductOperation : public MultiThreadedOperation { private: SocketReader *m_input1Operation; SocketReader *m_input2Operation; @@ -33,6 +33,10 @@ class DotproductOperation : public NodeOperation { void initExecution() override; void deinitExecution() override; + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_InpaintOperation.cc b/source/blender/compositor/operations/COM_InpaintOperation.cc index bfcd504177f..5e76c41752c 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.cc +++ b/source/blender/compositor/operations/COM_InpaintOperation.cc @@ -39,6 +39,7 @@ InpaintSimpleOperation::InpaintSimpleOperation() this->m_manhattan_distance = nullptr; this->m_cached_buffer = nullptr; this->m_cached_buffer_ready = false; + flags.is_fullframe_operation = true; } void InpaintSimpleOperation::initExecution() { @@ -286,4 +287,47 @@ bool InpaintSimpleOperation::determineDependingAreaOfInterest(rcti * /*input*/, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void InpaintSimpleOperation::get_area_of_interest(const int input_idx, + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void InpaintSimpleOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */ + MemoryBuffer *input = inputs[0]; + if (!m_cached_buffer_ready) { + if (input->is_a_single_elem()) { + MemoryBuffer *tmp = input->inflate(); + m_cached_buffer = tmp->release_ownership_buffer(); + delete tmp; + } + else { + m_cached_buffer = (float *)MEM_dupallocN(input->getBuffer()); + } + + this->calc_manhattan_distance(); + + int curr = 0; + int x, y; + while (this->next_pixel(x, y, curr, this->m_iterations)) { + this->pix_step(x, y); + } + m_cached_buffer_ready = true; + } + + const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType()); + MemoryBuffer buf(m_cached_buffer, num_channels, input->getWidth(), input->getHeight()); + output->copy_from(&buf, area); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_InpaintOperation.h b/source/blender/compositor/operations/COM_InpaintOperation.h index e3d27bf7704..e11610bd263 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.h +++ b/source/blender/compositor/operations/COM_InpaintOperation.h @@ -66,6 +66,13 @@ class InpaintSimpleOperation : public NodeOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) override; + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + private: void calc_manhattan_distance(); void clamp_xy(int &x, int &y); diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.cc b/source/blender/compositor/operations/COM_MapRangeOperation.cc index ada3cd6f159..82fb033bf24 100644 --- a/source/blender/compositor/operations/COM_MapRangeOperation.cc +++ b/source/blender/compositor/operations/COM_MapRangeOperation.cc @@ -30,6 +30,7 @@ MapRangeOperation::MapRangeOperation() this->addOutputSocket(DataType::Value); this->m_inputOperation = nullptr; this->m_useClamp = false; + flags.can_be_constant = true; } void MapRangeOperation::initExecution() @@ -104,4 +105,43 @@ void MapRangeOperation::deinitExecution() this->m_destMaxOperation = nullptr; } +void MapRangeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float source_min = *it.in(1); + const float source_max = *it.in(2); + if (fabsf(source_max - source_min) < 1e-6f) { + it.out[0] = 0.0f; + continue; + } + + float value = *it.in(0); + const float dest_min = *it.in(3); + const float dest_max = *it.in(4); + if (value >= -BLENDER_ZMAX && value <= BLENDER_ZMAX) { + value = (value - source_min) / (source_max - source_min); + value = dest_min + value * (dest_max - dest_min); + } + else if (value > BLENDER_ZMAX) { + value = dest_max; + } + else { + value = dest_min; + } + + if (m_useClamp) { + if (dest_max > dest_min) { + CLAMP(value, dest_min, dest_max); + } + else { + CLAMP(value, dest_max, dest_min); + } + } + + it.out[0] = value; + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MapRangeOperation.h b/source/blender/compositor/operations/COM_MapRangeOperation.h index a544c59887e..a01be14d528 100644 --- a/source/blender/compositor/operations/COM_MapRangeOperation.h +++ b/source/blender/compositor/operations/COM_MapRangeOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_texture_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class MapRangeOperation : public NodeOperation { +class MapRangeOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -68,6 +68,10 @@ class MapRangeOperation : public NodeOperation { { this->m_useClamp = value; } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MapValueOperation.cc b/source/blender/compositor/operations/COM_MapValueOperation.cc index 03fa80d220d..94fecc3f49e 100644 --- a/source/blender/compositor/operations/COM_MapValueOperation.cc +++ b/source/blender/compositor/operations/COM_MapValueOperation.cc @@ -25,6 +25,7 @@ MapValueOperation::MapValueOperation() this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Value); this->m_inputOperation = nullptr; + flags.can_be_constant = true; } void MapValueOperation::initExecution() @@ -60,4 +61,27 @@ void MapValueOperation::deinitExecution() this->m_inputOperation = nullptr; } +void MapValueOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float input = *it.in(0); + TexMapping *texmap = this->m_settings; + float value = (input + texmap->loc[0]) * texmap->size[0]; + if (texmap->flag & TEXMAP_CLIP_MIN) { + if (value < texmap->min[0]) { + value = texmap->min[0]; + } + } + if (texmap->flag & TEXMAP_CLIP_MAX) { + if (value > texmap->max[0]) { + value = texmap->max[0]; + } + } + + it.out[0] = value; + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MapValueOperation.h b/source/blender/compositor/operations/COM_MapValueOperation.h index eb7714714e9..a595eac3155 100644 --- a/source/blender/compositor/operations/COM_MapValueOperation.h +++ b/source/blender/compositor/operations/COM_MapValueOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_texture_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * this program converts an input color to an output value. * it assumes we are in sRGB color space. */ -class MapValueOperation : public NodeOperation { +class MapValueOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -63,6 +63,10 @@ class MapValueOperation : public NodeOperation { { this->m_settings = settings; } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.cc b/source/blender/compositor/operations/COM_NormalizeOperation.cc index f93afcaab95..c3e72d2575f 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.cc +++ b/source/blender/compositor/operations/COM_NormalizeOperation.cc @@ -27,6 +27,7 @@ NormalizeOperation::NormalizeOperation() this->m_imageReader = nullptr; this->m_cachedInstance = nullptr; this->flags.complex = true; + flags.can_be_constant = true; } void NormalizeOperation::initExecution() { @@ -56,6 +57,7 @@ void NormalizeOperation::deinitExecution() { this->m_imageReader = nullptr; delete this->m_cachedInstance; + m_cachedInstance = nullptr; NodeOperation::deinitMutex(); } @@ -127,4 +129,60 @@ void NormalizeOperation::deinitializeTileData(rcti * /*rect*/, void * /*data*/) /* pass */ } +void NormalizeOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + NodeOperation *input = get_input_operation(0); + r_input_area.xmin = 0; + r_input_area.xmax = input->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = input->getHeight(); +} + +void NormalizeOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (m_cachedInstance == nullptr) { + MemoryBuffer *input = inputs[0]; + + /* Using generic two floats struct to store `x: min`, `y: multiply`. */ + NodeTwoFloats *minmult = new NodeTwoFloats(); + + float minv = 1.0f + BLENDER_ZMAX; + float maxv = -1.0f - BLENDER_ZMAX; + for (const float *elem : input->as_range()) { + const float value = *elem; + if ((value > maxv) && (value <= BLENDER_ZMAX)) { + maxv = value; + } + if ((value < minv) && (value >= -BLENDER_ZMAX)) { + minv = value; + } + } + + minmult->x = minv; + /* The case of a flat buffer would cause a divide by 0. */ + minmult->y = ((maxv != minv) ? 1.0f / (maxv - minv) : 0.0f); + + m_cachedInstance = minmult; + } +} + +void NormalizeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + NodeTwoFloats *minmult = m_cachedInstance; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float input_value = *it.in(0); + + *it.out = (input_value - minmult->x) * minmult->y; + + /* Clamp infinities. */ + CLAMP(*it.out, 0.0f, 1.0f); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.h b/source/blender/compositor/operations/COM_NormalizeOperation.h index c89ba372189..7af2aad8a88 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.h +++ b/source/blender/compositor/operations/COM_NormalizeOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_node_types.h" namespace blender::compositor { @@ -27,7 +27,7 @@ namespace blender::compositor { * \brief base class of normalize, implementing the simple normalize * \ingroup operation */ -class NormalizeOperation : public NodeOperation { +class NormalizeOperation : public MultiThreadedOperation { protected: /** * \brief Cached reference to the reader @@ -64,6 +64,14 @@ class NormalizeOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_started(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PosterizeOperation.cc b/source/blender/compositor/operations/COM_PosterizeOperation.cc new file mode 100644 index 00000000000..db5860f48f8 --- /dev/null +++ b/source/blender/compositor/operations/COM_PosterizeOperation.cc @@ -0,0 +1,82 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_PosterizeOperation.h" + +namespace blender::compositor { + +PosterizeOperation::PosterizeOperation() +{ + this->addInputSocket(DataType::Color); + this->addInputSocket(DataType::Value); + this->addOutputSocket(DataType::Color); + this->m_inputProgram = nullptr; + this->m_inputStepsProgram = nullptr; + flags.can_be_constant = true; +} + +void PosterizeOperation::initExecution() +{ + this->m_inputProgram = this->getInputSocketReader(0); + this->m_inputStepsProgram = this->getInputSocketReader(1); +} + +void PosterizeOperation::executePixelSampled(float output[4], + float x, + float y, + PixelSampler sampler) +{ + float inputValue[4]; + float inputSteps[4]; + + this->m_inputProgram->readSampled(inputValue, x, y, sampler); + this->m_inputStepsProgram->readSampled(inputSteps, x, y, sampler); + CLAMP(inputSteps[0], 2.0f, 1024.0f); + const float steps_inv = 1.0f / inputSteps[0]; + + output[0] = floor(inputValue[0] / steps_inv) * steps_inv; + output[1] = floor(inputValue[1] / steps_inv) * steps_inv; + output[2] = floor(inputValue[2] / steps_inv) * steps_inv; + output[3] = inputValue[3]; +} + +void PosterizeOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *in_value = it.in(0); + const float *in_steps = it.in(1); + float steps = in_steps[0]; + CLAMP(steps, 2.0f, 1024.0f); + const float steps_inv = 1.0f / steps; + + it.out[0] = floor(in_value[0] / steps_inv) * steps_inv; + it.out[1] = floor(in_value[1] / steps_inv) * steps_inv; + it.out[2] = floor(in_value[2] / steps_inv) * steps_inv; + it.out[3] = in_value[3]; + } +} + +void PosterizeOperation::deinitExecution() +{ + this->m_inputProgram = nullptr; + this->m_inputStepsProgram = nullptr; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PosterizeOperation.h b/source/blender/compositor/operations/COM_PosterizeOperation.h new file mode 100644 index 00000000000..c625cbb83c6 --- /dev/null +++ b/source/blender/compositor/operations/COM_PosterizeOperation.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_MultiThreadedOperation.h" + +namespace blender::compositor { + +class PosterizeOperation : public MultiThreadedOperation { + private: + /** + * Cached reference to the inputProgram + */ + SocketReader *m_inputProgram; + SocketReader *m_inputStepsProgram; + + public: + PosterizeOperation(); + + /** + * The inner loop of this operation. + */ + void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + /** + * Initialize the execution + */ + void initExecution() override; + + /** + * Deinitialize the execution + */ + void deinitExecution() override; + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PreviewOperation.cc b/source/blender/compositor/operations/COM_PreviewOperation.cc index e7c11613aa3..fa8b5ffcabf 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.cc +++ b/source/blender/compositor/operations/COM_PreviewOperation.cc @@ -171,4 +171,43 @@ eCompositorPriority PreviewOperation::getRenderPriority() const return eCompositorPriority::Low; } +void PreviewOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + + r_input_area.xmin = output_area.xmin / m_divider; + r_input_area.xmax = output_area.xmax / m_divider; + r_input_area.ymin = output_area.ymin / m_divider; + r_input_area.ymax = output_area.ymax / m_divider; +} + +void PreviewOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output), + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + MemoryBuffer *input = inputs[0]; + struct ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new( + m_viewSettings, m_displaySettings); + + rcti buffer_area; + BLI_rcti_init(&buffer_area, 0, this->getWidth(), 0, this->getHeight()); + BuffersIteratorBuilder<uchar> it_builder( + m_outputBuffer, buffer_area, area, COM_data_type_num_channels(DataType::Color)); + + for (BuffersIterator<uchar> it = it_builder.build(); !it.is_end(); ++it) { + const float rx = it.x / m_divider; + const float ry = it.y / m_divider; + + float color[4]; + input->read_elem_checked(rx, ry, color); + IMB_colormanagement_processor_apply_v4(cm_processor, color); + rgba_float_to_uchar(it.out, color); + } + + IMB_colormanagement_processor_free(cm_processor); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PreviewOperation.h b/source/blender/compositor/operations/COM_PreviewOperation.h index 0f43f01c5d6..05dae9c4dd8 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.h +++ b/source/blender/compositor/operations/COM_PreviewOperation.h @@ -20,13 +20,13 @@ #include "BKE_global.h" #include "BLI_rect.h" -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_color_types.h" #include "DNA_image_types.h" namespace blender::compositor { -class PreviewOperation : public NodeOperation { +class PreviewOperation : public MultiThreadedOperation { protected: unsigned char *m_outputBuffer; @@ -63,6 +63,11 @@ class PreviewOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc index b078d85372d..4153b9c8523 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.cc +++ b/source/blender/compositor/operations/COM_SMAAOperation.cc @@ -61,6 +61,8 @@ namespace blender::compositor { /*-----------------------------------------------------------------------------*/ /* Internal Functions to Sample Pixel Color from Image */ +/* TODO(manzanilla): to be removed with tiled implementation. Replace it with + * #buffer->read_elem_checked. */ static inline void sample(SocketReader *reader, int x, int y, float color[4]) { if (x < 0 || x >= reader->getWidth() || y < 0 || y >= reader->getHeight()) { @@ -71,8 +73,13 @@ static inline void sample(SocketReader *reader, int x, int y, float color[4]) reader->read(color, x, y, nullptr); } -static void sample_bilinear_vertical( - SocketReader *reader, int x, int y, float yoffset, float color[4]) +static inline void sample(MemoryBuffer *reader, int x, int y, float color[4]) +{ + reader->read_elem_checked(x, y, color); +} + +template<typename T> +static void sample_bilinear_vertical(T *reader, int x, int y, float yoffset, float color[4]) { float iy = floorf(yoffset); float fy = yoffset - iy; @@ -89,8 +96,8 @@ static void sample_bilinear_vertical( color[3] = interpf(color01[3], color00[3], fy); } -static void sample_bilinear_horizontal( - SocketReader *reader, int x, int y, float xoffset, float color[4]) +template<typename T> +static void sample_bilinear_horizontal(T *reader, int x, int y, float xoffset, float color[4]) { float ix = floorf(xoffset); float fx = xoffset - ix; @@ -162,7 +169,7 @@ static void area_diag(int d1, int d2, int e1, int e2, float weights[2]) SMAAEdgeDetectionOperation::SMAAEdgeDetectionOperation() { this->addInputSocket(DataType::Color); /* image */ - this->addInputSocket(DataType::Value); /* depth, material ID, etc. */ + this->addInputSocket(DataType::Value); /* Depth, material ID, etc. TODO: currently unused. */ this->addOutputSocket(DataType::Color); this->flags.complex = true; this->m_imageReader = nullptr; @@ -207,6 +214,16 @@ bool SMAAEdgeDetectionOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAAEdgeDetectionOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area.xmax = output_area.xmax + 1; + r_input_area.xmin = output_area.xmin - 2; + r_input_area.ymax = output_area.ymax + 1; + r_input_area.ymin = output_area.ymin - 2; +} + void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, void * /*data*/) { float color[4]; @@ -288,6 +305,94 @@ void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, voi } } +void SMAAEdgeDetectionOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[0]; + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { + float color[4]; + const int x = it.x; + const int y = it.y; + + /* Calculate luma deltas: */ + image->read_elem_checked(x, y, color); + const float L = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x - 1, y, color); + const float Lleft = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x, y - 1, color); + const float Ltop = IMB_colormanagement_get_luminance(color); + const float Dleft = fabsf(L - Lleft); + const float Dtop = fabsf(L - Ltop); + + /* We do the usual threshold: */ + it.out[0] = (x > 0 && Dleft >= m_threshold) ? 1.0f : 0.0f; + it.out[1] = (y > 0 && Dtop >= m_threshold) ? 1.0f : 0.0f; + it.out[2] = 0.0f; + it.out[3] = 1.0f; + + /* Then discard if there is no edge: */ + if (is_zero_v2(it.out)) { + continue; + } + + /* Calculate right and bottom deltas: */ + image->read_elem_checked(x + 1, y, color); + const float Lright = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x, y + 1, color); + const float Lbottom = IMB_colormanagement_get_luminance(color); + const float Dright = fabsf(L - Lright); + const float Dbottom = fabsf(L - Lbottom); + + /* Calculate the maximum delta in the direct neighborhood: */ + float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom)); + + /* Calculate luma used for both left and top edges: */ + image->read_elem_checked(x - 1, y - 1, color); + const float Llefttop = IMB_colormanagement_get_luminance(color); + + /* Left edge */ + if (it.out[0] != 0.0f) { + /* Calculate deltas around the left pixel: */ + image->read_elem_checked(x - 2, y, color); + const float Lleftleft = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x - 1, y + 1, color); + const float Lleftbottom = IMB_colormanagement_get_luminance(color); + const float Dleftleft = fabsf(Lleft - Lleftleft); + const float Dlefttop = fabsf(Lleft - Llefttop); + const float Dleftbottom = fabsf(Lleft - Lleftbottom); + + /* Calculate the final maximum delta: */ + maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom))); + + /* Local contrast adaptation: */ + if (maxDelta > m_contrast_limit * Dleft) { + it.out[0] = 0.0f; + } + } + + /* Top edge */ + if (it.out[1] != 0.0f) { + /* Calculate top-top delta: */ + image->read_elem_checked(x, y - 2, color); + const float Ltoptop = IMB_colormanagement_get_luminance(color); + image->read_elem_checked(x + 1, y - 1, color); + const float Ltopright = IMB_colormanagement_get_luminance(color); + const float Dtoptop = fabsf(Ltop - Ltoptop); + const float Dtopleft = fabsf(Ltop - Llefttop); + const float Dtopright = fabsf(Ltop - Ltopright); + + /* Calculate the final maximum delta: */ + maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright))); + + /* Local contrast adaptation: */ + if (maxDelta > m_contrast_limit * Dtop) { + it.out[1] = 0.0f; + } + } + } +} + /*-----------------------------------------------------------------------------*/ /* Blending Weight Calculation (Second Pass) */ /*-----------------------------------------------------------------------------*/ @@ -309,6 +414,9 @@ void *SMAABlendingWeightCalculationOperation::initializeTileData(rcti *rect) void SMAABlendingWeightCalculationOperation::initExecution() { this->m_imageReader = this->getInputSocketReader(0); + if (execution_model_ == eExecutionModel::Tiled) { + sample_image_fn_ = [=](int x, int y, float *out) { sample(m_imageReader, x, y, out); }; + } } void SMAABlendingWeightCalculationOperation::setCornerRounding(float rounding) @@ -414,6 +522,113 @@ void SMAABlendingWeightCalculationOperation::executePixel(float output[4], } } +void SMAABlendingWeightCalculationOperation::update_memory_buffer_started( + MemoryBuffer *UNUSED(output), const rcti &UNUSED(out_area), Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *image = inputs[0]; + sample_image_fn_ = [=](int x, int y, float *out) { image->read_elem_checked(x, y, out); }; +} + +void SMAABlendingWeightCalculationOperation::update_memory_buffer_partial( + MemoryBuffer *output, const rcti &out_area, Span<MemoryBuffer *> UNUSED(inputs)) +{ + for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + zero_v4(it.out); + + float edges[4]; + sample_image_fn_(x, y, edges); + + /* Edge at north */ + float c[4]; + if (edges[1] > 0.0f) { + /* Diagonals have both north and west edges, so calculating weights for them */ + /* in one of the boundaries is enough. */ + calculateDiagWeights(x, y, edges, it.out); + + /* We give priority to diagonals, so if we find a diagonal we skip. */ + /* horizontal/vertical processing. */ + if (!is_zero_v2(it.out)) { + continue; + } + + /* Find the distance to the left and the right: */ + int left = searchXLeft(x, y); + int right = searchXRight(x, y); + int d1 = x - left, d2 = right - x; + + /* Fetch the left and right crossing edges: */ + int e1 = 0, e2 = 0; + sample_image_fn_(left, y - 1, c); + if (c[0] > 0.0) { + e1 += 1; + } + sample_image_fn_(left, y, c); + if (c[0] > 0.0) { + e1 += 2; + } + sample_image_fn_(right + 1, y - 1, c); + if (c[0] > 0.0) { + e2 += 1; + } + sample_image_fn_(right + 1, y, c); + if (c[0] > 0.0) { + e2 += 2; + } + + /* Ok, we know how this pattern looks like, now it is time for getting */ + /* the actual area: */ + area(d1, d2, e1, e2, it.out); /* R, G */ + + /* Fix corners: */ + if (m_corner_rounding) { + detectHorizontalCornerPattern(it.out, left, right, y, d1, d2); + } + } + + /* Edge at west */ + if (edges[0] > 0.0f) { + /* Did we already do diagonal search for this west edge from the left neighboring pixel? */ + if (isVerticalSearchUnneeded(x, y)) { + continue; + } + + /* Find the distance to the top and the bottom: */ + int top = searchYUp(x, y); + int bottom = searchYDown(x, y); + int d1 = y - top, d2 = bottom - y; + + /* Fetch the top and bottom crossing edges: */ + int e1 = 0, e2 = 0; + sample_image_fn_(x - 1, top, c); + if (c[1] > 0.0) { + e1 += 1; + } + sample_image_fn_(x, top, c); + if (c[1] > 0.0) { + e1 += 2; + } + sample_image_fn_(x - 1, bottom + 1, c); + if (c[1] > 0.0) { + e2 += 1; + } + sample_image_fn_(x, bottom + 1, c); + if (c[1] > 0.0) { + e2 += 2; + } + + /* Get the area for this direction: */ + area(d1, d2, e1, e2, it.out + 2); /* B, A */ + + /* Fix corners: */ + if (m_corner_rounding) { + detectVerticalCornerPattern(it.out + 2, x, top, bottom, d1, d2); + } + } + } +} + void SMAABlendingWeightCalculationOperation::deinitExecution() { this->m_imageReader = nullptr; @@ -434,6 +649,19 @@ bool SMAABlendingWeightCalculationOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAABlendingWeightCalculationOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area.xmax = output_area.xmax + + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG + 1); + r_input_area.xmin = output_area.xmin - + fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG + 1); + r_input_area.ymax = output_area.ymax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG); + r_input_area.ymin = output_area.ymin - + fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG); +} + /*-----------------------------------------------------------------------------*/ /* Diagonal Search Functions */ @@ -449,7 +677,7 @@ int SMAABlendingWeightCalculationOperation::searchDiag1(int x, int y, int dir, b while (x != end) { x += dir; y -= dir; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { *found = true; break; @@ -472,12 +700,12 @@ int SMAABlendingWeightCalculationOperation::searchDiag2(int x, int y, int dir, b while (x != end) { x += dir; y += dir; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { *found = true; break; } - sample(m_imageReader, x + 1, y, e); + sample_image_fn_(x + 1, y, e); if (e[0] == 0.0f) { *found = true; return (dir > 0) ? x : x - dir; @@ -522,11 +750,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int left = x - d1, bottom = y + d1; - sample(m_imageReader, left - 1, bottom, c); + sample_image_fn_(left - 1, bottom, c); if (c[1] > 0.0) { e1 += 2; } - sample(m_imageReader, left, bottom, c); + sample_image_fn_(left, bottom, c); if (c[0] > 0.0) { e1 += 1; } @@ -536,11 +764,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int right = x + d2, top = y - d2; - sample(m_imageReader, right + 1, top, c); + sample_image_fn_(right + 1, top, c); if (c[1] > 0.0) { e2 += 2; } - sample(m_imageReader, right + 1, top - 1, c); + sample_image_fn_(right + 1, top - 1, c); if (c[0] > 0.0) { e2 += 1; } @@ -552,7 +780,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Search for the line ends: */ d1 = x - searchDiag2(x, y, -1, &d1_found); - sample(m_imageReader, x + 1, y, e); + sample_image_fn_(x + 1, y, e); if (e[0] > 0.0f) { d2 = searchDiag2(x, y, 1, &d2_found) - x; } @@ -568,11 +796,11 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int left = x - d1, top = y - d1; - sample(m_imageReader, left - 1, top, c); + sample_image_fn_(left - 1, top, c); if (c[1] > 0.0) { e1 += 2; } - sample(m_imageReader, left, top - 1, c); + sample_image_fn_(left, top - 1, c); if (c[0] > 0.0) { e1 += 1; } @@ -582,7 +810,7 @@ void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x, /* Fetch the crossing edges: */ int right = x + d2, bottom = y + d2; - sample(m_imageReader, right + 1, bottom, c); + sample_image_fn_(right + 1, bottom, c); if (c[1] > 0.0) { e2 += 2; } @@ -610,7 +838,7 @@ bool SMAABlendingWeightCalculationOperation::isVerticalSearchUnneeded(int x, int } /* Search for the line ends: */ - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] > 0.0f) { d1 = x - searchDiag2(x - 1, y, -1, &found); } @@ -631,14 +859,14 @@ int SMAABlendingWeightCalculationOperation::searchXLeft(int x, int y) float e[4]; while (x > end) { - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f) { /* Is the edge not activated? */ break; } if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return x; } - sample(m_imageReader, x, y - 1, e); + sample_image_fn_(x, y - 1, e); if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return x; } @@ -655,12 +883,12 @@ int SMAABlendingWeightCalculationOperation::searchXRight(int x, int y) while (x < end) { x++; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[1] == 0.0f || /* Is the edge not activated? */ e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } - sample(m_imageReader, x, y - 1, e); + sample_image_fn_(x, y - 1, e); if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } @@ -675,14 +903,14 @@ int SMAABlendingWeightCalculationOperation::searchYUp(int x, int y) float e[4]; while (y > end) { - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[0] == 0.0f) { /* Is the edge not activated? */ break; } if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return y; } - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ return y; } @@ -699,12 +927,12 @@ int SMAABlendingWeightCalculationOperation::searchYDown(int x, int y) while (y < end) { y++; - sample(m_imageReader, x, y, e); + sample_image_fn_(x, y, e); if (e[0] == 0.0f || /* Is the edge not activated? */ e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } - sample(m_imageReader, x - 1, y, e); + sample_image_fn_(x - 1, y, e); if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */ break; } @@ -728,16 +956,16 @@ void SMAABlendingWeightCalculationOperation::detectHorizontalCornerPattern( /* Near the left corner */ if (d1 <= d2) { - sample(m_imageReader, left, y + 1, e); + sample_image_fn_(left, y + 1, e); factor[0] -= rounding * e[0]; - sample(m_imageReader, left, y - 2, e); + sample_image_fn_(left, y - 2, e); factor[1] -= rounding * e[0]; } /* Near the right corner */ if (d1 >= d2) { - sample(m_imageReader, right + 1, y + 1, e); + sample_image_fn_(right + 1, y + 1, e); factor[0] -= rounding * e[0]; - sample(m_imageReader, right + 1, y - 2, e); + sample_image_fn_(right + 1, y - 2, e); factor[1] -= rounding * e[0]; } @@ -757,16 +985,16 @@ void SMAABlendingWeightCalculationOperation::detectVerticalCornerPattern( /* Near the top corner */ if (d1 <= d2) { - sample(m_imageReader, x + 1, top, e); + sample_image_fn_(x + 1, top, e); factor[0] -= rounding * e[1]; - sample(m_imageReader, x - 2, top, e); + sample_image_fn_(x - 2, top, e); factor[1] -= rounding * e[1]; } /* Near the bottom corner */ if (d1 >= d2) { - sample(m_imageReader, x + 1, bottom + 1, e); + sample_image_fn_(x + 1, bottom + 1, e); factor[0] -= rounding * e[1]; - sample(m_imageReader, x - 2, bottom + 1, e); + sample_image_fn_(x - 2, bottom + 1, e); factor[1] -= rounding * e[1]; } @@ -847,6 +1075,59 @@ void SMAANeighborhoodBlendingOperation::executePixel(float output[4], madd_v4_v4fl(output, color2, weight2); } +void SMAANeighborhoodBlendingOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &out_area, + Span<MemoryBuffer *> inputs) +{ + MemoryBuffer *image1 = inputs[0]; + MemoryBuffer *image2 = inputs[1]; + for (BuffersIterator<float> it = output->iterate_with({}, out_area); !it.is_end(); ++it) { + const float x = it.x; + const float y = it.y; + float w[4]; + + /* Fetch the blending weights for current pixel: */ + image2->read_elem_checked(x, y, w); + const float left = w[2], top = w[0]; + image2->read_elem_checked(x + 1, y, w); + const float right = w[3]; + image2->read_elem_checked(x, y + 1, w); + const float bottom = w[1]; + + /* Is there any blending weight with a value greater than 0.0? */ + if (right + bottom + left + top < 1e-5f) { + image1->read_elem_checked(x, y, it.out); + continue; + } + + /* Calculate the blending offsets: */ + void (*sample_fn)(MemoryBuffer * reader, int x, int y, float xoffset, float color[4]); + float offset1, offset2, weight1, weight2, color1[4], color2[4]; + + if (fmaxf(right, left) > fmaxf(bottom, top)) { /* `max(horizontal) > max(vertical)` */ + sample_fn = sample_bilinear_horizontal; + offset1 = right; + offset2 = -left; + weight1 = right / (right + left); + weight2 = left / (right + left); + } + else { + sample_fn = sample_bilinear_vertical; + offset1 = bottom; + offset2 = -top; + weight1 = bottom / (bottom + top); + weight2 = top / (bottom + top); + } + + /* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */ + sample_fn(image1, x, y, offset1, color1); + sample_fn(image1, x, y, offset2, color2); + + mul_v4_v4fl(it.out, color1, weight1); + madd_v4_v4fl(it.out, color2, weight2); + } +} + void SMAANeighborhoodBlendingOperation::deinitExecution() { this->m_image1Reader = nullptr; @@ -866,4 +1147,12 @@ bool SMAANeighborhoodBlendingOperation::determineDependingAreaOfInterest( return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void SMAANeighborhoodBlendingOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &output_area, + rcti &r_input_area) +{ + r_input_area = output_area; + expand_area_for_sampler(r_input_area, PixelSampler::Bilinear); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SMAAOperation.h b/source/blender/compositor/operations/COM_SMAAOperation.h index 781762202b4..91b9299ee43 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.h +++ b/source/blender/compositor/operations/COM_SMAAOperation.h @@ -20,14 +20,14 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { /*-----------------------------------------------------------------------------*/ /* Edge Detection (First Pass) */ -class SMAAEdgeDetectionOperation : public NodeOperation { +class SMAAEdgeDetectionOperation : public MultiThreadedOperation { protected: SocketReader *m_imageReader; SocketReader *m_valueReader; @@ -60,15 +60,20 @@ class SMAAEdgeDetectionOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; /*-----------------------------------------------------------------------------*/ /* Blending Weight Calculation (Second Pass) */ -class SMAABlendingWeightCalculationOperation : public NodeOperation { +class SMAABlendingWeightCalculationOperation : public MultiThreadedOperation { private: SocketReader *m_imageReader; - + std::function<void(int x, int y, float *out)> sample_image_fn_; int m_corner_rounding; public: @@ -96,6 +101,14 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_started(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + private: /* Diagonal Search Functions */ int searchDiag1(int x, int y, int dir, bool *found); @@ -117,7 +130,7 @@ class SMAABlendingWeightCalculationOperation : public NodeOperation { /*-----------------------------------------------------------------------------*/ /* Neighborhood Blending (Third Pass) */ -class SMAANeighborhoodBlendingOperation : public NodeOperation { +class SMAANeighborhoodBlendingOperation : public MultiThreadedOperation { private: SocketReader *m_image1Reader; SocketReader *m_image2Reader; @@ -144,6 +157,11 @@ class SMAANeighborhoodBlendingOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.cc b/source/blender/compositor/operations/COM_VectorBlurOperation.cc index df65044afc1..5405e6d424a 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.cc @@ -57,6 +57,7 @@ VectorBlurOperation::VectorBlurOperation() this->m_inputSpeedProgram = nullptr; this->m_inputZProgram = nullptr; flags.complex = true; + flags.is_fullframe_operation = true; } void VectorBlurOperation::initExecution() { @@ -121,6 +122,51 @@ bool VectorBlurOperation::determineDependingAreaOfInterest(rcti * /*input*/, return false; } +void VectorBlurOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void VectorBlurOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + /* TODO(manzanilla): once tiled implementation is removed, run multi-threaded where possible. */ + if (!m_cachedInstance) { + MemoryBuffer *image = inputs[IMAGE_INPUT_INDEX]; + const bool is_image_inflated = image->is_a_single_elem(); + image = is_image_inflated ? image->inflate() : image; + + /* Must be a copy because it's modified in #generateVectorBlur. */ + MemoryBuffer *speed = inputs[SPEED_INPUT_INDEX]; + speed = speed->is_a_single_elem() ? speed->inflate() : new MemoryBuffer(*speed); + + MemoryBuffer *z = inputs[Z_INPUT_INDEX]; + const bool is_z_inflated = z->is_a_single_elem(); + z = is_z_inflated ? z->inflate() : z; + + m_cachedInstance = (float *)MEM_dupallocN(image->getBuffer()); + this->generateVectorBlur(m_cachedInstance, image, speed, z); + + if (is_image_inflated) { + delete image; + } + delete speed; + if (is_z_inflated) { + delete z; + } + } + + const int num_channels = COM_data_type_num_channels(getOutputSocket()->getDataType()); + MemoryBuffer buf(m_cachedInstance, num_channels, this->getWidth(), this->getHeight()); + output->copy_from(&buf, area); +} + void VectorBlurOperation::generateVectorBlur(float *data, MemoryBuffer *inputImage, MemoryBuffer *inputSpeed, diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.h b/source/blender/compositor/operations/COM_VectorBlurOperation.h index dfcf1fb16f7..c30c150db3c 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.h +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.h @@ -26,6 +26,10 @@ namespace blender::compositor { class VectorBlurOperation : public NodeOperation, public QualityStepHelper { private: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int Z_INPUT_INDEX = 1; + static constexpr int SPEED_INPUT_INDEX = 2; + /** * \brief Cached reference to the inputProgram */ @@ -68,6 +72,13 @@ class VectorBlurOperation : public NodeOperation, public QualityStepHelper { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) override; + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + protected: void generateVectorBlur(float *data, MemoryBuffer *inputImage, diff --git a/source/blender/compositor/tests/COM_NodeOperation_test.cc b/source/blender/compositor/tests/COM_NodeOperation_test.cc new file mode 100644 index 00000000000..94e9fdeedb1 --- /dev/null +++ b/source/blender/compositor/tests/COM_NodeOperation_test.cc @@ -0,0 +1,169 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "testing/testing.h" + +#include "COM_ConstantOperation.h" + +namespace blender::compositor::tests { + +class NonHashedOperation : public NodeOperation { + public: + NonHashedOperation(int id) + { + set_id(id); + addOutputSocket(DataType::Value); + setWidth(2); + setHeight(3); + } +}; + +class NonHashedConstantOperation : public ConstantOperation { + float constant_; + + public: + NonHashedConstantOperation(int id) + { + set_id(id); + addOutputSocket(DataType::Value); + setWidth(2); + setHeight(3); + constant_ = 1.0f; + } + + const float *get_constant_elem() override + { + return &constant_; + } + + void set_constant(float value) + { + constant_ = value; + } +}; + +class HashedOperation : public NodeOperation { + private: + int param1; + float param2; + + public: + HashedOperation(NodeOperation &input, int width, int height) + { + addInputSocket(DataType::Value); + addOutputSocket(DataType::Color); + setWidth(width); + setHeight(height); + param1 = 2; + param2 = 7.0f; + + getInputSocket(0)->setLink(input.getOutputSocket()); + } + + void set_param1(int value) + { + param1 = value; + } + + void hash_output_params() override + { + hash_params(param1, param2); + } +}; + +static void test_non_equal_hashes_compare(NodeOperationHash &h1, + NodeOperationHash &h2, + NodeOperationHash &h3) +{ + if (h1 < h2) { + if (h3 < h1) { + EXPECT_TRUE(h3 < h2); + } + else if (h3 < h2) { + EXPECT_TRUE(h1 < h3); + } + else { + EXPECT_TRUE(h1 < h3); + EXPECT_TRUE(h2 < h3); + } + } + else { + EXPECT_TRUE(h2 < h1); + } +} + +TEST(NodeOperation, generate_hash) +{ + /* Constant input. */ + { + NonHashedConstantOperation input_op1(1); + input_op1.set_constant(1.0f); + EXPECT_EQ(input_op1.generate_hash(), std::nullopt); + + HashedOperation op1(input_op1, 6, 4); + std::optional<NodeOperationHash> hash1_opt = op1.generate_hash(); + EXPECT_NE(hash1_opt, std::nullopt); + NodeOperationHash hash1 = *hash1_opt; + + NonHashedConstantOperation input_op2(1); + input_op2.set_constant(1.0f); + HashedOperation op2(input_op2, 6, 4); + NodeOperationHash hash2 = *op2.generate_hash(); + EXPECT_EQ(hash1, hash2); + + input_op2.set_constant(3.0f); + hash2 = *op2.generate_hash(); + EXPECT_NE(hash1, hash2); + } + + /* Non constant input. */ + { + NonHashedOperation input_op(1); + EXPECT_EQ(input_op.generate_hash(), std::nullopt); + + HashedOperation op1(input_op, 6, 4); + HashedOperation op2(input_op, 6, 4); + NodeOperationHash hash1 = *op1.generate_hash(); + NodeOperationHash hash2 = *op2.generate_hash(); + EXPECT_EQ(hash1, hash2); + op1.set_param1(-1); + hash1 = *op1.generate_hash(); + EXPECT_NE(hash1, hash2); + + HashedOperation op3(input_op, 11, 14); + NodeOperationHash hash3 = *op3.generate_hash(); + EXPECT_NE(hash2, hash3); + EXPECT_NE(hash1, hash3); + + test_non_equal_hashes_compare(hash1, hash2, hash3); + test_non_equal_hashes_compare(hash3, hash2, hash1); + test_non_equal_hashes_compare(hash2, hash3, hash1); + test_non_equal_hashes_compare(hash3, hash1, hash2); + + NonHashedOperation input_op2(2); + HashedOperation op4(input_op2, 11, 14); + NodeOperationHash hash4 = *op4.generate_hash(); + EXPECT_NE(hash3, hash4); + + input_op2.set_id(1); + hash4 = *op4.generate_hash(); + EXPECT_EQ(hash3, hash4); + } +} + +} // namespace blender::compositor::tests diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 3ad26c6f4db..41253117096 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -161,6 +161,13 @@ set(LIB bf_blenkernel ) +if(WITH_PYTHON) + add_definitions(-DWITH_PYTHON) + list(APPEND INC + ../python + ) +endif() + blender_add_lib(bf_depsgraph "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(WITH_GTESTS) diff --git a/source/blender/depsgraph/DEG_depsgraph_query.h b/source/blender/depsgraph/DEG_depsgraph_query.h index 17f5ca0db79..e9195a1eb26 100644 --- a/source/blender/depsgraph/DEG_depsgraph_query.h +++ b/source/blender/depsgraph/DEG_depsgraph_query.h @@ -145,15 +145,7 @@ typedef struct DEGObjectIterData { eEvaluationMode eval_mode; - /* **** Iteration over geometry components **** */ - - /* The object whose components we currently iterate over. - * This might point to #temp_dupli_object. */ - struct Object *geometry_component_owner; - /* Some identifier that is used to determine which geometry component should be returned next. */ - int geometry_component_id; - /* Temporary storage for an object that is created from a component. */ - struct Object temp_geometry_component_object; + struct Object *next_object; /* **** Iteration over dupli-list. *** */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index ef1db8be933..28cfc5a9e1a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -1006,11 +1006,13 @@ void DepsgraphRelationBuilder::build_object_parent(Object *object) /* Bone Parent */ case PARBONE: { - ComponentKey parent_bone_key(parent_id, NodeType::BONE, object->parsubstr); - OperationKey parent_transform_key( - parent_id, NodeType::TRANSFORM, OperationCode::TRANSFORM_FINAL); - add_relation(parent_bone_key, object_transform_key, "Bone Parent"); - add_relation(parent_transform_key, object_transform_key, "Armature Parent"); + if (object->parsubstr[0] != '\0') { + ComponentKey parent_bone_key(parent_id, NodeType::BONE, object->parsubstr); + OperationKey parent_transform_key( + parent_id, NodeType::TRANSFORM, OperationCode::TRANSFORM_FINAL); + add_relation(parent_bone_key, object_transform_key, "Bone Parent"); + add_relation(parent_transform_key, object_transform_key, "Armature Parent"); + } break; } diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc index 770d9775dd3..7af3d03d478 100644 --- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc +++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc @@ -120,130 +120,6 @@ bool deg_object_hide_original(eEvaluationMode eval_mode, Object *ob, DupliObject return false; } -void deg_iterator_components_init(DEGObjectIterData *data, Object *object) -{ - data->geometry_component_owner = object; - data->geometry_component_id = 0; -} - -/* Returns false when iterator is exhausted. */ -bool deg_iterator_components_step(BLI_Iterator *iter) -{ - DEGObjectIterData *data = (DEGObjectIterData *)iter->data; - if (data->geometry_component_owner == nullptr) { - return false; - } - - if (data->geometry_component_owner->runtime.geometry_set_eval == nullptr) { - /* Return the object itself, if it does not have a geometry set yet. */ - iter->current = data->geometry_component_owner; - data->geometry_component_owner = nullptr; - return true; - } - - GeometrySet *geometry_set = data->geometry_component_owner->runtime.geometry_set_eval; - if (geometry_set == nullptr) { - data->geometry_component_owner = nullptr; - return false; - } - - /* The mesh component. */ - if (data->geometry_component_id == 0) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a mesh object. */ - if (data->geometry_component_owner->type == OB_MESH) { - iter->current = data->geometry_component_owner; - return true; - } - - const Mesh *mesh = geometry_set->get_mesh_for_read(); - if (mesh != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_MESH; - temp_object->data = (void *)mesh; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - - /* The pointcloud component. */ - if (data->geometry_component_id == 1) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a point cloud object. */ - if (data->geometry_component_owner->type == OB_POINTCLOUD) { - iter->current = data->geometry_component_owner; - return true; - } - - const PointCloud *pointcloud = geometry_set->get_pointcloud_for_read(); - if (pointcloud != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_POINTCLOUD; - temp_object->data = (void *)pointcloud; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - - /* The volume component. */ - if (data->geometry_component_id == 2) { - data->geometry_component_id++; - - /* Don't use a temporary object for this component, when the owner is a volume object. */ - if (data->geometry_component_owner->type == OB_VOLUME) { - iter->current = data->geometry_component_owner; - return true; - } - - const VolumeComponent *component = geometry_set->get_component_for_read<VolumeComponent>(); - if (component != nullptr) { - const Volume *volume = component->get_for_read(); - - if (volume != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_VOLUME; - temp_object->data = (void *)volume; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - } - - /* The curve component. */ - if (data->geometry_component_id == 3) { - data->geometry_component_id++; - - const CurveComponent *component = geometry_set->get_component_for_read<CurveComponent>(); - if (component != nullptr) { - const Curve *curve = component->get_curve_for_render(); - - if (curve != nullptr) { - Object *temp_object = &data->temp_geometry_component_object; - *temp_object = *data->geometry_component_owner; - temp_object->type = OB_CURVE; - temp_object->data = (void *)curve; - /* Assign data_eval here too, because curve rendering code tries - * to use a mesh if it can find one in this pointer. */ - temp_object->runtime.data_eval = (ID *)curve; - temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; - iter->current = temp_object; - return true; - } - } - } - - data->geometry_component_owner = nullptr; - return false; -} - void deg_iterator_duplis_init(DEGObjectIterData *data, Object *object) { if ((data->flag & DEG_ITER_OBJECT_FLAG_DUPLI) && @@ -292,6 +168,9 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data) temp_dupli_object->dt = MIN2(temp_dupli_object->dt, dupli_parent->dt); copy_v4_v4(temp_dupli_object->color, dupli_parent->color); temp_dupli_object->runtime.select_id = dupli_parent->runtime.select_id; + if (dob->ob->data != dob->ob_data) { + BKE_object_replace_data_on_shallow_copy(temp_dupli_object, dob->ob_data); + } /* Duplicated elements shouldn't care whether their original collection is visible or not. */ temp_dupli_object->base_flag |= BASE_VISIBLE_DEPSGRAPH; @@ -308,7 +187,7 @@ bool deg_iterator_duplis_step(DEGObjectIterData *data) copy_m4_m4(data->temp_dupli_object.obmat, dob->mat); invert_m4_m4(data->temp_dupli_object.imat, data->temp_dupli_object.obmat); - deg_iterator_components_init(data, &data->temp_dupli_object); + data->next_object = &data->temp_dupli_object; BLI_assert(deg::deg_validate_copy_on_write_datablock(&data->temp_dupli_object.id)); return true; } @@ -377,7 +256,7 @@ bool deg_iterator_objects_step(DEGObjectIterData *data) } if (ob_visibility & (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES)) { - deg_iterator_components_init(data, object); + data->next_object = object; } data->id_node_index++; return true; @@ -400,6 +279,7 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data) return; } + data->next_object = nullptr; data->dupli_parent = nullptr; data->dupli_list = nullptr; data->dupli_object_next = nullptr; @@ -408,8 +288,6 @@ void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data) data->id_node_index = 0; data->num_id_nodes = num_id_nodes; data->eval_mode = DEG_get_mode(depsgraph); - data->geometry_component_id = 0; - data->geometry_component_owner = nullptr; deg_invalidate_iterator_work_data(data); DEG_iterator_objects_next(iter); @@ -419,7 +297,9 @@ void DEG_iterator_objects_next(BLI_Iterator *iter) { DEGObjectIterData *data = (DEGObjectIterData *)iter->data; while (true) { - if (deg_iterator_components_step(iter)) { + if (data->next_object != nullptr) { + iter->current = data->next_object; + data->next_object = nullptr; return; } if (deg_iterator_duplis_step(data)) { diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index ad88cf656ad..c816c7b8db5 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -41,6 +41,10 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" +#ifdef WITH_PYTHON +# include "BPY_extern.h" +#endif + #include "atomic_ops.h" #include "intern/depsgraph.h" @@ -375,6 +379,11 @@ void deg_evaluate_on_refresh(Depsgraph *graph) graph->debug.begin_graph_evaluation(); +#ifdef WITH_PYTHON + /* Release the GIL so that Python drivers can be evaluated. See T91046. */ + BPy_BEGIN_ALLOW_THREADS; +#endif + graph->is_evaluating = true; depsgraph_ensure_view_layer(graph); /* Set up evaluation state. */ @@ -415,6 +424,10 @@ void deg_evaluate_on_refresh(Depsgraph *graph) deg_graph_clear_tags(graph); graph->is_evaluating = false; +#ifdef WITH_PYTHON + BPy_END_ALLOW_THREADS; +#endif + graph->debug.end_graph_evaluation(); } diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc index 30ec9e948fd..1081528ece1 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc @@ -98,7 +98,7 @@ void ObjectRuntimeBackup::restore_to_object(Object *object) object->runtime = runtime; object->runtime.data_orig = data_orig; object->runtime.bb = bb; - if (ELEM(object->type, OB_MESH, OB_LATTICE) && data_eval != nullptr) { + if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_CURVE, OB_FONT) && data_eval != nullptr) { if (object->id.recalc & ID_RECALC_GEOMETRY) { /* If geometry is tagged for update it means, that part of * evaluated mesh are not valid anymore. In this case we can not @@ -112,9 +112,11 @@ void ObjectRuntimeBackup::restore_to_object(Object *object) BKE_object_free_derived_caches(object); } else { - /* Do same thing as object update: override actual object data - * pointer with evaluated datablock. */ - object->data = data_eval; + /* Do same thing as object update: override actual object data pointer with evaluated + * datablock, but only if the evaluated data has the same type as the original data. */ + if (GS(((ID *)object->data)->name) == GS(data_eval->name)) { + object->data = data_eval; + } /* Evaluated mesh simply copied edit_mesh pointer from * original mesh during update, need to make sure no dead diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 8bf74dae7f8..71115c5ceb9 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -447,6 +447,7 @@ data_to_c_simple(engines/overlay/shaders/extra_wire_frag.glsl SRC) data_to_c_simple(engines/overlay/shaders/extra_wire_vert.glsl SRC) data_to_c_simple(engines/overlay/shaders/facing_frag.glsl SRC) data_to_c_simple(engines/overlay/shaders/facing_vert.glsl SRC) +data_to_c_simple(engines/overlay/shaders/grid_background_frag.glsl SRC) data_to_c_simple(engines/overlay/shaders/grid_frag.glsl SRC) data_to_c_simple(engines/overlay/shaders/grid_vert.glsl SRC) data_to_c_simple(engines/overlay/shaders/image_vert.glsl SRC) diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h index 108e7ff84d0..d3638f6be2b 100644 --- a/source/blender/draw/DRW_engine.h +++ b/source/blender/draw/DRW_engine.h @@ -117,6 +117,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, bool use_obedit_skip, bool draw_surface, bool use_nearest, + const bool do_material_sub_selection, const struct rcti *rect, DRW_SelectPassFn select_pass_fn, void *select_pass_user_data, diff --git a/source/blender/draw/engines/basic/basic_engine.c b/source/blender/draw/engines/basic/basic_engine.c index c120df7e897..87f5c6f5857 100644 --- a/source/blender/draw/engines/basic/basic_engine.c +++ b/source/blender/draw/engines/basic/basic_engine.c @@ -25,9 +25,12 @@ #include "DRW_render.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "BKE_particle.h" +#include "BLI_alloca.h" + #include "DNA_particle_types.h" #include "GPU_shader.h" @@ -80,6 +83,7 @@ typedef struct BASIC_PrivateData { DRWShadingGroup *depth_shgrp[2]; DRWShadingGroup *depth_shgrp_cull[2]; DRWShadingGroup *depth_hair_shgrp[2]; + bool use_material_slot_selection; } BASIC_PrivateData; /* Transient data */ /* Functions */ @@ -131,6 +135,8 @@ static void basic_cache_init(void *vedata) stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__); } + stl->g_data->use_material_slot_selection = DRW_state_is_material_select(); + /* Twice for normal and in front objects. */ for (int i = 0; i < 2; i++) { DRWState clip_state = (draw_ctx->sh_cfg == GPU_SHADER_CFG_CLIPPED) ? DRW_STATE_CLIP_PLANES : 0; @@ -155,6 +161,38 @@ static void basic_cache_init(void *vedata) } } +/* TODO(fclem): DRW_cache_object_surface_material_get needs a refactor to allow passing NULL + * instead of gpumat_array. Avoiding all this boilerplate code. */ +static struct GPUBatch **basic_object_surface_material_get(Object *ob) +{ + const int materials_len = DRW_cache_object_material_count_get(ob); + struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len); + memset(gpumat_array, 0, sizeof(*gpumat_array) * materials_len); + + return DRW_cache_object_surface_material_get(ob, gpumat_array, materials_len); +} + +static void basic_cache_populate_particles(void *vedata, Object *ob) +{ + const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; + BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl; + for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) { + if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) { + continue; + } + ParticleSettings *part = psys->part; + const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as; + if (draw_as == PART_DRAW_PATH) { + struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL); + if (stl->g_data->use_material_slot_selection) { + const short material_slot = part->omat; + DRW_select_load_id(ob->runtime.select_id | (material_slot << 16)); + } + DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL); + } + } +} + static void basic_cache_populate(void *vedata, Object *ob) { BASIC_StorageList *stl = ((BASIC_Data *)vedata)->stl; @@ -165,24 +203,13 @@ static void basic_cache_populate(void *vedata, Object *ob) return; } - bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; - const DRWContextState *draw_ctx = DRW_context_state_get(); if (ob != draw_ctx->object_edit) { - for (ParticleSystem *psys = ob->particlesystem.first; psys != NULL; psys = psys->next) { - if (!DRW_object_is_visible_psys_in_active_context(ob, psys)) { - continue; - } - ParticleSettings *part = psys->part; - const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as; - if (draw_as == PART_DRAW_PATH) { - struct GPUBatch *hairs = DRW_cache_particles_get_hair(ob, psys, NULL); - DRW_shgroup_call(stl->g_data->depth_hair_shgrp[do_in_front], hairs, NULL); - } - } + basic_cache_populate_particles(vedata, ob); } /* Make flat object selectable in ortho view if wireframe is enabled. */ + const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; if ((draw_ctx->v3d->overlay.flag & V3D_OVERLAY_WIREFRAMES) || (draw_ctx->v3d->shading.type == OB_WIRE) || (ob->dtx & OB_DRAWWIRE) || (ob->dt == OB_WIRE)) { int flat_axis = 0; @@ -211,9 +238,25 @@ static void basic_cache_populate(void *vedata, Object *ob) DRW_shgroup_call_sculpt(shgrp, ob, false, false); } else { - struct GPUBatch *geom = DRW_cache_object_surface_get(ob); - if (geom) { - DRW_shgroup_call(shgrp, geom, ob); + if (stl->g_data->use_material_slot_selection && BKE_object_supports_material_slots(ob)) { + struct GPUBatch **geoms = basic_object_surface_material_get(ob); + if (geoms) { + const int materials_len = DRW_cache_object_material_count_get(ob); + for (int i = 0; i < materials_len; i++) { + if (geoms[i] == NULL) { + continue; + } + const short material_slot_select_id = i + 1; + DRW_select_load_id(ob->runtime.select_id | (material_slot_select_id << 16)); + DRW_shgroup_call(shgrp, geoms[i], ob); + } + } + } + else { + struct GPUBatch *geom = DRW_cache_object_surface_get(ob); + if (geom) { + DRW_shgroup_call(shgrp, geom, ob); + } } } } diff --git a/source/blender/draw/engines/overlay/overlay_edit_text.c b/source/blender/draw/engines/overlay/overlay_edit_text.c index fd68b319f02..5356700f156 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_text.c +++ b/source/blender/draw/engines/overlay/overlay_edit_text.c @@ -180,19 +180,12 @@ static void edit_text_cache_populate_boxes(OVERLAY_Data *vedata, Object *ob) void OVERLAY_edit_text_cache_populate(OVERLAY_Data *vedata, Object *ob) { OVERLAY_PrivateData *pd = vedata->stl->pd; - Curve *cu = ob->data; struct GPUBatch *geom; bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; - bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f || cu->ext2 != 0.0f; - if ((cu->flag & CU_FAST) || !has_surface) { - geom = DRW_cache_text_edge_wire_get(ob); - if (geom) { - DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob); - } - } - else { - /* object mode draws */ + geom = DRW_cache_text_edge_wire_get(ob); + if (geom) { + DRW_shgroup_call(pd->edit_text_wire_grp[do_in_front], geom, ob); } edit_text_cache_populate_select(vedata, ob); diff --git a/source/blender/draw/engines/overlay/overlay_grid.c b/source/blender/draw/engines/overlay/overlay_grid.c index 5bb157ed081..60cda9f2d61 100644 --- a/source/blender/draw/engines/overlay/overlay_grid.c +++ b/source/blender/draw/engines/overlay/overlay_grid.c @@ -61,10 +61,19 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) if (pd->space_type == SPACE_IMAGE) { SpaceImage *sima = (SpaceImage *)draw_ctx->space_data; - shd->grid_flag = ED_space_image_has_buffer(sima) ? 0 : PLANE_IMAGE | SHOW_GRID; + if (sima->mode == SI_MODE_UV || !ED_space_image_has_buffer(sima)) { + shd->grid_flag = GRID_BACK | PLANE_IMAGE | SHOW_GRID; + } + else { + shd->grid_flag = 0; + } + shd->grid_distance = 1.0f; - copy_v3_fl3( - shd->grid_size, (float)sima->tile_grid_shape[0], (float)sima->tile_grid_shape[1], 1.0f); + copy_v3_fl3(shd->grid_size, 1.0f, 1.0f, 1.0f); + if (sima->mode == SI_MODE_UV) { + shd->grid_size[0] = (float)sima->tile_grid_shape[0]; + shd->grid_size[1] = (float)sima->tile_grid_shape[1]; + } for (int step = 0; step < 8; step++) { shd->grid_steps[step] = powf(4, step) * (1.0f / 16.0f); } @@ -209,11 +218,12 @@ void OVERLAY_grid_cache_init(OVERLAY_Data *vedata) float mat[4][4]; /* add quad background */ - sh = OVERLAY_shader_grid_image(); + sh = OVERLAY_shader_grid_background(); grp = DRW_shgroup_create(sh, psl->grid_ps); float color_back[4]; interp_v4_v4v4(color_back, G_draw.block.colorBackground, G_draw.block.colorGrid, 0.5); DRW_shgroup_uniform_vec4_copy(grp, "color", color_back); + DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth); unit_m4(mat); mat[0][0] = shd->grid_size[0]; mat[1][1] = shd->grid_size[1]; diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 68f60bee779..23df571e8de 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -722,6 +722,7 @@ GPUShader *OVERLAY_shader_extra_point(void); GPUShader *OVERLAY_shader_facing(void); GPUShader *OVERLAY_shader_gpencil_canvas(void); GPUShader *OVERLAY_shader_grid(void); +GPUShader *OVERLAY_shader_grid_background(void); GPUShader *OVERLAY_shader_grid_image(void); GPUShader *OVERLAY_shader_image(void); GPUShader *OVERLAY_shader_motion_path_line(void); diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c index edf9148c8c0..389704b3d66 100644 --- a/source/blender/draw/engines/overlay/overlay_shader.c +++ b/source/blender/draw/engines/overlay/overlay_shader.c @@ -91,6 +91,7 @@ extern char datatoc_extra_wire_frag_glsl[]; extern char datatoc_extra_wire_vert_glsl[]; extern char datatoc_facing_frag_glsl[]; extern char datatoc_facing_vert_glsl[]; +extern char datatoc_grid_background_frag_glsl[]; extern char datatoc_grid_frag_glsl[]; extern char datatoc_grid_vert_glsl[]; extern char datatoc_image_frag_glsl[]; @@ -198,6 +199,7 @@ typedef struct OVERLAY_Shaders { GPUShader *facing; GPUShader *gpencil_canvas; GPUShader *grid; + GPUShader *grid_background; GPUShader *grid_image; GPUShader *image; GPUShader *motion_path_line; @@ -1053,6 +1055,20 @@ GPUShader *OVERLAY_shader_grid(void) return sh_data->grid; } +GPUShader *OVERLAY_shader_grid_background(void) +{ + OVERLAY_Shaders *sh_data = &e_data.sh_data[0]; + if (!sh_data->grid_background) { + sh_data->grid_background = GPU_shader_create_from_arrays({ + .vert = (const char *[]){datatoc_common_view_lib_glsl, + datatoc_edit_uv_tiled_image_borders_vert_glsl, + NULL}, + .frag = (const char *[]){datatoc_grid_background_frag_glsl, NULL}, + }); + } + return sh_data->grid_background; +} + GPUShader *OVERLAY_shader_grid_image(void) { OVERLAY_Shaders *sh_data = &e_data.sh_data[0]; diff --git a/source/blender/draw/engines/overlay/overlay_wireframe.c b/source/blender/draw/engines/overlay/overlay_wireframe.c index b8a61ecc403..fde376beeb2 100644 --- a/source/blender/draw/engines/overlay/overlay_wireframe.c +++ b/source/blender/draw/engines/overlay/overlay_wireframe.c @@ -218,18 +218,10 @@ void OVERLAY_wireframe_cache_populate(OVERLAY_Data *vedata, struct GPUBatch *geom = NULL; switch (ob->type) { case OB_CURVE: - if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache && - BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) { - break; - } geom = DRW_cache_curve_edge_wire_get(ob); break; case OB_FONT: - if (!pd->wireframe_mode && !use_wire && ob->runtime.curve_cache && - BKE_displist_has_faces(&ob->runtime.curve_cache->disp)) { - break; - } - geom = DRW_cache_text_loose_edges_get(ob); + geom = DRW_cache_text_edge_wire_get(ob); break; case OB_SURF: geom = DRW_cache_surf_edge_wire_get(ob); diff --git a/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl new file mode 100644 index 00000000000..f09918da6dc --- /dev/null +++ b/source/blender/draw/engines/overlay/shaders/grid_background_frag.glsl @@ -0,0 +1,12 @@ + +uniform vec4 color; +uniform sampler2D depthBuffer; + +out vec4 fragColor; + +void main() +{ + fragColor = color; + float scene_depth = texelFetch(depthBuffer, ivec2(gl_FragCoord.xy), 0).r; + fragColor.a = (scene_depth == 1.0) ? 1.0 : 0.0; +} diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index 6639a100af9..660a4adaf51 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -731,6 +731,7 @@ void DRW_select_load_id(uint id); /* Draw State */ bool DRW_state_is_fbo(void); bool DRW_state_is_select(void); +bool DRW_state_is_material_select(void); bool DRW_state_is_depth(void); bool DRW_state_is_image_render(void); bool DRW_state_is_scene_render(void); diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 734b312bbd9..0009b24c0cb 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -832,6 +832,10 @@ GPUBatch *DRW_gpencil_dummy_buffer_get(void) /* -------------------------------------------------------------------- */ /** \name Common Object API + * + * \note Curve and text objects evaluate to the evaluated geometry set's mesh component if + * they have a surface, so curve objects themselves do not have a surface (the mesh component + * is presented to render engines as a separate object). * \{ */ GPUBatch *DRW_cache_object_all_edges_get(Object *ob) @@ -852,11 +856,11 @@ GPUBatch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold) case OB_MESH: return DRW_cache_mesh_edge_detection_get(ob, r_is_manifold); case OB_CURVE: - return DRW_cache_curve_edge_detection_get(ob, r_is_manifold); + return NULL; case OB_SURF: return DRW_cache_surf_edge_detection_get(ob, r_is_manifold); case OB_FONT: - return DRW_cache_text_edge_detection_get(ob, r_is_manifold); + return NULL; case OB_MBALL: return DRW_cache_mball_edge_detection_get(ob, r_is_manifold); case OB_HAIR: @@ -876,11 +880,11 @@ GPUBatch *DRW_cache_object_face_wireframe_get(Object *ob) case OB_MESH: return DRW_cache_mesh_face_wireframe_get(ob); case OB_CURVE: - return DRW_cache_curve_face_wireframe_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_face_wireframe_get(ob); case OB_FONT: - return DRW_cache_text_face_wireframe_get(ob); + return NULL; case OB_MBALL: return DRW_cache_mball_face_wireframe_get(ob); case OB_HAIR: @@ -903,11 +907,11 @@ GPUBatch *DRW_cache_object_loose_edges_get(struct Object *ob) case OB_MESH: return DRW_cache_mesh_loose_edges_get(ob); case OB_CURVE: - return DRW_cache_curve_loose_edges_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_loose_edges_get(ob); case OB_FONT: - return DRW_cache_text_loose_edges_get(ob); + return NULL; case OB_MBALL: return NULL; case OB_HAIR: @@ -927,11 +931,11 @@ GPUBatch *DRW_cache_object_surface_get(Object *ob) case OB_MESH: return DRW_cache_mesh_surface_get(ob); case OB_CURVE: - return DRW_cache_curve_surface_get(ob); + return NULL; case OB_SURF: return DRW_cache_surf_surface_get(ob); case OB_FONT: - return DRW_cache_text_surface_get(ob); + return NULL; case OB_MBALL: return DRW_cache_mball_surface_get(ob); case OB_HAIR: @@ -977,9 +981,9 @@ int DRW_cache_object_material_count_get(struct Object *ob) Mesh *me = BKE_object_get_evaluated_mesh(ob); if (me != NULL && type != OB_POINTCLOUD) { - /* Some object types (e.g. curves) can have a Curve in ob->data, but will be rendered as mesh. - * For point clouds this never happens. Ideally this check would happen at another level and we - * would just have to care about ob->data here. */ + /* Some object types can have one data type in ob->data, but will be rendered as mesh. + * For point clouds this never happens. Ideally this check would happen at another level + * and we would just have to care about ob->data here. */ type = OB_MESH; } @@ -1012,11 +1016,11 @@ GPUBatch **DRW_cache_object_surface_material_get(struct Object *ob, case OB_MESH: return DRW_cache_mesh_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_CURVE: - return DRW_cache_curve_surface_shaded_get(ob, gpumat_array, gpumat_array_len); + return NULL; case OB_SURF: return DRW_cache_surf_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_FONT: - return DRW_cache_text_surface_shaded_get(ob, gpumat_array, gpumat_array_len); + return NULL; case OB_MBALL: return DRW_cache_mball_surface_shaded_get(ob, gpumat_array, gpumat_array_len); case OB_HAIR: @@ -2967,20 +2971,13 @@ GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob) GPUBatch *DRW_cache_curve_edge_wire_get(Object *ob) { BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - return DRW_curve_batch_cache_get_wire_edge(cu); } GPUBatch *DRW_cache_curve_edge_normal_get(Object *ob) { BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_normal_edge(cu); } @@ -3001,75 +2998,6 @@ GPUBatch *DRW_cache_curve_vert_overlay_get(Object *ob) return DRW_curve_batch_cache_get_edit_verts(cu); } -GPUBatch *DRW_cache_curve_surface_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface(mesh_eval); - } - - return DRW_curve_batch_cache_get_triangles_with_normals(cu); -} - -GPUBatch *DRW_cache_curve_loose_edges_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - - /* TODO */ - UNUSED_VARS(cu); - return NULL; -} - -GPUBatch *DRW_cache_curve_face_wireframe_get(Object *ob) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval); - } - - return DRW_curve_batch_cache_get_wireframes_face(cu); -} - -GPUBatch *DRW_cache_curve_edge_detection_get(Object *ob, bool *r_is_manifold) -{ - BLI_assert(ob->type == OB_CURVE); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold); - } - - return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold); -} - -/* Return list of batches */ -GPUBatch **DRW_cache_curve_surface_shaded_get(Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len) -{ - BLI_assert(ob->type == OB_CURVE); - - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len); - } - - return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -3113,96 +3041,9 @@ GPUBatch *DRW_cache_text_edge_wire_get(Object *ob) { BLI_assert(ob->type == OB_FONT); struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - const bool has_surface = (cu->flag & (CU_FRONT | CU_BACK)) || cu->ext1 != 0.0f || - cu->ext2 != 0.0f; - if (!has_surface) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - - return DRW_curve_batch_cache_get_wire_edge(cu); -} - -GPUBatch *DRW_cache_text_surface_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface(mesh_eval); - } - - return DRW_curve_batch_cache_get_triangles_with_normals(cu); -} - -GPUBatch *DRW_cache_text_edge_detection_get(Object *ob, bool *r_is_manifold) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_edge_detection(mesh_eval, r_is_manifold); - } - - return DRW_curve_batch_cache_get_edge_detection(cu, r_is_manifold); -} - -GPUBatch *DRW_cache_text_loose_edges_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_loose_edges(mesh_eval); - } - return DRW_curve_batch_cache_get_wire_edge(cu); } -GPUBatch *DRW_cache_text_face_wireframe_get(Object *ob) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_wireframes_face(mesh_eval); - } - - return DRW_curve_batch_cache_get_wireframes_face(cu); -} - -GPUBatch **DRW_cache_text_surface_shaded_get(Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len) -{ - BLI_assert(ob->type == OB_FONT); - struct Curve *cu = ob->data; - struct Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); - if (cu->editfont && (cu->flag & CU_FAST)) { - return NULL; - } - if (mesh_eval != NULL) { - return DRW_mesh_batch_cache_get_surface_shaded(mesh_eval, gpumat_array, gpumat_array_len); - } - - return DRW_curve_batch_cache_get_surface_shaded(cu, gpumat_array, gpumat_array_len); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -3582,6 +3423,8 @@ void drw_batch_cache_validate(Object *ob) break; case OB_CURVE: case OB_FONT: + DRW_curve_batch_cache_validate((Curve *)ob->data); + break; case OB_SURF: if (mesh_eval != NULL) { DRW_mesh_batch_cache_validate(mesh_eval); @@ -3630,6 +3473,8 @@ void drw_batch_cache_generate_requested(Object *ob) break; case OB_CURVE: case OB_FONT: + DRW_curve_batch_cache_create_requested(ob, scene); + break; case OB_SURF: if (mesh_eval) { DRW_mesh_batch_cache_create_requested( @@ -3656,8 +3501,6 @@ void DRW_batch_cache_free_old(Object *ob, int ctime) case OB_MESH: DRW_mesh_batch_cache_free_old((Mesh *)ob->data, ctime); break; - case OB_CURVE: - case OB_FONT: case OB_SURF: if (mesh_eval) { DRW_mesh_batch_cache_free_old(mesh_eval, ctime); diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index 6b2b0a173fe..5863ada2ccf 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -154,28 +154,14 @@ struct GPUBatch *DRW_cache_mesh_surface_mesh_analysis_get(struct Object *ob); struct GPUBatch *DRW_cache_mesh_face_wireframe_get(struct Object *ob); /* Curve */ -struct GPUBatch *DRW_cache_curve_surface_get(struct Object *ob); -struct GPUBatch **DRW_cache_curve_surface_shaded_get(struct Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len); -struct GPUBatch *DRW_cache_curve_loose_edges_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_edge_wire_get(struct Object *ob); -struct GPUBatch *DRW_cache_curve_face_wireframe_get(struct Object *ob); -struct GPUBatch *DRW_cache_curve_edge_detection_get(struct Object *ob, bool *r_is_manifold); /* edit-mode */ struct GPUBatch *DRW_cache_curve_edge_normal_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_edge_overlay_get(struct Object *ob); struct GPUBatch *DRW_cache_curve_vert_overlay_get(struct Object *ob); /* Font */ -struct GPUBatch *DRW_cache_text_surface_get(struct Object *ob); -struct GPUBatch *DRW_cache_text_edge_detection_get(struct Object *ob, bool *r_is_manifold); -struct GPUBatch *DRW_cache_text_loose_edges_get(struct Object *ob); struct GPUBatch *DRW_cache_text_edge_wire_get(struct Object *ob); -struct GPUBatch **DRW_cache_text_surface_shaded_get(struct Object *ob, - struct GPUMaterial **gpumat_array, - uint gpumat_array_len); -struct GPUBatch *DRW_cache_text_face_wireframe_get(struct Object *ob); /* Surface */ struct GPUBatch *DRW_cache_surf_surface_get(struct Object *ob); diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc index 1efe0c080be..0804745fab5 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.cc +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -112,43 +112,6 @@ static void curve_render_overlay_verts_edges_len_get(ListBase *lb, } } -static void curve_render_wire_verts_edges_len_get(const CurveCache *ob_curve_cache, - int *r_curve_len, - int *r_vert_len, - int *r_edge_len) -{ - BLI_assert(r_vert_len || r_edge_len); - int vert_len = 0; - int edge_len = 0; - int curve_len = 0; - LISTBASE_FOREACH (const BevList *, bl, &ob_curve_cache->bev) { - if (bl->nr > 0) { - const bool is_cyclic = bl->poly != -1; - edge_len += (is_cyclic) ? bl->nr : bl->nr - 1; - vert_len += bl->nr; - curve_len += 1; - } - } - LISTBASE_FOREACH (const DispList *, dl, &ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - BLI_assert(dl->parts == 1); - const bool is_cyclic = dl->type == DL_POLY; - edge_len += (is_cyclic) ? dl->nr : dl->nr - 1; - vert_len += dl->nr; - curve_len += 1; - } - } - if (r_vert_len) { - *r_vert_len = vert_len; - } - if (r_edge_len) { - *r_edge_len = edge_len; - } - if (r_curve_len) { - *r_curve_len = curve_len; - } -} - static void curve_eval_render_wire_verts_edges_len_get(const CurveEval &curve_eval, int *r_curve_len, int *r_vert_len, @@ -243,7 +206,7 @@ enum { }; /* - * ob_curve_cache can be NULL, only needed for CU_DATATYPE_WIRE + * ob_curve_cache can be NULL */ static CurveRenderData *curve_render_data_create(Curve *cu, CurveCache *ob_curve_cache, @@ -267,12 +230,6 @@ static CurveRenderData *curve_render_data_create(Curve *cu, &rdata->wire.vert_len, &rdata->wire.edge_len); } - else { - curve_render_wire_verts_edges_len_get(rdata->ob_curve_cache, - &rdata->wire.curve_len, - &rdata->wire.vert_len, - &rdata->wire.edge_len); - } } if (cu->editnurb) { @@ -594,6 +551,10 @@ void DRW_curve_batch_cache_free(Curve *cu) /* GPUBatch cache usage. */ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curves_pos) { + if (rdata->curve_eval == nullptr) { + return; + } + static GPUVertFormat format = {0}; static struct { uint pos; @@ -606,46 +567,26 @@ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curv GPU_vertbuf_init_with_format(vbo_curves_pos, &format); GPU_vertbuf_data_alloc(vbo_curves_pos, vert_len); - if (rdata->curve_eval != nullptr) { - const CurveEval &curve_eval = *rdata->curve_eval; - Span<SplinePtr> splines = curve_eval.splines(); - Array<int> offsets = curve_eval.evaluated_point_offsets(); - BLI_assert(offsets.last() == vert_len); - - for (const int i_spline : splines.index_range()) { - Span<float3> positions = splines[i_spline]->evaluated_positions(); - for (const int i_point : positions.index_range()) { - GPU_vertbuf_attr_set( - vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]); - } - } - } - else { - BLI_assert(rdata->ob_curve_cache != nullptr); - - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const int i_end = v_idx + bl->nr; - for (const BevPoint *bevp = bl->bevpoints; v_idx < i_end; v_idx++, bevp++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, bevp->vec); - } - } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - for (int i = 0; i < dl->nr; v_idx++, i++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, &((float(*)[3])dl->verts)[i]); - } - } + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); + + for (const int i_spline : splines.index_range()) { + Span<float3> positions = splines[i_spline]->evaluated_positions(); + for (const int i_point : positions.index_range()) { + GPU_vertbuf_attr_set( + vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]); } - BLI_assert(v_idx == vert_len); } } static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_curve_lines) { + if (rdata->curve_eval == nullptr) { + return; + } + const int vert_len = curve_render_data_wire_verts_len_get(rdata); const int edge_len = curve_render_data_wire_edges_len_get(rdata); const int curve_len = curve_render_data_wire_curve_len_get(rdata); @@ -655,54 +596,20 @@ static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_c GPUIndexBufBuilder elb; GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINE_STRIP, index_len, vert_len); - if (rdata->curve_eval != nullptr) { - const CurveEval &curve_eval = *rdata->curve_eval; - Span<SplinePtr> splines = curve_eval.splines(); - Array<int> offsets = curve_eval.evaluated_point_offsets(); - BLI_assert(offsets.last() == vert_len); - - for (const int i_spline : splines.index_range()) { - const int eval_size = splines[i_spline]->evaluated_points_size(); - if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) { - GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1); - } - for (const int i_point : IndexRange(eval_size)) { - GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point); - } - GPU_indexbuf_add_primitive_restart(&elb); - } - } - else { - BLI_assert(rdata->ob_curve_cache != nullptr); + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const bool is_cyclic = bl->poly != -1; - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (bl->nr - 1)); - } - for (int i = 0; i < bl->nr; i++) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + i); - } - GPU_indexbuf_add_primitive_restart(&elb); - v_idx += bl->nr; + for (const int i_spline : splines.index_range()) { + const int eval_size = splines[i_spline]->evaluated_points_size(); + if (splines[i_spline]->is_cyclic() && splines[i_spline]->evaluated_edges_size() > 1) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1); } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - const bool is_cyclic = dl->type == DL_POLY; - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (dl->nr - 1)); - } - for (int i = 0; i < dl->nr; i++) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + i); - } - GPU_indexbuf_add_primitive_restart(&elb); - v_idx += dl->nr; - } + for (const int i_point : IndexRange(eval_size)) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point); } + GPU_indexbuf_add_primitive_restart(&elb); } GPU_indexbuf_build_in_place(&elb, ibo_curve_lines); diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 098eb2e2fcc..e49f5235c15 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -634,14 +634,29 @@ void DRW_viewport_request_redraw(void) /** \name Duplis * \{ */ -static void drw_duplidata_load(DupliObject *dupli) +static uint dupli_key_hash(const void *key) { + const DupliKey *dupli_key = (const DupliKey *)key; + return BLI_ghashutil_ptrhash(dupli_key->ob) ^ BLI_ghashutil_ptrhash(dupli_key->ob_data); +} + +static bool dupli_key_cmp(const void *key1, const void *key2) +{ + const DupliKey *dupli_key1 = (const DupliKey *)key1; + const DupliKey *dupli_key2 = (const DupliKey *)key2; + return dupli_key1->ob != dupli_key2->ob || dupli_key1->ob_data != dupli_key2->ob_data; +} + +static void drw_duplidata_load(Object *ob) +{ + DupliObject *dupli = DST.dupli_source; if (dupli == NULL) { return; } - if (DST.dupli_origin != dupli->ob) { + if (DST.dupli_origin != dupli->ob || (DST.dupli_origin_data != dupli->ob_data)) { DST.dupli_origin = dupli->ob; + DST.dupli_origin_data = dupli->ob_data; } else { /* Same data as previous iter. No need to poll ghash for this. */ @@ -649,16 +664,23 @@ static void drw_duplidata_load(DupliObject *dupli) } if (DST.dupli_ghash == NULL) { - DST.dupli_ghash = BLI_ghash_ptr_new(__func__); + DST.dupli_ghash = BLI_ghash_new(dupli_key_hash, dupli_key_cmp, __func__); } + DupliKey *key = MEM_callocN(sizeof(DupliKey), __func__); + key->ob = dupli->ob; + key->ob_data = dupli->ob_data; + void **value; - if (!BLI_ghash_ensure_p(DST.dupli_ghash, DST.dupli_origin, &value)) { + if (!BLI_ghash_ensure_p(DST.dupli_ghash, key, &value)) { *value = MEM_callocN(sizeof(void *) * DST.enabled_engine_count, __func__); /* TODO: Meh a bit out of place but this is nice as it is - * only done once per "original" object. */ - drw_batch_cache_validate(DST.dupli_origin); + * only done once per instance type. */ + drw_batch_cache_validate(ob); + } + else { + MEM_freeN(key); } DST.dupli_datas = *(void ***)value; } @@ -672,12 +694,24 @@ static void duplidata_value_free(void *val) MEM_freeN(val); } +static void duplidata_key_free(void *key) +{ + DupliKey *dupli_key = (DupliKey *)key; + if (dupli_key->ob_data == dupli_key->ob->data) { + drw_batch_cache_generate_requested(dupli_key->ob); + } + else { + Object temp_object = *dupli_key->ob; + BKE_object_replace_data_on_shallow_copy(&temp_object, dupli_key->ob_data); + drw_batch_cache_generate_requested(&temp_object); + } + MEM_freeN(key); +} + static void drw_duplidata_free(void) { if (DST.dupli_ghash != NULL) { - BLI_ghash_free(DST.dupli_ghash, - (void (*)(void *key))drw_batch_cache_generate_requested, - duplidata_value_free); + BLI_ghash_free(DST.dupli_ghash, duplidata_key_free, duplidata_value_free); DST.dupli_ghash = NULL; } } @@ -1525,6 +1559,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, /* Only iterate over objects for internal engines or when overlays are enabled */ if (do_populate_loop) { DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) != 0) { continue; @@ -1534,7 +1569,7 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, } DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END; @@ -1883,12 +1918,13 @@ void DRW_render_object_iter( draw_ctx->v3d->object_type_exclude_viewport : 0; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) == 0) { DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; DST.ob_handle = 0; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); if (!DST.dupli_source) { drw_batch_cache_validate(ob); @@ -2198,6 +2234,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, bool use_obedit_skip, bool draw_surface, bool UNUSED(use_nearest), + const bool do_material_sub_selection, const rcti *rect, DRW_SelectPassFn select_pass_fn, void *select_pass_user_data, @@ -2265,6 +2302,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, DST.viewport = viewport; DST.options.is_select = true; + DST.options.is_material_select = do_material_sub_selection; drw_task_graph_init(); /* Get list of enabled engines */ if (use_obedit) { @@ -2332,6 +2370,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, v3d->object_type_exclude_select); bool filter_exclude = false; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob) { if (!BKE_object_is_visible_in_viewport(v3d, ob)) { continue; @@ -2364,7 +2403,7 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, DRW_select_load_id(ob->runtime.select_id); DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } } @@ -2477,6 +2516,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph, const int object_type_exclude_viewport = v3d->object_type_exclude_viewport; DST.dupli_origin = NULL; + DST.dupli_origin_data = NULL; DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (DST.draw_ctx.depsgraph, ob) { if ((object_type_exclude_viewport & (1 << ob->type)) != 0) { continue; @@ -2486,7 +2526,7 @@ static void drw_draw_depth_loop_impl(struct Depsgraph *depsgraph, } DST.dupli_parent = data_.dupli_parent; DST.dupli_source = data_.dupli_object_current; - drw_duplidata_load(DST.dupli_source); + drw_duplidata_load(ob); drw_engines_cache_populate(ob); } DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END; @@ -2740,6 +2780,11 @@ bool DRW_state_is_select(void) return DST.options.is_select; } +bool DRW_state_is_material_select(void) +{ + return DST.options.is_material_select; +} + bool DRW_state_is_depth(void) { return DST.options.is_depth; diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h index 33e1a57198c..c09126c98ef 100644 --- a/source/blender/draw/intern/draw_manager.h +++ b/source/blender/draw/intern/draw_manager.h @@ -497,6 +497,11 @@ typedef struct DRWDebugSphere { /* ------------- DRAW MANAGER ------------ */ +typedef struct DupliKey { + struct Object *ob; + struct ID *ob_data; +} DupliKey; + #define DST_MAX_SLOTS 64 /* Cannot be changed without modifying RST.bound_tex_slots */ #define MAX_CLIP_PLANES 6 /* GL_MAX_CLIP_PLANES is at least 6 */ #define STENCIL_UNDEFINED 256 @@ -515,15 +520,19 @@ typedef struct DRWManager { /** Handle of next DRWPass to be allocated. */ DRWResourceHandle pass_handle; - /** Dupli state. NULL if not dupli. */ + /** Dupli object that corresponds to the current object. */ struct DupliObject *dupli_source; + /** Object that created the dupli-list the current object is part of. */ struct Object *dupli_parent; + /** Object referenced by the current dupli object. */ struct Object *dupli_origin; - /** Ghash containing original objects. */ + /** Object-data referenced by the current dupli object. */ + struct ID *dupli_origin_data; + /** Ghash: #DupliKey -> void pointer for each enabled engine. */ struct GHash *dupli_ghash; /** TODO(fclem): try to remove usage of this. */ DRWInstanceData *object_instance_data[MAX_INSTANCE_DATA_SIZE]; - /* Array of dupli_data (one for each enabled engine) to handle duplis. */ + /* Dupli data for the current dupli for each enabled engine. */ void **dupli_datas; /* Rendering state */ @@ -544,6 +553,7 @@ typedef struct DRWManager { struct { uint is_select : 1; + uint is_material_select : 1; uint is_depth : 1; uint is_image_render : 1; uint is_scene_render : 1; diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c index 916d4232f03..97679723d84 100644 --- a/source/blender/editors/animation/anim_deps.c +++ b/source/blender/editors/animation/anim_deps.c @@ -206,16 +206,17 @@ static void animchan_sync_fcurve_scene(bAnimListElem *ale) BLI_assert(GS(owner_id->name) == ID_SCE); Scene *scene = (Scene *)owner_id; FCurve *fcu = (FCurve *)ale->data; + Sequence *seq = NULL; /* Only affect if F-Curve involves sequence_editor.sequences. */ - char *seq_name = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all["); - if (seq_name == NULL) { + char seq_name[sizeof(seq->name)]; + if (!BLI_str_quoted_substr(fcu->rna_path, "sequences_all[", seq_name, sizeof(seq_name))) { return; } /* Check if this strip is selected. */ Editing *ed = SEQ_editing_get(scene); - Sequence *seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false); + seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false); MEM_freeN(seq_name); if (seq == NULL) { diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c index 6d272bfc180..993d10cf303 100644 --- a/source/blender/editors/animation/anim_draw.c +++ b/source/blender/editors/animation/anim_draw.c @@ -521,6 +521,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_ MaskLayer *masklay = BKE_mask_layer_active(mask); mask_to_keylist(&ads, masklay, keylist); } + ED_keylist_prepare_for_direct_access(keylist); /* TODO(jbakker): Keylists are ordered, no need to do any searching at all. */ /* find matching keyframe in the right direction */ diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index 7e3e3f363c2..b12e0ae5cab 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -1061,13 +1061,14 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id if (GS(owner_id->name) == ID_OB) { Object *ob = (Object *)owner_id; - char *bone_name; + bPoseChannel *pchan = NULL; + char bone_name[sizeof(pchan->name)]; /* Only consider if F-Curve involves `pose.bones`. */ - if (fcu->rna_path && (bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["))) { + if (fcu->rna_path && + BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) { /* Get bone-name, and check if this bone is selected. */ - bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name); - MEM_freeN(bone_name); + pchan = BKE_pose_channel_find_name(ob->pose, bone_name); /* check whether to continue or skip */ if (pchan && pchan->bone) { @@ -1097,21 +1098,41 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id } else if (GS(owner_id->name) == ID_SCE) { Scene *scene = (Scene *)owner_id; - char *seq_name; + Sequence *seq = NULL; + char seq_name[sizeof(seq->name)]; /* Only consider if F-Curve involves `sequence_editor.sequences`. */ - if (fcu->rna_path && (seq_name = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all["))) { + if (fcu->rna_path && + BLI_str_quoted_substr(fcu->rna_path, "sequences_all[", seq_name, sizeof(seq_name))) { /* Get strip name, and check if this strip is selected. */ - Sequence *seq = NULL; Editing *ed = SEQ_editing_get(scene); if (ed) { seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false); } - MEM_freeN(seq_name); /* Can only add this F-Curve if it is selected. */ if (ads->filterflag & ADS_FILTER_ONLYSEL) { - if ((seq == NULL) || (seq->flag & SELECT) == 0) { + + /* NOTE(@campbellbarton): The `seq == NULL` check doesn't look right + * (compared to other checks in this function which skip data that can't be found). + * + * This is done since the search for sequence strips doesn't use a global lookup: + * - Nested meta-strips are excluded. + * - When inside a meta-strip - strips outside the meta-strip excluded. + * + * Instead, only the strips directly visible to the user are considered for selection. + * The NULL check here means everything else is considered unselected and is not shown. + * + * There is a subtle difference between nodes, pose-bones ... etc + * since data-paths that point to missing strips are not shown. + * If this is an important difference, the NULL case could perform a global lookup, + * only returning `true` if the sequence strip exists elsewhere + * (ignoring it's selection state). */ + if (seq == NULL) { + return true; + } + + if ((seq->flag & SELECT) == 0) { return true; } } @@ -1119,14 +1140,14 @@ static bool skip_fcurve_selected_data(bDopeSheet *ads, FCurve *fcu, ID *owner_id } else if (GS(owner_id->name) == ID_NT) { bNodeTree *ntree = (bNodeTree *)owner_id; - char *node_name; + bNode *node = NULL; + char node_name[sizeof(node->name)]; /* Check for selected nodes. */ - if (fcu->rna_path && (node_name = BLI_str_quoted_substrN(fcu->rna_path, "nodes["))) { - bNode *node = NULL; + if (fcu->rna_path && + (BLI_str_quoted_substr(fcu->rna_path, "nodes[", node_name, sizeof(node_name)))) { /* Get strip name, and check if this strip is selected. */ node = nodeFindNodebyName(ntree, node_name); - MEM_freeN(node_name); /* Can only add this F-Curve if it is selected. */ if (node) { diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c index eda87cf1897..33b4882927a 100644 --- a/source/blender/editors/animation/anim_ipo_utils.c +++ b/source/blender/editors/animation/anim_ipo_utils.c @@ -106,23 +106,14 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu) * - If a pointer just refers to the ID-block, then don't repeat this info * since this just introduces clutter. */ - if (strstr(fcu->rna_path, "bones") && strstr(fcu->rna_path, "constraints")) { - /* perform string 'chopping' to get "Bone Name : Constraint Name" */ - char *pchanName = BLI_str_quoted_substrN(fcu->rna_path, "bones["); - char *constName = BLI_str_quoted_substrN(fcu->rna_path, "constraints["); + + char pchanName[256], constName[256]; + if (BLI_str_quoted_substr(fcu->rna_path, "bones[", pchanName, sizeof(pchanName)) && + BLI_str_quoted_substr(fcu->rna_path, "constraints[", constName, sizeof(constName))) { /* assemble the string to display in the UI... */ - structname = BLI_sprintfN( - "%s : %s", pchanName ? pchanName : "", constName ? constName : ""); + structname = BLI_sprintfN("%s : %s", pchanName, constName); free_structname = 1; - - /* free the temp names */ - if (pchanName) { - MEM_freeN(pchanName); - } - if (constName) { - MEM_freeN(constName); - } } else if (ptr.data != ptr.owner_id) { PropertyRNA *nameprop = RNA_struct_name_property(ptr.type); @@ -139,18 +130,15 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu) * displaying the struct name alone is no meaningful information (and also cannot be * filtered well), same for modifiers. So display strip name alongside as well. */ if (GS(ptr.owner_id->name) == ID_SCE) { - if (BLI_str_startswith(fcu->rna_path, "sequence_editor.sequences_all[\"")) { + char stripname[256]; + if (BLI_str_quoted_substr( + fcu->rna_path, "sequence_editor.sequences_all[", stripname, sizeof(stripname))) { if (strstr(fcu->rna_path, ".transform.") || strstr(fcu->rna_path, ".crop.") || strstr(fcu->rna_path, ".modifiers[")) { - char *stripname = BLI_str_quoted_substrN(fcu->rna_path, "sequences_all["); - const char *structname_all = BLI_sprintfN( - "%s : %s", stripname ? stripname : "", structname); + const char *structname_all = BLI_sprintfN("%s : %s", stripname, structname); if (free_structname) { MEM_freeN((void *)structname); } - if (stripname) { - MEM_freeN(stripname); - } structname = structname_all; free_structname = 1; } diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c index d976f5f72ad..d130941b9bc 100644 --- a/source/blender/editors/animation/anim_motion_paths.c +++ b/source/blender/editors/animation/anim_motion_paths.c @@ -329,6 +329,7 @@ static void motionpath_calculate_update_range(MPathTarget *mpt, for (FCurve *fcu = fcurve_list->first; fcu != NULL; fcu = fcu->next) { struct AnimKeylist *keylist = ED_keylist_create(); fcurve_to_keylist(adt, fcu, keylist, 0); + ED_keylist_prepare_for_direct_access(keylist); int fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, keylist, current_frame); int fcu_efra = motionpath_get_next_next_keyframe(mpt, keylist, current_frame); @@ -443,6 +444,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph, action_to_keylist(adt, adt->action, mpt->keylist, 0); } } + ED_keylist_prepare_for_direct_access(mpt->keylist); if (range == ANIMVIZ_CALC_RANGE_CHANGED) { int mpt_sfra, mpt_efra; diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index 61918871b90..ac7db9f4f46 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -30,6 +30,7 @@ #include "BLI_dlrbTree.h" #include "BLI_listbase.h" #include "BLI_rect.h" +#include "BLI_task.h" #include "DNA_anim_types.h" #include "DNA_gpencil_types.h" @@ -346,10 +347,12 @@ static void draw_keylist_block(const DrawKeylistUIData *ctx, const ActKeyColumn } static void draw_keylist_blocks(const DrawKeylistUIData *ctx, - const ListBase * /*ActKeyColumn*/ columns, + const ActKeyColumn *keys, + const int key_len, float ypos) { - LISTBASE_FOREACH (ActKeyColumn *, ab, columns) { + for (int i = 0; i < key_len; i++) { + const ActKeyColumn *ab = &keys[i]; draw_keylist_block(ctx, ab, ypos); } } @@ -362,13 +365,15 @@ static bool draw_keylist_is_visible_key(const View2D *v2d, const ActKeyColumn *a static void draw_keylist_keys(const DrawKeylistUIData *ctx, View2D *v2d, const KeyframeShaderBindings *sh_bindings, - const ListBase * /*ActKeyColumn*/ keys, + const ActKeyColumn *keys, + const int key_len, float ypos, eSAction_Flag saction_flag) { short handle_type = KEYFRAME_HANDLE_NONE, extreme_type = KEYFRAME_EXTREME_NONE; - LISTBASE_FOREACH (ActKeyColumn *, ak, keys) { + for (int i = 0; i < key_len; i++) { + const ActKeyColumn *ak = &keys[i]; if (draw_keylist_is_visible_key(v2d, ak)) { if (ctx->show_ipo) { handle_type = ak->handle_type; @@ -469,8 +474,9 @@ static void ED_keylist_draw_list_elem_draw_blocks(AnimKeylistDrawListElem *elem, DrawKeylistUIData ctx; draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag); - const ListBase *keys = ED_keylist_listbase(elem->keylist); - draw_keylist_blocks(&ctx, keys, elem->ypos); + const int key_len = ED_keylist_array_len(elem->keylist); + const ActKeyColumn *keys = ED_keylist_array(elem->keylist); + draw_keylist_blocks(&ctx, keys, key_len, elem->ypos); } static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem, @@ -479,8 +485,15 @@ static void ED_keylist_draw_list_elem_draw_keys(AnimKeylistDrawListElem *elem, { DrawKeylistUIData ctx; draw_keylist_ui_data_init(&ctx, v2d, elem->yscale_fac, elem->channel_locked, elem->saction_flag); - const ListBase *keys = ED_keylist_listbase(elem->keylist); - draw_keylist_keys(&ctx, v2d, sh_bindings, keys, elem->ypos, elem->saction_flag); + + const int key_len = ED_keylist_array_len(elem->keylist); + const ActKeyColumn *keys = ED_keylist_array(elem->keylist); + draw_keylist_keys(&ctx, v2d, sh_bindings, keys, key_len, elem->ypos, elem->saction_flag); +} + +static void ED_keylist_draw_list_elem_prepare_for_drawing(AnimKeylistDrawListElem *elem) +{ + ED_keylist_prepare_for_direct_access(elem->keylist); } typedef struct AnimKeylistDrawList { @@ -492,11 +505,25 @@ AnimKeylistDrawList *ED_keylist_draw_list_create(void) return MEM_callocN(sizeof(AnimKeylistDrawList), __func__); } +static void ED_keylist_draw_list_elem_build_task(void *__restrict UNUSED(userdata), + void *item, + int UNUSED(index), + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + AnimKeylistDrawListElem *elem = item; + ED_keylist_draw_list_elem_build_keylist(elem); + ED_keylist_draw_list_elem_prepare_for_drawing(elem); +} + static void ED_keylist_draw_list_build_keylists(AnimKeylistDrawList *draw_list) { - LISTBASE_FOREACH (AnimKeylistDrawListElem *, elem, &draw_list->channels) { - ED_keylist_draw_list_elem_build_keylist(elem); - } + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + /* Create a task per item, a single item is complex enough to deserve its own task. */ + settings.min_iter_per_thread = 1; + + BLI_task_parallel_listbase( + &draw_list->channels, NULL, ED_keylist_draw_list_elem_build_task, &settings); } static void ED_keylist_draw_list_draw_blocks(AnimKeylistDrawList *draw_list, View2D *v2d) diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index 9f3fe239113..ec33a42af3b 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -761,11 +761,10 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data) if ((aci->id_type == ID_OB) && (((Object *)aci->id)->type == OB_ARMATURE) && aci->rna_path) { Object *ob = (Object *)aci->id; - char *bone_name = BLI_str_quoted_substrN(aci->rna_path, "pose.bones["); - if (bone_name) { - bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name); - MEM_freeN(bone_name); - + bPoseChannel *pchan; + char bone_name[sizeof(pchan->name)]; + if (BLI_str_quoted_substr(aci->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) { + pchan = BKE_pose_channel_find_name(ob->pose, bone_name); if (pchan) { aci->is_bone = true; } diff --git a/source/blender/editors/animation/keyframes_keylist.cc b/source/blender/editors/animation/keyframes_keylist.cc index f6ade11a517..c1a18196a3a 100644 --- a/source/blender/editors/animation/keyframes_keylist.cc +++ b/source/blender/editors/animation/keyframes_keylist.cc @@ -23,15 +23,20 @@ /* System includes ----------------------------------------------------- */ +#include <algorithm> #include <cfloat> #include <cmath> #include <cstdlib> #include <cstring> +#include <functional> +#include <optional> #include "MEM_guardedalloc.h" +#include "BLI_array.hh" #include "BLI_dlrbTree.h" #include "BLI_listbase.h" +#include "BLI_math.h" #include "BLI_range.h" #include "BLI_utildefines.h" @@ -50,117 +55,294 @@ extern "C" { /* *************************** Keyframe Processing *************************** */ -struct AnimKeylist { - DLRBT_Tree keys; -}; +/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ + +BLI_INLINE bool is_cfra_eq(const float a, const float b) +{ + return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); +} -static void ED_keylist_init(AnimKeylist *keylist) +BLI_INLINE bool is_cfra_lt(const float a, const float b) { - BLI_dlrbTree_init(&keylist->keys); + return (b - a) > BEZT_BINARYSEARCH_THRESH; } +/* --------------- */ + +struct AnimKeylist { + /* Number of ActKeyColumn's in the keylist. */ + size_t column_len = 0; + + bool is_runtime_initialized = false; + + /* Before initializing the runtime, the key_columns list base is used to quickly add columns. + * Contains `ActKeyColumn`. Should not be used after runtime is initialized. */ + ListBase /* ActKeyColumn */ key_columns; + /* Last accessed column in the key_columns list base. Inserting columns are typically done in + * order. The last accessed column is used as starting point to search for a location to add or + * update the next column.*/ + std::optional<ActKeyColumn *> last_accessed_column = std::nullopt; + + struct { + /* When initializing the runtime the columns from the list base `AnimKeyList.key_columns` are + * transferred to an array to support binary searching and index based access. */ + blender::Array<ActKeyColumn> key_columns; + /* Wrapper around runtime.key_columns so it can still be accessed as a ListBase. Elements are + * owned by runtime.key_columns. */ + ListBase /* ActKeyColumn */ list_wrapper; + } runtime; + + AnimKeylist() + { + BLI_listbase_clear(&this->key_columns); + BLI_listbase_clear(&this->runtime.list_wrapper); + } + + ~AnimKeylist() + { + BLI_freelistN(&this->key_columns); + BLI_listbase_clear(&this->runtime.list_wrapper); + } + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("editors:AnimKeylist") +#endif +}; + AnimKeylist *ED_keylist_create(void) { - AnimKeylist *keylist = static_cast<AnimKeylist *>(MEM_callocN(sizeof(AnimKeylist), __func__)); - ED_keylist_init(keylist); + AnimKeylist *keylist = new AnimKeylist(); return keylist; } void ED_keylist_free(AnimKeylist *keylist) { BLI_assert(keylist); - BLI_dlrbTree_free(&keylist->keys); - MEM_freeN(keylist); + delete keylist; } -const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, float cfra) +static void ED_keylist_convert_key_columns_to_array(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_exact( - &keylist->keys, compare_ak_cfraPtr, &cfra); + size_t index; + LISTBASE_FOREACH_INDEX (ActKeyColumn *, key, &keylist->key_columns, index) { + keylist->runtime.key_columns[index] = *key; + } } -const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, float cfra) +static void ED_keylist_runtime_update_key_column_next_prev(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_next(&keylist->keys, compare_ak_cfraPtr, &cfra); + for (size_t index = 0; index < keylist->column_len; index++) { + const bool is_first = (index == 0); + keylist->runtime.key_columns[index].prev = is_first ? nullptr : + &keylist->runtime.key_columns[index - 1]; + const bool is_last = (index == keylist->column_len - 1); + keylist->runtime.key_columns[index].next = is_last ? nullptr : + &keylist->runtime.key_columns[index + 1]; + } } -const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, float cfra) +static void ED_keylist_runtime_init_listbase(AnimKeylist *keylist) { - return (const ActKeyColumn *)BLI_dlrbTree_search_prev(&keylist->keys, compare_ak_cfraPtr, &cfra); + if (ED_keylist_is_empty(keylist)) { + BLI_listbase_clear(&keylist->runtime.list_wrapper); + return; + } + + keylist->runtime.list_wrapper.first = &keylist->runtime.key_columns[0]; + keylist->runtime.list_wrapper.last = &keylist->runtime.key_columns[keylist->column_len - 1]; } -/* TODO(jbakker): Should we change this to use `ED_keylist_find_next(keys, min_fra)` and only check - * boundary of `max_fra`. */ -const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist, - const Range2f frame_range) +static void ED_keylist_runtime_init(AnimKeylist *keylist) { - for (const ActKeyColumn *ak = static_cast<const ActKeyColumn *>(keylist->keys.root); ak; - ak = static_cast<const ActKeyColumn *>((ak->cfra < frame_range.min) ? ak->right : - ak->left)) { - if (range2f_in_range(&frame_range, ak->cfra)) { - return ak; - } + BLI_assert(!keylist->is_runtime_initialized); + + keylist->runtime.key_columns = blender::Array<ActKeyColumn>(keylist->column_len); + + /* Convert linked list to array to support fast searching. */ + ED_keylist_convert_key_columns_to_array(keylist); + /* Ensure that the array can also be used as a listbase for external usages. */ + ED_keylist_runtime_update_key_column_next_prev(keylist); + ED_keylist_runtime_init_listbase(keylist); + + keylist->is_runtime_initialized = true; +} + +static void ED_keylist_reset_last_accessed(AnimKeylist *keylist) +{ + BLI_assert(!keylist->is_runtime_initialized); + keylist->last_accessed_column.reset(); +} + +void ED_keylist_prepare_for_direct_access(AnimKeylist *keylist) +{ + if (keylist->is_runtime_initialized) { + return; + } + ED_keylist_runtime_init(keylist); +} + +static const ActKeyColumn *ED_keylist_find_lower_bound(const AnimKeylist *keylist, + const float cfra) +{ + BLI_assert(!ED_keylist_is_empty(keylist)); + const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + ActKeyColumn value; + value.cfra = cfra; + + const ActKeyColumn *found_column = std::lower_bound( + begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) { + return is_cfra_lt(column.cfra, other.cfra); + }); + return found_column; +} + +static const ActKeyColumn *ED_keylist_find_upper_bound(const AnimKeylist *keylist, + const float cfra) +{ + BLI_assert(!ED_keylist_is_empty(keylist)); + const ActKeyColumn *begin = std::begin(keylist->runtime.key_columns); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + ActKeyColumn value; + value.cfra = cfra; + + const ActKeyColumn *found_column = std::upper_bound( + begin, end, value, [](const ActKeyColumn &column, const ActKeyColumn &other) { + return is_cfra_lt(column.cfra, other.cfra); + }); + return found_column; +} + +const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, const float cfra) +{ + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra); + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (found_column == end) { + return nullptr; + } + if (is_cfra_eq(found_column->cfra, cfra)) { + return found_column; } return nullptr; } -bool ED_keylist_is_empty(const struct AnimKeylist *keylist) +const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, const float cfra) { - return keylist->keys.root == nullptr; + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *found_column = ED_keylist_find_upper_bound(keylist, cfra); + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (found_column == end) { + return nullptr; + } + return found_column; } -const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist) +const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, const float cfra) { - return (ListBase *)&keylist->keys; + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); + + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + const ActKeyColumn *found_column = ED_keylist_find_lower_bound(keylist, cfra); + + if (found_column == end) { + /* Nothing found, return the last item. */ + return end - 1; + } + + const ActKeyColumn *prev_column = found_column->prev; + return prev_column; } -bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range) +const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist, + const Range2f frame_range) { - BLI_assert(r_frame_range); + BLI_assert_msg(keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before searching."); if (ED_keylist_is_empty(keylist)) { - return false; + return nullptr; } - const ActKeyColumn *first_column = (const ActKeyColumn *)keylist->keys.first; - r_frame_range->min = first_column->cfra; + const ActKeyColumn *column = ED_keylist_find_lower_bound(keylist, frame_range.min); + const ActKeyColumn *end = std::end(keylist->runtime.key_columns); + if (column == end) { + return nullptr; + } + if (column->cfra >= frame_range.max) { + return nullptr; + } + return column; +} - const ActKeyColumn *last_column = (const ActKeyColumn *)keylist->keys.last; - r_frame_range->max = last_column->cfra; +const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist) +{ + BLI_assert_msg( + keylist->is_runtime_initialized, + "ED_keylist_prepare_for_direct_access needs to be called before accessing array."); + return keylist->runtime.key_columns.data(); +} - return true; +int64_t ED_keylist_array_len(const struct AnimKeylist *keylist) +{ + return keylist->column_len; } -/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ -BLI_INLINE bool is_cfra_eq(const float a, const float b) +bool ED_keylist_is_empty(const struct AnimKeylist *keylist) { - return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); + return keylist->column_len == 0; } -BLI_INLINE bool is_cfra_lt(const float a, const float b) +const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist) { - return (b - a) > BEZT_BINARYSEARCH_THRESH; + if (keylist->is_runtime_initialized) { + return &keylist->runtime.list_wrapper; + } + return &keylist->key_columns; } -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */ -short compare_ak_cfraPtr(void *node, void *data) +bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const float *cframe = static_cast<const float *>(data); - const float val = *cframe; + BLI_assert(r_frame_range); - if (is_cfra_eq(val, ak->cfra)) { - return 0; + if (ED_keylist_is_empty(keylist)) { + return false; } - if (val < ak->cfra) { - return -1; + const ActKeyColumn *first_column; + const ActKeyColumn *last_column; + if (keylist->is_runtime_initialized) { + first_column = &keylist->runtime.key_columns[0]; + last_column = &keylist->runtime.key_columns[keylist->column_len - 1]; } - return 1; -} + else { + first_column = static_cast<const ActKeyColumn *>(keylist->key_columns.first); + last_column = static_cast<const ActKeyColumn *>(keylist->key_columns.last); + } + r_frame_range->min = first_column->cfra; + r_frame_range->max = last_column->cfra; -/* --------------- */ + return true; +} /* Set of references to three logically adjacent keys. */ struct BezTripleChain { @@ -243,16 +425,8 @@ static eKeyframeExtremeDrawOpts bezt_extreme_type(const BezTripleChain *chain) return KEYFRAME_EXTREME_NONE; } -/* Comparator callback used for ActKeyColumns and BezTripleChain */ -static short compare_ak_bezt(void *node, void *data) -{ - BezTripleChain *chain = static_cast<BezTripleChain *>(data); - - return compare_ak_cfraPtr(node, &chain->cur->vec[1][0]); -} - /* New node callback used for building ActKeyColumns from BezTripleChain */ -static DLRBT_Node *nalloc_ak_bezt(void *data) +static ActKeyColumn *nalloc_ak_bezt(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn")); @@ -269,13 +443,12 @@ static DLRBT_Node *nalloc_ak_bezt(void *data) /* count keyframes in this column */ ak->totkey = 1; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from BezTripleChain */ -static void nupdate_ak_bezt(void *node, void *data) +static void nupdate_ak_bezt(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = static_cast<ActKeyColumn *>(node); const BezTripleChain *chain = static_cast<const BezTripleChain *>(data); const BezTriple *bezt = chain->cur; @@ -312,17 +485,8 @@ static void nupdate_ak_bezt(void *node, void *data) /* ......... */ -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_gpframe(void *node, void *data) -{ - const bGPDframe *gpf = (bGPDframe *)data; - - float frame = gpf->framenum; - return compare_ak_cfraPtr(node, &frame); -} - /* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_gpframe(void *data) +static ActKeyColumn *nalloc_ak_gpframe(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF")); @@ -340,14 +504,13 @@ static DLRBT_Node *nalloc_ak_gpframe(void *data) ak->block.sel = ak->sel; ak->block.flag |= ACTKEYBLOCK_FLAG_GPENCIL; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_gpframe(void *node, void *data) +static void nupdate_ak_gpframe(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const bGPDframe *gpf = (bGPDframe *)data; + bGPDframe *gpf = (bGPDframe *)data; /* set selection status and 'touched' status */ if (gpf->flag & GP_FRAME_SELECT) { @@ -366,17 +529,8 @@ static void nupdate_ak_gpframe(void *node, void *data) /* ......... */ -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_masklayshape(void *node, void *data) -{ - const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data; - - float frame = masklay_shape->frame; - return compare_ak_cfraPtr(node, &frame); -} - /* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_masklayshape(void *data) +static ActKeyColumn *nalloc_ak_masklayshape(void *data) { ActKeyColumn *ak = static_cast<ActKeyColumn *>( MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF")); @@ -389,14 +543,13 @@ static DLRBT_Node *nalloc_ak_masklayshape(void *data) /* count keyframes in this column */ ak->totkey = 1; - return (DLRBT_Node *)ak; + return ak; } /* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_masklayshape(void *node, void *data) +static void nupdate_ak_masklayshape(ActKeyColumn *ak, void *data) { - ActKeyColumn *ak = (ActKeyColumn *)node; - const MaskLayerShape *masklay_shape = (const MaskLayerShape *)data; + MaskLayerShape *masklay_shape = (MaskLayerShape *)data; /* set selection status and 'touched' status */ if (masklay_shape->flag & MASK_SHAPE_SELECT) { @@ -408,6 +561,95 @@ static void nupdate_ak_masklayshape(void *node, void *data) } /* --------------- */ +using KeylistCreateColumnFunction = std::function<ActKeyColumn *(void *userdata)>; +using KeylistUpdateColumnFunction = std::function<void(ActKeyColumn *, void *)>; + +/* `ED_keylist_find_neighbour_front_to_back` is called before the runtime can be initialized so we + * cannot use bin searching. */ +static ActKeyColumn *ED_keylist_find_neighbour_front_to_back(ActKeyColumn *cursor, float cfra) +{ + while (cursor->next && cursor->next->cfra <= cfra) { + cursor = cursor->next; + } + return cursor; +} + +/* `ED_keylist_find_neighbour_back_to_front` is called before the runtime can be initialized so we + * cannot use bin searching. */ +static ActKeyColumn *ED_keylist_find_neighbour_back_to_front(ActKeyColumn *cursor, float cfra) +{ + while (cursor->prev && cursor->prev->cfra >= cfra) { + cursor = cursor->prev; + } + return cursor; +} + +/* + * `ED_keylist_find_exact_or_neighbour_column` is called before the runtime can be initialized so + * we cannot use bin searching. + * + * This function is called to add or update columns in the keylist. + * Typically columns are sorted by frame number so keeping track of the last_accessed_column + * reduces searching. + */ +static ActKeyColumn *ED_keylist_find_exact_or_neighbour_column(AnimKeylist *keylist, float cfra) +{ + BLI_assert(!keylist->is_runtime_initialized); + if (ED_keylist_is_empty(keylist)) { + return nullptr; + } + + ActKeyColumn *cursor = keylist->last_accessed_column.value_or( + static_cast<ActKeyColumn *>(keylist->key_columns.first)); + if (!is_cfra_eq(cursor->cfra, cfra)) { + const bool walking_direction_front_to_back = cursor->cfra <= cfra; + if (walking_direction_front_to_back) { + cursor = ED_keylist_find_neighbour_front_to_back(cursor, cfra); + } + else { + cursor = ED_keylist_find_neighbour_back_to_front(cursor, cfra); + } + } + + keylist->last_accessed_column = cursor; + return cursor; +} + +static void ED_keylist_add_or_update_column(AnimKeylist *keylist, + float cfra, + KeylistCreateColumnFunction create_func, + KeylistUpdateColumnFunction update_func, + void *userdata) +{ + BLI_assert_msg( + !keylist->is_runtime_initialized, + "Modifying AnimKeylist isn't allowed after runtime is initialized " + "keylist->key_columns/columns_len will get out of sync with runtime.key_columns."); + if (ED_keylist_is_empty(keylist)) { + ActKeyColumn *key_column = create_func(userdata); + BLI_addhead(&keylist->key_columns, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + return; + } + + ActKeyColumn *nearest = ED_keylist_find_exact_or_neighbour_column(keylist, cfra); + if (is_cfra_eq(nearest->cfra, cfra)) { + update_func(nearest, userdata); + } + else if (is_cfra_lt(nearest->cfra, cfra)) { + ActKeyColumn *key_column = create_func(userdata); + BLI_insertlinkafter(&keylist->key_columns, nearest, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + } + else { + ActKeyColumn *key_column = create_func(userdata); + BLI_insertlinkbefore(&keylist->key_columns, nearest, key_column); + keylist->column_len += 1; + keylist->last_accessed_column = key_column; + } +} /* Add the given BezTriple to the given 'list' of Keyframes */ static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *bezt) @@ -416,7 +658,8 @@ static void add_bezt_to_keycolumns_list(AnimKeylist *keylist, BezTripleChain *be return; } - BLI_dlrbTree_add(&keylist->keys, compare_ak_bezt, nalloc_ak_bezt, nupdate_ak_bezt, bezt); + float cfra = bezt->cur->vec[1][0]; + ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_bezt, nupdate_ak_bezt, bezt); } /* Add the given GPencil Frame to the given 'list' of Keyframes */ @@ -426,7 +669,8 @@ static void add_gpframe_to_keycolumns_list(AnimKeylist *keylist, bGPDframe *gpf) return; } - BLI_dlrbTree_add(&keylist->keys, compare_ak_gpframe, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); + float cfra = gpf->framenum; + ED_keylist_add_or_update_column(keylist, cfra, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); } /* Add the given MaskLayerShape Frame to the given 'list' of Keyframes */ @@ -436,11 +680,9 @@ static void add_masklay_to_keycolumns_list(AnimKeylist *keylist, MaskLayerShape return; } - BLI_dlrbTree_add(&keylist->keys, - compare_ak_masklayshape, - nalloc_ak_masklayshape, - nupdate_ak_masklayshape, - masklay_shape); + float cfra = masklay_shape->frame; + ED_keylist_add_or_update_column( + keylist, cfra, nalloc_ak_masklayshape, nupdate_ak_masklayshape, masklay_shape); } /* ActKeyBlocks (Long Keyframes) ------------------------------------------ */ @@ -476,7 +718,7 @@ static void compute_keyblock_data(ActKeyBlockInfo *info, hold = IS_EQF(beztn->vec[1][1], beztn->vec[0][1]) && IS_EQF(prev->vec[1][1], prev->vec[2][1]); } - /* This interpolation type induces movement even between identical keys. */ + /* This interpolation type induces movement even between identical columns. */ else { hold = !ELEM(prev->ipo, BEZT_IPO_ELASTIC); } @@ -514,7 +756,7 @@ static void add_keyblock_info(ActKeyColumn *col, const ActKeyBlockInfo *block) static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len) { - ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->keys.first); + ActKeyColumn *col = static_cast<ActKeyColumn *>(keylist->key_columns.first); if (bezt && bezt_len >= 2) { ActKeyBlockInfo block; @@ -532,19 +774,15 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co if (is_cfra_lt(bezt[1].vec[1][0], bezt[0].vec[1][0])) { /* Backtrack to find the right location. */ if (is_cfra_lt(bezt[1].vec[1][0], col->cfra)) { - ActKeyColumn *newcol = (ActKeyColumn *)BLI_dlrbTree_search_exact( - &keylist->keys, compare_ak_cfraPtr, &bezt[1].vec[1][0]); + ActKeyColumn *newcol = ED_keylist_find_exact_or_neighbour_column(keylist, col->cfra); - if (newcol != nullptr) { - col = newcol; + BLI_assert(newcol); + BLI_assert(newcol->cfra == col->cfra); - /* The previous keyblock is garbage too. */ - if (col->prev != nullptr) { - add_keyblock_info(col->prev, &dummy_keyblock); - } - } - else { - BLI_assert(false); + col = newcol; + /* The previous keyblock is garbage too. */ + if (col->prev != nullptr) { + add_keyblock_info(col->prev, &dummy_keyblock); } } @@ -577,20 +815,17 @@ static void add_bezt_to_keyblocks_list(AnimKeylist *keylist, BezTriple *bezt, co */ static void update_keyblocks(AnimKeylist *keylist, BezTriple *bezt, const int bezt_len) { - /* Recompute the prev/next linked list. */ - BLI_dlrbTree_linkedlist_sync(&keylist->keys); - /* Find the curve count */ int max_curve = 0; - LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) { + LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) { max_curve = MAX2(max_curve, col->totcurve); } /* Propagate blocks to inserted keys */ ActKeyColumn *prev_ready = nullptr; - LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->keys) { + LISTBASE_FOREACH (ActKeyColumn *, col, &keylist->key_columns) { /* Pre-existing column. */ if (col->totcurve > 0) { prev_ready = col; @@ -775,6 +1010,7 @@ void cachefile_to_keylist(bDopeSheet *ads, void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const int saction_flag) { if (fcu && fcu->totvert && fcu->bezt) { + ED_keylist_reset_last_accessed(keylist); /* apply NLA-mapping (if applicable) */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, fcu, false, false); @@ -790,7 +1026,7 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i for (int v = 0; v < fcu->totvert; v++) { chain.cur = &fcu->bezt[v]; - /* Neighbor keys, accounting for being cyclic. */ + /* Neighbor columns, accounting for being cyclic. */ if (do_extremes) { chain.prev = (v > 0) ? &fcu->bezt[v - 1] : is_cyclic ? &fcu->bezt[fcu->totvert - 2] : @@ -856,6 +1092,7 @@ void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, AnimKeylist *keylist, con void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylist) { if (gpl && keylist) { + ED_keylist_reset_last_accessed(keylist); /* Although the frames should already be in an ordered list, * they are not suitable for displaying yet. */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { @@ -869,6 +1106,7 @@ void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, AnimKeylist *keylis void mask_to_keylist(bDopeSheet *UNUSED(ads), MaskLayer *masklay, AnimKeylist *keylist) { if (masklay && keylist) { + ED_keylist_reset_last_accessed(keylist); LISTBASE_FOREACH (MaskLayerShape *, masklay_shape, &masklay->splines_shapes) { add_masklay_to_keycolumns_list(keylist, masklay_shape); } diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 292d665caca..8dc4aed9f0e 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -2218,11 +2218,11 @@ static int clear_anim_v3d_exec(bContext *C, wmOperator *UNUSED(op)) if (ob->mode & OB_MODE_POSE) { if (fcu->rna_path) { /* Get bone-name, and check if this bone is selected. */ - char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); - if (bone_name) { - bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name); - MEM_freeN(bone_name); - + bPoseChannel *pchan = NULL; + char bone_name[sizeof(pchan->name)]; + if (BLI_str_quoted_substr( + fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) { + pchan = BKE_pose_channel_find_name(ob->pose, bone_name); /* Delete if bone is selected. */ if ((pchan) && (pchan->bone)) { if (pchan->bone->flag & BONE_SELECTED) { @@ -2323,13 +2323,11 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) bPoseChannel *pchan = NULL; /* Get bone-name, and check if this bone is selected. */ - char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); - if (bone_name == NULL) { + char bone_name[sizeof(pchan->name)]; + if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) { continue; } - pchan = BKE_pose_channel_find_name(ob->pose, bone_name); - MEM_freeN(bone_name); /* skip if bone is not selected */ if ((pchan) && (pchan->bone)) { diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index 4db8569a7d2..e5b8983af93 100644 --- a/source/blender/editors/armature/pose_select.c +++ b/source/blender/editors/armature/pose_select.c @@ -1106,12 +1106,12 @@ static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool ex for (ksp = ks->paths.first; ksp; ksp = ksp->next) { /* only items related to this object will be relevant */ if ((ksp->id == &ob->id) && (ksp->rna_path != NULL)) { - char *boneName = BLI_str_quoted_substrN(ksp->rna_path, "bones["); - if (boneName == NULL) { + bPoseChannel *pchan = NULL; + char boneName[sizeof(pchan->name)]; + if (!BLI_str_quoted_substr(ksp->rna_path, "bones[", boneName, sizeof(boneName))) { continue; } - bPoseChannel *pchan = BKE_pose_channel_find_name(pose, boneName); - MEM_freeN(boneName); + pchan = BKE_pose_channel_find_name(pose, boneName); if (pchan) { /* select if bone is visible and can be affected */ diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index bc5cbd92deb..f23376867af 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -976,6 +976,7 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, const wmEvent * } /* Cancel if no keyframes found. */ + ED_keylist_prepare_for_direct_access(pso->keylist); if (ED_keylist_is_empty(pso->keylist)) { BKE_report(op->reports, RPT_ERROR, "No keyframes to slide between"); pose_slide_exit(C, op); @@ -1267,6 +1268,8 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Perform pose updates - in response to some user action * (e.g. pressing a key or moving the mouse). */ if (do_pose_update) { + RNA_float_set(op->ptr, "factor", ED_slider_factor_get(pso->slider)); + /* Update percentage indicator in header. */ pose_slide_draw_status(C, pso); @@ -1712,6 +1715,7 @@ static float pose_propagate_get_boneHoldEndFrame(tPChanFCurveLink *pfl, float st FCurve *fcu = (FCurve *)ld->data; fcurve_to_keylist(adt, fcu, keylist, 0); } + ED_keylist_prepare_for_direct_access(keylist); /* Find the long keyframe (i.e. hold), and hence obtain the endFrame value * - the best case would be one that starts on the frame itself diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index d1fe162fc4a..75fb17e8cc1 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -515,7 +515,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) bool newob = false; bool enter_editmode; ushort local_view_bits; - float dia; float loc[3], rot[3]; float mat[4][4]; @@ -535,7 +534,6 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) newob = true; cu = (Curve *)obedit->data; - cu->flag |= CU_DEFORM_FILL; if (type & CU_PRIM_PATH) { cu->flag |= CU_PATH | CU_3D; @@ -556,9 +554,10 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) } } - ED_object_new_primitive_matrix(C, obedit, loc, rot, mat); - dia = RNA_float_get(op->ptr, "radius"); - mul_mat3_m4_fl(mat, dia); + float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); + ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, mat); nu = ED_curve_add_nurbs_primitive(C, obedit, mat, type, newob); editnurb = object_editcurve_get(obedit); diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt index 2542247f252..5f25114eaf3 100644 --- a/source/blender/editors/datafiles/CMakeLists.txt +++ b/source/blender/editors/datafiles/CMakeLists.txt @@ -336,6 +336,7 @@ set(ICON_NAMES lightprobe_cubemap lightprobe_planar lightprobe_grid + mod_dash color_red color_green color_blue @@ -390,6 +391,9 @@ set(ICON_NAMES small_caps modifier con_action + mod_length + mod_dash + mod_lineart holdout_off holdout_on indirect_only_off diff --git a/source/blender/editors/gizmo_library/CMakeLists.txt b/source/blender/editors/gizmo_library/CMakeLists.txt index eeb1e60166b..bfe8334b390 100644 --- a/source/blender/editors/gizmo_library/CMakeLists.txt +++ b/source/blender/editors/gizmo_library/CMakeLists.txt @@ -27,6 +27,7 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager + ../../../../intern/clog ../../../../intern/eigen ../../../../intern/glew-mx ../../../../intern/guardedalloc diff --git a/source/blender/editors/gizmo_library/gizmo_library_utils.c b/source/blender/editors/gizmo_library/gizmo_library_utils.c index 7d0ae5afb9b..a0db2a8e627 100644 --- a/source/blender/editors/gizmo_library/gizmo_library_utils.c +++ b/source/blender/editors/gizmo_library/gizmo_library_utils.c @@ -39,9 +39,13 @@ #include "ED_view3d.h" +#include "CLG_log.h" + /* own includes */ #include "gizmo_library_intern.h" +static CLG_LogRef LOG = {"ed.gizmo.library_utils"}; + /* factor for precision tweaking */ #define GIZMO_PRECISION_FAC 0.05f @@ -182,7 +186,7 @@ bool gizmo_window_project_2d(bContext *C, bool use_offset, float r_co[2]) { - float mat[4][4]; + float mat[4][4], imat[4][4]; { float mat_identity[4][4]; struct WM_GizmoMatrixParams params = {NULL}; @@ -193,6 +197,14 @@ bool gizmo_window_project_2d(bContext *C, WM_gizmo_calc_matrix_final_params(gz, ¶ms, mat); } + if (!invert_m4_m4(imat, mat)) { + CLOG_WARN(&LOG, + "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted " + "(projection will fail)", + gz->type->idname, + gz->parent_gzgroup->type->idname); + } + /* rotate mouse in relation to the center and relocate it */ if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { /* For 3d views, transform 2D mouse pos onto plane. */ @@ -202,8 +214,6 @@ bool gizmo_window_project_2d(bContext *C, plane_from_point_normal_v3(plane, mat[3], mat[2]); bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp; if (ED_view3d_win_to_3d_on_plane(region, plane, mval, clip_ray, co)) { - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); r_co[0] = co[(axis + 1) % 3]; r_co[1] = co[(axis + 2) % 3]; @@ -213,8 +223,6 @@ bool gizmo_window_project_2d(bContext *C, } float co[3] = {mval[0], mval[1], 0.0f}; - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); copy_v2_v2(r_co, co); return true; @@ -223,7 +231,7 @@ bool gizmo_window_project_2d(bContext *C, bool gizmo_window_project_3d( bContext *C, const struct wmGizmo *gz, const float mval[2], bool use_offset, float r_co[3]) { - float mat[4][4]; + float mat[4][4], imat[4][4]; { float mat_identity[4][4]; struct WM_GizmoMatrixParams params = {NULL}; @@ -234,20 +242,25 @@ bool gizmo_window_project_3d( WM_gizmo_calc_matrix_final_params(gz, ¶ms, mat); } + if (!invert_m4_m4(imat, mat)) { + CLOG_WARN(&LOG, + "Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted " + "(projection will fail)", + gz->type->idname, + gz->parent_gzgroup->type->idname); + } + if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); /* NOTE: we might want a custom reference point passed in, * instead of the gizmo center. */ ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co); - invert_m4(mat); - mul_m4_v3(mat, r_co); + mul_m4_v3(imat, r_co); return true; } float co[3] = {mval[0], mval[1], 0.0f}; - float imat[4][4]; - invert_m4_m4(imat, mat); mul_m4_v3(imat, co); copy_v2_v2(r_co, co); return true; diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 8d1f841da6c..aa3178ddc2c 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -3618,7 +3618,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } elem = &strokes_list[i]; /* Join new_stroke and stroke B. */ - BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true); + BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false); elem->used = true; } @@ -3967,7 +3967,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) /* perform smoothing */ if (smooth_position) { - BKE_gpencil_stroke_smooth(gps, i, factor); + BKE_gpencil_stroke_smooth_point(gps, i, factor); } if (smooth_strength) { BKE_gpencil_stroke_smooth_strength(gps, i, factor); diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 0c88d678ef4..f5474a7cdc3 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1569,7 +1569,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) float smoothfac = 1.0f; for (int r = 0; r < 1; r++) { for (int i = 0; i < gps->totpoints; i++) { - BKE_gpencil_stroke_smooth(gps, i, smoothfac - reduce); + BKE_gpencil_stroke_smooth_point(gps, i, smoothfac - reduce); } reduce += 0.25f; /* reduce the factor */ } diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index a8bd3b11bb1..fdd9f44605e 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -333,7 +333,7 @@ static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps, float reduce = 0.0f; for (int r = 0; r < smooth_steps; r++) { for (int i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_stroke_smooth(gps, i, smooth_factor - reduce); + BKE_gpencil_stroke_smooth_point(gps, i, smooth_factor - reduce); BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor); } reduce += 0.25f; /* reduce the factor */ diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 28a22633742..9b157224178 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1209,7 +1209,8 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) float reduce = 0.0f; for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) { for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_stroke_smooth(gps, i, brush->gpencil_settings->draw_smoothfac - reduce); + BKE_gpencil_stroke_smooth_point( + gps, i, brush->gpencil_settings->draw_smoothfac - reduce); BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac); } reduce += 0.25f; /* reduce the factor */ @@ -1221,7 +1222,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) float ifac = (float)brush->gpencil_settings->input_samples / 10.0f; float sfac = interpf(1.0f, 0.2f, ifac); for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_stroke_smooth(gps, i, sfac); + BKE_gpencil_stroke_smooth_point(gps, i, sfac); BKE_gpencil_stroke_smooth_strength(gps, i, sfac); } } @@ -1288,11 +1289,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* Join with existing strokes. */ if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) { if (gps->prev != NULL) { + BKE_gpencil_stroke_boundingbox_calc(gps); + float diff_mat[4][4], ctrl1[2], ctrl2[2]; + BKE_gpencil_layer_transform_matrix_get(depsgraph, p->ob, gpl, diff_mat); + ED_gpencil_stroke_extremes_to2d(&p->gsc, diff_mat, gps, ctrl1, ctrl2); + int pt_index = 0; bool doit = true; while (doit && gps) { - bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends( - p->C, &p->gsc, gpl, gpl->actframe, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index); + bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(p->C, + &p->gsc, + gpl, + gpl->actframe, + gps, + ctrl1, + ctrl2, + GPENCIL_MINIMUM_JOIN_DIST, + &pt_index); if (gps_target != NULL) { gps = ED_gpencil_stroke_join_and_trim(p->gpd, p->gpf, gps, gps_target, pt_index); } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 5ecb6d9a212..f8cfc130e35 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1382,11 +1382,23 @@ static void gpencil_primitive_interaction_end(bContext *C, if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) { if (ELEM(tgpi->type, GP_STROKE_ARC, GP_STROKE_LINE, GP_STROKE_CURVE, GP_STROKE_POLYLINE)) { if (gps->prev != NULL) { + BKE_gpencil_stroke_boundingbox_calc(gps); + float diff_mat[4][4], ctrl1[2], ctrl2[2]; + BKE_gpencil_layer_transform_matrix_get(tgpi->depsgraph, tgpi->ob, tgpi->gpl, diff_mat); + ED_gpencil_stroke_extremes_to2d(&tgpi->gsc, diff_mat, gps, ctrl1, ctrl2); + int pt_index = 0; bool doit = true; while (doit && gps) { - bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends( - C, &tgpi->gsc, tgpi->gpl, gpf, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index); + bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(C, + &tgpi->gsc, + tgpi->gpl, + gpf, + gps, + ctrl1, + ctrl2, + GPENCIL_MINIMUM_JOIN_DIST, + &pt_index); if (gps_target != NULL) { gps = ED_gpencil_stroke_join_and_trim(tgpi->gpd, gpf, gps, gps_target, pt_index); } diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index 869254cef3b..e9a6beab798 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -337,7 +337,7 @@ static bool gpencil_brush_smooth_apply(tGP_BrushEditData *gso, /* perform smoothing */ if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { - BKE_gpencil_stroke_smooth(gps, pt_index, inf); + BKE_gpencil_stroke_smooth_point(gps, pt_index, inf); } if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) { BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf); diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 5cc52303cd6..bb05b93ad81 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -3213,11 +3213,28 @@ bool ED_gpencil_stroke_point_is_inside(const bGPDstroke *gps, return hit; } +/* Get extremes of stroke in 2D using current view. */ +void ED_gpencil_stroke_extremes_to2d(const GP_SpaceConversion *gsc, + const float diff_mat[4][4], + bGPDstroke *gps, + float r_ctrl1[2], + float r_ctrl2[2]) +{ + bGPDspoint pt_dummy_ps; + + gpencil_point_to_parent_space(&gps->points[0], diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl1[0], &r_ctrl1[1]); + gpencil_point_to_parent_space(&gps->points[gps->totpoints - 1], diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl2[0], &r_ctrl2[1]); +} + bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C, const GP_SpaceConversion *gsc, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, + const float ctrl1[2], + const float ctrl2[2], const float radius, int *r_index) { @@ -3267,6 +3284,15 @@ bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C, gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]); + /* If the distance to the original stroke extremes is too big, the stroke must not be joined. + */ + if ((len_squared_v2v2(ctrl1, pt2d_target_start) > radius_sqr) && + (len_squared_v2v2(ctrl1, pt2d_target_end) > radius_sqr) && + (len_squared_v2v2(ctrl2, pt2d_target_start) > radius_sqr) && + (len_squared_v2v2(ctrl2, pt2d_target_end) > radius_sqr)) { + continue; + } + if ((len_squared_v2v2(pt2d_start, pt2d_target_start) > radius_sqr) && (len_squared_v2v2(pt2d_start, pt2d_target_end) > radius_sqr) && (len_squared_v2v2(pt2d_end, pt2d_target_start) > radius_sqr) && @@ -3350,7 +3376,7 @@ bGPDstroke *ED_gpencil_stroke_join_and_trim( /* Join both strokes. */ int totpoint = gps_final->totpoints; - BKE_gpencil_stroke_join(gps_final, gps, false, true); + BKE_gpencil_stroke_join(gps_final, gps, false, true, true); /* Select the join points and merge if the distance is very small. */ pt = &gps_final->points[totpoint - 1]; diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c index 402bccce2f7..5c3a7cf9e6f 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_ops.c +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -588,6 +588,7 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op) changed = true; copy_v3_v3(gps->vert_color_fill, brush->rgb); gps->vert_color_fill[3] = factor; + srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill); } /* Stroke points. */ @@ -596,10 +597,13 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op) int i; bGPDspoint *pt; + float color[4]; + copy_v3_v3(color, brush->rgb); + color[3] = factor; + srgb_to_linearrgb_v4(color, color); for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if ((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) { - copy_v3_v3(pt->vert_color, brush->rgb); - pt->vert_color[3] = factor; + copy_v3_v3(pt->vert_color, color); } } } diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 8a8d91a570c..c760b661373 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -391,8 +391,15 @@ struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C, struct bGPDlayer *gpl, struct bGPDframe *gpf, struct bGPDstroke *gps, + const float ctrl1[2], + const float ctrl2[2], const float radius, int *r_index); +void ED_gpencil_stroke_extremes_to2d(const struct GP_SpaceConversion *gsc, + const float diff_mat[4][4], + struct bGPDstroke *gps, + float r_ctrl1[2], + float r_ctrl2[2]); struct bGPDstroke *ED_gpencil_stroke_join_and_trim(struct bGPdata *gpd, struct bGPDframe *gpf, diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h index 3a9750c1206..4194444ca0f 100644 --- a/source/blender/editors/include/ED_keyframes_keylist.h +++ b/source/blender/editors/include/ED_keyframes_keylist.h @@ -139,14 +139,20 @@ typedef enum eKeyframeExtremeDrawOpts { struct AnimKeylist *ED_keylist_create(void); void ED_keylist_free(struct AnimKeylist *keylist); -const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist, float cfra); -const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist, float cfra); -const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist, float cfra); +void ED_keylist_prepare_for_direct_access(struct AnimKeylist *keylist); +const struct ActKeyColumn *ED_keylist_find_exact(const struct AnimKeylist *keylist, + const float cfra); +const struct ActKeyColumn *ED_keylist_find_next(const struct AnimKeylist *keylist, + const float cfra); +const struct ActKeyColumn *ED_keylist_find_prev(const struct AnimKeylist *keylist, + const float cfra); const struct ActKeyColumn *ED_keylist_find_any_between(const struct AnimKeylist *keylist, const Range2f frame_range); bool ED_keylist_is_empty(const struct AnimKeylist *keylist); const struct ListBase /* ActKeyColumn */ *ED_keylist_listbase(const struct AnimKeylist *keylist); bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range); +const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist); +int64_t ED_keylist_array_len(const struct AnimKeylist *keylist); /* Key-data Generation --------------- */ @@ -197,8 +203,6 @@ void mask_to_keylist(struct bDopeSheet *ads, struct AnimKeylist *keylist); /* ActKeyColumn API ---------------- */ -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -short compare_ak_cfraPtr(void *node, void *data); /* Checks if ActKeyColumn has any block data */ bool actkeyblock_is_valid(const ActKeyColumn *ac); diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index 3f02be64294..65e13b29015 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -287,6 +287,7 @@ float ED_object_new_primitive_matrix(struct bContext *C, struct Object *obedit, const float loc[3], const float rot[3], + const float scale[3], float primmat[4][4]); /* Avoid allowing too much insane values even by typing diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 2c958d282f9..cf8dcbd7995 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -566,6 +566,13 @@ eV3DSelectObjectFilter ED_view3d_select_filter_from_mode(const struct Scene *sce void view3d_opengl_select_cache_begin(void); void view3d_opengl_select_cache_end(void); +int view3d_opengl_select_ex(struct ViewContext *vc, + unsigned int *buffer, + unsigned int bufsize, + const struct rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter, + const bool do_material_slot_selection); int view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigned int bufsize, @@ -638,6 +645,9 @@ void ED_view3d_draw_setup_view(const struct wmWindowManager *wm, struct Base *ED_view3d_give_base_under_cursor(struct bContext *C, const int mval[2]); struct Object *ED_view3d_give_object_under_cursor(struct bContext *C, const int mval[2]); +struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C, + const int mval[2], + int *r_material_slot); bool ED_view3d_is_object_under_cursor(struct bContext *C, const int mval[2]); void ED_view3d_quadview_update(struct ScrArea *area, struct ARegion *region, bool do_clip); void ED_view3d_update_viewmat(struct Depsgraph *depsgraph, diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h index 15689619c14..0ae3e61293c 100644 --- a/source/blender/editors/include/UI_icons.h +++ b/source/blender/editors/include/UI_icons.h @@ -490,9 +490,9 @@ DEF_ICON_MODIFIER(CON_ACTION) DEF_ICON_BLANK(745) DEF_ICON_BLANK(746) DEF_ICON_BLANK(747) -DEF_ICON_BLANK(748) -DEF_ICON_BLANK(749) -DEF_ICON_BLANK(750) +DEF_ICON_MODIFIER(MOD_LENGTH) +DEF_ICON_MODIFIER(MOD_DASH) +DEF_ICON_MODIFIER(MOD_LINEART) DEF_ICON_BLANK(751) DEF_ICON(HOLDOUT_OFF) DEF_ICON(HOLDOUT_ON) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 7211cf9f893..916105b0f8e 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2209,6 +2209,11 @@ enum uiTemplateListFlags { UI_TEMPLATE_LIST_SORT_LOCK = (1 << 1), /* Don't allow resizing the list, i.e. don't add the grip button. */ UI_TEMPLATE_LIST_NO_GRIP = (1 << 2), + /** Do not show filtering options, not even the button to expand/collapse them. Also hides the + * grip button. */ + UI_TEMPLATE_LIST_NO_FILTER_OPTIONS = (1 << 3), + /** For #UILST_LAYOUT_BIG_PREVIEW_GRID, don't reserve space for the name label. */ + UI_TEMPLATE_LIST_NO_NAMES = (1 << 4), UI_TEMPLATE_LIST_FLAGS_LAST }; @@ -2289,6 +2294,12 @@ int uiTemplateRecentFiles(struct uiLayout *layout, int rows); void uiTemplateFileSelectPath(uiLayout *layout, struct bContext *C, struct FileSelectParams *params); + +enum { + UI_TEMPLATE_ASSET_DRAW_NO_NAMES = (1 << 0), + UI_TEMPLATE_ASSET_DRAW_NO_FILTER = (1 << 1), + UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY = (1 << 2), +}; void uiTemplateAssetView(struct uiLayout *layout, struct bContext *C, const char *list_id, @@ -2299,6 +2310,7 @@ void uiTemplateAssetView(struct uiLayout *layout, struct PointerRNA *active_dataptr, const char *active_propname, const struct AssetFilterSettings *filter_settings, + const int display_flags, const char *activate_opname, struct PointerRNA *r_activate_op_properties, const char *drag_opname, diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 9b601727e29..f27b37a27de 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -46,6 +46,7 @@ struct AssetViewListData { AssetLibraryReference asset_library_ref; bScreen *screen; + bool show_names; }; static void asset_view_item_but_drag_set(uiBut *but, @@ -95,14 +96,15 @@ static void asset_view_draw_item(uiList *ui_list, uiLayoutSetContextPointer(layout, "asset_handle", itemptr); uiBlock *block = uiLayoutGetBlock(layout); + const bool show_names = list_data->show_names; /* TODO ED_fileselect_init_layout(). Share somehow? */ const float size_x = (96.0f / 20.0f) * UI_UNIT_X; - const float size_y = (96.0f / 20.0f) * UI_UNIT_Y; + const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y); uiBut *but = uiDefIconTextBut(block, UI_BTYPE_PREVIEW_TILE, 0, ED_asset_handle_get_preview_icon_id(asset_handle), - ED_asset_handle_get_name(asset_handle), + show_names ? ED_asset_handle_get_name(asset_handle) : "", 0, 0, size_x, @@ -202,6 +204,7 @@ void uiTemplateAssetView(uiLayout *layout, PointerRNA *active_dataptr, const char *active_propname, const AssetFilterSettings *filter_settings, + const int display_flags, const char *activate_opname, PointerRNA *r_activate_op_properties, const char *drag_opname, @@ -220,9 +223,11 @@ void uiTemplateAssetView(uiLayout *layout, RNA_property_enum_get(asset_library_dataptr, asset_library_prop)); uiLayout *row = uiLayoutRow(col, true); - uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); - if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) { - uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY) == 0) { + uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); + if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) { + uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + } } ED_assetlist_storage_fetch(&asset_library_ref, C); @@ -236,6 +241,15 @@ void uiTemplateAssetView(uiLayout *layout, "AssetViewListData"); list_data->asset_library_ref = asset_library_ref; list_data->screen = CTX_wm_screen(C); + list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0; + + uiTemplateListFlags template_list_flags = UI_TEMPLATE_LIST_NO_GRIP; + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_NAMES; + } + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_FILTER) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_FILTER_OPTIONS; + } /* TODO can we have some kind of model-view API to handle referencing, filtering and lazy loading * (of previews) of the items? */ @@ -252,7 +266,7 @@ void uiTemplateAssetView(uiLayout *layout, 0, UILST_LAYOUT_BIG_PREVIEW_GRID, 0, - UI_TEMPLATE_LIST_NO_GRIP, + template_list_flags, list_data); if (!list) { /* List creation failed. */ diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc index 0ab45ea0f81..8246759ad36 100644 --- a/source/blender/editors/interface/interface_template_list.cc +++ b/source/blender/editors/interface/interface_template_list.cc @@ -944,10 +944,16 @@ static void ui_template_list_layout_draw(bContext *C, /* For scrollbar. */ row = uiLayoutRow(glob, false); + const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0; + /* TODO ED_fileselect_init_layout(). Share somehow? */ float size_x = (96.0f / 20.0f) * UI_UNIT_X; float size_y = (96.0f / 20.0f) * UI_UNIT_Y; + if (!show_names) { + size_y -= UI_UNIT_Y; + } + const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1); uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true); @@ -1033,7 +1039,8 @@ static void ui_template_list_layout_draw(bContext *C, break; } - if (glob) { + const bool add_filters_but = (flags & UI_TEMPLATE_LIST_NO_FILTER_OPTIONS) == 0; + if (glob && add_filters_but) { const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0; /* About #UI_BTYPE_GRIP drag-resize: diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 08d78552710..0c9eb20af19 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -673,8 +673,8 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) } } else { - if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_newptr_and_tag_clear(bmain); + if (BKE_lib_id_make_local(bmain, id, 0)) { + BKE_id_newptr_and_tag_clear(id); /* Reassign to get proper updates/notifiers. */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); @@ -1031,7 +1031,7 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_DISABLED); } else { - const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || + const bool disabled = (!BKE_idtype_idcode_is_localizable(GS(id->name)) || (idfrom && idfrom->lib)); but = uiDefIconBut(block, UI_BTYPE_BUT, diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index a2b86ccd947..0dc7c2d3f9a 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -5443,13 +5443,20 @@ void ui_draw_preview_item_stateless(const uiFontStyle *fstyle, rcti trect = *rect; const float text_size = UI_UNIT_Y; float font_dims[2] = {0.0f, 0.0f}; + const bool has_text = name && name[0]; - /* draw icon in rect above the space reserved for the label */ - rect->ymin += text_size; + if (has_text) { + /* draw icon in rect above the space reserved for the label */ + rect->ymin += text_size; + } GPU_blend(GPU_BLEND_ALPHA); widget_draw_preview(iconid, 1.0f, rect); GPU_blend(GPU_BLEND_NONE); + if (!has_text) { + return; + } + BLF_width_and_height( fstyle->uifont_id, name, BLF_DRAW_STR_DUMMY_MAX, &font_dims[0], &font_dims[1]); diff --git a/source/blender/editors/mesh/editmesh_add.c b/source/blender/editors/mesh/editmesh_add.c index a64b90e15a3..c826da74010 100644 --- a/source/blender/editors/mesh/editmesh_add.c +++ b/source/blender/editors/mesh/editmesh_add.c @@ -73,11 +73,7 @@ static Object *make_prim_init(bContext *C, r_creation_data->was_editmode = true; } - ED_object_new_primitive_matrix(C, obedit, loc, rot, r_creation_data->mat); - - if (scale) { - rescale_m4(r_creation_data->mat, scale); - } + ED_object_new_primitive_matrix(C, obedit, loc, rot, scale, r_creation_data->mat); return obedit; } @@ -351,7 +347,7 @@ static int add_primitive_cylinder_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b " + "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b " "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "vertices"), RNA_float_get(op->ptr, "radius"), @@ -427,7 +423,7 @@ static int add_primitive_cone_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b " + "create_cone segments=%i radius1=%f radius2=%f cap_ends=%b " "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "vertices"), RNA_float_get(op->ptr, "radius1"), @@ -642,7 +638,7 @@ static int add_primitive_uvsphere_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_uvsphere u_segments=%i v_segments=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_uvsphere u_segments=%i v_segments=%i radius=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "segments"), RNA_int_get(op->ptr, "ring_count"), RNA_float_get(op->ptr, "radius"), @@ -710,7 +706,7 @@ static int add_primitive_icosphere_exec(bContext *C, wmOperator *op) op, "verts.out", false, - "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b", RNA_int_get(op->ptr, "subdivisions"), RNA_float_get(op->ptr, "radius"), creation_data.mat, diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c index 01736f2919a..0d74187b50e 100644 --- a/source/blender/editors/mesh/editmesh_bevel.c +++ b/source/blender/editors/mesh/editmesh_bevel.c @@ -97,7 +97,6 @@ typedef struct { int launch_event; float mcenter[2]; void *draw_handle_pixel; - short gizmo_flag; short value_mode; /* Which value does mouse movement and numeric input affect? */ float segments; /* Segments as float so smooth mouse pan works in small increments */ @@ -307,11 +306,6 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) opdata->draw_handle_pixel = ED_region_draw_cb_activate( region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); G.moving = G_TRANSFORM_EDIT; - - if (v3d) { - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } } return true; @@ -433,15 +427,11 @@ static void edbm_bevel_exit(bContext *C, wmOperator *op) } if (opdata->is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup); } ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel); - if (v3d) { - v3d->gizmo_flag = opdata->gizmo_flag; - } G.moving = 0; } MEM_SAFE_FREE(opdata->ob_store); diff --git a/source/blender/editors/mesh/editmesh_bisect.c b/source/blender/editors/mesh/editmesh_bisect.c index 3c8afe8e7db..27a1bf9658f 100644 --- a/source/blender/editors/mesh/editmesh_bisect.c +++ b/source/blender/editors/mesh/editmesh_bisect.c @@ -72,7 +72,6 @@ typedef struct { bool is_dirty; } * backup; int backup_len; - short gizmo_flag; } BisectData; static void mesh_bisect_interactive_calc(bContext *C, @@ -88,6 +87,7 @@ static void mesh_bisect_interactive_calc(bContext *C, int y_start = RNA_int_get(op->ptr, "ystart"); int x_end = RNA_int_get(op->ptr, "xend"); int y_end = RNA_int_get(op->ptr, "yend"); + const bool use_flip = RNA_boolean_get(op->ptr, "flip"); /* reference location (some point in front of the view) for finding a point on a plane */ const float *co_ref = rv3d->ofs; @@ -105,6 +105,9 @@ static void mesh_bisect_interactive_calc(bContext *C, /* cross both to get a normal */ cross_v3_v3v3(plane_no, co_a, co_b); normalize_v3(plane_no); /* not needed but nicer for user */ + if (use_flip) { + negate_v3(plane_no); + } /* point on plane, can use either start or endpoint */ ED_view3d_win_to_3d(v3d, region, co_ref, co_a_ss, plane_co); @@ -116,7 +119,7 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) int valid_objects = 0; /* If the properties are set or there is no rv3d, - * skip model and exec immediately. */ + * skip modal and exec immediately. */ if ((CTX_wm_region_view3d(C) == NULL) || (RNA_struct_property_is_set(op->ptr, "plane_co") && RNA_struct_property_is_set(op->ptr, "plane_no"))) { return mesh_bisect_exec(C, op); @@ -140,10 +143,19 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - int ret = WM_gesture_straightline_invoke(C, op, event); - if (ret & OPERATOR_RUNNING_MODAL) { - View3D *v3d = CTX_wm_view3d(C); + /* Support flipping if side matters. */ + int ret; + const bool clear_inner = RNA_boolean_get(op->ptr, "clear_inner"); + const bool clear_outer = RNA_boolean_get(op->ptr, "clear_outer"); + const bool use_fill = RNA_boolean_get(op->ptr, "use_fill"); + if ((clear_inner != clear_outer) || use_fill) { + ret = WM_gesture_straightline_active_side_invoke(C, op, event); + } + else { + ret = WM_gesture_straightline_invoke(C, op, event); + } + if (ret & OPERATOR_RUNNING_MODAL) { wmGesture *gesture = op->customdata; BisectData *opdata; @@ -166,8 +178,6 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) /* Misc other vars. */ G.moving = G_TRANSFORM_EDIT; - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; /* Initialize modal callout. */ ED_workspace_status_text(C, TIP_("LMB: Click and drag to draw cut line")); @@ -176,10 +186,8 @@ static int mesh_bisect_invoke(bContext *C, wmOperator *op, const wmEvent *event) return ret; } -static void edbm_bisect_exit(bContext *C, BisectData *opdata) +static void edbm_bisect_exit(BisectData *opdata) { - View3D *v3d = CTX_wm_view3d(C); - v3d->gizmo_flag = opdata->gizmo_flag; G.moving = 0; for (int ob_index = 0; ob_index < opdata->backup_len; ob_index++) { @@ -210,7 +218,7 @@ static int mesh_bisect_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (ret & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) { - edbm_bisect_exit(C, &opdata_back); + edbm_bisect_exit(&opdata_back); #ifdef USE_GIZMO /* Setup gizmos */ @@ -769,7 +777,7 @@ static void MESH_GGT_bisect(struct wmGizmoGroupType *gzgt) gzgt->name = "Mesh Bisect"; gzgt->idname = "MESH_GGT_bisect"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/mesh/editmesh_inset.c b/source/blender/editors/mesh/editmesh_inset.c index 18f51ae9df2..7be169f70f4 100644 --- a/source/blender/editors/mesh/editmesh_inset.c +++ b/source/blender/editors/mesh/editmesh_inset.c @@ -76,7 +76,6 @@ typedef struct { int launch_event; float mcenter[2]; void *draw_handle_pixel; - short gizmo_flag; } InsetData; static void edbm_inset_update_header(wmOperator *op, bContext *C) @@ -177,7 +176,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal) opdata->num_input.unit_type[1] = B_UNIT_LENGTH; if (is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { @@ -189,10 +187,6 @@ static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal) opdata->draw_handle_pixel = ED_region_draw_cb_activate( region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); G.moving = G_TRANSFORM_EDIT; - if (v3d) { - opdata->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } } return true; @@ -206,15 +200,11 @@ static void edbm_inset_exit(bContext *C, wmOperator *op) opdata = op->customdata; if (opdata->is_modal) { - View3D *v3d = CTX_wm_view3d(C); ARegion *region = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup); } ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel); - if (v3d) { - v3d->gizmo_flag = opdata->gizmo_flag; - } G.moving = 0; } diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 18f2b58eb65..040b5cd5066 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -37,6 +37,9 @@ set(INC ../../../../intern/clog ../../../../intern/glew-mx ../../../../intern/guardedalloc + + # dna_type_offsets.h in BLO_read_write.h + ${CMAKE_BINARY_DIR}/source/blender/makesdna/intern ) set(SRC @@ -93,3 +96,5 @@ if(WITH_EXPERIMENTAL_FEATURES) endif() blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +add_dependencies(bf_editor_object bf_dna) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 2e34284f46e..beadbf2689e 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -332,8 +332,12 @@ void ED_object_base_init_transform_on_add(Object *object, const float loc[3], co /* Uses context to figure out transform for primitive. * Returns standard diameter. */ -float ED_object_new_primitive_matrix( - bContext *C, Object *obedit, const float loc[3], const float rot[3], float r_primmat[4][4]) +float ED_object_new_primitive_matrix(bContext *C, + Object *obedit, + const float loc[3], + const float rot[3], + const float scale[3], + float r_primmat[4][4]) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); @@ -356,6 +360,10 @@ float ED_object_new_primitive_matrix( invert_m3_m3(imat, mat); mul_m3_v3(imat, r_primmat[3]); + if (scale != NULL) { + rescale_m4(r_primmat, scale); + } + { const float dia = v3d ? ED_view3d_grid_scale(scene, v3d, NULL) : ED_scene_grid_scale(scene, NULL); @@ -863,7 +871,7 @@ static int effector_add_exec(bContext *C, wmOperator *op) ED_object_editmode_enter_ex(bmain, scene, ob, 0); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat); mul_mat3_m4_fl(mat, dia); BLI_addtail(&cu->editnurb->nurbs, ED_curve_add_nurbs_primitive(C, ob, mat, CU_NURBS | CU_PRIM_PATH, 1)); @@ -999,7 +1007,7 @@ static int object_metaball_add_exec(bContext *C, wmOperator *op) } float mat[4][4]; - ED_object_new_primitive_matrix(C, obedit, loc, rot, mat); + ED_object_new_primitive_matrix(C, obedit, loc, rot, NULL, mat); /* Halving here is done to account for constant values from #BKE_mball_element_add. * While the default radius of the resulting meta element is 2, * we want to pass in 1 so other values such as resolution are scaled by 1.0. */ @@ -1365,30 +1373,28 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) case GP_EMPTY: { float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + ED_object_new_primitive_matrix(C, ob, loc, rot, NULL, mat); ED_gpencil_create_blank(C, ob, mat); break; } case GP_STROKE: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_stroke(C, ob, mat); break; } case GP_MONKEY: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_monkey(C, ob, mat); break; @@ -1397,12 +1403,11 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) case GP_LRT_COLLECTION: case GP_LRT_OBJECT: { float radius = RNA_float_get(op->ptr, "radius"); + float scale[3]; + copy_v3_fl(scale, radius); float mat[4][4]; - ED_object_new_primitive_matrix(C, ob, loc, rot, mat); - mul_v3_fl(mat[0], radius); - mul_v3_fl(mat[1], radius); - mul_v3_fl(mat[2], radius); + ED_object_new_primitive_matrix(C, ob, loc, rot, scale, mat); ED_gpencil_create_lineart(C, ob); @@ -2246,13 +2251,6 @@ static bool dupliobject_instancer_cmp(const void *a_, const void *b_) return false; } -static bool object_has_geometry_set_instances(const Object *object_eval) -{ - struct GeometrySet *geometry_set = object_eval->runtime.geometry_set_eval; - - return (geometry_set != NULL) && BKE_geometry_set_has_instances(geometry_set); -} - static void make_object_duplilist_real(bContext *C, Depsgraph *depsgraph, Scene *scene, @@ -2266,7 +2264,8 @@ static void make_object_duplilist_real(bContext *C, Object *object_eval = DEG_get_evaluated_object(depsgraph, base->object); - if (!(base->object->transflag & OB_DUPLI) && !object_has_geometry_set_instances(object_eval)) { + if (!(base->object->transflag & OB_DUPLI) && + !BKE_object_has_geometry_set_instances(object_eval)) { return; } diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index e0419e0a4cc..8702b18a46f 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -1786,7 +1786,8 @@ static bool constraint_copy_to_selected_poll(bContext *C) if (pchan) { bool found = false; - CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, UNUSED(ob)) { + CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, ob) { + UNUSED_VARS(ob); if (pchan != chan) { /** NOTE: Can not return here, because CTX_DATA_BEGIN_WITH_ID allocated * a list that needs to be freed by CTX_DATA_END. */ @@ -1862,6 +1863,7 @@ static int constraint_move_down_exec(bContext *C, wmOperator *op) BLI_remlink(conlist, con); BLI_insertlinkafter(conlist, nextCon, con); + ED_object_constraint_update(CTX_data_main(C), ob); WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob); return OPERATOR_FINISHED; @@ -1917,6 +1919,7 @@ static int constraint_move_up_exec(bContext *C, wmOperator *op) BLI_remlink(conlist, con); BLI_insertlinkbefore(conlist, prevCon, con); + ED_object_constraint_update(CTX_data_main(C), ob); WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob); return OPERATOR_FINISHED; @@ -1970,6 +1973,8 @@ static int constraint_move_to_index_exec(bContext *C, wmOperator *op) if (con) { ED_object_constraint_move_to_index(ob, con, new_index); + ED_object_constraint_update(CTX_data_main(C), ob); + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/object/object_gpencil_modifier.c b/source/blender/editors/object/object_gpencil_modifier.c index 3995728c428..e3c2932e17a 100644 --- a/source/blender/editors/object/object_gpencil_modifier.c +++ b/source/blender/editors/object/object_gpencil_modifier.c @@ -28,6 +28,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_defaults.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" #include "DNA_object_types.h" @@ -35,6 +36,7 @@ #include "BLI_listbase.h" #include "BLI_string_utf8.h" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -55,6 +57,8 @@ #include "ED_object.h" #include "ED_screen.h" +#include "BLT_translation.h" + #include "UI_interface.h" #include "WM_api.h" @@ -939,3 +943,237 @@ void OBJECT_OT_gpencil_modifier_copy_to_selected(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; gpencil_edit_modifier_properties(ot); } + +/************************* Dash Modifier *******************************/ + +static bool dash_segment_poll(bContext *C) +{ + return gpencil_edit_modifier_poll_generic(C, &RNA_DashGpencilModifierData, 0, false); +} + +static bool dash_segment_name_exists_fn(void *arg, const char *name) +{ + const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg; + for (int i = 0; i < dmd->segments_len; i++) { + if (STREQ(dmd->segments[i].name, name)) { + return true; + } + } + return false; +} + +static int dash_segment_add_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + const int new_active_index = dmd->segment_active_index + 1; + DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN( + dmd->segments_len + 1, sizeof(DashGpencilModifierSegment), __func__); + + if (dmd->segments_len != 0) { + /* Copy the segments before the new segment. */ + memcpy(new_segments, dmd->segments, sizeof(DashGpencilModifierSegment) * new_active_index); + /* Copy the segments after the new segment. */ + memcpy(new_segments + new_active_index + 1, + dmd->segments + new_active_index, + sizeof(DashGpencilModifierSegment) * (dmd->segments_len - new_active_index)); + } + + /* Create the new segment. */ + DashGpencilModifierSegment *ds = &new_segments[new_active_index]; + memcpy( + ds, DNA_struct_default_get(DashGpencilModifierSegment), sizeof(DashGpencilModifierSegment)); + BLI_uniquename_cb( + dash_segment_name_exists_fn, dmd, DATA_("Segment"), '.', ds->name, sizeof(ds->name)); + ds->dmd = dmd; + + MEM_SAFE_FREE(dmd->segments); + dmd->segments = new_segments; + dmd->segments_len++; + dmd->segment_active_index++; + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_add_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Segment"; + ot->description = "Add a segment to the dash modifier"; + ot->idname = "GPENCIL_OT_segment_add"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_add_invoke; + ot->exec = dash_segment_add_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); +} + +static int dash_segment_remove_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + if (dmd->segment_active_index < 0 || dmd->segment_active_index >= dmd->segments_len) { + return OPERATOR_CANCELLED; + } + + if (dmd->segments_len == 1) { + MEM_SAFE_FREE(dmd->segments); + dmd->segment_active_index = -1; + } + else { + DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN( + dmd->segments_len, sizeof(DashGpencilModifierSegment), __func__); + + /* Copy the segments before the deleted segment. */ + memcpy(new_segments, + dmd->segments, + sizeof(DashGpencilModifierSegment) * dmd->segment_active_index); + + /* Copy the segments after the deleted segment. */ + memcpy(new_segments + dmd->segment_active_index, + dmd->segments + dmd->segment_active_index + 1, + sizeof(DashGpencilModifierSegment) * + (dmd->segments_len - dmd->segment_active_index - 1)); + + MEM_freeN(dmd->segments); + dmd->segments = new_segments; + dmd->segment_active_index = MAX2(dmd->segment_active_index - 1, 0); + } + + dmd->segments_len--; + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_remove_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_remove_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Dash Segment"; + ot->description = "Remove the active segment from the dash modifier"; + ot->idname = "GPENCIL_OT_segment_remove"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_remove_invoke; + ot->exec = dash_segment_remove_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); + + RNA_def_int( + ot->srna, "index", 0, 0, INT_MAX, "Index", "Index of the segment to remove", 0, INT_MAX); +} + +enum { + GP_SEGEMENT_MOVE_UP = -1, + GP_SEGEMENT_MOVE_DOWN = 1, +}; + +static int dash_segment_move_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get( + op, ob, eGpencilModifierType_Dash); + + if (dmd->segments_len < 2) { + return OPERATOR_CANCELLED; + } + + const int direction = RNA_enum_get(op->ptr, "type"); + if (direction == GP_SEGEMENT_MOVE_UP) { + if (dmd->segment_active_index == 0) { + return OPERATOR_CANCELLED; + } + + SWAP(DashGpencilModifierSegment, + dmd->segments[dmd->segment_active_index], + dmd->segments[dmd->segment_active_index - 1]); + + dmd->segment_active_index--; + } + else if (direction == GP_SEGEMENT_MOVE_DOWN) { + if (dmd->segment_active_index == dmd->segments_len - 1) { + return OPERATOR_CANCELLED; + } + + SWAP(DashGpencilModifierSegment, + dmd->segments[dmd->segment_active_index], + dmd->segments[dmd->segment_active_index + 1]); + + dmd->segment_active_index++; + } + else { + return OPERATOR_CANCELLED; + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + + return OPERATOR_FINISHED; +} + +static int dash_segment_move_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) { + return dash_segment_move_exec(C, op); + } + return OPERATOR_CANCELLED; +} + +void GPENCIL_OT_segment_move(wmOperatorType *ot) +{ + static const EnumPropertyItem segment_move[] = { + {GP_SEGEMENT_MOVE_UP, "UP", 0, "Up", ""}, + {GP_SEGEMENT_MOVE_DOWN, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Move Dash Segment"; + ot->description = "Move the active dash segment up or down"; + ot->idname = "GPENCIL_OT_segment_move"; + + /* api callbacks */ + ot->poll = dash_segment_poll; + ot->invoke = dash_segment_move_invoke; + ot->exec = dash_segment_move_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); + + ot->prop = RNA_def_enum(ot->srna, "type", segment_move, 0, "Type", ""); +} diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 10e016738d0..b2d3216b101 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -202,6 +202,10 @@ void OBJECT_OT_gpencil_modifier_apply(struct wmOperatorType *ot); void OBJECT_OT_gpencil_modifier_copy(struct wmOperatorType *ot); void OBJECT_OT_gpencil_modifier_copy_to_selected(struct wmOperatorType *ot); +void GPENCIL_OT_segment_add(struct wmOperatorType *ot); +void GPENCIL_OT_segment_remove(struct wmOperatorType *ot); +void GPENCIL_OT_segment_move(struct wmOperatorType *ot); + /* object_shader_fx.c */ void OBJECT_OT_shaderfx_add(struct wmOperatorType *ot); void OBJECT_OT_shaderfx_copy(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index c1928cf7f8a..4b8431be530 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -156,6 +156,10 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy); WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected); + WM_operatortype_append(GPENCIL_OT_segment_add); + WM_operatortype_append(GPENCIL_OT_segment_remove); + WM_operatortype_append(GPENCIL_OT_segment_move); + /* grease pencil line art */ WM_operatortypes_lineart(); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index ec72ff11683..75269dffec8 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -2729,25 +2729,26 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C, PointerRNA *properties, const wmEvent *event) { - Object *ob = ED_view3d_give_object_under_cursor(C, event->mval); + int mat_slot = 0; + Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot); if (ob == NULL) { return BLI_strdup(""); } + mat_slot = max_ii(mat_slot, 1); char name[MAX_ID_NAME - 2]; RNA_string_get(properties, "name", name); - int active_mat_slot = max_ii(ob->actcol, 1); - Material *prev_mat = BKE_object_material_get(ob, active_mat_slot); + Material *prev_mat = BKE_object_material_get(ob, mat_slot); char *result; if (prev_mat) { const char *tooltip = TIP_("Drop %s on %s (slot %d, replacing %s)"); - result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot, prev_mat->id.name + 2); + result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot, prev_mat->id.name + 2); } else { const char *tooltip = TIP_("Drop %s on %s (slot %d)"); - result = BLI_sprintfN(tooltip, name, ob->id.name + 2, active_mat_slot); + result = BLI_sprintfN(tooltip, name, ob->id.name + 2, mat_slot); } return result; } @@ -2755,7 +2756,10 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C, static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Main *bmain = CTX_data_main(C); - Object *ob = ED_view3d_give_object_under_cursor(C, event->mval); + int mat_slot = 0; + Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot); + mat_slot = max_ii(mat_slot, 1); + Material *ma; char name[MAX_ID_NAME - 2]; @@ -2765,9 +2769,7 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } - const short active_mat_slot = ob->actcol; - - BKE_object_material_assign(CTX_data_main(C), ob, ma, active_mat_slot, BKE_MAT_ASSIGN_USERPREF); + BKE_object_material_assign(CTX_data_main(C), ob, ma, mat_slot, BKE_MAT_ASSIGN_USERPREF); DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index e08a4e946f6..9546035375c 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -882,7 +882,7 @@ static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area) return; } - if (U.app_flag & USER_APP_LOCK_UI_LAYOUT) { + if (U.app_flag & USER_APP_LOCK_CORNER_SPLIT) { return; } @@ -1058,6 +1058,14 @@ static bool region_azone_edge_poll(const ARegion *region, const bool is_fullscre return false; } + if (is_hidden && (U.app_flag & USER_APP_HIDE_REGION_TOGGLE)) { + return false; + } + + if (!is_hidden && (U.app_flag & USER_APP_LOCK_EDGE_RESIZE)) { + return false; + } + return true; } diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c index 51edad0332b..e67c933cb8e 100644 --- a/source/blender/editors/screen/screen_geometry.c +++ b/source/blender/editors/screen/screen_geometry.c @@ -130,6 +130,10 @@ ScrEdge *screen_geom_find_active_scredge(const wmWindow *win, const int mx, const int my) { + if (U.app_flag & USER_APP_LOCK_EDGE_RESIZE) { + return NULL; + } + /* Use layout size (screen excluding global areas) for screen-layout area edges */ rcti screen_rect; WM_window_screen_rect_calc(win, &screen_rect); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index daac196a90c..3efe4ae85d5 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -1134,14 +1134,22 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) && (screen_geom_area_map_find_active_scredge( AREAMAP_FROM_SCREEN(screen), &screen_rect, event->x, event->y) == NULL)) { - /* Are we still in same area? */ - if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) { + + /* What area are we now in? */ + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); + + if (area == sad->sa1) { /* Same area, so possible split. */ WM_cursor_set(win, SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); is_gesture = (delta_max > split_threshold); } + else if (!area || area->global) { + /* No area or Top bar or Status bar. */ + WM_cursor_set(win, WM_CURSOR_STOP); + is_gesture = false; + } else { /* Different area, so possible join. */ if (sad->gesture_dir == SCREEN_DIR_N) { @@ -1161,7 +1169,7 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) } } else { - WM_cursor_set(CTX_wm_window(C), WM_CURSOR_CROSS); + WM_cursor_set(win, WM_CURSOR_CROSS); is_gesture = false; } } @@ -1466,15 +1474,39 @@ static void SCREEN_OT_area_dupli(wmOperatorType *ot) * Close selected area, replace by expanding a neighbor * \{ */ -/* operator callback */ -static int area_close_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event)) +/** + * \note This can be used interactively or from Python. + * + * \note Most of the window management operators don't support execution from Python. + * An exception is made for closing areas since it allows application templates + * to customize the layout. + */ +static int area_close_exec(bContext *C, wmOperator *op) { + bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); - if ((area != NULL) && screen_area_close(C, CTX_wm_screen(C), area)) { - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); - return OPERATOR_FINISHED; + + /* This operator is scriptable, so the area passed could be invalid. */ + if (BLI_findindex(&screen->areabase, area) == -1) { + BKE_report(op->reports, RPT_ERROR, "Area not found in the active screen"); + return OPERATOR_CANCELLED; } - return OPERATOR_CANCELLED; + + if (!screen_area_close(C, screen, area)) { + BKE_report(op->reports, RPT_ERROR, "Unable to close area"); + return OPERATOR_CANCELLED; + } + + /* Ensure the event loop doesn't attempt to continue handling events. + * + * This causes execution from the Python console fail to return to the prompt as it should. + * This glitch could be solved in the event loop handling as other operators may also + * destructively manipulate windowing data. */ + CTX_wm_window_set(C, NULL); + + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); + + return OPERATOR_FINISHED; } static bool area_close_poll(bContext *C) @@ -1506,7 +1538,7 @@ static void SCREEN_OT_area_close(wmOperatorType *ot) ot->name = "Close Area"; ot->description = "Close selected area"; ot->idname = "SCREEN_OT_area_close"; - ot->invoke = area_close_invoke; + ot->exec = area_close_exec; ot->poll = area_close_poll; } @@ -3075,6 +3107,7 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op) mask_to_keylist(&ads, masklay, keylist); } } + ED_keylist_prepare_for_direct_access(keylist); /* find matching keyframe in the right direction */ const ActKeyColumn *ak; diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index 53d85b33a5b..9a5d4de3ebd 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -1327,6 +1327,7 @@ static void sculpt_gesture_apply_trim(SculptGestureContext *sgcontext) sculpt_mesh); BM_mesh_free(bm); result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); BKE_mesh_nomain_to_mesh( result, sgcontext->vc.obact->data, sgcontext->vc.obact, &CD_MASK_MESH, true); } diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_machine.c b/source/blender/editors/sculpt_paint/sculpt_brush_machine.c index 0f3b542f0e0..b4771bd783d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_machine.c +++ b/source/blender/editors/sculpt_paint/sculpt_brush_machine.c @@ -1,4 +1,4 @@ -#if 0 +#if 1 # include "MEM_guardedalloc.h" # include "BLI_alloca.h" @@ -10,53 +10,307 @@ # include "BLI_math.h" # include "BLI_memarena.h" +# include "DNA_brush_enums.h" +# include "DNA_brush_types.h" +# include "DNA_color_types.h" +# include "DNA_curveprofile_types.h" # include "DNA_node_types.h" +# include "BKE_brush.h" +# include "BKE_colorband.h" +# include "BKE_colortools.h" # include "BKE_context.h" # include "BKE_node.h" +# include "BKE_paint.h" + +# include "BKE_curveprofile.h" + +# define MAX_BRUSH_COMMAND_PARAMS 16 +# define MAX_BRUSH_CHANNEL_CURVES 3 + +enum { + BRUSH_CHANNEL_RADIUS = 1 << 0, + BRUSH_CHANNEL_STRENGTH = 1 << 1, + BRUSH_CHANNEL_CLOTH_TYPE = 1 << 2, // int + BRUSH_CHANNEL_RADIUS_SCALE = 1 << 3, + // BRUSH_CHANNEL_ITERATIONS = 1 << 3, // int + // BRUSH_CHANNEL_BOUNDARY_TYPE = 1 << 4, + // BRUSH_CHANNEL_AUTOMASKING_TYPE = 1 << 5, + CHANNEL_CUSTOM = 1 << 20 +}; + +typedef struct BrushChannel { + int type; + char name[32]; // for custom types + + float value; + CurveMapping curves[MAX_BRUSH_CHANNEL_CURVES]; + int flag; +} BrushChannel; + +# define MAX_BRUSH_ENUM_DEF 32 + +typedef struct BrushEnumDef { + EnumPropertyItem items[MAX_BRUSH_ENUM_DEF]; +} BrushEnumDef; + +typedef struct BrushChannelType { + char name[32]; + int channel; + float min, max, softmin, softmax; + int curve_presets[MAX_BRUSH_CHANNEL_CURVES]; + int type, subtype; + BrushEnumDef enumdef; // if an enum type +} BrushChannelType; + +// curves +enum { BRUSH_CHANNEL_PRESSURE, BRUSH_CHANNEL_XTILT, BRUSH_CHANNEL_YTILT }; + +enum { + BRUSH_CHANNEL_FLOAT = 1 << 0, + BRUSH_CHANNEL_INT = 1 << 1, + BRUSH_CHANNEL_ENUM = 1 << 0 // subtype +}; + +/* BrushChannel->flag */ +enum { + /*float only flags*/ + BRUSH_CHANNEL_USE_PRESSURE = 1 << 0, + BRUSH_CHANNEL_INV_PRESSURE = 1 << 1, + BRUSH_CHANNEL_USE_TILT = 1 << 2, +}; + +/* +Brush command lists. + +Command lists are built dynamically from +brush flags, pen input settings, etc. + +Eventually they will be generated by node +networks. BrushCommandPreset will be +generated from the node group inputs. +*/ + +typedef struct BrushCommandPreset { + char name[64]; + int tool; + + struct { + char name[32]; + int type; + float defval; + BrushChannelType *custom_type; + } channels[32]; +} BrushCommandPreset; + +/* clang-format off */ +BrushCommandPreset DrawBrush = { + .name = "Draw", + .tool = SCULPT_TOOL_DRAW, + .channels = { + {.name = "Radius", .type = BRUSH_CHANNEL_RADIUS, .defval = 50.0f}, + {.name = "Strength", .type = BRUSH_CHANNEL_STRENGTH, .defval = 1.0f}, + {.name = "Autosmooth", .type = BRUSH_CHANNEL_STRENGTH, .defval = 0.0f}, + {.name = "Autosmooth Radius Scale", .type = BRUSH_CHANNEL_RADIUS_SCALE, .defval = 1.0f}, + {.name = "Topology Rake", .type = BRUSH_CHANNEL_STRENGTH, .defval = 0.0f}, + {.name = "Topology Rake Radius Scale", .type = BRUSH_CHANNEL_RADIUS_SCALE, .defval = 1.0f}, + {.type = -1} + } +}; + +typedef struct BrushCommand { + int tool; + BrushChannel params[MAX_BRUSH_COMMAND_PARAMS]; + int totparam; +} BrushCommand; + +typedef struct BrushCommandList { + BrushCommand *commands; + int totcommand; +} BrushCommandList; + +static BrushChannelType brush_builtin_channels[] = { + {.name = "Radius", + .channel = BRUSH_CHANNEL_RADIUS, + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.001, + .max = 1024, + .softmin = 0.001, + .softmax = 700, + .curve_presets = {CURVE_PRESET_SMOOTH}}, + {.name = "Strength", + .channel = BRUSH_CHANNEL_STRENGTH, + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.001, + .max = 1024, + .softmin = 0.001, + .softmax = 700, + .curve_presets = {CURVE_PRESET_SMOOTH}}, + {.name = "Cloth Deform Type", + .channel = BRUSH_CHANNEL_CLOTH_TYPE, + .type = BRUSH_CHANNEL_INT, + .subtype = BRUSH_CHANNEL_ENUM, + .enumdef = + { + {BRUSH_CLOTH_DEFORM_DRAG, "DRAG", 0, "Drag", ""}, + {BRUSH_CLOTH_DEFORM_PUSH, "PUSH", 0, "Push", ""}, + {BRUSH_CLOTH_DEFORM_PINCH_POINT, "PINCH_POINT", 0, "Pinch Point", ""}, + {BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR, + "PINCH_PERPENDICULAR", + 0, + "Pinch Perpendicular", + ""}, + {BRUSH_CLOTH_DEFORM_INFLATE, "INFLATE", 0, "Inflate", ""}, + {BRUSH_CLOTH_DEFORM_GRAB, "GRAB", 0, "Grab", ""}, + {BRUSH_CLOTH_DEFORM_EXPAND, "EXPAND", 0, "Expand", ""}, + {BRUSH_CLOTH_DEFORM_SNAKE_HOOK, "SNAKE_HOOK", 0, "Snake Hook", ""}, + {0, NULL, 0, NULL, NULL}, + }}, + {.name = "Radius Scale", + .channel = BRUSH_CHANNEL_RADIUS, + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.001, + .max = 15.0, + .softmin = 0.1, + .softmax = 4.0, + .curve_presets = {CURVE_PRESET_SMOOTH}}}; + +enum { + BRUSH_COMMAND_TOPOLOGY_RAKE = 500, + BRUSH_COMMAND_DYNTOPO = 501, + BRUSH_COMMAND_SMOOTH_LAP = 502, + BRUSH_COMMAND_SMOOTH_SURFACE = 503, +}; + +/* clang-format on */ enum { - OP_MATH, - OP_COERCE, - OP_TOOL, - OP_CURVE_MAP, - OP_DYNTOPO, - OP_PEN_INPUT, - OP_PUSH, - OP_POP, - OP_POP_REG, - OP_PUSH_REG, - OP_LOAD_REG, - OP_STORE_REG -}; - -# define MAX_BRUSH_OP_ARGS 8 -# define MAX_BRUSH_REGISTERS 128 - -typedef union BrushOpArg { + SCULPT_OP_INT = 1 << 0, + SCULPT_OP_FLOAT = 1 << 1, + SCULPT_OP_VEC2 = 1 << 2, + SCULPT_OP_VEC3 = 1 << 3, + SCULPT_OP_VEC4 = 1 << 4, + SCULPT_OP_VEC5 = 1 << 5, + SCULPT_OP_PTR = 1 << 6 +}; + +typedef union SculptReg { float f; int i; + float v[5]; void *p; - char *s; - float v[4]; -} BrushOpArg; + char ch[32]; + // BrushChannel *ch; +} SculptReg; -typedef struct BrushOpCode { +typedef struct SculptOpCode { int code; + SculptReg params[16]; +} SculptOpCode; + +# define SCULPT_MAXREG 32 + +typedef struct SculptVM { + SculptOpCode *opcodes; + int totopcode; + SculptReg regs[SCULPT_MAXREG]; +}; + +enum { + SOP_LOADF = 0, // reg, float + SOP_LOADI, + SOP_LOADF2, // reg, float, float + SOP_LOADF3, // reg, float, float, float + SOP_LOADF4, // reg, float, float, float, float + SOP_LOADPTR, // reg, ptr + + SOP_PUSH, // reg + SOP_POP, // reg + SOP_POP_DISCARD, // + SOP_MUL, // dstreg reg reg + SOP_ADD, // dstreg reg reg + SOP_SUB, // dstreg reg reg + SOP_DIV, // dstreg reg reg + + SOP_LOAD_CHANNEL_F, // reg channel + SOP_LOAD_CHANNEL_I, // reg channel + SOP_TOOL_EXEC, // tool, ... +}; + +# define BRUSH_VALUE_DEFAULT FLT_MAX; + +/* clang-format off */ +#define REG_RADIUS 10 +#define REG_STRENGTH 11 +#define REG_AUTOSMOOTH 12 +#define REG_TOPORAKE 13 + +#define OP(op) {op}, +#define OP_I_CH(op, i1, ch1) {op, {{.i = i1}, {.ch = ch1}, {.i = -1}}}, +#define OP_I(op, i1, i2) {op, {{.i = i1}, {.i = -1}}}, +#define OP_I2(op, i1, i2) {op, {{.i = i1}, {.i = i2}, {.i = -1}}}, +#define OP_I3(op, i1, i2, i3) {op, {{.i = i1}, {.i = i2}, {.i = i3}, {.i = -1}}}, +#define OP_I4(op, i1, i2, i3, i4) {op, {{.i = i1}, {.i = i2}, {.i = i3}, {.i = i4}, {.i = -1}}}, + +SculptOpCode preset[] = { + OP_I_CH(SOP_LOAD_CHANNEL_F, REG_RADIUS, "Radius") + OP_I_CH(SOP_LOAD_CHANNEL_F, REG_STRENGTH, "Strength") + OP_I3(SOP_TOOL_EXEC, SCULPT_TOOL_DRAW, REG_RADIUS, REG_STRENGTH) + + OP_I_CH(SOP_LOAD_CHANNEL_F, REG_AUTOSMOOTH, "Radius") + OP_I_CH(SOP_LOAD_CHANNEL_F, REG_AUTOSMOOTH, "Autosmooth Radius Scale") + OP_I3(SOP_MUL, REG_AUTOSMOOTH, REG_AUTOSMOOTH, REG_RADIUS) +}; + +typedef struct GraphNode { + char name[32]; + char id[32]; + + struct { + char src[32]; + char node[32]; + char dst[32]; + } inputs[32]; +} GraphNode; + +/* node preset solution b: + +from brush_builder import Builder; + +def build(input, output): + input.add("Strength", "float", "strength").range(0.0, 3.0) + input.add("Radius", "float", "radius").range(0.01, 1024.0) + input.add("Autosmooth", "float", "autosmooth").range(0.0, 4.0) + input.add("Topology Rake", "float", "topology rake").range(0.0, 4.0) + input.add("Smooth Radius Scale", "float", "autosmooth_radius_scale").range(0.01, 5.0) + input.add("Rake Radius Scale", "float", "toporake_radius_scale").range(0.01, 5.0) + + draw = input.make.tool("DRAW") + draw.radius = input.radius + draw.strength = input.strength + + smooth = input.make.tool("SMOOTH") + smooth.radius = input.radius * input.autosmooth_radius_scale + smooth.strength = input.autosmooth; + smooth.flow = draw.outflow + + rake = input.make.tool("TOPORAKE") + rake.radius = input.radius * input.toporake_radius_scale + rake.strength = input.topology; + rake.flow = smooth.outflow + + output.out = rake.outflow - BrushOpArg args[MAX_BRUSH_OP_ARGS]; -} BrushOpCode; +preset = Builder(build) -typedef struct SculptBrushVM { - BrushOpCode *codes; - int totcode; +*/ - BrushOpArg registers[MAX_BRUSH_REGISTERS]; - BLI_ -} SculptBrushVM; -void SculptVM_AppendOp(SculptBrushVM *vm, BrushOpArg arg) -{ -} +/* +bNodeType sculpt_tool_node = { + .idname = "SculptTool", + .ui_name = "SculptTool", +};*/ +/* cland-format on */ #endif diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index 6f4e295cbb2..3e38be243c9 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -192,7 +192,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const bGPDlayer *gpl = ale->data; bGPDframe *gpf; - /* find gp-frame which is less than or equal to cframe */ + /* Find gp-frame which is less than or equal to current-frame. */ for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { const float framenum = (float)gpf->framenum; *min = min_ff(*min, framenum); @@ -204,7 +204,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const MaskLayer *masklay = ale->data; MaskLayerShape *masklay_shape; - /* find mask layer which is less than or equal to cframe */ + /* Find mask layer which is less than or equal to current-frame. */ for (masklay_shape = masklay->splines_shapes.first; masklay_shape; masklay_shape = masklay_shape->next) { const float framenum = (float)masklay_shape->frame; diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index 9dcfc626a50..a5e75e31e38 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -162,6 +162,7 @@ static void actkeys_find_key_in_list_element(bAnimContext *ac, struct AnimKeylist *keylist = ED_keylist_create(); actkeys_list_element_to_keylist(ac, keylist, ale); + ED_keylist_prepare_for_direct_access(keylist); AnimData *adt = ANIM_nla_mapping_get(ac, ale); diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index c7d23943b6c..511b5b255e9 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -1442,7 +1442,9 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat if (preview->in_memory_preview) { if (BKE_previewimg_is_finished(preview->in_memory_preview, ICON_SIZE_PREVIEW)) { ImBuf *imbuf = BKE_previewimg_to_imbuf(preview->in_memory_preview, ICON_SIZE_PREVIEW); - preview->icon_id = BKE_icon_imbuf_create(imbuf); + if (imbuf) { + preview->icon_id = BKE_icon_imbuf_create(imbuf); + } done = true; } } @@ -1953,7 +1955,9 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in if (entry->local_data.preview_image && BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { ImBuf *ibuf = BKE_previewimg_to_imbuf(entry->local_data.preview_image, ICON_SIZE_PREVIEW); - ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + if (ibuf) { + ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + } } BLI_addtail(&cache->cached_entries, ret); return ret; diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 4ab7014cf82..11b06d2b414 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -135,7 +135,8 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR; base_params->display = FILE_IMGDISPLAY; base_params->sort = FILE_SORT_ALPHA; - base_params->recursion_level = 1; + /* Asset libraries include all sub-directories, so enable maximal recursion. */ + base_params->recursion_level = FILE_SELECT_MAX_RECURSIONS; /* 'SMALL' size by default. More reasonable since this is typically used as regular editor, * space is more of an issue here. */ base_params->thumbnail_size = 96; diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index 10629caa8b0..f04336cab84 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -41,6 +41,7 @@ #include "ED_keyframes_edit.h" #include "ED_numinput.h" #include "ED_screen.h" +#include "ED_util.h" #include "WM_api.h" #include "WM_types.h" @@ -94,6 +95,8 @@ typedef struct tDecimateGraphOp { /** The original bezt curve data (used for restoring fcurves). */ ListBase bezt_arr_list; + struct tSlider *slider; + NumInput num; } tDecimateGraphOp; @@ -161,6 +164,8 @@ static void decimate_exit(bContext *C, wmOperator *op) ScrArea *area = dgo->area; LinkData *link; + ED_slider_destroy(C, dgo->slider); + for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) { tBeztCopyData *copy = link->data; MEM_freeN(copy->bezt); @@ -178,11 +183,14 @@ static void decimate_exit(bContext *C, wmOperator *op) op->customdata = NULL; } -/* Draw a percentage indicator in header. */ -static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) +/* Draw a percentage indicator in workspace footer. */ +static void decimate_draw_status(bContext *C, tDecimateGraphOp *dgo) { char status_str[UI_MAX_DRAW_STR]; char mode_str[32]; + char slider_string[UI_MAX_DRAW_STR]; + + ED_slider_status_string_get(dgo->slider, slider_string, UI_MAX_DRAW_STR); strcpy(mode_str, TIP_("Decimate Keyframes")); @@ -194,23 +202,10 @@ static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_ofs); } else { - float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); - BLI_snprintf( - status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f)); + BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, slider_string); } - ED_area_status_text(dgo->area, status_str); -} - -/* Calculate percentage based on position of mouse (we only use x-axis for now. - * Since this is more convenient for users to do), and store new percentage value. - */ -static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo, - wmOperator *op, - const wmEvent *event) -{ - float percentage = (event->x - dgo->region->winrct.xmin) / ((float)dgo->region->winx); - RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); + ED_workspace_status_text(C, status_str); } static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event) @@ -234,10 +229,11 @@ static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent dgo->area = CTX_wm_area(C); dgo->region = CTX_wm_region(C); - /* Initialize percentage so that it will have the correct value before the first mouse move. */ - decimate_mouse_update_percentage(dgo, op, event); + dgo->slider = ED_slider_create(C); + ED_slider_init(dgo->slider, event); + ED_slider_allow_overshoot_set(dgo->slider, false); - decimate_draw_status_header(op, dgo); + decimate_draw_status(C, dgo); /* Construct a list with the original bezt arrays so we can restore them during modal operation. */ @@ -300,13 +296,14 @@ static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op) * (e.g. pressing a key or moving the mouse). */ tDecimateGraphOp *dgo = op->customdata; - decimate_draw_status_header(op, dgo); + decimate_draw_status(C, dgo); /* Reset keyframe data (so we get back to the original state). */ decimate_reset_bezts(dgo); /* Apply... */ - float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop); + float remove_ratio = ED_slider_factor_get(dgo->slider); + RNA_property_float_set(op->ptr, dgo->percentage_prop, remove_ratio); /* We don't want to limit the decimation to a certain error margin. */ const float error_sq_max = FLT_MAX; decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max); @@ -323,6 +320,8 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent * const bool has_numinput = hasNumInput(&dgo->num); + ED_slider_modal(dgo->slider, event); + switch (event->type) { case LEFTMOUSE: /* Confirm */ case EVT_RETKEY: @@ -353,9 +352,6 @@ static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent * case MOUSEMOVE: /* Calculate new position. */ { if (has_numinput == false) { - /* Update percentage based on position of mouse. */ - decimate_mouse_update_percentage(dgo, op, event); - /* Update pose to reflect the new values. */ graphkeys_decimate_modal_update(C, op); } diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index 4107fd619aa..de8e4684d45 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -461,7 +461,7 @@ static void IMAGE_GGT_gizmo2d(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo"; gzgt->idname = "IMAGE_GGT_gizmo2d"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -475,7 +475,7 @@ static void IMAGE_GGT_gizmo2d_translate(wmGizmoGroupType *gzgt) gzgt->name = "UV Translate Gizmo"; gzgt->idname = "IMAGE_GGT_gizmo2d_translate"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -489,7 +489,7 @@ static void IMAGE_GGT_gizmo2d_resize(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo Resize"; gzgt->idname = "IMAGE_GGT_gizmo2d_resize"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; @@ -503,7 +503,7 @@ static void IMAGE_GGT_gizmo2d_rotate(wmGizmoGroupType *gzgt) gzgt->name = "UV Transform Gizmo Resize"; gzgt->idname = "IMAGE_GGT_gizmo2d_rotate"; - gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + gzgt->flag |= (WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); gzgt->gzmap_params.spaceid = SPACE_IMAGE; diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 0f7a911e3ce..4b859de0ac9 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -362,8 +362,12 @@ static void node_draw_frame_label(bNodeTree *ntree, bNode *node, const float asp float x = BLI_rctf_cent_x(rct) - (0.5f * width); float y = rct->ymax - label_height; - BLF_position(fontid, x, y, 0); - BLF_draw(fontid, label, BLF_DRAW_STR_DUMMY_MAX); + /* label */ + const bool has_label = node->label[0] != '\0'; + if (has_label) { + BLF_position(fontid, x, y, 0); + BLF_draw(fontid, label, BLF_DRAW_STR_DUMMY_MAX); + } /* draw text body */ if (node->id) { @@ -374,7 +378,8 @@ static void node_draw_frame_label(bNodeTree *ntree, bNode *node, const float asp /* 'x' doesn't need aspect correction */ x = rct->xmin + margin; - y = rct->ymax - (label_height + line_spacing); + y = rct->ymax - label_height - (has_label ? line_spacing : 0); + /* early exit */ int y_min = y + ((margin * 2) - (y - rct->ymin)); @@ -455,10 +460,8 @@ static void node_draw_frame(const bContext *C, UI_draw_roundbox_aa(rct, false, BASIS_RAD, color); } - /* label */ - if (node->label[0] != '\0') { - node_draw_frame_label(ntree, node, snode->runtime->aspect); - } + /* label and text */ + node_draw_frame_label(ntree, node, snode->runtime->aspect); UI_block_end(C, node->block); UI_block_draw(C, node->block); diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 9264c9d3572..4d2e00e97a1 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -575,7 +575,7 @@ static int node_add_texture_exec(bContext *C, wmOperator *op) bNode *texture_node = node_add_node(C, nullptr, - GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, + GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, snode->runtime->cursor[0], snode->runtime->cursor[1]); if (!texture_node) { diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 5b4e3b3b6f5..aa241071425 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -41,10 +41,12 @@ #include "BLI_span.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" +#include "BLI_vector_set.hh" #include "BLT_translation.h" #include "BKE_context.h" +#include "BKE_geometry_set.hh" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -78,6 +80,8 @@ #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field_cpp_type.hh" + #include "node_intern.h" /* own include */ #ifdef WITH_COMPOSITOR @@ -88,7 +92,11 @@ using blender::Map; using blender::Set; using blender::Span; using blender::Vector; +using blender::VectorSet; using blender::fn::CPPType; +using blender::fn::FieldCPPType; +using blender::fn::FieldInput; +using blender::fn::GField; using blender::fn::GPointer; namespace geo_log = blender::nodes::geometry_nodes_eval_log; @@ -850,31 +858,70 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal }; const GPointer value = value_log.value(); - if (value.is_type<int>()) { - ss << *value.get<int>() << TIP_(" (Integer)"); - } - else if (value.is_type<float>()) { - ss << *value.get<float>() << TIP_(" (Float)"); - } - else if (value.is_type<blender::float3>()) { - ss << *value.get<blender::float3>() << TIP_(" (Vector)"); - } - else if (value.is_type<bool>()) { - ss << (*value.get<bool>() ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); - } - else if (value.is_type<std::string>()) { - ss << *value.get<std::string>() << TIP_(" (String)"); + const CPPType &type = *value.type(); + if (const FieldCPPType *field_type = dynamic_cast<const FieldCPPType *>(&type)) { + const CPPType &base_type = field_type->field_type(); + BUFFER_FOR_CPP_TYPE_VALUE(base_type, buffer); + const GField &field = field_type->get_gfield(value.get()); + if (field.node().depends_on_input()) { + if (base_type.is<int>()) { + ss << TIP_("Integer Field"); + } + else if (base_type.is<float>()) { + ss << TIP_("Float Field"); + } + else if (base_type.is<blender::float3>()) { + ss << TIP_("Vector Field"); + } + else if (base_type.is<bool>()) { + ss << TIP_("Boolean Field"); + } + else if (base_type.is<std::string>()) { + ss << TIP_("String Field"); + } + ss << TIP_(" based on:\n"); + + /* Use vector set to deduplicate inputs. */ + VectorSet<std::reference_wrapper<const FieldInput>> field_inputs; + field.node().foreach_field_input( + [&](const FieldInput &field_input) { field_inputs.add(field_input); }); + for (const FieldInput &field_input : field_inputs) { + ss << "\u2022 " << field_input.socket_inspection_name(); + if (field_input != field_inputs.as_span().last().get()) { + ss << ".\n"; + } + } + } + else { + blender::fn::evaluate_constant_field(field, buffer); + if (base_type.is<int>()) { + ss << *(int *)buffer << TIP_(" (Integer)"); + } + else if (base_type.is<float>()) { + ss << *(float *)buffer << TIP_(" (Float)"); + } + else if (base_type.is<blender::float3>()) { + ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); + } + else if (base_type.is<bool>()) { + ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); + } + else if (base_type.is<std::string>()) { + ss << *(std::string *)buffer << TIP_(" (String)"); + } + base_type.destruct(buffer); + } } - else if (value.is_type<Object *>()) { + else if (type.is<Object *>()) { id_to_inspection_string((ID *)*value.get<Object *>(), ID_OB); } - else if (value.is_type<Material *>()) { + else if (type.is<Material *>()) { id_to_inspection_string((ID *)*value.get<Material *>(), ID_MA); } - else if (value.is_type<Tex *>()) { + else if (type.is<Tex *>()) { id_to_inspection_string((ID *)*value.get<Tex *>(), ID_TE); } - else if (value.is_type<Collection *>()) { + else if (type.is<Collection *>()) { id_to_inspection_string((ID *)*value.get<Collection *>(), ID_GR); } } diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index db37c8c1c8c..c06a1010168 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -32,6 +32,7 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "DNA_text_types.h" #include "BLI_blenlib.h" #include "BLI_math.h" @@ -59,6 +60,7 @@ #include "DEG_depsgraph_build.h" #include "ED_armature.h" +#include "ED_fileselect.h" #include "ED_outliner.h" #include "ED_screen.h" @@ -2625,9 +2627,17 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case ID_NLA: data.icon = ICON_NLA; break; - case ID_TXT: - data.icon = ICON_SCRIPT; + case ID_TXT: { + Text *text = (Text *)tselem->id; + if (text->filepath == NULL || (text->flags & TXT_ISMEM)) { + data.icon = ICON_FILE_TEXT; + } + else { + /* Helps distinguish text-based formats like the file-browser does. */ + data.icon = ED_file_extension_icon(text->filepath); + } break; + } case ID_GR: data.icon = ICON_OUTLINER_COLLECTION; break; diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 7709c6bb053..9e314701719 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -737,13 +737,8 @@ static void id_local_fn(bContext *C, { if (ID_IS_LINKED(tselem->id) && (tselem->id->tag & LIB_TAG_EXTERN)) { Main *bmain = CTX_data_main(C); - /* if the ID type has no special local function, - * just clear the lib */ - if (BKE_lib_id_make_local(bmain, tselem->id, false, 0) == false) { - BKE_lib_id_clear_library_data(bmain, tselem->id); - } - else { - BKE_main_id_newptr_and_tag_clear(bmain); + if (BKE_lib_id_make_local(bmain, tselem->id, 0)) { + BKE_id_newptr_and_tag_clear(tselem->id); } } else if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id)) { diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index 4c938a412d2..80d3e2cbdaa 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -504,238 +504,245 @@ void SEQUENCER_OT_select_inverse(struct wmOperatorType *ot) /** \name Select Operator * \{ */ -static int sequencer_select_exec(bContext *C, wmOperator *op) +static void sequencer_select_set_active(Scene *scene, Sequence *seq) { - View2D *v2d = UI_view2d_fromcontext(C); - Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); - const bool extend = RNA_boolean_get(op->ptr, "extend"); - const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all"); - const bool linked_handle = RNA_boolean_get(op->ptr, "linked_handle"); - const bool linked_time = RNA_boolean_get(op->ptr, "linked_time"); - bool side_of_frame = RNA_boolean_get(op->ptr, "side_of_frame"); - bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); - int mval[2]; - int ret_value = OPERATOR_CANCELLED; - mval[0] = RNA_int_get(op->ptr, "mouse_x"); - mval[1] = RNA_int_get(op->ptr, "mouse_y"); - - Sequence *seq, *neighbor, *act_orig; - int hand, sel_side; + SEQ_select_active_set(scene, seq); - if (ed == NULL) { - return OPERATOR_CANCELLED; + if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) { + if (seq->strip) { + BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR); + } } - - if (extend) { - wait_to_deselect_others = false; + else if (seq->type == SEQ_TYPE_SOUND_RAM) { + if (seq->strip) { + BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR); + } } + recurs_sel_seq(seq); +} - seq = find_nearest_seq(scene, v2d, &hand, mval); +static void sequencer_select_do_updates(bContext *C, Scene *scene) +{ + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); +} - /* XXX: not nice, Ctrl+RMB needs to do side_of_frame only when not over a strip. */ - if (seq && linked_time) { - side_of_frame = false; - } +static void sequencer_select_side_of_frame(const bContext *C, + const View2D *v2d, + const int mval[2], + Scene *scene) +{ + Editing *ed = SEQ_editing_get(scene); - /* Select left, right or overlapping the current frame. */ - if (side_of_frame) { - /* Use different logic for this. */ - if (extend == false) { - ED_sequencer_deselect_all(scene); + const float x = UI_view2d_region_to_view_x(v2d, mval[0]); + LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) { + if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) || + ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) { + /* Select left or right. */ + seq_iter->flag |= SELECT; + recurs_sel_seq(seq_iter); } + } - const float x = UI_view2d_region_to_view_x(v2d, mval[0]); + { + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (sseq && sseq->flag & SEQ_MARKER_TRANS) { + TimeMarker *tmarker; - LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) { - if (((x < CFRA) && (seq_iter->enddisp <= CFRA)) || - ((x >= CFRA) && (seq_iter->startdisp >= CFRA))) { - /* Select left or right. */ - seq_iter->flag |= SELECT; - recurs_sel_seq(seq_iter); + for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) { + if (((x < CFRA) && (tmarker->frame <= CFRA)) || + ((x >= CFRA) && (tmarker->frame >= CFRA))) { + tmarker->flag |= SELECT; + } + else { + tmarker->flag &= ~SELECT; + } } } + } +} - { - SpaceSeq *sseq = CTX_wm_space_seq(C); - if (sseq && sseq->flag & SEQ_MARKER_TRANS) { - TimeMarker *tmarker; +static void sequencer_select_linked_handle(const bContext *C, + Sequence *seq, + const int handle_clicked) +{ + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + if (!ELEM(handle_clicked, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) { + /* First click selects the strip and its adjacent handles (if valid). + * Second click selects the strip, + * both of its handles and its adjacent handles (if valid). */ + const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT); + seq->flag &= ~SEQ_ALLSEL; + seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT; + select_surrounding_handles(scene, seq); + } + else { + /* Always select the strip under the cursor. */ + seq->flag |= SELECT; - for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) { - if (((x < CFRA) && (tmarker->frame <= CFRA)) || - ((x >= CFRA) && (tmarker->frame >= CFRA))) { - tmarker->flag |= SELECT; + /* First click selects adjacent handles on that side. + * Second click selects all strips in that direction. + * If there are no adjacent strips, it just selects all in that direction. + */ + int sel_side = handle_clicked; + Sequence *neighbor = find_neighboring_sequence(scene, seq, sel_side, -1); + if (neighbor) { + switch (sel_side) { + case SEQ_SIDE_LEFT: + if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) { + seq->flag |= SELECT; + select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp); } else { - tmarker->flag &= ~SELECT; + seq->flag |= SELECT; + neighbor->flag |= SELECT; + recurs_sel_seq(neighbor); + neighbor->flag |= SEQ_RIGHTSEL; + seq->flag |= SEQ_LEFTSEL; } - } + break; + case SEQ_SIDE_RIGHT: + if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) { + seq->flag |= SELECT; + select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp); + } + else { + seq->flag |= SELECT; + neighbor->flag |= SELECT; + recurs_sel_seq(neighbor); + neighbor->flag |= SEQ_LEFTSEL; + seq->flag |= SEQ_RIGHTSEL; + } + break; } } + else { - ret_value = OPERATOR_FINISHED; + select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp); + } } - else { - act_orig = ed->act_seq; - - if (seq) { - /* Are we trying to select a handle that's already selected? */ - const bool handle_selected = ((hand == SEQ_SIDE_LEFT) && (seq->flag & SEQ_LEFTSEL)) || - ((hand == SEQ_SIDE_RIGHT) && (seq->flag & SEQ_RIGHTSEL)); - - if (wait_to_deselect_others && (seq->flag & SELECT) && - (hand == SEQ_SIDE_NONE || handle_selected)) { - ret_value = OPERATOR_RUNNING_MODAL; - } - else if (!extend && !linked_handle) { - ED_sequencer_deselect_all(scene); - ret_value = OPERATOR_FINISHED; - } - else { - ret_value = OPERATOR_FINISHED; - } - - SEQ_select_active_set(scene, seq); +} - if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) { - if (seq->strip) { - BLI_strncpy(ed->act_imagedir, seq->strip->dir, FILE_MAXDIR); - } - } - else if (seq->type == SEQ_TYPE_SOUND_RAM) { - if (seq->strip) { - BLI_strncpy(ed->act_sounddir, seq->strip->dir, FILE_MAXDIR); - } - } +static bool element_already_selected(const Sequence *seq, const int handle_clicked) +{ + const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) && + (seq->flag & SEQ_LEFTSEL)) || + ((handle_clicked == SEQ_SIDE_RIGHT) && + (seq->flag & SEQ_RIGHTSEL)); + return ((seq->flag & SELECT) && handle_clicked == SEQ_SIDE_NONE) || handle_already_selected; +} - /* On Alt selection, select the strip and bordering handles. */ - if (linked_handle) { - if (!ELEM(hand, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) { - /* First click selects the strip and its adjacent handles (if valid). - * Second click selects the strip, - * both of its handles and its adjacent handles (if valid). */ - const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT); +static void sequencer_select_strip_impl(const Editing *ed, + Sequence *seq, + const int handle_clicked, + const bool extend) +{ + /* Deselect strip. */ + if (extend && (seq->flag & SELECT) && ed->act_seq == seq) { + switch (handle_clicked) { + case SEQ_SIDE_NONE: + seq->flag &= ~SEQ_ALLSEL; + break; + case SEQ_SIDE_LEFT: + seq->flag ^= SEQ_LEFTSEL; + break; + case SEQ_SIDE_RIGHT: + seq->flag ^= SEQ_RIGHTSEL; + break; + } + } + else { /* Select strip. */ + seq->flag |= SELECT; + if (handle_clicked == SEQ_SIDE_LEFT) { + seq->flag |= SEQ_LEFTSEL; + } + if (handle_clicked == SEQ_SIDE_RIGHT) { + seq->flag |= SEQ_RIGHTSEL; + } + } +} - if (!extend) { - ED_sequencer_deselect_all(scene); - } - seq->flag &= ~SEQ_ALLSEL; - seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT; - select_surrounding_handles(scene, seq); - } - else { - /* Always select the strip under the cursor. */ - seq->flag |= SELECT; +static int sequencer_select_exec(bContext *C, wmOperator *op) +{ + View2D *v2d = UI_view2d_fromcontext(C); + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + const bool extend = RNA_boolean_get(op->ptr, "extend"); - /* First click selects adjacent handles on that side. - * Second click selects all strips in that direction. - * If there are no adjacent strips, it just selects all in that direction. - */ - sel_side = hand; - neighbor = find_neighboring_sequence(scene, seq, sel_side, -1); - if (neighbor) { - switch (sel_side) { - case SEQ_SIDE_LEFT: - if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - select_active_side(ed->seqbasep, SEQ_SIDE_LEFT, seq->machine, seq->startdisp); - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - neighbor->flag |= SELECT; - recurs_sel_seq(neighbor); - neighbor->flag |= SEQ_RIGHTSEL; - seq->flag |= SEQ_LEFTSEL; - } - break; - case SEQ_SIDE_RIGHT: - if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - select_active_side(ed->seqbasep, SEQ_SIDE_RIGHT, seq->machine, seq->startdisp); - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - seq->flag |= SELECT; - - neighbor->flag |= SELECT; - recurs_sel_seq(neighbor); - neighbor->flag |= SEQ_LEFTSEL; - seq->flag |= SEQ_RIGHTSEL; - } - break; - } - } - else { - if (extend == 0) { - ED_sequencer_deselect_all(scene); - } - select_active_side(ed->seqbasep, sel_side, seq->machine, seq->startdisp); - } - } + if (ed == NULL) { + return OPERATOR_CANCELLED; + } - ret_value = OPERATOR_FINISHED; - } - else { - if (extend && (seq->flag & SELECT) && ed->act_seq == act_orig) { - switch (hand) { - case SEQ_SIDE_NONE: - if (linked_handle == 0) { - seq->flag &= ~SEQ_ALLSEL; - } - break; - case SEQ_SIDE_LEFT: - seq->flag ^= SEQ_LEFTSEL; - break; - case SEQ_SIDE_RIGHT: - seq->flag ^= SEQ_RIGHTSEL; - break; - } - ret_value = OPERATOR_FINISHED; - } - else { - seq->flag |= SELECT; - if (hand == SEQ_SIDE_LEFT) { - seq->flag |= SEQ_LEFTSEL; - } - if (hand == SEQ_SIDE_RIGHT) { - seq->flag |= SEQ_RIGHTSEL; - } - } - } + int mval[2]; + mval[0] = RNA_int_get(op->ptr, "mouse_x"); + mval[1] = RNA_int_get(op->ptr, "mouse_y"); - recurs_sel_seq(seq); + int handle_clicked; + Sequence *seq = find_nearest_seq(scene, v2d, &handle_clicked, mval); - if (linked_time) { - select_linked_time(ed->seqbasep, seq); - } + /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap, + * therefore both properties can be true at the same time. */ + if (seq && RNA_boolean_get(op->ptr, "linked_time")) { + if (!extend) { + ED_sequencer_deselect_all(scene); + } + sequencer_select_strip_impl(ed, seq, handle_clicked, extend); + select_linked_time(ed->seqbasep, seq); + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); + return OPERATOR_FINISHED; + } - BLI_assert((ret_value & OPERATOR_CANCELLED) == 0); + /* Select left, right or overlapping the current frame. */ + if (RNA_boolean_get(op->ptr, "side_of_frame")) { + if (!extend) { + ED_sequencer_deselect_all(scene); } - else if (deselect_all) { + sequencer_select_side_of_frame(C, v2d, mval, scene); + sequencer_select_do_updates(C, scene); + return OPERATOR_FINISHED; + } + + /* On Alt selection, select the strip and bordering handles. */ + if (seq && RNA_boolean_get(op->ptr, "linked_handle")) { + if (!extend) { ED_sequencer_deselect_all(scene); - ret_value = OPERATOR_FINISHED; } + sequencer_select_linked_handle(C, seq, handle_clicked); + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); + return OPERATOR_FINISHED; } - ED_outliner_select_sync_from_sequence_tag(C); + const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); - WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); + /* Clicking on already selected element falls on modal operation. + * All strips are deselected on mouse button release unless extend mode is used. */ + if (seq && element_already_selected(seq, handle_clicked) && wait_to_deselect_others && !extend) { + return OPERATOR_RUNNING_MODAL; + } + + int ret_value = OPERATOR_CANCELLED; + if (!extend) { + ED_sequencer_deselect_all(scene); + ret_value = OPERATOR_FINISHED; + } + + /* Nothing to select, but strips could be deselected. */ + if (!seq) { + sequencer_select_do_updates(C, scene); + return ret_value; + } + + /* Do actual selection. */ + sequencer_select_strip_impl(ed, seq, handle_clicked, extend); + ret_value = OPERATOR_FINISHED; + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, seq); return ret_value; } @@ -764,13 +771,6 @@ void SEQUENCER_OT_select(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, - "deselect_all", - false, - "Deselect On Nothing", - "Deselect all when nothing under the cursor"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - - prop = RNA_def_boolean(ot->srna, "linked_handle", false, "Linked Handle", diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index fcc92345bea..a82648aeee0 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -270,7 +270,7 @@ Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet, return nullptr; } Object *object_orig = (Object *)used_id; - if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { + if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE, OB_FONT)) { return nullptr; } @@ -370,7 +370,7 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) std::unique_ptr<ColumnValues> values_ptr = data_source->get_column_values(*column->id); /* Should have been removed before if it does not exist anymore. */ BLI_assert(values_ptr); - const ColumnValues *values = scope.add(std::move(values_ptr), __func__); + const ColumnValues *values = scope.add(std::move(values_ptr)); const int width = get_column_width_in_pixels(*values); spreadsheet_layout.columns.append({values, width}); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh index 680da9b6794..97170693cb3 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -35,6 +35,10 @@ struct CollectionCellValue { const Collection *collection; }; +struct GeometrySetCellValue { + const GeometrySet *geometry_set; +}; + /** * This is a type that can hold the value of a cell in a spreadsheet. This type allows us to * decouple the drawing of individual cells from the code that generates the data to be displayed. @@ -53,6 +57,7 @@ class CellValue { std::optional<ColorGeometry4f> value_color; std::optional<ObjectCellValue> value_object; std::optional<CollectionCellValue> value_collection; + std::optional<GeometrySetCellValue> value_geometry_set; }; } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index e38c70afd0f..78d9f61d8d5 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -45,15 +45,19 @@ namespace blender::ed::spreadsheet { void GeometryDataSource::foreach_default_column_ids( FunctionRef<void(const SpreadsheetColumnID &)> fn) const { - component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != domain_) { - return true; - } - SpreadsheetColumnID column_id; - column_id.name = (char *)name.c_str(); - fn(column_id); - return true; - }); + component_->attribute_foreach( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != domain_) { + return true; + } + if (attribute_id.is_anonymous()) { + return true; + } + SpreadsheetColumnID column_id; + column_id.name = (char *)attribute_id.name().data(); + fn(column_id); + return true; + }); } std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( @@ -65,7 +69,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( if (!attribute) { return {}; } - const fn::GVArray *varray = scope_.add(std::move(attribute.varray), __func__); + const fn::GVArray *varray = scope_.add(std::move(attribute.varray)); if (attribute.domain != domain_) { return {}; } @@ -332,6 +336,11 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( r_cell_value.value_collection = CollectionCellValue{&collection}; break; } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &geometry_set = reference.geometry_set(); + r_cell_value.value_geometry_set = GeometrySetCellValue{&geometry_set}; + break; + } case InstanceReference::Type::None: { break; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index 8079763a339..1a5eac53306 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -209,6 +209,23 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { 0, nullptr); } + else if (cell_value.value_geometry_set.has_value()) { + uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_MESH_DATA, + "Geometry", + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + } } void draw_float_vector(const CellDrawParams ¶ms, const Span<float> values) const diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc index ae336edfead..1e46fef8d71 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc @@ -328,7 +328,7 @@ Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, geometry_data_source->apply_selection_filter(rows_included); } - Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(__func__); + Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(); index_vector_from_bools(rows_included, indices); return indices; diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index e3f97dd1c63..ff98762e373 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -1953,7 +1953,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc, const int mval[2], eV3DSelectObjectFilter select_filter, bool do_nearest, - bool do_nearest_xray_if_supported) + bool do_nearest_xray_if_supported, + const bool do_material_slot_selection) { rcti rect; int hits15, hits9 = 0, hits5 = 0; @@ -1972,7 +1973,8 @@ static int mixed_bones_object_selectbuffer(ViewContext *vc, view3d_opengl_select_cache_begin(); BLI_rcti_init_pt_radius(&rect, mval, 14); - hits15 = view3d_opengl_select(vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter); + hits15 = view3d_opengl_select_ex( + vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter, do_material_slot_selection); if (hits15 == 1) { hits = selectbuffer_ret_hits_15(buffer, hits15); goto finally; @@ -2071,7 +2073,8 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, do_nearest = do_nearest && !enumerate; - int hits = mixed_bones_object_selectbuffer(vc, buffer, mval, select_filter, do_nearest, true); + int hits = mixed_bones_object_selectbuffer( + vc, buffer, mval, select_filter, do_nearest, true, false); return hits; } @@ -2088,12 +2091,14 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, int hits, Base *startbase, bool has_bones, - bool do_nearest) + bool do_nearest, + int *r_sub_selection) { ViewLayer *view_layer = vc->view_layer; View3D *v3d = vc->v3d; Base *base, *basact = NULL; int a; + int sub_selection_id = 0; if (do_nearest) { uint min = 0xFFFFFFFF; @@ -2105,6 +2110,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, if (min > buffer[4 * a + 1] && (buffer[4 * a + 3] & 0xFFFF0000)) { min = buffer[4 * a + 1]; selcol = buffer[4 * a + 3] & 0xFFFF; + sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16; } } } @@ -2118,6 +2124,7 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, if (min > buffer[4 * a + 1] && notcol != (buffer[4 * a + 3] & 0xFFFF)) { min = buffer[4 * a + 1]; selcol = buffer[4 * a + 3] & 0xFFFF; + sub_selection_id = (buffer[4 * a + 3] & 0xFFFF0000) >> 16; } } } @@ -2184,11 +2191,16 @@ static Base *mouse_select_eval_buffer(ViewContext *vc, } } + if (basact && r_sub_selection) { + *r_sub_selection = sub_selection_id; + } + return basact; } -/* mval comes from event->mval, only use within region handlers */ -Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) +static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, + const int mval[2], + int *r_material_slot) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewContext vc; @@ -2202,18 +2214,30 @@ Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) ED_view3d_viewcontext_init(C, &vc, depsgraph); const bool do_nearest = !XRAY_ACTIVE(vc.v3d); + const bool do_material_slot_selection = r_material_slot != NULL; const int hits = mixed_bones_object_selectbuffer( - &vc, buffer, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false); + &vc, buffer, mval, VIEW3D_SELECT_FILTER_NOP, do_nearest, false, do_material_slot_selection); if (hits > 0) { - const bool has_bones = selectbuffer_has_bones(buffer, hits); - basact = mouse_select_eval_buffer( - &vc, buffer, hits, vc.view_layer->object_bases.first, has_bones, do_nearest); + const bool has_bones = (r_material_slot == NULL) && selectbuffer_has_bones(buffer, hits); + basact = mouse_select_eval_buffer(&vc, + buffer, + hits, + vc.view_layer->object_bases.first, + has_bones, + do_nearest, + r_material_slot); } return basact; } +/* mval comes from event->mval, only use within region handlers */ +Base *ED_view3d_give_base_under_cursor(bContext *C, const int mval[2]) +{ + return ed_view3d_give_base_under_cursor_ex(C, mval, NULL); +} + Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) { Base *base = ED_view3d_give_base_under_cursor(C, mval); @@ -2223,6 +2247,17 @@ Object *ED_view3d_give_object_under_cursor(bContext *C, const int mval[2]) return NULL; } +struct Object *ED_view3d_give_material_slot_under_cursor(struct bContext *C, + const int mval[2], + int *r_material_slot) +{ + Base *base = ed_view3d_give_base_under_cursor_ex(C, mval, r_material_slot); + if (base) { + return base->object; + } + return NULL; +} + bool ED_view3d_is_object_under_cursor(bContext *C, const int mval[2]) { return ED_view3d_give_object_under_cursor(C, mval) != NULL; @@ -2374,7 +2409,8 @@ static bool ed_object_select_pick(bContext *C, } } else { - basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, has_bones, do_nearest); + basact = mouse_select_eval_buffer( + &vc, buffer, hits, startbase, has_bones, do_nearest, NULL); } if (has_bones && basact) { @@ -2436,7 +2472,7 @@ static bool ed_object_select_pick(bContext *C, if (!changed) { /* fallback to regular object selection if no new bundles were selected, * allows to select object parented to reconstruction object */ - basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, 0, do_nearest); + basact = mouse_select_eval_buffer(&vc, buffer, hits, startbase, 0, do_nearest, NULL); } } } @@ -2677,7 +2713,7 @@ static int view3d_select_exec(bContext *C, wmOperator *op) uint buffer[MAXPICKBUF]; const int hits = mixed_bones_object_selectbuffer( - &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true); + &vc, buffer, location, VIEW3D_SELECT_FILTER_NOP, false, true, false); retval = bone_mouse_select_menu(C, buffer, hits, true, extend, deselect, toggle); } if (!retval) { diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c index 86a610f8dd9..f5da7c14a88 100644 --- a/source/blender/editors/space_view3d/view3d_view.c +++ b/source/blender/editors/space_view3d/view3d_view.c @@ -137,7 +137,6 @@ void ED_view3d_smooth_view_ex( { RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore sms = {{0}}; - bool ok = false; /* initialize sms */ view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d); @@ -200,92 +199,75 @@ void ED_view3d_smooth_view_ex( sms.to_camera = true; /* restore view3d values in end */ } - /* skip smooth viewing for external render engine draw */ + if ((sview->camera_old == sview->camera) && /* Camera. */ + (sms.dst.dist == rv3d->dist) && /* Distance. */ + (sms.dst.lens == v3d->lens) && /* Lens. */ + equals_v3v3(sms.dst.ofs, rv3d->ofs) && /* Offset. */ + equals_v4v4(sms.dst.quat, rv3d->viewquat) /* Rotation. */ + ) { + /* Early return if nothing changed. */ + return; + } + + /* Skip smooth viewing for external render engine draw. */ if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) { - bool changed = false; /* zero means no difference */ - if (sview->camera_old != sview->camera) { - changed = true; - } - else if (sms.dst.dist != rv3d->dist) { - changed = true; - } - else if (sms.dst.lens != v3d->lens) { - changed = true; - } - else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) { - changed = true; + /* original values */ + if (sview->camera_old) { + Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old); + if (sview->ofs != NULL) { + sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f); + } + ED_view3d_from_object( + ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens); } - else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) { - changed = true; + /* grid draw as floor */ + if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { + /* use existing if exists, means multiple calls to smooth view + * won't lose the original 'view' setting */ + rv3d->view = RV3D_VIEW_USER; } - /* The new view is different from the old one - * so animate the view */ - if (changed) { - /* original values */ - if (sview->camera_old) { - Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old); - if (sview->ofs != NULL) { - sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f); - } - ED_view3d_from_object( - ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens); - } - /* grid draw as floor */ - if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) { - /* use existing if exists, means multiple calls to smooth view - * won't lose the original 'view' setting */ - rv3d->view = RV3D_VIEW_USER; - } - - sms.time_allowed = (double)smooth_viewtx / 1000.0; - - /* if this is view rotation only - * we can decrease the time allowed by - * the angle between quats - * this means small rotations won't lag */ - if (sview->quat && !sview->ofs && !sview->dist) { - /* scale the time allowed by the rotation */ - /* 180deg == 1.0 */ - sms.time_allowed *= (double)fabsf( - angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) / - M_PI; - } + sms.time_allowed = (double)smooth_viewtx / 1000.0; - /* ensure it shows correct */ - if (sms.to_camera) { - /* use ortho if we move from an ortho view to an ortho camera */ - Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera); - rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) && - (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ? - RV3D_ORTHO : - RV3D_PERSP); - } + /* If this is view rotation only we can decrease the time allowed by the angle between quats + * this means small rotations won't lag. */ + if (sview->quat && !sview->ofs && !sview->dist) { + /* scale the time allowed by the rotation */ + /* 180deg == 1.0 */ + sms.time_allowed *= (double)fabsf(angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) / + M_PI; + } - rv3d->rflag |= RV3D_NAVIGATING; + /* ensure it shows correct */ + if (sms.to_camera) { + /* use ortho if we move from an ortho view to an ortho camera */ + Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera); + rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) && + (((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ? + RV3D_ORTHO : + RV3D_PERSP); + } - /* not essential but in some cases the caller will tag the area for redraw, and in that - * case we can get a flicker of the 'org' user view but we want to see 'src' */ - view3d_smooth_view_state_restore(&sms.src, v3d, rv3d); + rv3d->rflag |= RV3D_NAVIGATING; - /* keep track of running timer! */ - if (rv3d->sms == NULL) { - rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d"); - } - *rv3d->sms = sms; - if (rv3d->smooth_timer) { - WM_event_remove_timer(wm, win, rv3d->smooth_timer); - } - /* #TIMER1 is hard-coded in key-map. */ - rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); + /* not essential but in some cases the caller will tag the area for redraw, and in that + * case we can get a flicker of the 'org' user view but we want to see 'src' */ + view3d_smooth_view_state_restore(&sms.src, v3d, rv3d); - ok = true; + /* keep track of running timer! */ + if (rv3d->sms == NULL) { + rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d"); + } + *rv3d->sms = sms; + if (rv3d->smooth_timer) { + WM_event_remove_timer(wm, win, rv3d->smooth_timer); } + /* #TIMER1 is hard-coded in key-map. */ + rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); } - - /* if we get here nothing happens */ - if (ok == false) { + else { + /* Animation is disabled, apply immediately. */ if (sms.to_camera == false) { copy_v3_v3(rv3d->ofs, sms.dst.ofs); copy_qt_qt(rv3d->viewquat, sms.dst.quat); @@ -300,6 +282,8 @@ void ED_view3d_smooth_view_ex( } ED_region_tag_redraw(region); + + WM_event_add_mousemove(win); } } @@ -320,6 +304,7 @@ void ED_view3d_smooth_view(bContext *C, /* only meant for timer usage */ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview) { + wmWindowManager *wm = CTX_wm_manager(C); RegionView3D *rv3d = region->regiondata; struct SmoothView3DStore *sms = rv3d->sms; float step, step_inv; @@ -333,6 +318,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b /* end timer */ if (step >= 1.0f) { + wmWindow *win = CTX_wm_window(C); /* if we went to camera, store the original */ if (sms->to_camera) { @@ -355,9 +341,12 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b MEM_freeN(rv3d->sms); rv3d->sms = NULL; - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), rv3d->smooth_timer); + WM_event_remove_timer(wm, win, rv3d->smooth_timer); rv3d->smooth_timer = NULL; rv3d->rflag &= ~RV3D_NAVIGATING; + + /* Event handling won't know if a UI item has been moved under the pointer. */ + WM_event_add_mousemove(win); } else { /* ease in/out */ @@ -380,12 +369,9 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); - if (ED_screen_animation_playing(CTX_wm_manager(C))) { + if (ED_screen_animation_playing(wm)) { ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true); } - - /* Event handling won't know if a UI item has been moved under the pointer. */ - WM_event_add_mousemove(CTX_wm_window(C)); } if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) { @@ -964,12 +950,13 @@ static bool drw_select_filter_object_mode_lock_for_weight_paint(Object *ob, void * * \note (vc->obedit == NULL) can be set to explicitly skip edit-object selection. */ -int view3d_opengl_select(ViewContext *vc, - uint *buffer, - uint bufsize, - const rcti *input, - eV3DSelectMode select_mode, - eV3DSelectObjectFilter select_filter) +int view3d_opengl_select_ex(ViewContext *vc, + uint *buffer, + uint bufsize, + const rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter, + const bool do_material_slot_selection) { struct bThemeState theme_state; const wmWindowManager *wm = CTX_wm_manager(vc->C); @@ -1119,6 +1106,7 @@ int view3d_opengl_select(ViewContext *vc, use_obedit_skip, draw_surface, use_nearest, + do_material_slot_selection, &rect, drw_select_loop_pass, &drw_select_loop_user_data, @@ -1149,6 +1137,7 @@ int view3d_opengl_select(ViewContext *vc, use_obedit_skip, draw_surface, use_nearest, + do_material_slot_selection, &rect, drw_select_loop_pass, &drw_select_loop_user_data, @@ -1178,6 +1167,16 @@ finally: return hits; } +int view3d_opengl_select(ViewContext *vc, + uint *buffer, + uint bufsize, + const rcti *input, + eV3DSelectMode select_mode, + eV3DSelectObjectFilter select_filter) +{ + return view3d_opengl_select_ex(vc, buffer, bufsize, input, select_mode, select_filter, false); +} + int view3d_opengl_select_with_id_filter(ViewContext *vc, uint *buffer, uint bufsize, diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 013c5faa54a..d1a1937cef1 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -618,9 +618,6 @@ typedef struct TransInfo { O_SET, } orient_curr; - /** backup from view3d, to restore on end. */ - short gizmo_flag; - short prop_mode; /** Value taken as input, either through mouse coordinates or entered as a parameter. */ diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index 075db30fa61..a6658ae00a3 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -51,7 +51,10 @@ /* helper struct for gp-frame transforms */ typedef struct tGPFtransdata { - float val; /* where transdata writes transform */ + union { + float val; /* where transdata writes transform */ + float loc[3]; /* #td->val and #td->loc share the same pointer. */ + }; int *sdata; /* pointer to gpf->framenum */ } tGPFtransdata; @@ -245,8 +248,8 @@ static int GPLayerToTransData(TransData *td, tfd->val = (float)gpf->framenum; tfd->sdata = &gpf->framenum; - td->val = td->loc = &tfd->val; /* XXX: It's not a 3d array. */ - td->ival = td->iloc[0] = (float)gpf->framenum; + td->val = td->loc = &tfd->val; + td->ival = td->iloc[0] = tfd->val; td->center[0] = td->ival; td->center[1] = ypos; @@ -279,16 +282,15 @@ static int MaskLayerToTransData(TransData *td, masklay_shape = masklay_shape->next) { if (is_prop_edit || (masklay_shape->flag & MASK_SHAPE_SELECT)) { if (FrameOnMouseSide(side, (float)masklay_shape->frame, cfra)) { - /* memory is calloc'ed, so that should zero everything nicely for us */ - td->val = &tfd->val; - td->ival = (float)masklay_shape->frame; + tfd->val = (float)masklay_shape->frame; + tfd->sdata = &masklay_shape->frame; + + td->val = td->loc = &tfd->val; + td->ival = td->iloc[0] = tfd->val; td->center[0] = td->ival; td->center[1] = ypos; - tfd->val = (float)masklay_shape->frame; - tfd->sdata = &masklay_shape->frame; - /* advance td now */ td++; tfd++; diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 98e00c20170..8f896512410 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -145,15 +145,15 @@ static void autokeyframe_pose( if (act) { for (fcu = act->curves.first; fcu; fcu = fcu->next) { /* only insert keyframes for this F-Curve if it affects the current bone */ - char *pchanName = BLI_str_quoted_substrN(fcu->rna_path, "bones["); - if (pchanName == NULL) { + char pchan_name[sizeof(pchan->name)]; + if (!BLI_str_quoted_substr(fcu->rna_path, "bones[", pchan_name, sizeof(pchan_name))) { continue; } /* only if bone name matches too... * NOTE: this will do constraints too, but those are ok to do here too? */ - if (STREQ(pchanName, pchan->name)) { + if (STREQ(pchan_name, pchan->name)) { insert_keyframe(bmain, reports, id, @@ -166,8 +166,6 @@ static void autokeyframe_pose( &nla_cache, flag); } - - MEM_freeN(pchanName); } } } diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 9f5e74db501..c493b9bd102 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -249,12 +249,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->view = v3d; t->animtimer = (animscreen) ? animscreen->animtimer : NULL; - /* turn gizmo off during transform */ - if (t->flag & T_MODAL) { - t->gizmo_flag = v3d->gizmo_flag; - v3d->gizmo_flag = V3D_GIZMO_HIDE; - } - if (t->scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) { t->flag |= T_V3D_ALIGN; } @@ -742,13 +736,6 @@ void postTrans(bContext *C, TransInfo *t) } } } - else if (t->spacetype == SPACE_VIEW3D) { - View3D *v3d = t->area->spacedata.first; - /* restore gizmo */ - if (t->flag & T_MODAL) { - v3d->gizmo_flag = t->gizmo_flag; - } - } if (t->mouse.data) { MEM_freeN(t->mouse.data); @@ -791,7 +778,7 @@ static void restoreElement(TransData *td) { transdata_restore_basic((TransDataBasic *)td); - if (td->val) { + if (td->val && td->val != td->loc) { *td->val = td->ival; } diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 8dc4f107837..0fa179c4f74 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -1974,8 +1974,8 @@ void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt) gzgt->name = "3D View: Transform Gizmo"; gzgt->idname = "VIEW3D_GGT_xform_gizmo"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -2216,8 +2216,8 @@ void VIEW3D_GGT_xform_cage(wmGizmoGroupType *gzgt) gzgt->name = "Transform Cage"; gzgt->idname = "VIEW3D_GGT_xform_cage"; - gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -2459,8 +2459,8 @@ void VIEW3D_GGT_xform_shear(wmGizmoGroupType *gzgt) gzgt->name = "Transform Shear"; gzgt->idname = "VIEW3D_GGT_xform_shear"; - gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index bb04f557074..811f30c96e5 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -501,9 +501,7 @@ static void iter_snap_objects(SnapObjectContext *sctx, } Object *obj_eval = DEG_get_evaluated_object(depsgraph, base->object); - if (obj_eval->transflag & OB_DUPLI || - (obj_eval->runtime.geometry_set_eval != NULL && - BKE_geometry_set_has_instances(obj_eval->runtime.geometry_set_eval))) { + if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) { ListBase *lb = object_duplilist(depsgraph, sctx->scene, obj_eval); for (DupliObject *dupli_ob = lb->first; dupli_ob; dupli_ob = dupli_ob->next) { BLI_assert(DEG_is_evaluated_object(dupli_ob->ob)); diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index a54149912a9..e82a00bcc77 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -260,7 +260,7 @@ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) SeqCollection *snap_sources = SEQ_query_selected_strips(seqbase); SeqCollection *snap_targets = query_snap_targets(t, snap_sources); - if (SEQ_collection_len(snap_sources) == 0 || SEQ_collection_len(snap_targets) == 0) { + if (SEQ_collection_len(snap_sources) == 0) { SEQ_collection_free(snap_targets); SEQ_collection_free(snap_sources); MEM_freeN(snap_data); diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index 3e0029156c1..22064e04e86 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -574,7 +574,12 @@ static bool ed_undo_is_init_poll(bContext *C) { wmWindowManager *wm = CTX_wm_manager(C); if (wm->undo_stack == NULL) { - CTX_wm_operator_poll_msg_set(C, "Undo disabled at startup"); + /* This message is intended for Python developers, + * it will be part of the exception when attempting to call undo in background mode. */ + CTX_wm_operator_poll_msg_set( + C, + "Undo disabled at startup in background-mode " + "(call `ed.undo_push()` to explicitly initialize the undo-system)"); return false; } return true; diff --git a/source/blender/freestyle/intern/application/AppConfig.h b/source/blender/freestyle/intern/application/AppConfig.h index 61beff33876..adb6d906e68 100644 --- a/source/blender/freestyle/intern/application/AppConfig.h +++ b/source/blender/freestyle/intern/application/AppConfig.h @@ -115,10 +115,6 @@ static const string OPTIONS_FILE("options.xml"); static const string OPTIONS_CURRENT_DIRS_FILE("current_dirs.xml"); static const string OPTIONS_QGLVIEWER_FILE("qglviewer.xml"); -// Default options -static const real DEFAULT_SPHERE_RADIUS = 1.0; -static const real DEFAULT_DKR_EPSILON = 0.0; - } // namespace Config } /* namespace Freestyle */ diff --git a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp index 7772a30c5f4..c74fd60fe35 100644 --- a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp +++ b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp @@ -65,9 +65,6 @@ using namespace Freestyle; extern "C" { -#define DEFAULT_SPHERE_RADIUS 1.0f -#define DEFAULT_DKR_EPSILON 0.0f - struct FreestyleGlobals g_freestyle; // Freestyle configuration @@ -433,14 +430,8 @@ static void prepare(Render *re, ViewLayer *view_layer, Depsgraph *depsgraph) } // set parameters - if (config->flags & FREESTYLE_ADVANCED_OPTIONS_FLAG) { - controller->setSphereRadius(config->sphere_radius); - controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon); - } - else { - controller->setSphereRadius(DEFAULT_SPHERE_RADIUS); - controller->setSuggestiveContourKrDerivativeEpsilon(DEFAULT_DKR_EPSILON); - } + controller->setSphereRadius(config->sphere_radius); + controller->setSuggestiveContourKrDerivativeEpsilon(config->dkr_epsilon); controller->setFaceSmoothness((config->flags & FREESTYLE_FACE_SMOOTHNESS_FLAG) ? true : false); controller->setCreaseAngle(RAD2DEGF(config->crease_angle)); controller->setVisibilityAlgo((config->flags & FREESTYLE_CULLING) ? diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index f8d2acc74a8..856668f01d7 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -28,14 +28,21 @@ set(INC_SYS set(SRC intern/cpp_types.cc + intern/field.cc intern/generic_vector_array.cc intern/generic_virtual_array.cc intern/generic_virtual_vector_array.cc intern/multi_function.cc intern/multi_function_builder.cc + intern/multi_function_parallel.cc + intern/multi_function_procedure.cc + intern/multi_function_procedure_builder.cc + intern/multi_function_procedure_executor.cc FN_cpp_type.hh FN_cpp_type_make.hh + FN_field.hh + FN_field_cpp_type.hh FN_generic_pointer.hh FN_generic_span.hh FN_generic_value_map.hh @@ -48,6 +55,10 @@ set(SRC FN_multi_function_data_type.hh FN_multi_function_param_type.hh FN_multi_function_params.hh + FN_multi_function_parallel.hh + FN_multi_function_procedure.hh + FN_multi_function_procedure_builder.hh + FN_multi_function_procedure_executor.hh FN_multi_function_signature.hh ) @@ -55,13 +66,31 @@ set(LIB bf_blenlib ) +if(WITH_TBB) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${TBB_LIBRARIES} + ) +endif() + blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(WITH_GTESTS) set(TEST_SRC tests/FN_cpp_type_test.cc + tests/FN_field_test.cc tests/FN_generic_span_test.cc tests/FN_generic_vector_array_test.cc + tests/FN_multi_function_procedure_test.cc tests/FN_multi_function_test.cc ) set(TEST_LIB diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh new file mode 100644 index 00000000000..d4375b625ce --- /dev/null +++ b/source/blender/functions/FN_field.hh @@ -0,0 +1,487 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + * + * A #Field represents a function that outputs a value based on an arbitrary number of inputs. The + * inputs for a specific field evaluation are provided by a #FieldContext. + * + * A typical example is a field that computes a displacement vector for every vertex on a mesh + * based on its position. + * + * Fields can be build, composed and evaluated at run-time. They are stored in a directed tree + * graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode + * has an arbitrary number of inputs and at least one output and a #Field references a specific + * output of a #FieldNode. The inputs of a #FieldNode are other fields. + * + * There are two different types of field nodes: + * - #FieldInput: Has no input and exactly one output. It represents an input to the entire field + * when it is evaluated. During evaluation, the value of this input is based on a #FieldContext. + * - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main + * use is to compose multiple existing fields into new fields. + * + * When fields are evaluated, they are converted into a multi-function procedure which allows + * efficient computation. In the future, we might support different field evaluation mechanisms for + * e.g. the following scenarios: + * - Latency of a single evaluation is more important than throughput. + * - Evaluation should happen on other hardware like GPUs. + * + * Whenever possible, multiple fields should be evaluated together to avoid duplicate work when + * they share common sub-fields and a common context. + */ + +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "FN_generic_virtual_array.hh" +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_procedure.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" + +namespace blender::fn { + +class FieldInput; + +/** + * A node in a field-tree. It has at least one output that can be referenced by fields. + */ +class FieldNode { + private: + bool is_input_; + /** + * True when this node is a #FieldInput or (potentially indirectly) depends on one. This could + * always be derived again later by traversing the field-tree, but keeping track of it while the + * field is built is cheaper. + * + * If this is false, the field is constant. Note that even when this is true, the field may be + * constant when all inputs are constant. + */ + bool depends_on_input_; + + public: + FieldNode(bool is_input, bool depends_on_input) + : is_input_(is_input), depends_on_input_(depends_on_input) + { + } + + virtual ~FieldNode() = default; + + virtual const CPPType &output_cpp_type(int output_index) const = 0; + + bool is_input() const + { + return is_input_; + } + + bool is_operation() const + { + return !is_input_; + } + + bool depends_on_input() const + { + return depends_on_input_; + } + + /** + * Invoke callback for every field input. It might be called multiple times for the same input. + * The caller is responsible for deduplication if required. + */ + virtual void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const = 0; + + virtual uint64_t hash() const + { + return get_default_hash(this); + } + + friend bool operator==(const FieldNode &a, const FieldNode &b) + { + return a.is_equal_to(b); + } + + friend bool operator!=(const FieldNode &a, const FieldNode &b) + { + return !(a == b); + } + + virtual bool is_equal_to(const FieldNode &other) const + { + return this == &other; + } +}; + +/** + * Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef. + */ +template<typename NodePtr> class GFieldBase { + protected: + NodePtr node_ = nullptr; + int node_output_index_ = 0; + + GFieldBase(NodePtr node, const int node_output_index) + : node_(std::move(node)), node_output_index_(node_output_index) + { + } + + public: + GFieldBase() = default; + + operator bool() const + { + return node_ != nullptr; + } + + friend bool operator==(const GFieldBase &a, const GFieldBase &b) + { + /* Two nodes can compare equal even when their pointer is not the same. For example, two + * "Position" nodes are the same. */ + return *a.node_ == *b.node_ && a.node_output_index_ == b.node_output_index_; + } + + uint64_t hash() const + { + return get_default_hash_2(*node_, node_output_index_); + } + + const fn::CPPType &cpp_type() const + { + return node_->output_cpp_type(node_output_index_); + } + + const FieldNode &node() const + { + return *node_; + } + + int node_output_index() const + { + return node_output_index_; + } +}; + +/** + * A field whose output type is only known at run-time. + */ +class GField : public GFieldBase<std::shared_ptr<FieldNode>> { + public: + GField() = default; + + GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index) + { + } +}; + +/** + * Same as #GField but is cheaper to copy/move around, because it does not contain a + * #std::shared_ptr. + */ +class GFieldRef : public GFieldBase<const FieldNode *> { + public: + GFieldRef() = default; + + GFieldRef(const GField &field) + : GFieldBase<const FieldNode *>(&field.node(), field.node_output_index()) + { + } + + GFieldRef(const FieldNode &node, const int node_output_index = 0) + : GFieldBase<const FieldNode *>(&node, node_output_index) + { + } +}; + +/** + * A typed version of #GField. It has the same memory layout as #GField. + */ +template<typename T> class Field : public GField { + public: + Field() = default; + + Field(GField field) : GField(std::move(field)) + { + BLI_assert(this->cpp_type().template is<T>()); + } + + Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : Field(GField(std::move(node), node_output_index)) + { + } +}; + +/** + * A #FieldNode that allows composing existing fields into new fields. + */ +class FieldOperation : public FieldNode { + /** + * The multi-function used by this node. It is optionally owned. + * Multi-functions with mutable or vector parameters are not supported currently. + */ + std::unique_ptr<const MultiFunction> owned_function_; + const MultiFunction *function_; + + /** Inputs to the operation. */ + blender::Vector<GField> inputs_; + + public: + FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {}); + FieldOperation(const MultiFunction &function, Vector<GField> inputs = {}); + + Span<GField> inputs() const + { + return inputs_; + } + + const MultiFunction &multi_function() const + { + return *function_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + int output_counter = 0; + for (const int param_index : function_->param_indices()) { + MFParamType param_type = function_->param_type(param_index); + if (param_type.is_output()) { + if (output_counter == output_index) { + return param_type.data_type().single_type(); + } + output_counter++; + } + } + BLI_assert_unreachable(); + return CPPType::get<float>(); + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +class FieldContext; + +/** + * A #FieldNode that represents an input to the entire field-tree. + */ +class FieldInput : public FieldNode { + protected: + const CPPType *type_; + std::string debug_name_; + + public: + FieldInput(const CPPType &type, std::string debug_name = ""); + + /** + * Get the value of this specific input based on the given context. The returned virtual array, + * should live at least as long as the passed in #scope. May return null. + */ + virtual const GVArray *get_varray_for_context(const FieldContext &context, + IndexMask mask, + ResourceScope &scope) const = 0; + + virtual std::string socket_inspection_name() const + { + return debug_name_; + } + + blender::StringRef debug_name() const + { + return debug_name_; + } + + const CPPType &cpp_type() const + { + return *type_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + BLI_assert(output_index == 0); + UNUSED_VARS_NDEBUG(output_index); + return *type_; + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +/** + * Provides inputs for a specific field evaluation. + */ +class FieldContext { + public: + ~FieldContext() = default; + + virtual const GVArray *get_varray_for_input(const FieldInput &field_input, + IndexMask mask, + ResourceScope &scope) const; +}; + +/** + * Utility class that makes it easier to evaluate fields. + */ +class FieldEvaluator : NonMovable, NonCopyable { + private: + struct OutputPointerInfo { + void *dst = nullptr; + /* When a destination virtual array is provided for an input, this is + * unnecessary, otherwise this is used to construct the required virtual array. */ + void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr; + }; + + ResourceScope scope_; + const FieldContext &context_; + const IndexMask mask_; + Vector<GField> fields_to_evaluate_; + Vector<GVMutableArray *> dst_varrays_; + Vector<const GVArray *> evaluated_varrays_; + Vector<OutputPointerInfo> output_pointer_infos_; + bool is_evaluated_ = false; + + public: + /** Takes #mask by pointer because the mask has to live longer than the evaluator. */ + FieldEvaluator(const FieldContext &context, const IndexMask *mask) + : context_(context), mask_(*mask) + { + } + + /** Construct a field evaluator for all indices less than #size. */ + FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size) + { + } + + ~FieldEvaluator() + { + /* While this assert isn't strictly necessary, and could be replaced with a warning, + * it will catch cases where someone forgets to call #evaluate(). */ + BLI_assert(is_evaluated_); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable virtual array that the evaluated result for this field is be written into. + */ + int add_with_destination(GField field, GVMutableArray &dst); + + /** Same as #add_with_destination but typed. */ + template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(dst); + return this->add_with_destination(GField(std::move(field)), varray); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + int add_with_destination(GField field, GMutableSpan dst); + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(dst); + return this->add_with_destination(std::move(field), varray); + } + + int add(GField field, const GVArray **varray_ptr); + + /** + * \param field: Field to add to the evaluator. + * \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be + * assigned to the given position. + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr) + { + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append( + OutputPointerInfo{varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) { + *(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(varray); + }}); + return field_index; + } + + /** + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + int add(GField field); + + /** + * Evaluate all fields on the evaluator. This can only be called once. + */ + void evaluate(); + + const GVArray &get_evaluated(const int field_index) const + { + BLI_assert(is_evaluated_); + return *evaluated_varrays_[field_index]; + } + + template<typename T> const VArray<T> &get_evaluated(const int field_index) + { + const GVArray &varray = this->get_evaluated(field_index); + GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(varray); + return *typed_varray; + } + + /** + * Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used + * to avoid calculations for unnecessary elements later on. The evaluator will own the indices in + * some cases, so it must live at least as long as the returned mask. + */ + IndexMask get_evaluated_as_mask(const int field_index); +}; + +Vector<const GVArray *> evaluate_fields(ResourceScope &scope, + Span<GFieldRef> fields_to_evaluate, + IndexMask mask, + const FieldContext &context, + Span<GVMutableArray *> dst_varrays = {}); + +/* -------------------------------------------------------------------- + * Utility functions for simple field creation and evaluation. + */ + +void evaluate_constant_field(const GField &field, void *r_value); + +template<typename T> T evaluate_constant_field(const Field<T> &field) +{ + T value; + value.~T(); + evaluate_constant_field(field, &value); + return value; +} + +template<typename T> Field<T> make_constant_field(T value) +{ + auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value)); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return Field<T>{GField{std::move(operation), 0}}; +} + +GField make_field_constant_if_possible(GField field); + +} // namespace blender::fn diff --git a/source/blender/functions/FN_field_cpp_type.hh b/source/blender/functions/FN_field_cpp_type.hh new file mode 100644 index 00000000000..5e6f1b5a585 --- /dev/null +++ b/source/blender/functions/FN_field_cpp_type.hh @@ -0,0 +1,72 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_cpp_type_make.hh" +#include "FN_field.hh" + +namespace blender::fn { + +template<typename T> struct FieldCPPTypeParam { +}; + +class FieldCPPType : public CPPType { + private: + const CPPType &field_type_; + + public: + template<typename T> + FieldCPPType(FieldCPPTypeParam<Field<T>> /* unused */, StringRef debug_name) + : CPPType(CPPTypeParam<Field<T>, CPPTypeFlags::None>(), debug_name), + field_type_(CPPType::get<T>()) + { + } + + const CPPType &field_type() const + { + return field_type_; + } + + /* Ensure that #GField and #Field<T> have the same layout, to enable casting between the two. */ + static_assert(sizeof(Field<int>) == sizeof(GField)); + static_assert(sizeof(Field<int>) == sizeof(Field<std::string>)); + + const GField &get_gfield(const void *field) const + { + return *(const GField *)field; + } + + void construct_from_gfield(void *r_value, const GField &gfield) const + { + new (r_value) GField(gfield); + } +}; + +} // namespace blender::fn + +#define MAKE_FIELD_CPP_TYPE(DEBUG_NAME, FIELD_TYPE) \ + template<> \ + const blender::fn::CPPType &blender::fn::CPPType::get_impl<blender::fn::Field<FIELD_TYPE>>() \ + { \ + static blender::fn::FieldCPPType cpp_type{ \ + blender::fn::FieldCPPTypeParam<blender::fn::Field<FIELD_TYPE>>(), STRINGIFY(DEBUG_NAME)}; \ + return cpp_type; \ + } diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh index f429243e2de..703118ba23e 100644 --- a/source/blender/functions/FN_generic_virtual_array.hh +++ b/source/blender/functions/FN_generic_virtual_array.hh @@ -911,4 +911,50 @@ template<typename T> class GVMutableArray_Typed { } }; +class GVArray_For_SlicedGVArray : public GVArray { + protected: + const GVArray &varray_; + int64_t offset_; + + public: + GVArray_For_SlicedGVArray(const GVArray &varray, const IndexRange slice) + : GVArray(varray.type(), slice.size()), varray_(varray), offset_(slice.start()) + { + BLI_assert(slice.one_after_last() <= varray.size()); + } + + /* TODO: Add #materialize method. */ + void get_impl(const int64_t index, void *r_value) const override; + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override; +}; + +/** + * Utility class to create the "best" sliced virtual array. + */ +class GVArray_Slice { + private: + const GVArray *varray_; + /* Of these optional virtual arrays, at most one is constructed at any time. */ + std::optional<GVArray_For_GSpan> varray_span_; + std::optional<GVArray_For_SlicedGVArray> varray_any_; + + public: + GVArray_Slice(const GVArray &varray, const IndexRange slice); + + const GVArray &operator*() + { + return *varray_; + } + + const GVArray *operator->() + { + return varray_; + } + + operator const GVArray &() + { + return *varray_; + } +}; + } // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh index f6c4addfb52..98788025558 100644 --- a/source/blender/functions/FN_multi_function.hh +++ b/source/blender/functions/FN_multi_function.hh @@ -121,8 +121,13 @@ class MultiFunction { } }; -inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size) - : MFParamsBuilder(fn.signature(), min_array_size) +inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, int64_t mask_size) + : MFParamsBuilder(fn.signature(), IndexMask(mask_size)) +{ +} + +inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, const IndexMask *mask) + : MFParamsBuilder(fn.signature(), *mask) { } diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh index 7a526bb640b..0ce05cbca30 100644 --- a/source/blender/functions/FN_multi_function_builder.hh +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -326,18 +326,21 @@ template<typename From, typename To> class CustomMF_Convert : public MultiFuncti /** * A multi-function that outputs the same value every time. The value is not owned by an instance - * of this function. The caller is responsible for destructing and freeing the value. + * of this function. If #make_value_copy is false, the caller is responsible for destructing and + * freeing the value. */ class CustomMF_GenericConstant : public MultiFunction { private: const CPPType &type_; const void *value_; MFSignature signature_; + bool owns_value_; template<typename T> friend class CustomMF_Constant; public: - CustomMF_GenericConstant(const CPPType &type, const void *value); + CustomMF_GenericConstant(const CPPType &type, const void *value, bool make_value_copy); + ~CustomMF_GenericConstant(); void call(IndexMask mask, MFParams params, MFContext context) const override; uint64_t hash() const override; bool equals(const MultiFunction &other) const override; @@ -417,4 +420,13 @@ class CustomMF_DefaultOutput : public MultiFunction { void call(IndexMask mask, MFParams params, MFContext context) const override; }; +class CustomMF_GenericCopy : public MultiFunction { + private: + MFSignature signature_; + + public: + CustomMF_GenericCopy(StringRef name, MFDataType data_type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + } // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_parallel.hh b/source/blender/functions/FN_multi_function_parallel.hh new file mode 100644 index 00000000000..84c57efd434 --- /dev/null +++ b/source/blender/functions/FN_multi_function_parallel.hh @@ -0,0 +1,39 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function.hh" + +namespace blender::fn { + +class ParallelMultiFunction : public MultiFunction { + private: + const MultiFunction &fn_; + const int64_t grain_size_; + bool threading_supported_; + + public: + ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size); + + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh index a480287d578..fe4d2b90d80 100644 --- a/source/blender/functions/FN_multi_function_params.hh +++ b/source/blender/functions/FN_multi_function_params.hh @@ -38,6 +38,7 @@ class MFParamsBuilder { private: ResourceScope scope_; const MFSignature *signature_; + IndexMask mask_; int64_t min_array_size_; Vector<const GVArray *> virtual_arrays_; Vector<GMutableSpan> mutable_spans_; @@ -46,35 +47,39 @@ class MFParamsBuilder { friend class MFParams; - public: - MFParamsBuilder(const MFSignature &signature, int64_t min_array_size) - : signature_(&signature), min_array_size_(min_array_size) + MFParamsBuilder(const MFSignature &signature, const IndexMask mask) + : signature_(&signature), mask_(mask), min_array_size_(mask.min_array_size()) { } - MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size); + public: + MFParamsBuilder(const class MultiFunction &fn, int64_t size); + /** + * The indices referenced by the #mask has to live longer than the params builder. This is + * because the it might have to destruct elements for all masked indices in the end. + */ + MFParamsBuilder(const class MultiFunction &fn, const IndexMask *mask); template<typename T> void add_readonly_single_input_value(T value, StringRef expected_name = "") { - T *value_ptr = &scope_.add_value<T>(std::move(value), __func__); + T *value_ptr = &scope_.add_value<T>(std::move(value)); this->add_readonly_single_input(value_ptr, expected_name); } template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( - __func__, CPPType::get<T>(), min_array_size_, value), - expected_name); + this->add_readonly_single_input( + scope_.construct<GVArray_For_SingleValueRef>(CPPType::get<T>(), min_array_size_, value), + expected_name); } void add_readonly_single_input(const GSpan span, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(__func__, span), - expected_name); + this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(span), expected_name); } void add_readonly_single_input(GPointer value, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( - __func__, *value.type(), min_array_size_, value.get()), - expected_name); + this->add_readonly_single_input( + scope_.construct<GVArray_For_SingleValueRef>(*value.type(), min_array_size_, value.get()), + expected_name); } void add_readonly_single_input(const GVArray &ref, StringRef expected_name = "") { @@ -85,13 +90,13 @@ class MFParamsBuilder { void add_readonly_vector_input(const GVectorArray &vector_array, StringRef expected_name = "") { - this->add_readonly_vector_input( - scope_.construct<GVVectorArray_For_GVectorArray>(__func__, vector_array), expected_name); + this->add_readonly_vector_input(scope_.construct<GVVectorArray_For_GVectorArray>(vector_array), + expected_name); } void add_readonly_vector_input(const GSpan single_vector, StringRef expected_name = "") { this->add_readonly_vector_input( - scope_.construct<GVVectorArray_For_SingleGSpan>(__func__, single_vector, min_array_size_), + scope_.construct<GVVectorArray_For_SingleGSpan>(single_vector, min_array_size_), expected_name); } void add_readonly_vector_input(const GVVectorArray &ref, StringRef expected_name = "") @@ -112,6 +117,17 @@ class MFParamsBuilder { BLI_assert(ref.size() >= min_array_size_); mutable_spans_.append(ref); } + void add_ignored_single_output(StringRef expected_name = "") + { + this->assert_current_param_name(expected_name); + const int param_index = this->current_param_index(); + const MFParamType ¶m_type = signature_->param_types[param_index]; + BLI_assert(param_type.category() == MFParamType::SingleOutput); + const CPPType &type = param_type.data_type().single_type(); + /* An empty span indicates that this is ignored. */ + const GMutableSpan dummy_span{type}; + mutable_spans_.append(dummy_span); + } void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "") { @@ -176,6 +192,19 @@ class MFParamsBuilder { #endif } + void assert_current_param_name(StringRef expected_name) + { + UNUSED_VARS_NDEBUG(expected_name); +#ifdef DEBUG + if (expected_name.is_empty()) { + return; + } + const int param_index = this->current_param_index(); + StringRef actual_name = signature_->param_names[param_index]; + BLI_assert(actual_name == expected_name); +#endif + } + int current_param_index() const { return virtual_arrays_.size() + mutable_spans_.size() + virtual_vector_arrays_.size() + @@ -195,7 +224,7 @@ class MFParams { template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "") { const GVArray &array = this->readonly_single_input(param_index, name); - return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array); + return builder_->scope_.construct<GVArray_Typed<T>>(array); } const GVArray &readonly_single_input(int param_index, StringRef name = "") { @@ -204,6 +233,19 @@ class MFParams { return *builder_->virtual_arrays_[data_index]; } + /** + * \return True when the caller provided a buffer for this output parameter. This allows the + * called multi-function to skip some computation. It is still valid to call + * #uninitialized_single_output when this returns false. In this case a new temporary buffer is + * allocated. + */ + bool single_output_is_required(int param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleOutput); + int data_index = builder_->signature_->data_index(param_index); + return !builder_->mutable_spans_[data_index].is_empty(); + } + template<typename T> MutableSpan<T> uninitialized_single_output(int param_index, StringRef name = "") { @@ -213,14 +255,28 @@ class MFParams { { this->assert_correct_param(param_index, name, MFParamType::SingleOutput); int data_index = builder_->signature_->data_index(param_index); - return builder_->mutable_spans_[data_index]; + GMutableSpan span = builder_->mutable_spans_[data_index]; + if (span.is_empty()) { + /* The output is ignored by the caller, but the multi-function does not handle this case. So + * create a temporary buffer that the multi-function can write to. */ + const CPPType &type = span.type(); + void *buffer = builder_->scope_.linear_allocator().allocate( + builder_->min_array_size_ * type.size(), type.alignment()); + if (!type.is_trivially_destructible()) { + /* Make sure the temporary elements will be destructed in the end. */ + builder_->scope_.add_destruct_call( + [&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); }); + } + span = GMutableSpan{type, buffer, builder_->min_array_size_}; + } + return span; } template<typename T> const VVectorArray<T> &readonly_vector_input(int param_index, StringRef name = "") { const GVVectorArray &vector_array = this->readonly_vector_input(param_index, name); - return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(__func__, vector_array); + return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(vector_array); } const GVVectorArray &readonly_vector_input(int param_index, StringRef name = "") { diff --git a/source/blender/functions/FN_multi_function_procedure.hh b/source/blender/functions/FN_multi_function_procedure.hh new file mode 100644 index 00000000000..4c06ce98ee3 --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure.hh @@ -0,0 +1,539 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function.hh" + +namespace blender::fn { + +class MFVariable; +class MFInstruction; +class MFCallInstruction; +class MFBranchInstruction; +class MFDestructInstruction; +class MFDummyInstruction; +class MFReturnInstruction; +class MFProcedure; + +/** Every instruction has exactly one of these types. */ +enum class MFInstructionType { + Call, + Branch, + Destruct, + Dummy, + Return, +}; + +/** + * An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction + * can be inserted. + */ +class MFInstructionCursor { + public: + enum Type { + None, + Entry, + Call, + Destruct, + Branch, + Dummy, + }; + + private: + Type type_ = None; + MFInstruction *instruction_ = nullptr; + /* Only used when it is a branch instruction. */ + bool branch_output_ = false; + + public: + MFInstructionCursor() = default; + MFInstructionCursor(MFCallInstruction &instruction); + MFInstructionCursor(MFDestructInstruction &instruction); + MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output); + MFInstructionCursor(MFDummyInstruction &instruction); + + static MFInstructionCursor ForEntry(); + + MFInstruction *next(MFProcedure &procedure) const; + void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const; + + MFInstruction *instruction() const; + + Type type() const; + + friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b) + { + return a.type_ == b.type_ && a.instruction_ == b.instruction_ && + a.branch_output_ == b.branch_output_; + } + + friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b) + { + return !(a == b); + } +}; + +/** + * A variable is similar to a virtual register in other libraries. During evaluation, every is + * either uninitialized or contains a value for every index (remember, a multi-function procedure + * is always evaluated for many indices at the same time). + */ +class MFVariable : NonCopyable, NonMovable { + private: + MFDataType data_type_; + Vector<MFInstruction *> users_; + std::string name_; + int id_; + + friend MFProcedure; + friend MFCallInstruction; + friend MFBranchInstruction; + friend MFDestructInstruction; + + public: + MFDataType data_type() const; + Span<MFInstruction *> users(); + + StringRefNull name() const; + void set_name(std::string name); + + int id() const; +}; + +/** Base class for all instruction types. */ +class MFInstruction : NonCopyable, NonMovable { + protected: + MFInstructionType type_; + Vector<MFInstructionCursor> prev_; + + friend MFProcedure; + friend MFCallInstruction; + friend MFBranchInstruction; + friend MFDestructInstruction; + friend MFDummyInstruction; + friend MFReturnInstruction; + + public: + MFInstructionType type() const; + + /** + * Other instructions that come before this instruction. There can be multiple previous + * instructions when branching is used in the procedure. + */ + Span<MFInstructionCursor> prev() const; +}; + +/** + * References a multi-function that is evaluated when the instruction is executed. It also + * references the variables whose data will be passed into the multi-function. + */ +class MFCallInstruction : public MFInstruction { + private: + const MultiFunction *fn_ = nullptr; + MFInstruction *next_ = nullptr; + MutableSpan<MFVariable *> params_; + + friend MFProcedure; + + public: + const MultiFunction &fn() const; + + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); + + void set_param_variable(int param_index, MFVariable *variable); + void set_params(Span<MFVariable *> variables); + + Span<MFVariable *> params(); + Span<const MFVariable *> params() const; +}; + +/** + * What makes a branch instruction special is that it has two successor instructions. One that will + * be used when a condition variable was true, and one otherwise. + */ +class MFBranchInstruction : public MFInstruction { + private: + MFVariable *condition_ = nullptr; + MFInstruction *branch_true_ = nullptr; + MFInstruction *branch_false_ = nullptr; + + friend MFProcedure; + + public: + MFVariable *condition(); + const MFVariable *condition() const; + void set_condition(MFVariable *variable); + + MFInstruction *branch_true(); + const MFInstruction *branch_true() const; + void set_branch_true(MFInstruction *instruction); + + MFInstruction *branch_false(); + const MFInstruction *branch_false() const; + void set_branch_false(MFInstruction *instruction); +}; + +/** + * A destruct instruction destructs a single variable. So the variable value will be uninitialized + * after this instruction. All variables that are not output variables of the procedure, have to be + * destructed before the procedure ends. Destructing early is generally a good thing, because it + * might help with memory buffer reuse, which decreases memory-usage and increases performance. + */ +class MFDestructInstruction : public MFInstruction { + private: + MFVariable *variable_ = nullptr; + MFInstruction *next_ = nullptr; + + friend MFProcedure; + + public: + MFVariable *variable(); + const MFVariable *variable() const; + void set_variable(MFVariable *variable); + + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); +}; + +/** + * This instruction does nothing, it just exists to building a procedure simpler in some cases. + */ +class MFDummyInstruction : public MFInstruction { + private: + MFInstruction *next_ = nullptr; + + friend MFProcedure; + + public: + MFInstruction *next(); + const MFInstruction *next() const; + void set_next(MFInstruction *instruction); +}; + +/** + * This instruction ends the procedure. + */ +class MFReturnInstruction : public MFInstruction { +}; + +/** + * Inputs and outputs of the entire procedure network. + */ +struct MFParameter { + MFParamType::InterfaceType type; + MFVariable *variable; +}; + +struct ConstMFParameter { + MFParamType::InterfaceType type; + const MFVariable *variable; +}; + +/** + * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of + * variables and instructions that operate on those variables. Branching and looping within the + * procedure is supported as well. + * + * Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more + * utility methods for common use cases. + */ +class MFProcedure : NonCopyable, NonMovable { + private: + LinearAllocator<> allocator_; + Vector<MFCallInstruction *> call_instructions_; + Vector<MFBranchInstruction *> branch_instructions_; + Vector<MFDestructInstruction *> destruct_instructions_; + Vector<MFDummyInstruction *> dummy_instructions_; + Vector<MFReturnInstruction *> return_instructions_; + Vector<MFVariable *> variables_; + Vector<MFParameter> params_; + MFInstruction *entry_ = nullptr; + + friend class MFProcedureDotExport; + + public: + MFProcedure() = default; + ~MFProcedure(); + + MFVariable &new_variable(MFDataType data_type, std::string name = ""); + MFCallInstruction &new_call_instruction(const MultiFunction &fn); + MFBranchInstruction &new_branch_instruction(); + MFDestructInstruction &new_destruct_instruction(); + MFDummyInstruction &new_dummy_instruction(); + MFReturnInstruction &new_return_instruction(); + + void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable); + + Span<ConstMFParameter> params() const; + + MFInstruction *entry(); + const MFInstruction *entry() const; + void set_entry(MFInstruction &entry); + + Span<MFVariable *> variables(); + Span<const MFVariable *> variables() const; + + std::string to_dot() const; + + bool validate() const; + + private: + bool validate_all_instruction_pointers_set() const; + bool validate_all_params_provided() const; + bool validate_same_variables_in_one_call() const; + bool validate_parameters() const; + bool validate_initialization() const; + + struct InitState { + bool can_be_initialized = false; + bool can_be_uninitialized = false; + }; + + InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction, + const MFVariable &variable) const; +}; + +namespace multi_function_procedure_types { +using MFVariable = fn::MFVariable; +using MFInstruction = fn::MFInstruction; +using MFCallInstruction = fn::MFCallInstruction; +using MFBranchInstruction = fn::MFBranchInstruction; +using MFDestructInstruction = fn::MFDestructInstruction; +using MFProcedure = fn::MFProcedure; +} // namespace multi_function_procedure_types + +/* -------------------------------------------------------------------- + * MFInstructionCursor inline methods. + */ + +inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction) + : type_(Call), instruction_(&instruction) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction) + : type_(Destruct), instruction_(&instruction) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction, + bool branch_output) + : type_(Branch), instruction_(&instruction), branch_output_(branch_output) +{ +} + +inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction) + : type_(Dummy), instruction_(&instruction) +{ +} + +inline MFInstructionCursor MFInstructionCursor::ForEntry() +{ + MFInstructionCursor cursor; + cursor.type_ = Type::Entry; + return cursor; +} + +inline MFInstruction *MFInstructionCursor::instruction() const +{ + /* This isn't really const correct unfortunately, because to make it correct we'll need a const + * version of #MFInstructionCursor. */ + return instruction_; +} + +inline MFInstructionCursor::Type MFInstructionCursor::type() const +{ + return type_; +} + +/* -------------------------------------------------------------------- + * MFVariable inline methods. + */ + +inline MFDataType MFVariable::data_type() const +{ + return data_type_; +} + +inline Span<MFInstruction *> MFVariable::users() +{ + return users_; +} + +inline StringRefNull MFVariable::name() const +{ + return name_; +} + +inline int MFVariable::id() const +{ + return id_; +} + +/* -------------------------------------------------------------------- + * MFInstruction inline methods. + */ + +inline MFInstructionType MFInstruction::type() const +{ + return type_; +} + +inline Span<MFInstructionCursor> MFInstruction::prev() const +{ + return prev_; +} + +/* -------------------------------------------------------------------- + * MFCallInstruction inline methods. + */ + +inline const MultiFunction &MFCallInstruction::fn() const +{ + return *fn_; +} + +inline MFInstruction *MFCallInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFCallInstruction::next() const +{ + return next_; +} + +inline Span<MFVariable *> MFCallInstruction::params() +{ + return params_; +} + +inline Span<const MFVariable *> MFCallInstruction::params() const +{ + return params_; +} + +/* -------------------------------------------------------------------- + * MFBranchInstruction inline methods. + */ + +inline MFVariable *MFBranchInstruction::condition() +{ + return condition_; +} + +inline const MFVariable *MFBranchInstruction::condition() const +{ + return condition_; +} + +inline MFInstruction *MFBranchInstruction::branch_true() +{ + return branch_true_; +} + +inline const MFInstruction *MFBranchInstruction::branch_true() const +{ + return branch_true_; +} + +inline MFInstruction *MFBranchInstruction::branch_false() +{ + return branch_false_; +} + +inline const MFInstruction *MFBranchInstruction::branch_false() const +{ + return branch_false_; +} + +/* -------------------------------------------------------------------- + * MFDestructInstruction inline methods. + */ + +inline MFVariable *MFDestructInstruction::variable() +{ + return variable_; +} + +inline const MFVariable *MFDestructInstruction::variable() const +{ + return variable_; +} + +inline MFInstruction *MFDestructInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFDestructInstruction::next() const +{ + return next_; +} + +/* -------------------------------------------------------------------- + * MFDummyInstruction inline methods. + */ + +inline MFInstruction *MFDummyInstruction::next() +{ + return next_; +} + +inline const MFInstruction *MFDummyInstruction::next() const +{ + return next_; +} + +/* -------------------------------------------------------------------- + * MFProcedure inline methods. + */ + +inline Span<ConstMFParameter> MFProcedure::params() const +{ + static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter)); + return params_.as_span().cast<ConstMFParameter>(); +} + +inline MFInstruction *MFProcedure::entry() +{ + return entry_; +} + +inline const MFInstruction *MFProcedure::entry() const +{ + return entry_; +} + +inline Span<MFVariable *> MFProcedure::variables() +{ + return variables_; +} + +inline Span<const MFVariable *> MFProcedure::variables() const +{ + return variables_; +} + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_procedure_builder.hh b/source/blender/functions/FN_multi_function_procedure_builder.hh new file mode 100644 index 00000000000..e416f7e500d --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure_builder.hh @@ -0,0 +1,203 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function_procedure.hh" + +namespace blender::fn { + +/** + * Utility class to build a #MFProcedure. + */ +class MFProcedureBuilder { + private: + /** Procedure that is being build. */ + MFProcedure *procedure_ = nullptr; + /** Cursors where the next instruction should be inserted. */ + Vector<MFInstructionCursor> cursors_; + + public: + struct Branch; + struct Loop; + + MFProcedureBuilder(MFProcedure &procedure, + MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry()); + + MFProcedureBuilder(Span<MFProcedureBuilder *> builders); + + MFProcedureBuilder(Branch &branch); + + void set_cursor(const MFInstructionCursor &cursor); + void set_cursor(Span<MFInstructionCursor> cursors); + void set_cursor(Span<MFProcedureBuilder *> builders); + void set_cursor_after_branch(Branch &branch); + void set_cursor_after_loop(Loop &loop); + + void add_destruct(MFVariable &variable); + void add_destruct(Span<MFVariable *> variables); + + MFReturnInstruction &add_return(); + + Branch add_branch(MFVariable &condition); + + Loop add_loop(); + void add_loop_continue(Loop &loop); + void add_loop_break(Loop &loop); + + MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn); + MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn, + Span<MFVariable *> param_variables); + + Vector<MFVariable *> add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables = {}); + + template<int OutputN> + std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables = {}); + + void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable); + MFVariable &add_parameter(MFParamType param_type, std::string name = ""); + + MFVariable &add_input_parameter(MFDataType data_type, std::string name = ""); + template<typename T> MFVariable &add_single_input_parameter(std::string name = ""); + template<typename T> MFVariable &add_single_mutable_parameter(std::string name = ""); + + void add_output_parameter(MFVariable &variable); + + private: + void link_to_cursors(MFInstruction *instruction); +}; + +struct MFProcedureBuilder::Branch { + MFProcedureBuilder branch_true; + MFProcedureBuilder branch_false; +}; + +struct MFProcedureBuilder::Loop { + MFInstruction *begin = nullptr; + MFDummyInstruction *end = nullptr; +}; + +/* -------------------------------------------------------------------- + * MFProcedureBuilder inline methods. + */ + +inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch) + : MFProcedureBuilder(*branch.branch_true.procedure_) +{ + this->set_cursor_after_branch(branch); +} + +inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure, + MFInstructionCursor initial_cursor) + : procedure_(&procedure), cursors_({initial_cursor}) +{ +} + +inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders) + : MFProcedureBuilder(*builders[0]->procedure_) +{ + this->set_cursor(builders); +} + +inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor) +{ + cursors_ = {cursor}; +} + +inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors) +{ + cursors_ = cursors; +} + +inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch) +{ + this->set_cursor({&branch.branch_false, &branch.branch_true}); +} + +inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop) +{ + this->set_cursor(MFInstructionCursor{*loop.end}); +} + +inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders) +{ + cursors_.clear(); + for (MFProcedureBuilder *builder : builders) { + cursors_.extend(builder->cursors_); + } +} + +template<int OutputN> +inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call( + const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables) +{ + Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables); + BLI_assert(output_variables.size() == OutputN); + + std::array<MFVariable *, OutputN> output_array; + initialized_copy_n(output_variables.data(), OutputN, output_array.data()); + return output_array; +} + +inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type, + MFVariable &variable) +{ + procedure_->add_parameter(interface_type, variable); +} + +inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name) +{ + MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name)); + this->add_parameter(param_type.interface_type(), variable); + return variable; +} + +inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name) +{ + return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name)); +} + +template<typename T> +inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name) +{ + return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name)); +} + +template<typename T> +inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name) +{ + return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name)); +} + +inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable) +{ + this->add_parameter(MFParamType::Output, variable); +} + +inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction) +{ + for (MFInstructionCursor &cursor : cursors_) { + cursor.set_next(*procedure_, instruction); + } +} + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh new file mode 100644 index 00000000000..9c8b59739b8 --- /dev/null +++ b/source/blender/functions/FN_multi_function_procedure_executor.hh @@ -0,0 +1,39 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function_procedure.hh" + +namespace blender::fn { + +/** A multi-function that executes a procedure internally. */ +class MFProcedureExecutor : public MultiFunction { + private: + MFSignature signature_; + const MFProcedure &procedure_; + + public: + MFProcedureExecutor(std::string name, const MFProcedure &procedure); + + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace blender::fn diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc index 7be34d2a1bf..058fb76af2b 100644 --- a/source/blender/functions/intern/cpp_types.cc +++ b/source/blender/functions/intern/cpp_types.cc @@ -15,6 +15,7 @@ */ #include "FN_cpp_type_make.hh" +#include "FN_field_cpp_type.hh" #include "BLI_color.hh" #include "BLI_float2.hh" @@ -39,4 +40,12 @@ MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType MAKE_CPP_TYPE(string, std::string, CPPTypeFlags::BasicType) +MAKE_FIELD_CPP_TYPE(FloatField, float); +MAKE_FIELD_CPP_TYPE(Float2Field, float2); +MAKE_FIELD_CPP_TYPE(Float3Field, float3); +MAKE_FIELD_CPP_TYPE(ColorGeometry4fField, blender::ColorGeometry4f); +MAKE_FIELD_CPP_TYPE(BoolField, bool); +MAKE_FIELD_CPP_TYPE(Int32Field, int32_t); +MAKE_FIELD_CPP_TYPE(StringField, std::string); + } // namespace blender::fn diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc new file mode 100644 index 00000000000..6a4518ad4a6 --- /dev/null +++ b/source/blender/functions/intern/field.cc @@ -0,0 +1,672 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector_set.hh" + +#include "FN_field.hh" +#include "FN_multi_function_parallel.hh" + +namespace blender::fn { + +/* -------------------------------------------------------------------- + * Field Evaluation. + */ + +struct FieldTreeInfo { + /** + * When fields are built, they only have references to the fields that they depend on. This map + * allows traversal of fields in the opposite direction. So for every field it stores the other + * fields that depend on it directly. + */ + MultiValueMap<GFieldRef, GFieldRef> field_users; + /** + * The same field input may exist in the field tree as as separate nodes due to the way + * the tree is constructed. This set contains every different input only once. + */ + VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs; +}; + +/** + * Collects some information from the field tree that is required by later steps. + */ +static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields) +{ + FieldTreeInfo field_tree_info; + + Stack<GFieldRef> fields_to_check; + Set<GFieldRef> handled_fields; + + for (GFieldRef field : entry_fields) { + if (handled_fields.add(field)) { + fields_to_check.push(field); + } + } + + while (!fields_to_check.is_empty()) { + GFieldRef field = fields_to_check.pop(); + if (field.node().is_input()) { + const FieldInput &field_input = static_cast<const FieldInput &>(field.node()); + field_tree_info.deduplicated_field_inputs.add(field_input); + continue; + } + BLI_assert(field.node().is_operation()); + const FieldOperation &operation = static_cast<const FieldOperation &>(field.node()); + for (const GFieldRef operation_input : operation.inputs()) { + field_tree_info.field_users.add(operation_input, field); + if (handled_fields.add(operation_input)) { + fields_to_check.push(operation_input); + } + } + } + return field_tree_info; +} + +/** + * Retrieves the data from the context that is passed as input into the field. + */ +static Vector<const GVArray *> get_field_context_inputs( + ResourceScope &scope, + const IndexMask mask, + const FieldContext &context, + const Span<std::reference_wrapper<const FieldInput>> field_inputs) +{ + Vector<const GVArray *> field_context_inputs; + for (const FieldInput &field_input : field_inputs) { + const GVArray *varray = context.get_varray_for_input(field_input, mask, scope); + if (varray == nullptr) { + const CPPType &type = field_input.cpp_type(); + varray = &scope.construct<GVArray_For_SingleValueRef>( + type, mask.min_array_size(), type.default_value()); + } + field_context_inputs.append(varray); + } + return field_context_inputs; +} + +/** + * \return A set that contains all fields from the field tree that depend on an input that varies + * for different indices. + */ +static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info, + Span<const GVArray *> field_context_inputs) +{ + Set<GFieldRef> found_fields; + Stack<GFieldRef> fields_to_check; + + /* The varying fields are the ones that depend on inputs that are not constant. Therefore we + * start the tree search at the non-constant input fields and traverse through all fields that + * depend on them. */ + for (const int i : field_context_inputs.index_range()) { + const GVArray *varray = field_context_inputs[i]; + if (varray->is_single()) { + continue; + } + const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i]; + const GFieldRef field_input_field{field_input, 0}; + const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field); + for (const GFieldRef &field : users) { + if (found_fields.add(field)) { + fields_to_check.push(field); + } + } + } + while (!fields_to_check.is_empty()) { + GFieldRef field = fields_to_check.pop(); + const Span<GFieldRef> users = field_tree_info.field_users.lookup(field); + for (GFieldRef field : users) { + if (found_fields.add(field)) { + fields_to_check.push(field); + } + } + } + return found_fields; +} + +/** + * Builds the #procedure so that it computes the the fields. + */ +static void build_multi_function_procedure_for_fields(MFProcedure &procedure, + ResourceScope &scope, + const FieldTreeInfo &field_tree_info, + Span<GFieldRef> output_fields) +{ + MFProcedureBuilder builder{procedure}; + /* Every input, intermediate and output field corresponds to a variable in the procedure. */ + Map<GFieldRef, MFVariable *> variable_by_field; + + /* Start by adding the field inputs as parameters to the procedure. */ + for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) { + MFVariable &variable = builder.add_input_parameter( + MFDataType::ForSingle(field_input.cpp_type()), field_input.debug_name()); + variable_by_field.add_new({field_input, 0}, &variable); + } + + /* Utility struct that is used to do proper depth first search traversal of the tree below. */ + struct FieldWithIndex { + GFieldRef field; + int current_input_index = 0; + }; + + for (GFieldRef field : output_fields) { + /* We start a new stack for each output field to make sure that a field pushed later to the + * stack does never depend on a field that was pushed before. */ + Stack<FieldWithIndex> fields_to_check; + fields_to_check.push({field, 0}); + while (!fields_to_check.is_empty()) { + FieldWithIndex &field_with_index = fields_to_check.peek(); + const GFieldRef &field = field_with_index.field; + if (variable_by_field.contains(field)) { + /* The field has been handled already. */ + fields_to_check.pop(); + continue; + } + /* Field inputs should already be handled above. */ + BLI_assert(field.node().is_operation()); + + const FieldOperation &operation = static_cast<const FieldOperation &>(field.node()); + const Span<GField> operation_inputs = operation.inputs(); + + if (field_with_index.current_input_index < operation_inputs.size()) { + /* Not all inputs are handled yet. Push the next input field to the stack and increment the + * input index. */ + fields_to_check.push({operation_inputs[field_with_index.current_input_index]}); + field_with_index.current_input_index++; + } + else { + /* All inputs variables are ready, now gather all variables that are used by the function + * and call it. */ + const MultiFunction &multi_function = operation.multi_function(); + Vector<MFVariable *> variables(multi_function.param_amount()); + + int param_input_index = 0; + int param_output_index = 0; + for (const int param_index : multi_function.param_indices()) { + const MFParamType param_type = multi_function.param_type(param_index); + const MFParamType::InterfaceType interface_type = param_type.interface_type(); + if (interface_type == MFParamType::Input) { + const GField &input_field = operation_inputs[param_input_index]; + variables[param_index] = variable_by_field.lookup(input_field); + param_input_index++; + } + else if (interface_type == MFParamType::Output) { + const GFieldRef output_field{operation, param_output_index}; + const bool output_is_ignored = + field_tree_info.field_users.lookup(output_field).is_empty() && + !output_fields.contains(output_field); + if (output_is_ignored) { + /* Ignored outputs don't need a variable. */ + variables[param_index] = nullptr; + } + else { + /* Create a new variable for used outputs. */ + MFVariable &new_variable = procedure.new_variable(param_type.data_type()); + variables[param_index] = &new_variable; + variable_by_field.add_new(output_field, &new_variable); + } + param_output_index++; + } + else { + BLI_assert_unreachable(); + } + } + builder.add_call_with_all_variables(multi_function, variables); + } + } + } + + /* Add output parameters to the procedure. */ + Set<MFVariable *> already_output_variables; + for (const GFieldRef &field : output_fields) { + MFVariable *variable = variable_by_field.lookup(field); + if (!already_output_variables.add(variable)) { + /* One variable can be output at most once. To output the same value twice, we have to make + * a copy first. */ + const MultiFunction ©_fn = scope.construct<CustomMF_GenericCopy>("copy", + variable->data_type()); + variable = builder.add_call<1>(copy_fn, {variable})[0]; + } + builder.add_output_parameter(*variable); + } + + /* Remove the variables that should not be destructed from the map. */ + for (const GFieldRef &field : output_fields) { + variable_by_field.remove(field); + } + /* Add destructor calls for the remaining variables. */ + for (MFVariable *variable : variable_by_field.values()) { + builder.add_destruct(*variable); + } + + builder.add_return(); + + // std::cout << procedure.to_dot() << "\n"; + BLI_assert(procedure.validate()); +} + +/** + * Utility class that destructs elements from a partially initialized array. + */ +struct PartiallyInitializedArray : NonCopyable, NonMovable { + void *buffer; + IndexMask mask; + const CPPType *type; + + ~PartiallyInitializedArray() + { + this->type->destruct_indices(this->buffer, this->mask); + } +}; + +/** + * Evaluate fields in the given context. If possible, multiple fields should be evaluated together, + * because that can be more efficient when they share common sub-fields. + * + * \param scope: The resource scope that owns data that makes up the output virtual arrays. Make + * sure the scope is not destructed when the output virtual arrays are still used. + * \param fields_to_evaluate: The fields that should be evaluated together. + * \param mask: Determines which indices are computed. The mask may be referenced by the returned + * virtual arrays. So the underlying indices (if applicable) should live longer then #scope. + * \param context: The context that the field is evaluated in. Used to retrieve data from each + * #FieldInput in the field network. + * \param dst_varrays: If provided, the computed data will be written into those virtual arrays + * instead of into newly created ones. That allows making the computed data live longer than + * #scope and is more efficient when the data will be written into those virtual arrays + * later anyway. + * \return The computed virtual arrays for each provided field. If #dst_varrays is passed, the + * provided virtual arrays are returned. + */ +Vector<const GVArray *> evaluate_fields(ResourceScope &scope, + Span<GFieldRef> fields_to_evaluate, + IndexMask mask, + const FieldContext &context, + Span<GVMutableArray *> dst_varrays) +{ + Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr); + const int array_size = mask.min_array_size(); + + /* Destination arrays are optional. Create a small utility method to access them. */ + auto get_dst_varray_if_available = [&](int index) -> GVMutableArray * { + if (dst_varrays.is_empty()) { + return nullptr; + } + BLI_assert(dst_varrays[index] == nullptr || dst_varrays[index]->size() >= array_size); + return dst_varrays[index]; + }; + + /* Traverse the field tree and prepare some data that is used in later steps. */ + FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate); + + /* Get inputs that will be passed into the field when evaluated. */ + Vector<const GVArray *> field_context_inputs = get_field_context_inputs( + scope, mask, context, field_tree_info.deduplicated_field_inputs); + + /* Finish fields that output an input varray directly. For those we don't have to do any further + * processing. */ + for (const int out_index : fields_to_evaluate.index_range()) { + const GFieldRef &field = fields_to_evaluate[out_index]; + if (!field.node().is_input()) { + continue; + } + const FieldInput &field_input = static_cast<const FieldInput &>(field.node()); + const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(field_input); + const GVArray *varray = field_context_inputs[field_input_index]; + r_varrays[out_index] = varray; + } + + Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs); + + /* Separate fields into two categories. Those that are constant and need to be evaluated only + * once, and those that need to be evaluated for every index. */ + Vector<GFieldRef> varying_fields_to_evaluate; + Vector<int> varying_field_indices; + Vector<GFieldRef> constant_fields_to_evaluate; + Vector<int> constant_field_indices; + for (const int i : fields_to_evaluate.index_range()) { + if (r_varrays[i] != nullptr) { + /* Already done. */ + continue; + } + GFieldRef field = fields_to_evaluate[i]; + if (varying_fields.contains(field)) { + varying_fields_to_evaluate.append(field); + varying_field_indices.append(i); + } + else { + constant_fields_to_evaluate.append(field); + constant_field_indices.append(i); + } + } + + /* Evaluate varying fields if necessary. */ + if (!varying_fields_to_evaluate.is_empty()) { + /* Build the procedure for those fields. */ + MFProcedure procedure; + build_multi_function_procedure_for_fields( + procedure, scope, field_tree_info, varying_fields_to_evaluate); + MFProcedureExecutor procedure_executor{"Procedure", procedure}; + /* Add multi threading capabilities to the field evaluation. */ + const int grain_size = 10000; + fn::ParallelMultiFunction parallel_procedure_executor{procedure_executor, grain_size}; + /* Utility variable to make easy to switch the executor. */ + const MultiFunction &executor_fn = parallel_procedure_executor; + + MFParamsBuilder mf_params{executor_fn, &mask}; + MFContextBuilder mf_context; + + /* Provide inputs to the procedure executor. */ + for (const GVArray *varray : field_context_inputs) { + mf_params.add_readonly_single_input(*varray); + } + + for (const int i : varying_fields_to_evaluate.index_range()) { + const GFieldRef &field = varying_fields_to_evaluate[i]; + const CPPType &type = field.cpp_type(); + const int out_index = varying_field_indices[i]; + + /* Try to get an existing virtual array that the result should be written into. */ + GVMutableArray *output_varray = get_dst_varray_if_available(out_index); + void *buffer; + if (output_varray == nullptr || !output_varray->is_span()) { + /* Allocate a new buffer for the computed result. */ + buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment()); + + /* Make sure that elements in the buffer will be destructed. */ + PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); + destruct_helper.buffer = buffer; + destruct_helper.mask = mask; + destruct_helper.type = &type; + + r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>( + GSpan{type, buffer, array_size}); + } + else { + /* Write the result into the existing span. */ + buffer = output_varray->get_internal_span().data(); + + r_varrays[out_index] = output_varray; + } + + /* Pass output buffer to the procedure executor. */ + const GMutableSpan span{type, buffer, array_size}; + mf_params.add_uninitialized_single_output(span); + } + + executor_fn.call(mask, mf_params, mf_context); + } + + /* Evaluate constant fields if necessary. */ + if (!constant_fields_to_evaluate.is_empty()) { + /* Build the procedure for those fields. */ + MFProcedure procedure; + build_multi_function_procedure_for_fields( + procedure, scope, field_tree_info, constant_fields_to_evaluate); + MFProcedureExecutor procedure_executor{"Procedure", procedure}; + MFParamsBuilder mf_params{procedure_executor, 1}; + MFContextBuilder mf_context; + + /* Provide inputs to the procedure executor. */ + for (const GVArray *varray : field_context_inputs) { + mf_params.add_readonly_single_input(*varray); + } + + for (const int i : constant_fields_to_evaluate.index_range()) { + const GFieldRef &field = constant_fields_to_evaluate[i]; + const CPPType &type = field.cpp_type(); + /* Allocate memory where the computed value will be stored in. */ + void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment()); + + /* Use this to make sure that the value is destructed in the end. */ + PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); + destruct_helper.buffer = buffer; + destruct_helper.mask = IndexRange(1); + destruct_helper.type = &type; + + /* Pass output buffer to the procedure executor. */ + mf_params.add_uninitialized_single_output({type, buffer, 1}); + + /* Create virtual array that can be used after the procedure has been executed below. */ + const int out_index = constant_field_indices[i]; + r_varrays[out_index] = &scope.construct<GVArray_For_SingleValueRef>( + type, array_size, buffer); + } + + procedure_executor.call(IndexRange(1), mf_params, mf_context); + } + + /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has + * written the computed data in the right place already. */ + if (!dst_varrays.is_empty()) { + for (const int out_index : fields_to_evaluate.index_range()) { + GVMutableArray *output_varray = get_dst_varray_if_available(out_index); + if (output_varray == nullptr) { + /* Caller did not provide a destination for this output. */ + continue; + } + const GVArray *computed_varray = r_varrays[out_index]; + BLI_assert(computed_varray->type() == output_varray->type()); + if (output_varray == computed_varray) { + /* The result has been written into the destination provided by the caller already. */ + continue; + } + /* Still have to copy over the data in the destination provided by the caller. */ + if (output_varray->is_span()) { + /* Materialize into a span. */ + computed_varray->materialize_to_uninitialized(output_varray->get_internal_span().data()); + } + else { + /* Slower materialize into a different structure. */ + const CPPType &type = computed_varray->type(); + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + for (const int i : mask) { + computed_varray->get_to_uninitialized(i, buffer); + output_varray->set_by_relocate(i, buffer); + } + } + r_varrays[out_index] = output_varray; + } + } + return r_varrays; +} + +void evaluate_constant_field(const GField &field, void *r_value) +{ + ResourceScope scope; + FieldContext context; + Vector<const GVArray *> varrays = evaluate_fields(scope, {field}, IndexRange(1), context); + varrays[0]->get_to_uninitialized(0, r_value); +} + +/** + * If the field depends on some input, the same field is returned. + * Otherwise the field is evaluated and a new field is created that just computes this constant. + * + * Making the field constant has two benefits: + * - The field-tree becomes a single node, which is more efficient when the field is evaluated many + * times. + * - Memory of the input fields may be freed. + */ +GField make_field_constant_if_possible(GField field) +{ + if (field.node().depends_on_input()) { + return field; + } + const CPPType &type = field.cpp_type(); + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + evaluate_constant_field(field, buffer); + auto constant_fn = std::make_unique<CustomMF_GenericConstant>(type, buffer, true); + type.destruct(buffer); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return GField{operation, 0}; +} + +const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, + IndexMask mask, + ResourceScope &scope) const +{ + /* By default ask the field input to create the varray. Another field context might overwrite + * the context here. */ + return field_input.get_varray_for_context(*this, mask, scope); +} + +/* -------------------------------------------------------------------- + * FieldOperation. + */ + +FieldOperation::FieldOperation(std::unique_ptr<const MultiFunction> function, + Vector<GField> inputs) + : FieldOperation(*function, std::move(inputs)) +{ + owned_function_ = std::move(function); +} + +static bool any_field_depends_on_input(Span<GField> fields) +{ + for (const GField &field : fields) { + if (field.node().depends_on_input()) { + return true; + } + } + return false; +} + +FieldOperation::FieldOperation(const MultiFunction &function, Vector<GField> inputs) + : FieldNode(false, any_field_depends_on_input(inputs)), + function_(&function), + inputs_(std::move(inputs)) +{ +} + +void FieldOperation::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + for (const GField &field : inputs_) { + field.node().foreach_field_input(foreach_fn); + } +} + +/* -------------------------------------------------------------------- + * FieldInput. + */ + +FieldInput::FieldInput(const CPPType &type, std::string debug_name) + : FieldNode(true, true), type_(&type), debug_name_(std::move(debug_name)) +{ +} + +void FieldInput::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + foreach_fn(*this); +} + +/* -------------------------------------------------------------------- + * FieldEvaluator. + */ + +static Vector<int64_t> indices_from_selection(const VArray<bool> &selection) +{ + /* If the selection is just a single value, it's best to avoid calling this + * function when constructing an IndexMask and use an IndexRange instead. */ + BLI_assert(!selection.is_single()); + + Vector<int64_t> indices; + if (selection.is_span()) { + Span<bool> span = selection.get_internal_span(); + for (const int64_t i : span.index_range()) { + if (span[i]) { + indices.append(i); + } + } + } + else { + for (const int i : selection.index_range()) { + if (selection[i]) { + indices.append(i); + } + } + } + return indices; +} + +int FieldEvaluator::add_with_destination(GField field, GVMutableArray &dst) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(&dst); + output_pointer_infos_.append({}); + return field_index; +} + +int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst) +{ + GVMutableArray &varray = scope_.construct<GVMutableArray_For_GMutableSpan>(dst); + return this->add_with_destination(std::move(field), varray); +} + +int FieldEvaluator::add(GField field, const GVArray **varray_ptr) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append(OutputPointerInfo{ + varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &UNUSED(scope)) { + *(const GVArray **)dst = &varray; + }}); + return field_index; +} + +int FieldEvaluator::add(GField field) +{ + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append({}); + return field_index; +} + +void FieldEvaluator::evaluate() +{ + BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice."); + Array<GFieldRef> fields(fields_to_evaluate_.size()); + for (const int i : fields_to_evaluate_.index_range()) { + fields[i] = fields_to_evaluate_[i]; + } + evaluated_varrays_ = evaluate_fields(scope_, fields, mask_, context_, dst_varrays_); + BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size()); + for (const int i : fields_to_evaluate_.index_range()) { + OutputPointerInfo &info = output_pointer_infos_[i]; + if (info.dst != nullptr) { + info.set(info.dst, *evaluated_varrays_[i], scope_); + } + } + is_evaluated_ = true; +} + +IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index) +{ + const GVArray &varray = this->get_evaluated(field_index); + GVArray_Typed<bool> typed_varray{varray}; + + if (typed_varray->is_single()) { + if (typed_varray->get_internal_single()) { + return IndexRange(typed_varray.size()); + } + return IndexRange(0); + } + + return scope_.add_value(indices_from_selection(*typed_varray)).as_span(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc index bd033a429de..9a83d8cd497 100644 --- a/source/blender/functions/intern/generic_virtual_array.cc +++ b/source/blender/functions/intern/generic_virtual_array.cc @@ -387,4 +387,47 @@ void GVMutableArray_GSpan::disable_not_applied_warning() show_not_saved_warning_ = false; } +/* -------------------------------------------------------------------- + * GVArray_For_SlicedGVArray. + */ + +void GVArray_For_SlicedGVArray::get_impl(const int64_t index, void *r_value) const +{ + varray_.get(index + offset_, r_value); +} + +void GVArray_For_SlicedGVArray::get_to_uninitialized_impl(const int64_t index, void *r_value) const +{ + varray_.get_to_uninitialized(index + offset_, r_value); +} + +/* -------------------------------------------------------------------- + * GVArray_Slice. + */ + +GVArray_Slice::GVArray_Slice(const GVArray &varray, const IndexRange slice) +{ + if (varray.is_span()) { + /* Create a new virtual for the sliced span. */ + const GSpan span = varray.get_internal_span(); + const GSpan sliced_span = span.slice(slice.start(), slice.size()); + varray_span_.emplace(sliced_span); + varray_ = &*varray_span_; + } + else if (varray.is_single()) { + /* Can just use the existing virtual array, because it's the same value for the indices in the + * slice anyway. */ + varray_ = &varray; + } + else { + /* Generic version when none of the above method works. + * We don't necessarily want to materialize the input varray because there might be + * large distances between the required indices. Then we would materialize many elements that + * are not accessed later on. + */ + varray_any_.emplace(varray, slice); + varray_ = &*varray_any_; + } +} + } // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc index c6b3b808130..f891f162820 100644 --- a/source/blender/functions/intern/multi_function_builder.cc +++ b/source/blender/functions/intern/multi_function_builder.cc @@ -20,9 +20,18 @@ namespace blender::fn { -CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const void *value) - : type_(type), value_(value) +CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, + const void *value, + bool make_value_copy) + : type_(type), owns_value_(make_value_copy) { + if (make_value_copy) { + void *copied_value = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_construct(value, copied_value); + value = copied_value; + } + value_ = value; + MFSignatureBuilder signature{"Constant " + type.name()}; std::stringstream ss; type.print_or_default(value, ss, type.name()); @@ -31,6 +40,14 @@ CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const vo this->set_signature(&signature_); } +CustomMF_GenericConstant::~CustomMF_GenericConstant() +{ + if (owns_value_) { + signature_.param_types[0].data_type().single_type().destruct((void *)value_); + MEM_freeN((void *)value_); + } +} + void CustomMF_GenericConstant::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const @@ -123,4 +140,32 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU } } +CustomMF_GenericCopy::CustomMF_GenericCopy(StringRef name, MFDataType data_type) +{ + MFSignatureBuilder signature{name}; + signature.input("Input", data_type); + signature.output("Output", data_type); + signature_ = signature.build(); + this->set_signature(&signature_); +} + +void CustomMF_GenericCopy::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + const MFDataType data_type = this->param_type(0).data_type(); + switch (data_type.category()) { + case MFDataType::Single: { + const GVArray &inputs = params.readonly_single_input(0, "Input"); + GMutableSpan outputs = params.uninitialized_single_output(1, "Output"); + inputs.materialize_to_uninitialized(mask, outputs.data()); + break; + } + case MFDataType::Vector: { + const GVVectorArray &inputs = params.readonly_vector_input(0, "Input"); + GVectorArray &outputs = params.vector_output(1, "Output"); + outputs.extend(mask, inputs); + break; + } + } +} + } // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_parallel.cc b/source/blender/functions/intern/multi_function_parallel.cc new file mode 100644 index 00000000000..5a8c621f0b3 --- /dev/null +++ b/source/blender/functions/intern/multi_function_parallel.cc @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "FN_multi_function_parallel.hh" + +#include "BLI_task.hh" + +namespace blender::fn { + +ParallelMultiFunction::ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size) + : fn_(fn), grain_size_(grain_size) +{ + this->set_signature(&fn.signature()); + + threading_supported_ = true; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.data_type().category() == MFDataType::Vector) { + /* Vector parameters do not support threading yet. */ + threading_supported_ = false; + break; + } + } +} + +void ParallelMultiFunction::call(IndexMask full_mask, MFParams params, MFContext context) const +{ + if (full_mask.size() <= grain_size_ || !threading_supported_) { + fn_.call(full_mask, params, context); + return; + } + + threading::parallel_for(full_mask.index_range(), grain_size_, [&](const IndexRange mask_slice) { + Vector<int64_t> sub_mask_indices; + const IndexMask sub_mask = full_mask.slice_and_offset(mask_slice, sub_mask_indices); + if (sub_mask.is_empty()) { + return; + } + const int64_t input_slice_start = full_mask[mask_slice.first()]; + const int64_t input_slice_size = full_mask[mask_slice.last()] - input_slice_start + 1; + const IndexRange input_slice_range{input_slice_start, input_slice_size}; + + MFParamsBuilder sub_params{fn_, sub_mask.min_array_size()}; + ResourceScope &scope = sub_params.resource_scope(); + + /* All parameters are sliced so that the wrapped multi-function does not have to take care of + * the index offset. */ + for (const int param_index : fn_.param_indices()) { + const MFParamType param_type = fn_.param_type(param_index); + switch (param_type.category()) { + case MFParamType::SingleInput: { + const GVArray &varray = params.readonly_single_input(param_index); + const GVArray &sliced_varray = scope.construct<GVArray_Slice>(varray, input_slice_range); + sub_params.add_readonly_single_input(sliced_varray); + break; + } + case MFParamType::SingleMutable: { + const GMutableSpan span = params.single_mutable(param_index); + const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size); + sub_params.add_single_mutable(sliced_span); + break; + } + case MFParamType::SingleOutput: { + const GMutableSpan span = params.uninitialized_single_output(param_index); + const GMutableSpan sliced_span = span.slice(input_slice_start, input_slice_size); + sub_params.add_uninitialized_single_output(sliced_span); + break; + } + case MFParamType::VectorInput: + case MFParamType::VectorMutable: + case MFParamType::VectorOutput: { + BLI_assert_unreachable(); + break; + } + } + } + + fn_.call(sub_mask, sub_params, context); + }); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc new file mode 100644 index 00000000000..fa95e8de71e --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure.cc @@ -0,0 +1,874 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "FN_multi_function_procedure.hh" + +#include "BLI_dot_export.hh" +#include "BLI_stack.hh" + +namespace blender::fn { + +void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const +{ + switch (type_) { + case Type::None: { + break; + } + case Type::Entry: { + procedure.set_entry(*new_instruction); + break; + } + case Type::Call: { + static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction); + break; + } + case Type::Branch: { + MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_); + if (branch_output_) { + branch_instruction.set_branch_true(new_instruction); + } + else { + branch_instruction.set_branch_false(new_instruction); + } + break; + } + case Type::Destruct: { + static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction); + break; + } + case Type::Dummy: { + static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction); + break; + } + } +} + +MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const +{ + switch (type_) { + case Type::None: + return nullptr; + case Type::Entry: + return procedure.entry(); + case Type::Call: + return static_cast<MFCallInstruction *>(instruction_)->next(); + case Type::Branch: { + MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_); + if (branch_output_) { + return branch_instruction.branch_true(); + } + return branch_instruction.branch_false(); + } + case Type::Destruct: + return static_cast<MFDestructInstruction *>(instruction_)->next(); + case Type::Dummy: + return static_cast<MFDummyInstruction *>(instruction_)->next(); + } + return nullptr; +} + +void MFVariable::set_name(std::string name) +{ + name_ = std::move(name); +} + +void MFCallInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable) +{ + if (params_[param_index] != nullptr) { + params_[param_index]->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type()); + variable->users_.append(this); + } + params_[param_index] = variable; +} + +void MFCallInstruction::set_params(Span<MFVariable *> variables) +{ + BLI_assert(variables.size() == params_.size()); + for (const int i : variables.index_range()) { + this->set_param_variable(i, variables[i]); + } +} + +void MFBranchInstruction::set_condition(MFVariable *variable) +{ + if (condition_ != nullptr) { + condition_->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + variable->users_.append(this); + } + condition_ = variable; +} + +void MFBranchInstruction::set_branch_true(MFInstruction *instruction) +{ + if (branch_true_ != nullptr) { + branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true}); + } + if (instruction != nullptr) { + instruction->prev_.append({*this, true}); + } + branch_true_ = instruction; +} + +void MFBranchInstruction::set_branch_false(MFInstruction *instruction) +{ + if (branch_false_ != nullptr) { + branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false}); + } + if (instruction != nullptr) { + instruction->prev_.append({*this, false}); + } + branch_false_ = instruction; +} + +void MFDestructInstruction::set_variable(MFVariable *variable) +{ + if (variable_ != nullptr) { + variable_->users_.remove_first_occurrence_and_reorder(this); + } + if (variable != nullptr) { + variable->users_.append(this); + } + variable_ = variable; +} + +void MFDestructInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +void MFDummyInstruction::set_next(MFInstruction *instruction) +{ + if (next_ != nullptr) { + next_->prev_.remove_first_occurrence_and_reorder(*this); + } + if (instruction != nullptr) { + instruction->prev_.append(*this); + } + next_ = instruction; +} + +MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name) +{ + MFVariable &variable = *allocator_.construct<MFVariable>().release(); + variable.name_ = std::move(name); + variable.data_type_ = data_type; + variable.id_ = variables_.size(); + variables_.append(&variable); + return variable; +} + +MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn) +{ + MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release(); + instruction.type_ = MFInstructionType::Call; + instruction.fn_ = &fn; + instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount()); + instruction.params_.fill(nullptr); + call_instructions_.append(&instruction); + return instruction; +} + +MFBranchInstruction &MFProcedure::new_branch_instruction() +{ + MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release(); + instruction.type_ = MFInstructionType::Branch; + branch_instructions_.append(&instruction); + return instruction; +} + +MFDestructInstruction &MFProcedure::new_destruct_instruction() +{ + MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release(); + instruction.type_ = MFInstructionType::Destruct; + destruct_instructions_.append(&instruction); + return instruction; +} + +MFDummyInstruction &MFProcedure::new_dummy_instruction() +{ + MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release(); + instruction.type_ = MFInstructionType::Dummy; + dummy_instructions_.append(&instruction); + return instruction; +} + +MFReturnInstruction &MFProcedure::new_return_instruction() +{ + MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release(); + instruction.type_ = MFInstructionType::Return; + return_instructions_.append(&instruction); + return instruction; +} + +void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable) +{ + params_.append({interface_type, &variable}); +} + +void MFProcedure::set_entry(MFInstruction &entry) +{ + if (entry_ != nullptr) { + entry_->prev_.remove_first_occurrence_and_reorder(MFInstructionCursor::ForEntry()); + } + entry_ = &entry; + entry_->prev_.append(MFInstructionCursor::ForEntry()); +} + +MFProcedure::~MFProcedure() +{ + for (MFCallInstruction *instruction : call_instructions_) { + instruction->~MFCallInstruction(); + } + for (MFBranchInstruction *instruction : branch_instructions_) { + instruction->~MFBranchInstruction(); + } + for (MFDestructInstruction *instruction : destruct_instructions_) { + instruction->~MFDestructInstruction(); + } + for (MFDummyInstruction *instruction : dummy_instructions_) { + instruction->~MFDummyInstruction(); + } + for (MFReturnInstruction *instruction : return_instructions_) { + instruction->~MFReturnInstruction(); + } + for (MFVariable *variable : variables_) { + variable->~MFVariable(); + } +} + +bool MFProcedure::validate() const +{ + if (entry_ == nullptr) { + return false; + } + if (!this->validate_all_instruction_pointers_set()) { + return false; + } + if (!this->validate_all_params_provided()) { + return false; + } + if (!this->validate_same_variables_in_one_call()) { + return false; + } + if (!this->validate_parameters()) { + return false; + } + if (!this->validate_initialization()) { + return false; + } + return true; +} + +bool MFProcedure::validate_all_instruction_pointers_set() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + for (const MFDestructInstruction *instruction : destruct_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + if (instruction->branch_true_ == nullptr) { + return false; + } + if (instruction->branch_false_ == nullptr) { + return false; + } + } + for (const MFDummyInstruction *instruction : dummy_instructions_) { + if (instruction->next_ == nullptr) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_all_params_provided() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = instruction->fn(); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.category() == MFParamType::SingleOutput) { + /* Single outputs are optional. */ + continue; + } + const MFVariable *variable = instruction->params_[param_index]; + if (variable == nullptr) { + return false; + } + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + if (instruction->condition_ == nullptr) { + return false; + } + } + for (const MFDestructInstruction *instruction : destruct_instructions_) { + if (instruction->variable_ == nullptr) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_same_variables_in_one_call() const +{ + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = *instruction->fn_; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = instruction->params_[param_index]; + if (variable == nullptr) { + continue; + } + for (const int other_param_index : fn.param_indices()) { + if (other_param_index == param_index) { + continue; + } + const MFVariable *other_variable = instruction->params_[other_param_index]; + if (other_variable != variable) { + continue; + } + if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) { + /* When a variable is used as mutable or output parameter, it can only be used once. */ + return false; + } + const MFParamType other_param_type = fn.param_type(other_param_index); + /* A variable is allowed to be used as input more than once. */ + if (other_param_type.interface_type() != MFParamType::Input) { + return false; + } + } + } + } + return true; +} + +bool MFProcedure::validate_parameters() const +{ + Set<const MFVariable *> variables; + for (const MFParameter ¶m : params_) { + /* One variable cannot be used as multiple parameters. */ + if (!variables.add(param.variable)) { + return false; + } + } + return true; +} + +bool MFProcedure::validate_initialization() const +{ + /* TODO: Issue warning when it maybe wrongly initialized. */ + for (const MFDestructInstruction *instruction : destruct_instructions_) { + const MFVariable &variable = *instruction->variable_; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + if (!state.can_be_initialized) { + return false; + } + } + for (const MFBranchInstruction *instruction : branch_instructions_) { + const MFVariable &variable = *instruction->condition_; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + if (!state.can_be_initialized) { + return false; + } + } + for (const MFCallInstruction *instruction : call_instructions_) { + const MultiFunction &fn = *instruction->fn_; + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable &variable = *instruction->params_[param_index]; + const InitState state = this->find_initialization_state_before_instruction(*instruction, + variable); + switch (param_type.interface_type()) { + case MFParamType::Input: + case MFParamType::Mutable: { + if (!state.can_be_initialized) { + return false; + } + break; + } + case MFParamType::Output: { + if (!state.can_be_uninitialized) { + return false; + } + break; + } + } + } + } + Set<const MFVariable *> variables_that_should_be_initialized_on_return; + for (const MFParameter ¶m : params_) { + if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) { + variables_that_should_be_initialized_on_return.add_new(param.variable); + } + } + for (const MFReturnInstruction *instruction : return_instructions_) { + for (const MFVariable *variable : variables_) { + const InitState init_state = this->find_initialization_state_before_instruction(*instruction, + *variable); + if (variables_that_should_be_initialized_on_return.contains(variable)) { + if (!init_state.can_be_initialized) { + return false; + } + } + else { + if (!init_state.can_be_uninitialized) { + return false; + } + } + } + } + return true; +} + +MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction( + const MFInstruction &target_instruction, const MFVariable &target_variable) const +{ + InitState state; + + auto check_entry_instruction = [&]() { + bool caller_initialized_variable = false; + for (const MFParameter ¶m : params_) { + if (param.variable == &target_variable) { + if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) { + caller_initialized_variable = true; + break; + } + } + } + if (caller_initialized_variable) { + state.can_be_initialized = true; + } + else { + state.can_be_uninitialized = true; + } + }; + + if (&target_instruction == entry_) { + check_entry_instruction(); + } + + Set<const MFInstruction *> checked_instructions; + Stack<const MFInstruction *> instructions_to_check; + for (const MFInstructionCursor &cursor : target_instruction.prev_) { + if (cursor.instruction() != nullptr) { + instructions_to_check.push(cursor.instruction()); + } + } + + while (!instructions_to_check.is_empty()) { + const MFInstruction &instruction = *instructions_to_check.pop(); + if (!checked_instructions.add(&instruction)) { + /* Skip if the instruction has been checked already. */ + continue; + } + bool state_modified = false; + switch (instruction.type_) { + case MFInstructionType::Call: { + const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>( + instruction); + const MultiFunction &fn = *call_instruction.fn_; + for (const int param_index : fn.param_indices()) { + if (call_instruction.params_[param_index] == &target_variable) { + const MFParamType param_type = fn.param_type(param_index); + if (param_type.interface_type() == MFParamType::Output) { + state.can_be_initialized = true; + state_modified = true; + break; + } + } + } + break; + } + case MFInstructionType::Destruct: { + const MFDestructInstruction &destruct_instruction = + static_cast<const MFDestructInstruction &>(instruction); + if (destruct_instruction.variable_ == &target_variable) { + state.can_be_uninitialized = true; + state_modified = true; + } + break; + } + case MFInstructionType::Branch: + case MFInstructionType::Dummy: + case MFInstructionType::Return: { + /* These instruction types don't change the initialization state of variables. */ + break; + } + } + + if (!state_modified) { + if (&instruction == entry_) { + check_entry_instruction(); + } + for (const MFInstructionCursor &cursor : instruction.prev_) { + if (cursor.instruction() != nullptr) { + instructions_to_check.push(cursor.instruction()); + } + } + } + } + + return state; +} + +class MFProcedureDotExport { + private: + const MFProcedure &procedure_; + dot::DirectedGraph digraph_; + Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_; + Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_; + + public: + MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure) + { + } + + std::string generate() + { + this->create_nodes(); + this->create_edges(); + return digraph_.to_dot_string(); + } + + void create_nodes() + { + Vector<const MFInstruction *> all_instructions; + auto add_instructions = [&](auto instructions) { + all_instructions.extend(instructions.begin(), instructions.end()); + }; + add_instructions(procedure_.call_instructions_); + add_instructions(procedure_.branch_instructions_); + add_instructions(procedure_.destruct_instructions_); + add_instructions(procedure_.dummy_instructions_); + add_instructions(procedure_.return_instructions_); + + Set<const MFInstruction *> handled_instructions; + + for (const MFInstruction *representative : all_instructions) { + if (handled_instructions.contains(representative)) { + continue; + } + Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block( + *representative); + std::stringstream ss; + ss << "<"; + + for (const MFInstruction *current : block_instructions) { + handled_instructions.add_new(current); + switch (current->type()) { + case MFInstructionType::Call: { + this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss); + break; + } + case MFInstructionType::Destruct: { + this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss); + break; + } + case MFInstructionType::Dummy: { + this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss); + break; + } + case MFInstructionType::Return: { + this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss); + break; + } + case MFInstructionType::Branch: { + this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss); + break; + } + } + ss << R"(<br align="left" />)"; + } + ss << ">"; + + dot::Node &dot_node = digraph_.new_node(ss.str()); + dot_node.set_shape(dot::Attr_shape::Rectangle); + dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node); + dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node); + } + } + + void create_edges() + { + auto create_edge = [&](dot::Node &from_node, + const MFInstruction *to_instruction) -> dot::DirectedEdge & { + if (to_instruction == nullptr) { + dot::Node &to_node = digraph_.new_node("missing"); + to_node.set_shape(dot::Attr_shape::Diamond); + return digraph_.new_edge(from_node, to_node); + } + dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction); + return digraph_.new_edge(from_node, to_node); + }; + + for (auto item : dot_nodes_by_end_.items()) { + const MFInstruction &from_instruction = *item.key; + dot::Node &from_node = *item.value; + switch (from_instruction.type()) { + case MFInstructionType::Call: { + const MFInstruction *to_instruction = + static_cast<const MFCallInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Destruct: { + const MFInstruction *to_instruction = + static_cast<const MFDestructInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Dummy: { + const MFInstruction *to_instruction = + static_cast<const MFDummyInstruction &>(from_instruction).next(); + create_edge(from_node, to_instruction); + break; + } + case MFInstructionType::Return: { + break; + } + case MFInstructionType::Branch: { + const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>( + from_instruction); + const MFInstruction *to_true_instruction = branch_instruction.branch_true(); + const MFInstruction *to_false_instruction = branch_instruction.branch_false(); + create_edge(from_node, to_true_instruction).attributes.set("color", "#118811"); + create_edge(from_node, to_false_instruction).attributes.set("color", "#881111"); + break; + } + } + } + + dot::Node &entry_node = this->create_entry_node(); + create_edge(entry_node, procedure_.entry()); + } + + bool has_to_be_block_begin(const MFInstruction &instruction) + { + if (instruction.prev().size() != 1) { + return true; + } + if (ELEM(instruction.prev()[0].type(), + MFInstructionCursor::Type::Branch, + MFInstructionCursor::Type::Entry)) { + return true; + } + return false; + } + + const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative) + { + const MFInstruction *current = &representative; + while (!this->has_to_be_block_begin(*current)) { + current = current->prev()[0].instruction(); + if (current == &representative) { + /* There is a loop without entry or exit, just break it up here. */ + break; + } + } + return *current; + } + + const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction, + const MFInstruction &block_begin) + { + const MFInstruction *next = nullptr; + switch (instruction.type()) { + case MFInstructionType::Call: { + next = static_cast<const MFCallInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Destruct: { + next = static_cast<const MFDestructInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Dummy: { + next = static_cast<const MFDummyInstruction &>(instruction).next(); + break; + } + case MFInstructionType::Return: + case MFInstructionType::Branch: { + break; + } + } + if (next == nullptr) { + return nullptr; + } + if (next == &block_begin) { + return nullptr; + } + if (this->has_to_be_block_begin(*next)) { + return nullptr; + } + return next; + } + + Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative) + { + Vector<const MFInstruction *> instructions; + const MFInstruction &begin = this->get_first_instruction_in_block(representative); + for (const MFInstruction *current = &begin; current != nullptr; + current = this->get_next_instruction_in_block(*current, begin)) { + instructions.append(current); + } + return instructions; + } + + void variable_to_string(const MFVariable *variable, std::stringstream &ss) + { + if (variable == nullptr) { + ss << "null"; + } + else { + ss << "$" << variable->id(); + if (!variable->name().is_empty()) { + ss << "(" << variable->name() << ")"; + } + } + } + + void instruction_name_format(StringRef name, std::stringstream &ss) + { + ss << name; + } + + void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss) + { + const MultiFunction &fn = instruction.fn(); + this->instruction_name_format(fn.name() + ": ", ss); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = instruction.params()[param_index]; + ss << R"(<font color="grey30">)"; + switch (param_type.interface_type()) { + case MFParamType::Input: { + ss << "in"; + break; + } + case MFParamType::Mutable: { + ss << "mut"; + break; + } + case MFParamType::Output: { + ss << "out"; + break; + } + } + ss << " </font> "; + variable_to_string(variable, ss); + if (param_index < fn.param_amount() - 1) { + ss << ", "; + } + } + } + + void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss) + { + instruction_name_format("Destruct ", ss); + variable_to_string(instruction.variable(), ss); + } + + void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss) + { + instruction_name_format("Dummy ", ss); + } + + void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss) + { + instruction_name_format("Return ", ss); + + Vector<ConstMFParameter> outgoing_parameters; + for (const ConstMFParameter ¶m : procedure_.params()) { + if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) { + outgoing_parameters.append(param); + } + } + for (const int param_index : outgoing_parameters.index_range()) { + const ConstMFParameter ¶m = outgoing_parameters[param_index]; + variable_to_string(param.variable, ss); + if (param_index < outgoing_parameters.size() - 1) { + ss << ", "; + } + } + } + + void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss) + { + instruction_name_format("Branch ", ss); + variable_to_string(instruction.condition(), ss); + } + + dot::Node &create_entry_node() + { + std::stringstream ss; + ss << "Entry: "; + Vector<ConstMFParameter> incoming_parameters; + for (const ConstMFParameter ¶m : procedure_.params()) { + if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) { + incoming_parameters.append(param); + } + } + for (const int param_index : incoming_parameters.index_range()) { + const ConstMFParameter ¶m = incoming_parameters[param_index]; + variable_to_string(param.variable, ss); + if (param_index < incoming_parameters.size() - 1) { + ss << ", "; + } + } + + dot::Node &node = digraph_.new_node(ss.str()); + node.set_shape(dot::Attr_shape::Ellipse); + return node; + } +}; + +std::string MFProcedure::to_dot() const +{ + MFProcedureDotExport dot_export{*this}; + return dot_export.generate(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure_builder.cc b/source/blender/functions/intern/multi_function_procedure_builder.cc new file mode 100644 index 00000000000..d30e6c0e14a --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure_builder.cc @@ -0,0 +1,131 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "FN_multi_function_procedure_builder.hh" + +namespace blender::fn { + +void MFProcedureBuilder::add_destruct(MFVariable &variable) +{ + MFDestructInstruction &instruction = procedure_->new_destruct_instruction(); + instruction.set_variable(&variable); + this->link_to_cursors(&instruction); + cursors_ = {MFInstructionCursor{instruction}}; +} + +void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables) +{ + for (MFVariable *variable : variables) { + this->add_destruct(*variable); + } +} + +MFReturnInstruction &MFProcedureBuilder::add_return() +{ + MFReturnInstruction &instruction = procedure_->new_return_instruction(); + this->link_to_cursors(&instruction); + cursors_ = {}; + return instruction; +} + +MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn) +{ + MFCallInstruction &instruction = procedure_->new_call_instruction(fn); + this->link_to_cursors(&instruction); + cursors_ = {MFInstructionCursor{instruction}}; + return instruction; +} + +MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables( + const MultiFunction &fn, Span<MFVariable *> param_variables) +{ + MFCallInstruction &instruction = this->add_call_with_no_variables(fn); + instruction.set_params(param_variables); + return instruction; +} + +Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn, + Span<MFVariable *> input_and_mutable_variables) +{ + Vector<MFVariable *> output_variables; + MFCallInstruction &instruction = this->add_call_with_no_variables(fn); + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + switch (param_type.interface_type()) { + case MFParamType::Input: + case MFParamType::Mutable: { + MFVariable *variable = input_and_mutable_variables.first(); + instruction.set_param_variable(param_index, variable); + input_and_mutable_variables = input_and_mutable_variables.drop_front(1); + break; + } + case MFParamType::Output: { + MFVariable &variable = procedure_->new_variable(param_type.data_type(), + fn.param_name(param_index)); + instruction.set_param_variable(param_index, &variable); + output_variables.append(&variable); + break; + } + } + } + /* All passed in variables should have been dropped in the loop above. */ + BLI_assert(input_and_mutable_variables.is_empty()); + return output_variables; +} + +MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition) +{ + MFBranchInstruction &instruction = procedure_->new_branch_instruction(); + instruction.set_condition(&condition); + this->link_to_cursors(&instruction); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); + + Branch branch{*procedure_, *procedure_}; + branch.branch_true.set_cursor(MFInstructionCursor{instruction, true}); + branch.branch_false.set_cursor(MFInstructionCursor{instruction, false}); + return branch; +} + +MFProcedureBuilder::Loop MFProcedureBuilder::add_loop() +{ + MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction(); + MFDummyInstruction &loop_end = procedure_->new_dummy_instruction(); + this->link_to_cursors(&loop_begin); + cursors_ = {MFInstructionCursor{loop_begin}}; + + Loop loop; + loop.begin = &loop_begin; + loop.end = &loop_end; + + return loop; +} + +void MFProcedureBuilder::add_loop_continue(Loop &loop) +{ + this->link_to_cursors(loop.begin); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); +} + +void MFProcedureBuilder::add_loop_break(Loop &loop) +{ + this->link_to_cursors(loop.end); + /* Clear cursors because this builder ends here. */ + cursors_.clear(); +} + +} // namespace blender::fn diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc new file mode 100644 index 00000000000..b97282accdd --- /dev/null +++ b/source/blender/functions/intern/multi_function_procedure_executor.cc @@ -0,0 +1,1227 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "FN_multi_function_procedure_executor.hh" + +#include "BLI_stack.hh" + +namespace blender::fn { + +MFProcedureExecutor::MFProcedureExecutor(std::string name, const MFProcedure &procedure) + : procedure_(procedure) +{ + MFSignatureBuilder signature(std::move(name)); + + for (const ConstMFParameter ¶m : procedure.params()) { + signature.add(param.variable->name(), MFParamType(param.type, param.variable->data_type())); + } + + signature_ = signature.build(); + this->set_signature(&signature_); +} + +using IndicesSplitVectors = std::array<Vector<int64_t>, 2>; + +namespace { +enum class ValueType { + GVArray = 0, + Span = 1, + GVVectorArray = 2, + GVectorArray = 3, + OneSingle = 4, + OneVector = 5, +}; +constexpr int tot_variable_value_types = 6; +} // namespace + +/** + * During evaluation, a variable may be stored in various different forms, depending on what + * instructions do with the variables. + */ +struct VariableValue { + ValueType type; + + VariableValue(ValueType type) : type(type) + { + } +}; + +/* This variable is the unmodified virtual array from the caller. */ +struct VariableValue_GVArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVArray; + const GVArray &data; + + VariableValue_GVArray(const GVArray &data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has a different value for every index. Some values may be uninitialized. The span + * may be owned by the caller. */ +struct VariableValue_Span : public VariableValue { + static inline constexpr ValueType static_type = ValueType::Span; + void *data; + bool owned; + + VariableValue_Span(void *data, bool owned) : VariableValue(static_type), data(data), owned(owned) + { + } +}; + +/* This variable is the unmodified virtual vector array from the caller. */ +struct VariableValue_GVVectorArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVVectorArray; + const GVVectorArray &data; + + VariableValue_GVVectorArray(const GVVectorArray &data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has a different vector for every index. */ +struct VariableValue_GVectorArray : public VariableValue { + static inline constexpr ValueType static_type = ValueType::GVectorArray; + GVectorArray &data; + bool owned; + + VariableValue_GVectorArray(GVectorArray &data, bool owned) + : VariableValue(static_type), data(data), owned(owned) + { + } +}; + +/* This variable has the same value for every index. */ +struct VariableValue_OneSingle : public VariableValue { + static inline constexpr ValueType static_type = ValueType::OneSingle; + void *data; + bool is_initialized = false; + + VariableValue_OneSingle(void *data) : VariableValue(static_type), data(data) + { + } +}; + +/* This variable has the same vector for every index. */ +struct VariableValue_OneVector : public VariableValue { + static inline constexpr ValueType static_type = ValueType::OneVector; + GVectorArray &data; + + VariableValue_OneVector(GVectorArray &data) : VariableValue(static_type), data(data) + { + } +}; + +static_assert(std::is_trivially_destructible_v<VariableValue_GVArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_Span>); +static_assert(std::is_trivially_destructible_v<VariableValue_GVVectorArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_GVectorArray>); +static_assert(std::is_trivially_destructible_v<VariableValue_OneSingle>); +static_assert(std::is_trivially_destructible_v<VariableValue_OneVector>); + +class VariableState; + +/** + * The #ValueAllocator is responsible for providing memory for variables and their values. It also + * manages the reuse of buffers to improve performance. + */ +class ValueAllocator : NonCopyable, NonMovable { + private: + /* Allocate with 64 byte alignment for better reusability of buffers and improved cache + * performance. */ + static constexpr inline int min_alignment = 64; + + /* Use stacks so that the most recently used buffers are reused first. This improves cache + * efficiency. */ + std::array<Stack<VariableValue *>, tot_variable_value_types> values_free_lists_; + /* The integer key is the size of one element (e.g. 4 for an integer buffer). All buffers are + * aligned to #min_alignment bytes. */ + Map<int, Stack<void *>> span_buffers_free_list_; + + public: + ValueAllocator() = default; + + ~ValueAllocator() + { + for (Stack<VariableValue *> &stack : values_free_lists_) { + while (!stack.is_empty()) { + MEM_freeN(stack.pop()); + } + } + for (Stack<void *> &stack : span_buffers_free_list_.values()) { + while (!stack.is_empty()) { + MEM_freeN(stack.pop()); + } + } + } + + template<typename... Args> VariableState *obtain_variable_state(Args &&...args); + + void release_variable_state(VariableState *state); + + VariableValue_GVArray *obtain_GVArray(const GVArray &varray) + { + return this->obtain<VariableValue_GVArray>(varray); + } + + VariableValue_GVVectorArray *obtain_GVVectorArray(const GVVectorArray &varray) + { + return this->obtain<VariableValue_GVVectorArray>(varray); + } + + VariableValue_Span *obtain_Span_not_owned(void *buffer) + { + return this->obtain<VariableValue_Span>(buffer, false); + } + + VariableValue_Span *obtain_Span(const CPPType &type, int size) + { + void *buffer = nullptr; + + const int element_size = type.size(); + const int alignment = type.alignment(); + + if (alignment > min_alignment) { + /* In this rare case we fallback to not reusing existing buffers. */ + buffer = MEM_mallocN_aligned(element_size * size, alignment, __func__); + } + else { + Stack<void *> *stack = span_buffers_free_list_.lookup_ptr(element_size); + if (stack == nullptr || stack->is_empty()) { + buffer = MEM_mallocN_aligned(element_size * size, min_alignment, __func__); + } + else { + /* Reuse existing buffer. */ + buffer = stack->pop(); + } + } + + return this->obtain<VariableValue_Span>(buffer, true); + } + + VariableValue_GVectorArray *obtain_GVectorArray_not_owned(GVectorArray &data) + { + return this->obtain<VariableValue_GVectorArray>(data, false); + } + + VariableValue_GVectorArray *obtain_GVectorArray(const CPPType &type, int size) + { + GVectorArray *vector_array = new GVectorArray(type, size); + return this->obtain<VariableValue_GVectorArray>(*vector_array, true); + } + + VariableValue_OneSingle *obtain_OneSingle(const CPPType &type) + { + void *buffer = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + return this->obtain<VariableValue_OneSingle>(buffer); + } + + VariableValue_OneVector *obtain_OneVector(const CPPType &type) + { + GVectorArray *vector_array = new GVectorArray(type, 1); + return this->obtain<VariableValue_OneVector>(*vector_array); + } + + void release_value(VariableValue *value, const MFDataType &data_type) + { + switch (value->type) { + case ValueType::GVArray: { + break; + } + case ValueType::Span: { + auto *value_typed = static_cast<VariableValue_Span *>(value); + if (value_typed->owned) { + const CPPType &type = data_type.single_type(); + /* Assumes all values in the buffer are uninitialized already. */ + Stack<void *> &buffers = span_buffers_free_list_.lookup_or_add_default(type.size()); + buffers.push(value_typed->data); + } + break; + } + case ValueType::GVVectorArray: { + break; + } + case ValueType::GVectorArray: { + auto *value_typed = static_cast<VariableValue_GVectorArray *>(value); + if (value_typed->owned) { + delete &value_typed->data; + } + break; + } + case ValueType::OneSingle: { + auto *value_typed = static_cast<VariableValue_OneSingle *>(value); + if (value_typed->is_initialized) { + const CPPType &type = data_type.single_type(); + type.destruct(value_typed->data); + } + MEM_freeN(value_typed->data); + break; + } + case ValueType::OneVector: { + auto *value_typed = static_cast<VariableValue_OneVector *>(value); + delete &value_typed->data; + break; + } + } + + Stack<VariableValue *> &stack = values_free_lists_[(int)value->type]; + stack.push(value); + } + + private: + template<typename T, typename... Args> T *obtain(Args &&...args) + { + static_assert(std::is_base_of_v<VariableValue, T>); + Stack<VariableValue *> &stack = values_free_lists_[(int)T::static_type]; + if (stack.is_empty()) { + void *buffer = MEM_mallocN(sizeof(T), __func__); + return new (buffer) T(std::forward<Args>(args)...); + } + return new (stack.pop()) T(std::forward<Args>(args)...); + } +}; + +/** + * This class keeps track of a single variable during evaluation. + */ +class VariableState : NonCopyable, NonMovable { + private: + /** The current value of the variable. The storage format may change over time. */ + VariableValue *value_; + /** Number of indices that are currently initialized in this variable. */ + int tot_initialized_; + /* This a non-owning pointer to either span buffer or #GVectorArray or null. */ + void *caller_provided_storage_ = nullptr; + + public: + VariableState(VariableValue &value, int tot_initialized, void *caller_provided_storage = nullptr) + : value_(&value), + tot_initialized_(tot_initialized), + caller_provided_storage_(caller_provided_storage) + { + } + + void destruct_self(ValueAllocator &value_allocator, const MFDataType &data_type) + { + value_allocator.release_value(value_, data_type); + value_allocator.release_variable_state(this); + } + + /* True if this contains only one value for all indices, i.e. the value for all indices is + * the same. */ + bool is_one() const + { + switch (value_->type) { + case ValueType::GVArray: + return this->value_as<VariableValue_GVArray>()->data.is_single(); + case ValueType::Span: + return tot_initialized_ == 0; + case ValueType::GVVectorArray: + return this->value_as<VariableValue_GVVectorArray>()->data.is_single_vector(); + case ValueType::GVectorArray: + return tot_initialized_ == 0; + case ValueType::OneSingle: + return true; + case ValueType::OneVector: + return true; + } + BLI_assert_unreachable(); + return false; + } + + bool is_fully_initialized(const IndexMask full_mask) + { + return tot_initialized_ == full_mask.size(); + } + + bool is_fully_uninitialized(const IndexMask full_mask) + { + UNUSED_VARS(full_mask); + return tot_initialized_ == 0; + } + + void add_as_input(MFParamsBuilder ¶ms, IndexMask mask, const MFDataType &data_type) const + { + /* Sanity check to make sure that enough values are initialized. */ + BLI_assert(mask.size() <= tot_initialized_); + + switch (value_->type) { + case ValueType::GVArray: { + params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data); + break; + } + case ValueType::Span: { + const void *data = this->value_as<VariableValue_Span>()->data; + const GSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_readonly_single_input(span); + break; + } + case ValueType::GVVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data); + break; + } + case ValueType::GVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::OneSingle: { + const auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + const GPointer gpointer{data_type.single_type(), value_typed->data}; + params.add_readonly_single_input(gpointer); + break; + } + case ValueType::OneVector: { + params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data[0]); + break; + } + } + } + + void ensure_is_mutable(IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + if (ELEM(value_->type, ValueType::Span, ValueType::GVectorArray)) { + return; + } + + const int array_size = full_mask.min_array_size(); + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + VariableValue_Span *new_value = nullptr; + if (caller_provided_storage_ == nullptr) { + new_value = value_allocator.obtain_Span(type, array_size); + } + else { + /* Reuse the storage provided caller when possible. */ + new_value = value_allocator.obtain_Span_not_owned(caller_provided_storage_); + } + if (value_->type == ValueType::GVArray) { + /* Fill new buffer with data from virtual array. */ + this->value_as<VariableValue_GVArray>()->data.materialize_to_uninitialized( + full_mask, new_value->data); + } + else if (value_->type == ValueType::OneSingle) { + auto *old_value_typed_ = this->value_as<VariableValue_OneSingle>(); + if (old_value_typed_->is_initialized) { + /* Fill the buffer with a single value. */ + type.fill_construct_indices(old_value_typed_->data, new_value->data, full_mask); + } + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + VariableValue_GVectorArray *new_value = nullptr; + if (caller_provided_storage_ == nullptr) { + new_value = value_allocator.obtain_GVectorArray(type, array_size); + } + else { + new_value = value_allocator.obtain_GVectorArray_not_owned( + *(GVectorArray *)caller_provided_storage_); + } + if (value_->type == ValueType::GVVectorArray) { + /* Fill new vector array with data from virtual vector array. */ + new_value->data.extend(full_mask, this->value_as<VariableValue_GVVectorArray>()->data); + } + else if (value_->type == ValueType::OneVector) { + /* Fill all indices with the same value. */ + const GSpan vector = this->value_as<VariableValue_OneVector>()->data[0]; + new_value->data.extend(full_mask, GVVectorArray_For_SingleGSpan{vector, array_size}); + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + } + } + + void add_as_mutable(MFParamsBuilder ¶ms, + IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + /* Sanity check to make sure that enough values are initialized. */ + BLI_assert(mask.size() <= tot_initialized_); + + this->ensure_is_mutable(full_mask, data_type, value_allocator); + + switch (value_->type) { + case ValueType::Span: { + void *data = this->value_as<VariableValue_Span>()->data; + const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_single_mutable(span); + break; + } + case ValueType::GVectorArray: { + params.add_vector_mutable(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::GVArray: + case ValueType::GVVectorArray: + case ValueType::OneSingle: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + } + + void add_as_output(MFParamsBuilder ¶ms, + IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + /* Sanity check to make sure that enough values are not initialized. */ + BLI_assert(mask.size() <= full_mask.size() - tot_initialized_); + this->ensure_is_mutable(full_mask, data_type, value_allocator); + + switch (value_->type) { + case ValueType::Span: { + void *data = this->value_as<VariableValue_Span>()->data; + const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()}; + params.add_uninitialized_single_output(span); + break; + } + case ValueType::GVectorArray: { + params.add_vector_output(this->value_as<VariableValue_GVectorArray>()->data); + break; + } + case ValueType::GVArray: + case ValueType::GVVectorArray: + case ValueType::OneSingle: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + + tot_initialized_ += mask.size(); + } + + void add_as_input__one(MFParamsBuilder ¶ms, const MFDataType &data_type) const + { + BLI_assert(this->is_one()); + + switch (value_->type) { + case ValueType::GVArray: { + params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data); + break; + } + case ValueType::GVVectorArray: { + params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data); + break; + } + case ValueType::OneSingle: { + const auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + GPointer ptr{data_type.single_type(), value_typed->data}; + params.add_readonly_single_input(ptr); + break; + } + case ValueType::OneVector: { + params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data); + break; + } + case ValueType::Span: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + } + + void ensure_is_mutable__one(const MFDataType &data_type, ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + if (ELEM(value_->type, ValueType::OneSingle, ValueType::OneVector)) { + return; + } + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + VariableValue_OneSingle *new_value = value_allocator.obtain_OneSingle(type); + if (value_->type == ValueType::GVArray) { + this->value_as<VariableValue_GVArray>()->data.get_internal_single_to_uninitialized( + new_value->data); + new_value->is_initialized = true; + } + else if (value_->type == ValueType::Span) { + BLI_assert(tot_initialized_ == 0); + /* Nothing to do, the single value is uninitialized already. */ + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + VariableValue_OneVector *new_value = value_allocator.obtain_OneVector(type); + if (value_->type == ValueType::GVVectorArray) { + const GVVectorArray &old_vector_array = + this->value_as<VariableValue_GVVectorArray>()->data; + new_value->data.extend(IndexRange(1), old_vector_array); + } + else if (value_->type == ValueType::GVectorArray) { + BLI_assert(tot_initialized_ == 0); + /* Nothing to do. */ + } + else { + BLI_assert_unreachable(); + } + value_allocator.release_value(value_, data_type); + value_ = new_value; + break; + } + } + } + + void add_as_mutable__one(MFParamsBuilder ¶ms, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + this->ensure_is_mutable__one(data_type, value_allocator); + + switch (value_->type) { + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + params.add_single_mutable(GMutableSpan{data_type.single_type(), value_typed->data, 1}); + break; + } + case ValueType::OneVector: { + params.add_vector_mutable(this->value_as<VariableValue_OneVector>()->data); + break; + } + case ValueType::GVArray: + case ValueType::Span: + case ValueType::GVVectorArray: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + } + + void add_as_output__one(MFParamsBuilder ¶ms, + IndexMask mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + BLI_assert(this->is_one()); + this->ensure_is_mutable__one(data_type, value_allocator); + + switch (value_->type) { + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(!value_typed->is_initialized); + params.add_uninitialized_single_output( + GMutableSpan{data_type.single_type(), value_typed->data, 1}); + /* It becomes initialized when the multi-function is called. */ + value_typed->is_initialized = true; + break; + } + case ValueType::OneVector: { + auto *value_typed = this->value_as<VariableValue_OneVector>(); + BLI_assert(value_typed->data[0].is_empty()); + params.add_vector_output(value_typed->data); + break; + } + case ValueType::GVArray: + case ValueType::Span: + case ValueType::GVVectorArray: + case ValueType::GVectorArray: { + BLI_assert_unreachable(); + break; + } + } + + tot_initialized_ += mask.size(); + } + + void destruct(IndexMask mask, + IndexMask full_mask, + const MFDataType &data_type, + ValueAllocator &value_allocator) + { + int new_tot_initialized = tot_initialized_ - mask.size(); + + /* Sanity check to make sure that enough indices can be destructed. */ + BLI_assert(new_tot_initialized >= 0); + + switch (value_->type) { + case ValueType::GVArray: { + if (mask.size() == full_mask.size()) { + /* All elements are destructed. The elements are owned by the caller, so we don't + * actually destruct them. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneSingle(data_type.single_type()); + } + else { + /* Not all elements are destructed. Since we can't work on the original array, we have to + * create a copy first. */ + this->ensure_is_mutable(full_mask, data_type, value_allocator); + BLI_assert(value_->type == ValueType::Span); + const CPPType &type = data_type.single_type(); + type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask); + } + break; + } + case ValueType::Span: { + const CPPType &type = data_type.single_type(); + type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask); + if (new_tot_initialized == 0) { + /* Release span when all values are initialized. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneSingle(data_type.single_type()); + } + break; + } + case ValueType::GVVectorArray: { + if (mask.size() == full_mask.size()) { + /* All elements are cleared. The elements are owned by the caller, so don't actually + * destruct them. */ + value_allocator.release_value(value_, data_type); + value_ = value_allocator.obtain_OneVector(data_type.vector_base_type()); + } + else { + /* Not all elements are cleared. Since we can't work on the original vector array, we + * have to create a copy first. A possible future optimization is to create the partial + * copy directly. */ + this->ensure_is_mutable(full_mask, data_type, value_allocator); + BLI_assert(value_->type == ValueType::GVectorArray); + this->value_as<VariableValue_GVectorArray>()->data.clear(mask); + } + break; + } + case ValueType::GVectorArray: { + this->value_as<VariableValue_GVectorArray>()->data.clear(mask); + break; + } + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + if (mask.size() == tot_initialized_) { + const CPPType &type = data_type.single_type(); + type.destruct(value_typed->data); + value_typed->is_initialized = false; + } + break; + } + case ValueType::OneVector: { + auto *value_typed = this->value_as<VariableValue_OneVector>(); + if (mask.size() == tot_initialized_) { + value_typed->data.clear({0}); + } + break; + } + } + + tot_initialized_ = new_tot_initialized; + } + + void indices_split(IndexMask mask, IndicesSplitVectors &r_indices) + { + BLI_assert(mask.size() <= tot_initialized_); + + switch (value_->type) { + case ValueType::GVArray: { + const GVArray_Typed<bool> varray{this->value_as<VariableValue_GVArray>()->data}; + for (const int i : mask) { + r_indices[varray[i]].append(i); + } + break; + } + case ValueType::Span: { + const Span<bool> span((bool *)this->value_as<VariableValue_Span>()->data, + mask.min_array_size()); + for (const int i : mask) { + r_indices[span[i]].append(i); + } + break; + } + case ValueType::OneSingle: { + auto *value_typed = this->value_as<VariableValue_OneSingle>(); + BLI_assert(value_typed->is_initialized); + const bool condition = *(bool *)value_typed->data; + r_indices[condition].extend(mask); + break; + } + case ValueType::GVVectorArray: + case ValueType::GVectorArray: + case ValueType::OneVector: { + BLI_assert_unreachable(); + break; + } + } + } + + template<typename T> T *value_as() + { + BLI_assert(value_->type == T::static_type); + return static_cast<T *>(value_); + } + + template<typename T> const T *value_as() const + { + BLI_assert(value_->type == T::static_type); + return static_cast<T *>(value_); + } +}; + +template<typename... Args> VariableState *ValueAllocator::obtain_variable_state(Args &&...args) +{ + return new VariableState(std::forward<Args>(args)...); +} + +void ValueAllocator::release_variable_state(VariableState *state) +{ + delete state; +} + +/** Keeps track of the states of all variables during evaluation. */ +class VariableStates { + private: + ValueAllocator value_allocator_; + Map<const MFVariable *, VariableState *> variable_states_; + IndexMask full_mask_; + + public: + VariableStates(IndexMask full_mask) : full_mask_(full_mask) + { + } + + ~VariableStates() + { + for (auto &&item : variable_states_.items()) { + const MFVariable *variable = item.key; + VariableState *state = item.value; + state->destruct_self(value_allocator_, variable->data_type()); + } + } + + ValueAllocator &value_allocator() + { + return value_allocator_; + } + + const IndexMask &full_mask() const + { + return full_mask_; + } + + void add_initial_variable_states(const MFProcedureExecutor &fn, + const MFProcedure &procedure, + MFParams ¶ms) + { + for (const int param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + const MFVariable *variable = procedure.params()[param_index].variable; + + auto add_state = [&](VariableValue *value, + bool input_is_initialized, + void *caller_provided_storage = nullptr) { + const int tot_initialized = input_is_initialized ? full_mask_.size() : 0; + variable_states_.add_new(variable, + value_allocator_.obtain_variable_state( + *value, tot_initialized, caller_provided_storage)); + }; + + switch (param_type.category()) { + case MFParamType::SingleInput: { + const GVArray &data = params.readonly_single_input(param_index); + add_state(value_allocator_.obtain_GVArray(data), true); + break; + } + case MFParamType::VectorInput: { + const GVVectorArray &data = params.readonly_vector_input(param_index); + add_state(value_allocator_.obtain_GVVectorArray(data), true); + break; + } + case MFParamType::SingleOutput: { + GMutableSpan data = params.uninitialized_single_output(param_index); + add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data()); + break; + } + case MFParamType::VectorOutput: { + GVectorArray &data = params.vector_output(param_index); + add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data); + break; + } + case MFParamType::SingleMutable: { + GMutableSpan data = params.single_mutable(param_index); + add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data()); + break; + } + case MFParamType::VectorMutable: { + GVectorArray &data = params.vector_mutable(param_index); + add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data); + break; + } + } + } + } + + void add_as_param(VariableState &variable_state, + MFParamsBuilder ¶ms, + const MFParamType ¶m_type, + const IndexMask &mask) + { + const MFDataType data_type = param_type.data_type(); + switch (param_type.interface_type()) { + case MFParamType::Input: { + variable_state.add_as_input(params, mask, data_type); + break; + } + case MFParamType::Mutable: { + variable_state.add_as_mutable(params, mask, full_mask_, data_type, value_allocator_); + break; + } + case MFParamType::Output: { + variable_state.add_as_output(params, mask, full_mask_, data_type, value_allocator_); + break; + } + } + } + + void add_as_param__one(VariableState &variable_state, + MFParamsBuilder ¶ms, + const MFParamType ¶m_type, + const IndexMask &mask) + { + const MFDataType data_type = param_type.data_type(); + switch (param_type.interface_type()) { + case MFParamType::Input: { + variable_state.add_as_input__one(params, data_type); + break; + } + case MFParamType::Mutable: { + variable_state.add_as_mutable__one(params, data_type, value_allocator_); + break; + } + case MFParamType::Output: { + variable_state.add_as_output__one(params, mask, data_type, value_allocator_); + break; + } + } + } + + void destruct(const MFVariable &variable, const IndexMask &mask) + { + VariableState &variable_state = this->get_variable_state(variable); + variable_state.destruct(mask, full_mask_, variable.data_type(), value_allocator_); + } + + VariableState &get_variable_state(const MFVariable &variable) + { + return *variable_states_.lookup_or_add_cb( + &variable, [&]() { return this->create_new_state_for_variable(variable); }); + } + + VariableState *create_new_state_for_variable(const MFVariable &variable) + { + MFDataType data_type = variable.data_type(); + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single_type(); + return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneSingle(type), 0); + } + case MFDataType::Vector: { + const CPPType &type = data_type.vector_base_type(); + return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneVector(type), 0); + } + } + BLI_assert_unreachable(); + return nullptr; + } +}; + +static bool evaluate_as_one(const MultiFunction &fn, + Span<VariableState *> param_variable_states, + const IndexMask &mask, + const IndexMask &full_mask) +{ + if (fn.depends_on_context()) { + return false; + } + if (mask.size() < full_mask.size()) { + return false; + } + for (VariableState *state : param_variable_states) { + if (state != nullptr && !state->is_one()) { + return false; + } + } + return true; +} + +static void execute_call_instruction(const MFCallInstruction &instruction, + IndexMask mask, + VariableStates &variable_states, + const MFContext &context) +{ + const MultiFunction &fn = instruction.fn(); + + Vector<VariableState *> param_variable_states; + param_variable_states.resize(fn.param_amount()); + + for (const int param_index : fn.param_indices()) { + const MFVariable *variable = instruction.params()[param_index]; + if (variable == nullptr) { + param_variable_states[param_index] = nullptr; + } + else { + VariableState &variable_state = variable_states.get_variable_state(*variable); + param_variable_states[param_index] = &variable_state; + } + } + + /* If all inputs to the function are constant, it's enough to call the function only once instead + * of for every index. */ + if (evaluate_as_one(fn, param_variable_states, mask, variable_states.full_mask())) { + MFParamsBuilder params(fn, 1); + + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + VariableState *variable_state = param_variable_states[param_index]; + if (variable_state == nullptr) { + params.add_ignored_single_output(); + } + else { + variable_states.add_as_param__one(*variable_state, params, param_type, mask); + } + } + + fn.call(IndexRange(1), params, context); + } + else { + MFParamsBuilder params(fn, &mask); + + for (const int param_index : fn.param_indices()) { + const MFParamType param_type = fn.param_type(param_index); + VariableState *variable_state = param_variable_states[param_index]; + if (variable_state == nullptr) { + params.add_ignored_single_output(); + } + else { + variable_states.add_as_param(*variable_state, params, param_type, mask); + } + } + + fn.call(mask, params, context); + } +} + +/** An index mask, that might own the indices if necessary. */ +struct InstructionIndices { + bool is_owned; + Vector<int64_t> owned_indices; + IndexMask referenced_indices; + + IndexMask mask() const + { + if (this->is_owned) { + return this->owned_indices.as_span(); + } + return this->referenced_indices; + } +}; + +/** Contains information about the next instruction that should be executed. */ +struct NextInstructionInfo { + const MFInstruction *instruction = nullptr; + InstructionIndices indices; + + IndexMask mask() const + { + return this->indices.mask(); + } + + operator bool() const + { + return this->instruction != nullptr; + } +}; + +/** + * Keeps track of the next instruction for all indices and decides in which order instructions are + * evaluated. + */ +class InstructionScheduler { + private: + Map<const MFInstruction *, Vector<InstructionIndices>> indices_by_instruction_; + + public: + InstructionScheduler() = default; + + void add_referenced_indices(const MFInstruction &instruction, IndexMask mask) + { + if (mask.is_empty()) { + return; + } + InstructionIndices new_indices; + new_indices.is_owned = false; + new_indices.referenced_indices = mask; + indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices)); + } + + void add_owned_indices(const MFInstruction &instruction, Vector<int64_t> indices) + { + if (indices.is_empty()) { + return; + } + BLI_assert(IndexMask::indices_are_valid_index_mask(indices)); + + InstructionIndices new_indices; + new_indices.is_owned = true; + new_indices.owned_indices = std::move(indices); + indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices)); + } + + void add_previous_instruction_indices(const MFInstruction &instruction, + NextInstructionInfo &instr_info) + { + indices_by_instruction_.lookup_or_add_default(&instruction) + .append(std::move(instr_info.indices)); + } + + NextInstructionInfo pop_next() + { + if (indices_by_instruction_.is_empty()) { + return {}; + } + /* TODO: Implement better mechanism to determine next instruction. */ + const MFInstruction *instruction = *indices_by_instruction_.keys().begin(); + + NextInstructionInfo next_instruction_info; + next_instruction_info.instruction = instruction; + next_instruction_info.indices = this->pop_indices_array(instruction); + return next_instruction_info; + } + + private: + InstructionIndices pop_indices_array(const MFInstruction *instruction) + { + Vector<InstructionIndices> *indices = indices_by_instruction_.lookup_ptr(instruction); + if (indices == nullptr) { + return {}; + } + InstructionIndices r_indices = (*indices).pop_last(); + BLI_assert(!r_indices.mask().is_empty()); + if (indices->is_empty()) { + indices_by_instruction_.remove_contained(instruction); + } + return r_indices; + } +}; + +void MFProcedureExecutor::call(IndexMask full_mask, MFParams params, MFContext context) const +{ + BLI_assert(procedure_.validate()); + + LinearAllocator<> allocator; + + VariableStates variable_states{full_mask}; + variable_states.add_initial_variable_states(*this, procedure_, params); + + InstructionScheduler scheduler; + scheduler.add_referenced_indices(*procedure_.entry(), full_mask); + + /* Loop until all indices got to a return instruction. */ + while (NextInstructionInfo instr_info = scheduler.pop_next()) { + const MFInstruction &instruction = *instr_info.instruction; + switch (instruction.type()) { + case MFInstructionType::Call: { + const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>( + instruction); + execute_call_instruction(call_instruction, instr_info.mask(), variable_states, context); + scheduler.add_previous_instruction_indices(*call_instruction.next(), instr_info); + break; + } + case MFInstructionType::Branch: { + const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>( + instruction); + const MFVariable *condition_var = branch_instruction.condition(); + VariableState &variable_state = variable_states.get_variable_state(*condition_var); + + IndicesSplitVectors new_indices; + variable_state.indices_split(instr_info.mask(), new_indices); + scheduler.add_owned_indices(*branch_instruction.branch_false(), new_indices[false]); + scheduler.add_owned_indices(*branch_instruction.branch_true(), new_indices[true]); + break; + } + case MFInstructionType::Destruct: { + const MFDestructInstruction &destruct_instruction = + static_cast<const MFDestructInstruction &>(instruction); + const MFVariable *variable = destruct_instruction.variable(); + variable_states.destruct(*variable, instr_info.mask()); + scheduler.add_previous_instruction_indices(*destruct_instruction.next(), instr_info); + break; + } + case MFInstructionType::Dummy: { + const MFDummyInstruction &dummy_instruction = static_cast<const MFDummyInstruction &>( + instruction); + scheduler.add_previous_instruction_indices(*dummy_instruction.next(), instr_info); + break; + } + case MFInstructionType::Return: { + /* Don't insert the indices back into the scheduler. */ + break; + } + } + } + + for (const int param_index : this->param_indices()) { + const MFParamType param_type = this->param_type(param_index); + const MFVariable *variable = procedure_.params()[param_index].variable; + VariableState &variable_state = variable_states.get_variable_state(*variable); + switch (param_type.interface_type()) { + case MFParamType::Input: { + /* Input variables must be destructed in the end. */ + BLI_assert(variable_state.is_fully_uninitialized(full_mask)); + break; + } + case MFParamType::Mutable: + case MFParamType::Output: { + /* Mutable and output variables must be initialized in the end. */ + BLI_assert(variable_state.is_fully_initialized(full_mask)); + /* Make sure that the data is in the memory provided by the caller. */ + variable_state.ensure_is_mutable( + full_mask, param_type.data_type(), variable_states.value_allocator()); + break; + } + } + } +} + +} // namespace blender::fn diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc new file mode 100644 index 00000000000..041cdbd0f00 --- /dev/null +++ b/source/blender/functions/tests/FN_field_test.cc @@ -0,0 +1,294 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "FN_cpp_type.hh" +#include "FN_field.hh" +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_test_common.hh" + +namespace blender::fn::tests { + +TEST(field, ConstantFunction) +{ + /* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */ + GField constant_field{std::make_shared<FieldOperation>( + FieldOperation(std::make_unique<CustomMF_Constant<int>>(10), {})), + 0}; + + Array<int> result(4); + + FieldContext context; + FieldEvaluator evaluator{context, 4}; + evaluator.add_with_destination(constant_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[0], 10); + EXPECT_EQ(result[1], 10); + EXPECT_EQ(result[2], 10); + EXPECT_EQ(result[3], 10); +} + +class IndexFieldInput final : public FieldInput { + public: + IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") + { + } + + const GVArray *get_varray_for_context(const FieldContext &UNUSED(context), + IndexMask mask, + ResourceScope &scope) const final + { + auto index_func = [](int i) { return i; }; + return &scope.construct< + GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( + mask.min_array_size(), mask.min_array_size(), index_func); + } +}; + +TEST(field, VArrayInput) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + Array<int> result_1(4); + + FieldContext context; + FieldEvaluator evaluator{context, 4}; + evaluator.add_with_destination(index_field, result_1.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[0], 0); + EXPECT_EQ(result_1[1], 1); + EXPECT_EQ(result_1[2], 2); + EXPECT_EQ(result_1[3], 3); + + /* Evaluate a second time, just to test that the first didn't break anything. */ + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldEvaluator evaluator_2{context, &mask}; + evaluator_2.add_with_destination(index_field, result_2.as_mutable_span()); + evaluator_2.evaluate(); + EXPECT_EQ(result_2[2], 2); + EXPECT_EQ(result_2[4], 4); + EXPECT_EQ(result_2[6], 6); + EXPECT_EQ(result_2[8], 8); +} + +TEST(field, VArrayInputMultipleOutputs) +{ + std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>(); + GField field_1{index_input}; + GField field_2{index_input}; + + Array<int> result_1(10); + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(field_1, result_1.as_mutable_span()); + evaluator.add_with_destination(field_2, result_2.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[2], 2); + EXPECT_EQ(result_1[4], 4); + EXPECT_EQ(result_1[6], 6); + EXPECT_EQ(result_1[8], 8); + EXPECT_EQ(result_2[2], 2); + EXPECT_EQ(result_2[4], 4); + EXPECT_EQ(result_2[6], 6); + EXPECT_EQ(result_2[8], 8); +} + +TEST(field, InputAndFunction) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>( + "add", [](int a, int b) { return a + b; }); + GField output_field{std::make_shared<FieldOperation>( + FieldOperation(std::move(add_fn), {index_field, index_field})), + 0}; + + Array<int> result(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(output_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[2], 4); + EXPECT_EQ(result[4], 8); + EXPECT_EQ(result[6], 12); + EXPECT_EQ(result[8], 16); +} + +TEST(field, TwoFunctions) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>( + "add", [](int a, int b) { return a + b; }); + GField add_field{std::make_shared<FieldOperation>( + FieldOperation(std::move(add_fn), {index_field, index_field})), + 0}; + + std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>( + "add_10", [](int a) { return a + 10; }); + GField result_field{ + std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {add_field})), 0}; + + Array<int> result(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(result_field, result.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result[2], 14); + EXPECT_EQ(result[4], 18); + EXPECT_EQ(result[6], 22); + EXPECT_EQ(result[8], 26); +} + +class TwoOutputFunction : public MultiFunction { + private: + MFSignature signature_; + + public: + TwoOutputFunction(StringRef name) + { + MFSignatureBuilder signature{name}; + signature.single_input<int>("In1"); + signature.single_input<int>("In2"); + signature.single_output<int>("Add"); + signature.single_output<int>("Add10"); + signature_ = signature.build(); + this->set_signature(&signature_); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1"); + const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2"); + MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add"); + MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10"); + mask.foreach_index([&](const int64_t i) { + add[i] = in1[i] + in2[i]; + add_10[i] = add[i] + 10; + }); + } +}; + +TEST(field, FunctionTwoOutputs) +{ + /* Also use two separate input fields, why not. */ + GField index_field_1{std::make_shared<IndexFieldInput>()}; + GField index_field_2{std::make_shared<IndexFieldInput>()}; + + std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation( + std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2})); + + GField result_field_1{fn, 0}; + GField result_field_2{fn, 1}; + + Array<int> result_1(10); + Array<int> result_2(10); + + const Array<int64_t> indices = {2, 4, 6, 8}; + const IndexMask mask{indices}; + + FieldContext context; + FieldEvaluator evaluator{context, &mask}; + evaluator.add_with_destination(result_field_1, result_1.as_mutable_span()); + evaluator.add_with_destination(result_field_2, result_2.as_mutable_span()); + evaluator.evaluate(); + EXPECT_EQ(result_1[2], 4); + EXPECT_EQ(result_1[4], 8); + EXPECT_EQ(result_1[6], 12); + EXPECT_EQ(result_1[8], 16); + EXPECT_EQ(result_2[2], 14); + EXPECT_EQ(result_2[4], 18); + EXPECT_EQ(result_2[6], 22); + EXPECT_EQ(result_2[8], 26); +} + +TEST(field, TwoFunctionsTwoOutputs) +{ + GField index_field{std::make_shared<IndexFieldInput>()}; + + std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation( + std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field})); + + Array<int64_t> mask_indices = {2, 4, 6, 8}; + IndexMask mask = mask_indices.as_span(); + + Field<int> result_field_1{fn, 0}; + Field<int> intermediate_field{fn, 1}; + + std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>( + "add_10", [](int a) { return a + 10; }); + Field<int> result_field_2{ + std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {intermediate_field})), + 0}; + + FieldContext field_context; + FieldEvaluator field_evaluator{field_context, &mask}; + const VArray<int> *result_1 = nullptr; + const VArray<int> *result_2 = nullptr; + field_evaluator.add(result_field_1, &result_1); + field_evaluator.add(result_field_2, &result_2); + field_evaluator.evaluate(); + + EXPECT_EQ(result_1->get(2), 4); + EXPECT_EQ(result_1->get(4), 8); + EXPECT_EQ(result_1->get(6), 12); + EXPECT_EQ(result_1->get(8), 16); + EXPECT_EQ(result_2->get(2), 24); + EXPECT_EQ(result_2->get(4), 28); + EXPECT_EQ(result_2->get(6), 32); + EXPECT_EQ(result_2->get(8), 36); +} + +TEST(field, SameFieldTwice) +{ + GField constant_field{ + std::make_shared<FieldOperation>(std::make_unique<CustomMF_Constant<int>>(10)), 0}; + + FieldContext field_context; + IndexMask mask{IndexRange(2)}; + ResourceScope scope; + Vector<const GVArray *> results = evaluate_fields( + scope, {constant_field, constant_field}, mask, field_context); + + GVArray_Typed<int> varray1{*results[0]}; + GVArray_Typed<int> varray2{*results[1]}; + + EXPECT_EQ(varray1->get(0), 10); + EXPECT_EQ(varray1->get(1), 10); + EXPECT_EQ(varray2->get(0), 10); + EXPECT_EQ(varray2->get(1), 10); +} + +TEST(field, IgnoredOutput) +{ + static OptionalOutputsFunction fn; + Field<int> field{std::make_shared<FieldOperation>(fn), 0}; + + FieldContext field_context; + FieldEvaluator field_evaluator{field_context, 10}; + const VArray<int> *results = nullptr; + field_evaluator.add(field, &results); + field_evaluator.evaluate(); + + EXPECT_EQ(results->get(0), 5); + EXPECT_EQ(results->get(3), 5); +} + +} // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_procedure_test.cc b/source/blender/functions/tests/FN_multi_function_procedure_test.cc new file mode 100644 index 00000000000..0b4b88fba3d --- /dev/null +++ b/source/blender/functions/tests/FN_multi_function_procedure_test.cc @@ -0,0 +1,344 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" +#include "FN_multi_function_test_common.hh" + +namespace blender::fn::tests { + +TEST(multi_function_procedure, SimpleTest) +{ + /** + * procedure(int var1, int var2, int *var4) { + * int var3 = var1 + var2; + * var4 = var2 + var3; + * var4 += 10; + * } + */ + + CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }}; + CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_input_parameter<int>(); + MFVariable *var2 = &builder.add_single_input_parameter<int>(); + auto [var3] = builder.add_call<1>(add_fn, {var1, var2}); + auto [var4] = builder.add_call<1>(add_fn, {var2, var3}); + builder.add_call(add_10_fn, {var4}); + builder.add_destruct({var1, var2, var3}); + builder.add_return(); + builder.add_output_parameter(*var4); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor executor{"My Procedure", procedure}; + + MFParamsBuilder params{executor, 3}; + MFContextBuilder context; + + Array<int> input_array = {1, 2, 3}; + params.add_readonly_single_input(input_array.as_span()); + params.add_readonly_single_input_value(3); + + Array<int> output_array(3); + params.add_uninitialized_single_output(output_array.as_mutable_span()); + + executor.call(IndexRange(3), params, context); + + EXPECT_EQ(output_array[0], 17); + EXPECT_EQ(output_array[1], 18); + EXPECT_EQ(output_array[2], 19); +} + +TEST(multi_function_procedure, BranchTest) +{ + /** + * procedure(int &var1, bool var2) { + * if (var2) { + * var1 += 100; + * } + * else { + * var1 += 10; + * } + * var1 += 10; + * } + */ + + CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }}; + CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_mutable_parameter<int>(); + MFVariable *var2 = &builder.add_single_input_parameter<bool>(); + + MFProcedureBuilder::Branch branch = builder.add_branch(*var2); + branch.branch_false.add_call(add_10_fn, {var1}); + branch.branch_true.add_call(add_100_fn, {var1}); + builder.set_cursor_after_branch(branch); + builder.add_call(add_10_fn, {var1}); + builder.add_destruct({var2}); + builder.add_return(); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Condition Test", procedure}; + MFParamsBuilder params(procedure_fn, 5); + + Array<int> values_a = {1, 5, 3, 6, 2}; + Array<bool> values_cond = {true, false, true, true, false}; + + params.add_single_mutable(values_a.as_mutable_span()); + params.add_readonly_single_input(values_cond.as_span()); + + MFContextBuilder context; + procedure_fn.call({1, 2, 3, 4}, params, context); + + EXPECT_EQ(values_a[0], 1); + EXPECT_EQ(values_a[1], 25); + EXPECT_EQ(values_a[2], 113); + EXPECT_EQ(values_a[3], 116); + EXPECT_EQ(values_a[4], 22); +} + +TEST(multi_function_procedure, EvaluateOne) +{ + /** + * procedure(int var1, int *var2) { + * var2 = var1 + 10; + * } + */ + + int tot_evaluations = 0; + CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) { + tot_evaluations++; + return a + 10; + }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var1 = &builder.add_single_input_parameter<int>(); + auto [var2] = builder.add_call<1>(add_10_fn, {var1}); + builder.add_destruct(*var1); + builder.add_return(); + builder.add_output_parameter(*var2); + + MFProcedureExecutor procedure_fn{"Evaluate One", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> values_out = {1, 2, 3, 4, 5}; + params.add_readonly_single_input_value(1); + params.add_uninitialized_single_output(values_out.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(values_out[0], 11); + EXPECT_EQ(values_out[1], 11); + EXPECT_EQ(values_out[2], 3); + EXPECT_EQ(values_out[3], 11); + EXPECT_EQ(values_out[4], 11); + /* We expect only one evaluation, because the input is constant. */ + EXPECT_EQ(tot_evaluations, 1); +} + +TEST(multi_function_procedure, SimpleLoop) +{ + /** + * procedure(int count, int *out) { + * out = 1; + * int index = 0' + * loop { + * if (index >= count) { + * break; + * } + * out *= 2; + * index += 1; + * } + * out += 1000; + * } + */ + + CustomMF_Constant<int> const_1_fn{1}; + CustomMF_Constant<int> const_0_fn{0}; + CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal", + [](int a, int b) { return a >= b; }}; + CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }}; + CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }}; + CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_count = &builder.add_single_input_parameter<int>("count"); + auto [var_out] = builder.add_call<1>(const_1_fn); + var_out->set_name("out"); + auto [var_index] = builder.add_call<1>(const_0_fn); + var_index->set_name("index"); + + MFProcedureBuilder::Loop loop = builder.add_loop(); + auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count}); + var_condition->set_name("condition"); + MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition); + branch.branch_true.add_destruct(*var_condition); + branch.branch_true.add_loop_break(loop); + branch.branch_false.add_destruct(*var_condition); + builder.set_cursor_after_branch(branch); + builder.add_call(double_fn, {var_out}); + builder.add_call(add_1_fn, {var_index}); + builder.add_loop_continue(loop); + builder.set_cursor_after_loop(loop); + builder.add_call(add_1000_fn, {var_out}); + builder.add_destruct({var_count, var_index}); + builder.add_return(); + builder.add_output_parameter(*var_out); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Simple Loop", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> counts = {4, 3, 7, 6, 4}; + Array<int> results(5, -1); + + params.add_readonly_single_input(counts.as_span()); + params.add_uninitialized_single_output(results.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(results[0], 1016); + EXPECT_EQ(results[1], 1008); + EXPECT_EQ(results[2], -1); + EXPECT_EQ(results[3], 1064); + EXPECT_EQ(results[4], 1016); +} + +TEST(multi_function_procedure, Vectors) +{ + /** + * procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) { + * v1.extend(v2); + * int constant = 5; + * v2.append(constant); + * v2.extend(v1); + * int len = sum(v2); + * v3 = range(len); + * } + */ + + CreateRangeFunction create_range_fn; + ConcatVectorsFunction extend_fn; + GenericAppendFunction append_fn{CPPType::get<int>()}; + SumVectorFunction sum_elements_fn; + CustomMF_Constant<int> constant_5_fn{5}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>()); + MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>())); + builder.add_call(extend_fn, {var_v1, var_v2}); + auto [var_constant] = builder.add_call<1>(constant_5_fn); + builder.add_call(append_fn, {var_v2, var_constant}); + builder.add_destruct(*var_constant); + builder.add_call(extend_fn, {var_v2, var_v1}); + auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2}); + auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len}); + builder.add_destruct({var_v1, var_len}); + builder.add_return(); + builder.add_output_parameter(*var_v3); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Vectors", procedure}; + MFParamsBuilder params{procedure_fn, 5}; + + Array<int> v1 = {5, 2, 3}; + GVectorArray v2{CPPType::get<int>(), 5}; + GVectorArray v3{CPPType::get<int>(), 5}; + + int value_10 = 10; + v2.append(0, &value_10); + v2.append(4, &value_10); + + params.add_readonly_vector_input(v1.as_span()); + params.add_vector_mutable(v2); + params.add_vector_output(v3); + + MFContextBuilder context; + procedure_fn.call({0, 1, 3, 4}, params, context); + + EXPECT_EQ(v2[0].size(), 6); + EXPECT_EQ(v2[1].size(), 4); + EXPECT_EQ(v2[2].size(), 0); + EXPECT_EQ(v2[3].size(), 4); + EXPECT_EQ(v2[4].size(), 6); + + EXPECT_EQ(v3[0].size(), 35); + EXPECT_EQ(v3[1].size(), 15); + EXPECT_EQ(v3[2].size(), 0); + EXPECT_EQ(v3[3].size(), 15); + EXPECT_EQ(v3[4].size(), 35); +} + +TEST(multi_function_procedure, BufferReuse) +{ + /** + * procedure(int a, int *out) { + * int b = a + 10; + * int c = c + 10; + * int d = d + 10; + * int e = d + 10; + * out = e + 10; + * } + */ + + CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }}; + + MFProcedure procedure; + MFProcedureBuilder builder{procedure}; + + MFVariable *var_a = &builder.add_single_input_parameter<int>(); + auto [var_b] = builder.add_call<1>(add_10_fn, {var_a}); + builder.add_destruct(*var_a); + auto [var_c] = builder.add_call<1>(add_10_fn, {var_b}); + builder.add_destruct(*var_b); + auto [var_d] = builder.add_call<1>(add_10_fn, {var_c}); + builder.add_destruct(*var_c); + auto [var_e] = builder.add_call<1>(add_10_fn, {var_d}); + builder.add_destruct(*var_d); + auto [var_out] = builder.add_call<1>(add_10_fn, {var_e}); + builder.add_destruct(*var_e); + builder.add_return(); + builder.add_output_parameter(*var_out); + + EXPECT_TRUE(procedure.validate()); + + MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure}; + + Array<int> inputs = {4, 1, 6, 2, 3}; + Array<int> results(5, -1); + + MFParamsBuilder params{procedure_fn, 5}; + params.add_readonly_single_input(inputs.as_span()); + params.add_uninitialized_single_output(results.as_mutable_span()); + + MFContextBuilder context; + procedure_fn.call({0, 2, 3, 4}, params, context); + + EXPECT_EQ(results[0], 54); + EXPECT_EQ(results[1], -1); + EXPECT_EQ(results[2], 56); + EXPECT_EQ(results[3], 52); + EXPECT_EQ(results[4], 53); +} + +} // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc index 91c72a51dd6..d99993b35ac 100644 --- a/source/blender/functions/tests/FN_multi_function_test.cc +++ b/source/blender/functions/tests/FN_multi_function_test.cc @@ -263,7 +263,7 @@ TEST(multi_function, CustomMF_Constant) TEST(multi_function, CustomMF_GenericConstant) { int value = 42; - CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value}; + CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value, false}; EXPECT_EQ(fn.param_name(0), "42"); Array<int> outputs(4, 0); @@ -328,5 +328,32 @@ TEST(multi_function, CustomMF_Convert) EXPECT_EQ(outputs[2], 9); } +TEST(multi_function, IgnoredOutputs) +{ + OptionalOutputsFunction fn; + { + MFParamsBuilder params(fn, 10); + params.add_ignored_single_output("Out 1"); + params.add_ignored_single_output("Out 2"); + MFContextBuilder context; + fn.call(IndexRange(10), params, context); + } + { + Array<int> results_1(10); + Array<std::string> results_2(10, NoInitialization()); + + MFParamsBuilder params(fn, 10); + params.add_uninitialized_single_output(results_1.as_mutable_span(), "Out 1"); + params.add_uninitialized_single_output(results_2.as_mutable_span(), "Out 2"); + MFContextBuilder context; + fn.call(IndexRange(10), params, context); + + EXPECT_EQ(results_1[0], 5); + EXPECT_EQ(results_1[3], 5); + EXPECT_EQ(results_1[9], 5); + EXPECT_EQ(results_2[0], "hello, this is a long string"); + } +} + } // namespace } // namespace blender::fn::tests diff --git a/source/blender/functions/tests/FN_multi_function_test_common.hh b/source/blender/functions/tests/FN_multi_function_test_common.hh index 51c8fac8a96..2a64cc302fe 100644 --- a/source/blender/functions/tests/FN_multi_function_test_common.hh +++ b/source/blender/functions/tests/FN_multi_function_test_common.hh @@ -171,4 +171,33 @@ class SumVectorFunction : public MultiFunction { } }; +class OptionalOutputsFunction : public MultiFunction { + public: + OptionalOutputsFunction() + { + static MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static MFSignature create_signature() + { + MFSignatureBuilder signature{"Optional Outputs"}; + signature.single_output<int>("Out 1"); + signature.single_output<std::string>("Out 2"); + return signature.build(); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + if (params.single_output_is_required(0, "Out 1")) { + MutableSpan<int> values = params.uninitialized_single_output<int>(0, "Out 1"); + values.fill_indices(mask, 5); + } + MutableSpan<std::string> values = params.uninitialized_single_output<std::string>(1, "Out 2"); + for (const int i : mask) { + new (&values[i]) std::string("hello, this is a long string"); + } + } +}; + } // namespace blender::fn::tests diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index ec965c9a29f..adf68e534bb 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -52,6 +52,7 @@ set(SRC intern/MOD_gpencilarray.c intern/MOD_gpencilbuild.c intern/MOD_gpencilcolor.c + intern/MOD_gpencildash.c intern/MOD_gpencilhook.c intern/MOD_gpencillattice.c intern/MOD_gpencillength.c diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h index 18310bd5dff..043186155b7 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h @@ -46,6 +46,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply; extern GpencilModifierTypeInfo modifierType_Gpencil_Texture; extern GpencilModifierTypeInfo modifierType_Gpencil_Weight; extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart; +extern GpencilModifierTypeInfo modifierType_Gpencil_Dash; /* MOD_gpencil_util.c */ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index 6409c86b6e3..5eb1eeab780 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -65,6 +65,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]) INIT_GP_TYPE(Texture); INIT_GP_TYPE(Weight); INIT_GP_TYPE(Lineart); + INIT_GP_TYPE(Dash); #undef INIT_GP_TYPE } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c new file mode 100644 index 00000000000..ba33edd6a94 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencildash.c @@ -0,0 +1,387 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021, Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup modifiers + */ + +#include <stdio.h> +#include <string.h> + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" + +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" +#include "BKE_modifier.h" +#include "BKE_screen.h" + +#include "MEM_guardedalloc.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "BLT_translation.h" + +#include "MOD_gpencil_modifiertypes.h" +#include "MOD_gpencil_ui_common.h" +#include "MOD_gpencil_util.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" + +static void initData(GpencilModifierData *md) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(dmd, modifier)); + + MEMCPY_STRUCT_AFTER(dmd, DNA_struct_default_get(DashGpencilModifierData), modifier); + + DashGpencilModifierSegment *ds = DNA_struct_default_alloc(DashGpencilModifierSegment); + ds->dmd = dmd; + BLI_strncpy(ds->name, DATA_("Segment"), sizeof(ds->name)); + + dmd->segments = ds; +} + +static void copyData(const GpencilModifierData *md, GpencilModifierData *target) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)target; + const DashGpencilModifierData *dmd_src = (const DashGpencilModifierData *)md; + + BKE_gpencil_modifier_copydata_generic(md, target); + + dmd->segments = MEM_dupallocN(dmd_src->segments); +} + +static void freeData(GpencilModifierData *md) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)md; + + MEM_SAFE_FREE(dmd->segments); +} + +/** + * Gap==0 means to start the next segment at the immediate next point, which will leave a visual + * gap of "1 point". This makes the algorithm give the same visual appearance as displayed on the + * UI and also simplifies the check for "no-length" situation where SEG==0 (which will not produce + * any effective dash). + */ +static int real_gap(const DashGpencilModifierSegment *ds) +{ + return ds->gap - 1; +} + +static bool stroke_dash(const bGPDstroke *gps, + const DashGpencilModifierData *dmd, + ListBase *r_strokes) +{ + int new_stroke_offset = 0; + int trim_start = 0; + + for (int i = 0; i < dmd->segments_len; i++) { + if (dmd->segments[i].dash + real_gap(&dmd->segments[i]) < 1) { + BLI_assert_unreachable(); + /* This means there's a part that doesn't have any length, can't do dot-dash. */ + return false; + } + } + + const DashGpencilModifierSegment *const first_segment = &dmd->segments[0]; + const DashGpencilModifierSegment *const last_segment = &dmd->segments[dmd->segments_len - 1]; + const DashGpencilModifierSegment *ds = first_segment; + + /* Determine starting configuration using offset. */ + int offset_trim = dmd->dash_offset; + while (offset_trim < 0) { + ds = (ds == first_segment) ? last_segment : ds - 1; + offset_trim += ds->dash + real_gap(ds); + } + + /* This segment is completely removed from view by the index offset, ignore it. */ + while (ds->dash + real_gap(ds) < offset_trim) { + offset_trim -= ds->dash + real_gap(ds); + ds = (ds == last_segment) ? first_segment : ds + 1; + } + + /* This segment is partially visible at the beginning of the stroke. */ + if (ds->dash > offset_trim) { + trim_start = offset_trim; + } + else { + /* This segment is not visible but the gap immediately after this segment is partially visible, + * use next segment's dash. */ + new_stroke_offset += ds->dash + real_gap(ds) - offset_trim; + ds = (ds == last_segment) ? first_segment : ds + 1; + } + + while (new_stroke_offset < gps->totpoints - 1) { + const int seg = ds->dash - trim_start; + if (!(seg || real_gap(ds))) { + ds = (ds == last_segment) ? first_segment : ds + 1; + continue; + } + + const int size = MIN2(gps->totpoints - new_stroke_offset, seg); + if (size == 0) { + continue; + } + + bGPDstroke *stroke = BKE_gpencil_stroke_new( + ds->mat_nr < 0 ? gps->mat_nr : ds->mat_nr, size, gps->thickness); + + for (int is = 0; is < size; is++) { + bGPDspoint *p = &gps->points[new_stroke_offset + is]; + stroke->points[is].x = p->x; + stroke->points[is].y = p->y; + stroke->points[is].z = p->z; + stroke->points[is].pressure = p->pressure * ds->radius; + stroke->points[is].strength = p->strength * ds->opacity; + } + BLI_addtail(r_strokes, stroke); + + if (gps->dvert) { + BKE_gpencil_dvert_ensure(stroke); + for (int di = 0; di < stroke->totpoints; di++) { + MDeformVert *dv = &gps->dvert[new_stroke_offset + di]; + if (dv && dv->totweight && dv->dw) { + MDeformWeight *dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight, + __func__); + memcpy(dw, dv->dw, sizeof(MDeformWeight) * dv->totweight); + stroke->dvert[di].dw = dw; + stroke->dvert[di].totweight = dv->totweight; + stroke->dvert[di].flag = dv->flag; + } + } + } + + new_stroke_offset += seg + real_gap(ds); + ds = (ds == last_segment) ? first_segment : ds + 1; + trim_start = 0; + } + + return true; +} + +static void apply_dash_for_frame( + Object *ob, bGPDlayer *gpl, bGPdata *gpd, bGPDframe *gpf, DashGpencilModifierData *dmd) +{ + if (dmd->segments_len == 0) { + return; + } + + ListBase result = {NULL, NULL}; + + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (is_stroke_affected_by_modifier(ob, + dmd->layername, + dmd->material, + dmd->pass_index, + dmd->layer_pass, + 1, + gpl, + gps, + dmd->flag & GP_LENGTH_INVERT_LAYER, + dmd->flag & GP_LENGTH_INVERT_PASS, + dmd->flag & GP_LENGTH_INVERT_LAYERPASS, + dmd->flag & GP_LENGTH_INVERT_MATERIAL)) { + stroke_dash(gps, dmd, &result); + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } + bGPDstroke *gps_dash; + while ((gps_dash = BLI_pophead(&result))) { + BLI_addtail(&gpf->strokes, gps_dash); + BKE_gpencil_stroke_geometry_update(gpd, gps_dash); + } +} + +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *UNUSED(depsgraph), + GpencilModifierData *md, + Object *ob) +{ + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md); + } + } +} + +/* -------------------------------- */ + +/* Generic "generateStrokes" callback */ +static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob) +{ + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + BKE_gpencil_frame_active_set(depsgraph, gpd); + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + return; + } + apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md); + } +} + +static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + DashGpencilModifierData *mmd = (DashGpencilModifierData *)md; + + walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); +} + +static void segment_list_item(struct uiList *UNUSED(ui_list), + struct bContext *UNUSED(C), + struct uiLayout *layout, + struct PointerRNA *UNUSED(idataptr), + struct PointerRNA *itemptr, + int UNUSED(icon), + struct PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + uiLayout *row = uiLayoutRow(layout, true); + uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE); +} + +static void panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "dash_offset", 0, NULL, ICON_NONE); + + uiLayout *row = uiLayoutRow(layout, false); + uiLayoutSetPropSep(row, false); + + uiTemplateList(row, + (bContext *)C, + "MOD_UL_dash_segment", + "", + ptr, + "segments", + ptr, + "segment_active_index", + NULL, + 3, + 10, + 0, + 1, + UI_TEMPLATE_LIST_FLAG_NONE); + + uiLayout *col = uiLayoutColumn(row, false); + uiLayoutSetContextPointer(col, "modifier", ptr); + + uiLayout *sub = uiLayoutColumn(col, true); + uiItemO(sub, "", ICON_ADD, "GPENCIL_OT_segment_add"); + uiItemO(sub, "", ICON_REMOVE, "GPENCIL_OT_segment_remove"); + uiItemS(col); + sub = uiLayoutColumn(col, true); + uiItemEnumO_string(sub, "", ICON_TRIA_UP, "GPENCIL_OT_segment_move", "type", "UP"); + uiItemEnumO_string(sub, "", ICON_TRIA_DOWN, "GPENCIL_OT_segment_move", "type", "DOWN"); + + DashGpencilModifierData *dmd = ptr->data; + + if (dmd->segment_active_index >= 0 && dmd->segment_active_index < dmd->segments_len) { + PointerRNA ds_ptr; + RNA_pointer_create(ptr->owner_id, + &RNA_DashGpencilModifierSegment, + &dmd->segments[dmd->segment_active_index], + &ds_ptr); + + sub = uiLayoutColumn(layout, true); + uiItemR(sub, &ds_ptr, "dash", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "gap", 0, NULL, ICON_NONE); + + sub = uiLayoutColumn(layout, false); + uiItemR(sub, &ds_ptr, "radius", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "opacity", 0, NULL, ICON_NONE); + uiItemR(sub, &ds_ptr, "material_index", 0, NULL, ICON_NONE); + } + + gpencil_modifier_panel_end(layout, ptr); +} + +static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + gpencil_modifier_masking_panel_draw(panel, true, false); +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel_type = gpencil_modifier_panel_register( + region_type, eGpencilModifierType_Dash, panel_draw); + gpencil_modifier_subpanel_register( + region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); + + uiListType *list_type = MEM_callocN(sizeof(uiListType), "dash modifier segment uilist"); + strcpy(list_type->idname, "MOD_UL_dash_segment"); + list_type->draw_item = segment_list_item; + WM_uilisttype_add(list_type); +} + +GpencilModifierTypeInfo modifierType_Gpencil_Dash = { + /* name */ "Dot Dash", + /* structName */ "DashGpencilModifierData", + /* structSize */ sizeof(DashGpencilModifierData), + /* type */ eGpencilModifierTypeType_Gpencil, + /* flags */ eGpencilModifierTypeFlag_SupportsEditmode, + + /* copyData */ copyData, + + /* deformStroke */ NULL, + /* generateStrokes */ generateStrokes, + /* bakeModifier */ bakeModifier, + /* remapTime */ NULL, + + /* initData */ initData, + /* freeData */ freeData, + /* isDisabled */ NULL, + /* updateDepsgraph */ NULL, + /* dependsOnTime */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* panelRegister */ panelRegister, +}; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c index 857c683d95a..6aa0e6c152e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c @@ -108,27 +108,6 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke } } -static void bakeModifier(Main *UNUSED(bmain), - Depsgraph *UNUSED(depsgraph), - GpencilModifierData *md, - Object *ob) -{ - - bGPdata *gpd = ob->data; - - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - applyLength(lmd, gpd, gps); - } - } - } -} - -/* -------------------------------- */ - -/* Generic "generateStrokes" callback */ static void deformStroke(GpencilModifierData *md, Depsgraph *UNUSED(depsgraph), Object *ob, @@ -154,6 +133,23 @@ static void deformStroke(GpencilModifierData *md, } } +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *depsgraph, + GpencilModifierData *md, + Object *ob) +{ + + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + deformStroke(md, depsgraph, ob, gpl, gpf, gps); + } + } + } +} + static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) { LengthGpencilModifierData *mmd = (LengthGpencilModifierData *)md; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 73ca4b9c529..01488a8b2de 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -390,6 +390,8 @@ static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(col, ptr, "use_edge_overlap", 0, IFACE_("Overlapping Edges As Contour"), ICON_NONE); uiItemR(col, ptr, "use_object_instances", 0, NULL, ICON_NONE); uiItemR(col, ptr, "use_clip_plane_boundaries", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "use_crease_on_smooth", 0, IFACE_("Crease On Smooth"), ICON_NONE); + uiItemR(layout, ptr, "use_crease_on_sharp", 0, IFACE_("Crease On Sharp"), ICON_NONE); } static void style_panel_draw(const bContext *UNUSED(C), Panel *panel) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c index 9e9eba3d61e..ae862ce3eda 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c @@ -324,7 +324,7 @@ static void random_panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetPropSep(layout, true); - uiLayoutSetActive(layout, RNA_boolean_get(ptr, "random")); + uiLayoutSetActive(layout, RNA_boolean_get(ptr, "use_random")); uiItemR(layout, ptr, "step", 0, NULL, ICON_NONE); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c index 4142a1c02dc..b00db1ba2d2 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c @@ -132,7 +132,7 @@ static void deformStroke(GpencilModifierData *md, const float val = mmd->factor * weight; /* perform smoothing */ if (mmd->flag & GP_SMOOTH_MOD_LOCATION) { - BKE_gpencil_stroke_smooth(gps, i, val); + BKE_gpencil_stroke_smooth_point(gps, i, val); } if (mmd->flag & GP_SMOOTH_MOD_STRENGTH) { BKE_gpencil_stroke_smooth_strength(gps, i, val); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c index f444103ae3f..ee5ba7e92ca 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c @@ -76,16 +76,12 @@ static void deformStroke(GpencilModifierData *md, SubdivGpencilModifierData *mmd = (SubdivGpencilModifierData *)md; bGPdata *gpd = ob->data; - /* It makes sense when adding points to a straight line */ - /* e.g. for creating thickness variation in later modifiers. */ - const int minimum_vert = (mmd->flag & GP_SUBDIV_SIMPLE) ? 2 : 3; - if (!is_stroke_affected_by_modifier(ob, mmd->layername, mmd->material, mmd->pass_index, mmd->layer_pass, - minimum_vert, + 2, gpl, gps, mmd->flag & GP_SUBDIV_INVERT_LAYER, @@ -95,7 +91,10 @@ static void deformStroke(GpencilModifierData *md, return; } - BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, mmd->type); + /* For strokes with less than 3 points, only the Simple Subdivision makes sense. */ + short type = gps->totpoints < 3 ? GP_SUBDIV_SIMPLE : mmd->type; + + BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, type); /* If the stroke is cyclic, must generate the closing geometry. */ if (gps->flag & GP_STROKE_CYCLIC) { diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 4b71011b99a..134d9707ade 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -304,6 +304,9 @@ typedef struct LineartRenderBuffer { bool filter_face_mark_invert; bool filter_face_mark_boundaries; + bool force_crease; + bool sharp_as_crease; + /* Keep an copy of these data so when line art is running it's self-contained. */ bool cam_is_persp; float cam_obmat[4][4]; diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 8c7e735251e..7176501f86d 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -1453,7 +1453,7 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, LineartTriangle *rt_array, LineartVert *rv_array, float crease_threshold, - bool no_crease, + bool use_auto_smooth, bool use_freestyle_edge, bool use_freestyle_face, BMesh *bm_if_freestyle) @@ -1533,10 +1533,20 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, edge_flag_result |= LRT_EDGE_FLAG_CONTOUR; } - if (rb->use_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) { - if (!no_crease) { + if (rb->use_crease) { + if (rb->sharp_as_crease && !BM_elem_flag_test(e, BM_ELEM_SMOOTH)) { edge_flag_result |= LRT_EDGE_FLAG_CREASE; } + else { + bool do_crease = true; + if (!rb->force_crease && !use_auto_smooth && + (BM_elem_flag_test(ll->f, BM_ELEM_SMOOTH) && BM_elem_flag_test(lr->f, BM_ELEM_SMOOTH))) { + do_crease = false; + } + if (do_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) { + edge_flag_result |= LRT_EDGE_FLAG_CREASE; + } + } } if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) { edge_flag_result |= LRT_EDGE_FLAG_MATERIAL; @@ -1747,9 +1757,14 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu eln->object_ref = orig_ob; obi->v_eln = eln; + bool use_auto_smooth = false; if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { use_crease = cosf(M_PI - orig_ob->lineart.crease_threshold); } + if (obi->original_me->flag & ME_AUTOSMOOTH) { + use_crease = cosf(obi->original_me->smoothresh); + use_auto_smooth = true; + } else { use_crease = rb->crease_threshold; } @@ -1833,15 +1848,15 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu e = BM_edge_at_index(bm, i); /* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */ - char eflag = lineart_identify_feature_line(rb, - e, - ort, - orv, - use_crease, - orig_ob->type == OB_FONT, - can_find_freestyle_edge, - can_find_freestyle_face, - bm); + uint16_t eflag = lineart_identify_feature_line(rb, + e, + ort, + orv, + use_crease, + use_auto_smooth, + can_find_freestyle_edge, + can_find_freestyle_face, + bm); if (eflag) { /* Only allocate for feature lines (instead of all lines) to save memory. * If allow duplicated edges, one edge gets added multiple times if it has multiple types. */ @@ -3058,6 +3073,9 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, rb->allow_duplicated_types = (lmd->calculation_flags & LRT_ALLOW_OVERLAP_EDGE_TYPES) != 0; + rb->force_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SMOOTH_SURFACES) != 0; + rb->sharp_as_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SHARP_EDGES) != 0; + int16_t edge_types = lmd->edge_types_override; rb->use_contour = (edge_types & LRT_EDGE_FLAG_CONTOUR) != 0; diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index f834ee5b234..62b748b7edf 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -54,7 +54,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, const char *fragcode, const char *geomcode, const char *libcode, - const char *defines); + const char *defines, + const char *name); GPUShader *GPU_shader_create_ex(const char *vertcode, const char *fragcode, const char *geomcode, @@ -85,6 +86,8 @@ void GPU_shader_free(GPUShader *shader); void GPU_shader_bind(GPUShader *shader); void GPU_shader_unbind(void); +const char *GPU_shader_get_name(GPUShader *shader); + /* Returns true if transform feedback was successfully enabled. */ bool GPU_shader_transform_feedback_enable(GPUShader *shader, struct GPUVertBuf *vertbuf); void GPU_shader_transform_feedback_disable(GPUShader *shader); diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index c754a649924..9340d311472 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -229,7 +229,8 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, const char *fragcode, const char *geomcode, const char *libcode, - const char *defines) + const char *defines, + const char *name) { char *libcodecat = nullptr; @@ -240,6 +241,9 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, libcode = libcodecat = BLI_strdupcat(libcode, datatoc_gpu_shader_colorspace_lib_glsl); } + /* Use pyGPUShader as default name for shader. */ + const char *shname = name != nullptr ? name : "pyGPUShader"; + GPUShader *sh = GPU_shader_create_ex(vertcode, fragcode, geomcode, @@ -249,7 +253,7 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, GPU_SHADER_TFB_NONE, nullptr, 0, - "pyGPUShader"); + shname); MEM_SAFE_FREE(libcodecat); return sh; @@ -369,6 +373,17 @@ void GPU_shader_unbind(void) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Shader name + * \{ */ + +const char *GPU_shader_get_name(GPUShader *shader) +{ + return unwrap(shader)->name_get(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Transform feedback * * TODO(fclem): Should be replaced by compute shaders. diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index 772fc19d919..2855d5078ff 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -460,7 +460,7 @@ void GLBackend::capabilities_init() GCaps.mem_stats_support = GLEW_NVX_gpu_memory_info || GLEW_ATI_meminfo; GCaps.shader_image_load_store_support = GLEW_ARB_shader_image_load_store; - GCaps.compute_shader_support = GLEW_ARB_compute_shader; + GCaps.compute_shader_support = GLEW_ARB_compute_shader && GLEW_VERSION_4_3; if (GCaps.compute_shader_support) { glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &GCaps.max_work_group_count[0]); glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &GCaps.max_work_group_count[1]); diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h index 4ad7aa98484..dd8e6549e24 100644 --- a/source/blender/imbuf/IMB_imbuf.h +++ b/source/blender/imbuf/IMB_imbuf.h @@ -150,6 +150,14 @@ bool IMB_initImBuf( /** * Create a copy of a pixel buffer and wrap it to a new ImBuf + * (transferring ownership to the in imbuf). + * \attention Defined in allocimbuf.c + */ +struct ImBuf *IMB_allocFromBufferOwn( + unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels); + +/** + * Create a copy of a pixel buffer and wrap it to a new ImBuf * \attention Defined in allocimbuf.c */ struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, diff --git a/source/blender/imbuf/IMB_thumbs.h b/source/blender/imbuf/IMB_thumbs.h index 9dd0cbe895f..e1a315a0bd2 100644 --- a/source/blender/imbuf/IMB_thumbs.h +++ b/source/blender/imbuf/IMB_thumbs.h @@ -52,6 +52,7 @@ typedef enum ThumbSource { #define THUMB_SIZE_MAX (100 * 1024 * 1024) #define PREVIEW_RENDER_DEFAULT_HEIGHT 128 +#define PREVIEW_RENDER_LARGE_HEIGHT 256 /* Note this can also be used as versioning system, * to force refreshing all thumbnails if e.g. we change some thumb generating code or so. diff --git a/source/blender/imbuf/intern/allocimbuf.c b/source/blender/imbuf/intern/allocimbuf.c index 90c863878ff..1369f8cc0f8 100644 --- a/source/blender/imbuf/intern/allocimbuf.c +++ b/source/blender/imbuf/intern/allocimbuf.c @@ -430,6 +430,41 @@ bool imb_addrectImBuf(ImBuf *ibuf) return false; } +/** + * \param take_ownership: When true, the buffers become owned by the resulting image. + */ +struct ImBuf *IMB_allocFromBufferOwn( + unsigned int *rect, float *rectf, unsigned int w, unsigned int h, unsigned int channels) +{ + ImBuf *ibuf = NULL; + + if (!(rect || rectf)) { + return NULL; + } + + ibuf = IMB_allocImBuf(w, h, 32, 0); + + ibuf->channels = channels; + + /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */ + if (rectf) { + BLI_assert(MEM_allocN_len(rectf) == sizeof(float[4]) * w * h); + ibuf->rect_float = rectf; + + ibuf->flags |= IB_rectfloat; + ibuf->mall |= IB_rectfloat; + } + if (rect) { + BLI_assert(MEM_allocN_len(rect) == sizeof(uchar[4]) * w * h); + ibuf->rect = rect; + + ibuf->flags |= IB_rect; + ibuf->mall |= IB_rect; + } + + return ibuf; +} + struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, const float *rectf, unsigned int w, @@ -445,13 +480,21 @@ struct ImBuf *IMB_allocFromBuffer(const unsigned int *rect, ibuf = IMB_allocImBuf(w, h, 32, 0); ibuf->channels = channels; + + /* Avoid #MEM_dupallocN since the buffers might not be allocated using guarded-allocation. */ if (rectf) { - ibuf->rect_float = MEM_dupallocN(rectf); + const size_t size = sizeof(float[4]) * w * h; + ibuf->rect_float = MEM_mallocN(size, __func__); + memcpy(ibuf->rect_float, rectf, size); + ibuf->flags |= IB_rectfloat; ibuf->mall |= IB_rectfloat; } if (rect) { - ibuf->rect = MEM_dupallocN(rect); + const size_t size = sizeof(uchar[4]) * w * h; + ibuf->rect = MEM_mallocN(size, __func__); + memcpy(ibuf->rect, rect, size); + ibuf->flags |= IB_rect; ibuf->mall |= IB_rect; } diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c index a09f06726a6..aa1da65253d 100644 --- a/source/blender/imbuf/intern/thumbs.c +++ b/source/blender/imbuf/intern/thumbs.c @@ -347,7 +347,7 @@ static ImBuf *thumb_create_ex(const char *file_path, tsize = PREVIEW_RENDER_DEFAULT_HEIGHT; break; case THB_LARGE: - tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2; + tsize = PREVIEW_RENDER_LARGE_HEIGHT; break; case THB_FAIL: tsize = 1; diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 0a3a43bb21f..0b5e927f02f 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -117,7 +117,9 @@ struct Mesh *ABC_read_mesh(struct CacheReader *reader, struct Mesh *existing_mesh, const float time, const char **err_str, - int read_flags); + const int read_flags, + const char *velocity_name, + const float velocity_scale); bool ABC_mesh_topology_changed(struct CacheReader *reader, struct Object *ob, @@ -133,16 +135,6 @@ struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *h struct Object *object, const char *object_path); -bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name); - -/* r_vertex_velocities should point to a preallocated array of num_vertices floats */ -int ABC_read_velocity_cache(struct CacheReader *reader, - const char *velocity_name, - float time, - float velocity_scale, - int num_vertices, - float *r_vertex_velocities); - #ifdef __cplusplus } #endif diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc index 910e04f3bf5..3665494c046 100644 --- a/source/blender/io/alembic/exporter/abc_writer_abstract.cc +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc @@ -118,7 +118,7 @@ void ABCAbstractWriter::update_bounding_box(Object *object) if (!bb) { if (object->type != OB_CAMERA) { - CLOG_WARN(&LOG, "Bounding box is null!\n"); + CLOG_WARN(&LOG, "Bounding box is null!"); } bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0; bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0; diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index fcd89b6731a..781bba363c4 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -25,6 +25,7 @@ #include "BLI_assert.h" #include "BLI_math_vector.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -108,9 +109,6 @@ void ABCGenericMeshWriter::create_alembic_objects(const HierarchyContext *contex OBoolProperty type(typeContainer, "meshtype"); type.set(subsurf_modifier_ == nullptr); } - - Scene *scene_eval = DEG_get_evaluated_scene(args_.depsgraph); - liquid_sim_modifier_ = get_liquid_sim_modifier(scene_eval, context->object); } Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const @@ -144,21 +142,6 @@ bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const return false; } -ModifierData *ABCGenericMeshWriter::get_liquid_sim_modifier(Scene *scene, Object *ob) -{ - ModifierData *md = BKE_modifiers_findby_type(ob, eModifierType_Fluidsim); - - if (md && (BKE_modifier_is_enabled(scene, md, eModifierMode_Render))) { - FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); - - if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) { - return md; - } - } - - return nullptr; -} - bool ABCGenericMeshWriter::is_supported(const HierarchyContext *context) const { if (args_.export_params->visible_objects_only) { @@ -284,8 +267,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); } - if (liquid_sim_modifier_ != nullptr) { - get_velocities(mesh, velocities); + if (get_velocities(mesh, velocities)) { mesh_sample.setVelocities(V3fArraySample(velocities)); } @@ -368,11 +350,6 @@ void ABCGenericMeshWriter::write_face_sets(Object *object, struct Mesh *mesh, Sc void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) { - if (liquid_sim_modifier_ != nullptr) { - /* We don't need anything more for liquid meshes. */ - return; - } - if (frame_has_been_written_ || !args_.export_params->vcolors) { return; } @@ -387,27 +364,28 @@ void ABCGenericMeshWriter::write_arb_geo_params(struct Mesh *me) write_custom_data(arb_geom_params, m_custom_data_config, &me->ldata, CD_MLOOPCOL); } -void ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +bool ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) { + /* Export velocity attribute output by fluid sim, sequence cache modifier + * and geometry nodes. */ + CustomDataLayer *velocity_layer = BKE_id_attribute_find( + &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT); + + if (velocity_layer == nullptr) { + return false; + } + const int totverts = mesh->totvert; + const float(*mesh_velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data); vels.clear(); vels.resize(totverts); - FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(liquid_sim_modifier_); - FluidsimSettings *fss = fmd->fss; - - if (fss->meshVelocities) { - float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities); - - for (int i = 0; i < totverts; i++) { - copy_yup_from_zup(vels[i].getValue(), mesh_vels); - mesh_vels += 3; - } - } - else { - std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f)); + for (int i = 0; i < totverts; i++) { + copy_yup_from_zup(vels[i].getValue(), mesh_velocities[i]); } + + return true; } void ABCGenericMeshWriter::get_geo_groups(Object *object, diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h index 0e1792b9dab..fb8a01a3bbf 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.h +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h @@ -45,7 +45,6 @@ class ABCGenericMeshWriter : public ABCAbstractWriter { * exported object. */ bool is_subd_; ModifierData *subsurf_modifier_; - ModifierData *liquid_sim_modifier_; CDStreamConfig m_custom_data_config; @@ -70,10 +69,8 @@ class ABCGenericMeshWriter : public ABCAbstractWriter { void write_subd(HierarchyContext &context, Mesh *mesh); template<typename Schema> void write_face_sets(Object *object, Mesh *mesh, Schema &schema); - ModifierData *get_liquid_sim_modifier(Scene *scene_eval, Object *ob_eval); - void write_arb_geo_params(Mesh *me); - void get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels); + bool get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels); void get_geo_groups(Object *object, Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geo_groups); diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index e9736555ead..4fba6a2f0f7 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -122,6 +122,12 @@ void read_custom_data(const std::string &iobject_full_name, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss); +void read_velocity(const Alembic::Abc::ICompoundProperty &prop, + const Alembic::Abc::PropertyHeader *prop_header, + const Alembic::Abc::ISampleSelector &selector, + const CDStreamConfig &config, + const char *velocity_name, + const float velocity_scale); typedef enum { ABC_UV_SCOPE_NONE, ABC_UV_SCOPE_LOOP, diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index 27ee35d1b39..d2ec7fb84db 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -94,7 +94,7 @@ void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele { Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); - cu->flag |= CU_DEFORM_FILL | CU_3D; + cu->flag |= CU_3D; cu->actvert = CU_ACT_NONE; cu->resolu = 1; @@ -283,6 +283,8 @@ void AbcCurveReader::read_curve_sample(Curve *cu, Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, int /*read_flag*/, + const char * /*velocity_name*/, + const float /*velocity_scale*/, const char **err_str) { ICurvesSchema::Sample sample; diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index 075ed5ca6a1..11b23c8a8cf 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -45,7 +45,9 @@ class AbcCurveReader : public AbcObjectReader { void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); void read_curve_sample(Curve *cu, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 77edd4908bd..eab94139f55 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -27,6 +27,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_customdata_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -36,6 +37,7 @@ #include "BLI_listbase.h" #include "BLI_math_geom.h" +#include "BKE_attribute.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -43,8 +45,10 @@ #include "BKE_object.h" using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::IV3fArrayProperty; using Alembic::Abc::P3fArraySamplePtr; using Alembic::Abc::PropertyHeader; +using Alembic::Abc::V3fArraySamplePtr; using Alembic::AbcGeom::IC3fGeomParam; using Alembic::AbcGeom::IC4fGeomParam; @@ -420,6 +424,52 @@ static void get_weight_and_index(CDStreamConfig &config, config.ceil_index = i1; } +static V3fArraySamplePtr get_velocity_prop(const ICompoundProperty &schema, + const ISampleSelector &selector, + const std::string &name) +{ + for (size_t i = 0; i < schema.getNumProperties(); i++) { + const PropertyHeader &header = schema.getPropertyHeader(i); + + if (header.isCompound()) { + const ICompoundProperty &prop = ICompoundProperty(schema, header.getName()); + + if (has_property(prop, name)) { + const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0); + if (velocity_prop) { + return velocity_prop.getValue(selector); + } + } + } + else if (header.isArray()) { + if (header.getName() == name) { + const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0); + return velocity_prop.getValue(selector); + } + } + } + + return V3fArraySamplePtr(); +} + +static void read_velocity(const V3fArraySamplePtr &velocities, + const CDStreamConfig &config, + const float velocity_scale) +{ + CustomDataLayer *velocity_layer = BKE_id_attribute_new( + &config.mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, nullptr); + float(*velocity)[3] = (float(*)[3])velocity_layer->data; + + const int num_velocity_vectors = static_cast<int>(velocities->size()); + BLI_assert(num_velocity_vectors == config.mesh->totvert); + + for (int i = 0; i < num_velocity_vectors; i++) { + const Imath::V3f &vel_in = (*velocities)[i]; + copy_zup_from_yup(velocity[i], vel_in.getValue()); + mul_v3_fl(velocity[i], velocity_scale); + } +} + static void read_mesh_sample(const std::string &iobject_full_name, ImportSettings *settings, const IPolyMeshSchema &schema, @@ -458,6 +508,13 @@ static void read_mesh_sample(const std::string &iobject_full_name, if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); } + + if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { + V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); + if (velocities) { + read_velocity(velocities, config, settings->velocity_scale); + } + } } CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation) @@ -563,7 +620,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); if (read_mesh != mesh) { /* XXX FIXME: after 2.80; mesh->flag isn't copied by #BKE_mesh_nomain_to_mesh(). */ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ @@ -630,7 +687,9 @@ bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) { IPolyMeshSchema::Sample sample; @@ -673,6 +732,8 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, /* Only read point data when streaming meshes, unless we need to create new ones. */ ImportSettings settings; settings.read_flag |= read_flag; + settings.velocity_name = velocity_name; + settings.velocity_scale = velocity_scale; if (topology_changed(existing_mesh, sample_sel)) { new_mesh = BKE_mesh_new_nomain_from_template( @@ -829,6 +890,13 @@ static void read_subd_sample(const std::string &iobject_full_name, if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); } + + if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { + V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); + if (velocities) { + read_velocity(velocities, config, settings->velocity_scale); + } + } } /* ************************************************************************** */ @@ -876,7 +944,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); if (read_mesh != mesh) { BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } @@ -935,7 +1003,9 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) { ISubDSchema::Sample sample; @@ -962,6 +1032,8 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, ImportSettings settings; settings.read_flag |= read_flag; + settings.velocity_name = velocity_name; + settings.velocity_scale = velocity_scale; if (existing_mesh->totvert != positions->size()) { new_mesh = BKE_mesh_new_nomain_from_template( diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index 3329b723b77..d9f89cc8085 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -42,7 +42,9 @@ class AbcMeshReader : public AbcObjectReader { struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str) override; bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel) override; @@ -73,7 +75,9 @@ class AbcSubDReader : public AbcObjectReader { void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); }; diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index 9a5ffd04bd1..a6d46c4b42a 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -167,6 +167,8 @@ Imath::M44d get_matrix(const IXformSchema &schema, const float time) struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), int UNUSED(read_flag), + const char *UNUSED(velocity_name), + const float UNUSED(velocity_scale), const char **UNUSED(err_str)) { return existing_mesh; diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h index 89590b26b61..6e97f841a88 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.h +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -50,6 +50,10 @@ struct ImportSettings { /* From MeshSeqCacheModifierData.read_flag */ int read_flag; + /* From CacheFile and MeshSeqCacheModifierData */ + std::string velocity_name; + float velocity_scale; + bool validate_meshes; bool always_add_cache_reader; @@ -65,6 +69,8 @@ struct ImportSettings { sequence_len(1), sequence_offset(0), read_flag(0), + velocity_name(""), + velocity_scale(1.0f), validate_meshes(false), always_add_cache_reader(false), cache_file(NULL) @@ -143,7 +149,9 @@ class AbcObjectReader { virtual struct Mesh *read_mesh(struct Mesh *mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, + const int read_flag, + const char *velocity_name, + const float velocity_scale, const char **err_str); virtual bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel); diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index deb945b767c..63565b902f3 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -786,7 +786,9 @@ Mesh *ABC_read_mesh(CacheReader *reader, Mesh *existing_mesh, const float time, const char **err_str, - int read_flag) + const int read_flag, + const char *velocity_name, + const float velocity_scale) { AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); if (abc_reader == nullptr) { @@ -794,7 +796,8 @@ Mesh *ABC_read_mesh(CacheReader *reader, } ISampleSelector sample_sel = sample_selector_for_time(time); - return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str); + return abc_reader->read_mesh( + existing_mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str); } bool ABC_mesh_topology_changed( @@ -860,136 +863,3 @@ CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle, return reinterpret_cast<CacheReader *>(abc_reader); } - -/* ************************************************************************** */ - -static const PropertyHeader *get_property_header(const IPolyMeshSchema &schema, const char *name) -{ - const PropertyHeader *prop_header = schema.getPropertyHeader(name); - - if (prop_header) { - return prop_header; - } - - ICompoundProperty prop = schema.getArbGeomParams(); - - if (!has_property(prop, name)) { - return nullptr; - } - - return prop.getPropertyHeader(name); -} - -bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name) -{ - AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); - - if (!abc_reader) { - return false; - } - - IObject iobject = abc_reader->iobject(); - - if (!iobject.valid()) { - return false; - } - - const ObjectHeader &header = iobject.getHeader(); - - if (!IPolyMesh::matches(header)) { - return false; - } - - IPolyMesh mesh(iobject, kWrapExisting); - IPolyMeshSchema schema = mesh.getSchema(); - - const PropertyHeader *prop_header = get_property_header(schema, name); - - if (!prop_header) { - return false; - } - - return IV3fArrayProperty::matches(prop_header->getMetaData()); -} - -static V3fArraySamplePtr get_velocity_prop(const IPolyMeshSchema &schema, - const ISampleSelector &iss, - const std::string &name) -{ - const PropertyHeader *prop_header = schema.getPropertyHeader(name); - - if (prop_header) { - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0); - return velocity_prop.getValue(iss); - } - - ICompoundProperty prop = schema.getArbGeomParams(); - - if (!has_property(prop, name)) { - return V3fArraySamplePtr(); - } - - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0); - - if (velocity_prop) { - return velocity_prop.getValue(iss); - } - - return V3fArraySamplePtr(); -} - -int ABC_read_velocity_cache(CacheReader *reader, - const char *velocity_name, - const float time, - float velocity_scale, - int num_vertices, - float *r_vertex_velocities) -{ - AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); - - if (!abc_reader) { - return -1; - } - - IObject iobject = abc_reader->iobject(); - - if (!iobject.valid()) { - return -1; - } - - const ObjectHeader &header = iobject.getHeader(); - - if (!IPolyMesh::matches(header)) { - return -1; - } - - IPolyMesh mesh(iobject, kWrapExisting); - IPolyMeshSchema schema = mesh.getSchema(); - ISampleSelector sample_sel(time); - const IPolyMeshSchema::Sample sample = schema.getValue(sample_sel); - - V3fArraySamplePtr velocities = get_velocity_prop(schema, sample_sel, velocity_name); - - if (!velocities) { - return -1; - } - - float vel[3]; - - int num_velocity_vectors = static_cast<int>(velocities->size()); - - if (num_velocity_vectors != num_vertices) { - return -1; - } - - for (size_t i = 0; i < velocities->size(); ++i) { - const Imath::V3f &vel_in = (*velocities)[i]; - copy_zup_from_yup(vel, vel_in.getValue()); - - mul_v3_fl(vel, velocity_scale); - - copy_v3_v3(r_vertex_velocities + i * 3, vel); - } - - return num_vertices; -} diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp index 82d8b6e9ff3..005197c9027 100644 --- a/source/blender/io/collada/BCAnimationCurve.cpp +++ b/source/blender/io/collada/BCAnimationCurve.cpp @@ -173,10 +173,9 @@ std::string BCAnimationCurve::get_animation_name(Object *ob) const name = ""; } else { - char *boneName = BLI_str_quoted_substrN(fcurve->rna_path, "pose.bones["); - if (boneName) { + char boneName[MAXBONENAME]; + if (BLI_str_quoted_substr(fcurve->rna_path, "pose.bones[", boneName, sizeof(boneName))) { name = id_name(ob) + "_" + std::string(boneName); - MEM_freeN(boneName); } else { name = ""; diff --git a/source/blender/io/collada/BCAnimationSampler.cpp b/source/blender/io/collada/BCAnimationSampler.cpp index d2bde193c0a..953af5adffc 100644 --- a/source/blender/io/collada/BCAnimationSampler.cpp +++ b/source/blender/io/collada/BCAnimationSampler.cpp @@ -447,10 +447,9 @@ void BCAnimationSampler::initialize_curves(BCAnimationCurveMap &curves, Object * for (; fcu; fcu = fcu->next) { object_type = BC_ANIMATION_TYPE_OBJECT; if (ob->type == OB_ARMATURE) { - char *boneName = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); - if (boneName) { + char boneName[MAXBONENAME]; + if (BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", boneName, sizeof(boneName))) { object_type = BC_ANIMATION_TYPE_BONE; - MEM_freeN(boneName); } } diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc index 31ecf27cf7e..12de1d82c72 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.cc +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -46,7 +46,7 @@ void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTim { curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); - curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc deleted file mode 100644 index e645b0237b9..00000000000 --- a/source/blender/io/usd/intern/usd_reader_instance.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2021 Blender Foundation. - * All rights reserved. - */ - -#include "usd_reader_instance.h" - -#include "BKE_object.h" -#include "DNA_object_types.h" - -#include <iostream> - -namespace blender::io::usd { - -USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim, - const USDImportParams &import_params, - const ImportSettings &settings) - : USDXformReader(prim, import_params, settings) -{ -} - -bool USDInstanceReader::valid() const -{ - return prim_.IsValid() && prim_.IsInstance(); -} - -void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */) -{ - this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); - this->object_->data = nullptr; - this->object_->transflag |= OB_DUPLICOLLECTION; -} - -void USDInstanceReader::set_instance_collection(Collection *coll) -{ - if (this->object_) { - this->object_->instance_collection = coll; - } -} - -pxr::SdfPath USDInstanceReader::proto_path() const -{ - if (pxr::UsdPrim master = prim_.GetMaster()) { - return master.GetPath(); - } - - return pxr::SdfPath(); -} - -} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc index 9b30b524729..d6977d9c91a 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.cc +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -62,7 +62,7 @@ void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime { curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); - curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 233b3d9da4d..8c4cc18a9af 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -20,7 +20,6 @@ #include "usd_reader_stage.h" #include "usd_reader_camera.h" #include "usd_reader_curve.h" -#include "usd_reader_instance.h" #include "usd_reader_light.h" #include "usd_reader_mesh.h" #include "usd_reader_nurbs.h" @@ -34,6 +33,7 @@ #include <pxr/usd/usdGeom/mesh.h> #include <pxr/usd/usdGeom/nurbsCurves.h> #include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdGeom/xform.h> #include <pxr/usd/usdLux/light.h> #include <iostream> diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index 54316e56867..61b14155dd0 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -26,6 +26,7 @@ #include "BLI_assert.h" #include "BLI_math_vector.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -219,7 +220,7 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) if (usd_export_context_.export_params.export_normals) { write_normals(mesh, usd_mesh); } - write_surface_velocity(context.object, mesh, usd_mesh); + write_surface_velocity(mesh, usd_mesh); /* TODO(Sybren): figure out what happens when the face groups change. */ if (frame_has_been_written_) { @@ -409,42 +410,25 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_ usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying); } -void USDGenericMeshWriter::write_surface_velocity(Object *object, - const Mesh *mesh, - pxr::UsdGeomMesh usd_mesh) +void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) { - /* Only velocities from the fluid simulation are exported. This is the most important case, - * though, as the baked mesh changes topology all the time, and thus computing the velocities - * at import time in a post-processing step is hard. */ - ModifierData *md = BKE_modifiers_findby_type(object, eModifierType_Fluidsim); - if (md == nullptr) { - return; - } + /* Export velocity attribute output by fluid sim, sequence cache modifier + * and geometry nodes. */ + CustomDataLayer *velocity_layer = BKE_id_attribute_find( + &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT); - /* Check that the fluid sim modifier is enabled and has useful data. */ - const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER); - const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; - const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph); - if (!BKE_modifier_is_enabled(scene, md, required_mode)) { - return; - } - FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); - if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) { - return; - } - FluidsimSettings *fss = fsmd->fss; - if (!fss->meshVelocities) { + if (velocity_layer == nullptr) { return; } + const float(*velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data); + /* Export per-vertex velocity vectors. */ pxr::VtVec3fArray usd_velocities; usd_velocities.reserve(mesh->totvert); - FluidVertexVelocity *mesh_velocities = fss->meshVelocities; - for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; - ++vertex_idx, ++mesh_velocities) { - usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel)); + for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; ++vertex_idx) { + usd_velocities.push_back(pxr::GfVec3f(velocities[vertex_idx])); } pxr::UsdTimeCode timecode = get_export_time_code(); diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h index 6345f2d4240..d60a6c4a59b 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.h +++ b/source/blender/io/usd/intern/usd_writer_mesh.h @@ -49,7 +49,7 @@ class USDGenericMeshWriter : public USDAbstractWriter { const MaterialFaceGroups &usd_face_groups); void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); - void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); + void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); }; class USDMeshWriter : public USDGenericMeshWriter { diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 4fba8e4d727..2f838513bf0 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -305,8 +305,6 @@ typedef enum eGp_Vertex_Mode { typedef enum eGP_Sculpt_Flag { /* invert the effect of the brush */ GP_SCULPT_FLAG_INVERT = (1 << 0), - /* smooth brush affects pressure values as well */ - GP_SCULPT_FLAG_SMOOTH_PRESSURE = (1 << 2), /* temporary invert action */ GP_SCULPT_FLAG_TMP_INVERT = (1 << 3), } eGP_Sculpt_Flag; diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h index 520fc6c1b00..a2433dbbbbd 100644 --- a/source/blender/makesdna/DNA_curve_types.h +++ b/source/blender/makesdna/DNA_curve_types.h @@ -302,8 +302,11 @@ typedef struct Curve { float fsize_realtime; /** - * A pointer to curve data from geometry nodes, currently only set for evaluated - * objects by the dependency graph iterator, and owned by #geometry_set_eval. + * A pointer to curve data from evaluation. Owned by the object's #geometry_set_eval, either as a + * geometry instance or the data of the evaluated #CurveComponent. The curve may also contain + * data in the #nurb list, but for evaluated curves this is the proper place to retrieve data, + * since it also contains the result of geometry nodes evaluation, and isn't just a copy of the + * original object data. */ struct CurveEval *curve_eval; @@ -344,8 +347,7 @@ enum { CU_DS_EXPAND = 1 << 11, /** make use of the path radius if this is enabled (default for new curves) */ CU_PATH_RADIUS = 1 << 12, - /** fill 2d curve after deformation */ - CU_DEFORM_FILL = 1 << 13, + /* CU_DEFORM_FILL = 1 << 13, */ /* DEPRECATED */ /** fill bevel caps */ CU_FILL_CAPS = 1 << 14, /** map taper object to beveled area */ diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 19e9aace7c3..9c57f74a332 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -31,6 +31,8 @@ extern "C" { #endif +struct AnonymousAttributeID; + /** Descriptor and storage for a custom data layer. */ typedef struct CustomDataLayer { /** Type of data in layer. */ @@ -53,6 +55,13 @@ typedef struct CustomDataLayer { char name[64]; /** Layer data. */ void *data; + /** + * Run-time identifier for this layer. If no one has a strong reference to this id anymore, + * the layer can be removed. The custom data layer only has a weak reference to the id, because + * otherwise there will always be a strong reference and the attribute can't be removed + * automatically. + */ + const struct AnonymousAttributeID *anonymous_id; } CustomDataLayer; #define MAX_CUSTOMDATA_LAYER_NAME 64 diff --git a/source/blender/makesdna/DNA_fluid_defaults.h b/source/blender/makesdna/DNA_fluid_defaults.h index 95f5b8b66b0..4135c4d40a8 100644 --- a/source/blender/makesdna/DNA_fluid_defaults.h +++ b/source/blender/makesdna/DNA_fluid_defaults.h @@ -50,7 +50,6 @@ .tex_flags = NULL, \ .tex_range_field = NULL, \ .guide_parent = NULL, \ - .mesh_velocities = NULL, \ .effector_weights = NULL, /* #BKE_effector_add_weights. */ \ .p0 = {0.0f, 0.0f, 0.0f}, \ .p1 = {0.0f, 0.0f, 0.0f}, \ @@ -122,7 +121,6 @@ .mesh_smoothen_pos = 1, \ .mesh_smoothen_neg = 1, \ .mesh_scale = 2, \ - .totvert = 0, \ .mesh_generator = FLUID_DOMAIN_MESH_IMPROVED, \ .particle_type = 0, \ .particle_scale = 1, \ diff --git a/source/blender/makesdna/DNA_fluid_types.h b/source/blender/makesdna/DNA_fluid_types.h index 835af3c6ff8..0cbef540306 100644 --- a/source/blender/makesdna/DNA_fluid_types.h +++ b/source/blender/makesdna/DNA_fluid_types.h @@ -480,10 +480,6 @@ enum { SM_HRES_FULLSAMPLE = 2, }; -typedef struct FluidDomainVertexVelocity { - float vel[3]; -} FluidDomainVertexVelocity; - typedef struct FluidDomainSettings { /* -- Runtime-only fields (from here on). -- */ @@ -509,8 +505,6 @@ typedef struct FluidDomainSettings { struct GPUTexture *tex_flags; struct GPUTexture *tex_range_field; struct Object *guide_parent; - /** Vertex velocities of simulated fluid mesh. */ - struct FluidDomainVertexVelocity *mesh_velocities; struct EffectorWeights *effector_weights; /* Domain object data. */ @@ -607,9 +601,8 @@ typedef struct FluidDomainSettings { int mesh_smoothen_pos; int mesh_smoothen_neg; int mesh_scale; - int totvert; short mesh_generator; - char _pad6[6]; /* Unused. */ + char _pad6[2]; /* Unused. */ /* Secondary particle options. */ int particle_type; diff --git a/source/blender/makesdna/DNA_freestyle_types.h b/source/blender/makesdna/DNA_freestyle_types.h index 4d4fbaed29a..ab1e7aa903c 100644 --- a/source/blender/makesdna/DNA_freestyle_types.h +++ b/source/blender/makesdna/DNA_freestyle_types.h @@ -40,7 +40,7 @@ enum { FREESTYLE_RIDGES_AND_VALLEYS_FLAG = 1 << 1, FREESTYLE_MATERIAL_BOUNDARIES_FLAG = 1 << 2, FREESTYLE_FACE_SMOOTHNESS_FLAG = 1 << 3, - FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4, + /* FREESTYLE_ADVANCED_OPTIONS_FLAG = 1 << 4, */ /* UNUSED */ FREESTYLE_CULLING = 1 << 5, FREESTYLE_VIEW_MAP_CACHE = 1 << 6, FREESTYLE_AS_RENDER_PASS = 1 << 7, diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 81e9abc4916..450527c7443 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -304,7 +304,7 @@ .opacity = 1.0f, \ .flags = LRT_GPENCIL_MATCH_OUTPUT_VGROUP, \ .crease_threshold = DEG2RAD(140.0f), \ - .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES, \ + .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES | LRT_USE_CREASE_ON_SHARP_EDGES, \ .angle_splitting_threshold = DEG2RAD(60.0f), \ .chaining_image_threshold = 0.001f, \ .overscan = 0.1f,\ @@ -319,5 +319,23 @@ .material = NULL,\ } +#define _DNA_DEFAULT_DashGpencilModifierData \ + { \ + .dash_offset = 0, \ + .segments = NULL, \ + .segments_len = 1, \ + .segment_active_index = 0, \ + } + +#define _DNA_DEFAULT_DashGpencilModifierSegment \ + { \ + .name = "", \ + .dash = 2, \ + .gap = 1, \ + .radius = 1.0f, \ + .opacity = 1.0f, \ + .mat_nr = -1, \ + } + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index c91afa58cb1..d3429329ef6 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -56,6 +56,7 @@ typedef enum GpencilModifierType { eGpencilModifierType_Lineart = 19, eGpencilModifierType_Length = 20, eGpencilModifierType_Weight = 21, + eGpencilModifierType_Dash = 22, /* Keep last. */ NUM_GREASEPENCIL_MODIFIER_TYPES, } GpencilModifierType; @@ -507,6 +508,39 @@ typedef enum eLengthGpencil_Type { GP_LENGTH_ABSOLUTE = 1, } eLengthGpencil_Type; +typedef struct DashGpencilModifierSegment { + char name[64]; + /* For path reference. */ + struct DashGpencilModifierData *dmd; + int dash; + int gap; + float radius; + float opacity; + int mat_nr; + int _pad; +} DashGpencilModifierSegment; + +typedef struct DashGpencilModifierData { + GpencilModifierData modifier; + /** Material for filtering. */ + struct Material *material; + /** Layer name. */ + char layername[64]; + /** Custom index for passes. */ + int pass_index; + /** Flags. */ + int flag; + /** Custom index for passes. */ + int layer_pass; + + int dash_offset; + + DashGpencilModifierSegment *segments; + int segments_len; + int segment_active_index; + +} DashGpencilModifierData; + typedef struct MirrorGpencilModifierData { GpencilModifierData modifier; struct Object *object; diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h index 22408687daf..30ca9540735 100644 --- a/source/blender/makesdna/DNA_image_types.h +++ b/source/blender/makesdna/DNA_image_types.h @@ -94,11 +94,17 @@ typedef struct RenderSlot { struct RenderResult *render; } RenderSlot; -typedef struct ImageTile_Runtime { +typedef struct ImageTile_RuntimeTextureSlot { int tilearray_layer; int _pad; int tilearray_offset[2]; int tilearray_size[2]; +} ImageTile_RuntimeTextureSlot; + +typedef struct ImageTile_Runtime { + /* Data per `eImageTextureResolution`. + * Should match `IMA_TEXTURE_RESOLUTION_LEN` */ + ImageTile_RuntimeTextureSlot slots[2]; } ImageTile_Runtime; typedef struct ImageTile { @@ -132,6 +138,15 @@ typedef enum eGPUTextureTarget { TEXTARGET_COUNT, } eGPUTextureTarget; +/* Resolution variations that can be cached for an image. */ +typedef enum eImageTextureResolution { + IMA_TEXTURE_RESOLUTION_FULL = 0, + IMA_TEXTURE_RESOLUTION_LIMITED, + + /* Not an option, but holds the number of options defined for this struct. */ + IMA_TEXTURE_RESOLUTION_LEN +} eImageTextureResolution; + typedef struct Image { ID id; @@ -140,8 +155,8 @@ typedef struct Image { /** Not written in file. */ struct MovieCache *cache; - /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes. */ - struct GPUTexture *gputexture[3][2]; + /** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes, 2 = IMA_TEXTURE_RESOLUTION_LEN. */ + struct GPUTexture *gputexture[3][2][2]; /* sources from: */ ListBase anims; @@ -233,12 +248,15 @@ enum { IMA_GPU_PARTIAL_REFRESH = (1 << 1), /** All mipmap levels in OpenGL texture set? */ IMA_GPU_MIPMAP_COMPLETE = (1 << 2), - /** Current texture resolution won't be limited by the GL Texture Limit user preference. */ - IMA_GPU_MAX_RESOLUTION = (1 << 3), + /* Reuse the max resolution textures as they fit in the limited scale. */ + IMA_GPU_REUSE_MAX_RESOLUTION = (1 << 3), + /* Has any limited scale textures been allocated. + * Adds additional checks to reuse max resolution images when they fit inside limited scale. */ + IMA_GPU_HAS_LIMITED_SCALE_TEXTURES = (1 << 4), }; /* Image.source, where the image comes from */ -enum { +typedef enum eImageSource { /* IMA_SRC_CHECK = 0, */ /* UNUSED */ IMA_SRC_FILE = 1, IMA_SRC_SEQUENCE = 2, @@ -246,10 +264,10 @@ enum { IMA_SRC_GENERATED = 4, IMA_SRC_VIEWER = 5, IMA_SRC_TILED = 6, -}; +} eImageSource; /* Image.type, how to handle or generate the image */ -enum { +typedef enum eImageType { IMA_TYPE_IMAGE = 0, IMA_TYPE_MULTILAYER = 1, /* generated */ @@ -257,7 +275,7 @@ enum { /* viewers */ IMA_TYPE_R_RESULT = 4, IMA_TYPE_COMPOSITE = 5, -}; +} eImageType; /* Image.gen_type */ enum { diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h index cdb09c3af50..bdc9bcb6980 100644 --- a/source/blender/makesdna/DNA_lineart_types.h +++ b/source/blender/makesdna/DNA_lineart_types.h @@ -47,6 +47,8 @@ typedef enum eLineartMainFlags { LRT_CHAIN_LOOSE_EDGES = (1 << 12), LRT_CHAIN_GEOMETRY_SPACE = (1 << 13), LRT_ALLOW_OVERLAP_EDGE_TYPES = (1 << 14), + LRT_USE_CREASE_ON_SMOOTH_SURFACES = (1 << 15), + LRT_USE_CREASE_ON_SHARP_EDGES = (1 << 16), } eLineartMainFlags; typedef enum eLineartEdgeFlag { diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index 1b3dbd148df..5b2694f420b 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -419,10 +419,6 @@ .velocity_scale = 1.0f, \ .reader = NULL, \ .reader_object_path = "", \ - .vertex_velocities = NULL, \ - .num_vertices = 0, \ - .velocity_delta = 0.0f, \ - .last_lookup_time = 0.0f, \ } #define _DNA_DEFAULT_MirrorModifierData \ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index c883c201ed8..17794295eb9 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -1940,6 +1940,7 @@ typedef struct MeshCacheModifierData { float factor; char deform_mode; + char defgrp_name[64]; char _pad[7]; /* play_mode == MOD_MESHCACHE_PLAY_CFEA */ @@ -1956,6 +1957,11 @@ typedef struct MeshCacheModifierData { char filepath[1024]; } MeshCacheModifierData; +/* MeshCache modifier flags. */ +enum { + MOD_MESHCACHE_INVERT_VERTEX_GROUP = 1 << 0, +}; + enum { MOD_MESHCACHE_TYPE_MDD = 1, MOD_MESHCACHE_TYPE_PC2 = 2, @@ -2136,10 +2142,6 @@ enum { MOD_NORMALEDIT_MIX_MUL = 3, }; -typedef struct MeshCacheVertexVelocity { - float vel[3]; -} MeshCacheVertexVelocity; - typedef struct MeshSeqCacheModifierData { ModifierData modifier; @@ -2155,25 +2157,6 @@ typedef struct MeshSeqCacheModifierData { /* Runtime. */ struct CacheReader *reader; char reader_object_path[1024]; - - /* Vertex velocities read from the cache. The velocities are not automatically read during - * modifier execution, and therefore have to manually be read when needed. This is only used - * through the RNA for now. */ - struct MeshCacheVertexVelocity *vertex_velocities; - - /* The number of vertices of the Alembic mesh, set when the modifier is executed. */ - int num_vertices; - - /* Time (in frames or seconds) between two velocity samples. Automatically computed to - * scale the velocity vectors at render time for generating proper motion blur data. */ - float velocity_delta; - - /* Caches the scene time (in seconds) used to lookup data in the Alembic archive when the - * modifier was last executed. Used to access Alembic samples through the RNA. */ - float last_lookup_time; - - int _pad1; - void *_pad2; } MeshSeqCacheModifierData; /* MeshSeqCacheModifierData.read_flag */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index de0f82de3c5..94520d59eea 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -216,6 +216,16 @@ typedef enum eNodeSocketFlag { SOCK_HIDE_LABEL = (1 << 12), } eNodeSocketFlag; +/** Workaround to forward-declare C++ type in C header. */ +#ifdef __cplusplus +namespace blender::nodes { +class NodeDeclaration; +} +using NodeDeclarationHandle = blender::nodes::NodeDeclaration; +#else +typedef struct NodeDeclarationHandle NodeDeclarationHandle; +#endif + /* TODO: Limit data in bNode to what we want to see saved. */ typedef struct bNode { struct bNode *next, *prev, *new_node; @@ -315,6 +325,26 @@ typedef struct bNode { * needs to be a float to feed GPU_uniform. */ float sss_id; + + /** + * Describes the desired interface of the node. This is run-time data only. + * The actual interface of the node may deviate from the declaration temporarily. + * It's possible to sync the actual state of the node to the desired state. Currently, this is + * only done when a node is created or loaded. + * + * In the future, we may want to keep more data only in the declaration, so that it does not have + * to be synced to other places that are stored in files. That especially applies to data that + * can't be edited by users directly (e.g. min/max values of sockets, tooltips, ...). + * + * The declaration of a node can be recreated at any time when it is used. Caching it here is + * just a bit more efficient when it is used a lot. To make sure that the cache is up-to-date, + * call #nodeDeclarationEnsure before using it. + * + * Currently, the declaration is the same for every node of the same type. Going forward, that is + * intended to change though. Especially when nodes become more dynamic with respect to how many + * sockets they have. + */ + NodeDeclarationHandle *declaration; } bNode; /* node->flag */ @@ -1441,6 +1471,13 @@ typedef struct NodeGeometryCurveFill { uint8_t mode; } NodeGeometryCurveFill; +typedef struct NodeGeometryAttributeCapture { + /* CustomDataType. */ + int8_t data_type; + /* AttributeDomain. */ + int8_t domain; +} NodeGeometryAttributeCapture; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1889,6 +1926,7 @@ typedef enum GeometryNodeTriangulateQuads { typedef enum GeometryNodePointInstanceType { GEO_NODE_POINT_INSTANCE_TYPE_OBJECT = 0, GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION = 1, + GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY = 2, } GeometryNodePointInstanceType; typedef enum GeometryNodePointInstanceFlag { diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 0250d853898..5a88ce7c9f5 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -158,8 +158,7 @@ typedef struct Object_Runtime { struct ID *data_orig; /** * Object data structure created during object evaluation. It has all modifiers applied. - * The type is determined by the type of the original object. For example, for mesh and curve - * objects, this is a mesh. For a volume object, this is a volume. + * The type is determined by the type of the original object. */ struct ID *data_eval; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 863c53615c1..5475e1bacd8 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -913,6 +913,13 @@ enum eFileDetails { #define FILE_MAX_LIBEXTRA (FILE_MAX + MAX_ID_NAME) +/** + * Maximum level of recursions accepted for #FileSelectParams.recursion_level. Rather than a + * completely arbitrary limit or none at all, make it just enough to support the most extreme case + * where the maximal path length is used with single letter directory/file names only. + */ +#define FILE_SELECT_MAX_RECURSIONS (FILE_MAX_LIBEXTRA / 2) + /* filesel types */ typedef enum eFileSelectType { FILE_LOADLIB = 1, @@ -936,13 +943,13 @@ typedef enum eFileSel_Action { * (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h). */ typedef enum eFileSel_Params_Flag { - FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), /* cleared */ + FILE_APPEND_SET_FAKEUSER = (1 << 0), FILE_RELPATH = (1 << 1), FILE_LINK = (1 << 2), FILE_HIDE_DOT = (1 << 3), FILE_AUTOSELECT = (1 << 4), FILE_ACTIVE_COLLECTION = (1 << 5), - FILE_PARAMS_FLAG_UNUSED_6 = (1 << 6), /* cleared */ + FILE_APPEND_RECURSIVE = (1 << 6), FILE_DIRSEL_ONLY = (1 << 7), FILE_FILTER = (1 << 8), FILE_OBDATA_INSTANCE = (1 << 9), diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 0d75376d94a..89779253717 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -647,7 +647,8 @@ typedef struct UserDef_Experimental { char use_extended_asset_browser; char use_override_templates; char use_sculpt_uvsmooth; - char _pad[4]; + char use_geometry_nodes_fields; + char _pad[3]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; @@ -925,9 +926,10 @@ typedef struct UserDef { short sequencer_proxy_setup; /* eUserpref_SeqProxySetup */ float collection_instance_empty_size; - char _pad10[3]; + char _pad10[2]; - char statusbar_flag; /* eUserpref_StatusBar_Flag */ + char file_preview_type; /* eUserpref_File_Preview_Type */ + char statusbar_flag; /* eUserpref_StatusBar_Flag */ struct WalkNavigation walk_navigation; @@ -997,7 +999,7 @@ typedef enum eUserPref_Flag { USER_NONUMPAD = (1 << 13), USER_ADD_CURSORALIGNED = (1 << 14), USER_FILECOMPRESS = (1 << 15), - USER_SAVE_PREVIEWS = (1 << 16), + USER_FLAG_UNUSED_5 = (1 << 16), /* dirty */ USER_CUSTOM_RANGE = (1 << 17), USER_ADD_EDITMODE = (1 << 18), USER_ADD_VIEWALIGNED = (1 << 19), @@ -1011,6 +1013,13 @@ typedef enum eUserPref_Flag { USER_FLAG_UNUSED_27 = (1 << 27), /* dirty */ } eUserPref_Flag; +/** #UserDef.file_preview_type */ +typedef enum eUserpref_File_Preview_Type { + USER_FILE_PREVIEW_NONE = 0, + USER_FILE_PREVIEW_SCREENSHOT, + USER_FILE_PREVIEW_CAMERA, +} eUserpref_File_Preview_Type; + typedef enum eUserPref_PrefFlag { USER_PREF_FLAG_SAVE = (1 << 0), } eUserPref_PrefFlag; @@ -1127,7 +1136,9 @@ typedef enum eUserpref_TableAPI { /** #UserDef.app_flag */ typedef enum eUserpref_APP_Flag { - USER_APP_LOCK_UI_LAYOUT = (1 << 0), + USER_APP_LOCK_CORNER_SPLIT = (1 << 0), + USER_APP_HIDE_REGION_TOGGLE = (1 << 1), + USER_APP_LOCK_EDGE_RESIZE = (1 << 2), } eUserpref_APP_Flag; /** #UserDef.statusbar_flag */ diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index a573e2f9e8c..4cb8610f6ac 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -68,6 +68,8 @@ * #BLO_update_defaults_startup_blend & #blo_do_versions_userdef. */ +#define DNA_DEPRECATED_ALLOW + #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -319,6 +321,8 @@ SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment); #undef SDNA_DEFAULT_DECL_STRUCT @@ -547,6 +551,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(WeightGpencilModifierData), SDNA_DEFAULT_DECL(LineartGpencilModifierData), SDNA_DEFAULT_DECL(LengthGpencilModifierData), + SDNA_DEFAULT_DECL(DashGpencilModifierData), + SDNA_DEFAULT_DECL(DashGpencilModifierSegment), }; #undef SDNA_DEFAULT_DECL #undef SDNA_DEFAULT_DECL_EX diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 76155973982..ce53e3390e1 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -177,6 +177,7 @@ extern StructRNA RNA_CompositorNodeMixRGB; extern StructRNA RNA_CompositorNodeNormal; extern StructRNA RNA_CompositorNodeNormalize; extern StructRNA RNA_CompositorNodeOutputFile; +extern StructRNA RNA_CompositorNodePosterize; extern StructRNA RNA_CompositorNodePremulKey; extern StructRNA RNA_CompositorNodeRGB; extern StructRNA RNA_CompositorNodeRGBToBW; @@ -221,6 +222,8 @@ extern StructRNA RNA_CurvePoint; extern StructRNA RNA_CurveProfile; extern StructRNA RNA_CurveProfilePoint; extern StructRNA RNA_DampedTrackConstraint; +extern StructRNA RNA_DashGpencilModifierData; +extern StructRNA RNA_DashGpencilModifierSegment; extern StructRNA RNA_DataTransferModifier; extern StructRNA RNA_DecimateModifier; extern StructRNA RNA_Depsgraph; @@ -1117,13 +1120,17 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop); char *RNA_path_append( const char *path, PointerRNA *ptr, PropertyRNA *prop, int intkey, const char *strkey); +#if 0 /* UNUSED. */ char *RNA_path_back(const char *path); +#endif /* path_resolve() variants only ensure that a valid pointer (and optionally property) exist */ bool RNA_path_resolve(PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop); bool RNA_path_resolve_full( PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index); +bool RNA_path_resolve_full_maybe_null( + PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index); /* path_resolve_property() variants ensure that pointer + property both exist */ bool RNA_path_resolve_property(PointerRNA *ptr, diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 8f8ad077935..eb887e1881b 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -931,11 +931,10 @@ static void rna_ID_user_remap(ID *id, Main *bmain, ID *new_id) static struct ID *rna_ID_make_local(struct ID *self, Main *bmain, bool clear_proxy) { - BKE_lib_id_make_local( - bmain, self, false, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BKE_lib_id_make_local(bmain, self, clear_proxy ? 0 : LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); ID *ret_id = self->newid ? self->newid : self; - BKE_id_clear_newpoin(self); + BKE_id_newptr_and_tag_clear(self); return ret_id; } diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 51e20eb9e7f..fceb6d045c3 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -4859,75 +4859,119 @@ PointerRNA rna_array_lookup_int( /* RNA Path - Experiment */ -static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen, int bracket) +/** + * Extract the first token from `path`. + * + * \param path: Extract the token from path, step the pointer to the beginning of the next token + * \return The nil terminated token. + */ +static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen) { - const char *p; int len = 0; - if (bracket) { - /* get data between [], check escaping quotes and back-slashes with #BLI_str_unescape. */ - if (**path == '[') { - (*path)++; - } - else { - return NULL; - } + /* Get data until `.` or `[`. */ + const char *p = *path; + while (*p && !ELEM(*p, '.', '[')) { + len++; + p++; + } - p = *path; + /* Empty, return. */ + if (UNLIKELY(len == 0)) { + return NULL; + } - /* 2 kinds of look-ups now, quoted or unquoted. */ - if (*p != '"') { - while (*p && (*p != ']')) { - len++; - p++; - } - } - else { - const char *p_end = BLI_str_escape_find_quote(p + 1); - if (p_end == NULL) { - /* No Matching quote. */ - return NULL; - } - /* Skip the last quoted char to get the `]`. */ - p_end += 1; + /* Try to use fixed buffer if possible. */ + char *buf = (len + 1 < fixedlen) ? fixedbuf : MEM_mallocN(sizeof(char) * (len + 1), __func__); + memcpy(buf, *path, sizeof(char) * len); + buf[len] = '\0'; - len += (p_end - p); - p = p_end; - } + if (*p == '.') { + p++; + } + *path = p; - if (*p != ']') { + return buf; +} + +/** + * Extract the first token in brackets from `path` (with quoted text support). + * + * - `[0]` -> `0` + * - `["Some\"Quote"]` -> `Some"Quote` + * + * \param path: Extract the token from path, step the pointer to the beginning of the next token + * (past quoted text and brackets). + * \return The nil terminated token. + */ +static char *rna_path_token_in_brackets(const char **path, + char *fixedbuf, + int fixedlen, + bool *r_quoted) +{ + int len = 0; + bool quoted = false; + + BLI_assert(r_quoted != NULL); + + /* Get data between `[]`, check escaping quotes and back-slashes with #BLI_str_unescape. */ + if (UNLIKELY(**path != '[')) { + return NULL; + } + + (*path)++; + const char *p = *path; + + /* 2 kinds of look-ups now, quoted or unquoted. */ + if (*p == '"') { + /* Find the matching quote. */ + (*path)++; + p = *path; + const char *p_end = BLI_str_escape_find_quote(p); + if (p_end == NULL) { + /* No Matching quote. */ return NULL; } + /* Exclude the last quote from the length. */ + len += (p_end - p); + + /* Skip the last quoted char to get the `]`. */ + p_end += 1; + p = p_end; + quoted = true; } else { - /* Get data until `.` or `[`. */ - p = *path; - - while (*p && *p != '.' && *p != '[') { + /* Find the matching bracket. */ + while (*p && (*p != ']')) { len++; p++; } } - /* empty, return */ - if (len == 0) { + if (UNLIKELY(*p != ']')) { + return NULL; + } + + /* Empty, return. */ + if (UNLIKELY(len == 0)) { return NULL; } /* Try to use fixed buffer if possible. */ char *buf = (len + 1 < fixedlen) ? fixedbuf : MEM_mallocN(sizeof(char) * (len + 1), __func__); - /* copy string, taking into account escaped ] */ - if (bracket) { + /* Copy string, taking into account escaped ']' */ + if (quoted) { BLI_str_unescape(buf, *path, len); - p = (*path) + len; + /* +1 to step over the last quote. */ + BLI_assert((*path)[len] == '"'); + p = (*path) + len + 1; } else { memcpy(buf, *path, sizeof(char) * len); buf[len] = '\0'; } - - /* set path to start of next token */ + /* Set path to start of next token. */ if (*p == ']') { p++; } @@ -4936,20 +4980,9 @@ static char *rna_path_token(const char **path, char *fixedbuf, int fixedlen, int } *path = p; - return buf; -} + *r_quoted = quoted; -static int rna_token_strip_quotes(char *token) -{ - if (token[0] == '"') { - int len = strlen(token); - if (len >= 2 && token[len - 1] == '"') { - /* strip away "" */ - token[len - 1] = '\0'; - return 1; - } - } - return 0; + return buf; } static bool rna_path_parse_collection_key(const char **path, @@ -4968,18 +5001,19 @@ static bool rna_path_parse_collection_key(const char **path, } if (**path == '[') { + bool quoted; char *token; /* resolve the lookup with [] brackets */ - token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 1); + token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), "ed); if (!token) { return false; } /* check for "" to see if it is a string */ - if (rna_token_strip_quotes(token)) { - if (RNA_property_collection_lookup_string(ptr, prop, token + 1, r_nextptr)) { + if (quoted) { + if (RNA_property_collection_lookup_string(ptr, prop, token, r_nextptr)) { /* pass */ } else { @@ -5041,15 +5075,16 @@ static bool rna_path_parse_array_index(const char **path, /* multi index resolve */ if (**path == '[') { - token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 1); + bool quoted; + token = rna_path_token_in_brackets(path, fixedbuf, sizeof(fixedbuf), "ed); if (token == NULL) { /* invalid syntax blah[] */ return false; } /* check for "" to see if it is a string */ - if (rna_token_strip_quotes(token)) { - temp_index = RNA_property_array_item_index(prop, *(token + 1)); + if (quoted) { + temp_index = RNA_property_array_item_index(prop, *token); } else { /* otherwise do int lookup */ @@ -5066,7 +5101,7 @@ static bool rna_path_parse_array_index(const char **path, } else if (dim == 1) { /* location.x || scale.X, single dimension arrays only */ - token = rna_path_token(path, fixedbuf, sizeof(fixedbuf), 0); + token = rna_path_token(path, fixedbuf, sizeof(fixedbuf)); if (token == NULL) { /* invalid syntax blah. */ return false; @@ -5166,8 +5201,7 @@ static bool rna_path_parse(PointerRNA *ptr, RNA_POINTER_INVALIDATE(&nextptr); } - int use_id_prop = (*path == '[') ? 1 : 0; - char *token; + const bool use_id_prop = (*path == '['); /* custom property lookup ? * C.object["someprop"] */ @@ -5177,8 +5211,10 @@ static bool rna_path_parse(PointerRNA *ptr, } /* look up property name in current struct */ - token = rna_path_token(&path, fixedbuf, sizeof(fixedbuf), use_id_prop); - + bool quoted = false; + char *token = use_id_prop ? + rna_path_token_in_brackets(&path, fixedbuf, sizeof(fixedbuf), "ed) : + rna_path_token(&path, fixedbuf, sizeof(fixedbuf)); if (!token) { return false; } @@ -5186,8 +5222,8 @@ static bool rna_path_parse(PointerRNA *ptr, prop = NULL; if (use_id_prop) { /* look up property name in current struct */ IDProperty *group = RNA_struct_idprops(&curptr, 0); - if (group && rna_token_strip_quotes(token)) { - prop = (PropertyRNA *)IDP_GetPropertyFromGroup(group, token + 1); + if (group && quoted) { + prop = (PropertyRNA *)IDP_GetPropertyFromGroup(group, token); } } else { @@ -5326,6 +5362,18 @@ bool RNA_path_resolve_full( } /** + * A version of #RNA_path_resolve_full doesn't check the value of #PointerRNA.data. + * + * \note While it's correct to ignore the value of #PointerRNA.data + * most callers need to know if the resulting pointer was found and not null. + */ +bool RNA_path_resolve_full_maybe_null( + PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index) +{ + return rna_path_parse(ptr, path, r_ptr, r_prop, r_index, NULL, NULL, true); +} + +/** * Resolve the given RNA Path to find both the pointer AND property * indicated by fully resolving the path. * @@ -5473,7 +5521,9 @@ char *RNA_path_append( return result; } -char *RNA_path_back(const char *path) +/* Having both path append & back seems like it could be useful, + * this function isn't used at the moment. */ +static UNUSED_FUNCTION_WITH_RETURN_TYPE(char *, RNA_path_back)(const char *path) { char fixedbuf[256]; const char *previous, *current; @@ -5492,7 +5542,7 @@ char *RNA_path_back(const char *path) while (*current) { char *token; - token = rna_path_token(¤t, fixedbuf, sizeof(fixedbuf), 0); + token = rna_path_token(¤t, fixedbuf, sizeof(fixedbuf)); if (!token) { return NULL; @@ -5502,7 +5552,8 @@ char *RNA_path_back(const char *path) } /* in case of collection we also need to strip off [] */ - token = rna_path_token(¤t, fixedbuf, sizeof(fixedbuf), 1); + bool quoted; + token = rna_path_token_in_brackets(¤t, fixedbuf, sizeof(fixedbuf), "ed); if (token && token != fixedbuf) { MEM_freeN(token); } diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c index 2c552970c82..f8a36c1b2e6 100644 --- a/source/blender/makesrna/intern/rna_access_compare_override.c +++ b/source/blender/makesrna/intern/rna_access_compare_override.c @@ -788,7 +788,7 @@ bool RNA_struct_override_matches(Main *bmain, continue; } - CLOG_INFO(&LOG, 5, "Override Checking %s\n", rna_path); + CLOG_INFO(&LOG, 5, "Override Checking %s", rna_path); IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path); if (ignore_overridden && op != NULL) { diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index b4ea70c33ab..49e813e6a6c 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -41,8 +41,8 @@ const EnumPropertyItem rna_enum_attribute_type_items[] = { {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"}, {CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"}, {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"}, - {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"}, - {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"}, + {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"}, + {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"}, {CD_PROP_STRING, "STRING", 0, "String", "Text string"}, {CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"}, {CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"}, @@ -54,8 +54,8 @@ const EnumPropertyItem rna_enum_attribute_type_with_auto_items[] = { {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"}, {CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"}, {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"}, - {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"}, - {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"}, + {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point values"}, + {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit values"}, {CD_PROP_STRING, "STRING", 0, "String", "Text string"}, {CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"}, {CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"}, @@ -443,7 +443,7 @@ static void rna_def_attribute_float_vector(BlenderRNA *brna) srna = RNA_def_struct(brna, "FloatVectorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point precision"); + srna, "Float Vector Attribute", "Vector geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "FloatVectorAttributeValue"); @@ -479,7 +479,7 @@ static void rna_def_attribute_float_color(BlenderRNA *brna) srna = RNA_def_struct(brna, "FloatColorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float Color Attribute", "Color geometry attribute, with floating-point precision"); + srna, "Float Color Attribute", "Color geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "FloatColorAttributeValue"); @@ -514,7 +514,7 @@ static void rna_def_attribute_byte_color(BlenderRNA *brna) srna = RNA_def_struct(brna, "ByteColorAttribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit precision"); + srna, "Byte Color Attribute", "Color geometry attribute, with 8-bit values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "ByteColorAttributeValue"); @@ -639,7 +639,7 @@ static void rna_def_attribute_float2(BlenderRNA *brna) srna = RNA_def_struct(brna, "Float2Attribute", "Attribute"); RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text( - srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point precision"); + srna, "Float2 Attribute", "2D vector geometry attribute, with floating-point values"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "Float2AttributeValue"); diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index bf9a8d3d817..165f22bca7b 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -1969,13 +1969,6 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Trim Stroke Ends", "Trim intersecting stroke ends"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - prop = RNA_def_property(srna, "use_edit_pressure", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "sculpt_flag", GP_SCULPT_FLAG_SMOOTH_PRESSURE); - RNA_def_property_ui_text( - prop, "Affect Pressure", "Affect pressure values as well when smoothing strokes"); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, NULL, "sculpt_flag"); RNA_def_property_enum_items(prop, prop_direction_items); diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c index 1ac6dd021e9..5c12fc3a227 100644 --- a/source/blender/makesrna/intern/rna_color.c +++ b/source/blender/makesrna/intern/rna_color.c @@ -189,7 +189,7 @@ static char *rna_ColorRamp_path(PointerRNA *ptr) SH_NODE_VALTORGB, CMP_NODE_VALTORGB, TEX_NODE_VALTORGB, - GEO_NODE_ATTRIBUTE_COLOR_RAMP)) { + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) { if (node->storage == ptr->data) { /* all node color ramp properties called 'color_ramp' * prepend path from ID to the node @@ -320,7 +320,7 @@ static void rna_ColorRamp_update(Main *bmain, Scene *UNUSED(scene), PointerRNA * SH_NODE_VALTORGB, CMP_NODE_VALTORGB, TEX_NODE_VALTORGB, - GEO_NODE_ATTRIBUTE_COLOR_RAMP)) { + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) { ED_node_tag_update_nodetree(bmain, ntree, node); } } diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c index 9c6659a7130..0bfb1200f49 100644 --- a/source/blender/makesrna/intern/rna_curve.c +++ b/source/blender/makesrna/intern/rna_curve.c @@ -1809,12 +1809,6 @@ static void rna_def_curve(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Twist Smooth", "Smoothing iteration for tangents"); RNA_def_property_update(prop, 0, "rna_Curve_update_data"); - prop = RNA_def_property(srna, "use_fill_deform", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_DEFORM_FILL); - RNA_def_property_ui_text( - prop, "Fill Deformed", "Fill curve after applying shape keys and all modifiers"); - RNA_def_property_update(prop, 0, "rna_Curve_update_data"); - prop = RNA_def_property(srna, "use_fill_caps", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", CU_FILL_CAPS); RNA_def_property_ui_text(prop, "Fill Caps", "Fill caps for beveled curves"); diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c index 10e899b7ee3..90e77406f23 100644 --- a/source/blender/makesrna/intern/rna_fluid.c +++ b/source/blender/makesrna/intern/rna_fluid.c @@ -1242,22 +1242,6 @@ static void rna_Fluid_flowtype_set(struct PointerRNA *ptr, int value) #else -static void rna_def_fluid_mesh_vertices(BlenderRNA *brna) -{ - StructRNA *srna; - PropertyRNA *prop; - - srna = RNA_def_struct(brna, "FluidDomainVertexVelocity", NULL); - RNA_def_struct_ui_text(srna, "Fluid Mesh Velocity", "Velocity of a simulated fluid mesh"); - RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL); - - prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY); - RNA_def_property_array(prop, 3); - RNA_def_property_float_sdna(prop, NULL, "vel"); - RNA_def_property_ui_text(prop, "Velocity", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); -} - static void rna_def_fluid_domain_settings(BlenderRNA *brna) { StructRNA *srna; @@ -2019,14 +2003,6 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Mesh generator", "Which particle level set generator to use"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Fluid_update"); - prop = RNA_def_property(srna, "mesh_vertices", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "mesh_velocities", "totvert"); - RNA_def_property_struct_type(prop, "FluidDomainVertexVelocity"); - RNA_def_property_ui_text( - prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation"); - - rna_def_fluid_mesh_vertices(brna); - prop = RNA_def_property(srna, "use_mesh", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", FLUID_DOMAIN_USE_MESH); RNA_def_property_ui_text(prop, "Use Mesh", "Enable fluid mesh (using amplification)"); diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 4e95174e42b..4fa33424994 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -35,6 +35,7 @@ #include "BLI_math.h" #include "BLI_rand.h" +#include "BLI_string_utils.h" #include "BLT_translation.h" @@ -68,9 +69,14 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_BUILD, "Build", "Create duplication of strokes"}, + {eGpencilModifierType_Dash, + "GP_DASH", + ICON_MOD_DASH, + "Dot Dash", + "Generate dot-dash styled strokes"}, {eGpencilModifierType_Lineart, "GP_LINEART", - ICON_MOD_EDGESPLIT, /* TODO: Use a proper icon. */ + ICON_MOD_LINEART, "Line Art", "Generate line art strokes from selected source"}, {eGpencilModifierType_Mirror, @@ -116,7 +122,7 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { "Deform strokes using lattice"}, {eGpencilModifierType_Length, "GP_LENGTH", - ICON_MOD_EDGESPLIT, + ICON_MOD_LENGTH, "Length", "Extend or shrink strokes"}, {eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"}, @@ -268,6 +274,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr) return &RNA_TextureGpencilModifier; case eGpencilModifierType_Lineart: return &RNA_LineartGpencilModifier; + case eGpencilModifierType_Dash: + return &RNA_DashGpencilModifierData; /* Default */ case eGpencilModifierType_None: case NUM_GREASEPENCIL_MODIFIER_TYPES: @@ -282,19 +290,19 @@ static void rna_GpencilModifier_name_set(PointerRNA *ptr, const char *value) GpencilModifierData *gmd = ptr->data; char oldname[sizeof(gmd->name)]; - /* make a copy of the old name first */ + /* Make a copy of the old name first. */ BLI_strncpy(oldname, gmd->name, sizeof(gmd->name)); - /* copy the new name into the name slot */ + /* Copy the new name into the name slot. */ BLI_strncpy_utf8(gmd->name, value, sizeof(gmd->name)); - /* make sure the name is truly unique */ + /* Make sure the name is truly unique. */ if (ptr->owner_id) { Object *ob = (Object *)ptr->owner_id; BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, gmd); } - /* fix all the animation data which may link to this */ + /* Fix all the animation data which may link to this. */ BKE_animdata_fix_paths_rename_all(NULL, "grease_pencil_modifiers", oldname, gmd->name); } @@ -674,6 +682,59 @@ static void rna_Lineart_end_level_set(PointerRNA *ptr, int value) lmd->level_start = MIN2(value, lmd->level_start); } +static void rna_GpencilDash_segments_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + DashGpencilModifierData *dmd = (DashGpencilModifierData *)ptr->data; + rna_iterator_array_begin( + iter, dmd->segments, sizeof(DashGpencilModifierSegment), dmd->segments_len, false, NULL); +} + +static char *rna_DashGpencilModifierSegment_path(PointerRNA *ptr) +{ + DashGpencilModifierSegment *ds = (DashGpencilModifierSegment *)ptr->data; + + DashGpencilModifierData *dmd = (DashGpencilModifierData *)ds->dmd; + + BLI_assert(dmd != NULL); + + char name_esc[sizeof(dmd->modifier.name) * 2 + 1]; + + BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc)); + + return BLI_sprintfN("grease_pencil_modifiers[\"%s\"].segments[\"%s\"]", name_esc, ds->name); +} + +static bool dash_segment_name_exists_fn(void *arg, const char *name) +{ + const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg; + for (int i = 0; i < dmd->segments_len; i++) { + if (STREQ(dmd->segments[i].name, name)) { + return true; + } + } + return false; +} + +static void rna_DashGpencilModifierSegment_name_set(PointerRNA *ptr, const char *value) +{ + DashGpencilModifierSegment *ds = ptr->data; + + char oldname[sizeof(ds->name)]; + BLI_strncpy(oldname, ds->name, sizeof(ds->name)); + + BLI_strncpy_utf8(ds->name, value, sizeof(ds->name)); + + BLI_assert(ds->dmd != NULL); + BLI_uniquename_cb( + dash_segment_name_exists_fn, ds->dmd, "Segment", '.', ds->name, sizeof(ds->name)); + + char prefix[256]; + sprintf(prefix, "grease_pencil_modifiers[\"%s\"].segments", ds->dmd->modifier.name); + + /* Fix all the animation data which may link to this. */ + BKE_animdata_fix_paths_rename_all(NULL, prefix, oldname, ds->name); +} + #else static void rna_def_modifier_gpencilnoise(BlenderRNA *brna) @@ -2902,7 +2963,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_struct_ui_text( srna, "Line Art Modifier", "Generate line art strokes from selected source"); RNA_def_struct_sdna(srna, "LineartGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + RNA_def_struct_ui_icon(srna, ICON_MOD_LINEART); RNA_define_lib_overridable(true); @@ -3188,6 +3249,18 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Masks", "Mask bits to match from Collection Line Art settings"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_crease_on_smooth", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SMOOTH_SURFACES); + RNA_def_property_ui_text( + prop, "Crease On Smooth Surfaces", "Allow crease edges to show inside smooth surfaces"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_crease_on_sharp", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_USE_CREASE_ON_SHARP_EDGES); + RNA_def_property_ui_text(prop, "Crease On Sharp Edges", "Allow crease to show on sharp edges"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + RNA_define_lib_overridable(false); } @@ -3199,7 +3272,7 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) srna = RNA_def_struct(brna, "LengthGpencilModifier", "GpencilModifier"); RNA_def_struct_ui_text(srna, "Length Modifier", "Stretch or shrink strokes"); RNA_def_struct_sdna(srna, "LengthGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + RNA_def_struct_ui_icon(srna, ICON_MOD_LENGTH); RNA_define_lib_overridable(true); @@ -3275,6 +3348,136 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_gpencildash(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "DashGpencilModifierSegment", NULL); + RNA_def_struct_ui_text(srna, "Dash Modifier Segment", "Configuration for a single dash segment"); + RNA_def_struct_sdna(srna, "DashGpencilModifierSegment"); + RNA_def_struct_path_func(srna, "rna_DashGpencilModifierSegment_path"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", "Name of the dash segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_DashGpencilModifierSegment_name_set"); + RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER | NA_RENAME, NULL); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "dash", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 1, INT16_MAX); + RNA_def_property_ui_text( + prop, + "Dash", + "The number of consecutive points from the original stroke to include in this segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "gap", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 1, INT16_MAX); + RNA_def_property_ui_text(prop, "Gap", "The number of points skipped after this segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_FACTOR | PROP_UNSIGNED); + RNA_def_property_ui_range(prop, 0, 1, 0.1, 2); + RNA_def_property_ui_text( + prop, "Radius", "The factor to apply to the original point's radius for the new points"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_ui_range(prop, 0, 1, 0.1, 2); + RNA_def_property_ui_text( + prop, "Opacity", "The factor to apply to the original point's opacity for the new points"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "mat_nr"); + RNA_def_property_range(prop, -1, INT16_MAX); + RNA_def_property_ui_text( + prop, + "Material Index", + "Use this index on generated segment. -1 means using the existing material"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + srna = RNA_def_struct(brna, "DashGpencilModifierData", "GpencilModifier"); + RNA_def_struct_ui_text(srna, "Dash Modifier", "Create dot-dash effect for strokes"); + RNA_def_struct_sdna(srna, "DashGpencilModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_DASH); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "DashGpencilModifierSegment"); + RNA_def_property_collection_sdna(prop, NULL, "segments", NULL); + RNA_def_property_collection_funcs(prop, + "rna_GpencilDash_segments_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + NULL, + NULL, + NULL, + NULL); + RNA_def_property_ui_text(prop, "Segments", ""); + + prop = RNA_def_property(srna, "segment_active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Active Dash Segement Index", "Active index in the segment list"); + + prop = RNA_def_property(srna, "dash_offset", PROP_INT, PROP_NONE); + RNA_def_property_ui_text( + prop, + "Offset", + "Offset into each stroke before the beginning of the dashed segment generation"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + /* Common properties. */ + + prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "layername"); + RNA_def_property_ui_text(prop, "Layer", "Layer name"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "pass_index"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYER); + RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_MATERIAL); + RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_PASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "layer_pass"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Layer pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYERPASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + RNA_define_lib_overridable(false); +} + void RNA_def_greasepencil_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -3352,6 +3555,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna) rna_def_modifier_gpencilweight(brna); rna_def_modifier_gpencillineart(brna); rna_def_modifier_gpencillength(brna); + rna_def_modifier_gpencildash(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index e44ddb07d53..4a013dc9bd7 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -404,7 +404,7 @@ static void rna_Image_resolution_set(PointerRNA *ptr, const float *values) static int rna_Image_bindcode_get(PointerRNA *ptr) { Image *ima = (Image *)ptr->data; - GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0]; + GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0][IMA_TEXTURE_RESOLUTION_FULL]; return (tex) ? GPU_texture_opengl_bindcode(tex) : 0; } diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index ee44e71042c..3708a7dc637 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -755,6 +755,7 @@ RNA_MOD_VGROUP_NAME_SET(LaplacianDeform, anchor_grp_name); RNA_MOD_VGROUP_NAME_SET(LaplacianSmooth, defgrp_name); RNA_MOD_VGROUP_NAME_SET(Lattice, name); RNA_MOD_VGROUP_NAME_SET(Mask, vgroup); +RNA_MOD_VGROUP_NAME_SET(MeshCache, defgrp_name); RNA_MOD_VGROUP_NAME_SET(MeshDeform, defgrp_name); RNA_MOD_VGROUP_NAME_SET(NormalEdit, defgrp_name); RNA_MOD_VGROUP_NAME_SET(Shrinkwrap, vgroup_name); @@ -1600,51 +1601,6 @@ static bool rna_Modifier_show_expanded_get(PointerRNA *ptr) return md->ui_expand_flag & UI_PANEL_DATA_EXPAND_ROOT; } -static int rna_MeshSequenceCacheModifier_has_velocity_get(PointerRNA *ptr) -{ -# ifdef WITH_ALEMBIC - MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data; - return ABC_has_vec3_array_property_named(mcmd->reader, mcmd->cache_file->velocity_name); -# else - return false; - UNUSED_VARS(ptr); -# endif -} - -static int rna_MeshSequenceCacheModifier_read_velocity_get(PointerRNA *ptr) -{ -# ifdef WITH_ALEMBIC - MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data; - - if (mcmd->num_vertices == 0) { - return 0; - } - - if (mcmd->vertex_velocities) { - MEM_freeN(mcmd->vertex_velocities); - } - - mcmd->vertex_velocities = MEM_mallocN(sizeof(MeshCacheVertexVelocity) * mcmd->num_vertices, - "Mesh Cache Velocities"); - - int num_read = ABC_read_velocity_cache(mcmd->reader, - mcmd->cache_file->velocity_name, - mcmd->last_lookup_time, - mcmd->velocity_scale * mcmd->velocity_delta, - mcmd->num_vertices, - (float *)mcmd->vertex_velocities); - - if (num_read == -1 || num_read != mcmd->num_vertices) { - return false; - } - - return true; -# else - return false; - UNUSED_VARS(ptr); -# endif -} - static bool rna_NodesModifier_node_group_poll(PointerRNA *UNUSED(ptr), PointerRNA value) { bNodeTree *ntree = value.data; @@ -6059,6 +6015,20 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Influence", "Influence of the deformation"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); + prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "defgrp_name"); + RNA_def_property_ui_text( + prop, + "Vertex Group", + "Name of the Vertex Group which determines the influence of the modifier per point"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_MeshCacheModifier_defgrp_name_set"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_MESHCACHE_INVERT_VERTEX_GROUP); + RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + /* -------------------------------------------------------------------- */ /* Axis Conversion */ prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE); @@ -6117,22 +6087,6 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna) RNA_define_lib_overridable(false); } -static void rna_def_mesh_cache_velocities(BlenderRNA *brna) -{ - StructRNA *srna; - PropertyRNA *prop; - - srna = RNA_def_struct(brna, "MeshCacheVertexVelocity", NULL); - RNA_def_struct_ui_text(srna, "Mesh Cache Velocity", "Velocity attribute of an Alembic mesh"); - RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL); - - prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY); - RNA_def_property_array(prop, 3); - RNA_def_property_float_sdna(prop, NULL, "vel"); - RNA_def_property_ui_text(prop, "Velocity", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); -} - static void rna_def_modifier_meshseqcache(BlenderRNA *brna) { StructRNA *srna; @@ -6189,26 +6143,6 @@ static void rna_def_modifier_meshseqcache(BlenderRNA *brna) "Multiplier used to control the magnitude of the velocity vectors for time effects"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); - /* -------------------------- Velocity Vectors -------------------------- */ - - prop = RNA_def_property(srna, "vertex_velocities", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "vertex_velocities", "num_vertices"); - RNA_def_property_struct_type(prop, "MeshCacheVertexVelocity"); - RNA_def_property_ui_text( - prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation"); - - rna_def_mesh_cache_velocities(brna); - - prop = RNA_def_property(srna, "has_velocity", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_ui_text(prop, "Has Velocity Cache", ""); - RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_has_velocity_get", NULL); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); - - prop = RNA_def_property(srna, "read_velocity", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_ui_text(prop, "Read Velocity Cache", ""); - RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_read_velocity_get", NULL); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); - RNA_define_lib_overridable(false); } diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 9e24a36915e..86e134799aa 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9295,6 +9295,11 @@ static void def_geo_point_instance(StructRNA *srna) ICON_NONE, "Collection", "Instance an entire collection on all points"}, + {GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY, + "GEOMETRY", + ICON_NONE, + "Geometry", + "Copy geometry to all points"}, {0, NULL, 0, NULL, NULL}, }; @@ -10086,6 +10091,12 @@ static void def_geo_curve_resample(StructRNA *srna) PropertyRNA *prop; static EnumPropertyItem mode_items[] = { + {GEO_NODE_CURVE_SAMPLE_EVALUATED, + "EVALUATED", + 0, + "Evaluated", + "Output the input spline's evaluated points, based on the resolution attribute for NURBS " + "and Bezier splines. Poly splines are unchanged"}, {GEO_NODE_CURVE_SAMPLE_COUNT, "COUNT", 0, @@ -10279,6 +10290,26 @@ static void def_geo_curve_fill(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_attribute_capture(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); + RNA_def_property_ui_text(prop, "Domain", "Which domain to store the data in"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index d3cd3158db1..99865078cbe 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -224,6 +224,12 @@ static EnumPropertyItem instance_items_empty[] = { INSTANCE_ITEM_COLLECTION, {0, NULL, 0, NULL, NULL}, }; + +static EnumPropertyItem instance_items_font[] = { + {0, "NONE", 0, "None", ""}, + {OB_DUPLIVERTS, "VERTS", 0, "Vertices", "Use Object Font on characters"}, + {0, NULL, 0, NULL, NULL}, +}; #endif #undef INSTANCE_ITEMS_SHARED #undef INSTANCE_ITEM_COLLECTION @@ -762,6 +768,9 @@ static const EnumPropertyItem *rna_Object_instance_type_itemf(bContext *UNUSED(C else if (ob->type == OB_POINTCLOUD) { item = instance_items_pointcloud; } + else if (ob->type == OB_FONT) { + item = instance_items_font; + } else { item = instance_items_nogroup; } diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 0ba2652f185..6c5ae1f3300 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2845,7 +2845,7 @@ static void rna_def_tool_settings(BlenderRNA *brna) "IMAGE", ICON_IMAGE_DATA, "Image", - "Strick stroke to the image"}, + "Stick stroke to the image"}, /* Weird, GP_PROJECT_VIEWALIGN is inverted. */ {0, "VIEW", ICON_RESTRICT_VIEW_ON, "View", "Stick stroke to the view"}, {0, NULL, 0, NULL, NULL}, @@ -4610,12 +4610,12 @@ void rna_def_freestyle_settings(BlenderRNA *brna) {FREESTYLE_CONTROL_SCRIPT_MODE, "SCRIPT", 0, - "Python Scripting Mode", + "Python Scripting", "Advanced mode for using style modules written in Python"}, {FREESTYLE_CONTROL_EDITOR_MODE, "EDITOR", 0, - "Parameter Editor Mode", + "Parameter Editor", "Basic mode for interactive style parameter editing"}, {0, NULL, 0, NULL, NULL}, }; @@ -4626,7 +4626,7 @@ void rna_def_freestyle_settings(BlenderRNA *brna) {FREESTYLE_QI_RANGE, "RANGE", 0, - "QI Range", + "Quantitative Invisibility", "Select feature edges within a range of quantitative invisibility (QI) values"}, {0, NULL, 0, NULL, NULL}, }; @@ -4943,14 +4943,6 @@ void rna_def_freestyle_settings(BlenderRNA *brna) prop, "Face Smoothness", "Take face smoothness into account in view map calculation"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); - prop = RNA_def_property(srna, "use_advanced_options", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_ADVANCED_OPTIONS_FLAG); - RNA_def_property_ui_text( - prop, - "Advanced Options", - "Enable advanced edge detection options (sphere radius and Kr derivative epsilon)"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); - prop = RNA_def_property(srna, "use_view_map_cache", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_VIEW_MAP_CACHE); RNA_def_property_ui_text( @@ -4970,11 +4962,13 @@ void rna_def_freestyle_settings(BlenderRNA *brna) prop = RNA_def_property(srna, "sphere_radius", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "sphere_radius"); + RNA_def_property_float_default(prop, 1.0); RNA_def_property_range(prop, 0.0, 1000.0); RNA_def_property_ui_text(prop, "Sphere Radius", "Sphere radius for computing curvatures"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update"); prop = RNA_def_property(srna, "kr_derivative_epsilon", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_default(prop, 0.0); RNA_def_property_float_sdna(prop, NULL, "dkr_epsilon"); RNA_def_property_range(prop, -1000.0, 1000.0); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index d9837f21833..cd87e4d10c1 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -845,6 +845,17 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, Pointer DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); } +static void rna_Sequence_pan_range( + PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax) +{ + Scene *scene = (Scene *)ptr->owner_id; + + *min = -FLT_MAX; + *max = FLT_MAX; + *softmax = 1 + (int)(scene->r.ffcodecdata.audio_channels > 2); + *softmin = -*softmax; +} + static int rna_Sequence_input_count_get(PointerRNA *ptr) { Sequence *seq = (Sequence *)(ptr->data); @@ -2559,8 +2570,10 @@ static void rna_def_sound(BlenderRNA *brna) prop = RNA_def_property(srna, "pan", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "pan"); - RNA_def_property_range(prop, -2.0f, 2.0f); + RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); + RNA_def_property_ui_range(prop, -2, 2, 1, 2); RNA_def_property_ui_text(prop, "Pan", "Playback panning of the sound (only for Mono sources)"); + RNA_def_property_float_funcs(prop, NULL, NULL, "rna_Sequence_pan_range"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update"); prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index d391b09189a..e3985b26eb0 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -1796,6 +1796,16 @@ static const EnumPropertyItem *rna_SpaceImageEditor_pivot_itemf(bContext *UNUSED } } +static void rna_SpaceUVEditor_tile_grid_shape_set(PointerRNA *ptr, const int *values) +{ + SpaceImage *data = (SpaceImage *)(ptr->data); + + int clamp[2] = {10, 100}; + for (int i = 0; i < 2; i++) { + data->tile_grid_shape[i] = CLAMPIS(values[i], 1, clamp[i]); + } +} + /* Space Text Editor */ static void rna_SpaceTextEditor_word_wrap_set(PointerRNA *ptr, bool value) @@ -3417,7 +3427,8 @@ static void rna_def_space_image_uv(BlenderRNA *brna) RNA_def_property_int_sdna(prop, NULL, "tile_grid_shape"); RNA_def_property_array(prop, 2); RNA_def_property_int_default(prop, 1); - RNA_def_property_range(prop, 1, 10); + RNA_def_property_range(prop, 1, 100); + RNA_def_property_int_funcs(prop, NULL, "rna_SpaceUVEditor_tile_grid_shape_set", NULL); RNA_def_property_ui_text( prop, "Tile Grid Shape", "How many tiles will be shown in the background"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); @@ -4930,18 +4941,19 @@ static void rna_def_space_view3d(BlenderRNA *brna) prop = RNA_def_property(srna, "lock_rotation", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_LOCK_ROTATION); - RNA_def_property_ui_text(prop, "Lock", "Lock view rotation in side views"); + RNA_def_property_ui_text( + prop, "Lock Rotation", "Lock view rotation of side views to Top/Front/Right"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update"); prop = RNA_def_property(srna, "show_sync_view", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXVIEW); - RNA_def_property_ui_text(prop, "Box", "Sync view position between side views"); + RNA_def_property_ui_text(prop, "Sync Zoom/Pan", "Sync view position between side views"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update"); prop = RNA_def_property(srna, "use_box_clip", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_BOXCLIP); RNA_def_property_ui_text( - prop, "Clip", "Clip objects based on what's visible in other side views"); + prop, "Clip Contents", "Clip view contents based on what is visible in other side views"); RNA_def_property_update( prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_clip_update"); diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index e06cc39a88b..f96b3fc5eee 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -622,6 +622,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, PointerRNA *active_dataptr, const char *active_propname, int filter_id_types, + int display_flags, const char *activate_opname, PointerRNA *r_activate_op_properties, const char *drag_opname, @@ -630,6 +631,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, AssetFilterSettings filter_settings = { .id_types = filter_id_types ? filter_id_types : FILTER_ID_ALL, }; + uiTemplateAssetView(layout, C, list_id, @@ -640,6 +642,7 @@ static void rna_uiTemplateAssetView(uiLayout *layout, active_dataptr, active_propname, &filter_settings, + display_flags, activate_opname, r_activate_op_properties, drag_opname, @@ -878,6 +881,25 @@ void RNA_api_ui_layout(StructRNA *srna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem asset_view_template_options[] = { + {UI_TEMPLATE_ASSET_DRAW_NO_NAMES, + "NO_NAMES", + 0, + "", + "Do not display the name of each asset underneath preview images"}, + {UI_TEMPLATE_ASSET_DRAW_NO_FILTER, + "NO_FILTER", + 0, + "", + "Do not display buttons for filtering the available assets"}, + {UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY, + "NO_LIBRARY", + 0, + "", + "Do not display buttons to choose or refresh an asset library"}, + {0, NULL, 0, NULL, NULL}, + }; + static float node_socket_color_default[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* simple layout specifiers */ @@ -1839,6 +1861,12 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_property_enum_items(parm, DummyRNA_NULL_items); RNA_def_property_enum_funcs(parm, NULL, NULL, "rna_uiTemplateAssetView_filter_id_types_itemf"); RNA_def_property_flag(parm, PROP_ENUM_FLAG); + RNA_def_enum_flag(func, + "display_options", + asset_view_template_options, + 0, + "", + "Displaying options for the asset view"); RNA_def_string(func, "activate_operator", NULL, diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 3e236371750..a731689082c 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -559,6 +559,11 @@ static PointerRNA rna_UserDef_system_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_PreferencesSystem, ptr->data); } +static PointerRNA rna_UserDef_apps_get(PointerRNA *ptr) +{ + return rna_pointer_inherit_refine(ptr, &RNA_PreferencesApps, ptr->data); +} + static void rna_UserDef_audio_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UNUSED(ptr)) { BKE_sound_init(bmain); @@ -4590,12 +4595,6 @@ static void rna_def_userdef_view(BlenderRNA *brna) "Color range used for weight visualization in weight painting mode"); RNA_def_property_update(prop, 0, "rna_UserDef_weight_color_update"); - prop = RNA_def_property(srna, "show_layout_ui", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_UI_LAYOUT); - RNA_def_property_ui_text( - prop, "Editor Corner Splitting", "Split and join editors by dragging from corners"); - RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); - prop = RNA_def_property(srna, "show_navigate_ui", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_SHOW_GIZMO_NAVIGATE); RNA_def_property_ui_text( @@ -6059,6 +6058,13 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem preview_type_items[] = { + {USER_FILE_PREVIEW_NONE, "NONE", 0, "None", "Do not create blend previews"}, + {USER_FILE_PREVIEW_SCREENSHOT, "SCREENSHOT", 0, "Screenshot", "Capture the entire window"}, + {USER_FILE_PREVIEW_CAMERA, "CAMERA", 0, "Camera View", "Workbench render of scene"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "PreferencesFilePaths", NULL); RNA_def_struct_sdna(srna, "UserDef"); RNA_def_struct_nested(brna, srna, "Preferences"); @@ -6066,26 +6072,24 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "File Paths", "Default paths for external files"); prop = RNA_def_property(srna, "show_hidden_files_datablocks", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_DOT); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_DOT); RNA_def_property_ui_text(prop, - "Hide Dot Files/Data-Blocks", - "Hide files and data-blocks if their name start with a dot (.*)"); + "Show Hidden Files/Data-Blocks", + "Show files and data-blocks that are normally hidden"); prop = RNA_def_property(srna, "use_filter_files", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_FILTERFILEEXTS); - RNA_def_property_ui_text(prop, - "Filter File Extensions", - "Display only files with extensions in the image select window"); + RNA_def_property_ui_text(prop, "Filter Files", "Enable filtering of files in the File Browser"); - prop = RNA_def_property(srna, "hide_recent_locations", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT); + prop = RNA_def_property(srna, "show_recent_locations", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_RECENT); RNA_def_property_ui_text( - prop, "Hide Recent Locations", "Hide recent locations in the file selector"); + prop, "Show Recent Locations", "Show Recent locations list in the File Browser"); - prop = RNA_def_property(srna, "hide_system_bookmarks", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS); + prop = RNA_def_property(srna, "show_system_bookmarks", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_HIDE_SYSTEM_BOOKMARKS); RNA_def_property_ui_text( - prop, "Hide System Bookmarks", "Hide system bookmarks in the file selector"); + prop, "Show System Locations", "Show System locations list in the File Browser"); prop = RNA_def_property(srna, "use_relative_paths", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_RELPATHS); @@ -6214,12 +6218,9 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Recent Files", "Maximum number of recently opened files to remember"); - prop = RNA_def_property(srna, "use_save_preview_images", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_SAVE_PREVIEWS); - RNA_def_property_ui_text(prop, - "Save Preview Images", - "Enables automatic saving of preview images in the .blend file " - "as well as a thumbnail of the .blend"); + prop = RNA_def_property(srna, "file_preview_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, preview_type_items); + RNA_def_property_ui_text(prop, "File Preview Type", "What type of blend preview to create"); rna_def_userdef_filepaths_asset_library(brna); @@ -6228,6 +6229,35 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Asset Libraries", ""); } +static void rna_def_userdef_apps(BlenderRNA *brna) +{ + PropertyRNA *prop; + StructRNA *srna; + + srna = RNA_def_struct(brna, "PreferencesApps", NULL); + RNA_def_struct_sdna(srna, "UserDef"); + RNA_def_struct_nested(brna, srna, "Preferences"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Apps", "Preferences that work only for apps"); + + prop = RNA_def_property(srna, "show_corner_split", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_CORNER_SPLIT); + RNA_def_property_ui_text( + prop, "Corner Splitting", "Split and join editors by dragging from corners"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); + + prop = RNA_def_property(srna, "show_edge_resize", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_LOCK_EDGE_RESIZE); + RNA_def_property_ui_text(prop, "Edge Resize", "Resize editors by dragging from the edges"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); + + prop = RNA_def_property(srna, "show_regions_visibility_toggle", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "app_flag", USER_APP_HIDE_REGION_TOGGLE); + RNA_def_property_ui_text( + prop, "Regions Visibility Toggle", "Header and side bars visibility toggles"); + RNA_def_property_update(prop, 0, "rna_userdef_screen_update"); +} + static void rna_def_userdef_experimental(BlenderRNA *brna) { StructRNA *srna; @@ -6300,6 +6330,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) prop = RNA_def_property(srna, "use_sculpt_uvsmooth", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_sculpt_uvsmooth", 1); RNA_def_property_ui_text(prop, "Sculpt UV Smooth", "Enable UV smooth sculpt brush"); + + prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1); + RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop) @@ -6443,6 +6477,12 @@ void RNA_def_userdef(BlenderRNA *brna) RNA_def_property_ui_text( prop, "System & OpenGL", "Graphics driver and operating system settings"); + prop = RNA_def_property(srna, "apps", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "PreferencesApps"); + RNA_def_property_pointer_funcs(prop, "rna_UserDef_apps_get", NULL, NULL, NULL); + RNA_def_property_ui_text(prop, "Apps", "Preferences that work only for apps"); + prop = RNA_def_property(srna, "experimental", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_struct_type(prop, "PreferencesExperimental"); @@ -6504,6 +6544,7 @@ void RNA_def_userdef(BlenderRNA *brna) rna_def_userdef_studiolights(brna); rna_def_userdef_studiolight(brna); rna_def_userdef_pathcompare(brna); + rna_def_userdef_apps(brna); rna_def_userdef_experimental(brna); USERDEF_TAG_DIRTY_PROPERTY_UPDATE_DISABLE; diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.c b/source/blender/makesrna/intern/rna_wm_gizmo.c index febb0e14e07..6a63723d174 100644 --- a/source/blender/makesrna/intern/rna_wm_gizmo.c +++ b/source/blender/makesrna/intern/rna_wm_gizmo.c @@ -1401,7 +1401,12 @@ static void rna_def_gizmogroup(BlenderRNA *brna) "SHOW_MODAL_ALL", 0, "Show Modal All", - "Show all while interacting"}, + "Show all while interacting, as well as this group when another is being interacted with"}, + {WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE, + "EXCLUDE_MODAL", + 0, + "Exclude Modal", + "Show all except this group while interacting"}, {WM_GIZMOGROUPTYPE_TOOL_INIT, "TOOL_INIT", 0, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 6a9c9715994..2f0f11ab56d 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -786,7 +786,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, * TODO: we may need to set other dirty flags as well? */ if (use_recalc_normals) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } if (vgroup_start_cap_remap) { diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index d32ef212ab3..9a22b221852 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -243,7 +243,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index c6bb8ca0670..fa4fbe48f57 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -161,7 +161,7 @@ static Mesh *get_quick_mesh( mul_m4_v3(omat, mv->co); } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } break; @@ -506,7 +506,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } if (result == nullptr) { @@ -541,7 +541,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } } diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c index a344a15b0c1..6cd8d70383d 100644 --- a/source/blender/modifiers/intern/MOD_build.c +++ b/source/blender/modifiers/intern/MOD_build.c @@ -281,7 +281,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, struct MEM_freeN(faceMap); if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } /* TODO(sybren): also copy flags & tags? */ diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c index 6047896ff17..3b71106a4ca 100644 --- a/source/blender/modifiers/intern/MOD_decimate.c +++ b/source/blender/modifiers/intern/MOD_decimate.c @@ -223,7 +223,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * TIMEIT_END(decim); #endif - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index 2e3a1743e7c..7e4befe3b2a 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -116,7 +116,7 @@ Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd) result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc index 306e79aa647..9a8af35109a 100644 --- a/source/blender/modifiers/intern/MOD_mask.cc +++ b/source/blender/modifiers/intern/MOD_mask.cc @@ -814,8 +814,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx) } BKE_mesh_calc_edges_loose(result); - /* Tag to recalculate normals later. */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c index 6ef64ad8bc9..74f9887a973 100644 --- a/source/blender/modifiers/intern/MOD_meshcache.c +++ b/source/blender/modifiers/intern/MOD_meshcache.c @@ -36,8 +36,11 @@ #include "DNA_screen_types.h" #include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" #include "BKE_scene.h" #include "BKE_screen.h" @@ -53,6 +56,7 @@ #include "MOD_meshcache_util.h" /* utility functions */ #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" +#include "MOD_util.h" static void initData(ModifierData *md) { @@ -84,11 +88,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene), static void meshcache_do(MeshCacheModifierData *mcmd, Scene *scene, Object *ob, + Mesh *mesh, float (*vertexCos_Real)[3], int numVerts) { const bool use_factor = mcmd->factor < 1.0f; - float(*vertexCos_Store)[3] = (use_factor || + int influence_group_index; + MDeformVert *dvert; + MOD_get_vgroup(ob, mesh, mcmd->defgrp_name, &dvert, &influence_group_index); + + float(*vertexCos_Store)[3] = (use_factor || influence_group_index != -1 || (mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ? MEM_malloc_arrayN( numVerts, sizeof(*vertexCos_Store), __func__) : @@ -256,7 +265,29 @@ static void meshcache_do(MeshCacheModifierData *mcmd, if (vertexCos_Store) { if (ok) { - if (use_factor) { + if (influence_group_index != -1) { + const float global_factor = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ? + -mcmd->factor : + mcmd->factor; + const float global_offset = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ? + mcmd->factor : + 0.0f; + if (mesh->dvert != NULL) { + for (int i = 0; i < numVerts; i++) { + /* For each vertex, compute its blending factor between the mesh cache (for `fac = 0`) + * and the former position of the vertex (for `fac = 1`). */ + const MDeformVert *currentIndexDVert = dvert + i; + const float local_vertex_fac = global_offset + + BKE_defvert_find_weight(currentIndexDVert, + influence_group_index) * + global_factor; + interp_v3_v3v3( + vertexCos_Real[i], vertexCos_Real[i], vertexCos_Store[i], local_vertex_fac); + } + } + } + else if (use_factor) { + /* Influence_group_index is -1. */ interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, numVerts * 3); } else { @@ -270,34 +301,59 @@ static void meshcache_do(MeshCacheModifierData *mcmd, static void deformVerts(ModifierData *md, const ModifierEvalContext *ctx, - Mesh *UNUSED(mesh), + Mesh *mesh, float (*vertexCos)[3], int numVerts) { MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); - meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); + Mesh *mesh_src = NULL; + + if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') { + /* `mesh_src` is only needed for vertex groups. */ + mesh_src = MOD_deform_mesh_eval_get(ctx->object, NULL, mesh, NULL, numVerts, false, false); + } + meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts); + + if (!ELEM(mesh_src, NULL, mesh)) { + BKE_id_free(NULL, mesh_src); + } } static void deformVertsEM(ModifierData *md, const ModifierEvalContext *ctx, - struct BMEditMesh *UNUSED(editData), - Mesh *UNUSED(mesh), + struct BMEditMesh *editData, + Mesh *mesh, float (*vertexCos)[3], int numVerts) { MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); - meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); + Mesh *mesh_src = NULL; + + if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') { + /* `mesh_src` is only needed for vertex groups. */ + mesh_src = MOD_deform_mesh_eval_get(ctx->object, editData, mesh, NULL, numVerts, false, false); + } + if (mesh_src != NULL) { + BKE_mesh_wrapper_ensure_mdata(mesh_src); + } + + meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts); + + if (!ELEM(mesh_src, NULL, mesh)) { + BKE_id_free(NULL, mesh_src); + } } static void panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *layout = panel->layout; - PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); uiLayoutSetPropSep(layout, true); @@ -307,6 +363,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE); + modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", NULL); modifier_panel_end(layout, ptr); } diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index 259c1cb2417..bcaf294ec8b 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -105,10 +105,6 @@ static void freeData(ModifierData *md) mcmd->reader_object_path[0] = '\0'; BKE_cachefile_reader_free(mcmd->cache_file, &mcmd->reader); } - - if (mcmd->vertex_velocities) { - MEM_freeN(mcmd->vertex_velocities); - } } static bool isDisabled(const struct Scene *UNUSED(scene), @@ -233,11 +229,26 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * Mesh *result = NULL; switch (cache_file->type) { - case CACHEFILE_TYPE_ALEMBIC: + case CACHEFILE_TYPE_ALEMBIC: { # ifdef WITH_ALEMBIC - result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag); + /* Time (in frames or seconds) between two velocity samples. Automatically computed to + * scale the velocity vectors at render time for generating proper motion blur data. */ + float velocity_scale = mcmd->velocity_scale; + if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { + velocity_scale *= FPS; + } + + result = ABC_read_mesh(mcmd->reader, + ctx->object, + mesh, + time, + &err_str, + mcmd->read_flag, + mcmd->cache_file->velocity_name, + velocity_scale); # endif break; + } case CACHEFILE_TYPE_USD: # ifdef WITH_USD result = USD_read_mesh( @@ -248,17 +259,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * break; } - mcmd->velocity_delta = 1.0f; - if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_SECOND) { - mcmd->velocity_delta /= FPS; - } - - mcmd->last_lookup_time = time; - - if (result != NULL) { - mcmd->num_vertices = result->totvert; - } - if (err_str) { BKE_modifier_set_error(ctx->object, md, "%s", err_str); } diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c index 6116cf8146a..7fd90c71c9f 100644 --- a/source/blender/modifiers/intern/MOD_mirror.c +++ b/source/blender/modifiers/intern/MOD_mirror.c @@ -117,7 +117,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = mirrorModifier__doMirror(mmd, ctx->object, mesh); if (result != mesh) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; } diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 9d8630b21e7..3b952e1e649 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -85,8 +85,10 @@ #include "NOD_geometry.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field.hh" #include "FN_multi_function.hh" +using blender::ColorGeometry4f; using blender::destruct_ptr; using blender::float3; using blender::FunctionRef; @@ -329,8 +331,24 @@ static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) ui_data->max = ui_data->soft_max = (double)value->max; ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default"); ui_data->default_array_len = 3; - for (int i = 3; i < 3; i++) { - ui_data->default_array[i] = (double)value->value[i]; + for (const int i : IndexRange(3)) { + ui_data->default_array[i] = double(value->value[i]); + } + return property; + } + case SOCK_RGBA: { + bNodeSocketValueRGBA *value = (bNodeSocketValueRGBA *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.array.len = 4; + idprop.array.type = IDP_FLOAT; + IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier); + copy_v4_v4((float *)IDP_Array(property), value->value); + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property); + ui_data->base.rna_subtype = PROP_COLOR; + ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__); + ui_data->default_array_len = 4; + for (const int i : IndexRange(4)) { + ui_data->default_array[i] = double(value->value[i]); } return property; } @@ -390,6 +408,8 @@ static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDP return property.type == IDP_INT; case SOCK_VECTOR: return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; + case SOCK_RGBA: + return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 4; case SOCK_BOOLEAN: return property.type == IDP_INT; case SOCK_STRING: @@ -410,28 +430,42 @@ static void init_socket_cpp_value_from_property(const IDProperty &property, { switch (socket_value_type) { case SOCK_FLOAT: { + float value = 0.0f; if (property.type == IDP_FLOAT) { - *(float *)r_value = IDP_Float(&property); + value = IDP_Float(&property); } else if (property.type == IDP_DOUBLE) { - *(float *)r_value = (float)IDP_Double(&property); + value = (float)IDP_Double(&property); } + new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value)); break; } case SOCK_INT: { - *(int *)r_value = IDP_Int(&property); + int value = IDP_Int(&property); + new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value)); break; } case SOCK_VECTOR: { - copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + float3 value; + copy_v3_v3(value, (const float *)IDP_Array(&property)); + new (r_value) blender::fn::Field<float3>(blender::fn::make_constant_field(value)); + break; + } + case SOCK_RGBA: { + blender::ColorGeometry4f value; + copy_v4_v4((float *)value, (const float *)IDP_Array(&property)); + new (r_value) blender::fn::Field<ColorGeometry4f>(blender::fn::make_constant_field(value)); break; } case SOCK_BOOLEAN: { - *(bool *)r_value = IDP_Int(&property) != 0; + bool value = IDP_Int(&property) != 0; + new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value)); break; } case SOCK_STRING: { - new (r_value) std::string(IDP_String(&property)); + std::string value = IDP_String(&property); + new (r_value) + blender::fn::Field<std::string>(blender::fn::make_constant_field(std::move(value))); break; } case SOCK_OBJECT: { @@ -1041,9 +1075,10 @@ ModifierTypeInfo modifierType_Nodes = { /* srna */ &RNA_NodesModifier, /* type */ eModifierTypeType_Constructive, /* flags */ - static_cast<ModifierTypeFlag>( - eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode | - eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping), + static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs | + eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode | + eModifierTypeFlag_SupportsMapping), /* icon */ ICON_NODETREE, /* copyData */ copyData, diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index 5646e37707c..f67f7f967c9 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -21,6 +21,8 @@ #include "DEG_depsgraph_query.h" +#include "FN_field.hh" +#include "FN_field_cpp_type.hh" #include "FN_generic_value_map.hh" #include "FN_multi_function.hh" @@ -33,6 +35,9 @@ namespace blender::modifiers::geometry_nodes { using fn::CPPType; +using fn::Field; +using fn::FieldCPPType; +using fn::GField; using fn::GValueMap; using nodes::GeoNodeExecParams; using namespace fn::multi_function_types; @@ -858,11 +863,10 @@ class GeometryNodesEvaluator { const MultiFunction &fn, NodeState &node_state) { - MFContextBuilder fn_context; - MFParamsBuilder fn_params{fn, 1}; LinearAllocator<> &allocator = local_allocators_.local(); /* Prepare the inputs for the multi function. */ + Vector<GField> input_fields; for (const int i : node->inputs().index_range()) { const InputSocketRef &socket_ref = node->input(i); if (!socket_ref.is_available()) { @@ -873,24 +877,12 @@ class GeometryNodesEvaluator { BLI_assert(input_state.was_ready_for_execution); SingleInputValue &single_value = *input_state.value.single; BLI_assert(single_value.value != nullptr); - fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value}); - } - /* Prepare the outputs for the multi function. */ - Vector<GMutablePointer> outputs; - for (const int i : node->outputs().index_range()) { - const OutputSocketRef &socket_ref = node->output(i); - if (!socket_ref.is_available()) { - continue; - } - const CPPType &type = *get_socket_cpp_type(socket_ref); - void *buffer = allocator.allocate(type.size(), type.alignment()); - fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1}); - outputs.append({type, buffer}); + input_fields.append(std::move(*(GField *)single_value.value)); } - fn.call(IndexRange(1), fn_params, fn_context); + auto operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields)); - /* Forward the computed outputs. */ + /* Forward outputs. */ int output_index = 0; for (const int i : node->outputs().index_range()) { const OutputSocketRef &socket_ref = node->output(i); @@ -899,8 +891,11 @@ class GeometryNodesEvaluator { } OutputState &output_state = node_state.outputs[i]; const DOutputSocket socket{node.context(), &socket_ref}; - GMutablePointer value = outputs[output_index]; - this->forward_output(socket, value); + const CPPType *cpp_type = get_socket_cpp_type(socket_ref); + GField new_field{operation, output_index}; + new_field = fn::make_field_constant_if_possible(std::move(new_field)); + GField &field_to_forward = *allocator.construct<GField>(std::move(new_field)).release(); + this->forward_output(socket, {cpp_type, &field_to_forward}); output_state.has_been_computed = true; output_index++; } @@ -922,7 +917,7 @@ class GeometryNodesEvaluator { OutputState &output_state = node_state.outputs[socket->index()]; output_state.has_been_computed = true; void *buffer = allocator.allocate(type->size(), type->alignment()); - type->copy_construct(type->default_value(), buffer); + this->construct_default_value(*type, buffer); this->forward_output({node.context(), socket}, {*type, buffer}); } } @@ -1389,14 +1384,42 @@ class GeometryNodesEvaluator { return; } + const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type); + const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type); + + if (from_field_type != nullptr && to_field_type != nullptr) { + const CPPType &from_base_type = from_field_type->field_type(); + const CPPType &to_base_type = to_field_type->field_type(); + if (conversions_.is_convertible(from_base_type, to_base_type)) { + const MultiFunction &fn = *conversions_.get_conversion_multi_function( + MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type)); + const GField &from_field = *(const GField *)from_value; + auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field}); + new (to_value) GField(std::move(operation), 0); + return; + } + } if (conversions_.is_convertible(from_type, to_type)) { /* Do the conversion if possible. */ conversions_.convert_to_uninitialized(from_type, to_type, from_value, to_value); } else { /* Cannot convert, use default value instead. */ - to_type.copy_construct(to_type.default_value(), to_value); + this->construct_default_value(to_type, to_value); + } + } + + void construct_default_value(const CPPType &type, void *r_value) + { + if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) { + const CPPType &base_type = field_cpp_type->field_type(); + auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>( + base_type, base_type.default_value(), false); + auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn)); + new (r_value) GField(std::move(operation), 0); + return; } + type.copy_construct(type.default_value(), r_value); } NodeState &get_node_state(const DNode node) diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c index 1dbdcf87d63..db2eedf9c02 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.c +++ b/source/blender/modifiers/intern/MOD_normal_edit.c @@ -450,7 +450,7 @@ static void normalEditModifier_do_directional(NormalEditModifierData *enmd, if (do_polynors_fix && polygons_check_flip(mloop, nos, &mesh->ldata, mpoly, polynors, num_polys)) { - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } BKE_mesh_normals_loop_custom_set(mvert, diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c index 1c502b94bdb..ff1055eff3b 100644 --- a/source/blender/modifiers/intern/MOD_ocean.c +++ b/source/blender/modifiers/intern/MOD_ocean.c @@ -317,7 +317,7 @@ static Mesh *generate_ocean_geometry(OceanModifierData *omd, Mesh *mesh_orig, co } } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -510,7 +510,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * result = doOcean(md, ctx, mesh); if (result != mesh) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index 49b5dabe72d..4fffa7c93f3 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -545,7 +545,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * MEM_SAFE_FREE(vert_part_index); MEM_SAFE_FREE(vert_part_value); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c index df3db894f4e..fef1f76c051 100644 --- a/source/blender/modifiers/intern/MOD_remesh.c +++ b/source/blender/modifiers/intern/MOD_remesh.c @@ -220,7 +220,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx) BKE_mesh_copy_parameters_for_eval(result, mesh); BKE_mesh_calc_edges(result, true, false); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c index 0819b314e32..f24f6951690 100644 --- a/source/blender/modifiers/intern/MOD_screw.c +++ b/source/blender/modifiers/intern/MOD_screw.c @@ -1135,12 +1135,12 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * ob_axis != NULL ? mtx_tx[3] : NULL, ltmd->merge_dist); if (result != result_prev) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } } if ((ltmd->flag & MOD_SCREW_NORMAL_CALC) == 0) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } return result; diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c index b4ab404261c..40094a7e4e7 100644 --- a/source/blender/modifiers/intern/MOD_skin.c +++ b/source/blender/modifiers/intern/MOD_skin.c @@ -1960,7 +1960,7 @@ static Mesh *base_skin(Mesh *origmesh, SkinModifierData *smd, eSkinErrorFlag *r_ result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, origmesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); skin_set_orig_indices(result); diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index 00fa6e24a64..8f9aa86e561 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -988,7 +988,7 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex /* must recalculate normals with vgroups since they can displace unevenly T26888. */ if ((mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) || do_rim || dvert) { - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); } else if (do_shell) { uint i; diff --git a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c index 5b4716a1a43..f654b69841e 100644 --- a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c +++ b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c @@ -1955,7 +1955,7 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, } } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); /* Make edges. */ { diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index 359b4a71af4..ecd2a0b3365 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -108,7 +108,7 @@ Mesh *triangulate_mesh(Mesh *mesh, me->flag |= ME_EDGEDRAW | ME_EDGERENDER; } - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index 5b97d0eb259..d57e92b4b35 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -216,7 +216,6 @@ Mesh *MOD_deform_mesh_eval_get(Object *ob, * we really need vertexCos here. */ else if (vertexCos) { BKE_mesh_vert_coords_apply(mesh, vertexCos); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; } if (use_orco) { diff --git a/source/blender/modifiers/intern/MOD_weld.c b/source/blender/modifiers/intern/MOD_weld.c index b1fa2a7d912..503297d5985 100644 --- a/source/blender/modifiers/intern/MOD_weld.c +++ b/source/blender/modifiers/intern/MOD_weld.c @@ -1979,8 +1979,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, BLI_assert(loop_cur == result_nloops); /* is this needed? */ - /* recalculate normals */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); weld_mesh_context_free(&weld_mesh); } diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c index 55fcfed8e98..296eb78a5ea 100644 --- a/source/blender/modifiers/intern/MOD_wireframe.c +++ b/source/blender/modifiers/intern/MOD_wireframe.c @@ -109,7 +109,7 @@ static Mesh *WireframeModifier_do(WireframeModifierData *wmd, Object *ob, Mesh * result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 29a1de381b5..b741461f820 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -104,6 +104,7 @@ set(SRC composite/nodes/node_composite_outputFile.c composite/nodes/node_composite_pixelate.c composite/nodes/node_composite_planetrackdeform.c + composite/nodes/node_composite_posterize.c composite/nodes/node_composite_premulkey.c composite/nodes/node_composite_rgb.c composite/nodes/node_composite_rotate.c @@ -140,7 +141,11 @@ set(SRC function/nodes/node_fn_random_float.cc function/node_function_util.cc + geometry/nodes/legacy/node_geo_material_assign.cc + geometry/nodes/legacy/node_geo_select_by_material.cc + geometry/nodes/node_geo_align_rotation_to_vector.cc + geometry/nodes/node_geo_attribute_capture.cc geometry/nodes/node_geo_attribute_clamp.cc geometry/nodes/node_geo_attribute_color_ramp.cc geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -186,10 +191,14 @@ set(SRC geometry/nodes/node_geo_delete_geometry.cc geometry/nodes/node_geo_edge_split.cc geometry/nodes/node_geo_input_material.cc + geometry/nodes/node_geo_input_normal.cc + geometry/nodes/node_geo_input_position.cc + geometry/nodes/node_geo_input_index.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc geometry/nodes/node_geo_material_assign.cc geometry/nodes/node_geo_material_replace.cc + geometry/nodes/node_geo_material_selection.cc geometry/nodes/node_geo_mesh_primitive_circle.cc geometry/nodes/node_geo_mesh_primitive_cone.cc geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -209,8 +218,8 @@ set(SRC geometry/nodes/node_geo_point_translate.cc geometry/nodes/node_geo_points_to_volume.cc geometry/nodes/node_geo_raycast.cc - geometry/nodes/node_geo_select_by_material.cc geometry/nodes/node_geo_separate_components.cc + geometry/nodes/node_geo_set_position.cc geometry/nodes/node_geo_subdivision_surface.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc @@ -286,16 +295,16 @@ set(SRC shader/nodes/node_shader_tex_checker.c shader/nodes/node_shader_tex_coord.c shader/nodes/node_shader_tex_environment.c - shader/nodes/node_shader_tex_gradient.c + shader/nodes/node_shader_tex_gradient.cc shader/nodes/node_shader_tex_image.c shader/nodes/node_shader_tex_magic.c - shader/nodes/node_shader_tex_musgrave.c - shader/nodes/node_shader_tex_noise.c + shader/nodes/node_shader_tex_musgrave.cc + shader/nodes/node_shader_tex_noise.cc shader/nodes/node_shader_tex_pointdensity.c shader/nodes/node_shader_tex_sky.c - shader/nodes/node_shader_tex_voronoi.c + shader/nodes/node_shader_tex_voronoi.cc shader/nodes/node_shader_tex_wave.c - shader/nodes/node_shader_tex_white_noise.c + shader/nodes/node_shader_tex_white_noise.cc shader/nodes/node_shader_uvAlongStroke.c shader/nodes/node_shader_uvmap.c shader/nodes/node_shader_valToRgb.cc diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index 258e4c961c9..2cbbd31c97a 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -80,6 +80,7 @@ void register_node_type_cmp_despeckle(void); void register_node_type_cmp_defocus(void); void register_node_type_cmp_denoise(void); void register_node_type_cmp_antialiasing(void); +void register_node_type_cmp_posterize(void); void register_node_type_cmp_valtorgb(void); void register_node_type_cmp_rgbtobw(void); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 00062400eee..a713da45f0b 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -29,6 +29,9 @@ void register_node_tree_type_geo(void); void register_node_type_geo_group(void); void register_node_type_geo_custom_group(bNodeType *ntype); +void register_node_type_geo_legacy_material_assign(void); +void register_node_type_geo_legacy_select_by_material(void); + void register_node_type_geo_align_rotation_to_vector(void); void register_node_type_geo_attribute_clamp(void); void register_node_type_geo_attribute_color_ramp(void); @@ -37,6 +40,7 @@ void register_node_type_geo_attribute_compare(void); void register_node_type_geo_attribute_convert(void); void register_node_type_geo_attribute_curve_map(void); void register_node_type_geo_attribute_fill(void); +void register_node_type_geo_attribute_capture(void); void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); void register_node_type_geo_attribute_mix(void); @@ -71,11 +75,15 @@ void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); void register_node_type_geo_delete_geometry(void); void register_node_type_geo_edge_split(void); +void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); +void register_node_type_geo_input_normal(void); +void register_node_type_geo_input_position(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); void register_node_type_geo_material_assign(void); void register_node_type_geo_material_replace(void); +void register_node_type_geo_material_selection(void); void register_node_type_geo_mesh_primitive_circle(void); void register_node_type_geo_mesh_primitive_cone(void); void register_node_type_geo_mesh_primitive_cube(void); @@ -97,8 +105,8 @@ void register_node_type_geo_points_to_volume(void); void register_node_type_geo_raycast(void); void register_node_type_geo_sample_texture(void); void register_node_type_geo_select_by_handle_type(void); -void register_node_type_geo_select_by_material(void); void register_node_type_geo_separate_components(void); +void register_node_type_geo_set_position(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index d6a23051c0b..dbb5f8b240d 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -16,7 +16,8 @@ #pragma once -#include "FN_generic_value_map.hh" +#include "FN_field.hh" +#include "FN_multi_function_builder.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" @@ -32,17 +33,24 @@ struct ModifierData; namespace blender::nodes { +using bke::AttributeIDRef; using bke::geometry_set_realize_instances; +using bke::GeometryComponentFieldContext; using bke::OutputAttribute; using bke::OutputAttribute_Typed; using bke::ReadAttributeLookup; +using bke::StrongAnonymousAttributeID; +using bke::WeakAnonymousAttributeID; using bke::WriteAttributeLookup; using fn::CPPType; +using fn::Field; +using fn::FieldInput; +using fn::FieldOperation; +using fn::GField; using fn::GMutablePointer; using fn::GMutableSpan; using fn::GPointer; using fn::GSpan; -using fn::GValueMap; using fn::GVArray; using fn::GVArray_GSpan; using fn::GVArray_Span; @@ -121,6 +129,14 @@ class GeoNodeExecParams { { } + template<typename T> + static inline constexpr bool is_stored_as_field_v = std::is_same_v<T, float> || + std::is_same_v<T, int> || + std::is_same_v<T, bool> || + std::is_same_v<T, ColorGeometry4f> || + std::is_same_v<T, float3> || + std::is_same_v<T, std::string>; + /** * Get the input value for the input socket with the given identifier. * @@ -142,11 +158,17 @@ class GeoNodeExecParams { */ template<typename T> T extract_input(StringRef identifier) { + if constexpr (is_stored_as_field_v<T>) { + Field<T> field = this->extract_input<Field<T>>(identifier); + return fn::evaluate_constant_field(field); + } + else { #ifdef DEBUG - this->check_input_access(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - GMutablePointer gvalue = this->extract_input(identifier); - return gvalue.relocate_out<T>(); + GMutablePointer gvalue = this->extract_input(identifier); + return gvalue.relocate_out<T>(); + } } /** @@ -159,7 +181,13 @@ class GeoNodeExecParams { Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier); Vector<T> values; for (GMutablePointer gvalue : gvalues) { - values.append(gvalue.relocate_out<T>()); + if constexpr (is_stored_as_field_v<T>) { + const Field<T> field = gvalue.relocate_out<Field<T>>(); + values.append(fn::evaluate_constant_field(field)); + } + else { + values.append(gvalue.relocate_out<T>()); + } } return values; } @@ -167,14 +195,20 @@ class GeoNodeExecParams { /** * Get the input value for the input socket with the given identifier. */ - template<typename T> const T &get_input(StringRef identifier) const + template<typename T> const T get_input(StringRef identifier) const { + if constexpr (is_stored_as_field_v<T>) { + const Field<T> &field = this->get_input<Field<T>>(identifier); + return fn::evaluate_constant_field(field); + } + else { #ifdef DEBUG - this->check_input_access(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - GPointer gvalue = provider_->get_input(identifier); - BLI_assert(gvalue.is_type<T>()); - return *(const T *)gvalue.get(); + GPointer gvalue = provider_->get_input(identifier); + BLI_assert(gvalue.is_type<T>()); + return *(const T *)gvalue.get(); + } } /** @@ -183,13 +217,19 @@ class GeoNodeExecParams { template<typename T> void set_output(StringRef identifier, T &&value) { using StoredT = std::decay_t<T>; - const CPPType &type = CPPType::get<std::decay_t<T>>(); + if constexpr (is_stored_as_field_v<StoredT>) { + this->set_output<Field<StoredT>>(identifier, + fn::make_constant_field<StoredT>(std::forward<T>(value))); + } + else { + const CPPType &type = CPPType::get<StoredT>(); #ifdef DEBUG - this->check_output_access(identifier, type); + this->check_output_access(identifier, type); #endif - GMutablePointer gvalue = provider_->alloc_output_value(type); - new (gvalue.get()) StoredT(std::forward<T>(value)); - provider_->set_output(identifier, gvalue); + GMutablePointer gvalue = provider_->alloc_output_value(type); + new (gvalue.get()) StoredT(std::forward<T>(value)); + provider_->set_output(identifier, gvalue); + } } /** diff --git a/source/blender/nodes/NOD_multi_function.hh b/source/blender/nodes/NOD_multi_function.hh index 2f4b104fb4c..58816544dc1 100644 --- a/source/blender/nodes/NOD_multi_function.hh +++ b/source/blender/nodes/NOD_multi_function.hh @@ -114,7 +114,7 @@ inline void NodeMultiFunctionBuilder::set_matching_fn(const MultiFunction &fn) template<typename T, typename... Args> inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...args) { - const T &fn = resource_scope_.construct<T>(__func__, std::forward<Args>(args)...); + const T &fn = resource_scope_.construct<T>(std::forward<Args>(args)...); this->set_matching_fn(&fn); } diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 52f4ac291d2..d64b76ccbb9 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -27,12 +27,19 @@ namespace blender::nodes { class NodeDeclarationBuilder; +/** + * Describes a single input or output socket. This is subclassed for different socket types. + */ class SocketDeclaration { protected: std::string name_; std::string identifier_; + bool hide_label_ = false; + bool hide_value_ = false; + bool is_multi_input_ = false; friend NodeDeclarationBuilder; + template<typename SocketDecl> friend class SocketDeclarationBuilder; public: virtual ~SocketDeclaration() = default; @@ -43,6 +50,49 @@ class SocketDeclaration { StringRefNull name() const; StringRefNull identifier() const; + + protected: + void set_common_flags(bNodeSocket &socket) const; + bool matches_common_data(const bNodeSocket &socket) const; +}; + +class BaseSocketDeclarationBuilder { + public: + virtual ~BaseSocketDeclarationBuilder() = default; +}; + +/** + * Wraps a #SocketDeclaration and provides methods to set it up correctly. + * This is separate from #SocketDeclaration, because it allows separating the API used by nodes to + * declare themselves from how the declaration is stored internally. + */ +template<typename SocketDecl> +class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { + protected: + using Self = typename SocketDecl::Builder; + static_assert(std::is_base_of_v<SocketDeclaration, SocketDecl>); + SocketDecl *decl_; + + friend class NodeDeclarationBuilder; + + public: + Self &hide_label(bool value = true) + { + decl_->hide_label_ = value; + return *(Self *)this; + } + + Self &hide_value(bool value = true) + { + decl_->hide_value_ = value; + return *(Self *)this; + } + + Self &multi_input(bool value = true) + { + decl_->is_multi_input_ = value; + return *(Self *)this; + } }; using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>; @@ -60,17 +110,28 @@ class NodeDeclaration { Span<SocketDeclarationPtr> inputs() const; Span<SocketDeclarationPtr> outputs() const; + + MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration") }; class NodeDeclarationBuilder { private: NodeDeclaration &declaration_; + Vector<std::unique_ptr<BaseSocketDeclarationBuilder>> builders_; public: NodeDeclarationBuilder(NodeDeclaration &declaration); - template<typename DeclType> DeclType &add_input(StringRef name, StringRef identifier = ""); - template<typename DeclType> DeclType &add_output(StringRef name, StringRef identifier = ""); + template<typename DeclType> + typename DeclType::Builder &add_input(StringRef name, StringRef identifier = ""); + template<typename DeclType> + typename DeclType::Builder &add_output(StringRef name, StringRef identifier = ""); + + private: + template<typename DeclType> + typename DeclType::Builder &add_socket(StringRef name, + StringRef identifier, + Vector<SocketDeclarationPtr> &r_decls); }; /* -------------------------------------------------------------------- @@ -97,27 +158,34 @@ inline NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declarati } template<typename DeclType> -inline DeclType &NodeDeclarationBuilder::add_input(StringRef name, StringRef identifier) +inline typename DeclType::Builder &NodeDeclarationBuilder::add_input(StringRef name, + StringRef identifier) { - static_assert(std::is_base_of_v<SocketDeclaration, DeclType>); - std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>(); - DeclType &ref = *socket_decl; - ref.name_ = name; - ref.identifier_ = identifier.is_empty() ? name : identifier; - declaration_.inputs_.append(std::move(socket_decl)); - return ref; + return this->add_socket<DeclType>(name, identifier, declaration_.inputs_); +} + +template<typename DeclType> +inline typename DeclType::Builder &NodeDeclarationBuilder::add_output(StringRef name, + StringRef identifier) +{ + return this->add_socket<DeclType>(name, identifier, declaration_.outputs_); } template<typename DeclType> -inline DeclType &NodeDeclarationBuilder::add_output(StringRef name, StringRef identifier) +inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket( + StringRef name, StringRef identifier, Vector<SocketDeclarationPtr> &r_decls) { static_assert(std::is_base_of_v<SocketDeclaration, DeclType>); + using Builder = typename DeclType::Builder; std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>(); - DeclType &ref = *socket_decl; - ref.name_ = name; - ref.identifier_ = identifier.is_empty() ? name : identifier; - declaration_.outputs_.append(std::move(socket_decl)); - return ref; + std::unique_ptr<Builder> socket_decl_builder = std::make_unique<Builder>(); + socket_decl_builder->decl_ = &*socket_decl; + socket_decl->name_ = name; + socket_decl->identifier_ = identifier.is_empty() ? name : identifier; + r_decls.append(std::move(socket_decl)); + Builder &socket_decl_builder_ref = *socket_decl_builder; + builders_.append(std::move(socket_decl_builder)); + return socket_decl_builder_ref; } /* -------------------------------------------------------------------- diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index 639d2c12d73..3d0cfdb5d5d 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -25,6 +25,8 @@ namespace blender::nodes::decl { +class FloatBuilder; + class Float : public SocketDeclaration { private: float default_value_ = 0.0f; @@ -32,36 +34,45 @@ class Float : public SocketDeclaration { float soft_max_value_ = FLT_MAX; PropertySubType subtype_ = PROP_NONE; + friend FloatBuilder; + public: - Float &min(const float value) + using Builder = FloatBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class FloatBuilder : public SocketDeclarationBuilder<Float> { + public: + FloatBuilder &min(const float value) { - soft_min_value_ = value; + decl_->soft_min_value_ = value; return *this; } - Float &max(const float value) + FloatBuilder &max(const float value) { - soft_max_value_ = value; + decl_->soft_max_value_ = value; return *this; } - Float &default_value(const float value) + FloatBuilder &default_value(const float value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Float &subtype(PropertySubType subtype) + FloatBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; +class IntBuilder; + class Int : public SocketDeclaration { private: int default_value_ = 0; @@ -69,169 +80,198 @@ class Int : public SocketDeclaration { int soft_max_value_ = INT32_MAX; PropertySubType subtype_ = PROP_NONE; + friend IntBuilder; + + public: + using Builder = IntBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class IntBuilder : public SocketDeclarationBuilder<Int> { public: - Int &min(const int value) + IntBuilder &min(const int value) { - soft_min_value_ = value; + decl_->soft_min_value_ = value; return *this; } - Int &max(const int value) + IntBuilder &max(const int value) { - soft_max_value_ = value; + decl_->soft_max_value_ = value; return *this; } - Int &default_value(const int value) + IntBuilder &default_value(const int value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Int &subtype(PropertySubType subtype) + IntBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; +class VectorBuilder; + class Vector : public SocketDeclaration { private: float3 default_value_ = {0, 0, 0}; + float soft_min_value_ = -FLT_MAX; + float soft_max_value_ = FLT_MAX; PropertySubType subtype_ = PROP_NONE; + friend VectorBuilder; + + public: + using Builder = VectorBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; +}; + +class VectorBuilder : public SocketDeclarationBuilder<Vector> { public: - Vector &default_value(const float3 value) + VectorBuilder &default_value(const float3 value) { - default_value_ = value; + decl_->default_value_ = value; return *this; } - Vector &subtype(PropertySubType subtype) + VectorBuilder &subtype(PropertySubType subtype) { - subtype_ = subtype; + decl_->subtype_ = subtype; return *this; } - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; - bool matches(const bNodeSocket &socket) const override; - bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; + VectorBuilder &min(const float min) + { + decl_->soft_min_value_ = min; + return *this; + } + + VectorBuilder &max(const float max) + { + decl_->soft_max_value_ = max; + return *this; + } }; +class BoolBuilder; + class Bool : public SocketDeclaration { private: bool default_value_ = false; + friend BoolBuilder; public: - Bool &default_value(const bool value) - { - default_value_ = value; - return *this; - } + using Builder = BoolBuilder; bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; +class BoolBuilder : public SocketDeclarationBuilder<Bool> { + public: + BoolBuilder &default_value(const bool value) + { + decl_->default_value_ = value; + return *this; + } +}; + +class ColorBuilder; + class Color : public SocketDeclaration { private: ColorGeometry4f default_value_; + friend ColorBuilder; + public: - Color &default_value(const ColorGeometry4f value) - { - default_value_ = value; - return *this; - } + using Builder = ColorBuilder; bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; +class ColorBuilder : public SocketDeclarationBuilder<Color> { + public: + ColorBuilder &default_value(const ColorGeometry4f value) + { + decl_->default_value_ = value; + return *this; + } +}; + class String : public SocketDeclaration { public: + using Builder = SocketDeclarationBuilder<String>; + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; -namespace detail { -struct CommonIDSocketData { - const char *idname; - bool hide_label = false; -}; - -bNodeSocket &build_id_socket(bNodeTree &ntree, - bNode &node, - eNodeSocketInOut in_out, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier); -bool matches_id_socket(const bNodeSocket &socket, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier); - -template<typename Subtype> class IDSocketDeclaration : public SocketDeclaration { +class IDSocketDeclaration : public SocketDeclaration { private: - CommonIDSocketData data_; + const char *idname_; public: - IDSocketDeclaration(const char *idname) : data_({idname}) - { - } - - Subtype &hide_label(bool value) - { - data_.hide_label = value; - return *(Subtype *)this; - } - - bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override + IDSocketDeclaration(const char *idname) : idname_(idname) { - return build_id_socket(ntree, node, in_out, data_, name_, identifier_); } - bool matches(const bNodeSocket &socket) const override - { - return matches_id_socket(socket, data_, name_, identifier_); - } + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; }; -} // namespace detail -class Object : public detail::IDSocketDeclaration<Object> { +class Object : public IDSocketDeclaration { public: - Object() : detail::IDSocketDeclaration<Object>("NodeSocketObject") + using Builder = SocketDeclarationBuilder<Object>; + + Object() : IDSocketDeclaration("NodeSocketObject") { } }; -class Material : public detail::IDSocketDeclaration<Material> { +class Material : public IDSocketDeclaration { public: - Material() : detail::IDSocketDeclaration<Material>("NodeSocketMaterial") + using Builder = SocketDeclarationBuilder<Material>; + + Material() : IDSocketDeclaration("NodeSocketMaterial") { } }; -class Collection : public detail::IDSocketDeclaration<Collection> { +class Collection : public IDSocketDeclaration { public: - Collection() : detail::IDSocketDeclaration<Collection>("NodeSocketCollection") + using Builder = SocketDeclarationBuilder<Collection>; + + Collection() : IDSocketDeclaration("NodeSocketCollection") { } }; -class Texture : public detail::IDSocketDeclaration<Texture> { +class Texture : public IDSocketDeclaration { public: - Texture() : detail::IDSocketDeclaration<Texture>("NodeSocketTexture") + using Builder = SocketDeclarationBuilder<Texture>; + + Texture() : IDSocketDeclaration("NodeSocketTexture") { } }; class Geometry : public SocketDeclaration { public: + using Builder = SocketDeclarationBuilder<Geometry>; + bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override; bool matches(const bNodeSocket &socket) const override; }; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 8028350418a..51d59821d3c 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -225,6 +225,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy, DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" ) DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" ) DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" ) +DefNode(CompositorNode, CMP_NODE_POSTERIZE, 0, "POSTERIZE", Posterize, "Posterize", "" ) DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" ) DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" ) @@ -268,25 +269,44 @@ DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") +DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "LEGACY_ATTRIBUTE_COLOR_RAMP", LegacyAttributeColorRamp, "Attribute Color Ramp", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "LEGACY_ATTRIBUTE_COMBINE_XYZ", LegacyAttributeCombineXYZ, "Attribute Combine XYZ", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "LEGACY_ATTRIBUTE_COMPARE", LegacyAttributeCompare, "Attribute Compare", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "LEGACY_ATTRIBUTE_CONVERT", LegacyAttributeConvert, "Attribute Convert", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "LEGACY_ATTRIBUTE_CURVE_MAP", LegacyAttributeCurveMap, "Attribute Curve Map", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_FILL, def_geo_attribute_fill, "LEGACY_ATTRIBUTE_FILL", LegacyAttributeFill, "Attribute Fill", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "LEGACY_ATTRIBUTE_MAP_RANGE", LegacyAttributeMapRange, "Attribute Map Range", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MATH, def_geo_attribute_math, "LEGACY_ATTRIBUTE_MATH", LegacyAttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MIX, def_geo_attribute_mix, "LEGACY_ATTRIBUTE_MIX", LegacyAttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "LEGACY_ATTRIBUTE_RANDOMIZE", LegacyAttributeRandomize, "Attribute Randomize", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, 0, "LEGACY_ATTRIBUTE_SAMPLE_TEXTURE", LegacyAttributeSampleTexture, "Attribute Sample Texture", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "LEGACY_ATTRIBUTE_SEPARATE_XYZ", LegacyAttributeSeparateXYZ, "Attribute Separate XYZ", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "LEGACY_ATTRIBUTE_TRANSFER", LegacyAttributeTransfer, "Attribute Transfer", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "LEGACY_ATTRIBUTE_VECTOR_MATH", LegacyAttributeVectorMath, "Attribute Vector Math", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_REVERSE, 0, "LEGACY_CURVE_REVERSE", LegacyCurveReverse, "Curve Reverse", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "LEGACY_CURVE_SELECT_HANDLES", LegacyCurveSelectHandles, "Select by Handle Type", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "LEGACY_CURVE_SPLINE_TYPE", LegacyCurveSplineType, "Set Spline Type", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_DELETE_GEOMETRY, 0, "LEGACY_DELETE_GEOMETRY", LegacyDeleteGeometry, "Delete Geometry", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_MATERIAL_ASSIGN, 0, "LEGACY_MATERIAL_ASSIGN", LegacyMaterialAssign, "Material Assign", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_MESH_TO_CURVE, 0, "LEGACY_MESH_TO_CURVE", LegacyMeshToCurve, "Mesh to Curve", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_DISTRIBUTE, def_geo_point_distribute, "LEGACY_POINT_DISTRIBUTE", LegacyPointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_INSTANCE, def_geo_point_instance, "LEGACY_POINT_INSTANCE", LegacyPointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_ROTATE, def_geo_point_rotate, "LEGACY_POINT_ROTATE", LegacyRotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SCALE, def_geo_point_scale, "LEGACY_POINT_SCALE", LegacyPointScale, "Point Scale", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SEPARATE, 0, "LEGACY_POINT_SEPARATE", LegacyPointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, "LEGACY_POINT_TRANSLATE", LegacyPointTranslate, "Point Translate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_points_to_volume, "LEGACY_POINTS_TO_VOLUME", LegacyPointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "") + +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") @@ -302,21 +322,19 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_prim DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "") DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") -DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "CURVE_SELECT_HANDLES", CurveSelectHandles, "Select by Handle Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "") -DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") +DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") +DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_REPLACE, 0, "MATERIAL_REPLACE", MaterialReplace, "Material Replace", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") @@ -326,18 +344,9 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Mesh Subdivide", "") -DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") -DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") -DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") -DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") -DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") -DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") -DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "") -DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") +DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") diff --git a/source/blender/io/usd/intern/usd_reader_instance.h b/source/blender/nodes/composite/nodes/node_composite_posterize.c index efc1c69a7dd..5093e581cdc 100644 --- a/source/blender/io/usd/intern/usd_reader_instance.h +++ b/source/blender/nodes/composite/nodes/node_composite_posterize.c @@ -16,32 +16,31 @@ * The Original Code is Copyright (C) 2021 Blender Foundation. * All rights reserved. */ -#pragma once -#include "usd_reader_xform.h" - -#include <pxr/usd/usdGeom/xform.h> - -struct Collection; - -namespace blender::io::usd { - -/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */ - -class USDInstanceReader : public USDXformReader { +/** \file + * \ingroup cmpnodes + */ - public: - USDInstanceReader(const pxr::UsdPrim &prim, - const USDImportParams &import_params, - const ImportSettings &settings); +#include "node_composite_util.h" - bool valid() const override; +/* **************** Posterize ******************** */ - void create_object(Main *bmain, double motionSampleTime) override; +static bNodeSocketTemplate cmp_node_posterize_in[] = { + {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, + {SOCK_FLOAT, N_("Steps"), 8.0f, 8.0f, 8.0f, 8.0f, 2.0f, 1024.0f, PROP_NONE}, + {-1, ""}, +}; +static bNodeSocketTemplate cmp_node_posterize_out[] = { + {SOCK_RGBA, N_("Image")}, + {-1, ""}, +}; - void set_instance_collection(Collection *coll); +void register_node_type_cmp_posterize(void) +{ + static bNodeType ntype; - pxr::SdfPath proto_path() const; -}; + cmp_node_type_base(&ntype, CMP_NODE_POSTERIZE, "Posterize", NODE_CLASS_OP_COLOR, 0); + node_type_socket_templates(&ntype, cmp_node_posterize_in, cmp_node_posterize_out); -} // namespace blender::io::usd + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/node_function_util.hh b/source/blender/nodes/function/node_function_util.hh index 96a8f29c3e9..46b485298e3 100644 --- a/source/blender/nodes/function/node_function_util.hh +++ b/source/blender/nodes/function/node_function_util.hh @@ -31,6 +31,7 @@ #include "NOD_function.h" #include "NOD_multi_function.hh" +#include "NOD_socket_declarations.hh" #include "node_util.h" diff --git a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc index 58e7d82beea..b71ee092de6 100644 --- a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc +++ b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc @@ -24,17 +24,17 @@ #include "node_function_util.hh" -static bNodeSocketTemplate fn_node_boolean_math_in[] = { - {SOCK_BOOLEAN, N_("Boolean")}, - {SOCK_BOOLEAN, N_("Boolean")}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate fn_node_boolean_math_out[] = { - {SOCK_BOOLEAN, N_("Boolean")}, - {-1, ""}, +static void fn_node_boolean_math_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Bool>("Boolean", "Boolean"); + b.add_input<decl::Bool>("Boolean", "Boolean_001"); + b.add_output<decl::Bool>("Boolean"); }; +} // namespace blender::nodes + static void fn_node_boolean_math_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); @@ -91,7 +91,7 @@ void register_node_type_fn_boolean_math() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_BOOLEAN_MATH, "Boolean Math", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, fn_node_boolean_math_in, fn_node_boolean_math_out); + ntype.declare = blender::nodes::fn_node_boolean_math_declare; node_type_label(&ntype, node_boolean_math_label); node_type_update(&ntype, node_boolean_math_update); ntype.build_multi_function = fn_node_boolean_math_build_multi_function; diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc index 918dd24e520..4f4830afabc 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc @@ -26,18 +26,18 @@ #include "node_function_util.hh" -static bNodeSocketTemplate fn_node_float_compare_in[] = { - {SOCK_FLOAT, N_("A"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f}, - {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f}, - {SOCK_FLOAT, N_("Epsilon"), 0.001f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate fn_node_float_compare_out[] = { - {SOCK_BOOLEAN, N_("Result")}, - {-1, ""}, +static void fn_node_float_compare_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("A").min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("B").min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Epsilon").default_value(0.001f).min(-10000.0f).max(10000.0f); + b.add_output<decl::Bool>("Result"); }; +} // namespace blender::nodes + static void geo_node_float_compare_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); @@ -110,7 +110,7 @@ void register_node_type_fn_float_compare() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Float Compare", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, fn_node_float_compare_in, fn_node_float_compare_out); + ntype.declare = blender::nodes::fn_node_float_compare_declare; node_type_label(&ntype, node_float_compare_label); node_type_update(&ntype, node_float_compare_update); ntype.build_multi_function = fn_node_float_compare_build_multi_function; diff --git a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc index 40b8f27f895..e59c78d2c04 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc @@ -25,16 +25,16 @@ #include "node_function_util.hh" -static bNodeSocketTemplate fn_node_float_to_int_in[] = { - {SOCK_FLOAT, N_("Float"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate fn_node_float_to_int_out[] = { - {SOCK_INT, N_("Integer")}, - {-1, ""}, +static void fn_node_float_to_int_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Float"); + b.add_output<decl::Int>("Integer"); }; +} // namespace blender::nodes + static void fn_node_float_to_int_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "rounding_mode", 0, "", ICON_NONE); @@ -88,7 +88,7 @@ void register_node_type_fn_float_to_int() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_FLOAT_TO_INT, "Float to Integer", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, fn_node_float_to_int_in, fn_node_float_to_int_out); + ntype.declare = blender::nodes::fn_node_float_to_int_declare; node_type_label(&ntype, node_float_to_int_label); ntype.build_multi_function = fn_node_float_to_int_build_multi_function; ntype.draw_buttons = fn_node_float_to_int_layout; diff --git a/source/blender/nodes/function/nodes/node_fn_input_string.cc b/source/blender/nodes/function/nodes/node_fn_input_string.cc index 560ace57aba..4a8e898fb9b 100644 --- a/source/blender/nodes/function/nodes/node_fn_input_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_input_string.cc @@ -19,11 +19,15 @@ #include "UI_interface.h" #include "UI_resources.h" -static bNodeSocketTemplate fn_node_input_string_out[] = { - {SOCK_STRING, N_("String")}, - {-1, ""}, +namespace blender::nodes { + +static void fn_node_input_string_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::String>("String"); }; +} // namespace blender::nodes + static void fn_node_input_string_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "string", 0, "", ICON_NONE); @@ -75,7 +79,7 @@ void register_node_type_fn_input_string() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_INPUT_STRING, "String", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, nullptr, fn_node_input_string_out); + ntype.declare = blender::nodes::fn_node_input_string_declare; node_type_init(&ntype, fn_node_input_string_init); node_type_storage(&ntype, "NodeInputString", fn_node_input_string_free, fn_node_string_copy); ntype.build_multi_function = fn_node_input_string_build_multi_function; diff --git a/source/blender/nodes/function/nodes/node_fn_input_vector.cc b/source/blender/nodes/function/nodes/node_fn_input_vector.cc index 244c045de9a..9548df7b423 100644 --- a/source/blender/nodes/function/nodes/node_fn_input_vector.cc +++ b/source/blender/nodes/function/nodes/node_fn_input_vector.cc @@ -21,11 +21,15 @@ #include "UI_interface.h" #include "UI_resources.h" -static bNodeSocketTemplate fn_node_input_vector_out[] = { - {SOCK_VECTOR, N_("Vector")}, - {-1, ""}, +namespace blender::nodes { + +static void fn_node_input_vector_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Vector"); }; +} // namespace blender::nodes + static void fn_node_input_vector_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayout *col = uiLayoutColumn(layout, true); @@ -52,7 +56,7 @@ void register_node_type_fn_input_vector() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_INPUT_VECTOR, "Vector", 0, 0); - node_type_socket_templates(&ntype, nullptr, fn_node_input_vector_out); + ntype.declare = blender::nodes::fn_node_input_vector_declare; node_type_init(&ntype, fn_node_input_vector_init); node_type_storage( &ntype, "NodeInputVector", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/node_fn_random_float.cc index 47ec9adf6bd..1bd39aacdca 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_float.cc +++ b/source/blender/nodes/function/nodes/node_fn_random_float.cc @@ -18,18 +18,18 @@ #include "BLI_hash.h" -static bNodeSocketTemplate fn_node_random_float_in[] = { - {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate fn_node_random_float_out[] = { - {SOCK_FLOAT, N_("Value")}, - {-1, ""}, +static void fn_node_random_float_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Min").min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Int>("Seed").min(-10000).max(10000); + b.add_output<decl::Float>("Value"); }; +} // namespace blender::nodes + class RandomFloatFunction : public blender::fn::MultiFunction { public: RandomFloatFunction() @@ -79,7 +79,7 @@ void register_node_type_fn_random_float() static bNodeType ntype; fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0); - node_type_socket_templates(&ntype, fn_node_random_float_in, fn_node_random_float_out); + ntype.declare = blender::nodes::fn_node_random_float_declare; ntype.build_multi_function = fn_node_random_float_build_multi_function; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 6a69c3d24ec..015ac0de002 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -84,7 +84,7 @@ struct CurveToPointsResults { MutableSpan<float> radii; MutableSpan<float> tilts; - Map<std::string, GMutableSpan> point_attributes; + Map<AttributeIDRef, GMutableSpan> point_attributes; MutableSpan<float3> tangents; MutableSpan<float3> normals; diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc new file mode 100644 index 00000000000..7d3481c1067 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +namespace blender::nodes { + +static void geo_node_legacy_material_assign_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Material>("Material").hide_label(true); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Geometry"); +} + +static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +{ + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } + } + if (new_material_index == -1) { + /* Append a new material index. */ + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); + } + + mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); + for (const int i : IndexRange(mesh.totpoly)) { + if (face_mask[i]) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } + } +} + +static void geo_node_legacy_material_assign_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string mask_name = params.extract_input<std::string>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( + mask_name, ATTR_DOMAIN_FACE, true); + assign_material_to_faces(*mesh, face_mask, material); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_material_assign() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_material_assign_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_material_assign_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc index 79b93502103..eabdd2bcd5a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc @@ -28,10 +28,10 @@ namespace blender::nodes { -static void geo_node_select_by_material_declare(NodeDeclarationBuilder &b) +static void geo_node_legacy_select_by_material_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(true); + b.add_input<decl::Material>("Material").hide_label(); b.add_input<decl::String>("Selection"); b.add_output<decl::Geometry>("Geometry"); } @@ -54,7 +54,7 @@ static void select_mesh_by_material(const Mesh &mesh, }); } -static void geo_node_select_by_material_exec(GeoNodeExecParams params) +static void geo_node_legacy_select_by_material_exec(GeoNodeExecParams params) { Material *material = params.extract_input<Material *>("Material"); const std::string selection_name = params.extract_input<std::string>("Selection"); @@ -80,13 +80,13 @@ static void geo_node_select_by_material_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_select_by_material() +void register_node_type_geo_legacy_select_by_material() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_select_by_material_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_select_by_material_exec; + &ntype, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_select_by_material_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_select_by_material_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index b76556b6c6c..d0bb906e8af 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -226,7 +226,7 @@ void register_node_type_geo_align_rotation_to_vector() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ALIGN_ROTATION_TO_VECTOR, + GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, "Align Rotation to Vector", NODE_CLASS_GEOMETRY, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc new file mode 100644 index 00000000000..1fa71d3f57d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -0,0 +1,210 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BKE_attribute_math.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Vector>("Value"); + b.add_input<decl::Float>("Value", "Value_001"); + b.add_input<decl::Color>("Value", "Value_002"); + b.add_input<decl::Bool>("Value", "Value_003"); + b.add_input<decl::Int>("Value", "Value_004"); + + b.add_output<decl::Geometry>("Geometry"); + b.add_output<decl::Vector>("Attribute"); + b.add_output<decl::Float>("Attribute", "Attribute_001"); + b.add_output<decl::Color>("Attribute", "Attribute_002"); + b.add_output<decl::Bool>("Attribute", "Attribute_003"); + b.add_output<decl::Int>("Attribute", "Attribute_004"); +} + +static void geo_node_attribute_capture_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN( + sizeof(NodeGeometryAttributeCapture), __func__); + data->data_type = CD_PROP_FLOAT; + data->domain = ATTR_DOMAIN_POINT; + + node->storage = data; +} + +static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) + node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *socket_value_attribute_name = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_value_vector = socket_value_attribute_name->next; + bNodeSocket *socket_value_float = socket_value_vector->next; + bNodeSocket *socket_value_color4f = socket_value_float->next; + bNodeSocket *socket_value_boolean = socket_value_color4f->next; + bNodeSocket *socket_value_int32 = socket_value_boolean->next; + + nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_value_attribute_name = (bNodeSocket *)node->outputs.first; + bNodeSocket *out_socket_value_vector = out_socket_value_attribute_name->next; + bNodeSocket *out_socket_value_float = out_socket_value_vector->next; + bNodeSocket *out_socket_value_color4f = out_socket_value_float->next; + bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next; + bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next; + + nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32); +} + +static void try_capture_field_on_geometry(GeometryComponent &component, + const AttributeIDRef &attribute_id, + const AttributeDomain domain, + const GField &field) +{ + GeometryComponentFieldContext field_context{component, domain}; + const int domain_size = component.attribute_domain_size(domain); + const IndexMask mask{IndexMask(domain_size)}; + + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type()); + OutputAttribute output_attribute = component.attribute_try_get_for_output_only( + attribute_id, domain, data_type); + + fn::FieldEvaluator evaluator{field_context, &mask}; + evaluator.add_with_destination(field, output_attribute.varray()); + evaluator.evaluate(); + + output_attribute.save(); +} + +static void geo_node_attribute_capture_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + const bNode &node = params.node(); + const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) + node.storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + + GField field; + switch (data_type) { + case CD_PROP_FLOAT: + field = params.get_input<Field<float>>("Value_001"); + break; + case CD_PROP_FLOAT3: + field = params.get_input<Field<float3>>("Value"); + break; + case CD_PROP_COLOR: + field = params.get_input<Field<ColorGeometry4f>>("Value_002"); + break; + case CD_PROP_BOOL: + field = params.get_input<Field<bool>>("Value_003"); + break; + case CD_PROP_INT32: + field = params.get_input<Field<int>>("Value_004"); + break; + default: + break; + } + + WeakAnonymousAttributeID anonymous_id{"Attribute Capture"}; + const CPPType &type = field.cpp_type(); + + static const Array<GeometryComponentType> types = { + GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; + for (const GeometryComponentType type : types) { + if (geometry_set.has(type)) { + GeometryComponent &component = geometry_set.get_component_for_write(type); + try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); + } + } + + GField output_field{ + std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)}; + + switch (data_type) { + case CD_PROP_FLOAT: { + params.set_output("Attribute_001", Field<float>(output_field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Attribute", Field<float3>(output_field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Attribute_002", Field<ColorGeometry4f>(output_field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Attribute_003", Field<bool>(output_field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Attribute_004", Field<int>(output_field)); + break; + } + default: + break; + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_capture() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0); + node_type_storage(&ntype, + "NodeGeometryAttributeCapture", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init); + node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update); + ntype.declare = blender::nodes::geo_node_attribute_capture_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec; + ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc index 3211cdbc5a7..97070184609 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc @@ -268,7 +268,8 @@ void register_node_type_geo_attribute_clamp() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_clamp_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_clamp_update); ntype.declare = blender::nodes::geo_node_attribute_clamp_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index aae906f2e5e..aa054af3acd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -125,8 +125,11 @@ void register_node_type_geo_attribute_color_ramp() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COLOR_RAMP, "Attribute Color Ramp", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, + "Attribute Color Ramp", + NODE_CLASS_ATTRIBUTE, + 0); node_type_storage( &ntype, "NodeAttributeColorRamp", node_free_standard_storage, node_copy_standard_storage); node_type_init(&ntype, blender::nodes::geo_node_attribute_color_ramp_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index 1638793525a..569d5a824ca 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -136,8 +136,11 @@ void register_node_type_geo_attribute_combine_xyz() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, "Attribute Combine XYZ", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, + "Attribute Combine XYZ", + NODE_CLASS_ATTRIBUTE, + 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_combine_xyz_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_combine_xyz_update); node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index 75c88a6953a..0b9708dae14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -348,7 +348,7 @@ void register_node_type_geo_attribute_compare() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_compare_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_compare_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc index 72e1930952a..a2382aa9d25 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc @@ -182,7 +182,7 @@ void register_node_type_geo_attribute_convert() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_convert_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_convert_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_convert_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc index cb86dfa90a8..b9621b4ae92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -211,7 +211,7 @@ void register_node_type_geo_attribute_curve_map() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); node_type_update(&ntype, blender::nodes::geo_node_attribute_curve_map_update); node_type_init(&ntype, blender::nodes::geo_node_attribute_curve_map_init); node_type_size_preset(&ntype, NODE_SIZE_LARGE); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index 632e4f86572..3c50ae5c837 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -152,7 +152,8 @@ void register_node_type_geo_attribute_fill() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_fill_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_fill_update); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_fill_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc index 4cb452a97cd..0ea3bbe1e45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -420,7 +420,7 @@ void register_node_type_geo_attribute_map_range() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_map_range_exec; node_type_init(&ntype, blender::nodes::geo_node_attribute_map_range_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_map_range_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index de1d5ca815d..efa09215b45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -301,7 +301,8 @@ void register_node_type_geo_attribute_math() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_math_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_math_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 582b1a88221..74e05cb997d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -31,7 +31,11 @@ static void geo_node_mix_attribute_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::String>("Factor"); - b.add_input<decl::Float>("Factor").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Factor", "Factor_001") + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR); b.add_input<decl::String>("A"); b.add_input<decl::Float>("A", "A_001"); b.add_input<decl::Vector>("A", "A_002"); @@ -239,7 +243,8 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) void register_node_type_geo_attribute_mix() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_mix_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_mix_update); ntype.declare = blender::nodes::geo_node_mix_attribute_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index 9ca19f70be6..0cf411343cf 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -237,7 +237,7 @@ void register_node_type_geo_attribute_proximity() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_attribute_proximity_init); node_type_storage(&ntype, "NodeGeometryAttributeProximity", diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 64035c4fc16..60b9910399c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -331,7 +331,7 @@ void register_node_type_geo_attribute_randomize() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_randomize_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_randomize_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index e4f3230ebb9..21a9a338857 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -16,28 +16,15 @@ #include "node_geometry_util.hh" -static bNodeSocketTemplate geo_node_attribute_remove_in[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_STRING, - N_("Attribute"), - 0.0f, - 0.0f, - 0.0f, - 1.0f, - -1.0f, - 1.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_attribute_remove_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; - namespace blender::nodes { +static void geo_node_attribute_remove_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Attribute").multi_input(); + b.add_output<decl::Geometry>("Geometry"); +} + static void remove_attribute(GeometryComponent &component, GeoNodeExecParams ¶ms, Span<std::string> attribute_names) @@ -85,7 +72,7 @@ void register_node_type_geo_attribute_remove() geo_node_type_base( &ntype, GEO_NODE_ATTRIBUTE_REMOVE, "Attribute Remove", NODE_CLASS_ATTRIBUTE, 0); - node_type_socket_templates(&ntype, geo_node_attribute_remove_in, geo_node_attribute_remove_out); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_remove_exec; + ntype.declare = blender::nodes::geo_node_attribute_remove_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index c78a3d956e3..52f97475941 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -33,7 +33,7 @@ namespace blender::nodes { static void geo_node_attribute_sample_texture_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Texture>("Texture").hide_label(true); + b.add_input<decl::Texture>("Texture").hide_label(); b.add_input<decl::String>("Mapping"); b.add_input<decl::String>("Result"); b.add_output<decl::Geometry>("Geometry"); @@ -126,7 +126,7 @@ void register_node_type_geo_sample_texture() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, + GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, "Attribute Sample Texture", NODE_CLASS_ATTRIBUTE, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index b1ac608faad..de0090406c6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -157,8 +157,11 @@ void register_node_type_geo_attribute_separate_xyz() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, "Attribute Separate XYZ", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, + "Attribute Separate XYZ", + NODE_CLASS_ATTRIBUTE, + 0); ntype.declare = blender::nodes::geo_node_attribute_separate_xyz_declare; node_type_init(&ntype, blender::nodes::geo_node_attribute_separate_xyz_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_separate_xyz_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc index 6cef09aca67..874350cd714 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -516,7 +516,7 @@ void register_node_type_geo_attribute_transfer() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); node_type_storage(&ntype, "NodeGeometryAttributeTransfer", diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index 47f2cf39d51..59903050f88 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -555,8 +555,11 @@ void register_node_type_geo_attribute_vector_math() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_VECTOR_MATH, "Attribute Vector Math", NODE_CLASS_ATTRIBUTE, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, + "Attribute Vector Math", + NODE_CLASS_ATTRIBUTE, + 0); ntype.declare = blender::nodes::geo_node_attribute_vector_math_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_math_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_math_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc index da753dfc11b..adaa4de3029 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -21,27 +21,26 @@ #include "UI_interface.h" #include "UI_resources.h" -static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_STRING, N_("Vector")}, - {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, - {SOCK_STRING, N_("Center")}, - {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, - {SOCK_STRING, N_("Axis")}, - {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE}, - {SOCK_STRING, N_("Angle")}, - {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE}, - {SOCK_STRING, N_("Rotation")}, - {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, - {SOCK_BOOLEAN, N_("Invert")}, - {SOCK_STRING, N_("Result")}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void geo_node_attribute_vector_rotate_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Vector"); + b.add_input<decl::Vector>("Vector", "Vector_001").min(0.0f).max(1.0f).hide_value(); + b.add_input<decl::String>("Center"); + b.add_input<decl::Vector>("Center", "Center_001").subtype(PROP_XYZ); + b.add_input<decl::String>("Axis"); + b.add_input<decl::Vector>("Axis", "Axis_001").min(-1.0f).max(1.0f).subtype(PROP_XYZ); + b.add_input<decl::String>("Angle"); + b.add_input<decl::Float>("Angle", "Angle_001").subtype(PROP_ANGLE); + b.add_input<decl::String>("Rotation"); + b.add_input<decl::Vector>("Rotation", "Rotation_001").subtype(PROP_EULER); + b.add_input<decl::Bool>("Invert"); + b.add_input<decl::String>("Result"); + + b.add_output<decl::Geometry>("Geometry"); +} static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, bContext *UNUSED(C), @@ -71,8 +70,6 @@ static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, } } -namespace blender::nodes { - static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) { const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage; @@ -339,14 +336,13 @@ void register_node_type_geo_attribute_vector_rotate() "Attribute Vector Rotate", NODE_CLASS_ATTRIBUTE, 0); - node_type_socket_templates( - &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out); node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update); node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init); node_type_size(&ntype, 165, 100, 600); node_type_storage( &ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec; - ntype.draw_buttons = geo_node_attribute_vector_rotate_layout; + ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_rotate_layout; + ntype.declare = blender::nodes::geo_node_attribute_vector_rotate_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index d8029ea1eeb..2a1c43a89fe 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -23,27 +23,16 @@ #include "node_geometry_util.hh" -static bNodeSocketTemplate geo_node_boolean_in[] = { - {SOCK_GEOMETRY, N_("Geometry 1")}, - {SOCK_GEOMETRY, - N_("Geometry 2"), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {SOCK_BOOLEAN, N_("Self Intersection")}, - {SOCK_BOOLEAN, N_("Hole Tolerant")}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_boolean_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void geo_node_boolean_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry 1"); + b.add_input<decl::Geometry>("Geometry 2").multi_input(); + b.add_input<decl::Bool>("Self Intersection"); + b.add_input<decl::Bool>("Hole Tolerant"); + b.add_output<decl::Geometry>("Geometry"); +} static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { @@ -77,8 +66,6 @@ static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node) node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE; } -namespace blender::nodes { - static void geo_node_boolean_exec(GeoNodeExecParams params) { GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; @@ -138,10 +125,10 @@ void register_node_type_geo_boolean() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0); - node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); - ntype.draw_buttons = geo_node_boolean_layout; - ntype.updatefunc = geo_node_boolean_update; - node_type_init(&ntype, geo_node_boolean_init); + ntype.declare = blender::nodes::geo_node_boolean_declare; + ntype.draw_buttons = blender::nodes::geo_node_boolean_layout; + ntype.updatefunc = blender::nodes::geo_node_boolean_update; + node_type_init(&ntype, blender::nodes::geo_node_boolean_init); ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index e731b4c0cdc..f4c295b06fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -27,7 +27,7 @@ namespace blender::nodes { static void geo_node_collection_info_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Collection>("Collection").hide_label(true); + b.add_input<decl::Collection>("Collection").hide_label(); b.add_output<decl::Geometry>("Geometry"); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc index 699fcc5e983..7853c5aa04a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc @@ -56,25 +56,26 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, Span<int> offsets, PointCloudComponent &points) { - curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != ATTR_DOMAIN_CURVE) { - return true; - } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( - name, ATTR_DOMAIN_CURVE, meta_data.data_type); - - OutputAttribute result_attribute = points.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, meta_data.data_type); - GMutableSpan result = result_attribute.as_span(); - - /* Only copy the attributes of splines in the offsets. */ - for (const int i : offsets.index_range()) { - spline_attribute->get(offsets[i], result[i]); - } - - result_attribute.save(); - return true; - }); + curve_component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != ATTR_DOMAIN_CURVE) { + return true; + } + GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); + + OutputAttribute result_attribute = points.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type); + GMutableSpan result = result_attribute.as_span(); + + /* Only copy the attributes of splines in the offsets. */ + for (const int i : offsets.index_range()) { + spline_attribute->get(offsets[i], result[i]); + } + + result_attribute.save(); + return true; + }); } /** @@ -124,20 +125,20 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines, /* Copy the point attribute data over. */ for (const auto &item : start_data.point_attributes.items()) { - const StringRef name = item.key; + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]); } for (const auto &item : end_data.point_attributes.items()) { - const StringRef name = item.key; + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc index 0d78d6c08d1..07ddaa8f61e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc @@ -30,10 +30,10 @@ static void geo_node_curve_primitive_quadrilateral_declare(NodeDeclarationBuilde b.add_input<decl::Float>("Offset").default_value(1.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Bottom Height").default_value(3.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Top Height").default_value(1.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 1").default_value({-1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 2").default_value({1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 3").default_value({1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 4").default_value({-1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); + b.add_input<decl::Vector>("Point 1").default_value({-1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); + b.add_input<decl::Vector>("Point 2").default_value({1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); + b.add_input<decl::Vector>("Point 3").default_value({1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); + b.add_input<decl::Vector>("Point 4").default_value({-1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); b.add_output<decl::Geometry>("Curve"); } @@ -106,10 +106,10 @@ static void create_rectangle_curve(MutableSpan<float3> positions, const float height, const float width) { - positions[0] = float3(width / 2.0f, -height / 2.0f, 0.0f); - positions[1] = float3(-width / 2.0f, -height / 2.0f, 0.0f); - positions[2] = float3(-width / 2.0f, height / 2.0f, 0.0f); - positions[3] = float3(width / 2.0f, height / 2.0f, 0.0f); + positions[0] = float3(width / 2.0f, height / 2.0f, 0.0f); + positions[1] = float3(-width / 2.0f, height / 2.0f, 0.0f); + positions[2] = float3(-width / 2.0f, -height / 2.0f, 0.0f); + positions[3] = float3(width / 2.0f, -height / 2.0f, 0.0f); } static void create_points_curve(MutableSpan<float3> positions, @@ -129,10 +129,10 @@ static void create_parallelogram_curve(MutableSpan<float3> positions, const float width, const float offset) { - positions[0] = float3(width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f); - positions[1] = float3(-width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f); - positions[2] = float3(-width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f); - positions[3] = float3(width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f); + positions[0] = float3(width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f); + positions[1] = float3(-width / 2.0f + offset / 2.0f, height / 2.0f, 0.0f); + positions[2] = float3(-width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f); + positions[3] = float3(width / 2.0f - offset / 2.0f, -height / 2.0f, 0.0f); } static void create_trapezoid_curve(MutableSpan<float3> positions, const float bottom, @@ -140,10 +140,10 @@ static void create_trapezoid_curve(MutableSpan<float3> positions, const float offset, const float height) { - positions[0] = float3(bottom / 2.0f, -height / 2.0f, 0.0f); - positions[1] = float3(-bottom / 2.0f, -height / 2.0f, 0.0f); - positions[2] = float3(-top / 2.0f + offset, height / 2.0f, 0.0f); - positions[3] = float3(top / 2.0f + offset, height / 2.0f, 0.0f); + positions[0] = float3(top / 2.0f + offset, height / 2.0f, 0.0f); + positions[1] = float3(-top / 2.0f + offset, height / 2.0f, 0.0f); + positions[2] = float3(-bottom / 2.0f, -height / 2.0f, 0.0f); + positions[3] = float3(bottom / 2.0f, -height / 2.0f, 0.0f); } static void create_kite_curve(MutableSpan<float3> positions, @@ -151,10 +151,10 @@ static void create_kite_curve(MutableSpan<float3> positions, const float bottom_height, const float top_height) { - positions[0] = float3(-width / 2.0f, 0, 0); - positions[1] = float3(0, top_height, 0); - positions[2] = float3(width / 2, 0, 0); - positions[3] = float3(0, -bottom_height, 0); + positions[0] = float3(0, -bottom_height, 0); + positions[1] = float3(width / 2, 0, 0); + positions[2] = float3(0, top_height, 0); + positions[3] = float3(-width / 2.0f, 0, 0); } static void geo_node_curve_primitive_quadrilateral_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc index 169a169bb1c..0803d43e5c3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc @@ -41,7 +41,7 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - const int totalpoints = resolution * rotations; + const int totalpoints = std::max(int(resolution * rotations), 1); const float delta_radius = (end_radius - start_radius) / (float)totalpoints; float radius = start_radius; const float delta_height = height / (float)totalpoints; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 0b71a8cb03a..e89d500fe57 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -42,7 +42,7 @@ static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b) static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) @@ -72,80 +72,95 @@ struct SampleModeParam { std::optional<int> count; }; -static SplinePtr resample_spline(const Spline &input_spline, const int count) +static SplinePtr resample_spline(const Spline &src, const int count) { - std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>(); - output_spline->set_cyclic(input_spline.is_cyclic()); - output_spline->normal_mode = input_spline.normal_mode; - - if (input_spline.evaluated_edges_size() < 1 || count == 1) { - output_spline->add_point(input_spline.positions().first(), - input_spline.tilts().first(), - input_spline.radii().first()); - output_spline->attributes.reallocate(1); - input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = input_spline.attributes.get_for_read(name); - BLI_assert(src); - if (!output_spline->attributes.create(name, meta_data.data_type)) { - BLI_assert_unreachable(); - return false; + std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>(); + Spline::copy_base_settings(src, *dst); + + if (src.evaluated_edges_size() < 1 || count == 1) { + dst->add_point(src.positions().first(), src.tilts().first(), src.radii().first()); + dst->attributes.reallocate(1); + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write( + attribute_id); + if (dst_attribute) { + src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data()); + return true; + } } - std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(name); - if (!dst) { - BLI_assert_unreachable(); - return false; - } - src->type().copy_assign(src->data(), dst->data()); - return true; + BLI_assert_unreachable(); + return false; }, ATTR_DOMAIN_POINT); - return output_spline; + return dst; } - output_spline->resize(count); + dst->resize(count); - Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); + Array<float> uniform_samples = src.sample_uniform_index_factors(count); - input_spline.sample_with_index_factors<float3>( - input_spline.evaluated_positions(), uniform_samples, output_spline->positions()); + src.sample_with_index_factors<float3>( + src.evaluated_positions(), uniform_samples, dst->positions()); - input_spline.sample_with_index_factors<float>( - input_spline.interpolate_to_evaluated(input_spline.radii()), - uniform_samples, - output_spline->radii()); + src.sample_with_index_factors<float>( + src.interpolate_to_evaluated(src.radii()), uniform_samples, dst->radii()); - input_spline.sample_with_index_factors<float>( - input_spline.interpolate_to_evaluated(input_spline.tilts()), - uniform_samples, - output_spline->tilts()); + src.sample_with_index_factors<float>( + src.interpolate_to_evaluated(src.tilts()), uniform_samples, dst->tilts()); - output_spline->attributes.reallocate(count); - input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name); - BLI_assert(input_attribute); - if (!output_spline->attributes.create(name, meta_data.data_type)) { - BLI_assert_unreachable(); - return false; - } - std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write( - name); - if (!output_attribute) { - BLI_assert_unreachable(); - return false; + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> input_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> output_attribute = dst->attributes.get_for_write( + attribute_id); + if (output_attribute) { + src.sample_with_index_factors(*src.interpolate_to_evaluated(*input_attribute), + uniform_samples, + *output_attribute); + return true; + } } - input_spline.sample_with_index_factors( - *input_spline.interpolate_to_evaluated(*input_attribute), - uniform_samples, - *output_attribute); + BLI_assert_unreachable(); + return false; + }, + ATTR_DOMAIN_POINT); + + return dst; +} + +static SplinePtr resample_spline_evaluated(const Spline &src) +{ + std::unique_ptr<PolySpline> dst = std::make_unique<PolySpline>(); + Spline::copy_base_settings(src, *dst); + dst->resize(src.evaluated_points_size()); + + dst->positions().copy_from(src.evaluated_positions()); + dst->positions().copy_from(src.evaluated_positions()); + src.interpolate_to_evaluated(src.radii())->materialize(dst->radii()); + src.interpolate_to_evaluated(src.tilts())->materialize(dst->tilts()); + + src.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); + if (dst->attributes.create(attribute_id, meta_data.data_type)) { + std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write(attribute_id); + if (dst_attribute) { + src.interpolate_to_evaluated(*src_attribute)->materialize(dst_attribute->data()); + return true; + } + } + BLI_assert_unreachable(); return true; }, ATTR_DOMAIN_POINT); - return output_spline; + return dst; } static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, @@ -169,11 +184,18 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { const float length = input_splines[i]->length(); - const int count = std::max(int(length / *mode_param.length), 1); + const int count = std::max(int(length / *mode_param.length) + 1, 1); output_splines[i] = resample_spline(*input_splines[i], count); } }); } + else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_EVALUATED) { + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = resample_spline_evaluated(*input_splines[i]); + } + }); + } output_curve->attributes = input_curve.attributes; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc index b826e1c6510..32bcbe2c608 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -29,31 +29,6 @@ static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Curve"); } -/** - * Reverse the data in a MutableSpan object. - */ -template<typename T> static void reverse_data(MutableSpan<T> r_data) -{ - const int size = r_data.size(); - for (const int i : IndexRange(size / 2)) { - std::swap(r_data[size - 1 - i], r_data[i]); - } -} - -/** - * Reverse and Swap the data between 2 MutableSpans. - */ -template<typename T> static void reverse_data(MutableSpan<T> left, MutableSpan<T> right) -{ - BLI_assert(left.size() == right.size()); - const int size = left.size(); - - for (const int i : IndexRange(size / 2 + size % 2)) { - std::swap(left[i], right[size - 1 - i]); - std::swap(right[i], left[size - 1 - i]); - } -} - static void geo_node_curve_reverse_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); @@ -74,42 +49,9 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - if (!selection[i]) { - continue; - } - - reverse_data<float3>(splines[i]->positions()); - reverse_data<float>(splines[i]->radii()); - reverse_data<float>(splines[i]->tilts()); - - splines[i]->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<blender::fn::GMutableSpan> output_attribute = - splines[i]->attributes.get_for_write(name); - if (!output_attribute) { - BLI_assert_unreachable(); - return false; - } - attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - reverse_data(output_attribute->typed<T>()); - }); - return true; - }, - ATTR_DOMAIN_POINT); - - /* Deal with extra info on derived types. */ - if (BezierSpline *spline = dynamic_cast<BezierSpline *>(splines[i].get())) { - reverse_data<BezierSpline::HandleType>(spline->handle_types_left()); - reverse_data<BezierSpline::HandleType>(spline->handle_types_right()); - reverse_data<float3>(spline->handle_positions_left(), spline->handle_positions_right()); + if (selection[i]) { + splines[i]->reverse(); } - else if (NURBSpline *spline = dynamic_cast<NURBSpline *>(splines[i].get())) { - reverse_data<float>(spline->weights()); - } - /* Nothing to do for poly splines. */ - - splines[i]->mark_cache_invalid(); } }); @@ -121,7 +63,8 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) void register_node_type_geo_curve_reverse() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_reverse_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc index bf33cf0294e..dfcae2e65b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc @@ -127,8 +127,11 @@ void register_node_type_geo_select_by_handle_type() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_CURVE_SELECT_HANDLES, "Select by Handle Type", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, + GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, + "Select by Handle Type", + NODE_CLASS_GEOMETRY, + 0); ntype.declare = blender::nodes::geo_node_select_by_handle_type_declare; ntype.geometry_node_execute = blender::nodes::geo_node_select_by_handle_type_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_select_by_handle_type_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc index 0b5be7addaf..31c13134f79 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc @@ -130,7 +130,7 @@ void register_node_type_geo_curve_set_handles() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 6a093f442cb..0ef107fd8a4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -74,14 +74,14 @@ template<typename CopyFn> static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) { input_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = input_spline.attributes.get_for_read(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id); BLI_assert(src); - if (!output_spline.attributes.create(name, meta_data.data_type)) { + if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) { BLI_assert_unreachable(); return false; } - std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(name); + std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id); if (!dst) { BLI_assert_unreachable(); return false; @@ -288,7 +288,7 @@ void register_node_type_geo_curve_spline_type() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_spline_type_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index 958d83e2967..0522f2b8981 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -283,16 +283,16 @@ static void subdivide_dynamic_attributes(const Spline &src_spline, { const bool is_cyclic = src_spline.is_cyclic(); src_spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = src_spline.attributes.get_for_read(name); + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id); BLI_assert(src); - if (!dst_spline.attributes.create(name, meta_data.data_type)) { + if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) { /* Since the source spline of the same type had the attribute, adding it should work. */ BLI_assert_unreachable(); } - std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(name); + std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id); BLI_assert(dst); attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { @@ -377,7 +377,8 @@ void register_node_type_geo_curve_subdivide() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index ebaae59fbd6..f46440fd949 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -39,6 +39,20 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } +/** Information about the creation of one curve spline and profile spline combination. */ +struct ResultInfo { + const Spline &spline; + const Spline &profile; + int vert_offset; + int edge_offset; + int loop_offset; + int poly_offset; + int spline_vert_len; + int spline_edge_len; + int profile_vert_len; + int profile_edge_len; +}; + static void vert_extrude_to_mesh_data(const Spline &spline, const float3 profile_vert, MutableSpan<MVert> r_verts, @@ -75,44 +89,33 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges) } } -static void spline_extrude_to_mesh_data(const Spline &spline, - const Spline &profile_spline, - const int vert_offset, - const int edge_offset, - const int loop_offset, - const int poly_offset, +static void spline_extrude_to_mesh_data(const ResultInfo &info, MutableSpan<MVert> r_verts, MutableSpan<MEdge> r_edges, MutableSpan<MLoop> r_loops, MutableSpan<MPoly> r_polys) { - const int spline_vert_len = spline.evaluated_points_size(); - const int spline_edge_len = spline.evaluated_edges_size(); - const int profile_vert_len = profile_spline.evaluated_points_size(); - const int profile_edge_len = profile_spline.evaluated_edges_size(); - if (spline_vert_len == 0) { - return; - } - - if (profile_vert_len == 1) { + const Spline &spline = info.spline; + const Spline &profile = info.profile; + if (info.profile_vert_len == 1) { vert_extrude_to_mesh_data(spline, - profile_spline.evaluated_positions()[0], + profile.evaluated_positions()[0], r_verts, r_edges, - vert_offset, - edge_offset); + info.vert_offset, + info.edge_offset); return; } /* Add the edges running along the length of the curve, starting at each profile vertex. */ - const int spline_edges_start = edge_offset; - for (const int i_profile : IndexRange(profile_vert_len)) { - const int profile_edge_offset = spline_edges_start + i_profile * spline_edge_len; - for (const int i_ring : IndexRange(spline_edge_len)) { - const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + const int spline_edges_start = info.edge_offset; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; - const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; MEdge &edge = r_edges[profile_edge_offset + i_ring]; edge.v1 = ring_vert_offset + i_profile; @@ -122,13 +125,14 @@ static void spline_extrude_to_mesh_data(const Spline &spline, } /* Add the edges running along each profile ring. */ - const int profile_edges_start = spline_edges_start + profile_vert_len * spline_edge_len; - for (const int i_ring : IndexRange(spline_vert_len)) { - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int profile_edges_start = spline_edges_start + + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int ring_edge_offset = profile_edges_start + i_ring * profile_edge_len; - for (const int i_profile : IndexRange(profile_edge_len)) { - const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; MEdge &edge = r_edges[ring_edge_offset + i_profile]; edge.v1 = ring_vert_offset + i_profile; @@ -138,24 +142,25 @@ static void spline_extrude_to_mesh_data(const Spline &spline, } /* Calculate poly and corner indices. */ - for (const int i_ring : IndexRange(spline_edge_len)) { - const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; - const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring; - const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring; + const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring; - const int ring_poly_offset = poly_offset + i_ring * profile_edge_len; - const int ring_loop_offset = loop_offset + i_ring * profile_edge_len * 4; + const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len; + const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; - for (const int i_profile : IndexRange(profile_edge_len)) { + for (const int i_profile : IndexRange(info.profile_edge_len)) { const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; - const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile; - const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile; + const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + + info.spline_edge_len * i_next_profile; MPoly &poly = r_polys[ring_poly_offset + i_profile]; poly.loopstart = ring_segment_loop_offset; @@ -164,16 +169,16 @@ static void spline_extrude_to_mesh_data(const Spline &spline, MLoop &loop_a = r_loops[ring_segment_loop_offset]; loop_a.v = ring_vert_offset + i_profile; - loop_a.e = spline_edge_start + i_ring; + loop_a.e = ring_edge_start + i_profile; MLoop &loop_b = r_loops[ring_segment_loop_offset + 1]; - loop_b.v = next_ring_vert_offset + i_profile; - loop_b.e = next_ring_edge_offset + i_profile; + loop_b.v = ring_vert_offset + i_next_profile; + loop_b.e = next_spline_edge_start + i_ring; MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; loop_c.v = next_ring_vert_offset + i_next_profile; - loop_c.e = next_spline_edge_start + i_ring; + loop_c.e = next_ring_edge_offset + i_profile; MLoop &loop_d = r_loops[ring_segment_loop_offset + 3]; - loop_d.v = ring_vert_offset + i_next_profile; - loop_d.e = ring_edge_start + i_profile; + loop_d.v = next_ring_vert_offset + i_profile; + loop_d.e = spline_edge_start + i_ring; } } @@ -181,29 +186,30 @@ static void spline_extrude_to_mesh_data(const Spline &spline, Span<float3> positions = spline.evaluated_positions(); Span<float3> tangents = spline.evaluated_tangents(); Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile_spline.evaluated_positions(); + Span<float3> profile_positions = profile.evaluated_positions(); GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i_ring : IndexRange(spline_vert_len)) { + for (const int i_ring : IndexRange(info.spline_vert_len)) { float4x4 point_matrix = float4x4::from_normalized_axis_data( positions[i_ring], normals[i_ring], tangents[i_ring]); point_matrix.apply_scale(radii[i_ring]); - const int ring_vert_start = vert_offset + i_ring * profile_vert_len; - for (const int i_profile : IndexRange(profile_vert_len)) { + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { MVert &vert = r_verts[ring_vert_start + i_profile]; copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); } } /* Mark edge loops from sharp vector control points sharp. */ - if (profile_spline.type() == Spline::Type::Bezier) { - const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); + if (profile.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); Span<int> control_point_offsets = bezier_spline.control_point_offsets(); for (const int i : IndexRange(bezier_spline.size())) { if (bezier_spline.point_is_sharp(i)) { - mark_edges_sharp(r_edges.slice( - spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); + mark_edges_sharp( + r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], + info.spline_edge_len)); } } } @@ -272,6 +278,372 @@ static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<Spl return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; } +static AttributeDomain get_result_attribute_domain(const MeshComponent &component, + const AttributeIDRef &attribute_id) +{ + /* Only use a different domain if it is builtin and must only exist on one domain. */ + if (!component.attribute_is_builtin(attribute_id)) { + return ATTR_DOMAIN_POINT; + } + + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); + if (!meta_data) { + /* This function has to return something in this case, but it shouldn't be used, + * so return an output that will assert later if the code attempts to handle it. */ + return ATTR_DOMAIN_AUTO; + } + + return meta_data->domain; +} + +/** + * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling + * `as_span()` for every single profile and curve spline combination, and for readability. + */ +struct ResultAttributeData { + GMutableSpan data; + AttributeDomain domain; +}; + +static std::optional<ResultAttributeData> create_attribute_and_get_span( + MeshComponent &component, + const AttributeIDRef &attribute_id, + AttributeMetaData meta_data, + Vector<OutputAttribute> &r_attributes) +{ + const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); + OutputAttribute attribute = component.attribute_try_get_for_output_only( + attribute_id, domain, meta_data.data_type); + if (!attribute) { + return std::nullopt; + } + + GMutableSpan span = attribute.as_span(); + r_attributes.append(std::move(attribute)); + return std::make_optional<ResultAttributeData>({span, domain}); +} + +/** + * Store the references to the attribute data from the curve and profile inputs. Here we rely on + * the invariants of the storage of curve attributes, that the order will be consistent between + * splines, and all splines will have the same attributes. + */ +struct ResultAttributes { + /** + * Result attributes on the mesh corresponding to each attribute on the curve input, in the same + * order. The data is optional only in case the attribute does not exist on the mesh for some + * reason, like "shade_smooth" when the result has no faces. + */ + Vector<std::optional<ResultAttributeData>> curve_point_attributes; + Vector<std::optional<ResultAttributeData>> curve_spline_attributes; + + /** + * Result attributes corresponding the attributes on the profile input, in the same order. The + * attributes are optional in case the attribute names correspond to a namse used by the curve + * input, in which case the curve input attributes take precedence. + */ + Vector<std::optional<ResultAttributeData>> profile_point_attributes; + Vector<std::optional<ResultAttributeData>> profile_spline_attributes; + + /** + * Because some builtin attributes are not stored contiguously, and the curve inputs might have + * attributes with those names, it's necessary to keep OutputAttributes around to give access to + * the result data in a contiguous array. + */ + Vector<OutputAttribute> attributes; +}; +static ResultAttributes create_result_attributes(const CurveEval &curve, + const CurveEval &profile, + Mesh &mesh) +{ + MeshComponent mesh_component; + mesh_component.replace(&mesh, GeometryOwnershipType::Editable); + Set<AttributeIDRef> curve_attributes; + + /* In order to prefer attributes on the main curve input when there are name collisions, first + * check the attributes on the curve, then add attributes on the profile that are not also on the + * main curve input. */ + ResultAttributes result; + curve.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_POINT); + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_CURVE); + profile.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_point_attributes.append({}); + } + else { + result.profile_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_POINT); + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_spline_attributes.append({}); + } + else { + result.profile_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_CURVE); + + return result; +} + +template<typename T> +static void copy_curve_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; + dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; + dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +static void copy_curve_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +template<typename T> +static void copy_profile_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + dst[profile_vert_start + i_profile] = src[i_profile]; + } + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; + dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + dst[profile_face_start + i_profile] = src[i_profile]; + } + } +} + +static void copy_profile_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, + ResultAttributes &attributes) +{ + if (!attributes.curve_point_attributes.is_empty()) { + int i = 0; + info.spline.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_point_attributes[i]) { + copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), + info, + *attributes.curve_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } + if (!attributes.profile_point_attributes.is_empty()) { + int i = 0; + info.profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_point_attributes[i]) { + copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), + info, + *attributes.profile_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } +} + +template<typename T> +static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) +{ + for (const int i : IndexRange(src.size())) { + dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); + } +} + +/** + * Since the offsets for each combination of curve and profile spline are stored for every mesh + * domain, and this just needs to fill the chunks corresponding to each combination, we can use + * the same function for all mesh domains. + */ +static void copy_spline_attribute_to_mesh(const GSpan src, + const ResultOffsets &offsets, + ResultAttributeData &dst_attribute) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst_attribute.domain) { + case ATTR_DOMAIN_POINT: + copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, + const CurveEval &profile, + const ResultOffsets &offsets, + ResultAttributes &attributes) +{ + if (!attributes.curve_spline_attributes.is_empty()) { + int i = 0; + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), + offsets, + *attributes.curve_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } + if (!attributes.profile_spline_attributes.is_empty()) { + int i = 0; + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), + offsets, + *attributes.profile_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } +} + /** * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. * Although it would be a sensible decision to use the better topology information available while @@ -294,30 +666,52 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr BKE_id_material_eval_ensure_default_slot(&mesh->id); mesh->flag |= ME_AUTOSMOOTH; mesh->smoothresh = DEG2RADF(180.0f); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); + + ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { for (const int i_spline : curves_range) { + const Spline &spline = *curves[i_spline]; + if (spline.evaluated_points_size() == 0) { + continue; + } const int spline_start_index = i_spline * profiles.size(); threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { for (const int i_profile : profiles_range) { + const Spline &profile = *profiles[i_profile]; const int i_mesh = spline_start_index + i_profile; - spline_extrude_to_mesh_data(*curves[i_spline], - *profiles[i_profile], - offsets.vert[i_mesh], - offsets.edge[i_mesh], - offsets.loop[i_mesh], - offsets.poly[i_mesh], + ResultInfo info{ + spline, + profile, + offsets.vert[i_mesh], + offsets.edge[i_mesh], + offsets.loop[i_mesh], + offsets.poly[i_mesh], + spline.evaluated_points_size(), + spline.evaluated_edges_size(), + profile.evaluated_points_size(), + profile.evaluated_edges_size(), + }; + + spline_extrude_to_mesh_data(info, {mesh->mvert, mesh->totvert}, {mesh->medge, mesh->totedge}, {mesh->mloop, mesh->totloop}, {mesh->mpoly, mesh->totpoly}); + + copy_point_domain_attributes_to_mesh(info, attributes); } }); } }); + copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); + + for (OutputAttribute &output_attribute : attributes.attributes) { + output_attribute.save(); + } + return mesh; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 052eb92d269..74740ba244f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -101,7 +101,7 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, int offset = 0; for (const int i : IndexRange(size)) { offsets[i] = offset; - offset += splines[i]->length() / resolution; + offset += splines[i]->length() / resolution + 1; } offsets.last() = offset; return offsets; @@ -115,21 +115,21 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, } static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points, - const StringRef name, + const AttributeIDRef &attribute_id, const CustomDataType data_type) { - points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); - WriteAttributeLookup attribute = points.attribute_try_get_for_write(name); + points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); + WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); BLI_assert(attribute); return attribute.varray->get_internal_span(); } template<typename T> static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points, - const StringRef name) + const AttributeIDRef &attribute_id) { GMutableSpan attribute = create_attribute_and_retrieve_span( - points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); + points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); return attribute.typed<T>(); } @@ -147,9 +147,10 @@ CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponen /* Because of the invariants of the curve component, we use the attributes of the * first spline as a representative for the attribute meta data all splines. */ curve.splines().first()->attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { attributes.point_attributes.add_new( - name, create_attribute_and_retrieve_span(points, name, meta_data.data_type)); + attribute_id, + create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type)); return true; }, ATTR_DOMAIN_POINT); @@ -179,12 +180,12 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines, spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size)); spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size)); - for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); spline.interpolate_to_evaluated(spline_span) ->materialize(point_span.slice(offset, size).data()); @@ -222,12 +223,12 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines, uniform_samples, data.tilts.slice(offset, size)); - for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; - BLI_assert(spline.attributes.get_for_read(name)); - GSpan spline_span = *spline.attributes.get_for_read(name); + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span), uniform_samples, @@ -257,31 +258,32 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, Span<int> offsets, PointCloudComponent &points) { - curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (meta_data.domain != ATTR_DOMAIN_CURVE) { - return true; - } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( - name, ATTR_DOMAIN_CURVE, meta_data.data_type); - const CPPType &type = spline_attribute->type(); - - OutputAttribute result_attribute = points.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, meta_data.data_type); - GMutableSpan result = result_attribute.as_span(); - - for (const int i : IndexRange(spline_attribute->size())) { - const int offset = offsets[i]; - const int size = offsets[i + 1] - offsets[i]; - if (size != 0) { - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - spline_attribute->get(i, buffer); - type.fill_assign_n(buffer, result[offset], size); - } - } - - result_attribute.save(); - return true; - }); + curve_component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain != ATTR_DOMAIN_CURVE) { + return true; + } + GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); + const CPPType &type = spline_attribute->type(); + + OutputAttribute result_attribute = points.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type); + GMutableSpan result = result_attribute.as_span(); + + for (const int i : IndexRange(spline_attribute->size())) { + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + if (size != 0) { + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + spline_attribute->get(i, buffer); + type.fill_assign_n(buffer, result[offset], size); + } + } + + result_attribute.save(); + return true; + }); } void curve_create_default_rotation_attribute(Span<float3> tangents, diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 578f0298a19..4ad311d63c5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -158,8 +158,8 @@ static void trim_poly_spline(Spline &spline, linear_trim_data<float>(start, end, spline.tilts()); spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - std::optional<GMutableSpan> src = spline.attributes.get_for_write(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id); BLI_assert(src); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { using T = decltype(dummy); @@ -193,14 +193,14 @@ static PolySpline trim_nurbs_spline(const Spline &spline, /* Copy generic attribute data. */ spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src = spline.attributes.get_for_read(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id); BLI_assert(src); - if (!new_spline.attributes.create(name, meta_data.data_type)) { + if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) { BLI_assert_unreachable(); return false; } - std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(name); + std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id); BLI_assert(dst); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { @@ -249,8 +249,8 @@ static void trim_bezier_spline(Spline &spline, linear_trim_data<float>(start, end, bezier_spline.radii()); linear_trim_data<float>(start, end, bezier_spline.tilts()); spline.attributes.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { - std::optional<GMutableSpan> src = spline.attributes.get_for_write(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id); BLI_assert(src); attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { using T = decltype(dummy); diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 6bc0ab49959..1e2f652cd78 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -93,17 +93,17 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src, const IndexMask mask) { src.foreach_attribute( - [&](StringRefNull name, const AttributeMetaData &meta_data) { - std::optional<GSpan> src_attribute = src.get_for_read(name); + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.get_for_read(attribute_id); BLI_assert(src_attribute); - if (!dst.create(name, meta_data.data_type)) { + if (!dst.create(attribute_id, meta_data.data_type)) { /* Since the source spline of the same type had the attribute, adding it should work. */ BLI_assert_unreachable(); } - std::optional<GMutableSpan> new_attribute = dst.get_for_write(name); + std::optional<GMutableSpan> new_attribute = dst.get_for_write(attribute_id); BLI_assert(new_attribute); attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) { @@ -559,7 +559,7 @@ static Mesh *delete_mesh_selection(const Mesh &mesh_in, mesh_in, *result, vertex_map, edge_map, selected_poly_indices, new_loop_starts); BKE_mesh_calc_edges_loose(result); /* Tag to recalculate normals later. */ - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(result); return result; } @@ -668,7 +668,8 @@ void register_node_type_geo_delete_geometry() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_delete_geometry_declare; ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc new file mode 100644 index 00000000000..c52ff3d448e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc @@ -0,0 +1,60 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_index_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>("Index"); +} + +class IndexFieldInput final : public fn::FieldInput { + public: + IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context), + IndexMask mask, + ResourceScope &scope) const final + { + /* TODO: Investigate a similar method to IndexRange::as_span() */ + auto index_func = [](int i) { return i; }; + return &scope.construct< + fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( + mask.min_array_size(), mask.min_array_size(), index_func); + } +}; + +static void geo_node_input_index_exec(GeoNodeExecParams params) +{ + Field<int> index_field{std::make_shared<IndexFieldInput>()}; + params.set_output("Index", std::move(index_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_index() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec; + ntype.declare = blender::nodes::geo_node_input_index_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc new file mode 100644 index 00000000000..07818f2a3ad --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -0,0 +1,211 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_normal_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Normal"); +} + +static GVArrayPtr mesh_face_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.pdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, polys.size())); + } + + auto normal_fn = [verts, polys, loops](const int i) -> float3 { + float3 normal; + const MPoly &poly = polys[i]; + BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal); + return normal; + }; + + return std::make_unique< + fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>( + mask.min_array_size(), mask.min_array_size(), normal_fn); +} + +static GVArrayPtr mesh_vertex_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.vdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, mesh.totvert)); + } + + /* If the normals are dirty, they must be recalculated for the output of this node's field + * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not + * possible at the moment, so we take ownership of the results. Sadly we must also create a copy + * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy + * calculation of normals on meshes. + * + * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */ + Array<MVert> temp_verts(verts); + Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */ + BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(), + mask.min_array_size(), + loops.data(), + loops.size(), + polys.data(), + polys.size(), + nullptr, + (float(*)[3])normals.data()); + + return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); +} + +static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain, + ResourceScope &scope) +{ + Span<MVert> verts{mesh.mvert, mesh.totvert}; + Span<MEdge> edges{mesh.medge, mesh.totedge}; + Span<MPoly> polys{mesh.mpoly, mesh.totpoly}; + Span<MLoop> loops{mesh.mloop, mesh.totloop}; + + switch (domain) { + case ATTR_DOMAIN_FACE: { + return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask)).get(); + } + case ATTR_DOMAIN_POINT: { + return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask)).get(); + } + case ATTR_DOMAIN_EDGE: { + /* In this case, start with vertex normals and convert to the edge domain, since the + * conversion from edges to vertices is very simple. Use the full mask since the edges + * might use the vertex normal from any index. */ + GVArrayPtr vert_normals = mesh_vertex_normals( + mesh, verts, polys, loops, IndexRange(verts.size())); + Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>(); + Array<float3> edge_normals(mask.min_array_size()); + + /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid + * calculating unnecessary values and to allow normalizing the result much more simply. */ + for (const int i : mask) { + const MEdge &edge = edges[i]; + edge_normals[i] = float3::interpolate( + vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f) + .normalized(); + } + + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(edge_normals)); + } + case ATTR_DOMAIN_CORNER: { + /* The normals on corners are just the mesh's face normals, so start with the face normal + * array and copy the face normal for each of its corners. */ + GVArrayPtr face_normals = mesh_face_normals( + mesh, verts, polys, loops, IndexRange(polys.size())); + + /* In this case using the mesh component's generic domain interpolation is fine, the data + * will still be normalized, since the face normal is just copied to every corner. */ + GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain( + std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); + return scope.add_value(std::move(loop_normals)).get(); + } + default: + return nullptr; + } +} + +class NormalFieldInput final : public fn::FieldInput { + public: + NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return nullptr; + } + + return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope); + } + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + /* TODO: Add curve normals support. */ + return nullptr; + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 669605641; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_normal_exec(GeoNodeExecParams params) +{ + Field<float3> normal_field{std::make_shared<NormalFieldInput>()}; + params.set_output("Normal", std::move(normal_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_normal() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec; + ntype.declare = blender::nodes::geo_node_input_normal_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc new file mode 100644 index 00000000000..c6365bf6809 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_position_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Position"); +} + +static void geo_node_input_position_exec(GeoNodeExecParams params) +{ + Field<float3> position_field{ + std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())}; + params.set_output("Position", std::move(position_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_position() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec; + ntype.declare = blender::nodes::geo_node_input_position_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 730cf08feaa..93643298f92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -29,27 +29,14 @@ using blender::fn::GVArray_For_GSpan; -static bNodeSocketTemplate geo_node_join_geometry_in[] = { - {SOCK_GEOMETRY, - N_("Geometry"), - 0.0f, - 0.0f, - 0.0f, - 1.0f, - -1.0f, - 1.0f, - PROP_NONE, - SOCK_MULTI_INPUT}, - {-1, ""}, -}; - -static bNodeSocketTemplate geo_node_join_geometry_out[] = { - {SOCK_GEOMETRY, N_("Geometry")}, - {-1, ""}, -}; - namespace blender::nodes { +static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry").multi_input(); + b.add_output<decl::Geometry>("Geometry"); +} + static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components) { int totverts = 0; @@ -161,34 +148,35 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component return components; } -static Map<std::string, AttributeMetaData> get_final_attribute_info( +static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info( Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes) { - Map<std::string, AttributeMetaData> info; + Map<AttributeIDRef, AttributeMetaData> info; for (const GeometryComponent *component : components) { - component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { - if (ignored_attributes.contains(name)) { - return true; - } - info.add_or_modify( - name, - [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, - [&](AttributeMetaData *meta_data_final) { - meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( - {meta_data_final->data_type, meta_data.data_type}); - meta_data_final->domain = blender::bke::attribute_domain_highest_priority( - {meta_data_final->domain, meta_data.domain}); - }); - return true; - }); + component->attribute_foreach( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { + return true; + } + info.add_or_modify( + attribute_id, + [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, + [&](AttributeMetaData *meta_data_final) { + meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( + {meta_data_final->data_type, meta_data.data_type}); + meta_data_final->domain = blender::bke::attribute_domain_highest_priority( + {meta_data_final->domain, meta_data.domain}); + }); + return true; + }); } return info; } static void fill_new_attribute(Span<const GeometryComponent *> src_components, - StringRef attribute_name, + const AttributeIDRef &attribute_id, const CustomDataType data_type, const AttributeDomain domain, GMutableSpan dst_span) @@ -203,7 +191,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, continue; } GVArrayPtr read_attribute = component->attribute_get_for_read( - attribute_name, domain, data_type, nullptr); + attribute_id, domain, data_type, nullptr); GVArray_GSpan src_span{*read_attribute}; const void *src_buffer = src_span.data(); @@ -218,20 +206,21 @@ static void join_attributes(Span<const GeometryComponent *> src_components, GeometryComponent &result, Span<StringRef> ignored_attributes = {}) { - const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components, - ignored_attributes); + const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components, + ignored_attributes); - for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { + const AttributeIDRef attribute_id = item.key; const AttributeMetaData &meta_data = item.value; OutputAttribute write_attribute = result.attribute_try_get_for_output_only( - name, meta_data.domain, meta_data.data_type); + attribute_id, meta_data.domain, meta_data.data_type); if (!write_attribute) { continue; } GMutableSpan dst_span = write_attribute.as_span(); - fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span); + fill_new_attribute( + src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span); write_attribute.save(); } } @@ -306,7 +295,7 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet * \note This takes advantage of the fact that creating attributes on joined curves never * changes a point attribute into a spline attribute; it is always the other way around. */ -static void ensure_control_point_attribute(const StringRef name, +static void ensure_control_point_attribute(const AttributeIDRef &attribute_id, const CustomDataType data_type, Span<CurveComponent *> src_components, CurveEval &result) @@ -321,7 +310,7 @@ static void ensure_control_point_attribute(const StringRef name, const CurveEval *current_curve = src_components[src_component_index]->get_for_read(); for (SplinePtr &spline : splines) { - std::optional<GSpan> attribute = spline->attributes.get_for_read(name); + std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id); if (attribute) { if (attribute->type() != type) { @@ -334,22 +323,22 @@ static void ensure_control_point_attribute(const StringRef name, conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type) ->materialize(converted_buffer); - spline->attributes.remove(name); - spline->attributes.create_by_move(name, data_type, converted_buffer); + spline->attributes.remove(attribute_id); + spline->attributes.create_by_move(attribute_id, data_type, converted_buffer); } } else { - spline->attributes.create(name, data_type); + spline->attributes.create(attribute_id, data_type); - if (current_curve->attributes.get_for_read(name)) { + if (current_curve->attributes.get_for_read(attribute_id)) { /* In this case the attribute did not exist, but there is a spline domain attribute * we can retrieve a value from, as a spline to point domain conversion. So fill the * new attribute with the value for this spline. */ GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read( - name, data_type, nullptr); + attribute_id, data_type, nullptr); - BLI_assert(spline->attributes.get_for_read(name)); - std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name); + BLI_assert(spline->attributes.get_for_read(attribute_id)); + std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id); BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); current_curve_attribute->get(spline_index_in_component, buffer); @@ -371,15 +360,15 @@ static void ensure_control_point_attribute(const StringRef name, /** * Fill data for an attribute on the new curve based on all source curves. */ -static void ensure_spline_attribute(const StringRef name, +static void ensure_spline_attribute(const AttributeIDRef &attribute_id, const CustomDataType data_type, Span<CurveComponent *> src_components, CurveEval &result) { const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - result.attributes.create(name, data_type); - GMutableSpan result_attribute = *result.attributes.get_for_write(name); + result.attributes.create(attribute_id, data_type); + GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id); int offset = 0; for (const CurveComponent *component : src_components) { @@ -388,7 +377,7 @@ static void ensure_spline_attribute(const StringRef name, if (size == 0) { continue; } - GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr); + GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr); GVArray_GSpan src_span{*read_attribute}; const void *src_buffer = src_span.data(); @@ -406,19 +395,19 @@ static void ensure_spline_attribute(const StringRef name, * \warning Splines have been moved out of the source components at this point, so it * is important to only read curve-level data (spline domain attributes) from them. */ -static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info, +static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info, Span<CurveComponent *> src_components, CurveEval &result) { - for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { - const StringRef name = item.key; + for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { + const AttributeIDRef attribute_id = item.key; const AttributeMetaData meta_data = item.value; if (meta_data.domain == ATTR_DOMAIN_CURVE) { - ensure_spline_attribute(name, meta_data.data_type, src_components, result); + ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result); } else { - ensure_control_point_attribute(name, meta_data.data_type, src_components, result); + ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result); } } } @@ -446,7 +435,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge } /* Retrieve attribute info before moving the splines out of the input components. */ - const Map<std::string, AttributeMetaData> info = get_final_attribute_info( + const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info( {(const GeometryComponent **)src_components.data(), src_components.size()}, {"position", "radius", "tilt", "cyclic", "resolution"}); @@ -506,7 +495,7 @@ void register_node_type_geo_join_geometry() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", NODE_CLASS_GEOMETRY, 0); - node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out); ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec; + ntype.declare = blender::nodes::geo_node_join_geometry_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc index 4d0b4cfecbc..43818947272 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -29,12 +29,12 @@ namespace blender::nodes { static void geo_node_material_assign_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(true); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Material>("Material").hide_label(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); b.add_output<decl::Geometry>("Geometry"); } -static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +static void assign_material_to_faces(Mesh &mesh, const IndexMask selection, Material *material) { int new_material_index = -1; for (const int i : IndexRange(mesh.totcol)) { @@ -51,18 +51,16 @@ static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, } mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); - for (const int i : IndexRange(mesh.totpoly)) { - if (face_mask[i]) { - MPoly &poly = mesh.mpoly[i]; - poly.mat_nr = new_material_index; - } + for (const int i : selection) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; } } static void geo_node_material_assign_exec(GeoNodeExecParams params) { Material *material = params.extract_input<Material *>("Material"); - const std::string mask_name = params.extract_input<std::string>("Selection"); + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -72,9 +70,15 @@ static void geo_node_material_assign_exec(GeoNodeExecParams params) MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); Mesh *mesh = mesh_component.get_for_write(); if (mesh != nullptr) { - GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( - mask_name, ATTR_DOMAIN_FACE, true); - assign_material_to_faces(*mesh, face_mask, material); + + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; + + fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + assign_material_to_faces(*mesh, selection, material); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc new file mode 100644 index 00000000000..22c24e34314 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -0,0 +1,131 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_task.hh" + +#include "BKE_material.h" + +namespace blender::nodes { + +static void geo_node_material_selection_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Material>("Material").hide_label(true); + b.add_output<decl::Bool>("Selection"); +} + +static void select_mesh_by_material(const Mesh &mesh, + const Material *material, + const IndexMask mask, + const MutableSpan<bool> r_selection) +{ + BLI_assert(mesh.totpoly >= r_selection.size()); + Vector<int> material_indices; + for (const int i : IndexRange(mesh.totcol)) { + if (mesh.mat[i] == material) { + material_indices.append(i); + } + } + threading::parallel_for(mask.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const int face_index = mask[i]; + r_selection[i] = material_indices.contains(mesh.mpoly[face_index].mat_nr); + } + }); +} + +class MaterialSelectionFieldInput final : public fn::FieldInput { + Material *material_; + + public: + MaterialSelectionFieldInput(Material *material) + : fn::FieldInput(CPPType::get<bool>(), "Material Selection"), material_(material) + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return nullptr; + } + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_FACE) { + Array<bool> selection(mask.min_array_size()); + select_mesh_by_material(*mesh, material_, mask, selection); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<bool>>>(std::move(selection)); + } + + Array<bool> selection(mesh->totpoly); + select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection); + GVArrayPtr face_selection = std::make_unique<fn::GVArray_For_ArrayContainer<Array<bool>>>( + std::move(selection)); + GVArrayPtr final_selection = mesh_component.attribute_try_adapt_domain( + std::move(face_selection), ATTR_DOMAIN_FACE, domain); + return scope.add_value(std::move(final_selection)).get(); + } + + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 91619626; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_material_selection_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + Field<bool> material_field{std::make_shared<MaterialSelectionFieldInput>(material)}; + params.set_output("Selection", std::move(material_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_selection() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_MATERIAL_SELECTION, "Material Selection", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_material_selection_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_material_selection_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index 561cde69e6f..23a411e36dc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -26,7 +26,7 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cube_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Size").default_value({1.0f, 1.0f, 1.0f}).subtype(PROP_TRANSLATION); + b.add_input<decl::Vector>("Size").default_value(float3(1)).min(0.0f).subtype(PROP_TRANSLATION); b.add_input<decl::Int>("Vertices X").default_value(2).min(2).max(1000); b.add_input<decl::Int>("Vertices Y").default_value(2).min(2).max(1000); b.add_input<decl::Int>("Vertices Z").default_value(2).min(2).max(1000); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index e57098af126..bf26c03272e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -44,7 +44,7 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) BMO_op_callf(bm, BMO_FLAG_DEFAULTS, - "create_icosphere subdivisions=%i diameter=%f matrix=%m4 calc_uvs=%b", + "create_icosphere subdivisions=%i radius=%f matrix=%m4 calc_uvs=%b", subdivisions, std::abs(radius), transform.values, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 2cea60ea112..11349dc7d42 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -52,10 +52,10 @@ static void copy_attributes_to_points(CurveEval &curve, Span<Vector<int>> point_to_vert_maps) { MutableSpan<SplinePtr> splines = curve.splines(); - Set<std::string> source_attribute_names = mesh_component.attribute_names(); + Set<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids(); /* Copy builtin control point attributes. */ - if (source_attribute_names.contains_as("tilt")) { + if (source_attribute_ids.contains("tilt")) { const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>( "tilt", ATTR_DOMAIN_POINT, 0.0f); threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { @@ -64,9 +64,9 @@ static void copy_attributes_to_points(CurveEval &curve, *tilt_attribute, point_to_vert_maps[i], splines[i]->tilts()); } }); - source_attribute_names.remove_contained_as("tilt"); + source_attribute_ids.remove_contained("tilt"); } - if (source_attribute_names.contains_as("radius")) { + if (source_attribute_ids.contains("radius")) { const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>( "radius", ATTR_DOMAIN_POINT, 1.0f); threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { @@ -75,15 +75,15 @@ static void copy_attributes_to_points(CurveEval &curve, *radius_attribute, point_to_vert_maps[i], splines[i]->radii()); } }); - source_attribute_names.remove_contained_as("radius"); + source_attribute_ids.remove_contained("radius"); } /* Don't copy other builtin control point attributes. */ - source_attribute_names.remove_as("position"); + source_attribute_ids.remove("position"); /* Copy dynamic control point attributes. */ - for (const StringRef name : source_attribute_names) { - const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name, + for (const AttributeIDRef &attribute_id : source_attribute_ids) { + const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id, ATTR_DOMAIN_POINT); /* Some attributes might not exist if they were builtin attribute on domains that don't * have any elements, i.e. a face attribute on the output of the line primitive node. */ @@ -96,8 +96,9 @@ static void copy_attributes_to_points(CurveEval &curve, threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { /* Create attribute on the spline points. */ - splines[i]->attributes.create(name, data_type); - std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name); + splines[i]->attributes.create(attribute_id, data_type); + std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write( + attribute_id); BLI_assert(spline_attribute); /* Copy attribute based on the map for this spline. */ @@ -305,7 +306,8 @@ void register_node_type_geo_mesh_to_curve() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_mesh_to_curve_declare; ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc index ab99c9bb3f8..389acc40f0f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -25,7 +25,7 @@ namespace blender::nodes { static void geo_node_object_info_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Object>("Object").hide_label(true); + b.add_input<decl::Object>("Object").hide_label(); b.add_output<decl::Vector>("Location"); b.add_output<decl::Vector>("Rotation"); b.add_output<decl::Vector>("Scale"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index cf874bea718..04b4003daed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -277,17 +277,17 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, BLI_NOINLINE static void interpolate_existing_attributes( Span<GeometryInstanceGroup> set_groups, Span<int> instance_start_offsets, - const Map<std::string, AttributeKind> &attributes, + const Map<AttributeIDRef, AttributeKind> &attributes, GeometryComponent &component, Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) { - for (Map<std::string, AttributeKind>::Item entry : attributes.items()) { - StringRef attribute_name = entry.key; + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; const CustomDataType output_data_type = entry.value.data_type; /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ OutputAttribute attribute_out = component.attribute_try_get_for_output_only( - attribute_name, ATTR_DOMAIN_POINT, output_data_type); + attribute_id, ATTR_DOMAIN_POINT, output_data_type); if (!attribute_out) { continue; } @@ -301,7 +301,7 @@ BLI_NOINLINE static void interpolate_existing_attributes( const Mesh &mesh = *source_component.get_for_read(); std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data( - attribute_name); + attribute_id); if (!attribute_info) { i_instance += set_group.transforms.size(); continue; @@ -309,7 +309,7 @@ BLI_NOINLINE static void interpolate_existing_attributes( const AttributeDomain source_domain = attribute_info->domain; GVArrayPtr source_attribute = source_component.attribute_get_for_read( - attribute_name, source_domain, output_data_type, nullptr); + attribute_id, source_domain, output_data_type, nullptr); if (!source_attribute) { i_instance += set_group.transforms.size(); continue; @@ -406,7 +406,7 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> BLI_NOINLINE static void add_remaining_point_attributes( Span<GeometryInstanceGroup> set_groups, Span<int> instance_start_offsets, - const Map<std::string, AttributeKind> &attributes, + const Map<AttributeIDRef, AttributeKind> &attributes, GeometryComponent &component, Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) @@ -629,7 +629,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) PointCloudComponent &point_component = geometry_set_out.get_component_for_write<PointCloudComponent>(); - Map<std::string, AttributeKind> attributes; + Map<AttributeIDRef, AttributeKind> attributes; bke::geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes); add_remaining_point_attributes(set_groups, @@ -649,7 +649,7 @@ void register_node_type_geo_point_distribute() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); node_type_update(&ntype, blender::nodes::node_point_distribute_update); ntype.declare = blender::nodes::geo_node_point_distribute_declare; ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 36017307739..fb45c22ced4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -29,15 +29,16 @@ namespace blender::nodes { static void geo_node_point_instance_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Object>("Object").hide_label(true); - b.add_input<decl::Collection>("Collection").hide_label(true); + b.add_input<decl::Object>("Object").hide_label(); + b.add_input<decl::Collection>("Collection").hide_label(); + b.add_input<decl::Geometry>("Instance Geometry"); b.add_input<decl::Int>("Seed").min(-10000).max(10000); b.add_output<decl::Geometry>("Geometry"); } static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "instance_type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "instance_type", 0, "", ICON_NONE); if (RNA_enum_get(ptr, "instance_type") == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) { uiItemR(layout, ptr, "use_whole_collection", 0, nullptr, ICON_NONE); } @@ -56,7 +57,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); bNodeSocket *collection_socket = object_socket->next; - bNodeSocket *seed_socket = collection_socket->next; + bNodeSocket *instance_geometry_socket = collection_socket->next; + bNodeSocket *seed_socket = instance_geometry_socket->next; NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node->storage; GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node_storage->instance_type; @@ -65,6 +67,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT); nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION); + nodeSetSocketAvailability(instance_geometry_socket, + type == GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY); nodeSetSocketAvailability( seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } @@ -114,6 +118,13 @@ static Vector<InstanceReference> get_instance_references__collection(GeoNodeExec return references; } +static Vector<InstanceReference> get_instance_references__geometry(GeoNodeExecParams ¶ms) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instance Geometry"); + geometry_set.ensure_owns_direct_data(); + return {std::move(geometry_set)}; +} + static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); @@ -128,6 +139,9 @@ static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { return get_instance_references__collection(params); } + case GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY: { + return get_instance_references__geometry(params); + } } return {}; } @@ -245,7 +259,8 @@ void register_node_type_geo_point_instance() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_instance_init); node_type_storage( &ntype, "NodeGeometryPointInstance", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc index 4d77bcc132f..60c82360007 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc @@ -219,7 +219,7 @@ void register_node_type_geo_point_rotate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_rotate_init); node_type_update(&ntype, blender::nodes::geo_node_point_rotate_update); node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc index 1a7ab9817d9..99adce149e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc @@ -126,7 +126,7 @@ void register_node_type_geo_point_scale() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_point_scale_declare; node_type_init(&ntype, blender::nodes::geo_node_point_scale_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc index 1b0061346c4..48b6676c1dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc @@ -53,8 +53,8 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, Span<bool> masks, const bool invert) { - for (const std::string &name : in_component.attribute_names()) { - ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name); + for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) { + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type()); /* Only copy point attributes. Theoretically this could interpolate attributes on other @@ -65,7 +65,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, } OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( - name, ATTR_DOMAIN_POINT, data_type); + attribute_id, ATTR_DOMAIN_POINT, data_type); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); @@ -164,7 +164,8 @@ void register_node_type_geo_point_separate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_point_instance_declare; ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec; ntype.geometry_node_execute_supports_laziness = true; diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc index d187bf0fa71..f2fce45c57b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc @@ -95,7 +95,8 @@ void register_node_type_geo_point_translate() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_point_translate_init); node_type_update(&ntype, blender::nodes::geo_node_point_translate_update); node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index 8f34fff9f66..d920c8de9f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -263,7 +263,7 @@ void register_node_type_geo_points_to_volume() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); node_type_storage(&ntype, "NodeGeometryPointsToVolume", node_free_standard_storage, diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index ed7ed87fa46..401a478f04c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -308,7 +308,7 @@ void register_node_type_geo_raycast() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_init(&ntype, blender::nodes::geo_node_raycast_init); node_type_update(&ntype, blender::nodes::geo_node_raycast_update); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc new file mode 100644 index 00000000000..4c754ddb643 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -0,0 +1,79 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DEG_depsgraph_query.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_set_position_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Vector>("Position"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); + b.add_output<decl::Geometry>("Geometry"); +} + +static void set_position_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float3> &position_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + fn::FieldEvaluator position_evaluator{field_context, &selection}; + position_evaluator.add_with_destination(position_field, positions.varray()); + position_evaluator.evaluate(); + positions.save(); +} + +static void geo_node_set_position_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); + geometry = geometry_set_realize_instances(geometry); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float3> position_field = params.extract_input<Field<float3>>("Position"); + + for (const GeometryComponentType type : + {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) { + if (geometry.has(type)) { + set_position_in_component( + geometry.get_component_for_write(type), selection_field, position_field); + } + } + + params.set_output("Geometry", std::move(geometry)); +} + +} // namespace blender::nodes + +void register_node_type_geo_set_position() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec; + ntype.declare = blender::nodes::geo_node_set_position_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc index d127f7dc0ba..4541bf3569f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -37,14 +37,13 @@ static void geo_node_subdivision_surface_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { -#ifndef WITH_OPENSUBDIV - UNUSED_VARS(ptr); - uiItemL(layout, IFACE_("Disabled, built without OpenSubdiv"), ICON_ERROR); -#else +#ifdef WITH_OPENSUBDIV uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "uv_smooth", 0, nullptr, ICON_NONE); uiItemR(layout, ptr, "boundary_smooth", 0, nullptr, ICON_NONE); +#else + UNUSED_VARS(layout, ptr); #endif } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index d7423aa6d32..d5eb067cad0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -69,8 +69,7 @@ void transform_mesh(Mesh *mesh, else { const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); BKE_mesh_transform(mesh, matrix.values, false); - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + BKE_mesh_normals_tag_dirty(mesh); } } diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index 7487f11d77d..3b3b643d0ae 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -161,8 +161,10 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful { bke::geometry_set_instances_attribute_foreach( geometry_set, - [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { - this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type}); + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (attribute_id.is_named()) { + this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); + } return true; }, 8); diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index dff92d5884f..f6b6cc49b2e 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -16,6 +16,8 @@ #include "NOD_node_declaration.hh" +#include "BKE_node.h" + namespace blender::nodes { void NodeDeclaration::build(bNodeTree &ntree, bNode &node) const @@ -62,4 +64,31 @@ bNodeSocket &SocketDeclaration::update_or_build(bNodeTree &ntree, return this->build(ntree, node, (eNodeSocketInOut)socket.in_out); } +void SocketDeclaration::set_common_flags(bNodeSocket &socket) const +{ + SET_FLAG_FROM_TEST(socket.flag, hide_value_, SOCK_HIDE_VALUE); + SET_FLAG_FROM_TEST(socket.flag, hide_label_, SOCK_HIDE_LABEL); + SET_FLAG_FROM_TEST(socket.flag, is_multi_input_, SOCK_MULTI_INPUT); +} + +bool SocketDeclaration::matches_common_data(const bNodeSocket &socket) const +{ + if (socket.name != name_) { + return false; + } + if (socket.identifier != identifier_) { + return false; + } + if (((socket.flag & SOCK_HIDE_VALUE) != 0) != hide_value_) { + return false; + } + if (((socket.flag & SOCK_HIDE_LABEL) != 0) != hide_label_) { + return false; + } + if (((socket.flag & SOCK_MULTI_INPUT) != 0) != is_multi_input_) { + return false; + } + return true; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index d386781e3ce..31260f95242 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -48,6 +48,7 @@ #include "NOD_socket.h" #include "FN_cpp_type_make.hh" +#include "FN_field.hh" using namespace blender; using blender::nodes::SocketDeclarationPtr; @@ -268,11 +269,9 @@ void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user) return; } if (ntype->declare != nullptr) { - blender::nodes::NodeDeclaration node_decl; - blender::nodes::NodeDeclarationBuilder builder{node_decl}; - ntype->declare(builder); - if (!node_decl.matches(*node)) { - refresh_node(*ntree, *node, node_decl, do_id_user); + nodeDeclarationEnsure(ntree, node); + if (!node->declaration->matches(*node)) { + refresh_node(*ntree, *node, *node->declaration, do_id_user); } return; } @@ -701,8 +700,14 @@ static bNodeSocketType *make_socket_type_bool() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<bool>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + bool value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -713,8 +718,14 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<float>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + float value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -725,8 +736,14 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<int>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + int value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -737,8 +754,14 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<blender::float3>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + blender::float3 value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<blender::float3>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -751,8 +774,15 @@ static bNodeSocketType *make_socket_type_rgba() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { *(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<blender::ColorGeometry4f>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + blender::ColorGeometry4f value; + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) + blender::fn::Field<blender::ColorGeometry4f>(blender::fn::make_constant_field(value)); + }; return socktype; } @@ -763,8 +793,15 @@ static bNodeSocketType *make_socket_type_string() socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) { new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value); }; - socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type; - socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value; + socktype->get_geometry_nodes_cpp_type = []() { + return &blender::fn::CPPType::get<blender::fn::Field<std::string>>(); + }; + socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) { + std::string value; + value.~basic_string(); + socket.typeinfo->get_base_cpp_value(socket, &value); + new (r_value) blender::fn::Field<std::string>(blender::fn::make_constant_field(value)); + }; return socktype; } diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 418fed146fb..4b0dbad3cff 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -38,6 +38,7 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_FLOAT, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -47,16 +48,13 @@ bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out bool Float::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_FLOAT) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_FLOAT) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; @@ -77,6 +75,7 @@ bNodeSocket &Float::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket & if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -92,6 +91,7 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_INT, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -101,16 +101,13 @@ bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) bool Int::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_INT) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_INT) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; @@ -131,6 +128,7 @@ bNodeSocket &Int::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &so if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value; value.min = soft_min_value_; value.max = soft_max_value_; @@ -146,23 +144,23 @@ bNodeSocket &Vector::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_VECTOR, subtype_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value; copy_v3_v3(value.value, default_value_); + value.min = soft_min_value_; + value.max = soft_max_value_; return socket; } bool Vector::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_VECTOR) { - return false; - } - if (socket.typeinfo->subtype != subtype_) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { + if (socket.type != SOCK_VECTOR) { return false; } - if (socket.identifier != identifier_) { + if (socket.typeinfo->subtype != subtype_) { return false; } return true; @@ -176,6 +174,7 @@ bNodeSocket &Vector::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket if (socket.typeinfo->subtype != subtype_) { modify_subtype_except_for_storage(socket, subtype_); } + this->set_common_flags(socket); bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value; value.subtype = subtype_; STRNCPY(socket.name, name_.c_str()); @@ -190,6 +189,7 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_BOOLEAN, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueBoolean &value = *(bNodeSocketValueBoolean *)socket.default_value; value.value = default_value_; return socket; @@ -197,13 +197,10 @@ bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) bool Bool::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_BOOLEAN) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_BOOLEAN) { return false; } return true; @@ -217,6 +214,7 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_RGBA, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); bNodeSocketValueRGBA &value = *(bNodeSocketValueRGBA *)socket.default_value; copy_v4_v4(value.value, default_value_); return socket; @@ -224,13 +222,15 @@ bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out bool Color::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_RGBA) { - return false; - } - if (socket.name != name_) { - return false; + if (!this->matches_common_data(socket)) { + if (socket.name != name_) { + return false; + } + if (socket.identifier != identifier_) { + return false; + } } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_RGBA) { return false; } return true; @@ -244,18 +244,16 @@ bNodeSocket &String::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ou { bNodeSocket &socket = *nodeAddStaticSocket( &ntree, &node, in_out, SOCK_STRING, PROP_NONE, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } bool String::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_STRING) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_STRING) { return false; } return true; @@ -265,42 +263,37 @@ bool String::matches(const bNodeSocket &socket) const * IDSocketDeclaration. */ -namespace detail { -bNodeSocket &build_id_socket(bNodeTree &ntree, - bNode &node, - eNodeSocketInOut in_out, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier) +bNodeSocket &IDSocketDeclaration::build(bNodeTree &ntree, + bNode &node, + eNodeSocketInOut in_out) const { bNodeSocket &socket = *nodeAddSocket( - &ntree, &node, in_out, data.idname, name.c_str(), identifier.c_str()); - if (data.hide_label) { - socket.flag |= SOCK_HIDE_LABEL; - } + &ntree, &node, in_out, idname_, identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } -bool matches_id_socket(const bNodeSocket &socket, - const CommonIDSocketData &data, - StringRefNull name, - StringRefNull identifier) +bool IDSocketDeclaration::matches(const bNodeSocket &socket) const { - if (!STREQ(socket.idname, data.idname)) { - return false; - } - if (data.hide_label != ((socket.flag & SOCK_HIDE_LABEL) != 0)) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name) { - return false; - } - if (socket.identifier != identifier) { + if (!STREQ(socket.idname, idname_)) { return false; } return true; } -} // namespace detail + +bNodeSocket &IDSocketDeclaration::update_or_build(bNodeTree &ntree, + bNode &node, + bNodeSocket &socket) const +{ + if (StringRef(socket.idname) != idname_) { + return this->build(ntree, node, (eNodeSocketInOut)socket.in_out); + } + this->set_common_flags(socket); + return socket; +} /* -------------------------------------------------------------------- * Geometry. @@ -310,18 +303,16 @@ bNodeSocket &Geometry::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_ { bNodeSocket &socket = *nodeAddSocket( &ntree, &node, in_out, "NodeSocketGeometry", identifier_.c_str(), name_.c_str()); + this->set_common_flags(socket); return socket; } bool Geometry::matches(const bNodeSocket &socket) const { - if (socket.type != SOCK_GEOMETRY) { + if (!this->matches_common_data(socket)) { return false; } - if (socket.name != name_) { - return false; - } - if (socket.identifier != identifier_) { + if (socket.type != SOCK_GEOMETRY) { return false; } return true; diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc index e3d4bad2bf8..0c0d75179a9 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_gradient.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_gradient.cc @@ -43,7 +43,8 @@ static bNodeSocketTemplate sh_node_tex_gradient_out[] = { static void node_shader_init_tex_gradient(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexGradient *tex = MEM_callocN(sizeof(NodeTexGradient), "NodeTexGradient"); + NodeTexGradient *tex = (NodeTexGradient *)MEM_callocN(sizeof(NodeTexGradient), + "NodeTexGradient"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->gradient_type = SHD_BLEND_LINEAR; diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc index 420c5b75926..f5e9aef3aad 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc @@ -49,7 +49,8 @@ static bNodeSocketTemplate sh_node_tex_musgrave_out[] = { static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexMusgrave *tex = MEM_callocN(sizeof(NodeTexMusgrave), "NodeTexMusgrave"); + NodeTexMusgrave *tex = (NodeTexMusgrave *)MEM_callocN(sizeof(NodeTexMusgrave), + "NodeTexMusgrave"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->musgrave_type = SHD_MUSGRAVE_FBM; @@ -58,6 +59,41 @@ static void node_shader_init_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) node->storage = tex; } +static const char *gpu_shader_name_get(const int type, const int dimensions) +{ + BLI_assert(type >= 0 && type < 5); + BLI_assert(dimensions > 0 && dimensions < 5); + + switch (type) { + case SHD_MUSGRAVE_MULTIFRACTAL: + return std::array{"node_tex_musgrave_multi_fractal_1d", + "node_tex_musgrave_multi_fractal_2d", + "node_tex_musgrave_multi_fractal_3d", + "node_tex_musgrave_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_FBM: + return std::array{"node_tex_musgrave_fBm_1d", + "node_tex_musgrave_fBm_2d", + "node_tex_musgrave_fBm_3d", + "node_tex_musgrave_fBm_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_HYBRID_MULTIFRACTAL: + return std::array{"node_tex_musgrave_hybrid_multi_fractal_1d", + "node_tex_musgrave_hybrid_multi_fractal_2d", + "node_tex_musgrave_hybrid_multi_fractal_3d", + "node_tex_musgrave_hybrid_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_RIDGED_MULTIFRACTAL: + return std::array{"node_tex_musgrave_ridged_multi_fractal_1d", + "node_tex_musgrave_ridged_multi_fractal_2d", + "node_tex_musgrave_ridged_multi_fractal_3d", + "node_tex_musgrave_ridged_multi_fractal_4d"}[dimensions - 1]; + case SHD_MUSGRAVE_HETERO_TERRAIN: + return std::array{"node_tex_musgrave_hetero_terrain_1d", + "node_tex_musgrave_hetero_terrain_2d", + "node_tex_musgrave_hetero_terrain_3d", + "node_tex_musgrave_hetero_terrain_4d"}[dimensions - 1]; + } + return nullptr; +} + static int node_shader_gpu_tex_musgrave(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), @@ -71,53 +107,9 @@ static int node_shader_gpu_tex_musgrave(GPUMaterial *mat, int dimensions = tex->dimensions; int type = tex->musgrave_type; - static const char *names[][5] = { - [SHD_MUSGRAVE_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_multi_fractal_1d", - "node_tex_musgrave_multi_fractal_2d", - "node_tex_musgrave_multi_fractal_3d", - "node_tex_musgrave_multi_fractal_4d", - }, - [SHD_MUSGRAVE_FBM] = - { - "", - "node_tex_musgrave_fBm_1d", - "node_tex_musgrave_fBm_2d", - "node_tex_musgrave_fBm_3d", - "node_tex_musgrave_fBm_4d", - }, - [SHD_MUSGRAVE_HYBRID_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_hybrid_multi_fractal_1d", - "node_tex_musgrave_hybrid_multi_fractal_2d", - "node_tex_musgrave_hybrid_multi_fractal_3d", - "node_tex_musgrave_hybrid_multi_fractal_4d", - }, - [SHD_MUSGRAVE_RIDGED_MULTIFRACTAL] = - { - "", - "node_tex_musgrave_ridged_multi_fractal_1d", - "node_tex_musgrave_ridged_multi_fractal_2d", - "node_tex_musgrave_ridged_multi_fractal_3d", - "node_tex_musgrave_ridged_multi_fractal_4d", - }, - [SHD_MUSGRAVE_HETERO_TERRAIN] = - { - "", - "node_tex_musgrave_hetero_terrain_1d", - "node_tex_musgrave_hetero_terrain_2d", - "node_tex_musgrave_hetero_terrain_3d", - "node_tex_musgrave_hetero_terrain_4d", - }, - }; - - BLI_assert(type >= 0 && type < 5); - BLI_assert(dimensions > 0 && dimensions < 5); + const char *name = gpu_shader_name_get(type, dimensions); - return GPU_stack_link(mat, node, names[type][dimensions], in, out); + return GPU_stack_link(mat, node, name, in, out); } static void node_shader_update_tex_musgrave(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc index 7b67c2d1f2e..de8e0916f4d 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc @@ -48,7 +48,7 @@ static bNodeSocketTemplate sh_node_tex_noise_out[] = { static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexNoise *tex = MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise"); + NodeTexNoise *tex = (NodeTexNoise *)MEM_callocN(sizeof(NodeTexNoise), "NodeTexNoise"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->dimensions = 3; @@ -56,6 +56,16 @@ static void node_shader_init_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) node->storage = tex; } +static const char *gpu_shader_get_name(const int dimensions) +{ + BLI_assert(dimensions >= 1 && dimensions <= 4); + return std::array{"node_noise_texture_1d", + "node_noise_texture_2d", + "node_noise_texture_3d", + "node_noise_texture_4d"}[dimensions - 1]; + return nullptr; +} + static int node_shader_gpu_tex_noise(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), @@ -66,14 +76,8 @@ static int node_shader_gpu_tex_noise(GPUMaterial *mat, node_shader_gpu_tex_mapping(mat, node, in, out); NodeTexNoise *tex = (NodeTexNoise *)node->storage; - static const char *names[] = { - "", - "node_noise_texture_1d", - "node_noise_texture_2d", - "node_noise_texture_3d", - "node_noise_texture_4d", - }; - return GPU_stack_link(mat, node, names[tex->dimensions], in, out); + const char *name = gpu_shader_get_name(tex->dimensions); + return GPU_stack_link(mat, node, name, in, out); } static void node_shader_update_tex_noise(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc index 64dc44fc67d..1cc715d99ea 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc @@ -69,7 +69,7 @@ static bNodeSocketTemplate sh_node_tex_voronoi_out[] = { static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTexVoronoi *tex = MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi"); + NodeTexVoronoi *tex = (NodeTexVoronoi *)MEM_callocN(sizeof(NodeTexVoronoi), "NodeTexVoronoi"); BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); BKE_texture_colormapping_default(&tex->base.color_mapping); tex->dimensions = 3; @@ -79,6 +79,51 @@ static void node_shader_init_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) node->storage = tex; } +static const char *gpu_shader_get_name(const int feature, const int dimensions) +{ + BLI_assert(feature >= 0 && feature < 5); + BLI_assert(dimensions > 0 && dimensions < 5); + + switch (feature) { + case SHD_VORONOI_F1: + return std::array{ + "node_tex_voronoi_f1_1d", + "node_tex_voronoi_f1_2d", + "node_tex_voronoi_f1_3d", + "node_tex_voronoi_f1_4d", + }[dimensions - 1]; + case SHD_VORONOI_F2: + return std::array{ + "node_tex_voronoi_f2_1d", + "node_tex_voronoi_f2_2d", + "node_tex_voronoi_f2_3d", + "node_tex_voronoi_f2_4d", + }[dimensions - 1]; + case SHD_VORONOI_SMOOTH_F1: + return std::array{ + "node_tex_voronoi_smooth_f1_1d", + "node_tex_voronoi_smooth_f1_2d", + "node_tex_voronoi_smooth_f1_3d", + "node_tex_voronoi_smooth_f1_4d", + }[dimensions - 1]; + case SHD_VORONOI_DISTANCE_TO_EDGE: + return std::array{ + "node_tex_voronoi_distance_to_edge_1d", + "node_tex_voronoi_distance_to_edge_2d", + "node_tex_voronoi_distance_to_edge_3d", + "node_tex_voronoi_distance_to_edge_4d", + }[dimensions - 1]; + case SHD_VORONOI_N_SPHERE_RADIUS: + return std::array{ + "node_tex_voronoi_n_sphere_radius_1d", + "node_tex_voronoi_n_sphere_radius_2d", + "node_tex_voronoi_n_sphere_radius_3d", + "node_tex_voronoi_n_sphere_radius_4d", + }[dimensions - 1]; + } + return nullptr; +} + static int node_shader_gpu_tex_voronoi(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), @@ -88,57 +133,12 @@ static int node_shader_gpu_tex_voronoi(GPUMaterial *mat, node_shader_gpu_default_tex_coord(mat, node, &in[0].link); node_shader_gpu_tex_mapping(mat, node, in, out); - static const char *names[][5] = { - [SHD_VORONOI_F1] = - { - "", - "node_tex_voronoi_f1_1d", - "node_tex_voronoi_f1_2d", - "node_tex_voronoi_f1_3d", - "node_tex_voronoi_f1_4d", - }, - [SHD_VORONOI_F2] = - { - "", - "node_tex_voronoi_f2_1d", - "node_tex_voronoi_f2_2d", - "node_tex_voronoi_f2_3d", - "node_tex_voronoi_f2_4d", - }, - [SHD_VORONOI_SMOOTH_F1] = - { - "", - "node_tex_voronoi_smooth_f1_1d", - "node_tex_voronoi_smooth_f1_2d", - "node_tex_voronoi_smooth_f1_3d", - "node_tex_voronoi_smooth_f1_4d", - }, - [SHD_VORONOI_DISTANCE_TO_EDGE] = - { - "", - "node_tex_voronoi_distance_to_edge_1d", - "node_tex_voronoi_distance_to_edge_2d", - "node_tex_voronoi_distance_to_edge_3d", - "node_tex_voronoi_distance_to_edge_4d", - }, - [SHD_VORONOI_N_SPHERE_RADIUS] = - { - "", - "node_tex_voronoi_n_sphere_radius_1d", - "node_tex_voronoi_n_sphere_radius_2d", - "node_tex_voronoi_n_sphere_radius_3d", - "node_tex_voronoi_n_sphere_radius_4d", - }, - }; - NodeTexVoronoi *tex = (NodeTexVoronoi *)node->storage; float metric = tex->distance; - BLI_assert(tex->feature >= 0 && tex->feature < 5); - BLI_assert(tex->dimensions > 0 && tex->dimensions < 5); + const char *name = gpu_shader_get_name(tex->feature, tex->dimensions); - return GPU_stack_link( - mat, node, names[tex->feature][tex->dimensions], in, out, GPU_constant(&metric)); + return GPU_stack_link(mat, node, name, in, out, GPU_constant(&metric)); } static void node_shader_update_tex_voronoi(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc index 60a3392c761..6e973189065 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc @@ -37,25 +37,23 @@ static void node_shader_init_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *no node->custom1 = 3; } +static const char *gpu_shader_get_name(const int dimensions) +{ + BLI_assert(dimensions >= 1 && dimensions <= 4); + return std::array{"node_white_noise_1d", + "node_white_noise_2d", + "node_white_noise_3d", + "node_white_noise_4d"}[dimensions - 1]; +} + static int gpu_shader_tex_white_noise(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), GPUNodeStack *in, GPUNodeStack *out) { - static const char *names[] = { - "", - "node_white_noise_1d", - "node_white_noise_2d", - "node_white_noise_3d", - "node_white_noise_4d", - }; - - if (node->custom1 < ARRAY_SIZE(names) && names[node->custom1]) { - return GPU_stack_link(mat, node, names[node->custom1], in, out); - } - - return 0; + const char *name = gpu_shader_get_name(node->custom1); + return GPU_stack_link(mat, node, name, in, out); } static void node_shader_update_tex_white_noise(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/python/generic/bpy_threads.c b/source/blender/python/generic/bpy_threads.c index bd707f728a1..8aa8c5c5d92 100644 --- a/source/blender/python/generic/bpy_threads.c +++ b/source/blender/python/generic/bpy_threads.c @@ -29,8 +29,12 @@ /* analogue of PyEval_SaveThread() */ BPy_ThreadStatePtr BPY_thread_save(void) { - /* The thread-state can be NULL when quitting Blender. */ - if (_PyThreadState_UncheckedGet()) { + /* Use `_PyThreadState_UncheckedGet()` instead of `PyThreadState_Get()`, to avoid a fatal error + * issued when a thread state is NULL (the thread state can be NULL when quitting Blender). + * + * `PyEval_SaveThread()` will release the GIL, so this thread has to have the GIL to begin with + * or badness will ensue. */ + if (_PyThreadState_UncheckedGet() && PyGILState_Check()) { return (BPy_ThreadStatePtr)PyEval_SaveThread(); } return NULL; diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c index 4296474c011..8dc0d2fb857 100644 --- a/source/blender/python/generic/idprop_py_api.c +++ b/source/blender/python/generic/idprop_py_api.c @@ -533,6 +533,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (((prop_data[i] = PyFloat_AsDouble(item)) == -1.0) && PyErr_Occurred()) { + IDP_FreeProperty(prop); return NULL; } } @@ -545,6 +546,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (((prop_data[i] = PyC_Long_AsI32(item)) == -1) && PyErr_Occurred()) { + IDP_FreeProperty(prop); return NULL; } } @@ -555,6 +557,7 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) for (i = 0; i < val.array.len; i++) { item = ob_seq_fast_items[i]; if (BPy_IDProperty_Map_ValidateAndCreate(NULL, prop, item) == false) { + IDP_FreeProperty(prop); return NULL; } } diff --git a/source/blender/python/generic/idprop_py_ui_api.c b/source/blender/python/generic/idprop_py_ui_api.c index 42856d88472..7827bd48dfe 100644 --- a/source/blender/python/generic/idprop_py_ui_api.c +++ b/source/blender/python/generic/idprop_py_ui_api.c @@ -468,7 +468,7 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict) Py_DECREF(list); } else { - PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->step)); + PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value)); Py_DECREF(item); } } @@ -499,7 +499,7 @@ static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict) Py_DECREF(list); } else { - PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->step)); + PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->default_value)); Py_DECREF(item); } } diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c index 0fef59d6352..abfde7b48c8 100644 --- a/source/blender/python/gpu/gpu_py_buffer.c +++ b/source/blender/python/gpu/gpu_py_buffer.c @@ -485,7 +485,7 @@ static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, int i, PyObject *v) case GPU_DATA_UINT: case GPU_DATA_UINT_24_8: case GPU_DATA_10_11_11_REV: - return PyArg_Parse(v, "b:Expected ints", &self->buf.as_uint[i]) ? 0 : -1; + return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1; default: return 0; /* should never happen */ } diff --git a/source/blender/python/gpu/gpu_py_shader.c b/source/blender/python/gpu/gpu_py_shader.c index d2167f2f102..1bdf9766c1b 100644 --- a/source/blender/python/gpu/gpu_py_shader.c +++ b/source/blender/python/gpu/gpu_py_shader.c @@ -76,21 +76,6 @@ static const struct PyC_StringEnumItems pygpu_shader_config_items[] = { {0, NULL}, }; -static const struct PyC_FlagSet pygpu_texture_samplerstate_items[] = { - {GPU_SAMPLER_DEFAULT, "DEFAULT"}, - {GPU_SAMPLER_FILTER, "FILTER"}, - {GPU_SAMPLER_MIPMAP, "MIPMAP"}, - {GPU_SAMPLER_REPEAT_S, "REPEAT_S"}, - {GPU_SAMPLER_REPEAT_T, "REPEAT_T"}, - {GPU_SAMPLER_REPEAT_R, "REPEAT_R"}, - {GPU_SAMPLER_CLAMP_BORDER, "CLAMP_BORDER"}, - {GPU_SAMPLER_COMPARE, "COMPARE"}, - {GPU_SAMPLER_ANISO, "ANISO"}, - {GPU_SAMPLER_ICON, "ICON"}, - {GPU_SAMPLER_REPEAT, "REPEAT"}, - {0, NULL}, -}; - static int pygpu_shader_uniform_location_get(GPUShader *shader, const char *name, const char *error_prefix) @@ -120,12 +105,13 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args const char *geocode; const char *libcode; const char *defines; + const char *name; } params = {0}; static const char *_keywords[] = { - "vertexcode", "fragcode", "geocode", "libcode", "defines", NULL}; + "vertexcode", "fragcode", "geocode", "libcode", "defines", "name", NULL}; - static _PyArg_Parser _parser = {"ss|$sss:GPUShader.__new__", _keywords, 0}; + static _PyArg_Parser _parser = {"ss|$ssss:GPUShader.__new__", _keywords, 0}; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, @@ -133,12 +119,17 @@ static PyObject *pygpu_shader__tp_new(PyTypeObject *UNUSED(type), PyObject *args ¶ms.fragcode, ¶ms.geocode, ¶ms.libcode, - ¶ms.defines)) { + ¶ms.defines, + ¶ms.name)) { return NULL; } - GPUShader *shader = GPU_shader_create_from_python( - params.vertexcode, params.fragcode, params.geocode, params.libcode, params.defines); + GPUShader *shader = GPU_shader_create_from_python(params.vertexcode, + params.fragcode, + params.geocode, + params.libcode, + params.defines, + params.name); if (shader == NULL) { PyErr_SetString(PyExc_Exception, "Shader Compile Error, see console for more details"); @@ -507,53 +498,25 @@ static PyObject *pygpu_shader_uniform_int(BPyGPUShader *self, PyObject *args) } PyDoc_STRVAR(pygpu_shader_uniform_sampler_doc, - ".. method:: uniform_sampler(name, texture, state={'DEFAULT'})\n" + ".. method:: uniform_sampler(name, texture)\n" "\n" - " Specify the texture and state for an uniform sampler in the current GPUShader.\n" + " Specify the value of a texture uniform variable for the current GPUShader.\n" "\n" " :param name: name of the uniform variable whose texture is to be specified.\n" " :type name: str\n" " :param texture: Texture to attach.\n" - " :type texture: :class:`gpu.types.GPUTexture`\n" - " :param state: set of values in:\n" - "\n" - " - ``DEFAULT``\n" - " - ``FILTER``\n" - " - ``MIPMAP``\n" - " - ``REPEAT_S``\n" - " - ``REPEAT_T``\n" - " - ``REPEAT_R``\n" - " - ``CLAMP_BORDER``\n" - " - ``COMPARE``\n" - " - ``ANISO``\n" - " - ``ICON``\n" - " - ``REPEAT``\n" - " :type state: set\n"); -static PyObject *pygpu_shader_uniform_sampler(BPyGPUShader *self, PyObject *args, PyObject *kwds) + " :type texture: :class:`gpu.types.GPUTexture`\n"); +static PyObject *pygpu_shader_uniform_sampler(BPyGPUShader *self, PyObject *args) { const char *name; BPyGPUTexture *py_texture; - PyObject *py_samplerstate = NULL; - - static const char *_keywords[] = {"name", "texture", "state", NULL}; - static _PyArg_Parser _parser = {"sO!|$O:uniform_sampler", _keywords, 0}; - if (!_PyArg_ParseTupleAndKeywordsFast( - args, kwds, &_parser, &name, &BPyGPUTexture_Type, &py_texture, &py_samplerstate)) { + if (!PyArg_ParseTuple( + args, "sO!:GPUShader.uniform_sampler", &name, &BPyGPUTexture_Type, &py_texture)) { return NULL; } - int sampler_state = GPU_SAMPLER_DEFAULT; - if (py_samplerstate) { - if (PyC_FlagSet_ToBitfield(pygpu_texture_samplerstate_items, - py_samplerstate, - &sampler_state, - "shader.uniform_sampler") == -1) { - return NULL; - } - } - int slot = GPU_shader_get_texture_binding(self->shader, name); - GPU_texture_bind_ex(py_texture->tex, (eGPUSamplerState)sampler_state, slot, false); + GPU_texture_bind(py_texture->tex, slot); GPU_shader_uniform_1i(self->shader, name, slot); Py_RETURN_NONE; @@ -665,7 +628,7 @@ static struct PyMethodDef pygpu_shader__tp_methods[] = { pygpu_shader_uniform_int_doc}, {"uniform_sampler", (PyCFunction)pygpu_shader_uniform_sampler, - METH_VARARGS | METH_KEYWORDS, + METH_VARARGS, pygpu_shader_uniform_sampler_doc}, {"uniform_block", (PyCFunction)pygpu_shader_uniform_block, @@ -682,6 +645,13 @@ static struct PyMethodDef pygpu_shader__tp_methods[] = { {NULL, NULL, 0, NULL}, }; +PyDoc_STRVAR(pygpu_shader_name_doc, + "The name of the shader object for debugging purposes (read-only).\n\n:type: str"); +static PyObject *pygpu_shader_name(BPyGPUShader *self) +{ + return PyUnicode_FromString(GPU_shader_get_name(self->shader)); +} + PyDoc_STRVAR( pygpu_shader_program_doc, "The name of the program object for use by the OpenGL API (read-only).\n\n:type: int"); @@ -692,6 +662,7 @@ static PyObject *pygpu_shader_program_get(BPyGPUShader *self, void *UNUSED(closu static PyGetSetDef pygpu_shader__tp_getseters[] = { {"program", (getter)pygpu_shader_program_get, (setter)NULL, pygpu_shader_program_doc, NULL}, + {"name", (getter)pygpu_shader_name, (setter)NULL, pygpu_shader_name_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; @@ -705,7 +676,8 @@ static void pygpu_shader__tp_dealloc(BPyGPUShader *self) PyDoc_STRVAR( pygpu_shader__tp_doc, - ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None)\n" + ".. class:: GPUShader(vertexcode, fragcode, geocode=None, libcode=None, defines=None, " + "name='pyGPUShader')\n" "\n" " GPUShader combines multiple GLSL shaders into a program used for drawing.\n" " It must contain at least a vertex and fragment shaders.\n" @@ -731,6 +703,8 @@ PyDoc_STRVAR( " :param libcode: Code with functions and presets to be shared between shaders.\n" " :type value: str\n" " :param defines: Preprocessor directives.\n" + " :type value: str\n" + " :param name: Name of shader code, for debugging purposes.\n" " :type value: str\n"); PyTypeObject BPyGPUShader_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUShader", diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 68731a91dc9..7a93a076621 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -753,7 +753,7 @@ int BPY_context_member_get(bContext *C, const char *member, bContextDataResult * CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not a valid type", member); } else { - CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found\n", member); + CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found", member); } } else { diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 9d0755a865d..35acb56e66a 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -3663,7 +3663,7 @@ static PyObject *pyrna_struct_path_resolve(BPy_StructRNA *self, PyObject *args) return NULL; } - if (RNA_path_resolve_full(&self->ptr, path, &r_ptr, &r_prop, &index)) { + if (RNA_path_resolve_full_maybe_null(&self->ptr, path, &r_ptr, &r_prop, &index)) { if (r_prop) { if (index != -1) { if (index >= RNA_property_array_length(&r_ptr, r_prop) || index < 0) { diff --git a/source/blender/python/mathutils/mathutils.c b/source/blender/python/mathutils/mathutils.c index be7dae6871b..0043fc36162 100644 --- a/source/blender/python/mathutils/mathutils.c +++ b/source/blender/python/mathutils/mathutils.c @@ -599,6 +599,15 @@ uchar Mathutils_RegisterCallback(Mathutils_Callback *cb) return i; } +int _BaseMathObject_CheckCallback(BaseMathObject *self) +{ + Mathutils_Callback *cb = mathutils_callbacks[self->cb_type]; + if (LIKELY(cb->check(self) != -1)) { + return 0; + } + return -1; +} + /* use macros to check for NULL */ int _BaseMathObject_ReadCallback(BaseMathObject *self) { @@ -687,6 +696,13 @@ PyObject *BaseMathObject_is_frozen_get(BaseMathObject *self, void *UNUSED(closur return PyBool_FromLong((self->flag & BASE_MATH_FLAG_IS_FROZEN) != 0); } +char BaseMathObject_is_valid_doc[] = + "True when the owner of this data is valid.\n\n:type: boolean"; +PyObject *BaseMathObject_is_valid_get(BaseMathObject *self, void *UNUSED(closure)) +{ + return PyBool_FromLong(BaseMath_CheckCallback(self) == 0); +} + char BaseMathObject_freeze_doc[] = ".. function:: freeze()\n" "\n" diff --git a/source/blender/python/mathutils/mathutils.h b/source/blender/python/mathutils/mathutils.h index 80be841785a..4aa26dcc5be 100644 --- a/source/blender/python/mathutils/mathutils.h +++ b/source/blender/python/mathutils/mathutils.h @@ -28,6 +28,7 @@ struct DynStr; extern char BaseMathObject_is_wrapped_doc[]; extern char BaseMathObject_is_frozen_doc[]; +extern char BaseMathObject_is_valid_doc[]; extern char BaseMathObject_owner_doc[]; #define BASE_MATH_NEW(struct_name, root_type, base_type) \ @@ -81,6 +82,7 @@ typedef struct { PyObject *BaseMathObject_owner_get(BaseMathObject *self, void *); PyObject *BaseMathObject_is_wrapped_get(BaseMathObject *self, void *); PyObject *BaseMathObject_is_frozen_get(BaseMathObject *self, void *); +PyObject *BaseMathObject_is_valid_get(BaseMathObject *self, void *); extern char BaseMathObject_freeze_doc[]; PyObject *BaseMathObject_freeze(BaseMathObject *self); @@ -117,6 +119,7 @@ struct Mathutils_Callback { unsigned char Mathutils_RegisterCallback(Mathutils_Callback *cb); +int _BaseMathObject_CheckCallback(BaseMathObject *self); int _BaseMathObject_ReadCallback(BaseMathObject *self); int _BaseMathObject_WriteCallback(BaseMathObject *self); int _BaseMathObject_ReadIndexCallback(BaseMathObject *self, int index); @@ -126,6 +129,8 @@ void _BaseMathObject_RaiseFrozenExc(const BaseMathObject *self); void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self); /* since this is called so often avoid where possible */ +#define BaseMath_CheckCallback(_self) \ + (((_self)->cb_user ? _BaseMathObject_CheckCallback((BaseMathObject *)_self) : 0)) #define BaseMath_ReadCallback(_self) \ (((_self)->cb_user ? _BaseMathObject_ReadCallback((BaseMathObject *)_self) : 0)) #define BaseMath_WriteCallback(_self) \ diff --git a/source/blender/python/mathutils/mathutils_Color.c b/source/blender/python/mathutils/mathutils_Color.c index 7546f2ef730..13d712bddb0 100644 --- a/source/blender/python/mathutils/mathutils_Color.c +++ b/source/blender/python/mathutils/mathutils_Color.c @@ -866,6 +866,11 @@ static PyGetSetDef Color_getseters[] = { (setter)NULL, BaseMathObject_is_frozen_doc, NULL}, + {"is_valid", + (getter)BaseMathObject_is_valid_get, + (setter)NULL, + BaseMathObject_is_valid_doc, + NULL}, {"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; diff --git a/source/blender/python/mathutils/mathutils_Euler.c b/source/blender/python/mathutils/mathutils_Euler.c index 595d03b533b..1033d186fca 100644 --- a/source/blender/python/mathutils/mathutils_Euler.c +++ b/source/blender/python/mathutils/mathutils_Euler.c @@ -699,6 +699,11 @@ static PyGetSetDef Euler_getseters[] = { (setter)NULL, BaseMathObject_is_frozen_doc, NULL}, + {"is_valid", + (getter)BaseMathObject_is_valid_get, + (setter)NULL, + BaseMathObject_is_valid_doc, + NULL}, {"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 36b8b0b6d35..ce04a143aae 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -3148,6 +3148,11 @@ static PyGetSetDef Matrix_getseters[] = { (setter)NULL, BaseMathObject_is_frozen_doc, NULL}, + {"is_valid", + (getter)BaseMathObject_is_valid_get, + (setter)NULL, + BaseMathObject_is_valid_doc, + NULL}, {"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; diff --git a/source/blender/python/mathutils/mathutils_Quaternion.c b/source/blender/python/mathutils/mathutils_Quaternion.c index 77a30dcd447..525b2da7d06 100644 --- a/source/blender/python/mathutils/mathutils_Quaternion.c +++ b/source/blender/python/mathutils/mathutils_Quaternion.c @@ -1505,6 +1505,11 @@ static PyGetSetDef Quaternion_getseters[] = { (setter)NULL, BaseMathObject_is_frozen_doc, NULL}, + {"is_valid", + (getter)BaseMathObject_is_valid_get, + (setter)NULL, + BaseMathObject_is_valid_doc, + NULL}, {"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; diff --git a/source/blender/python/mathutils/mathutils_Vector.c b/source/blender/python/mathutils/mathutils_Vector.c index efcaa9b6a51..23758c5603e 100644 --- a/source/blender/python/mathutils/mathutils_Vector.c +++ b/source/blender/python/mathutils/mathutils_Vector.c @@ -2551,6 +2551,11 @@ static PyGetSetDef Vector_getseters[] = { (setter)NULL, BaseMathObject_is_frozen_doc, NULL}, + {"is_valid", + (getter)BaseMathObject_is_valid_get, + (setter)NULL, + BaseMathObject_is_valid_doc, + NULL}, {"owner", (getter)BaseMathObject_owner_get, (setter)NULL, BaseMathObject_owner_doc, NULL}, /* Auto-generated swizzle attributes, see Python script above. */ diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 481a6662cc0..5728b784714 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -365,6 +365,17 @@ RenderResult *RE_engine_begin_result( return result; } +static void re_ensure_passes_allocated_thread_safe(Render *re) +{ + if (!re->result->passes_allocated) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + if (!re->result->passes_allocated) { + render_result_passes_allocated_ensure(re->result); + } + BLI_rw_mutex_unlock(&re->resultmutex); + } +} + void RE_engine_update_result(RenderEngine *engine, RenderResult *result) { if (engine->bake.pixels) { @@ -375,6 +386,7 @@ void RE_engine_update_result(RenderEngine *engine, RenderResult *result) Render *re = engine->re; if (result) { + re_ensure_passes_allocated_thread_safe(re); render_result_merge(re->result, result); result->renlay = result->layers.first; /* weak, draws first layer always */ re->display_update(re->duh, result, NULL); @@ -412,13 +424,7 @@ void RE_engine_end_result( return; } - if (!re->result->passes_allocated) { - BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); - if (!re->result->passes_allocated) { - render_result_passes_allocated_ensure(re->result); - } - BLI_rw_mutex_unlock(&re->resultmutex); - } + re_ensure_passes_allocated_thread_safe(re); /* merge. on break, don't merge in result for preview renders, looks nicer */ if (!highlight) { diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index c29ab342ed7..6cb6aabe885 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -250,6 +250,9 @@ RenderPass *render_layer_add_pass(RenderResult *rr, BLI_addtail(&rl->passes, rpass); + /* The result contains non-allocated pass now, so tag it as such. */ + rr->passes_allocated = false; + return rpass; } diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 80314d34360..4448db013fe 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3188,7 +3188,7 @@ float seq_speed_effect_target_frame_get(Scene *scene, case SEQ_SPEED_STRETCH: { /* Only right handle controls effect speed! */ const float target_content_length = seq_effect_speed_get_strip_content_length(source) - - source->startofs; + source->startofs; const float speed_effetct_length = seq_speed->enddisp - seq_speed->startdisp; const float ratio = frame_index / speed_effetct_length; target_frame = target_content_length * ratio; diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 8a1ff67b37c..c1730957432 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -925,7 +925,7 @@ typedef struct wmDragID { } wmDragID; typedef struct wmDragAsset { - /* Note: Can't store the AssetHandle here, since the FileDirEntry it wraps may be freed while + /* NOTE: Can't store the #AssetHandle here, since the #FileDirEntry it wraps may be freed while * dragging. So store necessary data here directly. */ char name[64]; /* MAX_NAME */ diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h index eab62ffce4c..a1edc4196dc 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h @@ -115,8 +115,15 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { WM_GIZMOGROUPTYPE_SELECT = (1 << 3), /** The gizmo group is to be kept (not removed on loading a new file for eg). */ WM_GIZMOGROUPTYPE_PERSISTENT = (1 << 4), - /** Show all other gizmos when interacting. */ + /** + * Show all other gizmos when interacting. + * Also show this group when another group is being interacted with. + */ WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL = (1 << 5), + + /** Don't draw this gizmo group when it is modal. */ + WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE = (1 << 6), + /** * When used with tool, only run when activating the tool, * instead of linking the gizmo while the tool is active. @@ -127,7 +134,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * when a tool can activate multiple operators based on the key-map. * We could even move the options into the key-map item. * ~ campbell. */ - WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 6), + WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 7), /** * This gizmo type supports using the fallback tools keymap. @@ -135,7 +142,7 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * * Often useful in combination with #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK */ - WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 7), + WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 8), /** * Use this from a gizmos refresh callback so we can postpone the refresh operation @@ -146,14 +153,14 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * for selection operations. This means gizmos that use this check don't interfere * with click drag events by popping up under the cursor and catching the tweak event. */ - WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 8), + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 9), /** * Cause continuous redraws, i.e. set the region redraw flag on every main loop iteration. This * should really be avoided by using proper region redraw tagging, notifiers and the message-bus, * however for VR it's sometimes needed. */ - WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 9), + WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 10), } eWM_GizmoFlagGroupTypeFlag; /** diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index 6f6a2402d89..295196c701b 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -381,31 +381,31 @@ static void gizmomap_prepare_drawing(wmGizmoMap *gzmap, wmGizmo *gz_modal = gzmap->gzmap_context.modal; - /* only active gizmo needs updating */ - if (gz_modal) { - if ((gz_modal->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0) { - if ((gz_modal->parent_gzgroup->hide.any == 0) && - wm_gizmogroup_is_visible_in_drawstep(gz_modal->parent_gzgroup, drawstep)) { - if (gizmo_prepare_drawing(gzmap, gz_modal, C, draw_gizmos, drawstep)) { - gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_PREPARE_DRAW; - } - } - /* don't draw any other gizmos */ - return; - } - } - /* Allow refresh functions to ask to be refreshed again, clear before the loop below. */ const bool do_refresh = gzmap->update_flag[drawstep] & GIZMOMAP_IS_REFRESH_CALLBACK; gzmap->update_flag[drawstep] &= ~GIZMOMAP_IS_REFRESH_CALLBACK; LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, &gzmap->groups) { /* check group visibility - drawstep first to avoid unnecessary call of group poll callback */ - if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep) || - !WM_gizmo_group_type_poll(C, gzgroup->type)) { + if (!wm_gizmogroup_is_visible_in_drawstep(gzgroup, drawstep)) { continue; } + if (gz_modal && (gzgroup == gz_modal->parent_gzgroup)) { + if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE) { + continue; + } + } + else { /* Don't poll modal gizmo since some poll functions unlink. */ + if (!WM_gizmo_group_type_poll(C, gzgroup->type)) { + continue; + } + /* When modal only show other gizmo groups tagged with #WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL. */ + if (gz_modal && ((gzgroup->type->flag & WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL) == 0)) { + continue; + } + } + /* Needs to be initialized on first draw. */ /* XXX weak: Gizmo-group may skip refreshing if it's invisible * (map gets untagged nevertheless). */ diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index e11ef52eb84..0b7d5e5f1f4 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -266,8 +266,7 @@ IDTypeInfo IDType_ID_WM = { .name = "WindowManager", .name_plural = "window_managers", .translation_context = BLT_I18NCONTEXT_ID_WINDOWMANAGER, - .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_MAKELOCAL | - IDTYPE_FLAGS_NO_ANIMDATA, + .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, .init_data = NULL, .copy_data = NULL, diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 1f47b152a2b..83a9a6c6383 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -3749,7 +3749,7 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm, const char *keymap_id = NULL; /* Support for the gizmo owning the tool keymap. */ - if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\n') { + if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') { wmGizmoMap *gzmap = NULL; wmGizmoGroup *gzgroup = NULL; LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 3fdd74e8d8e..5cbf2da9bfa 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -1515,14 +1515,73 @@ static void wm_history_file_update(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Save Main Blend-File (internal) +/** \name Save Main Blend-File (internal) Screen-Shot + * + * Screen-shot the active window. + * \{ */ + +static ImBuf *blend_file_thumb_from_screenshot(bContext *C, BlendThumbnail **thumb_pt) +{ + if (*thumb_pt) { + /* We are given a valid thumbnail data, so just generate image from it. */ + return BKE_main_thumbnail_to_imbuf(NULL, *thumb_pt); + } + + /* Redraw to remove menus that might be open. */ + WM_redraw_windows(C); + WM_cursor_wait(true); + + /* The window to capture should be a main window (without parent). */ + wmWindow *win = CTX_wm_window(C); + while (win && win->parent) { + win = win->parent; + } + + int win_size[2]; + uint *buffer = WM_window_pixels_read(CTX_wm_manager(C), win, win_size); + ImBuf *ibuf = IMB_allocFromBufferOwn(buffer, NULL, win_size[0], win_size[1], 24); + + if (ibuf) { + int ex, ey; + if (ibuf->x > ibuf->y) { + ex = BLEN_THUMB_SIZE; + ey = max_ii(1, (int)(((float)ibuf->y / (float)ibuf->x) * BLEN_THUMB_SIZE)); + } + else { + ex = max_ii(1, (int)(((float)ibuf->x / (float)ibuf->y) * BLEN_THUMB_SIZE)); + ey = BLEN_THUMB_SIZE; + } + + /* File-system thumbnail image can be 256x256. */ + IMB_scaleImBuf(ibuf, ex * 2, ey * 2); + + /* Thumbnail inside blend should be 128x128. */ + ImBuf *thumb_ibuf = IMB_dupImBuf(ibuf); + IMB_scaleImBuf(thumb_ibuf, ex, ey); + + BlendThumbnail *thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf); + IMB_freeImBuf(thumb_ibuf); + *thumb_pt = thumb; + } + WM_cursor_wait(false); + + /* Must be freed by caller. */ + return ibuf; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Save Main Blend-File (internal) Camera View + * + * Render the current scene with the active camera. * \{ */ /* screen can be NULL */ -static ImBuf *blend_file_thumb(const bContext *C, - Scene *scene, - bScreen *screen, - BlendThumbnail **thumb_pt) +static ImBuf *blend_file_thumb_from_camera(const bContext *C, + Scene *scene, + bScreen *screen, + BlendThumbnail **thumb_pt) { /* will be scaled down, but gives some nice oversampling */ ImBuf *ibuf; @@ -1573,8 +1632,8 @@ static ImBuf *blend_file_thumb(const bContext *C, NULL, OB_SOLID, scene->camera, - BLEN_THUMB_SIZE * 2, - BLEN_THUMB_SIZE * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, IB_rect, V3D_OFSDRAW_NONE, R_ALPHAPREMUL, @@ -1588,8 +1647,8 @@ static ImBuf *blend_file_thumb(const bContext *C, OB_SOLID, v3d, region, - BLEN_THUMB_SIZE * 2, - BLEN_THUMB_SIZE * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, + PREVIEW_RENDER_LARGE_HEIGHT * 2, IB_rect, R_ALPHAPREMUL, NULL, @@ -1610,8 +1669,14 @@ static ImBuf *blend_file_thumb(const bContext *C, if (ibuf) { /* dirty oversampling */ - IMB_scaleImBuf(ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE); - thumb = BKE_main_thumbnail_from_imbuf(NULL, ibuf); + ImBuf *thumb_ibuf; + thumb_ibuf = IMB_dupImBuf(ibuf); + /* BLEN_THUMB_SIZE is size of thumbnail inside blend file: 128x128. */ + IMB_scaleImBuf(thumb_ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE); + thumb = BKE_main_thumbnail_from_imbuf(NULL, thumb_ibuf); + IMB_freeImBuf(thumb_ibuf); + /* Thumbnail saved to file-system should be 256x256. */ + IMB_scaleImBuf(ibuf, PREVIEW_RENDER_LARGE_HEIGHT, PREVIEW_RENDER_LARGE_HEIGHT); } else { /* '*thumb_pt' needs to stay NULL to prevent a bad thumbnail from being handled */ @@ -1698,8 +1763,13 @@ static bool wm_file_write(bContext *C, /* Main now can store a '.blend' thumbnail, useful for background mode * or thumbnail customization. */ main_thumb = thumb = bmain->blen_thumb; - if ((U.flag & USER_SAVE_PREVIEWS) && BLI_thread_is_main()) { - ibuf_thumb = blend_file_thumb(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb); + if (BLI_thread_is_main()) { + if (U.file_preview_type == USER_FILE_PREVIEW_SCREENSHOT) { + ibuf_thumb = blend_file_thumb_from_screenshot(C, &thumb); + } + else if (U.file_preview_type == USER_FILE_PREVIEW_CAMERA) { + ibuf_thumb = blend_file_thumb_from_camera(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb); + } } /* operator now handles overwrite checks */ diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 606c9252ff9..29e34313be5 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -35,7 +35,9 @@ #include "MEM_guardedalloc.h" #include "DNA_ID.h" +#include "DNA_collection_types.h" #include "DNA_key_types.h" +#include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_windowmanager_types.h" @@ -50,15 +52,21 @@ #include "BLO_readfile.h" +#include "BKE_armature.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_key.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_lib_override.h" +#include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" #include "BKE_report.h" +#include "BKE_rigidbody.h" +#include "BKE_scene.h" #include "BKE_idtype.h" @@ -137,6 +145,14 @@ static short wm_link_append_flag(wmOperator *op) if (RNA_boolean_get(op->ptr, "link")) { flag |= FILE_LINK; } + else { + if (RNA_boolean_get(op->ptr, "use_recursive")) { + flag |= FILE_APPEND_RECURSIVE; + } + if (RNA_boolean_get(op->ptr, "set_fake")) { + flag |= FILE_APPEND_SET_FAKEUSER; + } + } if (RNA_boolean_get(op->ptr, "instance_collections")) { flag |= FILE_COLLECTION_INSTANCE; } @@ -153,6 +169,10 @@ typedef struct WMLinkAppendDataItem { *libraries; /* All libs (from WMLinkAppendData.libraries) to try to load this ID from. */ short idcode; + /** Type of action to do to append this item, and other append-specific information. */ + char append_action; + char append_tag; + ID *new_id; void *customdata; } WMLinkAppendDataItem; @@ -167,10 +187,32 @@ typedef struct WMLinkAppendData { */ int flag; + /** Allows to easily find an existing items from an ID pointer. Used by append code. */ + GHash *new_id_to_item; + /* Internal 'private' data */ MemArena *memarena; } WMLinkAppendData; +typedef struct WMLinkAppendDataCallBack { + WMLinkAppendData *lapp_data; + WMLinkAppendDataItem *item; + ReportList *reports; + +} WMLinkAppendDataCallBack; + +enum { + WM_APPEND_ACT_UNSET = 0, + WM_APPEND_ACT_KEEP_LINKED, + WM_APPEND_ACT_REUSE_LOCAL, + WM_APPEND_ACT_MAKE_LOCAL, + WM_APPEND_ACT_COPY_LOCAL, +}; + +enum { + WM_APPEND_TAG_INDIRECT = 1 << 0, +}; + static WMLinkAppendData *wm_link_append_data_new(const int flag) { MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); @@ -184,6 +226,10 @@ static WMLinkAppendData *wm_link_append_data_new(const int flag) static void wm_link_append_data_free(WMLinkAppendData *lapp_data) { + if (lapp_data->new_id_to_item != NULL) { + BLI_ghash_free(lapp_data->new_id_to_item, NULL, NULL); + } + BLI_memarena_free(lapp_data->memarena); } @@ -213,6 +259,7 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_data->memarena, lapp_data->num_libraries); item->new_id = NULL; + item->append_action = WM_APPEND_ACT_UNSET; item->customdata = customdata; BLI_linklist_append_arena(&lapp_data->items, item, lapp_data->memarena); @@ -221,6 +268,568 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp return item; } +/* -------------------------------------------------------------------- */ +/** \name Library appending helper functions. + * + * FIXME: Deduplicate code with similar one in readfile.c + * \{ */ + +static bool object_in_any_scene(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) { + if (BKE_scene_object_find(sce, ob)) { + return true; + } + } + + return false; +} + +static bool object_in_any_collection(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if (BKE_collection_has_object(collection, ob)) { + return true; + } + } + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->master_collection != NULL && + BKE_collection_has_object(scene->master_collection, ob)) { + return true; + } + } + + return false; +} + +/** + * Shared operations to perform on the object's base after adding it to the scene. + */ +static void wm_append_loose_data_instantiate_object_base_instance_init( + Object *ob, bool set_selected, bool set_active, ViewLayer *view_layer, const View3D *v3d) +{ + Base *base = BKE_view_layer_base_find(view_layer, ob); + + if (v3d != NULL) { + base->local_view_bits |= v3d->local_view_uuid; + } + + if (set_selected) { + if (base->flag & BASE_SELECTABLE) { + base->flag |= BASE_SELECTED; + } + } + + if (set_active) { + view_layer->basact = base; + } + + BKE_scene_object_base_flag_sync_from_base(base); +} + +static ID *wm_append_loose_data_instantiate_process_check(WMLinkAppendDataItem *item) +{ + /* We consider that if we either kept it linked, or re-used already local data, instantiation + * status of those should not be modified. */ + if (!ELEM(item->append_action, WM_APPEND_ACT_COPY_LOCAL, WM_APPEND_ACT_MAKE_LOCAL)) { + return NULL; + } + + ID *id = item->new_id; + if (id == NULL) { + return NULL; + } + + if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + return NULL; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; +} + +static void wm_append_loose_data_instantiate_ensure_active_collection( + WMLinkAppendData *lapp_data, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + Collection **r_active_collection) +{ + /* Find or add collection as needed. */ + if (*r_active_collection == NULL) { + if (lapp_data->flag & FILE_ACTIVE_COLLECTION) { + LayerCollection *lc = BKE_layer_collection_get_active(view_layer); + *r_active_collection = lc->collection; + } + else { + *r_active_collection = BKE_collection_add(bmain, scene->master_collection, NULL); + } + } +} + +/* TODO: De-duplicate this code with the one in readfile.c, think we need some utils code for that + * in BKE. */ +static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + const View3D *v3d) +{ + if (scene == NULL) { + /* In some cases, like the asset drag&drop e.g., the caller code manages instantiation itself. + */ + return; + } + + LinkNode *itemlink; + Collection *active_collection = NULL; + const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0; + + const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; + /* Do NOT make base active here! screws up GUI stuff, + * if you want it do it at the editor level. */ + const bool object_set_active = false; + + /* First pass on obdata to enable their instantiation by default, then do a second pass on + * objects to clear it for any obdata already in use. */ + if (do_obdata) { + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + + id->tag |= LIB_TAG_DOIT; + } + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + Object *ob = (Object *)id; + Object *new_ob = (Object *)id->newid; + if (ob->data != NULL) { + ((ID *)(ob->data))->tag &= ~LIB_TAG_DOIT; + } + if (new_ob != NULL && new_ob->data != NULL) { + ((ID *)(new_ob->data))->tag &= ~LIB_TAG_DOIT; + } + } + } + + /* First do collections, then objects, then obdata. */ + + /* NOTE: For collections we only view_layer-instantiate duplicated collections that have + * non-instantiated objects in them. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL || GS(id->name) != ID_GR) { + continue; + } + + /* We do not want to force instantiation of indirectly appended collections. Users can now + * easily instantiate collections (and their objects) as needed by themselves. See T67032. */ + /* We need to check that objects in that collections are already instantiated in a scene. + * Otherwise, it's better to add the collection to the scene's active collection, than to + * instantiate its objects in active scene's collection directly. See T61141. + * + * NOTE: We only check object directly into that collection, not recursively into its + * children. + */ + Collection *collection = (Collection *)id; + bool do_add_collection = false; + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + if (!object_in_any_scene(bmain, ob)) { + do_add_collection = true; + break; + } + } + if (do_add_collection) { + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + /* In case user requested instantiation of collections as empties, we do so for the one they + * explicitly selected (originally directly linked IDs). */ + if ((lapp_data->flag & FILE_COLLECTION_INSTANCE) != 0 && + (item->append_tag & WM_APPEND_TAG_INDIRECT) == 0) { + /* BKE_object_add(...) messes with the selection. */ + Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2); + ob->type = OB_EMPTY; + ob->empty_drawsize = U.collection_instance_empty_size; + + BKE_collection_object_add(bmain, active_collection, ob); + + const bool set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; + /* TODO: why is it OK to make this active here but not in other situations? + * See other callers of #object_base_instance_init */ + const bool set_active = set_selected; + wm_append_loose_data_instantiate_object_base_instance_init( + ob, set_selected, set_active, view_layer, v3d); + + /* Assign the collection. */ + ob->instance_collection = collection; + id_us_plus(&collection->id); + ob->transflag |= OB_DUPLICOLLECTION; + copy_v3_v3(ob->loc, scene->cursor.location); + } + else { + /* Add collection as child of active collection. */ + BKE_collection_child_add(bmain, active_collection, collection); + + if ((lapp_data->flag & FILE_AUTOSELECT) != 0) { + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + Base *base = BKE_view_layer_base_find(view_layer, ob); + if (base) { + base->flag |= BASE_SELECTED; + BKE_scene_object_base_flag_sync_from_base(base); + } + } + } + } + } + } + + /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used + * anywhere. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + Object *ob = (Object *)id; + + if (object_in_any_collection(bmain, ob)) { + continue; + } + + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + CLAMP_MIN(ob->id.us, 0); + ob->mode = OB_MODE_OBJECT; + + BKE_collection_object_add(bmain, active_collection, ob); + + wm_append_loose_data_instantiate_object_base_instance_init( + ob, object_set_selected, object_set_active, view_layer, v3d); + } + + if (!do_obdata) { + return; + } + + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = wm_append_loose_data_instantiate_process_check(item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + if ((id->tag & LIB_TAG_DOIT) == 0) { + continue; + } + + wm_append_loose_data_instantiate_ensure_active_collection( + lapp_data, bmain, scene, view_layer, &active_collection); + + const int type = BKE_object_obdata_to_type(id); + BLI_assert(type != -1); + Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2); + ob->data = id; + id_us_plus(id); + BKE_object_materials_test(bmain, ob, ob->data); + + BKE_collection_object_add(bmain, active_collection, ob); + + wm_append_loose_data_instantiate_object_base_instance_init( + ob, object_set_selected, object_set_active, view_layer, v3d); + + copy_v3_v3(ob->loc, scene->cursor.location); + } +} + +/** \} */ + +static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data) +{ + if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK)) { + return IDWALK_RET_NOP; + } + + WMLinkAppendDataCallBack *data = cb_data->user_data; + ID *id = *cb_data->id_pointer; + + if (id == NULL) { + return IDWALK_RET_NOP; + } + + if (!BKE_idtype_idcode_is_linkable(GS(id->name))) { + return IDWALK_RET_NOP; + } + + WMLinkAppendDataItem *item = BLI_ghash_lookup(data->lapp_data->new_id_to_item, id); + if (item == NULL) { + item = wm_link_append_data_item_add(data->lapp_data, id->name, GS(id->name), NULL); + item->new_id = id; + /* Since we did not have an item for that ID yet, we now user did not selected it explicitly, + * it was rather linked indirectly. This info is important for instantiation of collections. */ + item->append_tag |= WM_APPEND_TAG_INDIRECT; + BLI_ghash_insert(data->lapp_data->new_id_to_item, id, item); + } + + /* NOTE: currently there is no need to do anything else here, but in the future this would be + * the place to add specific per-usage decisions on how to append an ID. */ + + return IDWALK_RET_NOP; +} + +/* Perform append operation, using modern ID usage looper to detect which ID should be kept linked, + * made local, duplicated as local, re-used from local etc. + * + * TODO: Expose somehow this logic to the two other parts of code performing actual append + * (i.e. copy/paste and `bpy` link/append API). + * Then we can heavily simplify #BKE_library_make_local(). */ +static void wm_append_do(WMLinkAppendData *lapp_data, + ReportList *reports, + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + const View3D *v3d) +{ + BLI_assert((lapp_data->flag & FILE_LINK) == 0); + + const bool do_recursive = (lapp_data->flag & FILE_APPEND_RECURSIVE) != 0; + const bool set_fakeuser = (lapp_data->flag & FILE_APPEND_SET_FAKEUSER) != 0; + + LinkNode *itemlink; + + /* Generate a mapping between newly linked IDs and their items. */ + lapp_data->new_id_to_item = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_ghash_insert(lapp_data->new_id_to_item, id, item); + } + + /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect + * dependencies), this list will grow and we will process those IDs later, leading to a flatten + * recursive processing of all the linked dependencies. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(item->customdata == NULL); + + /* Clear tag previously used to mark IDs needing post-processing (instantiation of loose + * objects etc.). */ + id->tag &= ~LIB_TAG_DOIT; + + if (item->append_action != WM_APPEND_ACT_UNSET) { + /* Already set, pass. */ + } + if (GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name); + item->append_action = WM_APPEND_ACT_KEEP_LINKED; + } + else if (id->tag & LIB_TAG_PRE_EXISTING) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name); + item->append_action = WM_APPEND_ACT_COPY_LOCAL; + } + else { + /* In future we could search for already existing matching local ID etc. */ + CLOG_INFO(&LOG, 3, "Appended ID '%s' will be made local...", id->name); + item->append_action = WM_APPEND_ACT_MAKE_LOCAL; + } + + /* Only check dependencies if we are not keeping linked data, nor re-using existing local data. + */ + if (do_recursive && + !ELEM(item->append_action, WM_APPEND_ACT_KEEP_LINKED, WM_APPEND_ACT_REUSE_LOCAL)) { + WMLinkAppendDataCallBack cb_data = { + .lapp_data = lapp_data, .item = item, .reports = reports}; + BKE_library_foreach_ID_link( + bmain, id, foreach_libblock_append_callback, &cb_data, IDWALK_NOP); + } + } + + /* Effectively perform required operation on every linked ID. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + + ID *local_appended_new_id = NULL; + switch (item->append_action) { + case WM_APPEND_ACT_COPY_LOCAL: { + BKE_lib_id_make_local( + bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_COPY); + local_appended_new_id = id->newid; + break; + } + case WM_APPEND_ACT_MAKE_LOCAL: + BKE_lib_id_make_local(bmain, + id, + LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_LOCAL | + LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BLI_assert(id->newid == NULL); + local_appended_new_id = id; + break; + case WM_APPEND_ACT_KEEP_LINKED: + /* Nothing to do here. */ + break; + case WM_APPEND_ACT_REUSE_LOCAL: + /* We only need to set `newid` to ID found in previous loop, for proper remapping. */ + ID_NEW_SET(id->newid, item->customdata); + /* This is not a 'new' local appended id, do not set `local_appended_new_id` here. */ + break; + case WM_APPEND_ACT_UNSET: + CLOG_ERROR( + &LOG, "Unexpected unset append action for '%s' ID, assuming 'keep link'", id->name); + break; + default: + BLI_assert(0); + } + + if (local_appended_new_id != NULL) { + if (GS(local_appended_new_id->name) == ID_OB) { + BKE_rigidbody_ensure_local_object(bmain, (Object *)local_appended_new_id); + } + if (set_fakeuser) { + if (!ELEM(GS(local_appended_new_id->name), ID_OB, ID_GR)) { + /* Do not set fake user on objects nor collections (instancing). */ + id_fake_user_set(local_appended_new_id); + } + } + } + } + + /* Remap IDs as needed. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action == WM_APPEND_ACT_KEEP_LINKED) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + continue; + } + } + + BLI_assert(!ID_IS_LINKED(id)); + + BKE_libblock_relink_to_newid_new(bmain, id); + } + + /* Instantiate newly created (duplicated) IDs as needed. */ + wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d); + + /* Attempt to deal with object proxies. + * + * NOTE: Copied from `BKE_library_make_local`, but this is not really working (as in, not + * producing any useful result in any known use case), neither here nor in + * `BKE_library_make_local` currently. + * Proxies are end of life anyway, so not worth spending time on this. */ + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action != WM_APPEND_ACT_COPY_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + + /* Attempt to re-link copied proxy objects. This allows appending of an entire scene + * from another blend file into this one, even when that blend file contains proxified + * armatures that have local references. Since the proxified object needs to be linked + * (not local), this will only work when the "Localize all" checkbox is disabled. + * TL;DR: this is a dirty hack on top of an already weak feature (proxies). */ + if (GS(id->name) == ID_OB && ((Object *)id)->proxy != NULL) { + Object *ob = (Object *)id; + Object *ob_new = (Object *)id->newid; + bool is_local = false, is_lib = false; + + /* Proxies only work when the proxified object is linked-in from a library. */ + if (!ID_IS_LINKED(ob->proxy)) { + CLOG_WARN(&LOG, + "Proxy object %s will lose its link to %s, because the " + "proxified object is local", + id->newid->name, + ob->proxy->id.name); + continue; + } + + BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + + /* We can only switch the proxy'ing to a made-local proxy if it is no longer + * referred to from a library. Not checking for local use; if new local proxy + * was not used locally would be a nasty bug! */ + if (is_local || is_lib) { + CLOG_WARN(&LOG, + "Made-local proxy object %s will lose its link to %s, " + "because the linked-in proxy is referenced (is_local=%i, is_lib=%i)", + id->newid->name, + ob->proxy->id.name, + is_local, + is_lib); + } + else { + /* we can switch the proxy'ing from the linked-in to the made-local proxy. + * BKE_object_make_proxy() shouldn't be used here, as it allocates memory that + * was already allocated by object_make_local() (which called BKE_object_copy). */ + ob_new->proxy = ob->proxy; + ob_new->proxy_group = ob->proxy_group; + ob_new->proxy_from = ob->proxy_from; + ob_new->proxy->proxy_from = ob_new; + ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + } + } + } + + BKE_main_id_newptr_and_tag_clear(bmain); +} + static void wm_link_do(WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, @@ -263,6 +872,11 @@ static void wm_link_do(WMLinkAppendData *lapp_data, struct LibraryLink_Params liblink_params; BLO_library_link_params_init_with_context( &liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d); + /* In case of append, do not handle instantiation in linking process, but during append phase + * (see #wm_append_loose_data_instantiate ). */ + if ((flag & FILE_LINK) == 0) { + liblink_params.flag &= ~BLO_LIBLINK_NEEDS_ID_TAG_DOIT; + } mainl = BLO_library_link_begin(&bh, libname, &liblink_params); lib = mainl->curlib; @@ -325,9 +939,8 @@ static bool wm_link_append_item_poll(ReportList *reports, idcode = BKE_idtype_idcode_from_name(group); - /* XXX For now, we do a nasty exception for workspace, forbid linking them. - * Not nice, ultimately should be solved! */ - if (!BKE_idtype_idcode_is_linkable(idcode) && (do_append || idcode != ID_WS)) { + if (!BKE_idtype_idcode_is_linkable(idcode) || + (!do_append && BKE_idtype_idcode_is_only_appendable(idcode))) { if (reports) { if (do_append) { BKE_reportf(reports, @@ -501,28 +1114,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) /* append, rather than linking */ if (do_append) { - const bool set_fake = RNA_boolean_get(op->ptr, "set_fake"); - const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive"); - - if (use_recursive) { - BKE_library_make_local(bmain, NULL, NULL, true, set_fake); - } - else { - LinkNode *itemlink; - GSet *done_libraries = BLI_gset_new_ex( - BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__, lapp_data->num_libraries); - - for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { - ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id; - - if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) { - BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake); - BLI_gset_insert(done_libraries, new_id->lib); - } - } - - BLI_gset_free(done_libraries, NULL); - } + wm_append_do(lapp_data, op->reports, bmain, scene, view_layer, CTX_wm_view3d(C)); } wm_link_append_data_free(lapp_data); @@ -652,20 +1244,20 @@ void WM_OT_append(wmOperatorType *ot) * * \{ */ -static ID *wm_file_link_datablock_ex(Main *bmain, - Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - const char *filepath, - const short id_code, - const char *id_name, - bool clear_pre_existing_flag) +static ID *wm_file_link_append_datablock_ex(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name, + const bool do_append) { /* Tag everything so we can make local only the new datablock. */ BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); /* Define working data, with just the one item we want to link. */ - WMLinkAppendData *lapp_data = wm_link_append_data_new(0); + WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0); wm_link_append_data_library_add(lapp_data, filepath); WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL); @@ -676,15 +1268,22 @@ static ID *wm_file_link_datablock_ex(Main *bmain, /* Get linked datablock and free working data. */ ID *id = item->new_id; - wm_link_append_data_free(lapp_data); - if (clear_pre_existing_flag) { - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + if (do_append) { + wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d); } + wm_link_append_data_free(lapp_data); + + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + return id; } +/* + * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no + * instantiation of linked objects, collections etc. will be performed. + */ ID *WM_file_link_datablock(Main *bmain, Scene *scene, ViewLayer *view_layer, @@ -693,10 +1292,14 @@ ID *WM_file_link_datablock(Main *bmain, const short id_code, const char *id_name) { - return wm_file_link_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); + return wm_file_link_append_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); } +/* + * NOTE: `scene` (and related `view_layer` and `v3d`) pointers may be NULL, in which case no + * instantiation of appended objects, collections etc. will be performed. + */ ID *WM_file_append_datablock(Main *bmain, Scene *scene, ViewLayer *view_layer, @@ -705,14 +1308,8 @@ ID *WM_file_append_datablock(Main *bmain, const short id_code, const char *id_name) { - ID *id = wm_file_link_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); - - /* Make datablock local. */ - BKE_library_make_local(bmain, NULL, NULL, true, false); - - /* Clear pre existing tag. */ - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + ID *id = wm_file_link_append_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); return id; } diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 1c736647084..788e4214ac7 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -954,8 +954,9 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev break; } case GESTURE_MODAL_FLIP: { - /* Toggle snapping on/off. */ + /* Toggle flipping on/off. */ gesture->use_flip = !gesture->use_flip; + gesture_straightline_apply(C, op); break; } case GESTURE_MODAL_SELECT: { @@ -993,6 +994,7 @@ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *ev if (gesture->use_snap) { wm_gesture_straightline_do_angle_snap(rect); + gesture_straightline_apply(C, op); } wm_gesture_tag_redraw(win); diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index 25bcf1967ea..f1fe3e89007 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -460,8 +460,11 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap) if (UNLIKELY(BLI_listbase_is_empty(&keymap->items))) { /* Empty key-maps may be missing more there may be a typo in the name. - * Warn early to avoid losing time investigating each case. */ - CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); + * Warn early to avoid losing time investigating each case. + * When developing a customized Blender though you may want empty keymaps. */ + if (!U.app_template[0]) { + CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); + } } if (keymap->poll != NULL) { diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index df051328990..81dcc5ccea0 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -552,37 +552,38 @@ static const char *wm_context_member_from_ptr(bContext *C, const PointerRNA *ptr const View3D *v3d = (View3D *)space_data; const View3DShading *shading = &v3d->shading; - TEST_PTR_DATA_TYPE("space_data", RNA_View3DOverlay, ptr, v3d); - TEST_PTR_DATA_TYPE("space_data", RNA_View3DShading, ptr, shading); + TEST_PTR_DATA_TYPE("space_data.overlay", RNA_View3DOverlay, ptr, v3d); + TEST_PTR_DATA_TYPE("space_data.shading", RNA_View3DShading, ptr, shading); break; } case SPACE_GRAPH: { const SpaceGraph *sipo = (SpaceGraph *)space_data; const bDopeSheet *ads = sipo->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } case SPACE_FILE: { const SpaceFile *sfile = (SpaceFile *)space_data; const FileSelectParams *params = ED_fileselect_get_active_params(sfile); - TEST_PTR_DATA_TYPE("space_data", RNA_FileSelectParams, ptr, params); + TEST_PTR_DATA_TYPE("space_data.params", RNA_FileSelectParams, ptr, params); break; } case SPACE_IMAGE: { const SpaceImage *sima = (SpaceImage *)space_data; - TEST_PTR_DATA_TYPE("space_data", RNA_SpaceUVEditor, ptr, sima); + TEST_PTR_DATA_TYPE("space_data.overlay", RNA_SpaceImageOverlay, ptr, sima); + TEST_PTR_DATA_TYPE("space_data.uv_editor", RNA_SpaceUVEditor, ptr, sima); break; } case SPACE_NLA: { const SpaceNla *snla = (SpaceNla *)space_data; const bDopeSheet *ads = snla->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } case SPACE_ACTION: { const SpaceAction *sact = (SpaceAction *)space_data; const bDopeSheet *ads = &sact->ads; - TEST_PTR_DATA_TYPE("space_data", RNA_DopeSheet, ptr, ads); + TEST_PTR_DATA_TYPE("space_data.dopesheet", RNA_DopeSheet, ptr, ads); break; } } diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 004a845c667..0402b0d778a 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -808,16 +808,17 @@ wmWindow *WM_window_open(bContext *C, /* changes rect to fit within desktop */ wm_window_check_size(&rect); - /* Reuse temporary windows when they share the same title. */ + /* Reuse temporary windows when they share the same single area. */ wmWindow *win = NULL; if (temp) { LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { - if (WM_window_is_temp_screen(win_iter)) { - char *wintitle = GHOST_GetTitle(win_iter->ghostwin); - if (STREQ(title, wintitle)) { + const bScreen *screen = WM_window_get_active_screen(win_iter); + if (screen && screen->temp && BLI_listbase_is_single(&screen->areabase)) { + ScrArea *area = screen->areabase.first; + if (space_type == (area->butspacetype ? area->butspacetype : area->spacetype)) { win = win_iter; + break; } - free(wintitle); } } } diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 297205d1e79..8891840cb75 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -35,6 +35,8 @@ #include "GHOST_C-api.h" +#include "GPU_platform.h" + #include "WM_api.h" #include "wm_surface.h" @@ -91,6 +93,11 @@ bool wm_xr_init(wmWindowManager *wm) if (G.debug & G_DEBUG_XR_TIME) { create_info.context_flag |= GHOST_kXrContextDebugTime; } +#ifdef WIN32 + if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_WIN, GPU_DRIVER_ANY)) { + create_info.context_flag |= GHOST_kXrContextGpuNVIDIA; + } +#endif if (!(context = GHOST_XrContextCreate(&create_info))) { return false; diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index dc15b579e9d..88bf3ff453c 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -705,7 +705,7 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, } if (failure) { - CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out); + CLOG_ERROR(&LOG, "Failed to get buffer, %s", err_out); return false; } diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index 85ba4eca307..943646daa81 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -1920,7 +1920,7 @@ static int arg_handle_python_use_system_env_set(int UNUSED(argc), static const char arg_handle_addons_set_doc[] = "<addon(s)>\n" - "\tComma separated list of add-ons (no spaces)."; + "\tComma separated list (no spaces) of add-ons to enable in addition to any default add-ons."; static int arg_handle_addons_set(int argc, const char **argv, void *data) { /* workaround for scripts not getting a bpy.context.scene, causes internal errors elsewhere */ diff --git a/tests/performance/api/config.py b/tests/performance/api/config.py index d3a79eede14..b5e2b390aa3 100644 --- a/tests/performance/api/config.py +++ b/tests/performance/api/config.py @@ -25,6 +25,7 @@ class TestEntry: category: str = '' revision: str = '' git_hash: str = '' + environment: Dict = field(default_factory=dict) executable: str = '' date: int = 0 device_type: str = 'CPU' @@ -160,7 +161,13 @@ class TestConfig: def read_blender_executables(env, name) -> List: config = TestConfig._read_config_module(env.base_dir / name) builds = getattr(config, 'builds', {}) - return [pathlib.Path(build) for build in builds.values()] + executables = [] + + for executable in builds.values(): + executable, _ = TestConfig._split_environment_variables(executable) + executables.append(pathlib.Path(executable)) + + return executables @staticmethod def _read_config_module(base_dir: pathlib.Path) -> None: @@ -191,9 +198,10 @@ class TestConfig: # Get entries for specified commits, tags and branches. for revision_name, revision_commit in self.revisions.items(): + revision_commit, environment = self._split_environment_variables(revision_commit) git_hash = env.resolve_git_hash(revision_commit) date = env.git_hash_date(git_hash) - entries += self._get_entries(revision_name, git_hash, '', date) + entries += self._get_entries(revision_name, git_hash, '', environment, date) # Optimization to avoid rebuilds. revisions_to_build = set() @@ -204,6 +212,7 @@ class TestConfig: # Get entries for revisions based on existing builds. for revision_name, executable in self.builds.items(): + executable, environment = self._split_environment_variables(executable) executable_path = env._blender_executable_from_path(pathlib.Path(executable)) if not executable_path: sys.stderr.write(f'Error: build {executable} not found\n') @@ -214,7 +223,7 @@ class TestConfig: env.set_default_blender_executable() mtime = executable_path.stat().st_mtime - entries += self._get_entries(revision_name, git_hash, executable, mtime) + entries += self._get_entries(revision_name, git_hash, executable, environment, mtime) # Detect number of categories for more compact printing. categories = set() @@ -229,6 +238,7 @@ class TestConfig: revision_name: str, git_hash: str, executable: pathlib.Path, + environment: str, date: int) -> None: entries = [] for test in self.tests.tests: @@ -241,10 +251,12 @@ class TestConfig: # Test if revision hash or executable changed. if entry.git_hash != git_hash or \ entry.executable != executable or \ + entry.environment != environment or \ entry.benchmark_type != self.benchmark_type or \ entry.date != date: # Update existing entry. entry.git_hash = git_hash + entry.environment = environment entry.executable = executable entry.benchmark_type = self.benchmark_type entry.date = date @@ -256,6 +268,7 @@ class TestConfig: revision=revision_name, git_hash=git_hash, executable=executable, + environment=environment, date=date, test=test_name, category=test_category, @@ -266,3 +279,10 @@ class TestConfig: entries.append(entry) return entries + + @staticmethod + def _split_environment_variables(revision): + if isinstance(revision, str): + return revision, {} + else: + return revision[0], revision[1] diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py index 76c731b6118..eec92cc7b6b 100644 --- a/tests/performance/api/environment.py +++ b/tests/performance/api/environment.py @@ -98,15 +98,18 @@ class TestEnvironment: try: self.call([self.cmake_executable, '.'] + self.cmake_options, self.build_dir) self.call([self.cmake_executable, '--build', '.', '-j', jobs, '--target', 'install'], self.build_dir) + except KeyboardInterrupt as e: + raise e except: return False self._init_default_blender_executable() return True - def set_blender_executable(self, executable_path: pathlib.Path) -> None: + def set_blender_executable(self, executable_path: pathlib.Path, environment: Dict = {}) -> None: # Run all Blender commands with this executable. self.blender_executable = executable_path + self.blender_executable_environment = environment def _blender_executable_name(self) -> pathlib.Path: if platform.system() == "Windows": @@ -150,6 +153,7 @@ class TestEnvironment: def set_default_blender_executable(self) -> None: self.blender_executable = self.default_blender_executable + self.blender_executable_environment = {} def set_log_file(self, filepath: pathlib.Path, clear=True) -> None: # Log all commands and output to this file. @@ -161,7 +165,7 @@ class TestEnvironment: def unset_log_file(self) -> None: self.log_file = None - def call(self, args: List[str], cwd: pathlib.Path, silent=False) -> List[str]: + def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> List[str]: # Execute command with arguments in specified directory, # and return combined stdout and stderr output. @@ -173,7 +177,13 @@ class TestEnvironment: f = open(self.log_file, 'a') f.write('\n' + ' '.join([str(arg) for arg in args]) + '\n\n') - proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + env = os.environ + if len(environment): + env = env.copy() + for key, value in environment.items(): + env[key] = value + + proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) # Read line by line lines = [] @@ -185,17 +195,13 @@ class TestEnvironment: lines.append(line_str) if f: f.write(line_str) - except KeyboardInterrupt: + except KeyboardInterrupt as e: # Avoid processes that keep running when interrupting. proc.terminate() + raise e - if f: - f.close() - - # Print command output on error + # Raise error on failure if proc.returncode != 0 and not silent: - for line in lines: - print(line.rstrip()) raise Exception("Error executing command") return lines @@ -208,7 +214,8 @@ class TestEnvironment: else: common_args += ['--background'] - return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir) + return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir, + environment=self.blender_executable_environment) def run_in_blender(self, function: Callable[[Dict], Dict], diff --git a/tests/performance/api/graph.py b/tests/performance/api/graph.py index 4ee5ae7cf0e..e54adc194de 100644 --- a/tests/performance/api/graph.py +++ b/tests/performance/api/graph.py @@ -42,7 +42,7 @@ class TestGraph: # Generate one graph for every device x category x result key combination. for category, category_entries in categories.items(): - entries = sorted(category_entries, key=lambda entry: (entry.revision, entry.test)) + entries = sorted(category_entries, key=lambda entry: (entry.date, entry.revision, entry.test)) outputs = set() for entry in entries: @@ -58,8 +58,6 @@ class TestGraph: self.json = json.dumps(data, indent=2) def chart(self, device_name: str, chart_name: str, entries: List, chart_type: str, output: str) -> Dict: - entries = sorted(entries, key=lambda entry: entry.date) - # Gather used tests. tests = {} for entry in entries: diff --git a/tests/performance/benchmark b/tests/performance/benchmark index ad1e07d0ef3..a58c339e9f8 100755 --- a/tests/performance/benchmark +++ b/tests/performance/benchmark @@ -83,15 +83,20 @@ def match_entry(entry: api.TestEntry, args: argparse.Namespace): entry.test.find(args.test) != -1 or \ entry.category.find(args.test) != -1 -def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry: api.TestEntry): +def run_entry(env: api.TestEnvironment, + config: api.TestConfig, + row: List, + entry: api.TestEntry, + update_only: bool): # Check if entry needs to be run. - if entry.status not in ('queued', 'outdated'): + if update_only and entry.status not in ('queued', 'outdated'): print_row(config, row, end='\r') return False # Run test entry. revision = entry.revision git_hash = entry.git_hash + environment = entry.environment testname = entry.test testcategory = entry.category device_type = entry.device_type @@ -116,13 +121,15 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry print_row(config, row, end='\r') executable_ok = True if len(entry.executable): - env.set_blender_executable(pathlib.Path(entry.executable)) + env.set_blender_executable(pathlib.Path(entry.executable), environment) else: env.checkout(git_hash) executable_ok = env.build() if not executable_ok: entry.status = 'failed' entry.error_msg = 'Failed to build' + else: + env.set_blender_executable(env.blender_executable, environment) # Run test and update output and status. if executable_ok: @@ -134,6 +141,8 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry if not entry.output: raise Exception("Test produced no output") entry.status = 'done' + except KeyboardInterrupt as e: + raise e except Exception as e: entry.status = 'failed' entry.error_msg = str(e) @@ -219,7 +228,7 @@ def cmd_reset(env: api.TestEnvironment, argv: List): config.queue.write() -def cmd_run(env: api.TestEnvironment, argv: List): +def cmd_run(env: api.TestEnvironment, argv: List, update_only: bool): # Run tests. parser = argparse.ArgumentParser() parser.add_argument('config', nargs='?', default=None) @@ -229,17 +238,26 @@ def cmd_run(env: api.TestEnvironment, argv: List): configs = env.get_configs(args.config) for config in configs: updated = False + cancel = False print_header(config) for row in config.queue.rows(use_revision_columns(config)): if match_entry(row[0], args): for entry in row: - if run_entry(env, config, row, entry): - updated = True - # Write queue every time in case running gets interrupted, - # so it can be resumed. - config.queue.write() + try: + if run_entry(env, config, row, entry, update_only): + updated = True + # Write queue every time in case running gets interrupted, + # so it can be resumed. + config.queue.write() + except KeyboardInterrupt as e: + cancel = True + break + print_row(config, row) + if cancel: + break + if updated: # Generate graph if test were run. json_filepath = config.base_dir / "results.json" @@ -268,8 +286,9 @@ def main(): ' \n' ' list List available tests, devices and configurations\n' ' \n' - ' run [<config>] [<test>] Execute tests for configuration\n' - ' reset [<config>] [<test>] Clear tests results from config, for re-running\n' + ' run [<config>] [<test>] Execute all tests in configuration\n' + ' update [<config>] [<test>] Execute only queued and outdated tests\n' + ' reset [<config>] [<test>] Clear tests results in configuration\n' ' status [<config>] [<test>] List configurations and their tests\n' ' \n' ' graph a.json b.json... -o out.html Create graph from results in JSON files\n') @@ -304,7 +323,9 @@ def main(): if args.command == 'list': cmd_list(env, argv) elif args.command == 'run': - cmd_run(env, argv) + cmd_run(env, argv, update_only=False) + elif args.command == 'update': + cmd_run(env, argv, update_only=True) elif args.command == 'reset': cmd_reset(env, argv) elif args.command == 'status': diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 79632e49c1f..a1b94abc317 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -766,7 +766,7 @@ foreach(geo_node_test ${geo_node_tests}) ) endforeach() else() - MESSAGE(STATUS "No directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ found, disabling test.") + MESSAGE(STATUS "Directory named ${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/ Not Found, disabling test.") endif() endforeach() diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py index 38b3a93bbbc..4123f06b7c4 100644 --- a/tests/python/bl_blendfile_io.py +++ b/tests/python/bl_blendfile_io.py @@ -73,7 +73,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' for Test in TESTS: Test(args).run_all_tests() diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index 1d076d66913..992bf6b89d9 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -212,7 +212,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert(len(bpy.data.meshes) == 1) # This one fails currently, for unclear reasons. - # ~ assert(bpy.data.meshes[0].library is not None) + assert(bpy.data.meshes[0].library is not None) assert(bpy.data.meshes[0].users == 1) assert(len(bpy.data.objects) == 1) assert(bpy.data.objects[0].library is None) @@ -278,7 +278,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' for Test in TESTS: Test(args).run_all_tests() diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index b44e4d48564..3c7c77ce339 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -208,7 +208,7 @@ def main(): args = argparse_create().parse_args() # Don't write thumbnails into the home directory. - bpy.context.preferences.filepaths.use_save_preview_images = False + bpy.context.preferences.filepaths.file_preview_type = 'NONE' bpy.context.preferences.experimental.use_override_templates = True for Test in TESTS: |