From 54972123f73ac2d4674efc5fa07fbf372c5bb4f6 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 16 Feb 2022 10:45:46 +0100 Subject: Fix Image GPU texture. Due to recent changes there have been reports of incorrect loading of GPU textures. This fix reverts a part of {D13238} that might be the source of the issue. --- source/blender/blenkernel/intern/image_gpu.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc index c43df7e157e..42dcfcd7aa9 100644 --- a/source/blender/blenkernel/intern/image_gpu.cc +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -447,7 +447,8 @@ static GPUTexture *image_get_gpu_texture(Image *ima, if (ibuf_intern == nullptr) { ibuf_intern = BKE_image_acquire_ibuf(ima, iuser, nullptr); if (ibuf_intern == nullptr) { - return image_gpu_texture_error_create(textarget); + *tex = image_gpu_texture_error_create(textarget); + return *tex; } } -- cgit v1.2.3 From a6267f11678f7f5d05c50542945faad62a4e5c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 16 Feb 2022 14:40:01 +0100 Subject: BLI: Fix compilation error caused by rBa9f023e22638 Explicitly referencing the typename fixes the issue. --- source/blender/blenlib/BLI_math_vec_types.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_math_vec_types.hh b/source/blender/blenlib/BLI_math_vec_types.hh index 8e897870098..8d6f04ab15f 100644 --- a/source/blender/blenlib/BLI_math_vec_types.hh +++ b/source/blender/blenlib/BLI_math_vec_types.hh @@ -63,7 +63,7 @@ template uint64_t vector_hash(const T &vec) template inline bool is_any_zero(const T &a) { for (int i = 0; i < T::type_length; i++) { - if (a[i] == T::base_type(0)) { + if (a[i] == typename T::base_type(0)) { return true; } } -- cgit v1.2.3 From f059bdc82311e79a2b60f9af9154ac7822fd7001 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 17 Sep 2021 15:15:14 +0200 Subject: Cycles: restore basic standalone GUI, now using SDL GLUT does not support offscreen contexts, which is required for the new display driver. So we use SDL instead. Note that this requires using a system SDL package, the Blender precompiled SDL does not include the video subsystem. There is currently no text display support, instead info is printed to the terminal. This would require adding an embedded font and GLSL shaders, or using GUI library. Another improvement to be made is supporting OpenColorIO display transforms, right now we assume Rec.709 scene linear and display. All OpenGL, GLEW and SDL code was move out of core cycles and into app/opengl. This serves as a template for apps that want to integrate Cycles interactive rendering, with a simple OpenGLDisplayDriver example. In general this would be adapted to the graphics API and color management used by the app. Ref T91846 --- intern/cycles/app/CMakeLists.txt | 24 +- intern/cycles/app/cycles_standalone.cpp | 38 +-- intern/cycles/app/opengl/display_driver.cpp | 398 ++++++++++++++++++++++++++++ intern/cycles/app/opengl/display_driver.h | 130 +++++++++ intern/cycles/app/opengl/shader.cpp | 210 +++++++++++++++ intern/cycles/app/opengl/shader.h | 58 ++++ intern/cycles/app/opengl/window.cpp | 365 +++++++++++++++++++++++++ intern/cycles/app/opengl/window.h | 48 ++++ intern/cycles/cmake/external_libs.cmake | 20 +- intern/cycles/device/hip/device_impl.h | 2 - intern/cycles/util/CMakeLists.txt | 10 - intern/cycles/util/view.cpp | 282 -------------------- intern/cycles/util/view.h | 48 ---- 13 files changed, 1258 insertions(+), 375 deletions(-) create mode 100644 intern/cycles/app/opengl/display_driver.cpp create mode 100644 intern/cycles/app/opengl/display_driver.h create mode 100644 intern/cycles/app/opengl/shader.cpp create mode 100644 intern/cycles/app/opengl/shader.h create mode 100644 intern/cycles/app/opengl/window.cpp create mode 100644 intern/cycles/app/opengl/window.h delete mode 100644 intern/cycles/util/view.cpp delete mode 100644 intern/cycles/util/view.h diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt index 781639986f6..e4249ff19b7 100644 --- a/intern/cycles/app/CMakeLists.txt +++ b/intern/cycles/app/CMakeLists.txt @@ -44,15 +44,13 @@ else() endif() if(WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) - list(APPEND LIBRARIES ${GLUT_LIBRARIES}) + add_definitions(${GL_DEFINITIONS}) + list(APPEND INC_SYS ${GLEW_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS}) + list(APPEND LIBRARIES ${CYCLES_GL_LIBRARIES} ${SDL2_LIBRARIES}) endif() -list(APPEND LIBRARIES ${CYCLES_GL_LIBRARIES}) - # Common configuration. -add_definitions(${GL_DEFINITIONS}) - include_directories(${INC}) include_directories(SYSTEM ${INC_SYS}) @@ -66,6 +64,18 @@ if(WITH_CYCLES_STANDALONE) oiio_output_driver.cpp oiio_output_driver.h ) + + if(WITH_CYCLES_STANDALONE_GUI) + list(APPEND SRC + opengl/display_driver.cpp + opengl/display_driver.h + opengl/shader.cpp + opengl/shader.h + opengl/window.cpp + opengl/window.h + ) + endif() + add_executable(cycles ${SRC} ${INC} ${INC_SYS}) unset(SRC) @@ -80,6 +90,10 @@ if(WITH_CYCLES_STANDALONE) # OpenImageDenoise uses BNNS from the Accelerate framework. set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework Accelerate") endif() + if(WITH_CYCLES_STANDALONE_GUI) + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS + " -framework Cocoa -framework CoreAudio -framework AudioUnit -framework AudioToolbox -framework ForceFeedback -framework CoreVideo") + endif() endif() if(UNIX AND NOT APPLE) diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp index 7123edbef64..ef514be4369 100644 --- a/intern/cycles/app/cycles_standalone.cpp +++ b/intern/cycles/app/cycles_standalone.cpp @@ -40,11 +40,10 @@ #include "app/oiio_output_driver.h" #ifdef WITH_CYCLES_STANDALONE_GUI -# include "util/view.h" +# include "opengl/display_driver.h" +# include "opengl/window.h" #endif -#include "app/cycles_xml.h" - CCL_NAMESPACE_BEGIN struct Options { @@ -130,7 +129,14 @@ static void session_init() options.output_pass = "combined"; options.session = new Session(options.session_params, options.scene_params); - if (!options.output_filepath.empty()) { +#ifdef WITH_CYCLES_STANDALONE_GUI + if (!options.session_params.background) { + options.session->set_display_driver(make_unique( + window_opengl_context_enable, window_opengl_context_disable)); + } + else +#endif + if (!options.output_filepath.empty()) { options.session->set_output_driver(make_unique( options.output_filepath, options.output_pass, session_print)); } @@ -139,7 +145,7 @@ static void session_init() options.session->progress.set_update_callback(function_bind(&session_print_status)); #ifdef WITH_CYCLES_STANDALONE_GUI else - options.session->progress.set_update_callback(function_bind(&view_redraw)); + options.session->progress.set_update_callback(function_bind(&window_redraw)); #endif /* load scene */ @@ -204,10 +210,10 @@ static void display_info(Progress &progress) sample_time, interactive.c_str()); - view_display_info(str.c_str()); + window_display_info(str.c_str()); if (options.show_help) - view_display_help(); + window_display_help(); } static void display() @@ -538,15 +544,15 @@ int main(int argc, const char **argv) string title = "Cycles: " + path_filename(options.filepath); /* init/exit are callback so they run while GL is initialized */ - view_main_loop(title.c_str(), - options.width, - options.height, - session_init, - session_exit, - resize, - display, - keyboard, - motion); + window_main_loop(title.c_str(), + options.width, + options.height, + session_init, + session_exit, + resize, + display, + keyboard, + motion); } #endif diff --git a/intern/cycles/app/opengl/display_driver.cpp b/intern/cycles/app/opengl/display_driver.cpp new file mode 100644 index 00000000000..2c9902ff1de --- /dev/null +++ b/intern/cycles/app/opengl/display_driver.cpp @@ -0,0 +1,398 @@ +/* + * Copyright 2011-2022 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "app/opengl/display_driver.h" +#include "app/opengl/shader.h" + +#include "util/log.h" +#include "util/string.h" + +#include +#include + +CCL_NAMESPACE_BEGIN + +/* -------------------------------------------------------------------- + * OpenGLDisplayDriver. + */ + +OpenGLDisplayDriver::OpenGLDisplayDriver(const function &gl_context_enable, + const function &gl_context_disable) + : gl_context_enable_(gl_context_enable), gl_context_disable_(gl_context_disable) +{ +} + +OpenGLDisplayDriver::~OpenGLDisplayDriver() +{ +} + +/* -------------------------------------------------------------------- + * Update procedure. + */ + +void OpenGLDisplayDriver::next_tile_begin() +{ + /* Assuming no tiles used in interactive display. */ +} + +bool OpenGLDisplayDriver::update_begin(const Params ¶ms, int texture_width, int texture_height) +{ + /* Note that it's the responsibility of OpenGLDisplayDriver to ensure updating and drawing + * the texture does not happen at the same time. This is achieved indirectly. + * + * When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock. + * This same lock is also held when do_draw() is called, which together ensure mutual + * exclusion. + * + * This locking is not performed on the Cycles side, because that would cause lock inversion. */ + if (!gl_context_enable_()) { + return false; + } + + if (gl_render_sync_) { + glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED); + } + + if (!gl_texture_resources_ensure()) { + gl_context_disable_(); + return false; + } + + /* Update texture dimensions if needed. */ + if (texture_.width != texture_width || texture_.height != texture_height) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_.gl_id); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0); + texture_.width = texture_width; + texture_.height = texture_height; + glBindTexture(GL_TEXTURE_2D, 0); + + /* Texture did change, and no pixel storage was provided. Tag for an explicit zeroing out to + * avoid undefined content. */ + texture_.need_clear = true; + } + + /* Update PBO dimensions if needed. + * + * NOTE: Allocate the PBO for the the size which will fit the final render resolution (as in, + * at a resolution divider 1. This was we don't need to recreate graphics interoperability + * objects which are costly and which are tied to the specific underlying buffer size. + * The downside of this approach is that when graphics interoperability is not used we are + * sending too much data to GPU when resolution divider is not 1. */ + const int buffer_width = params.full_size.x; + const int buffer_height = params.full_size.y; + if (texture_.buffer_width != buffer_width || texture_.buffer_height != buffer_height) { + const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height; + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + texture_.buffer_width = buffer_width; + texture_.buffer_height = buffer_height; + } + + /* New content will be provided to the texture in one way or another, so mark this in a + * centralized place. */ + texture_.need_update = true; + + return true; +} + +void OpenGLDisplayDriver::update_end() +{ + gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + + gl_context_disable_(); +} + +/* -------------------------------------------------------------------- + * Texture buffer mapping. + */ + +half4 *OpenGLDisplayDriver::map_texture_buffer() +{ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + + half4 *mapped_rgba_pixels = reinterpret_cast( + glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); + if (!mapped_rgba_pixels) { + LOG(ERROR) << "Error mapping OpenGLDisplayDriver pixel buffer object."; + } + + if (texture_.need_clear) { + const int64_t texture_width = texture_.width; + const int64_t texture_height = texture_.height; + memset(reinterpret_cast(mapped_rgba_pixels), + 0, + texture_width * texture_height * sizeof(half4)); + texture_.need_clear = false; + } + + return mapped_rgba_pixels; +} + +void OpenGLDisplayDriver::unmap_texture_buffer() +{ + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +/* -------------------------------------------------------------------- + * Graphics interoperability. + */ + +OpenGLDisplayDriver::GraphicsInterop OpenGLDisplayDriver::graphics_interop_get() +{ + GraphicsInterop interop_dst; + + interop_dst.buffer_width = texture_.buffer_width; + interop_dst.buffer_height = texture_.buffer_height; + interop_dst.opengl_pbo_id = texture_.gl_pbo_id; + + interop_dst.need_clear = texture_.need_clear; + texture_.need_clear = false; + + return interop_dst; +} + +void OpenGLDisplayDriver::graphics_interop_activate() +{ + gl_context_enable_(); +} + +void OpenGLDisplayDriver::graphics_interop_deactivate() +{ + gl_context_disable_(); +} + +/* -------------------------------------------------------------------- + * Drawing. + */ + +void OpenGLDisplayDriver::clear() +{ + texture_.need_clear = true; +} + +void OpenGLDisplayDriver::draw(const Params ¶ms) +{ + /* See do_update_begin() for why no locking is required here. */ + if (texture_.need_clear) { + /* Texture is requested to be cleared and was not yet cleared. + * Do early return which should be equivalent of drawing all-zero texture. */ + return; + } + + if (!gl_draw_resources_ensure()) { + return; + } + + if (gl_upload_sync_) { + glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + display_shader_.bind(params.full_size.x, params.full_size.y); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_.gl_id); + + if (texture_.width != params.size.x || texture_.height != params.size.y) { + /* Resolution divider is different from 1, force nearest interpolation. */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); + + texture_update_if_needed(); + vertex_buffer_update(params); + + GLuint vertex_array_object; + glGenVertexArrays(1, &vertex_array_object); + glBindVertexArray(vertex_array_object); + + const int texcoord_attribute = display_shader_.get_tex_coord_attrib_location(); + const int position_attribute = display_shader_.get_position_attrib_location(); + + glEnableVertexAttribArray(texcoord_attribute); + glEnableVertexAttribArray(position_attribute); + + glVertexAttribPointer( + texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0); + glVertexAttribPointer(position_attribute, + 2, + GL_FLOAT, + GL_FALSE, + 4 * sizeof(float), + (const GLvoid *)(sizeof(float) * 2)); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + glDeleteVertexArrays(1, &vertex_array_object); + + display_shader_.unbind(); + + glDisable(GL_BLEND); + + gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); +} + +bool OpenGLDisplayDriver::gl_draw_resources_ensure() +{ + if (!texture_.gl_id) { + /* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can + * can not continue. Note that this is not an unrecoverable error, so once the texture is known + * we will come back here and create all the GPU resources needed for draw. */ + return false; + } + + if (gl_draw_resource_creation_attempted_) { + return gl_draw_resources_created_; + } + gl_draw_resource_creation_attempted_ = true; + + if (!vertex_buffer_) { + glGenBuffers(1, &vertex_buffer_); + if (!vertex_buffer_) { + LOG(ERROR) << "Error creating vertex buffer."; + return false; + } + } + + gl_draw_resources_created_ = true; + + return true; +} + +void OpenGLDisplayDriver::gl_resources_destroy() +{ + gl_context_enable_(); + + if (vertex_buffer_ != 0) { + glDeleteBuffers(1, &vertex_buffer_); + } + + if (texture_.gl_pbo_id) { + glDeleteBuffers(1, &texture_.gl_pbo_id); + texture_.gl_pbo_id = 0; + } + + if (texture_.gl_id) { + glDeleteTextures(1, &texture_.gl_id); + texture_.gl_id = 0; + } + + gl_context_disable_(); +} + +bool OpenGLDisplayDriver::gl_texture_resources_ensure() +{ + if (texture_.creation_attempted) { + return texture_.is_created; + } + texture_.creation_attempted = true; + + DCHECK(!texture_.gl_id); + DCHECK(!texture_.gl_pbo_id); + + /* Create texture. */ + glGenTextures(1, &texture_.gl_id); + if (!texture_.gl_id) { + LOG(ERROR) << "Error creating texture."; + return false; + } + + /* Configure the texture. */ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_.gl_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + /* Create PBO for the texture. */ + glGenBuffers(1, &texture_.gl_pbo_id); + if (!texture_.gl_pbo_id) { + LOG(ERROR) << "Error creating texture pixel buffer object."; + return false; + } + + /* Creation finished with a success. */ + texture_.is_created = true; + + return true; +} + +void OpenGLDisplayDriver::texture_update_if_needed() +{ + if (!texture_.need_update) { + return; + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + glTexSubImage2D( + GL_TEXTURE_2D, 0, 0, 0, texture_.width, texture_.height, GL_RGBA, GL_HALF_FLOAT, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + texture_.need_update = false; +} + +void OpenGLDisplayDriver::vertex_buffer_update(const Params ¶ms) +{ + /* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be + * rendered. */ + glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), NULL, GL_STREAM_DRAW); + + float *vpointer = reinterpret_cast(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); + if (!vpointer) { + return; + } + + vpointer[0] = 0.0f; + vpointer[1] = 0.0f; + vpointer[2] = params.full_offset.x; + vpointer[3] = params.full_offset.y; + + vpointer[4] = 1.0f; + vpointer[5] = 0.0f; + vpointer[6] = (float)params.size.x + params.full_offset.x; + vpointer[7] = params.full_offset.y; + + vpointer[8] = 1.0f; + vpointer[9] = 1.0f; + vpointer[10] = (float)params.size.x + params.full_offset.x; + vpointer[11] = (float)params.size.y + params.full_offset.y; + + vpointer[12] = 0.0f; + vpointer[13] = 1.0f; + vpointer[14] = params.full_offset.x; + vpointer[15] = (float)params.size.y + params.full_offset.y; + + glUnmapBuffer(GL_ARRAY_BUFFER); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/display_driver.h b/intern/cycles/app/opengl/display_driver.h new file mode 100644 index 00000000000..7645d7905b4 --- /dev/null +++ b/intern/cycles/app/opengl/display_driver.h @@ -0,0 +1,130 @@ +/* + * Copyright 2011-2022 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "app/opengl/shader.h" + +#include "session/display_driver.h" + +#include "util/function.h" +#include "util/unique_ptr.h" + +CCL_NAMESPACE_BEGIN + +class OpenGLDisplayDriver : public DisplayDriver { + public: + /* Callbacks for enabling and disabling the OpenGL context. Must be provided to support enabling + * the context on the Cycles render thread independent of the main thread. */ + OpenGLDisplayDriver(const function &gl_context_enable, + const function &gl_context_disable); + ~OpenGLDisplayDriver(); + + virtual void graphics_interop_activate() override; + virtual void graphics_interop_deactivate() override; + + virtual void clear() override; + + void set_zoom(float zoom_x, float zoom_y); + + protected: + virtual void next_tile_begin() override; + + virtual bool update_begin(const Params ¶ms, int texture_width, int texture_height) override; + virtual void update_end() override; + + virtual half4 *map_texture_buffer() override; + virtual void unmap_texture_buffer() override; + + virtual GraphicsInterop graphics_interop_get() override; + + virtual void draw(const Params ¶ms) override; + + /* Make sure texture is allocated and its initial configuration is performed. */ + bool gl_texture_resources_ensure(); + + /* Ensure all runtime GPU resources needed for drawing are allocated. + * Returns true if all resources needed for drawing are available. */ + bool gl_draw_resources_ensure(); + + /* Destroy all GPU resources which are being used by this object. */ + void gl_resources_destroy(); + + /* Update GPU texture dimensions and content if needed (new pixel data was provided). + * + * NOTE: The texture needs to be bound. */ + void texture_update_if_needed(); + + /* Update vertex buffer with new coordinates of vertex positions and texture coordinates. + * This buffer is used to render texture in the viewport. + * + * NOTE: The buffer needs to be bound. */ + void vertex_buffer_update(const Params ¶ms); + + /* Texture which contains pixels of the render result. */ + struct { + /* Indicates whether texture creation was attempted and succeeded. + * Used to avoid multiple attempts of texture creation on GPU issues or GPU context + * misconfiguration. */ + bool creation_attempted = false; + bool is_created = false; + + /* OpenGL resource IDs of the texture itself and Pixel Buffer Object (PBO) used to write + * pixels to it. + * + * NOTE: Allocated on the engine's context. */ + uint gl_id = 0; + uint gl_pbo_id = 0; + + /* Is true when new data was written to the PBO, meaning, the texture might need to be resized + * and new data is to be uploaded to the GPU. */ + bool need_update = false; + + /* Content of the texture is to be filled with zeroes. */ + std::atomic need_clear = true; + + /* Dimensions of the texture in pixels. */ + int width = 0; + int height = 0; + + /* Dimensions of the underlying PBO. */ + int buffer_width = 0; + int buffer_height = 0; + } texture_; + + OpenGLShader display_shader_; + + /* Special track of whether GPU resources were attempted to be created, to avoid attempts of + * their re-creation on failure on every redraw. */ + bool gl_draw_resource_creation_attempted_ = false; + bool gl_draw_resources_created_ = false; + + /* Vertex buffer which hold vertices of a triangle fan which is textures with the texture + * holding the render result. */ + uint vertex_buffer_ = 0; + + void *gl_render_sync_ = nullptr; + void *gl_upload_sync_ = nullptr; + + float2 zoom_ = make_float2(1.0f, 1.0f); + + function gl_context_enable_ = nullptr; + function gl_context_disable_ = nullptr; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/shader.cpp b/intern/cycles/app/opengl/shader.cpp new file mode 100644 index 00000000000..48ccafa0d95 --- /dev/null +++ b/intern/cycles/app/opengl/shader.cpp @@ -0,0 +1,210 @@ +/* + * Copyright 2011-2022 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "app/opengl/shader.h" + +#include "util/log.h" +#include "util/string.h" + +#include + +CCL_NAMESPACE_BEGIN + +/* -------------------------------------------------------------------- + * OpenGLShader. + */ + +static const char *VERTEX_SHADER = + "#version 330\n" + "uniform vec2 fullscreen;\n" + "in vec2 texCoord;\n" + "in vec2 pos;\n" + "out vec2 texCoord_interp;\n" + "\n" + "vec2 normalize_coordinates()\n" + "{\n" + " return (vec2(2.0) * (pos / fullscreen)) - vec2(1.0);\n" + "}\n" + "\n" + "void main()\n" + "{\n" + " gl_Position = vec4(normalize_coordinates(), 0.0, 1.0);\n" + " texCoord_interp = texCoord;\n" + "}\n\0"; + +static const char *FRAGMENT_SHADER = + "#version 330\n" + "uniform sampler2D image_texture;\n" + "in vec2 texCoord_interp;\n" + "out vec4 fragColor;\n" + "\n" + "void main()\n" + "{\n" + " vec4 rgba = texture(image_texture, texCoord_interp);\n" + /* Harcoded Rec.709 gamma, should use OpenColorIO eventually. */ + " fragColor = pow(rgba, vec4(0.45, 0.45, 0.45, 1.0));\n" + "}\n\0"; + +static void shader_print_errors(const char *task, const char *log, const char *code) +{ + LOG(ERROR) << "Shader: " << task << " error:"; + LOG(ERROR) << "===== shader string ===="; + + stringstream stream(code); + string partial; + + int line = 1; + while (getline(stream, partial, '\n')) { + if (line < 10) { + LOG(ERROR) << " " << line << " " << partial; + } + else { + LOG(ERROR) << line << " " << partial; + } + line++; + } + LOG(ERROR) << log; +} + +static int compile_shader_program(void) +{ + const struct Shader { + const char *source; + const GLenum type; + } shaders[2] = {{VERTEX_SHADER, GL_VERTEX_SHADER}, {FRAGMENT_SHADER, GL_FRAGMENT_SHADER}}; + + const GLuint program = glCreateProgram(); + + for (int i = 0; i < 2; i++) { + const GLuint shader = glCreateShader(shaders[i].type); + + string source_str = shaders[i].source; + const char *c_str = source_str.c_str(); + + glShaderSource(shader, 1, &c_str, NULL); + glCompileShader(shader); + + GLint compile_status; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); + + if (!compile_status) { + GLchar log[5000]; + GLsizei length = 0; + glGetShaderInfoLog(shader, sizeof(log), &length, log); + shader_print_errors("compile", log, c_str); + return 0; + } + + glAttachShader(program, shader); + } + + /* Link output. */ + glBindFragDataLocation(program, 0, "fragColor"); + + /* Link and error check. */ + glLinkProgram(program); + + GLint link_status; + glGetProgramiv(program, GL_LINK_STATUS, &link_status); + if (!link_status) { + GLchar log[5000]; + GLsizei length = 0; + glGetShaderInfoLog(program, sizeof(log), &length, log); + shader_print_errors("linking", log, VERTEX_SHADER); + shader_print_errors("linking", log, FRAGMENT_SHADER); + return 0; + } + + return program; +} + +int OpenGLShader::get_position_attrib_location() +{ + if (position_attribute_location_ == -1) { + const uint shader_program = get_shader_program(); + position_attribute_location_ = glGetAttribLocation(shader_program, position_attribute_name); + } + return position_attribute_location_; +} + +int OpenGLShader::get_tex_coord_attrib_location() +{ + if (tex_coord_attribute_location_ == -1) { + const uint shader_program = get_shader_program(); + tex_coord_attribute_location_ = glGetAttribLocation(shader_program, tex_coord_attribute_name); + } + return tex_coord_attribute_location_; +} + +void OpenGLShader::bind(int width, int height) +{ + create_shader_if_needed(); + + if (!shader_program_) { + return; + } + + glUseProgram(shader_program_); + glUniform1i(image_texture_location_, 0); + glUniform2f(fullscreen_location_, width, height); +} + +void OpenGLShader::unbind() +{ +} + +uint OpenGLShader::get_shader_program() +{ + return shader_program_; +} + +void OpenGLShader::create_shader_if_needed() +{ + if (shader_program_ || shader_compile_attempted_) { + return; + } + + shader_compile_attempted_ = true; + + shader_program_ = compile_shader_program(); + if (!shader_program_) { + return; + } + + glUseProgram(shader_program_); + + image_texture_location_ = glGetUniformLocation(shader_program_, "image_texture"); + if (image_texture_location_ < 0) { + LOG(ERROR) << "Shader doesn't contain the 'image_texture' uniform."; + destroy_shader(); + return; + } + + fullscreen_location_ = glGetUniformLocation(shader_program_, "fullscreen"); + if (fullscreen_location_ < 0) { + LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform."; + destroy_shader(); + return; + } +} + +void OpenGLShader::destroy_shader() +{ + glDeleteProgram(shader_program_); + shader_program_ = 0; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/shader.h b/intern/cycles/app/opengl/shader.h new file mode 100644 index 00000000000..682463891d5 --- /dev/null +++ b/intern/cycles/app/opengl/shader.h @@ -0,0 +1,58 @@ +/* + * Copyright 2011-2022 OpenGL Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "util/types.h" + +CCL_NAMESPACE_BEGIN + +class OpenGLShader { + public: + static constexpr const char *position_attribute_name = "pos"; + static constexpr const char *tex_coord_attribute_name = "texCoord"; + + OpenGLShader() = default; + virtual ~OpenGLShader() = default; + + /* Get attribute location for position and texture coordinate respectively. + * NOTE: The shader needs to be bound to have access to those. */ + int get_position_attrib_location(); + int get_tex_coord_attrib_location(); + + void bind(int width, int height); + void unbind(); + + protected: + uint get_shader_program(); + + void create_shader_if_needed(); + void destroy_shader(); + + /* Cached values of various OpenGL resources. */ + int position_attribute_location_ = -1; + int tex_coord_attribute_location_ = -1; + + uint shader_program_ = 0; + int image_texture_location_ = -1; + int fullscreen_location_ = -1; + + /* Shader compilation attempted. Which means, that if the shader program is 0 then compilation or + * linking has failed. Do not attempt to re-compile the shader. */ + bool shader_compile_attempted_ = false; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/window.cpp b/intern/cycles/app/opengl/window.cpp new file mode 100644 index 00000000000..4da0ebdece5 --- /dev/null +++ b/intern/cycles/app/opengl/window.cpp @@ -0,0 +1,365 @@ +/* + * Copyright 2011-2022 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "app/opengl/window.h" + +#include "util/string.h" +#include "util/thread.h" +#include "util/time.h" +#include "util/version.h" + +#include +#include + +CCL_NAMESPACE_BEGIN + +/* structs */ + +struct Window { + WindowInitFunc initf = nullptr; + WindowExitFunc exitf = nullptr; + WindowResizeFunc resize = nullptr; + WindowDisplayFunc display = nullptr; + WindowKeyboardFunc keyboard = nullptr; + WindowMotionFunc motion = nullptr; + + bool first_display = true; + bool redraw = false; + + int mouseX = 0, mouseY = 0; + int mouseBut0 = 0, mouseBut2 = 0; + + int width = 0, height = 0; + + SDL_Window *window = nullptr; + SDL_GLContext gl_context = nullptr; + thread_mutex gl_context_mutex; +} V; + +/* public */ + +static void window_display_text(int x, int y, const char *text) +{ +/* Not currently supported, need to add text rendering support. */ +#if 0 + const char *c; + + glRasterPos3f(x, y, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + printf("display %s\n", text); + + for (c = text; *c != '\0'; c++) { + const uint8_t *bitmap = helvetica10_character_map[*c]; + glBitmap(bitmap[0], + helvetica10_height, + helvetica10_x_offset, + helvetica10_y_offset, + bitmap[0], + 0.0f, + bitmap + 1); + } +#else + static string last_text = ""; + + if (text != last_text) { + printf("%s\n", text); + last_text = text; + } +#endif +} + +void window_display_info(const char *info) +{ + const int height = 20; + +#if 0 + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.1f, 0.1f, 0.1f, 0.8f); + glRectf(0.0f, V.height - height, V.width, V.height); + glDisable(GL_BLEND); + + glColor3f(0.5f, 0.5f, 0.5f); +#endif + + window_display_text(10, 7 + V.height - height, info); + +#if 0 + glColor3f(1.0f, 1.0f, 1.0f); +#endif +} + +void window_display_help() +{ + const int w = (int)((float)V.width / 1.15f); + const int h = (int)((float)V.height / 1.15f); + + const int x1 = (V.width - w) / 2; +#if 0 + const int x2 = x1 + w; +#endif + + const int y1 = (V.height - h) / 2; + const int y2 = y1 + h; + +#if 0 + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.5f, 0.5f, 0.5f, 0.8f); + glRectf(x1, y1, x2, y2); + glDisable(GL_BLEND); + + glColor3f(0.8f, 0.8f, 0.8f); +#endif + + string info = string("Cycles Renderer ") + CYCLES_VERSION_STRING; + + window_display_text(x1 + 20, y2 - 20, info.c_str()); + window_display_text(x1 + 20, y2 - 40, "(C) 2011-2016 Blender Foundation"); + window_display_text(x1 + 20, y2 - 80, "Controls:"); + window_display_text(x1 + 20, y2 - 100, "h: Info/Help"); + window_display_text(x1 + 20, y2 - 120, "r: Reset"); + window_display_text(x1 + 20, y2 - 140, "p: Pause"); + window_display_text(x1 + 20, y2 - 160, "esc: Cancel"); + window_display_text(x1 + 20, y2 - 180, "q: Quit program"); + + window_display_text(x1 + 20, y2 - 210, "i: Interactive mode"); + window_display_text(x1 + 20, y2 - 230, "Left mouse: Move camera"); + window_display_text(x1 + 20, y2 - 250, "Right mouse: Rotate camera"); + window_display_text(x1 + 20, y2 - 270, "W/A/S/D: Move camera"); + window_display_text(x1 + 20, y2 - 290, "0/1/2/3: Set max bounces"); + +#if 0 + glColor3f(1.0f, 1.0f, 1.0f); +#endif +} + +static void window_display() +{ + if (V.first_display) { + if (V.initf) { + V.initf(); + } + if (V.exitf) { + atexit(V.exitf); + } + + V.first_display = false; + } + + window_opengl_context_enable(); + + glViewport(0, 0, V.width, V.height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearColor(0.05f, 0.05f, 0.05f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, V.width, 0, V.height, -1, 1); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glRasterPos3f(0, 0, 0); + + if (V.display) + V.display(); + + SDL_GL_SwapWindow(V.window); + window_opengl_context_disable(); +} + +static void window_reshape(int width, int height) +{ + if (V.width != width || V.height != height) { + if (V.resize) { + V.resize(width, height); + } + } + + V.width = width; + V.height = height; +} + +static bool window_keyboard(unsigned char key) +{ + if (V.keyboard) + V.keyboard(key); + + if (key == 'q') { + if (V.exitf) + V.exitf(); + return true; + } + + return false; +} + +static void window_mouse(int button, int state, int x, int y) +{ + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_MOUSEBUTTONDOWN) { + V.mouseX = x; + V.mouseY = y; + V.mouseBut0 = 1; + } + else if (state == SDL_MOUSEBUTTONUP) { + V.mouseBut0 = 0; + } + } + else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_MOUSEBUTTONDOWN) { + V.mouseX = x; + V.mouseY = y; + V.mouseBut2 = 1; + } + else if (state == SDL_MOUSEBUTTONUP) { + V.mouseBut2 = 0; + } + } +} + +static void window_motion(int x, int y) +{ + const int but = V.mouseBut0 ? 0 : 2; + const int distX = x - V.mouseX; + const int distY = y - V.mouseY; + + if (V.motion) + V.motion(distX, distY, but); + + V.mouseX = x; + V.mouseY = y; +} + +bool window_opengl_context_enable() +{ + V.gl_context_mutex.lock(); + SDL_GL_MakeCurrent(V.window, V.gl_context); + return true; +} + +void window_opengl_context_disable() +{ + SDL_GL_MakeCurrent(V.window, nullptr); + V.gl_context_mutex.unlock(); +} + +void window_main_loop(const char *title, + int width, + int height, + WindowInitFunc initf, + WindowExitFunc exitf, + WindowResizeFunc resize, + WindowDisplayFunc display, + WindowKeyboardFunc keyboard, + WindowMotionFunc motion) +{ + V.width = width; + V.height = height; + V.first_display = true; + V.redraw = false; + V.initf = initf; + V.exitf = exitf; + V.resize = resize; + V.display = display; + V.keyboard = keyboard; + V.motion = motion; + + SDL_Init(SDL_INIT_VIDEO); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + V.window = SDL_CreateWindow(title, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); + if (V.window == nullptr) { + fprintf(stderr, "Failed to create window: %s\n", SDL_GetError()); + return; + } + + SDL_RaiseWindow(V.window); + + V.gl_context = SDL_GL_CreateContext(V.window); + glewInit(); + SDL_GL_MakeCurrent(V.window, nullptr); + + window_reshape(width, height); + window_display(); + + while (true) { + bool quit = false; + SDL_Event event; + while (!quit && SDL_PollEvent(&event)) { + if (event.type == SDL_TEXTINPUT) { + quit = window_keyboard(event.text.text[0]); + } + else if (event.type == SDL_MOUSEMOTION) { + window_motion(event.motion.x, event.motion.y); + } + else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { + window_mouse(event.button.button, event.button.state, event.button.x, event.button.y); + } + else if (event.type == SDL_WINDOWEVENT) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + window_reshape(event.window.data1, event.window.data2); + } + } + else if (event.type == SDL_QUIT) { + if (V.exitf) { + V.exitf(); + } + quit = true; + } + } + + if (quit) { + break; + } + + if (V.redraw) { + V.redraw = false; + window_display(); + } + + SDL_WaitEventTimeout(NULL, 100); + } + + SDL_GL_DeleteContext(V.gl_context); + SDL_DestroyWindow(V.window); + SDL_Quit(); +} + +void window_redraw() +{ + V.redraw = true; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/window.h b/intern/cycles/app/opengl/window.h new file mode 100644 index 00000000000..4f9e418e5c2 --- /dev/null +++ b/intern/cycles/app/opengl/window.h @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2022 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/* Functions to display a simple OpenGL window using SDL, simplified to the + * bare minimum we need to reduce boilerplate code in tests apps. */ + +CCL_NAMESPACE_BEGIN + +typedef void (*WindowInitFunc)(); +typedef void (*WindowExitFunc)(); +typedef void (*WindowResizeFunc)(int width, int height); +typedef void (*WindowDisplayFunc)(); +typedef void (*WindowKeyboardFunc)(unsigned char key); +typedef void (*WindowMotionFunc)(int x, int y, int button); + +void window_main_loop(const char *title, + int width, + int height, + WindowInitFunc initf, + WindowExitFunc exitf, + WindowResizeFunc resize, + WindowDisplayFunc display, + WindowKeyboardFunc keyboard, + WindowMotionFunc motion); + +void window_display_info(const char *info); +void window_display_help(); +void window_redraw(); + +bool window_opengl_context_enable(); +void window_opengl_context_disable(); + +CCL_NAMESPACE_END diff --git a/intern/cycles/cmake/external_libs.cmake b/intern/cycles/cmake/external_libs.cmake index 8d9631e5b44..95c6b23220e 100644 --- a/intern/cycles/cmake/external_libs.cmake +++ b/intern/cycles/cmake/external_libs.cmake @@ -491,26 +491,22 @@ else() endif() ########################################################################### -# GLUT +# SDL ########################################################################### if(WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) - if(MSVC AND EXISTS ${_cycles_lib_dir}) - add_definitions(-DFREEGLUT_STATIC -DFREEGLUT_LIB_PRAGMAS=0) - set(GLUT_LIBRARIES "${_cycles_lib_dir}/opengl/lib/freeglut_static.lib") - set(GLUT_INCLUDE_DIR "${_cycles_lib_dir}/opengl/include") - else() - find_package(GLUT) + # We can't use the version from the Blender precompiled libraries because + # it does not include the video subsystem. + find_package(SDL2) - if(NOT GLUT_FOUND) - set(WITH_CYCLES_STANDALONE_GUI OFF) - message(STATUS "GLUT not found, disabling Cycles standalone GUI") - endif() + if(NOT SDL2_FOUND) + set(WITH_CYCLES_STANDALONE_GUI OFF) + message(STATUS "SDL not found, disabling Cycles standalone GUI") endif() include_directories( SYSTEM - ${GLUT_INCLUDE_DIR} + ${SDL2_INCLUDE_DIRS} ) endif() diff --git a/intern/cycles/device/hip/device_impl.h b/intern/cycles/device/hip/device_impl.h index 08a7be57e9c..b4a9f892643 100644 --- a/intern/cycles/device/hip/device_impl.h +++ b/intern/cycles/device/hip/device_impl.h @@ -25,8 +25,6 @@ # ifdef WITH_HIP_DYNLOAD # include "hipew.h" -# else -# include "util/opengl.h" # endif CCL_NAMESPACE_BEGIN diff --git a/intern/cycles/util/CMakeLists.txt b/intern/cycles/util/CMakeLists.txt index a26934c0ace..475682c5b86 100644 --- a/intern/cycles/util/CMakeLists.txt +++ b/intern/cycles/util/CMakeLists.txt @@ -18,7 +18,6 @@ set(INC ) set(INC_SYS - ${GLEW_INCLUDE_DIR} ) set(SRC @@ -45,14 +44,6 @@ set(LIB ${TBB_LIBRARIES} ) -if(WITH_CYCLES_STANDALONE) - if(WITH_CYCLES_STANDALONE_GUI) - list(APPEND SRC - view.cpp - ) - endif() -endif() - set(SRC_HEADERS algorithm.h aligned_malloc.h @@ -153,7 +144,6 @@ set(SRC_HEADERS unique_ptr.h vector.h version.h - view.h windows.h xml.h ) diff --git a/intern/cycles/util/view.cpp b/intern/cycles/util/view.cpp deleted file mode 100644 index 1c70cea1a8b..00000000000 --- a/intern/cycles/util/view.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2011-2013 Blender Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include "util/opengl.h" -#include "util/string.h" -#include "util/time.h" -#include "util/version.h" -#include "util/view.h" - -#ifdef __APPLE__ -# include -#else -# include -#endif - -CCL_NAMESPACE_BEGIN - -/* structs */ - -struct View { - ViewInitFunc initf; - ViewExitFunc exitf; - ViewResizeFunc resize; - ViewDisplayFunc display; - ViewKeyboardFunc keyboard; - ViewMotionFunc motion; - - bool first_display; - bool redraw; - - int mouseX, mouseY; - int mouseBut0, mouseBut2; - - int width, height; -} V; - -/* public */ - -static void view_display_text(int x, int y, const char *text) -{ - const char *c; - - glRasterPos3f(x, y, 0); - - for (c = text; *c != '\0'; c++) - glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, *c); -} - -void view_display_info(const char *info) -{ - const int height = 20; - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.1f, 0.1f, 0.1f, 0.8f); - glRectf(0.0f, V.height - height, V.width, V.height); - glDisable(GL_BLEND); - - glColor3f(0.5f, 0.5f, 0.5f); - - view_display_text(10, 7 + V.height - height, info); - - glColor3f(1.0f, 1.0f, 1.0f); -} - -void view_display_help() -{ - const int w = (int)((float)V.width / 1.15f); - const int h = (int)((float)V.height / 1.15f); - - const int x1 = (V.width - w) / 2; - const int x2 = x1 + w; - - const int y1 = (V.height - h) / 2; - const int y2 = y1 + h; - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.5f, 0.5f, 0.5f, 0.8f); - glRectf(x1, y1, x2, y2); - glDisable(GL_BLEND); - - glColor3f(0.8f, 0.8f, 0.8f); - - string info = string("Cycles Renderer ") + CYCLES_VERSION_STRING; - - view_display_text(x1 + 20, y2 - 20, info.c_str()); - view_display_text(x1 + 20, y2 - 40, "(C) 2011-2016 Blender Foundation"); - view_display_text(x1 + 20, y2 - 80, "Controls:"); - view_display_text(x1 + 20, y2 - 100, "h: Info/Help"); - view_display_text(x1 + 20, y2 - 120, "r: Reset"); - view_display_text(x1 + 20, y2 - 140, "p: Pause"); - view_display_text(x1 + 20, y2 - 160, "esc: Cancel"); - view_display_text(x1 + 20, y2 - 180, "q: Quit program"); - - view_display_text(x1 + 20, y2 - 210, "i: Interactive mode"); - view_display_text(x1 + 20, y2 - 230, "Left mouse: Move camera"); - view_display_text(x1 + 20, y2 - 250, "Right mouse: Rotate camera"); - view_display_text(x1 + 20, y2 - 270, "W/A/S/D: Move camera"); - view_display_text(x1 + 20, y2 - 290, "0/1/2/3: Set max bounces"); - - glColor3f(1.0f, 1.0f, 1.0f); -} - -static void view_display() -{ - if (V.first_display) { - if (V.initf) - V.initf(); - if (V.exitf) - atexit(V.exitf); - - V.first_display = false; - } - - glClearColor(0.05f, 0.05f, 0.05f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, V.width, 0, V.height, -1, 1); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glRasterPos3f(0, 0, 0); - - if (V.display) - V.display(); - - glutSwapBuffers(); -} - -static void view_reshape(int width, int height) -{ - if (width <= 0 || height <= 0) - return; - - V.width = width; - V.height = height; - - glViewport(0, 0, width, height); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - if (V.resize) - V.resize(width, height); -} - -static void view_keyboard(unsigned char key, int x, int y) -{ - if (V.keyboard) - V.keyboard(key); - - if (key == 'm') - printf("mouse %d %d\n", x, y); - if (key == 'q') { - if (V.exitf) - V.exitf(); - exit(0); - } -} - -static void view_mouse(int button, int state, int x, int y) -{ - if (button == 0) { - if (state == GLUT_DOWN) { - V.mouseX = x; - V.mouseY = y; - V.mouseBut0 = 1; - } - else if (state == GLUT_UP) { - V.mouseBut0 = 0; - } - } - else if (button == 2) { - if (state == GLUT_DOWN) { - V.mouseX = x; - V.mouseY = y; - V.mouseBut2 = 1; - } - else if (state == GLUT_UP) { - V.mouseBut2 = 0; - } - } -} - -static void view_motion(int x, int y) -{ - const int but = V.mouseBut0 ? 0 : 2; - const int distX = x - V.mouseX; - const int distY = y - V.mouseY; - - if (V.motion) - V.motion(distX, distY, but); - - V.mouseX = x; - V.mouseY = y; -} - -static void view_idle() -{ - if (V.redraw) { - V.redraw = false; - glutPostRedisplay(); - } - - time_sleep(0.1); -} - -void view_main_loop(const char *title, - int width, - int height, - ViewInitFunc initf, - ViewExitFunc exitf, - ViewResizeFunc resize, - ViewDisplayFunc display, - ViewKeyboardFunc keyboard, - ViewMotionFunc motion) -{ - const char *name = "app"; - char *argv = (char *)name; - int argc = 1; - - memset(&V, 0, sizeof(V)); - V.width = width; - V.height = height; - V.first_display = true; - V.redraw = false; - V.initf = initf; - V.exitf = exitf; - V.resize = resize; - V.display = display; - V.keyboard = keyboard; - V.motion = motion; - - glutInit(&argc, &argv); - glutInitWindowSize(width, height); - glutInitWindowPosition(0, 0); - glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); - glutCreateWindow(title); - - glewInit(); - - view_reshape(width, height); - - glutDisplayFunc(view_display); - glutIdleFunc(view_idle); - glutReshapeFunc(view_reshape); - glutKeyboardFunc(view_keyboard); - glutMouseFunc(view_mouse); - glutMotionFunc(view_motion); - - glutMainLoop(); -} - -void view_redraw() -{ - V.redraw = true; -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/util/view.h b/intern/cycles/util/view.h deleted file mode 100644 index ad5c53ee5d5..00000000000 --- a/intern/cycles/util/view.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2011-2013 Blender Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __UTIL_VIEW_H__ -#define __UTIL_VIEW_H__ - -/* Functions to display a simple OpenGL window using GLUT, simplified to the - * bare minimum we need to reduce boilerplate code in tests apps. */ - -CCL_NAMESPACE_BEGIN - -typedef void (*ViewInitFunc)(); -typedef void (*ViewExitFunc)(); -typedef void (*ViewResizeFunc)(int width, int height); -typedef void (*ViewDisplayFunc)(); -typedef void (*ViewKeyboardFunc)(unsigned char key); -typedef void (*ViewMotionFunc)(int x, int y, int button); - -void view_main_loop(const char *title, - int width, - int height, - ViewInitFunc initf, - ViewExitFunc exitf, - ViewResizeFunc resize, - ViewDisplayFunc display, - ViewKeyboardFunc keyboard, - ViewMotionFunc motion); - -void view_display_info(const char *info); -void view_display_help(); -void view_redraw(); - -CCL_NAMESPACE_END - -#endif /*__UTIL_VIEW_H__*/ -- cgit v1.2.3 From 7e312f89d970744eb90351de01101358613b89ec Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 16 Feb 2022 11:52:48 +0100 Subject: Cleanup: Use const qualifier in modifier data copy --- source/blender/blenkernel/BKE_modifier.h | 6 ++++-- source/blender/blenkernel/intern/modifier.c | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 9a212cb1275..6df503153b9 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -415,8 +415,10 @@ bool BKE_modifier_unique_name(struct ListBase *modifiers, struct ModifierData *m void BKE_modifier_copydata_generic(const struct ModifierData *md, struct ModifierData *md_dst, int flag); -void BKE_modifier_copydata(struct ModifierData *md, struct ModifierData *target); -void BKE_modifier_copydata_ex(struct ModifierData *md, struct ModifierData *target, int flag); +void BKE_modifier_copydata(const struct ModifierData *md, struct ModifierData *target); +void BKE_modifier_copydata_ex(const struct ModifierData *md, + struct ModifierData *target, + int flag); bool BKE_modifier_depends_ontime(struct Scene *scene, struct ModifierData *md, int dag_eval_mode); bool BKE_modifier_supports_mapping(struct ModifierData *md); bool BKE_modifier_supports_cage(struct Scene *scene, struct ModifierData *md); diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 46fea5ae115..0e8838d16ce 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -348,7 +348,7 @@ static void modifier_copy_data_id_us_cb(void *UNUSED(userData), } } -void BKE_modifier_copydata_ex(ModifierData *md, ModifierData *target, const int flag) +void BKE_modifier_copydata_ex(const ModifierData *md, ModifierData *target, const int flag) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); @@ -378,7 +378,7 @@ void BKE_modifier_copydata_ex(ModifierData *md, ModifierData *target, const int } } -void BKE_modifier_copydata(ModifierData *md, ModifierData *target) +void BKE_modifier_copydata(const ModifierData *md, ModifierData *target) { BKE_modifier_copydata_ex(md, target, 0); } -- cgit v1.2.3 From 03ff58b67d50a714c5609e584a6942996cfdee1b Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 16 Feb 2022 11:56:04 +0100 Subject: Cleanup: Use const qualifier in modifier data copy Fix possible overflow of Modifier UUID The code prior this change was re-generating modifier's session UUID prior to copying this id from the source. This approach has a higher risk of modifiers session UUID to overflow and start colliding with existing modifiers. This change makes it so that modifier copy does not re-generated the session UUID unless it is needed. Differential Revision: https://developer.blender.org/D14125 --- source/blender/blenkernel/BKE_modifier.h | 2 ++ source/blender/blenkernel/intern/modifier.c | 19 ++++++++++++++++++- source/blender/blenkernel/intern/object.cc | 4 +--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 6df503153b9..acdca23b21c 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -409,6 +409,8 @@ void BKE_modifier_session_uuid_generate(struct ModifierData *md); bool BKE_modifier_unique_name(struct ListBase *modifiers, struct ModifierData *md); +struct ModifierData *BKE_modifier_copy_ex(const struct ModifierData *md, int flag); + /** * Callback's can use this to avoid copying every member. */ diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 0e8838d16ce..837c1fe179f 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -131,7 +131,7 @@ void BKE_modifier_panel_expand(ModifierData *md) /***/ -ModifierData *BKE_modifier_new(int type) +static ModifierData *modifier_allocate_and_init(int type) { const ModifierTypeInfo *mti = BKE_modifier_get_info(type); ModifierData *md = MEM_callocN(mti->structSize, mti->structName); @@ -152,6 +152,13 @@ ModifierData *BKE_modifier_new(int type) mti->initData(md); } + return md; +} + +ModifierData *BKE_modifier_new(int type) +{ + ModifierData *md = modifier_allocate_and_init(type); + BKE_modifier_session_uuid_generate(md); return md; @@ -315,6 +322,16 @@ void BKE_modifiers_foreach_tex_link(Object *ob, TexWalkFunc walk, void *userData } } +ModifierData *BKE_modifier_copy_ex(const ModifierData *md, int flag) +{ + ModifierData *md_dst = modifier_allocate_and_init(md->type); + + BLI_strncpy(md_dst->name, md->name, sizeof(md_dst->name)); + BKE_modifier_copydata_ex(md, md_dst, flag); + + return md_dst; +} + void BKE_modifier_copydata_generic(const ModifierData *md_src, ModifierData *md_dst, const int UNUSED(flag)) diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 579e61750f0..3a8d7d6bc8a 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -1599,9 +1599,7 @@ bool BKE_object_modifier_stack_copy(Object *ob_dst, continue; } - ModifierData *md_dst = BKE_modifier_new(md_src->type); - BLI_strncpy(md_dst->name, md_src->name, sizeof(md_dst->name)); - BKE_modifier_copydata_ex(md_src, md_dst, flag_subdata); + ModifierData *md_dst = BKE_modifier_copy_ex(md_src, flag_subdata); BLI_addtail(&ob_dst->modifiers, md_dst); } -- cgit v1.2.3 From 257ba175fa7e324792ce5f32d5369bcd685b8f55 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 16 Feb 2022 15:58:27 +0100 Subject: Fix: removing anonymous attributes before adding mesh to bmain This was an issue when e.g. `bpy.data.meshes.new_from_object` was used on an object that uses geometry nodes. --- source/blender/blenkernel/intern/mesh_convert.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 87631348188..22e4c3bf13c 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -1236,6 +1236,9 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, nullptr, &CD_MASK_MESH, true); + /* Anonymous attributes shouldn't exist on original data. */ + BKE_mesh_anonymous_attributes_remove(mesh_in_bmain); + /* User-count is required because so far mesh was in a limbo, where library management does * not perform any user management (i.e. copy of a mesh will not increase users of materials). */ BKE_library_foreach_ID_link( -- cgit v1.2.3 From 399168f3c13fadb41c9fbec8a1b5c56cb6609343 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 16 Feb 2022 10:28:18 -0600 Subject: BLI: Implement templated math functions for basic types This is meant to complement the `blender::math` functions recently added by D13791. It's sometimes desired to template an operation to work on vector types, but also basic types like `float` and `int`. This patch adds that ability with a new `BLI_math_base.hh` header. The existing vector math header is changed to use the `vec_base` type more explicitly, to allow the compiler's generic function overload resolution to determine which implementation of each math function to use. This is a relatively large change, but it also makes the file significantly easier to understand by reducing the use of macros. Differential Revision: https://developer.blender.org/D14113 --- source/blender/blenlib/BLI_math_base.hh | 104 +++++++++ source/blender/blenlib/BLI_math_vec_types.hh | 19 +- source/blender/blenlib/BLI_math_vector.hh | 245 +++++++++++---------- source/blender/blenlib/CMakeLists.txt | 1 + source/blender/blenlib/intern/delaunay_2d.cc | 4 +- source/blender/blenlib/tests/BLI_math_base_test.cc | 21 ++ 6 files changed, 275 insertions(+), 119 deletions(-) create mode 100644 source/blender/blenlib/BLI_math_base.hh diff --git a/source/blender/blenlib/BLI_math_base.hh b/source/blender/blenlib/BLI_math_base.hh new file mode 100644 index 00000000000..6a988eda8a9 --- /dev/null +++ b/source/blender/blenlib/BLI_math_base.hh @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include +#include +#include + +#include "BLI_math_base_safe.h" +#include "BLI_math_vec_types.hh" +#include "BLI_utildefines.h" + +#ifdef WITH_GMP +# include "BLI_math_mpq.hh" +#endif + +namespace blender::math { + +template inline bool is_zero(const T &a) +{ + return a == T(0); +} + +template inline bool is_any_zero(const T &a) +{ + return is_zero(a); +} + +template inline T abs(const T &a) +{ + return std::abs(a); +} + +template inline T min(const T &a, const T &b) +{ + return std::min(a, b); +} + +template inline T max(const T &a, const T &b) +{ + return std::max(a, b); +} + +template inline T clamp(const T &a, const T &min, const T &max) +{ + return std::clamp(a, min, max); +} + +template))> inline T mod(const T &a, const T &b) +{ + return std::fmod(a, b); +} + +template))> +inline T safe_mod(const T &a, const T &b) +{ + return (b != 0) ? std::fmod(a, b) : 0; +} + +template inline void min_max(const T &value, T &min, T &max) +{ + min = math::min(value, min); + max = math::max(value, max); +} + +template))> +inline T safe_divide(const T &a, const T &b) +{ + return (b != 0) ? a / b : T(0.0f); +} + +template))> inline T floor(const T &a) +{ + return std::floor(a); +} + +template))> inline T ceil(const T &a) +{ + return std::ceil(a); +} + +template))> inline T fract(const T &a) +{ + return a - std::floor(a); +} + +template))> +inline T interpolate(const T &a, const T &b, const T &t) +{ + return a * (1 - t) + b * t; +} + +template))> +inline T midpoint(const T &a, const T &b) +{ + return (a + b) * T(0.5); +} + +} // namespace blender::math diff --git a/source/blender/blenlib/BLI_math_vec_types.hh b/source/blender/blenlib/BLI_math_vec_types.hh index 8d6f04ab15f..b6b3d15aefe 100644 --- a/source/blender/blenlib/BLI_math_vec_types.hh +++ b/source/blender/blenlib/BLI_math_vec_types.hh @@ -14,6 +14,10 @@ #include "BLI_utildefines.h" +#ifdef WITH_GMP +# include "BLI_math_mpq.hh" +#endif + namespace blender { /* clang-format off */ @@ -60,10 +64,10 @@ template uint64_t vector_hash(const T &vec) return result; } -template inline bool is_any_zero(const T &a) +template inline bool is_any_zero(const vec_struct_base &a) { - for (int i = 0; i < T::type_length; i++) { - if (a[i] == typename T::base_type(0)) { + for (int i = 0; i < Size; i++) { + if (a[i] == T(0)) { return true; } } @@ -579,4 +583,13 @@ using double2 = vec_base; using double3 = vec_base; using double4 = vec_base; +template +inline constexpr bool is_math_float_type = (std::is_floating_point_v +#ifdef WITH_GMP + || std::is_same_v +#endif +); + +template inline constexpr bool is_math_integral_type = std::is_integral_v; + } // namespace blender diff --git a/source/blender/blenlib/BLI_math_vector.hh b/source/blender/blenlib/BLI_math_vector.hh index d2ef2a1c5c8..3bb89bb26b2 100644 --- a/source/blender/blenlib/BLI_math_vector.hh +++ b/source/blender/blenlib/BLI_math_vector.hh @@ -15,10 +15,6 @@ #include "BLI_span.hh" #include "BLI_utildefines.h" -#ifdef WITH_GMP -# include "BLI_math_mpq.hh" -#endif - namespace blender::math { #ifndef NDEBUG @@ -33,277 +29,293 @@ namespace blender::math { # define BLI_ASSERT_UNIT(v) (void)(v) #endif -#define bT typename T::base_type - -#ifdef WITH_GMP -# define BLI_ENABLE_IF_FLT_VEC(T) \ - BLI_ENABLE_IF((std::is_floating_point_v || \ - std::is_same_v)) -#else -# define BLI_ENABLE_IF_FLT_VEC(T) BLI_ENABLE_IF((std::is_floating_point_v)) -#endif - -#define BLI_ENABLE_IF_INT_VEC(T) BLI_ENABLE_IF((std::is_integral_v)) - -template inline bool is_zero(const T &a) +template inline bool is_zero(const vec_base &a) { - for (int i = 0; i < T::type_length; i++) { - if (a[i] != bT(0)) { + for (int i = 0; i < Size; i++) { + if (a[i] != T(0)) { return false; } } return true; } -template inline T abs(const T &a) +template inline vec_base abs(const vec_base &a) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = a[i] >= 0 ? a[i] : -a[i]; } return result; } -template inline T min(const T &a, const T &b) +template +inline vec_base min(const vec_base &a, const vec_base &b) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = a[i] < b[i] ? a[i] : b[i]; } return result; } -template inline T max(const T &a, const T &b) +template +inline vec_base max(const vec_base &a, const vec_base &b) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = a[i] > b[i] ? a[i] : b[i]; } return result; } -template inline T clamp(const T &a, const T &min_v, const T &max_v) +template +inline T clamp(const vec_base &a, + const vec_base &min, + const vec_base &max) { - T result = a; - for (int i = 0; i < T::type_length; i++) { - CLAMP(result[i], min_v[i], max_v[i]); + vec_base result = a; + for (int i = 0; i < Size; i++) { + std::clamp(result[i], min[i], max[i]); } return result; } -template inline T clamp(const T &a, const bT &min_v, const bT &max_v) +template +inline vec_base clamp(const vec_base &a, const T &min, const T &max) { - T result = a; - for (int i = 0; i < T::type_length; i++) { - CLAMP(result[i], min_v, max_v); + vec_base result = a; + for (int i = 0; i < Size; i++) { + std::clamp(result[i], min, max); } return result; } -template inline T mod(const T &a, const T &b) +template))> +inline vec_base mod(const vec_base &a, const vec_base &b) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { BLI_assert(b[i] != 0); result[i] = std::fmod(a[i], b[i]); } return result; } -template inline T mod(const T &a, bT b) +template))> +inline vec_base mod(const vec_base &a, const T &b) { BLI_assert(b != 0); - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = std::fmod(a[i], b); } return result; } -template inline T safe_mod(const T &a, const T &b) +template))> +inline T safe_mod(const vec_base &a, const vec_base &b) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = (b[i] != 0) ? std::fmod(a[i], b[i]) : 0; } return result; } -template inline T safe_mod(const T &a, bT b) +template))> +inline T safe_mod(const vec_base &a, const T &b) { if (b == 0) { - return T(0.0f); + return vec_base(0); } - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = std::fmod(a[i], b); } return result; } -template inline void min_max(const T &vector, T &min_vec, T &max_vec) +template +inline void min_max(const vec_base &vector, + vec_base &min, + vec_base &max) { - min_vec = min(vector, min_vec); - max_vec = max(vector, max_vec); + min = math::min(vector, min); + max = math::max(vector, max); } -template inline T safe_divide(const T &a, const T &b) +template))> +inline vec_base safe_divide(const vec_base &a, const vec_base &b) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = (b[i] == 0) ? 0 : a[i] / b[i]; } return result; } -template inline T safe_divide(const T &a, const bT b) +template))> +inline vec_base safe_divide(const vec_base &a, const T &b) { - return (b != 0) ? a / b : T(0.0f); + return (b != 0) ? a / b : vec_base(0.0f); } -template inline T floor(const T &a) +template))> +inline vec_base floor(const vec_base &a) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = std::floor(a[i]); } return result; } -template inline T ceil(const T &a) +template))> +inline vec_base ceil(const vec_base &a) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = std::ceil(a[i]); } return result; } -template inline T fract(const T &a) +template))> +inline vec_base fract(const vec_base &a) { - T result; - for (int i = 0; i < T::type_length; i++) { + vec_base result; + for (int i = 0; i < Size; i++) { result[i] = a[i] - std::floor(a[i]); } return result; } -template inline bT dot(const T &a, const T &b) +template))> +inline T dot(const vec_base &a, const vec_base &b) { - bT result = a[0] * b[0]; - for (int i = 1; i < T::type_length; i++) { + T result = a[0] * b[0]; + for (int i = 1; i < Size; i++) { result += a[i] * b[i]; } return result; } -template inline bT length_manhattan(const T &a) +template inline T length_manhattan(const vec_base &a) { - bT result = std::abs(a[0]); - for (int i = 1; i < T::type_length; i++) { + T result = std::abs(a[0]); + for (int i = 1; i < Size; i++) { result += std::abs(a[i]); } return result; } -template inline bT length_squared(const T &a) +template))> +inline T length_squared(const vec_base &a) { return dot(a, a); } -template inline bT length(const T &a) +template))> +inline T length(const vec_base &a) { return std::sqrt(length_squared(a)); } -template inline bT distance_manhattan(const T &a, const T &b) +template))> +inline T distance_manhattan(const vec_base &a, const vec_base &b) { return length_manhattan(a - b); } -template inline bT distance_squared(const T &a, const T &b) +template))> +inline T distance_squared(const vec_base &a, const vec_base &b) { return length_squared(a - b); } -template inline bT distance(const T &a, const T &b) +template))> +inline T distance(const vec_base &a, const vec_base &b) { return length(a - b); } -template inline T reflect(const T &incident, const T &normal) +template))> +inline vec_base reflect(const vec_base &incident, + const vec_base &normal) { BLI_ASSERT_UNIT(normal); return incident - 2.0 * dot(normal, incident) * normal; } -template -inline T refract(const T &incident, const T &normal, const bT eta) +template))> +inline vec_base refract(const vec_base &incident, + const vec_base &normal, + const T &eta) { float dot_ni = dot(normal, incident); float k = 1.0f - eta * eta * (1.0f - dot_ni * dot_ni); if (k < 0.0f) { - return T(0.0f); + return vec_base(0.0f); } return eta * incident - (eta * dot_ni + sqrt(k)) * normal; } -template inline T project(const T &p, const T &v_proj) +template))> +inline vec_base project(const vec_base &p, const vec_base &v_proj) { if (UNLIKELY(is_zero(v_proj))) { - return T(0.0f); + return vec_base(0.0f); } return v_proj * (dot(p, v_proj) / dot(v_proj, v_proj)); } -template -inline T normalize_and_get_length(const T &v, bT &out_length) +template))> +inline vec_base normalize_and_get_length(const vec_base &v, T &out_length) { out_length = length_squared(v); /* A larger value causes normalize errors in a scaled down models with camera extreme close. */ - constexpr bT threshold = std::is_same_v ? 1.0e-70 : 1.0e-35f; + constexpr T threshold = std::is_same_v ? 1.0e-70 : 1.0e-35f; if (out_length > threshold) { out_length = sqrt(out_length); return v / out_length; } /* Either the vector is small or one of it's values contained `nan`. */ out_length = 0.0; - return T(0.0); + return vec_base(0.0); } -template inline T normalize(const T &v) +template))> +inline vec_base normalize(const vec_base &v) { - bT len; + T len; return normalize_and_get_length(v, len); } -template -inline T cross(const T &a, const T &b) +template))> +inline vec_base cross(const vec_base &a, const vec_base &b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } -template)), - BLI_ENABLE_IF((T::type_length == 3))> -inline T cross_high_precision(const T &a, const T &b) +inline vec_base cross_high_precision(const vec_base &a, + const vec_base &b) { return {(float)((double)a.y * b.z - (double)a.z * b.y), (float)((double)a.z * b.x - (double)a.x * b.z), (float)((double)a.x * b.y - (double)a.y * b.x)}; } -template -inline T cross_poly(Span poly) +template))> +inline vec_base cross_poly(Span> poly) { /* Newell's Method. */ int nv = static_cast(poly.size()); if (nv < 3) { - return T(0, 0, 0); + return vec_base(0, 0, 0); } - const T *v_prev = &poly[nv - 1]; - const T *v_curr = &poly[0]; - T n(0, 0, 0); + const vec_base *v_prev = &poly[nv - 1]; + const vec_base *v_curr = &poly[0]; + vec_base n(0, 0, 0); for (int i = 0; i < nv;) { n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]); n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]); @@ -317,25 +329,31 @@ inline T cross_poly(Span poly) return n; } -template inline T interpolate(const T &a, const T &b, bT t) +template))> +inline vec_base interpolate(const vec_base &a, + const vec_base &b, + const T &t) { return a * (1 - t) + b * t; } -template inline T midpoint(const T &a, const T &b) +template))> +inline vec_base midpoint(const vec_base &a, const vec_base &b) { return (a + b) * 0.5; } -template -inline T faceforward(const T &vector, const T &incident, const T &reference) +template))> +inline vec_base faceforward(const vec_base &vector, + const vec_base &incident, + const vec_base &reference) { return (dot(reference, incident) < 0) ? vector : -vector; } -template inline int dominant_axis(const T &a) +template inline int dominant_axis(const vec_base &a) { - T b = abs(a); + vec_base b = abs(a); return ((b.x > b.y) ? ((b.x > b.z) ? 0 : 2) : ((b.y > b.z) ? 1 : 2)); } @@ -348,14 +366,13 @@ template struct isect_result { LINE_LINE_EXACT = 1, LINE_LINE_CROSS = 2, } kind; - bT lambda; + typename T::base_type lambda; }; -template -isect_result isect_seg_seg(const T &v1, const T &v2, const T &v3, const T &v4); - -#undef BLI_ENABLE_IF_FLT_VEC -#undef BLI_ENABLE_IF_INT_VEC -#undef bT +template))> +isect_result> isect_seg_seg(const vec_base &v1, + const vec_base &v2, + const vec_base &v3, + const vec_base &v4); } // namespace blender::math diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 29015084679..e67d673eb73 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -220,6 +220,7 @@ set(SRC BLI_map.hh BLI_map_slots.hh BLI_math.h + BLI_math_base.hh BLI_math_base.h BLI_math_base_safe.h BLI_math_bits.h diff --git a/source/blender/blenlib/intern/delaunay_2d.cc b/source/blender/blenlib/intern/delaunay_2d.cc index cb0ba763c94..cc5550ee34a 100644 --- a/source/blender/blenlib/intern/delaunay_2d.cc +++ b/source/blender/blenlib/intern/delaunay_2d.cc @@ -1691,7 +1691,7 @@ void fill_crossdata_for_intersect(const FatCo &curco, BLI_assert(se_vcva->vert == vc && se_vcva->next->vert == va); BLI_assert(se_vcvb->vert == vc && se_vcvb->next->vert == vb); UNUSED_VARS_NDEBUG(vc); - auto isect = isect_seg_seg>(va->co.exact, vb->co.exact, curco.exact, v2->co.exact); + auto isect = isect_seg_seg(va->co.exact, vb->co.exact, curco.exact, v2->co.exact); T &lambda = isect.lambda; switch (isect.kind) { case isect_result>::LINE_LINE_CROSS: { @@ -2556,7 +2556,7 @@ template void detect_holes(CDT_state *cdt_state) if (e->symedges[0].face->visit_index == e->symedges[1].face->visit_index) { continue; /* Don't count hits on edges between faces in same region. */ } - auto isect = isect_seg_seg>(ray_end.exact, + auto isect = isect_seg_seg(ray_end.exact, mid.exact, e->symedges[0].vert->co.exact, e->symedges[1].vert->co.exact); diff --git a/source/blender/blenlib/tests/BLI_math_base_test.cc b/source/blender/blenlib/tests/BLI_math_base_test.cc index 33acefeeac2..62f2b2775d0 100644 --- a/source/blender/blenlib/tests/BLI_math_base_test.cc +++ b/source/blender/blenlib/tests/BLI_math_base_test.cc @@ -3,6 +3,10 @@ #include "testing/testing.h" #include "BLI_math.h" +#include "BLI_math_base.hh" +#include "BLI_math_vector.hh" + +namespace blender::tests { /* In tests below, when we are using -1.0f as max_diff value, we actually turn the function into a * pure-ULP one. */ @@ -131,3 +135,20 @@ TEST(math_base, FloorPowerOf10) EXPECT_NEAR(floor_power_of_10(100.1f), 100.0f, 1e-4f); EXPECT_NEAR(floor_power_of_10(99.9f), 10.0f, 1e-4f); } + +TEST(math_base, MinVectorAndFloat) +{ + EXPECT_EQ(math::min(1.0f, 2.0f), 1.0f); +} + +TEST(math_base, ClampInt) +{ + EXPECT_EQ(math::clamp(111, -50, 101), 101); +} + +TEST(math_base, Midpoint) +{ + EXPECT_NEAR(math::midpoint(100.0f, 200.0f), 150.0f, 1e-4f); +} + +} // namespace blender::tests -- cgit v1.2.3 From 5b73017ddb679bb050fe13e4d9e3e5b04ea559b9 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 16 Feb 2022 10:53:40 -0600 Subject: BLI: Generalize short algorithm for finding bounds Finding the greatest and/or smallest element in an array is a common need. This commit refactors the point cloud bounds code added in 6d7dbdbb44f379682 to a more general header in blenlib. This will allow reusing the algorithm for curves without duplicating it. Differential Revision: https://developer.blender.org/D14053 --- source/blender/blenkernel/intern/pointcloud.cc | 67 ++++--------------- source/blender/blenlib/BLI_bounds.hh | 89 +++++++++++++++++++++++++ source/blender/blenlib/CMakeLists.txt | 2 + source/blender/blenlib/tests/BLI_bounds_test.cc | 57 ++++++++++++++++ 4 files changed, 162 insertions(+), 53 deletions(-) create mode 100644 source/blender/blenlib/BLI_bounds.hh create mode 100644 source/blender/blenlib/tests/BLI_bounds_test.cc diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index 2a4e0715293..3ee46fc4f15 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -11,6 +11,7 @@ #include "DNA_object_types.h" #include "DNA_pointcloud_types.h" +#include "BLI_bounds.hh" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_vec_types.hh" @@ -254,68 +255,28 @@ PointCloud *BKE_pointcloud_new_nomain(const int totpoint) return pointcloud; } -struct MinMaxResult { - float3 min; - float3 max; -}; - -static MinMaxResult min_max_no_radii(Span positions) +static std::optional> point_cloud_bounds( + const PointCloud &pointcloud) { - using namespace blender::math; - - return blender::threading::parallel_reduce( - positions.index_range(), - 1024, - MinMaxResult{float3(FLT_MAX), float3(-FLT_MAX)}, - [&](IndexRange range, const MinMaxResult &init) { - MinMaxResult result = init; - for (const int i : range) { - min_max(positions[i], result.min, result.max); - } - return result; - }, - [](const MinMaxResult &a, const MinMaxResult &b) { - return MinMaxResult{min(a.min, b.min), max(a.max, b.max)}; - }); -} - -static MinMaxResult min_max_with_radii(Span positions, Span radii) -{ - using namespace blender::math; - - return blender::threading::parallel_reduce( - positions.index_range(), - 1024, - MinMaxResult{float3(FLT_MAX), float3(-FLT_MAX)}, - [&](IndexRange range, const MinMaxResult &init) { - MinMaxResult result = init; - for (const int i : range) { - result.min = min(positions[i] - radii[i], result.min); - result.max = max(positions[i] + radii[i], result.max); - } - return result; - }, - [](const MinMaxResult &a, const MinMaxResult &b) { - return MinMaxResult{min(a.min, b.min), max(a.max, b.max)}; - }); + Span positions{reinterpret_cast(pointcloud.co), pointcloud.totpoint}; + if (pointcloud.radius) { + Span radii{pointcloud.radius, pointcloud.totpoint}; + return blender::bounds::min_max_with_radii(positions, radii); + } + return blender::bounds::min_max(positions); } bool BKE_pointcloud_minmax(const PointCloud *pointcloud, float r_min[3], float r_max[3]) { - using namespace blender::math; + using namespace blender; - if (!pointcloud->totpoint) { + const std::optional> min_max = point_cloud_bounds(*pointcloud); + if (!min_max) { return false; } - Span positions{reinterpret_cast(pointcloud->co), pointcloud->totpoint}; - const MinMaxResult min_max = (pointcloud->radius) ? - min_max_with_radii(positions, - {pointcloud->radius, pointcloud->totpoint}) : - min_max_no_radii(positions); - - copy_v3_v3(r_min, min(min_max.min, float3(r_min))); - copy_v3_v3(r_max, max(min_max.max, float3(r_max))); + copy_v3_v3(r_min, math::min(min_max->min, float3(r_min))); + copy_v3_v3(r_max, math::max(min_max->max, float3(r_max))); return true; } diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh new file mode 100644 index 00000000000..ce440bef704 --- /dev/null +++ b/source/blender/blenlib/BLI_bounds.hh @@ -0,0 +1,89 @@ +/* + * 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 bli + * + * Generic algorithms for finding the largest and smallest elements in a span. + */ + +#include + +#include "BLI_math_vector.hh" +#include "BLI_task.hh" + +namespace blender::bounds { + +template struct MinMaxResult { + T min; + T max; +}; + +/** + * Find the smallest and largest values element-wise in the span. + */ +template static std::optional> min_max(Span values) +{ + if (values.is_empty()) { + return std::nullopt; + } + return threading::parallel_reduce( + values.index_range(), + 1024, + MinMaxResult(), + [&](IndexRange range, const MinMaxResult &init) { + MinMaxResult result = init; + for (const int i : range) { + math::min_max(values[i], result.min, result.max); + } + return result; + }, + [](const MinMaxResult &a, const MinMaxResult &b) { + return MinMaxResult{math::min(a.min, b.min), math::max(a.max, b.max)}; + }); +} + +/** + * Find the smallest and largest values element-wise in the span, adding the radius to each element + * first. The template type T is expected to have an addition operator implemented with RadiusT. + */ +template +static std::optional> min_max_with_radii(Span values, Span radii) +{ + BLI_assert(values.size() == radii.size()); + if (values.is_empty()) { + return std::nullopt; + } + return threading::parallel_reduce( + values.index_range(), + 1024, + MinMaxResult(), + [&](IndexRange range, const MinMaxResult &init) { + MinMaxResult result = init; + for (const int i : range) { + result.min = math::min(values[i] - radii[i], result.min); + result.max = math::max(values[i] + radii[i], result.max); + } + return result; + }, + [](const MinMaxResult &a, const MinMaxResult &b) { + return MinMaxResult{math::min(a.min, b.min), math::max(a.max, b.max)}; + }); +} + +} // namespace blender::bounds diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index e67d673eb73..e1b6e218ff5 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -162,6 +162,7 @@ set(SRC BLI_bitmap_draw_2d.h BLI_blenlib.h BLI_boxpack_2d.h + BLI_bounds.hh BLI_buffer.h BLI_color.hh BLI_compiler_attrs.h @@ -395,6 +396,7 @@ if(WITH_GTESTS) tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc + tests/BLI_bounds_test.cc tests/BLI_color_test.cc tests/BLI_delaunay_2d_test.cc tests/BLI_disjoint_set_test.cc diff --git a/source/blender/blenlib/tests/BLI_bounds_test.cc b/source/blender/blenlib/tests/BLI_bounds_test.cc new file mode 100644 index 00000000000..fc3affd97de --- /dev/null +++ b/source/blender/blenlib/tests/BLI_bounds_test.cc @@ -0,0 +1,57 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "BLI_math_base.hh" + +#include "BLI_array.hh" +#include "BLI_bounds.hh" + +namespace blender::tests { + +TEST(bounds, Empty) +{ + Span empty_span{}; + EXPECT_TRUE(empty_span.is_empty()); + auto result = bounds::min_max(empty_span); + EXPECT_EQ(result, std::nullopt); +} + +TEST(bounds, MinMax) +{ + Array data = {float2(0, 1), float2(3, -1), float2(0, -2), float2(-1, 1)}; + auto result = bounds::min_max(data.as_span()); + EXPECT_EQ(result->min, float2(-1, -2)); + EXPECT_EQ(result->max, float2(3, 1)); +} + +TEST(bounds, MinMaxFloat) +{ + Array data = {1.0f, 3.0f, 0.0f, -1.0f}; + auto result = bounds::min_max(data.as_span()); + EXPECT_EQ(result->min, -1.0f); + EXPECT_EQ(result->max, 3.0f); +} + +TEST(bounds, MinMaxRadii) +{ + Array data = {int2(0, 1), int2(3, -1), int2(0, -2), int2(-1, 1)}; + Array radii = {5, 1, 1, 4}; + auto result = bounds::min_max_with_radii(data.as_span(), radii.as_span()); + EXPECT_EQ(result->min, int2(-5, -4)); + EXPECT_EQ(result->max, int2(5, 6)); +} + +TEST(bounds, Large) +{ + Array data(10000); + for (const int64_t i : data.index_range()) { + data[i] = int2(i, i); + } + + auto result = bounds::min_max(data.as_span()); + EXPECT_EQ(result->min, int2(0, 0)); + EXPECT_EQ(result->max, int2(9999, 9999)); +} + +} // namespace blender::tests -- cgit v1.2.3 From c324cf153924ae2ec5b7c59eabff71652847aeae Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 16 Feb 2022 11:32:37 -0600 Subject: Curves: Further implementation of new curves data structure The general idea here is to wrap the `CurvesGeometry` DNA struct with a C++ class that can do most of the heavy lifting for the curve geometry. Using a C++ class allows easier ways to group methods, easier const correctness, and code that's more readable and faster to write. This way, it works much more like a version of `CurveEval` that uses more efficient attribute storage. This commit adds the structure of some yet-to-be-implemented code, the largest thing being mutexes and vectors meant to hold lazily calculated evaluated positions, tangents, and normals. That part might change slightly, but it's helpful to be able to see the direction this commit is aiming in. In particular, the inherently single-threaded accumulated lengths and Bezier evaluated point offsets might be cached. Ref T95355 Differential Revision: https://developer.blender.org/D14054 --- source/blender/blenkernel/BKE_curves.h | 8 +- source/blender/blenkernel/BKE_curves.hh | 156 +++++++++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/curves.cc | 171 ++++------ .../blender/blenkernel/intern/curves_geometry.cc | 377 +++++++++++++++++++++ source/blender/blenkernel/intern/object.cc | 2 +- .../blender/draw/intern/draw_cache_impl_curves.cc | 6 +- source/blender/makesdna/DNA_curves_types.h | 40 ++- source/blender/makesrna/intern/rna_curves.c | 8 +- 9 files changed, 651 insertions(+), 119 deletions(-) create mode 100644 source/blender/blenkernel/BKE_curves.hh create mode 100644 source/blender/blenkernel/intern/curves_geometry.cc diff --git a/source/blender/blenkernel/BKE_curves.h b/source/blender/blenkernel/BKE_curves.h index 2cce15fbfd6..88bb1c67fd1 100644 --- a/source/blender/blenkernel/BKE_curves.h +++ b/source/blender/blenkernel/BKE_curves.h @@ -2,9 +2,11 @@ #pragma once +#include "DNA_curves_types.h" + /** \file * \ingroup bke - * \brief Low-level operations for curves. + * \brief Low-level operations for curves that cannot be defined in the C++ header yet. */ #ifdef __cplusplus @@ -23,14 +25,10 @@ void *BKE_curves_add(struct Main *bmain, const char *name); struct BoundBox *BKE_curves_boundbox_get(struct Object *ob); -void BKE_curves_update_customdata_pointers(struct Curves *curves); bool BKE_curves_customdata_required(struct Curves *curves, struct CustomDataLayer *layer); /* Depsgraph */ -struct Curves *BKE_curves_new_for_eval(const struct Curves *curves_src, - int totpoint, - int totcurve); struct Curves *BKE_curves_copy_for_eval(struct Curves *curves_src, bool reference); void BKE_curves_data_update(struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh new file mode 100644 index 00000000000..6bcbb0f6e66 --- /dev/null +++ b/source/blender/blenkernel/BKE_curves.hh @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_curves.h" + +/** \file + * \ingroup bke + * \brief Low-level operations for curves. + */ + +#include + +#include "BLI_float4x4.hh" +#include "BLI_index_mask.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_attribute_access.hh" + +#include "FN_generic_virtual_array.hh" + +namespace blender::bke { + +/** + * Contains derived data, caches, and other information not saved in files, besides a few pointers + * to arrays that are kept in the non-runtime struct to avoid dereferencing this whenever they are + * accessed. + */ +class CurvesGeometryRuntime { + public: + /** Cache of evaluated positions. */ + mutable Vector evaluated_position_cache; + mutable std::mutex position_cache_mutex; + mutable bool position_cache_dirty = true; + + /** Direction of the spline at each evaluated point. */ + mutable Vector evaluated_tangents_cache; + mutable std::mutex tangent_cache_mutex; + mutable bool tangent_cache_dirty = true; + + /** Normal direction vectors for each evaluated point. */ + mutable Vector evaluated_normals_cache; + mutable std::mutex normal_cache_mutex; + mutable bool normal_cache_dirty = true; +}; + +/** + * A C++ class that wraps the DNA struct for better encapsulation and ease of use. It inherits + * directly from the struct rather than storing a pointer to avoid more complcated ownership + * handling. + */ +class CurvesGeometry : public ::CurvesGeometry { + public: + CurvesGeometry(); + /** + * Create curves with the given size. Only the position attribute is created, along with the + * offsets. + */ + CurvesGeometry(int point_size, int curve_size); + CurvesGeometry(const CurvesGeometry &other); + CurvesGeometry &operator=(const CurvesGeometry &other); + ~CurvesGeometry(); + + static CurvesGeometry &wrap(::CurvesGeometry &dna_struct) + { + CurvesGeometry *geometry = reinterpret_cast(&dna_struct); + return *geometry; + } + static const CurvesGeometry &wrap(const ::CurvesGeometry &dna_struct) + { + const CurvesGeometry *geometry = reinterpret_cast(&dna_struct); + return *geometry; + } + + /* -------------------------------------------------------------------- + * Accessors. + */ + + int points_size() const; + int curves_size() const; + + /** + * The total number of points in the evaluated poly curve. + * This can depend on the resolution attribute if it exists. + */ + int evaluated_points_size() const; + + /** + * Access a range of indices of point data for a specific curve. + */ + IndexRange range_for_curve(int index) const; + + /** The type (#CurveType) of each curve, or potentially a single if all are the same type. */ + VArray curve_types() const; + /** Mutable access to curve types. Call #tag_topology_changed after changing any type. */ + MutableSpan curve_types(); + + MutableSpan positions(); + Span positions() const; + + /** + * Calculate the largest and smallest position values, only including control points + * (rather than evaluated points). The existing values of `min` and `max` are taken into account. + * + * \return Whether there are any points. If the curve is empty, the inputs will be unaffected. + */ + bool bounds_min_max(float3 &min, float3 &max) const; + + /** + * The index of the first point in every curve. The size of this span is one larger than the + * number of curves. Consider using #range_for_curve rather than using the offsets directly. + */ + Span offsets() const; + MutableSpan offsets(); + + /* -------------------------------------------------------------------- + * Operations. + */ + + /** + * Change the number of elements. New values for existing attributes should be properly + * initialized afterwards. + */ + void resize(int point_size, int curve_size); + + /** Call after deforming the position attribute. */ + void tag_positions_changed(); + /** + * Call after any operation that changes the topology + * (number of points, evaluated points, or the total count). + */ + void tag_topology_changed(); + /** Call after changing the "tilt" or "up" attributes. */ + void tag_normals_changed(); + + void translate(const float3 &translation); + void transform(const float4x4 &matrix); + + void update_customdata_pointers(); + + /* -------------------------------------------------------------------- + * Attributes. + */ + + fn::GVArray adapt_domain(const fn::GVArray &varray, + AttributeDomain from, + AttributeDomain to) const; +}; + +Curves *curves_new_nomain(int point_size, int curves_size); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index bf720fa1341..78a145335b4 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -103,6 +103,7 @@ set(SRC intern/cryptomatte.cc intern/curve.cc intern/curves.cc + intern/curves_geometry.cc intern/curve_bevel.c intern/curve_convert.c intern/curve_decimate.c @@ -341,6 +342,7 @@ set(SRC BKE_cryptomatte.hh BKE_curve.h BKE_curves.h + BKE_curves.hh BKE_curve_to_mesh.hh BKE_curveprofile.h BKE_customdata.h diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index ccc20d5118a..e4a57ee1897 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -14,16 +14,18 @@ #include "DNA_material_types.h" #include "DNA_object_types.h" +#include "BLI_bounds.hh" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_math_vector.hh" #include "BLI_rand.hh" +#include "BLI_span.hh" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BKE_anim_data.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "BKE_customdata.h" #include "BKE_global.h" #include "BKE_idtype.h" @@ -44,11 +46,12 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::RandomNumberGenerator; +using blender::Span; static const char *ATTR_POSITION = "position"; -static const char *ATTR_RADIUS = "radius"; static void curves_random(Curves *curves); +static void update_custom_data_pointers(Curves &curves); static void curves_init_data(ID *id) { @@ -57,50 +60,38 @@ static void curves_init_data(ID *id) MEMCPY_STRUCT_AFTER(curves, DNA_struct_default_get(Curves), id); - CustomData_reset(&curves->geometry.point_data); - CustomData_reset(&curves->geometry.curve_data); - - CustomData_add_layer_named(&curves->geometry.point_data, - CD_PROP_FLOAT3, - CD_CALLOC, - nullptr, - curves->geometry.point_size, - ATTR_POSITION); - CustomData_add_layer_named(&curves->geometry.point_data, - CD_PROP_FLOAT, - CD_CALLOC, - nullptr, - curves->geometry.point_size, - ATTR_RADIUS); - - BKE_curves_update_customdata_pointers(curves); + new (&curves->geometry) blender::bke::CurvesGeometry(); curves_random(curves); } static void curves_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag) { + using namespace blender; + Curves *curves_dst = (Curves *)id_dst; const Curves *curves_src = (const Curves *)id_src; curves_dst->mat = static_cast(MEM_dupallocN(curves_src->mat)); - curves_dst->geometry.point_size = curves_src->geometry.point_size; - curves_dst->geometry.curve_size = curves_src->geometry.curve_size; + const bke::CurvesGeometry &src = bke::CurvesGeometry::wrap(curves_src->geometry); + bke::CurvesGeometry &dst = bke::CurvesGeometry::wrap(curves_dst->geometry); + + /* We need special handling here because the generic ID management code has already done a + * shallow copy from the source to the destination, and because the copy-on-write functionality + * isn't supported more generically yet. */ + + dst.point_size = src.point_size; + dst.curve_size = src.curve_size; const eCDAllocType alloc_type = (flag & LIB_ID_COPY_CD_REFERENCE) ? CD_REFERENCE : CD_DUPLICATE; - CustomData_copy(&curves_src->geometry.point_data, - &curves_dst->geometry.point_data, - CD_MASK_ALL, - alloc_type, - curves_dst->geometry.point_size); - CustomData_copy(&curves_src->geometry.curve_data, - &curves_dst->geometry.curve_data, - CD_MASK_ALL, - alloc_type, - curves_dst->geometry.curve_size); - BKE_curves_update_customdata_pointers(curves_dst); - - curves_dst->geometry.offsets = static_cast(MEM_dupallocN(curves_src->geometry.offsets)); + CustomData_copy(&src.point_data, &dst.point_data, CD_MASK_ALL, alloc_type, dst.point_size); + CustomData_copy(&src.curve_data, &dst.curve_data, CD_MASK_ALL, alloc_type, dst.curve_size); + + dst.curve_offsets = static_cast(MEM_dupallocN(src.curve_offsets)); + + dst.runtime = MEM_new(__func__); + + dst.update_customdata_pointers(); curves_dst->batch_cache = nullptr; } @@ -110,12 +101,9 @@ static void curves_free_data(ID *id) Curves *curves = (Curves *)id; BKE_animdata_free(&curves->id, false); - BKE_curves_batch_cache_free(curves); - - CustomData_free(&curves->geometry.point_data, curves->geometry.point_size); - CustomData_free(&curves->geometry.curve_data, curves->geometry.curve_size); + blender::bke::CurvesGeometry::wrap(curves->geometry).~CurvesGeometry(); - MEM_SAFE_FREE(curves->geometry.offsets); + BKE_curves_batch_cache_free(curves); MEM_SAFE_FREE(curves->mat); } @@ -157,7 +145,7 @@ static void curves_blend_write(BlendWriter *writer, ID *id, const void *id_addre CD_MASK_ALL, &curves->id); - BLO_write_int32_array(writer, curves->geometry.curve_size + 1, curves->geometry.offsets); + BLO_write_int32_array(writer, curves->geometry.curve_size + 1, curves->geometry.curve_offsets); BLO_write_pointer_array(writer, curves->totcol, curves->mat); if (curves->adt) { @@ -182,9 +170,11 @@ static void curves_blend_read_data(BlendDataReader *reader, ID *id) /* Geometry */ CustomData_blend_read(reader, &curves->geometry.point_data, curves->geometry.point_size); CustomData_blend_read(reader, &curves->geometry.curve_data, curves->geometry.curve_size); - BKE_curves_update_customdata_pointers(curves); + update_custom_data_pointers(*curves); + + BLO_read_int32_array(reader, curves->geometry.curve_size + 1, &curves->geometry.curve_offsets); - BLO_read_int32_array(reader, curves->geometry.curve_size + 1, &curves->geometry.offsets); + curves->geometry.runtime = MEM_new(__func__); /* Materials */ BLO_read_pointer_array(reader, (void **)&curves->mat); @@ -236,34 +226,33 @@ IDTypeInfo IDType_ID_CV = { /*lib_override_apply_post */ nullptr, }; +static void update_custom_data_pointers(Curves &curves) +{ + blender::bke::CurvesGeometry::wrap(curves.geometry).update_customdata_pointers(); +} + static void curves_random(Curves *curves) { - CurvesGeometry &geometry = curves->geometry; const int numpoints = 8; - geometry.curve_size = 500; - - geometry.curve_size = 500; - geometry.point_size = geometry.curve_size * numpoints; + blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry); + geometry = blender::bke::CurvesGeometry(500 * numpoints, 500); - curves->geometry.offsets = (int *)MEM_calloc_arrayN( - curves->geometry.curve_size + 1, sizeof(int), __func__); - CustomData_realloc(&geometry.point_data, geometry.point_size); - CustomData_realloc(&geometry.curve_data, geometry.curve_size); - BKE_curves_update_customdata_pointers(curves); + MutableSpan offsets = geometry.offsets(); + MutableSpan positions = geometry.positions(); - MutableSpan offsets{geometry.offsets, geometry.curve_size + 1}; - MutableSpan positions{(float3 *)geometry.position, geometry.point_size}; - MutableSpan radii{geometry.radius, geometry.point_size}; + float *radius_data = (float *)CustomData_add_layer_named( + &geometry.point_data, CD_PROP_FLOAT, CD_DEFAULT, nullptr, geometry.point_size, "radius"); + MutableSpan radii{radius_data, geometry.points_size()}; for (const int i : offsets.index_range()) { - geometry.offsets[i] = numpoints * i; + offsets[i] = numpoints * i; } RandomNumberGenerator rng; for (int i = 0; i < geometry.curve_size; i++) { - const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); + const IndexRange curve_range = geometry.range_for_curve(i); MutableSpan curve_positions = positions.slice(curve_range); MutableSpan curve_radii = radii.slice(curve_range); @@ -304,18 +293,13 @@ BoundBox *BKE_curves_boundbox_get(Object *ob) if (ob->runtime.bb == nullptr) { ob->runtime.bb = MEM_cnew(__func__); - float min[3], max[3]; - INIT_MINMAX(min, max); - - float(*curves_co)[3] = curves->geometry.position; - float *curves_radius = curves->geometry.radius; - for (int a = 0; a < curves->geometry.point_size; a++) { - float *co = curves_co[a]; - float radius = (curves_radius) ? curves_radius[a] : 0.0f; - const float co_min[3] = {co[0] - radius, co[1] - radius, co[2] - radius}; - const float co_max[3] = {co[0] + radius, co[1] + radius, co[2] + radius}; - DO_MIN(co_min, min); - DO_MAX(co_max, max); + blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry); + + float3 min(FLT_MAX); + float3 max(-FLT_MAX); + if (!geometry.bounds_min_max(min, max)) { + min = float3(-1); + max = float3(1); } BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); @@ -324,46 +308,11 @@ BoundBox *BKE_curves_boundbox_get(Object *ob) return ob->runtime.bb; } -void BKE_curves_update_customdata_pointers(Curves *curves) -{ - curves->geometry.position = (float(*)[3])CustomData_get_layer_named( - &curves->geometry.point_data, CD_PROP_FLOAT3, ATTR_POSITION); - curves->geometry.radius = (float *)CustomData_get_layer_named( - &curves->geometry.point_data, CD_PROP_FLOAT, ATTR_RADIUS); -} - bool BKE_curves_customdata_required(Curves *UNUSED(curves), CustomDataLayer *layer) { return layer->type == CD_PROP_FLOAT3 && STREQ(layer->name, ATTR_POSITION); } -/* Dependency Graph */ - -Curves *BKE_curves_new_for_eval(const Curves *curves_src, int totpoint, int totcurve) -{ - Curves *curves_dst = static_cast(BKE_id_new_nomain(ID_CV, nullptr)); - - STRNCPY(curves_dst->id.name, curves_src->id.name); - curves_dst->mat = static_cast(MEM_dupallocN(curves_src->mat)); - curves_dst->totcol = curves_src->totcol; - - curves_dst->geometry.point_size = totpoint; - curves_dst->geometry.curve_size = totcurve; - CustomData_copy(&curves_src->geometry.point_data, - &curves_dst->geometry.point_data, - CD_MASK_ALL, - CD_CALLOC, - totpoint); - CustomData_copy(&curves_src->geometry.curve_data, - &curves_dst->geometry.curve_data, - CD_MASK_ALL, - CD_CALLOC, - totcurve); - BKE_curves_update_customdata_pointers(curves_dst); - - return curves_dst; -} - Curves *BKE_curves_copy_for_eval(Curves *curves_src, bool reference) { int flags = LIB_ID_COPY_LOCALIZE; @@ -414,7 +363,7 @@ static Curves *curves_evaluate_modifiers(struct Depsgraph *depsgraph, CD_PROP_FLOAT3, ATTR_POSITION, curves->geometry.point_size); - BKE_curves_update_customdata_pointers(curves); + update_custom_data_pointers(*curves); /* Created deformed coordinates array on demand. */ mti->deformVerts( @@ -457,3 +406,15 @@ void BKE_curves_batch_cache_free(Curves *curves) BKE_curves_batch_cache_free_cb(curves); } } + +namespace blender::bke { + +Curves *curves_new_nomain(const int point_size, const int curves_size) +{ + Curves *curves = static_cast(BKE_id_new_nomain(ID_CV, nullptr)); + CurvesGeometry &geometry = CurvesGeometry::wrap(curves->geometry); + geometry.resize(point_size, curves_size); + return curves; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc new file mode 100644 index 00000000000..dcd04a408fa --- /dev/null +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -0,0 +1,377 @@ +/* + * 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. + */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_bounds.hh" + +#include "DNA_curves_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" + +namespace blender::bke { + +static const std::string ATTR_POSITION = "position"; +static const std::string ATTR_RADIUS = "radius"; +static const std::string ATTR_CURVE_TYPE = "curve_type"; + +/* -------------------------------------------------------------------- */ +/** \name Constructors/Destructor + * \{ */ + +CurvesGeometry::CurvesGeometry() : CurvesGeometry(0, 0) +{ +} + +CurvesGeometry::CurvesGeometry(const int point_size, const int curve_size) +{ + this->point_size = point_size; + this->curve_size = curve_size; + CustomData_reset(&this->point_data); + CustomData_reset(&this->curve_data); + + CustomData_add_layer_named(&this->point_data, + CD_PROP_FLOAT3, + CD_DEFAULT, + nullptr, + this->point_size, + ATTR_POSITION.c_str()); + + this->curve_offsets = (int *)MEM_calloc_arrayN(this->curve_size + 1, sizeof(int), __func__); + + this->update_customdata_pointers(); + + this->runtime = MEM_new(__func__); +} + +static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) +{ + dst.point_size = src.point_size; + dst.curve_size = src.curve_size; + CustomData_copy(&src.point_data, &dst.point_data, CD_MASK_ALL, CD_DUPLICATE, dst.point_size); + CustomData_copy(&src.curve_data, &dst.curve_data, CD_MASK_ALL, CD_DUPLICATE, dst.curve_size); + + MEM_SAFE_FREE(dst.curve_offsets); + dst.curve_offsets = (int *)MEM_calloc_arrayN(dst.point_size + 1, sizeof(int), __func__); + dst.offsets().copy_from(src.offsets()); + + dst.tag_topology_changed(); + + dst.update_customdata_pointers(); +} + +CurvesGeometry::CurvesGeometry(const CurvesGeometry &other) + : CurvesGeometry(other.point_size, other.curve_size) +{ + copy_curves_geometry(*this, other); +} + +CurvesGeometry &CurvesGeometry::operator=(const CurvesGeometry &other) +{ + if (this != &other) { + copy_curves_geometry(*this, other); + } + return *this; +} + +CurvesGeometry::~CurvesGeometry() +{ + CustomData_free(&this->point_data, this->point_size); + CustomData_free(&this->curve_data, this->curve_size); + MEM_SAFE_FREE(this->curve_offsets); + MEM_delete(this->runtime); + this->runtime = nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Accessors + * \{ */ + +int CurvesGeometry::points_size() const +{ + return this->point_size; +} +int CurvesGeometry::curves_size() const +{ + return this->curve_size; +} +int CurvesGeometry::evaluated_points_size() const +{ + /* TODO: Implement when there are evaluated points. */ + return 0; +} + +IndexRange CurvesGeometry::range_for_curve(const int index) const +{ + const int offset = this->curve_offsets[index]; + const int offset_next = this->curve_offsets[index + 1]; + return {offset, offset_next - offset}; +} + +VArray CurvesGeometry::curve_types() const +{ + if (const int8_t *data = (const int8_t *)CustomData_get_layer_named( + &this->curve_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str())) { + return VArray::ForSpan({data, this->curve_size}); + } + return VArray::ForSingle(CURVE_TYPE_CATMULL_ROM, this->curve_size); +} + +MutableSpan CurvesGeometry::curve_types() +{ + int8_t *data = (int8_t *)CustomData_add_layer_named(&this->curve_data, + CD_PROP_INT8, + CD_CALLOC, + nullptr, + this->curve_size, + ATTR_CURVE_TYPE.c_str()); + BLI_assert(data != nullptr); + return {data, this->curve_size}; +} + +MutableSpan CurvesGeometry::positions() +{ + CustomData_duplicate_referenced_layer(&this->point_data, CD_PROP_FLOAT3, this->point_size); + this->update_customdata_pointers(); + return {(float3 *)this->position, this->point_size}; +} +Span CurvesGeometry::positions() const +{ + return {(const float3 *)this->position, this->point_size}; +} + +MutableSpan CurvesGeometry::offsets() +{ + return {this->curve_offsets, this->curve_size + 1}; +} +Span CurvesGeometry::offsets() const +{ + return {this->curve_offsets, this->curve_size + 1}; +} + +void CurvesGeometry::resize(const int point_size, const int curve_size) +{ + if (point_size != this->point_size) { + CustomData_realloc(&this->point_data, point_size); + this->point_size = point_size; + } + if (curve_size != this->curve_size) { + CustomData_realloc(&this->curve_data, curve_size); + this->curve_size = curve_size; + this->curve_offsets = (int *)MEM_reallocN(this->curve_offsets, sizeof(int) * (curve_size + 1)); + } + this->tag_topology_changed(); + this->update_customdata_pointers(); +} + +void CurvesGeometry::tag_positions_changed() +{ + this->runtime->position_cache_dirty = true; + this->runtime->tangent_cache_dirty = true; + this->runtime->normal_cache_dirty = true; +} +void CurvesGeometry::tag_topology_changed() +{ + this->runtime->position_cache_dirty = true; + this->runtime->tangent_cache_dirty = true; + this->runtime->normal_cache_dirty = true; +} +void CurvesGeometry::tag_normals_changed() +{ + this->runtime->normal_cache_dirty = true; +} + +void CurvesGeometry::translate(const float3 &translation) +{ + MutableSpan positions = this->positions(); + threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) { + for (float3 &position : positions.slice(range)) { + position += translation; + } + }); +} + +void CurvesGeometry::transform(const float4x4 &matrix) +{ + MutableSpan positions = this->positions(); + threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) { + for (float3 &position : positions.slice(range)) { + position = matrix * position; + } + }); +} + +static std::optional> curves_bounds(const CurvesGeometry &curves) +{ + Span positions = curves.positions(); + if (curves.radius) { + Span radii{curves.radius, curves.points_size()}; + return bounds::min_max_with_radii(positions, radii); + } + return bounds::min_max(positions); +} + +bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const +{ + const std::optional> bounds = curves_bounds(*this); + if (!bounds) { + return false; + } + min = math::min(bounds->min, min); + max = math::max(bounds->max, max); + return true; +} + +void CurvesGeometry::update_customdata_pointers() +{ + this->position = (float(*)[3])CustomData_get_layer_named( + &this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str()); + this->radius = (float *)CustomData_get_layer_named( + &this->point_data, CD_PROP_FLOAT, ATTR_RADIUS.c_str()); + this->curve_type = (int8_t *)CustomData_get_layer_named( + &this->point_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Domain Interpolation + * \{ */ + +/** + * Mix together all of a curve's control point values. + * + * \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 +static void adapt_curve_domain_point_to_curve_impl(const CurvesGeometry &curves, + const VArray &old_values, + MutableSpan r_values) +{ + attribute_math::DefaultMixer mixer(r_values); + for (const int i_curve : IndexRange(curves.curves_size())) { + for (const int i_point : curves.range_for_curve(i_curve)) { + mixer.mix_in(i_curve, old_values[i_point]); + } + } + mixer.finalize(); +} + +/** + * A curve 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_curve_impl(const CurvesGeometry &curves, + const VArray &old_values, + MutableSpan r_values) +{ + r_values.fill(true); + for (const int i_curve : IndexRange(curves.curves_size())) { + for (const int i_point : curves.range_for_curve(i_curve)) { + if (!old_values[i_point]) { + r_values[i_curve] = false; + break; + } + } + } +} + +static GVArray adapt_curve_domain_point_to_curve(const CurvesGeometry &curves, + const GVArray &varray) +{ + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v>) { + Array values(curves.curves_size()); + adapt_curve_domain_point_to_curve_impl(curves, varray.typed(), values); + new_varray = VArray::ForContainer(std::move(values)); + } + }); + return new_varray; +} + +/** + * Copy the value from a curve to all of its points. + * + * \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 +static void adapt_curve_domain_curve_to_point_impl(const CurvesGeometry &curves, + const VArray &old_values, + MutableSpan r_values) +{ + for (const int i_curve : IndexRange(curves.curves_size())) { + r_values.slice(curves.range_for_curve(i_curve)).fill(old_values[i_curve]); + } +} + +static GVArray adapt_curve_domain_curve_to_point(const CurvesGeometry &curves, + const GVArray &varray) +{ + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { + using T = decltype(dummy); + Array values(curves.points_size()); + adapt_curve_domain_curve_to_point_impl(curves, varray.typed(), values); + new_varray = VArray::ForContainer(std::move(values)); + }); + return new_varray; +} + +fn::GVArray CurvesGeometry::adapt_domain(const fn::GVArray &varray, + const AttributeDomain from, + const AttributeDomain to) const +{ + if (!varray) { + return {}; + } + if (varray.is_empty()) { + return {}; + } + if (from == to) { + return varray; + } + + if (from == ATTR_DOMAIN_POINT && to == ATTR_DOMAIN_CURVE) { + return adapt_curve_domain_point_to_curve(*this, varray); + } + if (from == ATTR_DOMAIN_CURVE && to == ATTR_DOMAIN_POINT) { + return adapt_curve_domain_curve_to_point(*this, varray); + } + + BLI_assert_unreachable(); + return {}; +} + +/** \} */ + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 3a8d7d6bc8a..a381fe2c2ff 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -73,7 +73,7 @@ #include "BKE_constraint.h" #include "BKE_crazyspace.h" #include "BKE_curve.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_duplilist.h" diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index a779c694cd2..2153b674463 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -22,7 +22,7 @@ #include "DNA_curves_types.h" #include "DNA_object_types.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "GPU_batch.h" #include "GPU_material.h" @@ -133,7 +133,7 @@ static void curves_batch_cache_fill_segments_proc_pos(Curves *curves, { /* TODO: use hair radius layer if available. */ const int curve_size = curves->geometry.curve_size; - Span offsets{curves->geometry.offsets, curves->geometry.curve_size + 1}; + Span offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; Span positions{(float3 *)curves->geometry.position, curves->geometry.point_size}; @@ -216,7 +216,7 @@ static void curves_batch_cache_fill_strands_data(Curves *curves, GPUVertBufRaw *seg_step) { const int curve_size = curves->geometry.curve_size; - Span offsets{curves->geometry.offsets, curves->geometry.curve_size + 1}; + Span offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; for (const int i : IndexRange(curve_size)) { const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h index 03a587c896b..c45de832e3c 100644 --- a/source/blender/makesdna/DNA_curves_types.h +++ b/source/blender/makesdna/DNA_curves_types.h @@ -13,6 +13,33 @@ extern "C" { #endif +#ifdef __cplusplus +namespace blender::bke { +class CurvesGeometryRuntime; +} // namespace blender::bke +using CurvesGeometryRuntimeHandle = blender::bke::CurvesGeometryRuntime; +#else +typedef struct CurvesGeometryRuntimeHandle CurvesGeometryRuntimeHandle; +#endif + +typedef enum CurveType { + CURVE_TYPE_CATMULL_ROM = 0, + CURVE_TYPE_POLY = 1, + CURVE_TYPE_BEZIER = 2, + CURVE_TYPE_NURBS = 3, +} CurveType; + +typedef enum HandleType { + /** The handle can be moved anywhere, and doesn't influence the point's other handle. */ + BEZIER_HANDLE_FREE = 0, + /** The location is automatically calculated to be smooth. */ + BEZIER_HANDLE_AUTO = 1, + /** The location is calculated to point to the next/previous control point. */ + BEZIER_HANDLE_VECTOR = 2, + /** The location is constrained to point in the opposite direction as the other handle. */ + BEZIER_HANDLE_ALIGN = 3, +} HandleType; + /** * A reusable data structure for geometry consisting of many curves. All control point data is * stored contiguously for better efficiency. Data for each curve is stored as a slice of the @@ -33,6 +60,12 @@ typedef struct CurvesGeometry { */ float *radius; + /** + * The type of each curve. #CurveType. + * \note This data is owned by #curve_data. + */ + int8_t *curve_type; + /** * The start index of each curve in the point data. The size of each curve can be calculated by * subtracting the offset from the next offset. That is valid even for the last curve because @@ -40,7 +73,7 @@ typedef struct CurvesGeometry { * * \note This is *not* stored in #CustomData because its size is one larger than #curve_data. */ - int *offsets; + int *curve_offsets; /** * All attributes stored on control points (#ATTR_DOMAIN_POINT). @@ -60,6 +93,11 @@ typedef struct CurvesGeometry { * The number of curves in the data-block. */ int curve_size; + + /** + * Runtime data for curves, stored as a pointer to allow defining this as a C++ class. + */ + CurvesGeometryRuntimeHandle *runtime; } CurvesGeometry; typedef struct Curves { diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c index 8e7fb415c7d..1552a7ddbfb 100644 --- a/source/blender/makesrna/intern/rna_curves.c +++ b/source/blender/makesrna/intern/rna_curves.c @@ -43,7 +43,7 @@ static void rna_Curves_curve_offset_data_begin(CollectionPropertyIterator *iter, { const Curves *curves = rna_curves(ptr); rna_iterator_array_begin(iter, - (void *)curves->geometry.offsets, + (void *)curves->geometry.curve_offsets, sizeof(int), curves->geometry.curve_size + 1, false, @@ -95,7 +95,7 @@ static char *rna_CurvePoint_path(PointerRNA *ptr) static int rna_CurveSlice_index_get(PointerRNA *ptr) { Curves *curves = rna_curves(ptr); - return (int)((int *)ptr->data - curves->geometry.offsets); + return (int)((int *)ptr->data - curves->geometry.curve_offsets); } static char *rna_CurveSlice_path(PointerRNA *ptr) @@ -220,7 +220,7 @@ static void rna_def_curves(BlenderRNA *brna) /* Point and Curve RNA API helpers. */ prop = RNA_def_property(srna, "curves", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.offsets", "geometry.curve_size"); + RNA_def_property_collection_sdna(prop, NULL, "geometry.curve_offsets", "geometry.curve_size"); RNA_def_property_struct_type(prop, "CurveSlice"); RNA_def_property_ui_text(prop, "Curves", "All curves in the data-block"); @@ -243,7 +243,7 @@ static void rna_def_curves(BlenderRNA *brna) RNA_define_verify_sdna(1); prop = RNA_def_property(srna, "curve_offset_data", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.offsets", NULL); + RNA_def_property_collection_sdna(prop, NULL, "geometry.curve_offsets", NULL); RNA_def_property_struct_type(prop, "IntAttributeValue"); RNA_def_property_collection_funcs(prop, "rna_Curves_curve_offset_data_begin", -- cgit v1.2.3 From 0622d2ec611dbd68fe4105f507f639e3e34c13f6 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 16 Feb 2022 18:58:01 +0100 Subject: Fix T95815: missing null check when computing dupli dimensions Some instances might be "empty" and therefore have no dimensions. Those should be ignored here. --- source/blender/blenkernel/intern/object.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index f9484205ef6..6dc6e37de27 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -4185,7 +4185,7 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph, ListBase *lb = object_duplilist(depsgraph, scene, ob); LISTBASE_FOREACH (DupliObject *, dob, lb) { - if ((use_hidden == false) && (dob->no_draw != 0)) { + if (((use_hidden == false) && (dob->no_draw != 0)) || dob->ob_data == nullptr) { /* pass */ } else { -- cgit v1.2.3 From 05697470ab0dde6646be939ae57a95c9d4099e0b Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Wed, 16 Feb 2022 19:49:58 +0100 Subject: Cleanup: Remove deprecated StringGrid from our openvdb code StringGrid has been deprecated in openvdb 9.0.0 and will be removed soon Reviewed By: Brecht Differential Revision: http://developer.blender.org/D14133 --- source/blender/blenkernel/BKE_volume.h | 3 --- source/blender/blenkernel/intern/volume.cc | 13 ++----------- source/blender/blenkernel/intern/volume_render.cc | 1 - source/blender/makesrna/intern/rna_volume.c | 1 - source/blender/modifiers/intern/MOD_volume_displace.cc | 6 ++---- 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/source/blender/blenkernel/BKE_volume.h b/source/blender/blenkernel/BKE_volume.h index 8b42de7303d..068cdf87d16 100644 --- a/source/blender/blenkernel/BKE_volume.h +++ b/source/blender/blenkernel/BKE_volume.h @@ -104,7 +104,6 @@ typedef enum VolumeGridType { VOLUME_GRID_INT, VOLUME_GRID_INT64, VOLUME_GRID_MASK, - VOLUME_GRID_STRING, VOLUME_GRID_VECTOR_FLOAT, VOLUME_GRID_VECTOR_DOUBLE, VOLUME_GRID_VECTOR_INT, @@ -218,8 +217,6 @@ auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op) return op.template operator()(); case VOLUME_GRID_VECTOR_DOUBLE: return op.template operator()(); - case VOLUME_GRID_STRING: - return op.template operator()(); case VOLUME_GRID_MASK: return op.template operator()(); case VOLUME_GRID_POINTS: diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 39a7725bfa3..a0e2d1a83cd 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -1345,9 +1345,6 @@ VolumeGridType BKE_volume_grid_type_openvdb(const openvdb::GridBase &grid) if (grid.isType()) { return VOLUME_GRID_VECTOR_DOUBLE; } - if (grid.isType()) { - return VOLUME_GRID_STRING; - } if (grid.isType()) { return VOLUME_GRID_MASK; } @@ -1383,7 +1380,6 @@ int BKE_volume_grid_channels(const VolumeGrid *grid) case VOLUME_GRID_VECTOR_DOUBLE: case VOLUME_GRID_VECTOR_INT: return 3; - case VOLUME_GRID_STRING: case VOLUME_GRID_POINTS: case VOLUME_GRID_UNKNOWN: return 0; @@ -1624,13 +1620,8 @@ struct CreateGridWithChangedResolutionOp { template typename openvdb::GridBase::Ptr operator()() { - if constexpr (std::is_same_v) { - return {}; - } - else { - return create_grid_with_changed_resolution(static_cast(grid), - resolution_factor); - } + return create_grid_with_changed_resolution(static_cast(grid), + resolution_factor); } }; diff --git a/source/blender/blenkernel/intern/volume_render.cc b/source/blender/blenkernel/intern/volume_render.cc index c0a205b5673..7ccf04df941 100644 --- a/source/blender/blenkernel/intern/volume_render.cc +++ b/source/blender/blenkernel/intern/volume_render.cc @@ -77,7 +77,6 @@ static void extract_dense_float_voxels(const VolumeGridType grid_type, case VOLUME_GRID_VECTOR_INT: return extract_dense_voxels( grid, bbox, reinterpret_cast(r_voxels)); - case VOLUME_GRID_STRING: case VOLUME_GRID_POINTS: case VOLUME_GRID_UNKNOWN: /* Zero channels to copy. */ diff --git a/source/blender/makesrna/intern/rna_volume.c b/source/blender/makesrna/intern/rna_volume.c index 3100c1195f4..2854f02a4d4 100644 --- a/source/blender/makesrna/intern/rna_volume.c +++ b/source/blender/makesrna/intern/rna_volume.c @@ -40,7 +40,6 @@ const EnumPropertyItem rna_enum_volume_grid_data_type_items[] = { {VOLUME_GRID_INT, "INT", 0, "Integer", "32-bit integer"}, {VOLUME_GRID_INT64, "INT64", 0, "Integer 64-bit", "64-bit integer"}, {VOLUME_GRID_MASK, "MASK", 0, "Mask", "No data, boolean mask of active voxels"}, - {VOLUME_GRID_STRING, "STRING", 0, "String", "Text string"}, {VOLUME_GRID_VECTOR_FLOAT, "VECTOR_FLOAT", 0, "Float Vector", "3D float vector"}, {VOLUME_GRID_VECTOR_DOUBLE, "VECTOR_DOUBLE", 0, "Double Vector", "3D double vector"}, {VOLUME_GRID_VECTOR_INT, "VECTOR_INT", 0, "Integer Vector", "3D integer vector"}, diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index af3a502162d..e2d7f445731 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -203,10 +203,8 @@ struct DisplaceGridOp { template void operator()() { - if constexpr (blender::is_same_any_v) { + if constexpr (blender:: + is_same_any_v) { /* We don't support displacing these grid types yet. */ return; } -- cgit v1.2.3 From 71545e542c5b14351a0addf713234cf87a39a5c3 Mon Sep 17 00:00:00 2001 From: Ankit Meel Date: Thu, 17 Feb 2022 01:28:10 +0530 Subject: macOS/bpy module: install scripts relative to bpy.so Brew's Python framework's site-packages is a symlink so the assumption that Resources and site-packages would be in the same directory doesn't hold. So install scripts etc relative to bpy.so. Part of D14111 --- source/creator/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index e6e122508a9..6a1d5b78611 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -319,11 +319,16 @@ elseif(WIN32) elseif(APPLE) if(WITH_PYTHON_MODULE) if(WITH_INSTALL_PORTABLE) + set(BPY_INSTALL_DIR) set(TARGETDIR_VER $/../Resources/${BLENDER_VERSION}) # Keep the `BLENDER_VERSION` folder and bpy.so in the build folder. set(INSTALL_BPY_TO_SITE_PACKAGES OFF) else() - set(TARGETDIR_VER "${PYTHON_LIBPATH}/Resources/${BLENDER_VERSION}") + # Parent directory of bpy.so for installation. + set(BPY_INSTALL_DIR ${PYTHON_LIBPATH}/site-packages) + # Defined in terms of site-packages since the site-packages + # directory can be a symlink (brew for example). + set(TARGETDIR_VER "${BPY_INSTALL_DIR}/../Resources/${BLENDER_VERSION}") set(INSTALL_BPY_TO_SITE_PACKAGES ON) endif() else() @@ -1108,11 +1113,12 @@ elseif(APPLE) ) unset(_py_inc_suffix) endif() + if(WITH_PYTHON_MODULE) if(INSTALL_BPY_TO_SITE_PACKAGES) install( TARGETS blender - LIBRARY DESTINATION ${PYTHON_LIBPATH}/site-packages + LIBRARY DESTINATION ${BPY_INSTALL_DIR} ) endif() endif() -- cgit v1.2.3 From 6862caea5e878ab5de22513fb9ca13e17add8919 Mon Sep 17 00:00:00 2001 From: Ankit Meel Date: Thu, 17 Feb 2022 01:30:51 +0530 Subject: macOS/bpy module: install text files correctly Instead of Blender.app (despite building bpy), install license etc in `Resources/text` of bpy install location. Part of D14111 --- source/creator/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 6a1d5b78611..d17afad0918 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -334,6 +334,8 @@ elseif(APPLE) else() set(TARGETDIR_VER Blender.app/Contents/Resources/${BLENDER_VERSION}) endif() + # License, copyright, readme files. + set(BLENDER_TEXT_FILES_DESTINATION "${TARGETDIR_VER}/../text") set(MAC_BLENDER_TARGET_DYLIBS_DIR "${TARGETDIR_VER}/lib") # Skip relinking on cpack / install set_target_properties(blender PROPERTIES BUILD_WITH_INSTALL_RPATH true) @@ -1057,9 +1059,6 @@ elseif(APPLE) DESTINATION "." ) - # install release and app files - set(BLENDER_TEXT_FILES_DESTINATION Blender.app/Contents/Resources/text) - install( FILES ${OSX_APP_SOURCEDIR}/Contents/PkgInfo DESTINATION Blender.app/Contents -- cgit v1.2.3 From 8c96ee8903e0840816e340fcf29fe0606e19821b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 16 Feb 2022 14:10:21 -0600 Subject: Cleanup: Deduplicate functions for creating attributes --- source/blender/blenkernel/intern/attribute_access_intern.hh | 10 ++++++++++ .../blenkernel/intern/geometry_component_instances.cc | 12 ------------ source/blender/blenkernel/intern/geometry_component_mesh.cc | 12 ------------ .../blenkernel/intern/geometry_component_pointcloud.cc | 12 ------------ 4 files changed, 10 insertions(+), 36 deletions(-) diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 4eff878778a..bfc4c8fcde0 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -207,6 +207,16 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { void foreach_domain(const FunctionRef callback) const final; }; +template GVArray make_array_read_attribute(const void *data, const int domain_size) +{ + return VArray::ForSpan(Span((const T *)data, domain_size)); +} + +template GVMutableArray make_array_write_attribute(void *data, const int domain_size) +{ + return VMutableArray::ForSpan(MutableSpan((T *)data, domain_size)); +} + /** * This provider is used to provide access to builtin attributes. It supports making internal types * available as different types. For example, the vertex position attribute is stored as part of diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index b83a8b1ee94..0cb2b0e812b 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -439,18 +439,6 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider } }; -template -static GVArray make_array_read_attribute(const void *data, const int domain_size) -{ - return VArray::ForSpan(Span((const T *)data, domain_size)); -} - -template -static GVMutableArray make_array_write_attribute(void *data, const int domain_size) -{ - return VMutableArray::ForSpan(MutableSpan((T *)data, domain_size)); -} - static ComponentAttributeProviders create_attribute_providers_for_instances() { static InstancePositionAttributeProvider position; diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 2f8ff944420..104166df913 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -854,18 +854,6 @@ static GVMutableArray make_derived_write_attribute(void *data, const int domain_ MutableSpan((StructT *)data, domain_size)); } -template -static GVArray make_array_read_attribute(const void *data, const int domain_size) -{ - return VArray::ForSpan(Span((const T *)data, domain_size)); -} - -template -static GVMutableArray make_array_write_attribute(void *data, const int domain_size) -{ - return VMutableArray::ForSpan(MutableSpan((T *)data, domain_size)); -} - static float3 get_vertex_position(const MVert &vert) { return float3(vert.co); diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index f6f3c4e1b4e..3db4db307a3 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -117,18 +117,6 @@ int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) con namespace blender::bke { -template -static GVArray make_array_read_attribute(const void *data, const int domain_size) -{ - return VArray::ForSpan(Span((const T *)data, domain_size)); -} - -template -static GVMutableArray make_array_write_attribute(void *data, const int domain_size) -{ - return VMutableArray::ForSpan(MutableSpan((T *)data, domain_size)); -} - /** * In this function all the attribute providers for a point cloud component are created. Most data * in this function is statically allocated, because it does not change over time. -- cgit v1.2.3 From b626edd751d64b269da12ef8e774ced2836c3c9e Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Wed, 16 Feb 2022 21:55:42 +0100 Subject: fix: RNA property not set for Graph editor breakdown op MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After running the breakdown operator for the graph editor, the factor property in the redo panel didn't reflect the value you chose to mitigate that issue down the line there is a new helper function to get the factor value, and store it at the same time Reviewed by: Sybren A. Stüvel Differential Revision: https://developer.blender.org/D14105 Ref: D14105 --- release/scripts/addons | 2 +- .../blender/editors/space_graph/graph_slider_ops.c | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/release/scripts/addons b/release/scripts/addons index e1d44bf3750..089aef61deb 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit e1d44bf37501eb19a057777bd0b0ba4484773531 +Subproject commit 089aef61debbece2baff6516e33fc7491629b1d0 diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index 1006ba4b3f4..2aebc6f2eeb 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -163,6 +163,16 @@ static void reset_bezts(tGraphSliderOp *gso) ANIM_animdata_freelist(&anim_data); } +/* Get factor value and store it in RNA property. Custom data of wmOperator needs to contain + * tGraphSliderOp. */ +const float slider_factor_get_and_remember(wmOperator *op) +{ + tGraphSliderOp *gso = op->customdata; + const float factor = ED_slider_factor_get(gso->slider); + RNA_property_float_set(op->ptr, gso->factor_prop, factor); + return factor; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -378,8 +388,7 @@ static void decimate_modal_update(bContext *C, wmOperator *op) reset_bezts(gso); /* Apply... */ - float factor = ED_slider_factor_get(gso->slider); - RNA_property_float_set(op->ptr, gso->factor_prop, factor); + const float factor = slider_factor_get_and_remember(op); /* We don't want to limit the decimation to a certain error margin. */ const float error_sq_max = FLT_MAX; decimate_graph_keys(&gso->ac, factor, error_sq_max); @@ -598,8 +607,7 @@ static void blend_to_neighbor_modal_update(bContext *C, wmOperator *op) /* Reset keyframe data to the state at invoke. */ reset_bezts(gso); - const float factor = ED_slider_factor_get(gso->slider); - RNA_property_float_set(op->ptr, gso->factor_prop, factor); + const float factor = slider_factor_get_and_remember(op); blend_to_neighbor_graph_keys(&gso->ac, factor); WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); @@ -725,7 +733,8 @@ static void breakdown_modal_update(bContext *C, wmOperator *op) /* Reset keyframe data to the state at invoke. */ reset_bezts(gso); - breakdown_graph_keys(&gso->ac, ED_slider_factor_get(gso->slider)); + const float factor = slider_factor_get_and_remember(op); + breakdown_graph_keys(&gso->ac, factor); WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); } @@ -739,6 +748,7 @@ static int breakdown_invoke(bContext *C, wmOperator *op, const wmEvent *event) tGraphSliderOp *gso = op->customdata; gso->modal_update = breakdown_modal_update; + gso->factor_prop = RNA_struct_find_property(op->ptr, "factor"); breakdown_draw_status_header(C, gso); return invoke_result; -- cgit v1.2.3 From bdde8c72071fe6df52111d9b7625a087a70fb993 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 17 Feb 2022 16:47:37 +1100 Subject: License headers: use SPDX identifiers --- source/blender/blenkernel/intern/curves_geometry.cc | 16 +--------------- source/blender/blenlib/BLI_bounds.hh | 16 +--------------- source/blender/blenlib/tests/BLI_bounds_test.cc | 2 +- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index dcd04a408fa..38cf3d351bd 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -1,18 +1,4 @@ -/* - * 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. - */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh index ce440bef704..d20382ed500 100644 --- a/source/blender/blenlib/BLI_bounds.hh +++ b/source/blender/blenlib/BLI_bounds.hh @@ -1,18 +1,4 @@ -/* - * 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. - */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once diff --git a/source/blender/blenlib/tests/BLI_bounds_test.cc b/source/blender/blenlib/tests/BLI_bounds_test.cc index fc3affd97de..9c123d4705c 100644 --- a/source/blender/blenlib/tests/BLI_bounds_test.cc +++ b/source/blender/blenlib/tests/BLI_bounds_test.cc @@ -1,4 +1,4 @@ -/* Apache License, Version 2.0 */ +/* SPDX-License-Identifier: Apache-2.0 */ #include "testing/testing.h" -- cgit v1.2.3 From 8c8d84fbc5681fd4dd3250a2df7422f7268263ce Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 17 Feb 2022 16:50:44 +1100 Subject: Cleanup: compiler warning, typo in comment --- source/blender/blenkernel/BKE_curves.hh | 2 +- source/blender/editors/space_graph/graph_slider_ops.c | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 6bcbb0f6e66..0f0b77902cc 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -50,7 +50,7 @@ class CurvesGeometryRuntime { /** * A C++ class that wraps the DNA struct for better encapsulation and ease of use. It inherits - * directly from the struct rather than storing a pointer to avoid more complcated ownership + * directly from the struct rather than storing a pointer to avoid more complicated ownership * handling. */ class CurvesGeometry : public ::CurvesGeometry { diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index 2aebc6f2eeb..1a3355b0139 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -163,9 +163,11 @@ static void reset_bezts(tGraphSliderOp *gso) ANIM_animdata_freelist(&anim_data); } -/* Get factor value and store it in RNA property. Custom data of wmOperator needs to contain - * tGraphSliderOp. */ -const float slider_factor_get_and_remember(wmOperator *op) +/** + * Get factor value and store it in RNA property. + * Custom data of #wmOperator needs to contain #tGraphSliderOp. + */ +static float slider_factor_get_and_remember(wmOperator *op) { tGraphSliderOp *gso = op->customdata; const float factor = ED_slider_factor_get(gso->slider); -- cgit v1.2.3 From dd6fd06c15b7edfa1e277a7af8b6bf08e275d8a1 Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Thu, 17 Feb 2022 15:27:24 +0900 Subject: Fix T76082: VR Scene Inspection: It shows me only a pink screen This fixes VR pink screen issues when using the DirectX backend, caused by `wglDXRegisterObjectNV()` failing to register the shared OpenGL-DirectX render buffer. The issue is mainly present on AMD graphics, however, there have been reports on NVIDIA as well. A limited workaround for the SteamVR runtime (AMD only) was provided in rB82ab2c167844, however this patch provides a more complete solution that should apply to all OpenXR runtimes. For example, with this patch, the Windows Mixed Reality runtime that exclusively uses DirectX can now be used with AMD graphics cards. Implementation-wise, a `GL_TEXTURE_2D` render target is used as a fallback for the shared OpenGL-DirectX resource in the case that registering a render buffer (`GL_RENDERBUFFER`) fails. While using a texture render target may be less optimal than a render buffer, it enables proper display in VR using the OpenGL/DirectX interop (tested on AMD Vega 64). Reviewed By: Severin Differential Revision: https://developer.blender.org/D14100 --- intern/ghost/intern/GHOST_ContextD3D.cpp | 95 ++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/intern/ghost/intern/GHOST_ContextD3D.cpp b/intern/ghost/intern/GHOST_ContextD3D.cpp index 73f6c12e100..11326f14d7d 100644 --- a/intern/ghost/intern/GHOST_ContextD3D.cpp +++ b/intern/ghost/intern/GHOST_ContextD3D.cpp @@ -124,9 +124,11 @@ class GHOST_SharedOpenGLResource { struct SharedData { HANDLE device; GLuint fbo; - HANDLE render_buf{nullptr}; + HANDLE render_target{nullptr}; } m_shared; + enum RenderTarget { TARGET_RENDERBUF, TARGET_TEX2D }; + public: GHOST_SharedOpenGLResource(ID3D11Device *device, ID3D11DeviceContext *device_ctx, @@ -193,37 +195,64 @@ class GHOST_SharedOpenGLResource { } if (m_is_initialized) { - if (m_shared.render_buf) { - wglDXUnregisterObjectNV(m_shared.device, m_shared.render_buf); + if (m_shared.render_target +#if 1 + /* TODO: #wglDXUnregisterObjectNV() causes an access violation on AMD when the shared + * resource is a GL texture. Since there is currently no good alternative, just skip + * unregistering the shared resource. */ + && !m_use_gl_texture2d +#endif + ) { + wglDXUnregisterObjectNV(m_shared.device, m_shared.render_target); } if (m_shared.device) { wglDXCloseDeviceNV(m_shared.device); } glDeleteFramebuffers(1, &m_shared.fbo); - glDeleteRenderbuffers(1, &m_gl_render_buf); + if (m_use_gl_texture2d) { + glDeleteTextures(1, &m_gl_render_target); + } + else { + glDeleteRenderbuffers(1, &m_gl_render_target); + } } } - void reregisterSharedObject() + /* Returns true if the shared object was successfully registered, false otherwise. */ + bool reregisterSharedObject(RenderTarget target) { - if (m_shared.render_buf) { - wglDXUnregisterObjectNV(m_shared.device, m_shared.render_buf); + if (m_shared.render_target) { + wglDXUnregisterObjectNV(m_shared.device, m_shared.render_target); } if (!m_render_target_tex) { - return; + return false; } - m_shared.render_buf = wglDXRegisterObjectNV(m_shared.device, - m_render_target_tex, - m_gl_render_buf, - GL_RENDERBUFFER, - WGL_ACCESS_READ_WRITE_NV); + if (target == TARGET_TEX2D) { + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA8, + m_cur_width, + m_cur_height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + nullptr); + } - if (!m_shared.render_buf) { + m_shared.render_target = wglDXRegisterObjectNV(m_shared.device, + m_render_target_tex, + m_gl_render_target, + (target == TARGET_TEX2D) ? GL_TEXTURE_2D : + GL_RENDERBUFFER, + WGL_ACCESS_READ_WRITE_NV); + if (!m_shared.render_target) { fprintf(stderr, "Error registering shared object using wglDXRegisterObjectNV()\n"); - return; + return false; } + + return true; } GHOST_TSuccess initialize() @@ -235,16 +264,33 @@ class GHOST_SharedOpenGLResource { } /* Build the renderbuffer. */ - glGenRenderbuffers(1, &m_gl_render_buf); - glBindRenderbuffer(GL_RENDERBUFFER, m_gl_render_buf); + glGenRenderbuffers(1, &m_gl_render_target); + glBindRenderbuffer(GL_RENDERBUFFER, m_gl_render_target); + + if (!reregisterSharedObject(TARGET_RENDERBUF)) { + glBindRenderbuffer(GL_RENDERBUFFER, 0); + if (m_gl_render_target) { + glDeleteRenderbuffers(1, &m_gl_render_target); + } + /* Fall back to texture 2d. */ + m_use_gl_texture2d = true; + glGenTextures(1, &m_gl_render_target); + glBindTexture(GL_TEXTURE_2D, m_gl_render_target); - reregisterSharedObject(); + reregisterSharedObject(TARGET_TEX2D); + } /* Build the framebuffer */ glGenFramebuffers(1, &m_shared.fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_shared.fbo); - glFramebufferRenderbuffer( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_gl_render_buf); + if (m_use_gl_texture2d) { + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_gl_render_target, 0); + } + else { + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_gl_render_target); + } m_is_initialized = true; return GHOST_kSuccess; @@ -259,7 +305,7 @@ class GHOST_SharedOpenGLResource { if ((m_cur_width != width) || (m_cur_height != height)) { m_cur_width = width; m_cur_height = height; - reregisterSharedObject(); + reregisterSharedObject(m_use_gl_texture2d ? TARGET_TEX2D : TARGET_RENDERBUF); } } @@ -307,18 +353,19 @@ class GHOST_SharedOpenGLResource { private: void beginGLOnly() { - wglDXLockObjectsNV(m_shared.device, 1, &m_shared.render_buf); + wglDXLockObjectsNV(m_shared.device, 1, &m_shared.render_target); } void endGLOnly() { - wglDXUnlockObjectsNV(m_shared.device, 1, &m_shared.render_buf); + wglDXUnlockObjectsNV(m_shared.device, 1, &m_shared.render_target); } ID3D11Device *m_device; ID3D11DeviceContext *m_device_ctx; - GLuint m_gl_render_buf; + GLuint m_gl_render_target; unsigned int m_cur_width, m_cur_height; bool m_is_initialized{false}; + bool m_use_gl_texture2d{false}; }; GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource( -- cgit v1.2.3 From 6a8709ba136ef4e8522555f279294a886595e34c Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Thu, 17 Feb 2022 15:41:53 +0900 Subject: XR: Allow variable count of action map subactions Previously, the number of action map subactions was limited to two per action (identified by user_path0, user_path1), however for devices with more than two user paths (e.g. Vive Tracker) it will be useful to support a variable amount instead. For example, a single pose action could then be used to query the positions of all connected trackers, with each tracker having its own subaction tracking space. NOTE: This introduces breaking changes for the XR Python API as follows: - XrActionMapItem: The new `user_paths` collection property replaces the `user_path0`/`user_path1` properties. - XrActionMapBinding: The new `component_paths` collection property replaces the `component_path0`/`component_path1` properties. Reviewed By: Severin Differential Revision: https://developer.blender.org/D13949 --- source/blender/makesdna/DNA_xr_types.h | 26 +- source/blender/makesrna/intern/rna_xr.c | 402 +++++++++++++++++---- source/blender/windowmanager/WM_api.h | 8 +- .../blender/windowmanager/xr/intern/wm_xr_action.c | 74 ++-- .../windowmanager/xr/intern/wm_xr_actionmap.c | 35 +- .../blender/windowmanager/xr/intern/wm_xr_intern.h | 8 +- .../windowmanager/xr/intern/wm_xr_session.c | 5 +- 7 files changed, 422 insertions(+), 136 deletions(-) diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index bf77339a494..09eab0d7bf7 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -106,8 +106,23 @@ typedef enum eXrPoseFlag { XR_POSE_AIM = (1 << 1), } eXrPoseFlag; +/** + * The following user and component path lengths are dependent on OpenXR's XR_MAX_PATH_LENGTH + * (256). A user path will be combined with a component path to identify an action binding, and + * that combined path should also have a max of XR_MAX_PATH_LENGTH (e.g. user_path = + * /user/hand/left, component_path = /input/trigger/value, full_path = + * /user/hand/left/input/trigger/value). + */ +#define XR_MAX_USER_PATH_LENGTH 64 +#define XR_MAX_COMPONENT_PATH_LENGTH 192 + /* -------------------------------------------------------------------- */ +typedef struct XrComponentPath { + struct XrComponentPath *next, *prev; + char path[192]; /* XR_MAX_COMPONENT_PATH_LENGTH */ +} XrComponentPath; + typedef struct XrActionMapBinding { struct XrActionMapBinding *next, *prev; @@ -117,8 +132,7 @@ typedef struct XrActionMapBinding { /** OpenXR interaction profile path. */ char profile[256]; /** OpenXR component paths. */ - char component_path0[192]; - char component_path1[192]; + ListBase component_paths; /* XrComponentPath */ /** Input threshold/region. */ float float_threshold; @@ -132,6 +146,11 @@ typedef struct XrActionMapBinding { /* -------------------------------------------------------------------- */ +typedef struct XrUserPath { + struct XrUserPath *next, *prev; + char path[64]; /* XR_MAX_USER_PATH_LENGTH */ +} XrUserPath; + typedef struct XrActionMapItem { struct XrActionMapItem *next, *prev; @@ -142,8 +161,7 @@ typedef struct XrActionMapItem { char _pad[7]; /** OpenXR user paths. */ - char user_path0[64]; - char user_path1[64]; + ListBase user_paths; /* XrUserPath */ /** Operator to be called on XR events. */ char op[64]; /* OP_MAX_TYPENAME */ diff --git a/source/blender/makesrna/intern/rna_xr.c b/source/blender/makesrna/intern/rna_xr.c index 87d8bc8d844..9fe3153eb1e 100644 --- a/source/blender/makesrna/intern/rna_xr.c +++ b/source/blender/makesrna/intern/rna_xr.c @@ -46,6 +46,43 @@ static wmXrData *rna_XrSession_wm_xr_data_get(PointerRNA *ptr) /** \name XR Action Map * \{ */ +static XrComponentPath *rna_XrComponentPath_new(XrActionMapBinding *amb, const char *path_str) +{ +# ifdef WITH_XR_OPENXR + XrComponentPath *component_path = MEM_callocN(sizeof(XrComponentPath), __func__); + BLI_strncpy(component_path->path, path_str, sizeof(component_path->path)); + BLI_addtail(&amb->component_paths, component_path); + return component_path; +# else + UNUSED_VARS(amb, path_str); + return NULL; +# endif +} + +static void rna_XrComponentPath_remove(XrActionMapBinding *amb, PointerRNA *component_path_ptr) +{ +# ifdef WITH_XR_OPENXR + XrComponentPath *component_path = component_path_ptr->data; + int idx = BLI_findindex(&amb->component_paths, component_path); + if (idx != -1) { + BLI_freelinkN(&amb->component_paths, component_path); + } + RNA_POINTER_INVALIDATE(component_path_ptr); +# else + UNUSED_VARS(amb, component_path_ptr); +# endif +} + +static XrComponentPath *rna_XrComponentPath_find(XrActionMapBinding *amb, const char *path_str) +{ +# ifdef WITH_XR_OPENXR + return BLI_findstring(&amb->component_paths, path_str, offsetof(XrComponentPath, path)); +# else + UNUSED_VARS(amb, path_str); + return NULL; +# endif +} + static XrActionMapBinding *rna_XrActionMapBinding_new(XrActionMapItem *ami, const char *name, bool replace_existing) @@ -99,6 +136,28 @@ static XrActionMapBinding *rna_XrActionMapBinding_find(XrActionMapItem *ami, con # endif } +static void rna_XrActionMapBinding_component_paths_begin(CollectionPropertyIterator *iter, + PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapBinding *amb = (XrActionMapBinding *)ptr->data; + rna_iterator_listbase_begin(iter, &amb->component_paths, NULL); +# else + UNUSED_VARS(iter, ptr); +# endif +} + +static int rna_XrActionMapBinding_component_paths_length(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapBinding *amb = (XrActionMapBinding *)ptr->data; + return BLI_listbase_count(&amb->component_paths); +# else + UNUSED_VARS(ptr); + return 0; +# endif +} + static int rna_XrActionMapBinding_axis0_region_get(PointerRNA *ptr) { # ifdef WITH_XR_OPENXR @@ -174,6 +233,43 @@ static void rna_XrActionMapBinding_name_update(Main *bmain, Scene *UNUSED(scene) # endif } +static XrUserPath *rna_XrUserPath_new(XrActionMapItem *ami, const char *path_str) +{ +# ifdef WITH_XR_OPENXR + XrUserPath *user_path = MEM_callocN(sizeof(XrUserPath), __func__); + BLI_strncpy(user_path->path, path_str, sizeof(user_path->path)); + BLI_addtail(&ami->user_paths, user_path); + return user_path; +# else + UNUSED_VARS(ami, path_str); + return NULL; +# endif +} + +static void rna_XrUserPath_remove(XrActionMapItem *ami, PointerRNA *user_path_ptr) +{ +# ifdef WITH_XR_OPENXR + XrUserPath *user_path = user_path_ptr->data; + int idx = BLI_findindex(&ami->user_paths, user_path); + if (idx != -1) { + BLI_freelinkN(&ami->user_paths, user_path); + } + RNA_POINTER_INVALIDATE(user_path_ptr); +# else + UNUSED_VARS(ami, user_path_ptr); +# endif +} + +static XrUserPath *rna_XrUserPath_find(XrActionMapItem *ami, const char *path_str) +{ +# ifdef WITH_XR_OPENXR + return BLI_findstring(&ami->user_paths, path_str, offsetof(XrUserPath, path)); +# else + UNUSED_VARS(ami, path_str); + return NULL; +# endif +} + static XrActionMapItem *rna_XrActionMapItem_new(XrActionMap *am, const char *name, bool replace_existing) @@ -222,6 +318,27 @@ static XrActionMapItem *rna_XrActionMapItem_find(XrActionMap *am, const char *na # endif } +static void rna_XrActionMapItem_user_paths_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapItem *ami = (XrActionMapItem *)ptr->data; + rna_iterator_listbase_begin(iter, &ami->user_paths, NULL); +# else + UNUSED_VARS(iter, ptr); +# endif +} + +static int rna_XrActionMapItem_user_paths_length(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapItem *ami = (XrActionMapItem *)ptr->data; + return BLI_listbase_count(&ami->user_paths); +# else + UNUSED_VARS(ptr); + return 0; +# endif +} + static void rna_XrActionMapItem_op_name_get(PointerRNA *ptr, char *value) { # ifdef WITH_XR_OPENXR @@ -395,6 +512,27 @@ static void rna_XrActionMapItem_pose_is_controller_aim_set(PointerRNA *ptr, bool # endif } +static void rna_XrActionMapItem_bindings_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapItem *ami = (XrActionMapItem *)ptr->data; + rna_iterator_listbase_begin(iter, &ami->bindings, NULL); +# else + UNUSED_VARS(iter, ptr); +# endif +} + +static int rna_XrActionMapItem_bindings_length(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMapItem *ami = (XrActionMapItem *)ptr->data; + return BLI_listbase_count(&ami->bindings); +# else + UNUSED_VARS(ptr); + return 0; +# endif +} + static void rna_XrActionMapItem_name_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { # ifdef WITH_XR_OPENXR @@ -471,6 +609,27 @@ static XrActionMap *rna_XrActionMap_find(PointerRNA *ptr, const char *name) # endif } +static void rna_XrActionMap_items_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMap *actionmap = (XrActionMap *)ptr->data; + rna_iterator_listbase_begin(iter, &actionmap->items, NULL); +# else + UNUSED_VARS(iter, ptr); +# endif +} + +static int rna_XrActionMap_items_length(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionMap *actionmap = (XrActionMap *)ptr->data; + return BLI_listbase_count(&actionmap->items); +# else + UNUSED_VARS(ptr); + return 0; +# endif +} + static void rna_XrActionMap_name_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { # ifdef WITH_XR_OPENXR @@ -576,26 +735,8 @@ static bool rna_XrSessionState_action_create(bContext *C, { # ifdef WITH_XR_OPENXR wmWindowManager *wm = CTX_wm_manager(C); - unsigned int count_subaction_paths = 0; - const char *subaction_paths[2]; - - if (ami->user_path0[0]) { - subaction_paths[0] = ami->user_path0; - ++count_subaction_paths; - - if (ami->user_path1[0]) { - subaction_paths[1] = ami->user_path1; - ++count_subaction_paths; - } - } - else { - if (ami->user_path1[0]) { - subaction_paths[0] = ami->user_path1; - ++count_subaction_paths; - } - else { - return false; - } + if (BLI_listbase_is_empty(&ami->user_paths)) { + return false; } const bool is_float_action = (ami->type == XR_FLOAT_INPUT || ami->type == XR_VECTOR2F_INPUT); @@ -621,8 +762,7 @@ static bool rna_XrSessionState_action_create(bContext *C, actionmap->name, ami->name, ami->type, - count_subaction_paths, - subaction_paths, + &ami->user_paths, ot, op_properties, is_button_action ? ami->haptic_name : NULL, @@ -645,30 +785,10 @@ static bool rna_XrSessionState_action_binding_create(bContext *C, { # ifdef WITH_XR_OPENXR wmWindowManager *wm = CTX_wm_manager(C); - unsigned int count_subaction_paths = 0; - const char *subaction_paths[2]; - const char *component_paths[2]; - - if (ami->user_path0[0]) { - subaction_paths[0] = ami->user_path0; - component_paths[0] = amb->component_path0; - ++count_subaction_paths; - - if (ami->user_path1[0]) { - subaction_paths[1] = ami->user_path1; - component_paths[1] = amb->component_path1; - ++count_subaction_paths; - } - } - else { - if (ami->user_path1[0]) { - subaction_paths[0] = ami->user_path1; - component_paths[0] = amb->component_path1; - ++count_subaction_paths; - } - else { - return false; - } + const int count_user_paths = BLI_listbase_count(&ami->user_paths); + const int count_component_paths = BLI_listbase_count(&amb->component_paths); + if (count_user_paths < 1 || (count_user_paths != count_component_paths)) { + return false; } const bool is_float_action = (ami->type == XR_FLOAT_INPUT || ami->type == XR_VECTOR2F_INPUT); @@ -695,9 +815,8 @@ static bool rna_XrSessionState_action_binding_create(bContext *C, actionmap->name, ami->name, amb->profile, - count_subaction_paths, - subaction_paths, - component_paths, + &ami->user_paths, + &amb->component_paths, is_float_action ? float_thresholds : NULL, is_button_action ? axis_flags : NULL, is_pose_action ? poses : NULL); @@ -954,6 +1073,18 @@ static void rna_XrSessionState_actionmaps_begin(CollectionPropertyIterator *iter # endif } +static int rna_XrSessionState_actionmaps_length(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); + ListBase *lb = WM_xr_actionmaps_get(xr->runtime); + return BLI_listbase_count(lb); +# else + UNUSED_VARS(ptr); + return 0; +# endif +} + static int rna_XrSessionState_active_actionmap_get(PointerRNA *ptr) { # ifdef WITH_XR_OPENXR @@ -1240,6 +1371,42 @@ static const EnumPropertyItem rna_enum_xr_axis1_flags[] = { /** \name XR Action Map * \{ */ +static void rna_def_xr_component_paths(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_property_srna(cprop, "XrComponentPaths"); + srna = RNA_def_struct(brna, "XrComponentPaths", NULL); + RNA_def_struct_sdna(srna, "XrActionMapBinding"); + RNA_def_struct_ui_text(srna, "XR Component Paths", "Collection of OpenXR component paths"); + + func = RNA_def_function(srna, "new", "rna_XrComponentPath_new"); + parm = RNA_def_string( + func, "path", NULL, XR_MAX_COMPONENT_PATH_LENGTH, "Path", "OpenXR component path"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer( + func, "component_path", "XrComponentPath", "Component Path", "Added component path"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_XrComponentPath_remove"); + parm = RNA_def_pointer(func, "component_path", "XrComponentPath", "Component Path", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); + + func = RNA_def_function(srna, "find", "rna_XrComponentPath_find"); + parm = RNA_def_string( + func, "path", NULL, XR_MAX_COMPONENT_PATH_LENGTH, "Path", "OpenXR component path"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, + "component_path", + "XrComponentPath", + "Component Path", + "The component path with the given path"); + RNA_def_function_return(func, parm); +} + static void rna_def_xr_actionmap_bindings(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; @@ -1289,6 +1456,36 @@ static void rna_def_xr_actionmap_bindings(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_function_return(func, parm); } +static void rna_def_xr_user_paths(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_property_srna(cprop, "XrUserPaths"); + srna = RNA_def_struct(brna, "XrUserPaths", NULL); + RNA_def_struct_sdna(srna, "XrActionMapItem"); + RNA_def_struct_ui_text(srna, "XR User Paths", "Collection of OpenXR user paths"); + + func = RNA_def_function(srna, "new", "rna_XrUserPath_new"); + parm = RNA_def_string(func, "path", NULL, XR_MAX_USER_PATH_LENGTH, "Path", "OpenXR user path"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "user_path", "XrUserPath", "User Path", "Added user path"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_XrUserPath_remove"); + parm = RNA_def_pointer(func, "user_path", "XrUserPath", "User Path", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); + + func = RNA_def_function(srna, "find", "rna_XrUserPath_find"); + parm = RNA_def_string(func, "path", NULL, XR_MAX_USER_PATH_LENGTH, "Path", "OpenXR user path"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer( + func, "user_path", "XrUserPath", "User Path", "The user path with the given path"); + RNA_def_function_return(func, parm); +} + static void rna_def_xr_actionmap_items(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; @@ -1404,6 +1601,15 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) prop = RNA_def_property(srna, "actionmap_items", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "items", NULL); RNA_def_property_struct_type(prop, "XrActionMapItem"); + RNA_def_property_collection_funcs(prop, + "rna_XrActionMap_items_begin", + "rna_iterator_listbase_next", + "rna_iterator_listbase_end", + "rna_iterator_listbase_get", + "rna_XrActionMap_items_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text( prop, "Items", @@ -1414,6 +1620,15 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) RNA_def_property_int_sdna(prop, NULL, "selitem"); RNA_def_property_ui_text(prop, "Selected Item", ""); + /* XrUserPath */ + srna = RNA_def_struct(brna, "XrUserPath", NULL); + RNA_def_struct_sdna(srna, "XrUserPath"); + RNA_def_struct_ui_text(srna, "XR User Path", ""); + + prop = RNA_def_property(srna, "path", PROP_STRING, PROP_NONE); + RNA_def_property_string_maxlength(prop, XR_MAX_USER_PATH_LENGTH); + RNA_def_property_ui_text(prop, "Path", "OpenXR user path"); + /* XrActionMapItem */ srna = RNA_def_struct(brna, "XrActionMapItem", NULL); RNA_def_struct_sdna(srna, "XrActionMapItem"); @@ -1429,13 +1644,19 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Type", "Action type"); RNA_def_property_update(prop, 0, "rna_XrActionMapItem_update"); - prop = RNA_def_property(srna, "user_path0", PROP_STRING, PROP_NONE); - RNA_def_property_string_maxlength(prop, 64); - RNA_def_property_ui_text(prop, "User Path 0", "OpenXR user path"); - - prop = RNA_def_property(srna, "user_path1", PROP_STRING, PROP_NONE); - RNA_def_property_string_maxlength(prop, 64); - RNA_def_property_ui_text(prop, "User Path 1", "OpenXR user path"); + prop = RNA_def_property(srna, "user_paths", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrUserPath"); + RNA_def_property_collection_funcs(prop, + "rna_XrActionMapItem_user_paths_begin", + "rna_iterator_listbase_next", + "rna_iterator_listbase_end", + "rna_iterator_listbase_get", + "rna_XrActionMapItem_user_paths_length", + NULL, + NULL, + NULL); + RNA_def_property_ui_text(prop, "User Paths", "OpenXR user paths"); + rna_def_xr_user_paths(brna, prop); prop = RNA_def_property(srna, "op", PROP_STRING, PROP_NONE); RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME); @@ -1520,6 +1741,15 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) prop = RNA_def_property(srna, "bindings", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "XrActionMapBinding"); + RNA_def_property_collection_funcs(prop, + "rna_XrActionMapItem_bindings_begin", + "rna_iterator_listbase_next", + "rna_iterator_listbase_end", + "rna_iterator_listbase_get", + "rna_XrActionMapItem_bindings_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text( prop, "Bindings", "Bindings for the action map item, mapping the action to an XR input"); rna_def_xr_actionmap_bindings(brna, prop); @@ -1528,6 +1758,15 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) RNA_def_property_int_sdna(prop, NULL, "selbinding"); RNA_def_property_ui_text(prop, "Selected Binding", "Currently selected binding"); + /* XrComponentPath */ + srna = RNA_def_struct(brna, "XrComponentPath", NULL); + RNA_def_struct_sdna(srna, "XrComponentPath"); + RNA_def_struct_ui_text(srna, "XR Component Path", ""); + + prop = RNA_def_property(srna, "path", PROP_STRING, PROP_NONE); + RNA_def_property_string_maxlength(prop, XR_MAX_COMPONENT_PATH_LENGTH); + RNA_def_property_ui_text(prop, "Path", "OpenXR component path"); + /* XrActionMapBinding */ srna = RNA_def_struct(brna, "XrActionMapBinding", NULL); RNA_def_struct_sdna(srna, "XrActionMapBinding"); @@ -1542,13 +1781,19 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) RNA_def_property_string_maxlength(prop, 256); RNA_def_property_ui_text(prop, "Profile", "OpenXR interaction profile path"); - prop = RNA_def_property(srna, "component_path0", PROP_STRING, PROP_NONE); - RNA_def_property_string_maxlength(prop, 192); - RNA_def_property_ui_text(prop, "Component Path 0", "OpenXR component path"); - - prop = RNA_def_property(srna, "component_path1", PROP_STRING, PROP_NONE); - RNA_def_property_string_maxlength(prop, 192); - RNA_def_property_ui_text(prop, "Component Path 1", "OpenXR component path"); + prop = RNA_def_property(srna, "component_paths", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrComponentPath"); + RNA_def_property_collection_funcs(prop, + "rna_XrActionMapBinding_component_paths_begin", + "rna_iterator_listbase_next", + "rna_iterator_listbase_end", + "rna_iterator_listbase_get", + "rna_XrActionMapBinding_component_paths_length", + NULL, + NULL, + NULL); + RNA_def_property_ui_text(prop, "Component Paths", "OpenXR component paths"); + rna_def_xr_component_paths(brna, prop); prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "float_threshold"); @@ -1812,7 +2057,7 @@ static void rna_def_xr_session_state(BlenderRNA *brna) RNA_def_function_flag(func, FUNC_NO_SELF); parm = RNA_def_pointer(func, "context", "Context", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_set", NULL, 64, "Action Set", "Action set name"); + parm = RNA_def_string(func, "action_set", NULL, MAX_NAME, "Action Set", "Action set name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_boolean(func, "result", 0, "Result", ""); RNA_def_function_return(func, parm); @@ -1823,19 +2068,19 @@ static void rna_def_xr_session_state(BlenderRNA *brna) RNA_def_function_flag(func, FUNC_NO_SELF); parm = RNA_def_pointer(func, "context", "Context", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_set", NULL, 64, "Action Set", "Action set name"); + parm = RNA_def_string(func, "action_set", NULL, MAX_NAME, "Action Set", "Action set name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_string(func, "grip_action", NULL, - 64, + MAX_NAME, "Grip Action", "Name of the action representing the controller grips"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_string(func, "aim_action", NULL, - 64, + MAX_NAME, "Aim Action", "Name of the action representing the controller aims"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); @@ -1847,11 +2092,12 @@ static void rna_def_xr_session_state(BlenderRNA *brna) RNA_def_function_flag(func, FUNC_NO_SELF); parm = RNA_def_pointer(func, "context", "Context", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_set_name", NULL, 64, "Action Set", "Action set name"); + parm = RNA_def_string(func, "action_set_name", NULL, MAX_NAME, "Action Set", "Action set name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_name", NULL, 64, "Action", "Action name"); + parm = RNA_def_string(func, "action_name", NULL, MAX_NAME, "Action", "Action name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "user_path", NULL, 64, "User Path", "OpenXR user path"); + parm = RNA_def_string( + func, "user_path", NULL, XR_MAX_USER_PATH_LENGTH, "User Path", "OpenXR user path"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_float_array( func, @@ -1871,15 +2117,15 @@ static void rna_def_xr_session_state(BlenderRNA *brna) RNA_def_function_flag(func, FUNC_NO_SELF); parm = RNA_def_pointer(func, "context", "Context", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_set_name", NULL, 64, "Action Set", "Action set name"); + parm = RNA_def_string(func, "action_set_name", NULL, MAX_NAME, "Action Set", "Action set name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_name", NULL, 64, "Action", "Action name"); + parm = RNA_def_string(func, "action_name", NULL, MAX_NAME, "Action", "Action name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_string( func, "user_path", NULL, - 64, + XR_MAX_USER_PATH_LENGTH, "User Path", "Optional OpenXR user path. If not set, the action will be applied to all paths"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); @@ -1922,15 +2168,15 @@ static void rna_def_xr_session_state(BlenderRNA *brna) RNA_def_function_flag(func, FUNC_NO_SELF); parm = RNA_def_pointer(func, "context", "Context", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_set_name", NULL, 64, "Action Set", "Action set name"); + parm = RNA_def_string(func, "action_set_name", NULL, MAX_NAME, "Action Set", "Action set name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - parm = RNA_def_string(func, "action_name", NULL, 64, "Action", "Action name"); + parm = RNA_def_string(func, "action_name", NULL, MAX_NAME, "Action", "Action name"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_string( func, "user_path", NULL, - 64, + XR_MAX_USER_PATH_LENGTH, "User Path", "Optional OpenXR user path. If not set, the action will be stopped for all paths"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); @@ -2068,16 +2314,16 @@ static void rna_def_xr_session_state(BlenderRNA *brna) "Additional scale multiplier to apply to base scale when determining viewer scale"); prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrActionMap"); RNA_def_property_collection_funcs(prop, "rna_XrSessionState_actionmaps_begin", "rna_iterator_listbase_next", "rna_iterator_listbase_end", "rna_iterator_listbase_get", - NULL, + "rna_XrSessionState_actionmaps_length", NULL, NULL, NULL); - RNA_def_property_struct_type(prop, "XrActionMap"); RNA_def_property_ui_text(prop, "XR Action Maps", ""); rna_def_xr_actionmaps(brna, prop); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index c1281632cc1..49fbf2c27e1 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1583,8 +1583,7 @@ bool WM_xr_action_create(wmXrData *xr, const char *action_set_name, const char *action_name, eXrActionType type, - unsigned int count_subaction_paths, - const char **subaction_paths, + const ListBase *user_paths, struct wmOperatorType *ot, struct IDProperty *op_properties, const char *haptic_name, @@ -1599,9 +1598,8 @@ bool WM_xr_action_binding_create(wmXrData *xr, const char *action_set_name, const char *action_name, const char *profile_path, - unsigned int count_subaction_paths, - const char **subaction_paths, - const char **component_paths, + const ListBase *user_paths, + const ListBase *component_paths, const float *float_thresholds, const eXrAxisFlag *axis_flags, const struct wmXrPose *poses); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_action.c b/source/blender/windowmanager/xr/intern/wm_xr_action.c index f6003428700..0ce0837d83a 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_action.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_action.c @@ -56,8 +56,7 @@ static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name) static wmXrAction *action_create(const char *action_name, eXrActionType type, - unsigned int count_subaction_paths, - const char **subaction_paths, + const ListBase *user_paths, wmOperatorType *ot, IDProperty *op_properties, const char *haptic_name, @@ -73,15 +72,16 @@ static wmXrAction *action_create(const char *action_name, strcpy(action->name, action_name); action->type = type; - const unsigned int count = count_subaction_paths; + const unsigned int count = (unsigned int)BLI_listbase_count(user_paths); + unsigned int subaction_idx = 0; action->count_subaction_paths = count; action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count, "XrAction_SubactionPaths"); - for (unsigned int i = 0; i < count; ++i) { - action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1, - "XrAction_SubactionPath"); - strcpy(action->subaction_paths[i], subaction_paths[i]); + LISTBASE_FOREACH_INDEX (XrUserPath *, user_path, user_paths, subaction_idx) { + action->subaction_paths[subaction_idx] = MEM_mallocN(strlen(user_path->path) + 1, + "XrAction_SubactionPath"); + strcpy(action->subaction_paths[subaction_idx], user_path->path); } size_t size; @@ -140,10 +140,9 @@ static void action_destroy(void *val) MEM_SAFE_FREE(action->name); - const unsigned int count = action->count_subaction_paths; char **subaction_paths = action->subaction_paths; if (subaction_paths) { - for (unsigned int i = 0; i < count; ++i) { + for (unsigned int i = 0; i < action->count_subaction_paths; ++i) { MEM_SAFE_FREE(subaction_paths[i]); } MEM_freeN(subaction_paths); @@ -214,8 +213,7 @@ bool WM_xr_action_create(wmXrData *xr, const char *action_set_name, const char *action_name, eXrActionType type, - unsigned int count_subaction_paths, - const char **subaction_paths, + const ListBase *user_paths, wmOperatorType *ot, IDProperty *op_properties, const char *haptic_name, @@ -232,8 +230,7 @@ bool WM_xr_action_create(wmXrData *xr, wmXrAction *action = action_create(action_name, type, - count_subaction_paths, - subaction_paths, + user_paths, ot, op_properties, haptic_name, @@ -244,9 +241,19 @@ bool WM_xr_action_create(wmXrData *xr, action_flag, haptic_flag); + const unsigned int count = (unsigned int)BLI_listbase_count(user_paths); + unsigned int subaction_idx = 0; + + char **subaction_paths = MEM_calloc_arrayN( + count, sizeof(*subaction_paths), "XrAction_SubactionPathPointers"); + + LISTBASE_FOREACH_INDEX (XrUserPath *, user_path, user_paths, subaction_idx) { + subaction_paths[subaction_idx] = (char *)user_path->path; + } + GHOST_XrActionInfo info = { .name = action_name, - .count_subaction_paths = count_subaction_paths, + .count_subaction_paths = count, .subaction_paths = subaction_paths, .states = action->states, .float_thresholds = action->float_thresholds, @@ -273,11 +280,11 @@ bool WM_xr_action_create(wmXrData *xr, break; } - if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) { - return false; - } + const bool success = GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info); - return true; + MEM_freeN(subaction_paths); + + return success; } void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name) @@ -323,19 +330,29 @@ bool WM_xr_action_binding_create(wmXrData *xr, const char *action_set_name, const char *action_name, const char *profile_path, - unsigned int count_subaction_paths, - const char **subaction_paths, - const char **component_paths, + const ListBase *user_paths, + const ListBase *component_paths, const float *float_thresholds, const eXrAxisFlag *axis_flags, const struct wmXrPose *poses) { + const unsigned int count = (unsigned int)BLI_listbase_count(user_paths); + BLI_assert(count == (unsigned int)BLI_listbase_count(component_paths)); + GHOST_XrActionBindingInfo *binding_infos = MEM_calloc_arrayN( - count_subaction_paths, sizeof(*binding_infos), __func__); + count, sizeof(*binding_infos), "XrActionBinding_Infos"); - for (unsigned int i = 0; i < count_subaction_paths; ++i) { + char **subaction_paths = MEM_calloc_arrayN( + count, sizeof(*subaction_paths), "XrActionBinding_SubactionPathPointers"); + + for (unsigned int i = 0; i < count; ++i) { GHOST_XrActionBindingInfo *binding_info = &binding_infos[i]; - binding_info->component_path = component_paths[i]; + const XrUserPath *user_path = BLI_findlink(user_paths, i); + const XrComponentPath *component_path = BLI_findlink(component_paths, i); + + subaction_paths[i] = (char *)user_path->path; + + binding_info->component_path = component_path->path; if (float_thresholds) { binding_info->float_threshold = float_thresholds[i]; } @@ -351,15 +368,18 @@ bool WM_xr_action_binding_create(wmXrData *xr, GHOST_XrActionProfileInfo profile_info = { .action_name = action_name, .profile_path = profile_path, - .count_subaction_paths = count_subaction_paths, + .count_subaction_paths = count, .subaction_paths = subaction_paths, .bindings = binding_infos, }; - bool ret = GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); + const bool success = GHOST_XrCreateActionBindings( + xr->runtime->context, action_set_name, 1, &profile_info); + MEM_freeN(subaction_paths); MEM_freeN(binding_infos); - return ret; + + return success; } void WM_xr_action_binding_destroy(wmXrData *xr, diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c index 0c356ab2b2e..267fb0481a8 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c @@ -118,11 +118,17 @@ XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami, return amb_dst; } +static void wm_xr_actionmap_binding_clear(XrActionMapBinding *amb) +{ + BLI_freelistN(&amb->component_paths); +} + bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb) { int idx = BLI_findindex(&ami->bindings, amb); if (idx != -1) { + wm_xr_actionmap_binding_clear(amb); BLI_freelinkN(&ami->bindings, amb); if (idx <= ami->selbinding) { @@ -155,12 +161,6 @@ XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const cha * Item in an XR action map, that maps an XR event to an operator, pose, or haptic output. * \{ */ -static void wm_xr_actionmap_item_bindings_clear(XrActionMapItem *ami) -{ - BLI_freelistN(&ami->bindings); - ami->selbinding = 0; -} - static void wm_xr_actionmap_item_properties_set(XrActionMapItem *ami) { WM_operator_properties_alloc(&(ami->op_properties_ptr), &(ami->op_properties), ami->op); @@ -180,6 +180,19 @@ static void wm_xr_actionmap_item_properties_free(XrActionMapItem *ami) } } +static void wm_xr_actionmap_item_clear(XrActionMapItem *ami) +{ + LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) { + wm_xr_actionmap_binding_clear(amb); + } + BLI_freelistN(&ami->bindings); + ami->selbinding = 0; + + wm_xr_actionmap_item_properties_free(ami); + + BLI_freelistN(&ami->user_paths); +} + void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami) { switch (ami->type) { @@ -324,8 +337,7 @@ bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami) int idx = BLI_findindex(&actionmap->items, ami); if (idx != -1) { - wm_xr_actionmap_item_bindings_clear(ami); - wm_xr_actionmap_item_properties_free(ami); + wm_xr_actionmap_item_clear(ami); BLI_freelinkN(&actionmap->items, ami); if (idx <= actionmap->selitem) { @@ -480,12 +492,9 @@ XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name) void WM_xr_actionmap_clear(XrActionMap *actionmap) { LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) { - wm_xr_actionmap_item_bindings_clear(ami); - wm_xr_actionmap_item_properties_free(ami); + wm_xr_actionmap_item_clear(ami); } - BLI_freelistN(&actionmap->items); - actionmap->selitem = 0; } @@ -494,9 +503,7 @@ void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime) LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) { WM_xr_actionmap_clear(am); } - BLI_freelistN(&runtime->actionmaps); - runtime->actactionmap = runtime->selactionmap = 0; } diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 0e7c4d18753..9480104150a 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -114,12 +114,8 @@ typedef struct wmXrDrawData { typedef struct wmXrController { struct wmXrController *next, *prev; - /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256). - This subaction path will later be combined with a component path, and that combined path should - also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path = - /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value). - */ - char subaction_path[64]; + /** OpenXR user path identifier. */ + char subaction_path[64]; /* XR_MAX_USER_PATH_LENGTH */ /** Pose (in world space) that represents the user's hand when holding the controller. */ GHOST_XrPose grip_pose; diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index 59b4eb00363..dfeaeae196c 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -1188,8 +1188,9 @@ void wm_xr_session_actions_update(wmWindowManager *wm) &state->viewer_pose, settings->base_scale * state->nav_scale, state->viewer_viewmat); } - int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); - if (!ret) { + const bool synced = GHOST_XrSyncActions(xr_context, + active_action_set ? active_action_set->name : NULL); + if (!synced) { return; } -- cgit v1.2.3 From 401383f2457860e9d5fef4827aa9f013e7451453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Thu, 17 Feb 2022 08:38:12 +0100 Subject: Fix vertex groups not rendering properly with GPU subdivision This was missing the BMesh case. Issue found while investigating T95827. --- .../mesh_extractors/extract_mesh_vbo_weights.cc | 38 +++++++++------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc index bb8853b8154..bb608014c53 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc @@ -169,10 +169,10 @@ static void extract_weights_iter_poly_mesh(const MeshRenderData *mr, } static void extract_weights_init_subdiv(const DRWSubdivCache *subdiv_cache, - const MeshRenderData *UNUSED(mr), + const MeshRenderData *mr, struct MeshBatchCache *cache, void *buffer, - void *UNUSED(data)) + void *_data) { Mesh *coarse_mesh = subdiv_cache->mesh; GPUVertBuf *vbo = static_cast(buffer); @@ -184,28 +184,20 @@ static void extract_weights_init_subdiv(const DRWSubdivCache *subdiv_cache, GPU_vertbuf_init_build_on_device(vbo, &format, subdiv_cache->num_subdiv_loops); GPUVertBuf *coarse_weights = GPU_vertbuf_calloc(); - GPU_vertbuf_init_with_format(coarse_weights, &format); - GPU_vertbuf_data_alloc(coarse_weights, coarse_mesh->totloop); - float *coarse_weights_data = static_cast(GPU_vertbuf_get_data(coarse_weights)); + extract_weights_init(mr, cache, coarse_weights, _data); - const DRW_MeshWeightState *wstate = &cache->weight_state; - const MDeformVert *dverts = static_cast( - CustomData_get_layer(&coarse_mesh->vdata, CD_MDEFORMVERT)); - - for (int i = 0; i < coarse_mesh->totpoly; i++) { - const MPoly *mpoly = &coarse_mesh->mpoly[i]; - - for (int loop_index = mpoly->loopstart; loop_index < mpoly->loopstart + mpoly->totloop; - loop_index++) { - const MLoop *ml = &coarse_mesh->mloop[loop_index]; - - if (dverts != nullptr) { - const MDeformVert *dvert = &dverts[ml->v]; - coarse_weights_data[loop_index] = evaluate_vertex_weight(dvert, wstate); - } - else { - coarse_weights_data[loop_index] = evaluate_vertex_weight(nullptr, wstate); - } + if (mr->extract_type != MR_EXTRACT_BMESH) { + for (int i = 0; i < coarse_mesh->totpoly; i++) { + const MPoly *mpoly = &coarse_mesh->mpoly[i]; + extract_weights_iter_poly_mesh(mr, mpoly, i, _data); + } + } + else { + BMIter f_iter; + BMFace *efa; + int face_index = 0; + BM_ITER_MESH_INDEX (efa, &f_iter, mr->bm, BM_FACES_OF_MESH, face_index) { + extract_weights_iter_poly_bm(mr, efa, face_index, _data); } } -- cgit v1.2.3 From be3047c500bebcbf82ec95158a5d24703c104f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Thu, 17 Feb 2022 08:40:38 +0100 Subject: Fix T95827: vertex groups do not display correctly with GPU subdivision Issue caused by 993839ce85137ac37a978f49ae894703a39dbf6a which modified the coarse face flags update function, but forgot the case where we have a mapped extraction with no BMesh. --- .../draw/intern/draw_cache_impl_subdivision.cc | 110 ++++++++++++--------- 1 file changed, 64 insertions(+), 46 deletions(-) diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc index e4c53604370..60c07db90b7 100644 --- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc +++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc @@ -607,6 +607,67 @@ void draw_subdiv_cache_free(DRWSubdivCache *cache) SUBDIV_COARSE_FACE_FLAG_ACTIVE) \ << SUBDIV_COARSE_FACE_FLAG_OFFSET) +static uint32_t compute_coarse_face_flag(BMFace *f, BMFace *efa_act) +{ + if (f == nullptr) { + /* May happen during mapped extraction. */ + return 0; + } + + uint32_t flag = 0; + if (BM_elem_flag_test(f, BM_ELEM_SMOOTH)) { + flag |= SUBDIV_COARSE_FACE_FLAG_SMOOTH; + } + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + flag |= SUBDIV_COARSE_FACE_FLAG_SELECT; + } + if (f == efa_act) { + flag |= SUBDIV_COARSE_FACE_FLAG_ACTIVE; + } + const int loopstart = BM_elem_index_get(f->l_first); + return (uint)(loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); +} + +static void draw_subdiv_cache_extra_coarse_face_data_bm(BMesh *bm, + BMFace *efa_act, + uint32_t *flags_data) +{ + BMFace *f; + BMIter iter; + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + const int index = BM_elem_index_get(f); + flags_data[index] = compute_coarse_face_flag(f, efa_act); + } +} + +static void draw_subdiv_cache_extra_coarse_face_data_mesh(Mesh *mesh, uint32_t *flags_data) +{ + for (int i = 0; i < mesh->totpoly; i++) { + uint32_t flag = 0; + if ((mesh->mpoly[i].flag & ME_SMOOTH) != 0) { + flag = SUBDIV_COARSE_FACE_FLAG_SMOOTH; + } + flags_data[i] = (uint)(mesh->mpoly[i].loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); + } +} + +static void draw_subdiv_cache_extra_coarse_face_data_mapped(Mesh *mesh, + BMesh *bm, + MeshRenderData *mr, + uint32_t *flags_data) +{ + if (bm == nullptr) { + draw_subdiv_cache_extra_coarse_face_data_mesh(mesh, flags_data); + return; + } + + for (int i = 0; i < mesh->totpoly; i++) { + BMFace *f = bm_original_face_get(mr, i); + flags_data[i] = compute_coarse_face_flag(f, mr->efa_act); + } +} + static void draw_subdiv_cache_update_extra_coarse_face_data(DRWSubdivCache *cache, Mesh *mesh, MeshRenderData *mr) @@ -626,56 +687,13 @@ static void draw_subdiv_cache_update_extra_coarse_face_data(DRWSubdivCache *cach uint32_t *flags_data = (uint32_t *)(GPU_vertbuf_get_data(cache->extra_coarse_face_data)); if (mr->extract_type == MR_EXTRACT_BMESH) { - BMesh *bm = cache->bm; - BMFace *f; - BMIter iter; - - /* Ensure all current elements follow new customdata layout. */ - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - const int index = BM_elem_index_get(f); - uint32_t flag = 0; - if (BM_elem_flag_test(f, BM_ELEM_SMOOTH)) { - flag |= SUBDIV_COARSE_FACE_FLAG_SMOOTH; - } - if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { - flag |= SUBDIV_COARSE_FACE_FLAG_SELECT; - } - if (f == mr->efa_act) { - flag |= SUBDIV_COARSE_FACE_FLAG_ACTIVE; - } - const int loopstart = BM_elem_index_get(f->l_first); - flags_data[index] = (uint)(loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); - } + draw_subdiv_cache_extra_coarse_face_data_bm(cache->bm, mr->efa_act, flags_data); } else if (mr->extract_type == MR_EXTRACT_MAPPED) { - for (int i = 0; i < mesh->totpoly; i++) { - BMFace *f = bm_original_face_get(mr, i); - uint32_t flag = 0; - - if (f) { - if (BM_elem_flag_test(f, BM_ELEM_SMOOTH)) { - flag |= SUBDIV_COARSE_FACE_FLAG_SMOOTH; - } - if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { - flag |= SUBDIV_COARSE_FACE_FLAG_SELECT; - } - if (f == mr->efa_act) { - flag |= SUBDIV_COARSE_FACE_FLAG_ACTIVE; - } - const int loopstart = BM_elem_index_get(f->l_first); - flag = (uint)(loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); - } - flags_data[i] = flag; - } + draw_subdiv_cache_extra_coarse_face_data_mapped(mesh, cache->bm, mr, flags_data); } else { - for (int i = 0; i < mesh->totpoly; i++) { - uint32_t flag = 0; - if ((mesh->mpoly[i].flag & ME_SMOOTH) != 0) { - flag = SUBDIV_COARSE_FACE_FLAG_SMOOTH; - } - flags_data[i] = (uint)(mesh->mpoly[i].loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); - } + draw_subdiv_cache_extra_coarse_face_data_mesh(mesh, flags_data); } /* Make sure updated data is re-uploaded. */ -- cgit v1.2.3 From c5dcfb63d9e40138f03c135346bfa779abcb0e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Wed, 16 Feb 2022 16:55:35 +0100 Subject: Fix T94881: GPU subdivision fails with high polycount coarse meshes Coarse meshes with high polycount would show as corrupted when GPU subdivision is used with AMD cards This was caused by the OpenSubdiv library not taking `GL_MAX_COMPUTE_WORK_GROUP_COUNT` into account when dispatching computes. AMD drivers tend to set the limit lower than NVidia ones (2^16 for the former, and 2^32 for the latter, at least on my machine). This moves the `GLComputeEvaluator` from the OpenSubdiv library into `intern/opensubdiv` and modifies it to compute a dispatch size in a similar way as for the draw code: we split the dispatch size into a 2 dimensional value based on `GL_MAX_COMPUTE_WORK_GROUP_COUNT` and manually compute an index in the shader. We could have patched the OpenSubdiv library and sent the fix upstream (which can still be done), however, moving it to our side allows us to better control the `GLComputeEvaluator` and in the future remove some redundant work that it does compared to Blender (see T94644) and probably prepare the ground for Vulkan support. As a matter of fact, this patch also removes the OpenGL initialization that OpenSubdiv would do here. This removal is not related to the bug fix, but necessary to not have to copy more files/code over. Differential Revision: https://developer.blender.org/D14131 --- intern/opensubdiv/CMakeLists.txt | 4 + .../internal/evaluator/eval_output_gpu.h | 4 +- .../internal/evaluator/gl_compute_evaluator.cc | 647 +++++ .../internal/evaluator/gl_compute_evaluator.h | 2465 ++++++++++++++++++++ .../evaluator/shaders/glsl_compute_kernel.glsl | 316 +++ 5 files changed, 3433 insertions(+), 3 deletions(-) create mode 100644 intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc create mode 100644 intern/opensubdiv/internal/evaluator/gl_compute_evaluator.h create mode 100644 intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl diff --git a/intern/opensubdiv/CMakeLists.txt b/intern/opensubdiv/CMakeLists.txt index f141689791f..2bc429d7844 100644 --- a/intern/opensubdiv/CMakeLists.txt +++ b/intern/opensubdiv/CMakeLists.txt @@ -83,6 +83,8 @@ if(WITH_OPENSUBDIV) internal/evaluator/evaluator_capi.cc internal/evaluator/evaluator_impl.cc internal/evaluator/evaluator_impl.h + internal/evaluator/gl_compute_evaluator.cc + internal/evaluator/gl_compute_evaluator.h internal/evaluator/patch_map.cc internal/evaluator/patch_map.h @@ -121,6 +123,8 @@ if(WITH_OPENSUBDIV) add_definitions(-DNOMINMAX) add_definitions(-D_USE_MATH_DEFINES) endif() + + data_to_c_simple(internal/evaluator/shaders/glsl_compute_kernel.glsl SRC) else() list(APPEND SRC stub/opensubdiv_stub.cc diff --git a/intern/opensubdiv/internal/evaluator/eval_output_gpu.h b/intern/opensubdiv/internal/evaluator/eval_output_gpu.h index 783efd484aa..dc137e4322e 100644 --- a/intern/opensubdiv/internal/evaluator/eval_output_gpu.h +++ b/intern/opensubdiv/internal/evaluator/eval_output_gpu.h @@ -20,13 +20,11 @@ #define OPENSUBDIV_EVAL_OUTPUT_GPU_H_ #include "internal/evaluator/eval_output.h" +#include "internal/evaluator/gl_compute_evaluator.h" -#include #include #include -using OpenSubdiv::Osd::GLComputeEvaluator; -using OpenSubdiv::Osd::GLStencilTableSSBO; using OpenSubdiv::Osd::GLVertexBuffer; namespace blender { diff --git a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc new file mode 100644 index 00000000000..0cab44518aa --- /dev/null +++ b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc @@ -0,0 +1,647 @@ +// +// Copyright 2015 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// + +#include "gl_compute_evaluator.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using OpenSubdiv::Far::LimitStencilTable; +using OpenSubdiv::Far::StencilTable; +using OpenSubdiv::Osd::BufferDescriptor; +using OpenSubdiv::Osd::PatchArray; +using OpenSubdiv::Osd::PatchArrayVector; + +extern "C" char datatoc_glsl_compute_kernel_glsl[]; + +namespace blender { +namespace opensubdiv { + +template GLuint createSSBO(std::vector const &src) +{ + if (src.empty()) { + return 0; + } + + GLuint devicePtr = 0; + +#if defined(GL_ARB_direct_state_access) + if (GLEW_ARB_direct_state_access) { + glCreateBuffers(1, &devicePtr); + glNamedBufferData(devicePtr, src.size() * sizeof(T), &src.at(0), GL_STATIC_DRAW); + } + else +#endif + { + GLint prev = 0; + glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &prev); + glGenBuffers(1, &devicePtr); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, devicePtr); + glBufferData(GL_SHADER_STORAGE_BUFFER, src.size() * sizeof(T), &src.at(0), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, prev); + } + + return devicePtr; +} + +GLStencilTableSSBO::GLStencilTableSSBO(StencilTable const *stencilTable) +{ + _numStencils = stencilTable->GetNumStencils(); + if (_numStencils > 0) { + _sizes = createSSBO(stencilTable->GetSizes()); + _offsets = createSSBO(stencilTable->GetOffsets()); + _indices = createSSBO(stencilTable->GetControlIndices()); + _weights = createSSBO(stencilTable->GetWeights()); + _duWeights = _dvWeights = 0; + _duuWeights = _duvWeights = _dvvWeights = 0; + } + else { + _sizes = _offsets = _indices = _weights = 0; + _duWeights = _dvWeights = 0; + _duuWeights = _duvWeights = _dvvWeights = 0; + } +} + +GLStencilTableSSBO::GLStencilTableSSBO(LimitStencilTable const *limitStencilTable) +{ + _numStencils = limitStencilTable->GetNumStencils(); + if (_numStencils > 0) { + _sizes = createSSBO(limitStencilTable->GetSizes()); + _offsets = createSSBO(limitStencilTable->GetOffsets()); + _indices = createSSBO(limitStencilTable->GetControlIndices()); + _weights = createSSBO(limitStencilTable->GetWeights()); + _duWeights = createSSBO(limitStencilTable->GetDuWeights()); + _dvWeights = createSSBO(limitStencilTable->GetDvWeights()); + _duuWeights = createSSBO(limitStencilTable->GetDuuWeights()); + _duvWeights = createSSBO(limitStencilTable->GetDuvWeights()); + _dvvWeights = createSSBO(limitStencilTable->GetDvvWeights()); + } + else { + _sizes = _offsets = _indices = _weights = 0; + _duWeights = _dvWeights = 0; + _duuWeights = _duvWeights = _dvvWeights = 0; + } +} + +GLStencilTableSSBO::~GLStencilTableSSBO() +{ + if (_sizes) + glDeleteBuffers(1, &_sizes); + if (_offsets) + glDeleteBuffers(1, &_offsets); + if (_indices) + glDeleteBuffers(1, &_indices); + if (_weights) + glDeleteBuffers(1, &_weights); + if (_duWeights) + glDeleteBuffers(1, &_duWeights); + if (_dvWeights) + glDeleteBuffers(1, &_dvWeights); + if (_duuWeights) + glDeleteBuffers(1, &_duuWeights); + if (_duvWeights) + glDeleteBuffers(1, &_duvWeights); + if (_dvvWeights) + glDeleteBuffers(1, &_dvvWeights); +} + +// --------------------------------------------------------------------------- + +GLComputeEvaluator::GLComputeEvaluator() : _workGroupSize(64), _patchArraysSSBO(0) +{ + memset(&_stencilKernel, 0, sizeof(_stencilKernel)); + memset(&_patchKernel, 0, sizeof(_patchKernel)); +} + +GLComputeEvaluator::~GLComputeEvaluator() +{ + if (_patchArraysSSBO) { + glDeleteBuffers(1, &_patchArraysSSBO); + } +} + +static GLuint compileKernel(BufferDescriptor const &srcDesc, + BufferDescriptor const &dstDesc, + BufferDescriptor const &duDesc, + BufferDescriptor const &dvDesc, + BufferDescriptor const &duuDesc, + BufferDescriptor const &duvDesc, + BufferDescriptor const &dvvDesc, + const char *kernelDefine, + int workGroupSize) +{ + GLuint program = glCreateProgram(); + + GLuint shader = glCreateShader(GL_COMPUTE_SHADER); + + std::string patchBasisShaderSource = + OpenSubdiv::Osd::GLSLPatchShaderSource::GetPatchBasisShaderSource(); + const char *patchBasisShaderSourceDefine = "#define OSD_PATCH_BASIS_GLSL\n"; + + std::ostringstream defines; + defines << "#define LENGTH " << srcDesc.length << "\n" + << "#define SRC_STRIDE " << srcDesc.stride << "\n" + << "#define DST_STRIDE " << dstDesc.stride << "\n" + << "#define WORK_GROUP_SIZE " << workGroupSize << "\n" + << kernelDefine << "\n" + << patchBasisShaderSourceDefine << "\n"; + + bool deriv1 = (duDesc.length > 0 || dvDesc.length > 0); + bool deriv2 = (duuDesc.length > 0 || duvDesc.length > 0 || dvvDesc.length > 0); + if (deriv1) { + defines << "#define OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES\n"; + } + if (deriv2) { + defines << "#define OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES\n"; + } + + std::string defineStr = defines.str(); + + const char *shaderSources[4] = {"#version 430\n", 0, 0, 0}; + + shaderSources[1] = defineStr.c_str(); + shaderSources[2] = patchBasisShaderSource.c_str(); + shaderSources[3] = datatoc_glsl_compute_kernel_glsl; + glShaderSource(shader, 4, shaderSources, NULL); + glCompileShader(shader); + glAttachShader(program, shader); + + GLint linked = 0; + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &linked); + + if (linked == GL_FALSE) { + char buffer[1024]; + glGetShaderInfoLog(shader, 1024, NULL, buffer); + OpenSubdiv::Far::Error(OpenSubdiv::Far::FAR_RUNTIME_ERROR, buffer); + + glGetProgramInfoLog(program, 1024, NULL, buffer); + OpenSubdiv::Far::Error(OpenSubdiv::Far::FAR_RUNTIME_ERROR, buffer); + + glDeleteProgram(program); + return 0; + } + + glDeleteShader(shader); + + return program; +} + +bool GLComputeEvaluator::Compile(BufferDescriptor const &srcDesc, + BufferDescriptor const &dstDesc, + BufferDescriptor const &duDesc, + BufferDescriptor const &dvDesc, + BufferDescriptor const &duuDesc, + BufferDescriptor const &duvDesc, + BufferDescriptor const &dvvDesc) +{ + + // create a stencil kernel + if (!_stencilKernel.Compile( + srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, _workGroupSize)) { + return false; + } + + // create a patch kernel + if (!_patchKernel.Compile( + srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, _workGroupSize)) { + return false; + } + + // create a patch arrays buffer + if (!_patchArraysSSBO) { + glGenBuffers(1, &_patchArraysSSBO); + } + + return true; +} + +/* static */ +void GLComputeEvaluator::Synchronize(void * /*kernel*/) +{ + // XXX: this is currently just for the performance measuring purpose. + // need to be reimplemented by fence and sync. + glFinish(); +} + +int GLComputeEvaluator::GetDispatchSize(int count) const +{ + return (count + _workGroupSize - 1) / _workGroupSize; +} + +void GLComputeEvaluator::DispatchCompute(int totalDispatchSize) const +{ + int maxWorkGroupCount[2] = {0, 0}; + + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &maxWorkGroupCount[0]); + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &maxWorkGroupCount[1]); + + const uint maxResX = static_cast(maxWorkGroupCount[0]); + + const int dispatchSize = GetDispatchSize(totalDispatchSize); + uint dispatchRX = static_cast(dispatchSize); + uint dispatchRY = 1u; + if (dispatchRX > maxResX) { + /* Since there are some limitations with regards to the maximum work group size (could be as + * low as 64k elements per call), we split the number elements into a "2d" number, with the + * final index being computed as `res_x + res_y * max_work_group_size`. Even with a maximum + * work group size of 64k, that still leaves us with roughly `64k * 64k = 4` billion elements + * total, which should be enough. If not, we could also use the 3rd dimension. */ + /* TODO(fclem): We could dispatch fewer groups if we compute the prime factorization and + * get the smallest rect fitting the requirements. */ + dispatchRX = dispatchRY = std::ceil(std::sqrt(dispatchSize)); + /* Avoid a completely empty dispatch line caused by rounding. */ + if ((dispatchRX * (dispatchRY - 1)) >= dispatchSize) { + dispatchRY -= 1; + } + } + + /* X and Y dimensions may have different limits so the above computation may not be right, but + * even with the standard 64k minimum on all dimensions we still have a lot of room. Therefore, + * we presume it all fits. */ + assert(dispatchRY < static_cast(maxWorkGroupCount[1])); + + glDispatchCompute(dispatchRX, dispatchRY, 1); +} + +bool GLComputeEvaluator::EvalStencils(GLuint srcBuffer, + BufferDescriptor const &srcDesc, + GLuint dstBuffer, + BufferDescriptor const &dstDesc, + GLuint duBuffer, + BufferDescriptor const &duDesc, + GLuint dvBuffer, + BufferDescriptor const &dvDesc, + GLuint sizesBuffer, + GLuint offsetsBuffer, + GLuint indicesBuffer, + GLuint weightsBuffer, + GLuint duWeightsBuffer, + GLuint dvWeightsBuffer, + int start, + int end) const +{ + + return EvalStencils(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + 0, + BufferDescriptor(), + 0, + BufferDescriptor(), + 0, + BufferDescriptor(), + sizesBuffer, + offsetsBuffer, + indicesBuffer, + weightsBuffer, + duWeightsBuffer, + dvWeightsBuffer, + 0, + 0, + 0, + start, + end); +} + +bool GLComputeEvaluator::EvalStencils(GLuint srcBuffer, + BufferDescriptor const &srcDesc, + GLuint dstBuffer, + BufferDescriptor const &dstDesc, + GLuint duBuffer, + BufferDescriptor const &duDesc, + GLuint dvBuffer, + BufferDescriptor const &dvDesc, + GLuint duuBuffer, + BufferDescriptor const &duuDesc, + GLuint duvBuffer, + BufferDescriptor const &duvDesc, + GLuint dvvBuffer, + BufferDescriptor const &dvvDesc, + GLuint sizesBuffer, + GLuint offsetsBuffer, + GLuint indicesBuffer, + GLuint weightsBuffer, + GLuint duWeightsBuffer, + GLuint dvWeightsBuffer, + GLuint duuWeightsBuffer, + GLuint duvWeightsBuffer, + GLuint dvvWeightsBuffer, + int start, + int end) const +{ + + if (!_stencilKernel.program) + return false; + int count = end - start; + if (count <= 0) { + return true; + } + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, srcBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, dstBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, duBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, dvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, duuBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, duvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, dvvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, sizesBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, offsetsBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, indicesBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, weightsBuffer); + if (duWeightsBuffer) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, duWeightsBuffer); + if (dvWeightsBuffer) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 9, dvWeightsBuffer); + if (duuWeightsBuffer) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, duuWeightsBuffer); + if (duvWeightsBuffer) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, duvWeightsBuffer); + if (dvvWeightsBuffer) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, dvvWeightsBuffer); + + glUseProgram(_stencilKernel.program); + + glUniform1i(_stencilKernel.uniformStart, start); + glUniform1i(_stencilKernel.uniformEnd, end); + glUniform1i(_stencilKernel.uniformSrcOffset, srcDesc.offset); + glUniform1i(_stencilKernel.uniformDstOffset, dstDesc.offset); + if (_stencilKernel.uniformDuDesc > 0) { + glUniform3i(_stencilKernel.uniformDuDesc, duDesc.offset, duDesc.length, duDesc.stride); + } + if (_stencilKernel.uniformDvDesc > 0) { + glUniform3i(_stencilKernel.uniformDvDesc, dvDesc.offset, dvDesc.length, dvDesc.stride); + } + if (_stencilKernel.uniformDuuDesc > 0) { + glUniform3i(_stencilKernel.uniformDuuDesc, duuDesc.offset, duuDesc.length, duuDesc.stride); + } + if (_stencilKernel.uniformDuvDesc > 0) { + glUniform3i(_stencilKernel.uniformDuvDesc, duvDesc.offset, duvDesc.length, duvDesc.stride); + } + if (_stencilKernel.uniformDvvDesc > 0) { + glUniform3i(_stencilKernel.uniformDvvDesc, dvvDesc.offset, dvvDesc.length, dvvDesc.stride); + } + + DispatchCompute(count); + + glUseProgram(0); + + glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT); + for (int i = 0; i < 16; ++i) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, 0); + } + + return true; +} + +bool GLComputeEvaluator::EvalPatches(GLuint srcBuffer, + BufferDescriptor const &srcDesc, + GLuint dstBuffer, + BufferDescriptor const &dstDesc, + GLuint duBuffer, + BufferDescriptor const &duDesc, + GLuint dvBuffer, + BufferDescriptor const &dvDesc, + int numPatchCoords, + GLuint patchCoordsBuffer, + const PatchArrayVector &patchArrays, + GLuint patchIndexBuffer, + GLuint patchParamsBuffer) const +{ + + return EvalPatches(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + 0, + BufferDescriptor(), + 0, + BufferDescriptor(), + 0, + BufferDescriptor(), + numPatchCoords, + patchCoordsBuffer, + patchArrays, + patchIndexBuffer, + patchParamsBuffer); +} + +bool GLComputeEvaluator::EvalPatches(GLuint srcBuffer, + BufferDescriptor const &srcDesc, + GLuint dstBuffer, + BufferDescriptor const &dstDesc, + GLuint duBuffer, + BufferDescriptor const &duDesc, + GLuint dvBuffer, + BufferDescriptor const &dvDesc, + GLuint duuBuffer, + BufferDescriptor const &duuDesc, + GLuint duvBuffer, + BufferDescriptor const &duvDesc, + GLuint dvvBuffer, + BufferDescriptor const &dvvDesc, + int numPatchCoords, + GLuint patchCoordsBuffer, + const PatchArrayVector &patchArrays, + GLuint patchIndexBuffer, + GLuint patchParamsBuffer) const +{ + + if (!_patchKernel.program) + return false; + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, srcBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, dstBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, duBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, dvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, duuBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, duvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, dvvBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, patchCoordsBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, patchIndexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, patchParamsBuffer); + + glUseProgram(_patchKernel.program); + + glUniform1i(_patchKernel.uniformSrcOffset, srcDesc.offset); + glUniform1i(_patchKernel.uniformDstOffset, dstDesc.offset); + + int patchArraySize = sizeof(PatchArray); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _patchArraysSSBO); + glBufferData( + GL_SHADER_STORAGE_BUFFER, patchArrays.size() * patchArraySize, NULL, GL_STATIC_DRAW); + for (int i = 0; i < (int)patchArrays.size(); ++i) { + glBufferSubData( + GL_SHADER_STORAGE_BUFFER, i * patchArraySize, sizeof(PatchArray), &patchArrays[i]); + } + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, _patchArraysSSBO); + + if (_patchKernel.uniformDuDesc > 0) { + glUniform3i(_patchKernel.uniformDuDesc, duDesc.offset, duDesc.length, duDesc.stride); + } + if (_patchKernel.uniformDvDesc > 0) { + glUniform3i(_patchKernel.uniformDvDesc, dvDesc.offset, dvDesc.length, dvDesc.stride); + } + if (_patchKernel.uniformDuuDesc > 0) { + glUniform3i(_patchKernel.uniformDuuDesc, duuDesc.offset, duuDesc.length, duuDesc.stride); + } + if (_patchKernel.uniformDuvDesc > 0) { + glUniform3i(_patchKernel.uniformDuvDesc, duvDesc.offset, duvDesc.length, duvDesc.stride); + } + if (_patchKernel.uniformDvvDesc > 0) { + glUniform3i(_patchKernel.uniformDvvDesc, dvvDesc.offset, dvvDesc.length, dvvDesc.stride); + } + + DispatchCompute(numPatchCoords); + + glUseProgram(0); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, 0); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, 0); + + return true; +} +// --------------------------------------------------------------------------- + +GLComputeEvaluator::_StencilKernel::_StencilKernel() : program(0) +{ +} +GLComputeEvaluator::_StencilKernel::~_StencilKernel() +{ + if (program) { + glDeleteProgram(program); + } +} + +bool GLComputeEvaluator::_StencilKernel::Compile(BufferDescriptor const &srcDesc, + BufferDescriptor const &dstDesc, + BufferDescriptor const &duDesc, + BufferDescriptor const &dvDesc, + BufferDescriptor const &duuDesc, + BufferDescriptor const &duvDesc, + BufferDescriptor const &dvvDesc, + int workGroupSize) +{ + // create stencil kernel + if (program) { + glDeleteProgram(program); + } + + const char *kernelDefine = "#define OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS\n"; + + program = compileKernel( + srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, kernelDefine, workGroupSize); + if (program == 0) + return false; + + // cache uniform locations (TODO: use uniform block) + uniformStart = glGetUniformLocation(program, "batchStart"); + uniformEnd = glGetUniformLocation(program, "batchEnd"); + uniformSrcOffset = glGetUniformLocation(program, "srcOffset"); + uniformDstOffset = glGetUniformLocation(program, "dstOffset"); + uniformDuDesc = glGetUniformLocation(program, "duDesc"); + uniformDvDesc = glGetUniformLocation(program, "dvDesc"); + uniformDuuDesc = glGetUniformLocation(program, "duuDesc"); + uniformDuvDesc = glGetUniformLocation(program, "duvDesc"); + uniformDvvDesc = glGetUniformLocation(program, "dvvDesc"); + + return true; +} + +// --------------------------------------------------------------------------- + +GLComputeEvaluator::_PatchKernel::_PatchKernel() : program(0) +{ +} +GLComputeEvaluator::_PatchKernel::~_PatchKernel() +{ + if (program) { + glDeleteProgram(program); + } +} + +bool GLComputeEvaluator::_PatchKernel::Compile(BufferDescriptor const &srcDesc, + BufferDescriptor const &dstDesc, + BufferDescriptor const &duDesc, + BufferDescriptor const &dvDesc, + BufferDescriptor const &duuDesc, + BufferDescriptor const &duvDesc, + BufferDescriptor const &dvvDesc, + int workGroupSize) +{ + // create stencil kernel + if (program) { + glDeleteProgram(program); + } + + const char *kernelDefine = "#define OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_PATCHES\n"; + + program = compileKernel( + srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, kernelDefine, workGroupSize); + if (program == 0) + return false; + + // cache uniform locations + uniformSrcOffset = glGetUniformLocation(program, "srcOffset"); + uniformDstOffset = glGetUniformLocation(program, "dstOffset"); + uniformPatchArray = glGetUniformLocation(program, "patchArray"); + uniformDuDesc = glGetUniformLocation(program, "duDesc"); + uniformDvDesc = glGetUniformLocation(program, "dvDesc"); + uniformDuuDesc = glGetUniformLocation(program, "duuDesc"); + uniformDuvDesc = glGetUniformLocation(program, "duvDesc"); + uniformDvvDesc = glGetUniformLocation(program, "dvvDesc"); + + return true; +} + +} // namespace opensubdiv +} // namespace blender diff --git a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.h b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.h new file mode 100644 index 00000000000..85c12f73b08 --- /dev/null +++ b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.h @@ -0,0 +1,2465 @@ +// +// Copyright 2015 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// + +#ifndef OPENSUBDIV_GL_COMPUTE_EVALUATOR_H_ +#define OPENSUBDIV_GL_COMPUTE_EVALUATOR_H_ + +#include +#include +#include +#include + +namespace OpenSubdiv { +namespace OPENSUBDIV_VERSION { +namespace Far { +class LimitStencilTable; +class StencilTable; +} // namespace Far +} // namespace OPENSUBDIV_VERSION +} // namespace OpenSubdiv + +namespace blender { +namespace opensubdiv { + +/// \brief GL stencil table (Shader Storage buffer) +/// +/// This class is a GLSL SSBO representation of OpenSubdiv::Far::StencilTable. +/// +/// GLSLComputeKernel consumes this table to apply stencils +/// +class GLStencilTableSSBO { + public: + static GLStencilTableSSBO *Create(OpenSubdiv::Far::StencilTable const *stencilTable, + void *deviceContext = NULL) + { + (void)deviceContext; // unused + return new GLStencilTableSSBO(stencilTable); + } + static GLStencilTableSSBO *Create(OpenSubdiv::Far::LimitStencilTable const *limitStencilTable, + void *deviceContext = NULL) + { + (void)deviceContext; // unused + return new GLStencilTableSSBO(limitStencilTable); + } + + explicit GLStencilTableSSBO(OpenSubdiv::Far::StencilTable const *stencilTable); + explicit GLStencilTableSSBO(OpenSubdiv::Far::LimitStencilTable const *limitStencilTable); + ~GLStencilTableSSBO(); + + // interfaces needed for GLSLComputeKernel + GLuint GetSizesBuffer() const + { + return _sizes; + } + GLuint GetOffsetsBuffer() const + { + return _offsets; + } + GLuint GetIndicesBuffer() const + { + return _indices; + } + GLuint GetWeightsBuffer() const + { + return _weights; + } + GLuint GetDuWeightsBuffer() const + { + return _duWeights; + } + GLuint GetDvWeightsBuffer() const + { + return _dvWeights; + } + GLuint GetDuuWeightsBuffer() const + { + return _duuWeights; + } + GLuint GetDuvWeightsBuffer() const + { + return _duvWeights; + } + GLuint GetDvvWeightsBuffer() const + { + return _dvvWeights; + } + int GetNumStencils() const + { + return _numStencils; + } + + private: + GLuint _sizes; + GLuint _offsets; + GLuint _indices; + GLuint _weights; + GLuint _duWeights; + GLuint _dvWeights; + GLuint _duuWeights; + GLuint _duvWeights; + GLuint _dvvWeights; + int _numStencils; +}; + +// --------------------------------------------------------------------------- + +class GLComputeEvaluator { + public: + typedef bool Instantiatable; + static GLComputeEvaluator *Create(OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + void *deviceContext = NULL) + { + return Create(srcDesc, + dstDesc, + duDesc, + dvDesc, + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor(), + deviceContext); + } + + static GLComputeEvaluator *Create(OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + void *deviceContext = NULL) + { + (void)deviceContext; // not used + GLComputeEvaluator *instance = new GLComputeEvaluator(); + if (instance->Compile(srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc)) + return instance; + delete instance; + return NULL; + } + + /// Constructor. + GLComputeEvaluator(); + + /// Destructor. note that the GL context must be made current. + ~GLComputeEvaluator(); + + /// ---------------------------------------------------------------------- + /// + /// Stencil evaluations with StencilTable + /// + /// ---------------------------------------------------------------------- + + /// \brief Generic static stencil function. This function has a same + /// signature as other device kernels have so that it can be called + /// transparently from OsdMesh template interface. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLSL kernel + /// + template + static bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + STENCIL_TABLE const *stencilTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalStencils(srcBuffer, srcDesc, dstBuffer, dstDesc, stencilTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, + dstDesc, + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor()); + if (instance) { + bool r = instance->EvalStencils(srcBuffer, srcDesc, dstBuffer, dstDesc, stencilTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic static stencil function. This function has a same + /// signature as other device kernels have so that it can be called + /// transparently from OsdMesh template interface. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLSL kernel + /// + template + static bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + STENCIL_TABLE const *stencilTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalStencils(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + stencilTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc); + if (instance) { + bool r = instance->EvalStencils(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + stencilTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic static stencil function. This function has a same + /// signature as other device kernels have so that it can be called + /// transparently from OsdMesh template interface. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLSL kernel + /// + template + static bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + STENCIL_TABLE const *stencilTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalStencils(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + stencilTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc); + if (instance) { + bool r = instance->EvalStencils(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + stencilTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic stencil function. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + template + bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + STENCIL_TABLE const *stencilTable) const + { + return EvalStencils(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + 0, + OpenSubdiv::Osd::BufferDescriptor(), + 0, + OpenSubdiv::Osd::BufferDescriptor(), + stencilTable->GetSizesBuffer(), + stencilTable->GetOffsetsBuffer(), + stencilTable->GetIndicesBuffer(), + stencilTable->GetWeightsBuffer(), + 0, + 0, + /* start = */ 0, + /* end = */ stencilTable->GetNumStencils()); + } + + /// \brief Generic stencil function. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + template + bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + STENCIL_TABLE const *stencilTable) const + { + return EvalStencils(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + stencilTable->GetSizesBuffer(), + stencilTable->GetOffsetsBuffer(), + stencilTable->GetIndicesBuffer(), + stencilTable->GetWeightsBuffer(), + stencilTable->GetDuWeightsBuffer(), + stencilTable->GetDvWeightsBuffer(), + /* start = */ 0, + /* end = */ stencilTable->GetNumStencils()); + } + + /// \brief Generic stencil function. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param stencilTable stencil table to be applied. The table must have + /// SSBO interfaces. + /// + template + bool EvalStencils(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + STENCIL_TABLE const *stencilTable) const + { + return EvalStencils(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + duuBuffer->BindVBO(), + duuDesc, + duvBuffer->BindVBO(), + duvDesc, + dvvBuffer->BindVBO(), + dvvDesc, + stencilTable->GetSizesBuffer(), + stencilTable->GetOffsetsBuffer(), + stencilTable->GetIndicesBuffer(), + stencilTable->GetWeightsBuffer(), + stencilTable->GetDuWeightsBuffer(), + stencilTable->GetDvWeightsBuffer(), + stencilTable->GetDuuWeightsBuffer(), + stencilTable->GetDuvWeightsBuffer(), + stencilTable->GetDvvWeightsBuffer(), + /* start = */ 0, + /* end = */ stencilTable->GetNumStencils()); + } + + /// \brief Dispatch the GLSL compute kernel on GPU asynchronously + /// returns false if the kernel hasn't been compiled yet. + /// + /// @param srcBuffer GL buffer of input primvar source data + /// + /// @param srcDesc vertex buffer descriptor for the srcBuffer + /// + /// @param dstBuffer GL buffer of output primvar destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer GL buffer of output derivative wrt u + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer GL buffer of output derivative wrt v + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param sizesBuffer GL buffer of the sizes in the stencil table + /// + /// @param offsetsBuffer GL buffer of the offsets in the stencil table + /// + /// @param indicesBuffer GL buffer of the indices in the stencil table + /// + /// @param weightsBuffer GL buffer of the weights in the stencil table + /// + /// @param duWeightsBuffer GL buffer of the du weights in the stencil table + /// + /// @param dvWeightsBuffer GL buffer of the dv weights in the stencil table + /// + /// @param start start index of stencil table + /// + /// @param end end index of stencil table + /// + bool EvalStencils(GLuint srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + GLuint dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + GLuint duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + GLuint dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + GLuint sizesBuffer, + GLuint offsetsBuffer, + GLuint indicesBuffer, + GLuint weightsBuffer, + GLuint duWeightsBuffer, + GLuint dvWeightsBuffer, + int start, + int end) const; + + /// \brief Dispatch the GLSL compute kernel on GPU asynchronously + /// returns false if the kernel hasn't been compiled yet. + /// + /// @param srcBuffer GL buffer of input primvar source data + /// + /// @param srcDesc vertex buffer descriptor for the srcBuffer + /// + /// @param dstBuffer GL buffer of output primvar destination data + /// + /// @param dstDesc vertex buffer descriptor for the dstBuffer + /// + /// @param duBuffer GL buffer of output derivative wrt u + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer GL buffer of output derivative wrt v + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer GL buffer of output 2nd derivative wrt u + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer GL buffer of output 2nd derivative wrt u and v + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer GL buffer of output 2nd derivative wrt v + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param sizesBuffer GL buffer of the sizes in the stencil table + /// + /// @param offsetsBuffer GL buffer of the offsets in the stencil table + /// + /// @param indicesBuffer GL buffer of the indices in the stencil table + /// + /// @param weightsBuffer GL buffer of the weights in the stencil table + /// + /// @param duWeightsBuffer GL buffer of the du weights in the stencil table + /// + /// @param dvWeightsBuffer GL buffer of the dv weights in the stencil table + /// + /// @param duuWeightsBuffer GL buffer of the duu weights in the stencil table + /// + /// @param duvWeightsBuffer GL buffer of the duv weights in the stencil table + /// + /// @param dvvWeightsBuffer GL buffer of the dvv weights in the stencil table + /// + /// @param start start index of stencil table + /// + /// @param end end index of stencil table + /// + bool EvalStencils(GLuint srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + GLuint dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + GLuint duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + GLuint dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + GLuint duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + GLuint duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + GLuint dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + GLuint sizesBuffer, + GLuint offsetsBuffer, + GLuint indicesBuffer, + GLuint weightsBuffer, + GLuint duWeightsBuffer, + GLuint dvWeightsBuffer, + GLuint duuWeightsBuffer, + GLuint duvWeightsBuffer, + GLuint dvvWeightsBuffer, + int start, + int end) const; + + /// ---------------------------------------------------------------------- + /// + /// Limit evaluations with PatchTable + /// + /// ---------------------------------------------------------------------- + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatches( + srcBuffer, srcDesc, dstBuffer, dstDesc, numPatchCoords, patchCoords, patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, + dstDesc, + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor()); + if (instance) { + bool r = instance->EvalPatches( + srcBuffer, srcDesc, dstBuffer, dstDesc, numPatchCoords, patchCoords, patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatches(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc); + if (instance) { + bool r = instance->EvalPatches(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatches(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc); + if (instance) { + bool r = instance->EvalPatches(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + 0, + OpenSubdiv::Osd::BufferDescriptor(), + 0, + OpenSubdiv::Osd::BufferDescriptor(), + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetPatchArrays(), + patchTable->GetPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + /// \brief Generic limit eval function with derivatives. This function has + /// a same signature as other device kernels have so that it can be + /// called in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetPatchArrays(), + patchTable->GetPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + /// \brief Generic limit eval function with derivatives. This function has + /// a same signature as other device kernels have so that it can be + /// called in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatches(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + duuBuffer->BindVBO(), + duuDesc, + duvBuffer->BindVBO(), + duvDesc, + dvvBuffer->BindVBO(), + dvvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetPatchArrays(), + patchTable->GetPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + bool EvalPatches(GLuint srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + GLuint dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + GLuint duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + GLuint dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + GLuint patchCoordsBuffer, + const OpenSubdiv::Osd::PatchArrayVector &patchArrays, + GLuint patchIndexBuffer, + GLuint patchParamsBuffer) const; + + bool EvalPatches(GLuint srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + GLuint dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + GLuint duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + GLuint dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + GLuint duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + GLuint duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + GLuint dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + GLuint patchCoordsBuffer, + const OpenSubdiv::Osd::PatchArrayVector &patchArrays, + GLuint patchIndexBuffer, + GLuint patchParamsBuffer) const; + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesVarying( + srcBuffer, srcDesc, dstBuffer, dstDesc, numPatchCoords, patchCoords, patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, + dstDesc, + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor()); + if (instance) { + bool r = instance->EvalPatchesVarying( + srcBuffer, srcDesc, dstBuffer, dstDesc, numPatchCoords, patchCoords, patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + 0, + OpenSubdiv::Osd::BufferDescriptor(), + 0, + OpenSubdiv::Osd::BufferDescriptor(), + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetVaryingPatchArrays(), + patchTable->GetVaryingPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc); + if (instance) { + bool r = instance->EvalPatchesVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetVaryingPatchArrays(), + patchTable->GetVaryingPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc); + if (instance) { + bool r = instance->EvalPatchesVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + template + bool EvalPatchesVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + duuBuffer->BindVBO(), + duuDesc, + duvBuffer->BindVBO(), + duvDesc, + dvvBuffer->BindVBO(), + dvvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetVaryingPatchArrays(), + patchTable->GetVaryingPatchIndexBuffer(), + patchTable->GetPatchParamBuffer()); + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, + dstDesc, + OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor()); + if (instance) { + bool r = instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + template + bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel = 0) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + 0, + OpenSubdiv::Osd::BufferDescriptor(), + 0, + OpenSubdiv::Osd::BufferDescriptor(), + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetFVarPatchArrays(fvarChannel), + patchTable->GetFVarPatchIndexBuffer(fvarChannel), + patchTable->GetFVarPatchParamBuffer(fvarChannel)); + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc); + if (instance) { + bool r = instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + template + bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel = 0) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetFVarPatchArrays(fvarChannel), + patchTable->GetFVarPatchIndexBuffer(fvarChannel), + patchTable->GetFVarPatchParamBuffer(fvarChannel)); + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + /// @param instance cached compiled instance. Clients are supposed to + /// pre-compile an instance of this class and provide + /// to this function. If it's null the kernel still + /// compute by instantiating on-demand kernel although + /// it may cause a performance problem. + /// + /// @param deviceContext not used in the GLXFB evaluator + /// + template + static bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel, + GLComputeEvaluator const *instance, + void *deviceContext = NULL) + { + + if (instance) { + return instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + } + else { + // Create an instance on demand (slow) + (void)deviceContext; // unused + instance = Create(srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc); + if (instance) { + bool r = instance->EvalPatchesFaceVarying(srcBuffer, + srcDesc, + dstBuffer, + dstDesc, + duBuffer, + duDesc, + dvBuffer, + dvDesc, + duuBuffer, + duuDesc, + duvBuffer, + duvDesc, + dvvBuffer, + dvvDesc, + numPatchCoords, + patchCoords, + patchTable, + fvarChannel); + delete instance; + return r; + } + return false; + } + } + + /// \brief Generic limit eval function. This function has a same + /// signature as other device kernels have so that it can be called + /// in the same way. + /// + /// @param srcBuffer Input primvar buffer. + /// must have BindVBO() method returning a GL + /// buffer object of source data + /// + /// @param srcDesc vertex buffer descriptor for the input buffer + /// + /// @param dstBuffer Output primvar buffer + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dstDesc vertex buffer descriptor for the output buffer + /// + /// @param duBuffer Output buffer derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duDesc vertex buffer descriptor for the duBuffer + /// + /// @param dvBuffer Output buffer derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvDesc vertex buffer descriptor for the dvBuffer + /// + /// @param duuBuffer Output buffer 2nd derivative wrt u + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duuDesc vertex buffer descriptor for the duuBuffer + /// + /// @param duvBuffer Output buffer 2nd derivative wrt u and v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param duvDesc vertex buffer descriptor for the duvBuffer + /// + /// @param dvvBuffer Output buffer 2nd derivative wrt v + /// must have BindVBO() method returning a GL + /// buffer object of destination data + /// + /// @param dvvDesc vertex buffer descriptor for the dvvBuffer + /// + /// @param numPatchCoords number of patchCoords. + /// + /// @param patchCoords array of locations to be evaluated. + /// must have BindVBO() method returning an + /// array of PatchCoord struct in VBO. + /// + /// @param patchTable GLPatchTable or equivalent + /// + /// @param fvarChannel face-varying channel + /// + template + bool EvalPatchesFaceVarying(SRC_BUFFER *srcBuffer, + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + DST_BUFFER *dstBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + DST_BUFFER *duBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + DST_BUFFER *dvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + DST_BUFFER *duuBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + DST_BUFFER *duvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + DST_BUFFER *dvvBuffer, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int numPatchCoords, + PATCHCOORD_BUFFER *patchCoords, + PATCH_TABLE *patchTable, + int fvarChannel = 0) const + { + + return EvalPatches(srcBuffer->BindVBO(), + srcDesc, + dstBuffer->BindVBO(), + dstDesc, + duBuffer->BindVBO(), + duDesc, + dvBuffer->BindVBO(), + dvDesc, + duuBuffer->BindVBO(), + duuDesc, + duvBuffer->BindVBO(), + duvDesc, + dvvBuffer->BindVBO(), + dvvDesc, + numPatchCoords, + patchCoords->BindVBO(), + patchTable->GetFVarPatchArrays(fvarChannel), + patchTable->GetFVarPatchIndexBuffer(fvarChannel), + patchTable->GetFVarPatchParamBuffer(fvarChannel)); + } + + /// ---------------------------------------------------------------------- + /// + /// Other methods + /// + /// ---------------------------------------------------------------------- + + /// Configure GLSL kernel. A valid GL context must be made current before + /// calling this function. Returns false if it fails to compile the kernel. + bool Compile( + OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + OpenSubdiv::Osd::BufferDescriptor const &duDesc = OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor const &dvDesc = OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor const &duuDesc = OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor const &duvDesc = OpenSubdiv::Osd::BufferDescriptor(), + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc = OpenSubdiv::Osd::BufferDescriptor()); + + /// Wait the dispatched kernel finishes. + static void Synchronize(void *deviceContext); + + private: + struct _StencilKernel { + _StencilKernel(); + ~_StencilKernel(); + bool Compile(OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int workGroupSize); + GLuint program; + GLuint uniformStart; + GLuint uniformEnd; + GLuint uniformSrcOffset; + GLuint uniformDstOffset; + GLuint uniformDuDesc; + GLuint uniformDvDesc; + GLuint uniformDuuDesc; + GLuint uniformDuvDesc; + GLuint uniformDvvDesc; + } _stencilKernel; + + struct _PatchKernel { + _PatchKernel(); + ~_PatchKernel(); + bool Compile(OpenSubdiv::Osd::BufferDescriptor const &srcDesc, + OpenSubdiv::Osd::BufferDescriptor const &dstDesc, + OpenSubdiv::Osd::BufferDescriptor const &duDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvDesc, + OpenSubdiv::Osd::BufferDescriptor const &duuDesc, + OpenSubdiv::Osd::BufferDescriptor const &duvDesc, + OpenSubdiv::Osd::BufferDescriptor const &dvvDesc, + int workGroupSize); + GLuint program; + GLuint uniformSrcOffset; + GLuint uniformDstOffset; + GLuint uniformPatchArray; + GLuint uniformDuDesc; + GLuint uniformDvDesc; + GLuint uniformDuuDesc; + GLuint uniformDuvDesc; + GLuint uniformDvvDesc; + } _patchKernel; + + int _workGroupSize; + GLuint _patchArraysSSBO; + + int GetDispatchSize(int count) const; + + void DispatchCompute(int totalDispatchSize) const; +}; +} // namespace opensubdiv +} // namespace blender + +#endif // OPENSUBDIV_GL_COMPUTE_EVALUATOR_H_ diff --git a/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl b/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl new file mode 100644 index 00000000000..2a58fa10ea0 --- /dev/null +++ b/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl @@ -0,0 +1,316 @@ +// +// Copyright 2013 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// + +//------------------------------------------------------------------------------ + + +layout(local_size_x=WORK_GROUP_SIZE, local_size_y=1, local_size_z=1) in; +layout(std430) buffer; + +// source and destination buffers + +uniform int srcOffset = 0; +uniform int dstOffset = 0; +layout(binding=0) buffer src_buffer { float srcVertexBuffer[]; }; +layout(binding=1) buffer dst_buffer { float dstVertexBuffer[]; }; + +// derivative buffers (if needed) + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) +uniform ivec3 duDesc; +uniform ivec3 dvDesc; +layout(binding=2) buffer du_buffer { float duBuffer[]; }; +layout(binding=3) buffer dv_buffer { float dvBuffer[]; }; +#endif + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) +uniform ivec3 duuDesc; +uniform ivec3 duvDesc; +uniform ivec3 dvvDesc; +layout(binding=10) buffer duu_buffer { float duuBuffer[]; }; +layout(binding=11) buffer duv_buffer { float duvBuffer[]; }; +layout(binding=12) buffer dvv_buffer { float dvvBuffer[]; }; +#endif + +// stencil buffers + +#if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS) + +uniform int batchStart = 0; +uniform int batchEnd = 0; +layout(binding=4) buffer stencilSizes { int _sizes[]; }; +layout(binding=5) buffer stencilOffsets { int _offsets[]; }; +layout(binding=6) buffer stencilIndices { int _indices[]; }; +layout(binding=7) buffer stencilWeights { float _weights[]; }; + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) +layout(binding=8) buffer stencilDuWeights { float _duWeights[]; }; +layout(binding=9) buffer stencilDvWeights { float _dvWeights[]; }; +#endif + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) +layout(binding=13) buffer stencilDuuWeights { float _duuWeights[]; }; +layout(binding=14) buffer stencilDuvWeights { float _duvWeights[]; }; +layout(binding=15) buffer stencilDvvWeights { float _dvvWeights[]; }; +#endif + +uint getGlobalInvocationIndex() +{ + uint invocations_per_row = gl_WorkGroupSize.x * gl_NumWorkGroups.x; + return gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * invocations_per_row; +} + +#endif + +// patch buffers + +#if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_PATCHES) + +layout(binding=4) buffer patchArray_buffer { OsdPatchArray patchArrayBuffer[]; }; +layout(binding=5) buffer patchCoord_buffer { OsdPatchCoord patchCoords[]; }; +layout(binding=6) buffer patchIndex_buffer { int patchIndexBuffer[]; }; +layout(binding=7) buffer patchParam_buffer { OsdPatchParam patchParamBuffer[]; }; + +OsdPatchCoord GetPatchCoord(int coordIndex) +{ + return patchCoords[coordIndex]; +} + +OsdPatchArray GetPatchArray(int arrayIndex) +{ + return patchArrayBuffer[arrayIndex]; +} + +OsdPatchParam GetPatchParam(int patchIndex) +{ + return patchParamBuffer[patchIndex]; +} + +#endif + +//------------------------------------------------------------------------------ + +struct Vertex { + float vertexData[LENGTH]; +}; + +void clear(out Vertex v) { + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] = 0; + } +} + +Vertex readVertex(int index) { + Vertex v; + int vertexIndex = srcOffset + index * SRC_STRIDE; + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] = srcVertexBuffer[vertexIndex + i]; + } + return v; +} + +void writeVertex(int index, Vertex v) { + int vertexIndex = dstOffset + index * DST_STRIDE; + for (int i = 0; i < LENGTH; ++i) { + dstVertexBuffer[vertexIndex + i] = v.vertexData[i]; + } +} + +void addWithWeight(inout Vertex v, const Vertex src, float weight) { + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] += weight * src.vertexData[i]; + } +} + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) +void writeDu(int index, Vertex du) { + int duIndex = duDesc.x + index * duDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duBuffer[duIndex + i] = du.vertexData[i]; + } +} + +void writeDv(int index, Vertex dv) { + int dvIndex = dvDesc.x + index * dvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + dvBuffer[dvIndex + i] = dv.vertexData[i]; + } +} +#endif + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) +void writeDuu(int index, Vertex duu) { + int duuIndex = duuDesc.x + index * duuDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duuBuffer[duuIndex + i] = duu.vertexData[i]; + } +} + +void writeDuv(int index, Vertex duv) { + int duvIndex = duvDesc.x + index * duvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duvBuffer[duvIndex + i] = duv.vertexData[i]; + } +} + +void writeDvv(int index, Vertex dvv) { + int dvvIndex = dvvDesc.x + index * dvvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + dvvBuffer[dvvIndex + i] = dvv.vertexData[i]; + } +} +#endif + +//------------------------------------------------------------------------------ +#if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS) + +void main() { + int current = int(getGlobalInvocationIndex()) + batchStart; + + if (current>=batchEnd) { + return; + } + + Vertex dst; + clear(dst); + + int offset = _offsets[current], + size = _sizes[current]; + + for (int stencil = 0; stencil < size; ++stencil) { + int vindex = offset + stencil; + addWithWeight( + dst, readVertex(_indices[vindex]), _weights[vindex]); + } + + writeVertex(current, dst); + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) + Vertex du, dv; + clear(du); + clear(dv); + for (int i=0; i 0) { // length + writeDu(current, du); + } + if (dvDesc.y > 0) { + writeDv(current, dv); + } +#endif +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) + Vertex duu, duv, dvv; + clear(duu); + clear(duv); + clear(dvv); + for (int i=0; i 0) { // length + writeDuu(current, duu); + } + if (duvDesc.y > 0) { + writeDuv(current, duv); + } + if (dvvDesc.y > 0) { + writeDvv(current, dvv); + } +#endif +} + +#endif + +//------------------------------------------------------------------------------ +#if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_PATCHES) + +// PERFORMANCE: stride could be constant, but not as significant as length + +void main() { + + int current = int(gl_GlobalInvocationID.x); + + OsdPatchCoord coord = GetPatchCoord(current); + OsdPatchArray array = GetPatchArray(coord.arrayIndex); + OsdPatchParam param = GetPatchParam(coord.patchIndex); + + int patchType = OsdPatchParamIsRegular(param) ? array.regDesc : array.desc; + + float wP[20], wDu[20], wDv[20], wDuu[20], wDuv[20], wDvv[20]; + int nPoints = OsdEvaluatePatchBasis(patchType, param, + coord.s, coord.t, wP, wDu, wDv, wDuu, wDuv, wDvv); + + Vertex dst, du, dv, duu, duv, dvv; + clear(dst); + clear(du); + clear(dv); + clear(duu); + clear(duv); + clear(dvv); + + int indexBase = array.indexBase + array.stride * + (coord.patchIndex - array.primitiveIdBase); + + for (int cv = 0; cv < nPoints; ++cv) { + int index = patchIndexBuffer[indexBase + cv]; + addWithWeight(dst, readVertex(index), wP[cv]); + addWithWeight(du, readVertex(index), wDu[cv]); + addWithWeight(dv, readVertex(index), wDv[cv]); + addWithWeight(duu, readVertex(index), wDuu[cv]); + addWithWeight(duv, readVertex(index), wDuv[cv]); + addWithWeight(dvv, readVertex(index), wDvv[cv]); + } + writeVertex(current, dst); + +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) + if (duDesc.y > 0) { // length + writeDu(current, du); + } + if (dvDesc.y > 0) { + writeDv(current, dv); + } +#endif +#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) + if (duuDesc.y > 0) { // length + writeDuu(current, duu); + } + if (duvDesc.y > 0) { // length + writeDuv(current, duv); + } + if (dvvDesc.y > 0) { + writeDvv(current, dvv); + } +#endif +} + +#endif -- cgit v1.2.3 From 68a09bf5fd2073d6e274ecd261f953435a35c3aa Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 17 Feb 2022 14:35:48 +0100 Subject: Cleanup/fix comment. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index c045082d569..cf11e303234 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -330,7 +330,7 @@ enum { /* 2 characters for ID code and 64 for actual name */ #define MAX_ID_NAME 66 -/* ID_Runtime.remapping_status */ +/* ID_Runtime_Remap.status */ enum { /** new_id is directly linked in current .blend. */ ID_REMAP_IS_LINKED_DIRECT = 1 << 0, -- cgit v1.2.3 From 45412493603e41526cac4076d46527c538c99985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Thu, 17 Feb 2022 15:15:46 +0100 Subject: Fix compile error on MSVC `uint` is POSIX type, use `GLuint` like for the rest of the code. --- intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc index 0cab44518aa..ee4eaeaec5d 100644 --- a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc +++ b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc @@ -267,11 +267,11 @@ void GLComputeEvaluator::DispatchCompute(int totalDispatchSize) const glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &maxWorkGroupCount[0]); glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &maxWorkGroupCount[1]); - const uint maxResX = static_cast(maxWorkGroupCount[0]); + const GLuint maxResX = static_cast(maxWorkGroupCount[0]); const int dispatchSize = GetDispatchSize(totalDispatchSize); - uint dispatchRX = static_cast(dispatchSize); - uint dispatchRY = 1u; + GLuint dispatchRX = static_cast(dispatchSize); + GLuint dispatchRY = 1u; if (dispatchRX > maxResX) { /* Since there are some limitations with regards to the maximum work group size (could be as * low as 64k elements per call), we split the number elements into a "2d" number, with the @@ -290,7 +290,7 @@ void GLComputeEvaluator::DispatchCompute(int totalDispatchSize) const /* X and Y dimensions may have different limits so the above computation may not be right, but * even with the standard 64k minimum on all dimensions we still have a lot of room. Therefore, * we presume it all fits. */ - assert(dispatchRY < static_cast(maxWorkGroupCount[1])); + assert(dispatchRY < static_cast(maxWorkGroupCount[1])); glDispatchCompute(dispatchRX, dispatchRY, 1); } -- cgit v1.2.3 From cd7550cfe712598727b548d1c90565ac98a274c2 Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Wed, 16 Feb 2022 17:36:47 +0100 Subject: Images: update code to support OpenEXR 3 Compatibility with OpenEXR 2 is preserved, since Blender releases and Linux distribution packages can be on different versions. Ref D14128 --- .../blender/imbuf/intern/openexr/openexr_api.cpp | 84 +++++++++++++--------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index ec8cf36dd49..46abb986259 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -32,30 +32,46 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +/* The OpenEXR version can reliably be found in this header file from OpenEXR, + * for both 2.x and 3.x: + */ +#include +#define COMBINED_OPENEXR_VERSION \ + ((10000 * OPENEXR_VERSION_MAJOR) + (100 * OPENEXR_VERSION_MINOR) + OPENEXR_VERSION_PATCH) + +#if COMBINED_OPENEXR_VERSION >= 20599 +/* >=2.5.99 -> OpenEXR >=3.0 */ +# include +# include +# define exr_file_offset_t uint64_t +#else +/* OpenEXR 2.x, use the old locations. */ +# include +# define exr_file_offset_t Int64 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* multiview/multipart */ -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include "DNA_scene_types.h" /* For OpenEXR compression constants */ @@ -131,12 +147,12 @@ class IMemStream : public Imf::IStream { return false; } - Int64 tellg() override + exr_file_offset_t tellg() override { return _exrpos; } - void seekg(Int64 pos) override + void seekg(exr_file_offset_t pos) override { _exrpos = pos; } @@ -146,8 +162,8 @@ class IMemStream : public Imf::IStream { } private: - Int64 _exrpos; - Int64 _exrsize; + exr_file_offset_t _exrpos; + exr_file_offset_t _exrsize; unsigned char *_exrbuf; }; @@ -182,12 +198,12 @@ class IFileStream : public Imf::IStream { return check_error(); } - Int64 tellg() override + exr_file_offset_t tellg() override { return std::streamoff(ifs.tellg()); } - void seekg(Int64 pos) override + void seekg(exr_file_offset_t pos) override { ifs.seekg(pos); check_error(); @@ -231,19 +247,19 @@ class OMemStream : public OStream { ibuf->encodedsize += n; } - Int64 tellp() override + exr_file_offset_t tellp() override { return offset; } - void seekp(Int64 pos) override + void seekp(exr_file_offset_t pos) override { offset = pos; ensure_size(offset); } private: - void ensure_size(Int64 size) + void ensure_size(exr_file_offset_t size) { /* if buffer is too small increase it. */ while (size > ibuf->encodedbuffersize) { @@ -254,7 +270,7 @@ class OMemStream : public OStream { } ImBuf *ibuf; - Int64 offset; + exr_file_offset_t offset; }; /* File Output Stream */ @@ -284,12 +300,12 @@ class OFileStream : public OStream { check_error(); } - Int64 tellp() override + exr_file_offset_t tellp() override { return std::streamoff(ofs.tellp()); } - void seekp(Int64 pos) override + void seekp(exr_file_offset_t pos) override { ofs.seekp(pos); check_error(); -- cgit v1.2.3 From 2c9931699e2080639bb836357e116b8e340335d9 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 16 Feb 2022 17:38:44 +0100 Subject: Build: update CMake to support OpenEXR 3 FindOpenEXR was updated to find new lib names and separate Imath. It's all added to the list of OpenEXR include dirs and libs. This keeps it compatible with both version 2 and 3 for now, and doesn't require changes outside the find module. Ref D14128 --- build_files/cmake/Modules/FindOpenEXR.cmake | 93 +++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/build_files/cmake/Modules/FindOpenEXR.cmake b/build_files/cmake/Modules/FindOpenEXR.cmake index 090f80b8df7..85e2cb55705 100644 --- a/build_files/cmake/Modules/FindOpenEXR.cmake +++ b/build_files/cmake/Modules/FindOpenEXR.cmake @@ -33,14 +33,6 @@ ENDIF() # Old versions (before 2.0?) do not have any version string, just assuming this should be fine though. SET(_openexr_libs_ver_init "2.0") -SET(_openexr_FIND_COMPONENTS - Half - Iex - IlmImf - IlmThread - Imath -) - SET(_openexr_SEARCH_DIRS ${OPENEXR_ROOT_DIR} /opt/lib/openexr @@ -93,6 +85,24 @@ UNSET(_openexr_libs_ver_init) STRING(REGEX REPLACE "([0-9]+)[.]([0-9]+).*" "\\1_\\2" _openexr_libs_ver ${OPENEXR_VERSION}) +# Different library names in 3.0, and Imath and Half moved out. +IF(OPENEXR_VERSION VERSION_GREATER_EQUAL "3.0.0") + SET(_openexr_FIND_COMPONENTS + Iex + IlmThread + OpenEXR + OpenEXRCore + ) +ELSE() + SET(_openexr_FIND_COMPONENTS + Half + Iex + IlmImf + IlmThread + Imath + ) +ENDIF() + SET(_openexr_LIBRARIES) FOREACH(COMPONENT ${_openexr_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) @@ -111,6 +121,57 @@ ENDFOREACH() UNSET(_openexr_libs_ver) +IF(OPENEXR_VERSION VERSION_GREATER_EQUAL "3.0.0") + # For OpenEXR 3.x, we also need to find the now separate Imath library. + # For simplicity we add it to the OpenEXR includes and libraries, as we + # have no direct dependency on Imath and it's simpler to support both + # 2.x and 3.x this way. + + # Find include directory + FIND_PATH(IMATH_INCLUDE_DIR + NAMES + Imath/ImathMath.h + HINTS + ${_openexr_SEARCH_DIRS} + PATH_SUFFIXES + include + ) + + # Find version + FIND_FILE(_imath_config + NAMES + ImathConfig.h + PATHS + ${IMATH_INCLUDE_DIR}/Imath + NO_DEFAULT_PATH + ) + + # Find line with version, extract string, and format for library suffix. + FILE(STRINGS "${_imath_config}" _imath_build_specification + REGEX "^[ \t]*#define[ \t]+IMATH_VERSION_STRING[ \t]+\"[.0-9]+\".*$") + STRING(REGEX REPLACE ".*#define[ \t]+IMATH_VERSION_STRING[ \t]+\"([.0-9]+)\".*" + "\\1" _imath_libs_ver ${_imath_build_specification}) + STRING(REGEX REPLACE "([0-9]+)[.]([0-9]+).*" "\\1_\\2" _imath_libs_ver ${_imath_libs_ver}) + + # Find library, with or without version number. + FIND_LIBRARY(IMATH_LIBRARY + NAMES + Imath-${_imath_libs_ver} Imath + NAMES_PER_DIR + HINTS + ${_openexr_SEARCH_DIRS} + PATH_SUFFIXES + lib64 lib + ) + LIST(APPEND _openexr_LIBRARIES "${IMATH_LIBRARY}") + + # In cmake version 3.21 and up, we can instead use the NO_CACHE option for + # FIND_FILE so we don't need to clear it from the cache here. + UNSET(_imath_config CACHE) + UNSET(_imath_libs_ver) + UNSET(_imath_build_specification) +ENDIF() + # handle the QUIETLY and REQUIRED arguments and set OPENEXR_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) @@ -119,13 +180,25 @@ FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR DEFAULT_MSG IF(OPENEXR_FOUND) SET(OPENEXR_LIBRARIES ${_openexr_LIBRARIES}) - # Both include paths are needed because of dummy OSL headers mixing #include and #include :( - SET(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${OPENEXR_INCLUDE_DIR}/OpenEXR) + # Both include paths are needed because of dummy OSL headers mixing + # #include and #include , as well as Alembic + # include directly. + SET(OPENEXR_INCLUDE_DIRS + ${OPENEXR_INCLUDE_DIR} + ${OPENEXR_INCLUDE_DIR}/OpenEXR) + + IF(OPENEXR_VERSION VERSION_GREATER_EQUAL "3.0.0") + LIST(APPEND OPENEXR_INCLUDE_DIRS + ${IMATH_INCLUDE_DIR} + ${IMATH_INCLUDE_DIR}/Imath) + ENDIF() ENDIF() MARK_AS_ADVANCED( OPENEXR_INCLUDE_DIR OPENEXR_VERSION + IMATH_INCLUDE_DIR + IMATH_LIBRARY ) FOREACH(COMPONENT ${_openexr_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) -- cgit v1.2.3 From e5100ca3ad17b1b9a40ffd8a8edccb6cb553e558 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 16 Feb 2022 17:40:20 +0100 Subject: Build: update CMake to support OpenImageIO 2.3.4 FindOpenImageIO was updated to link to separate OpenImageIO_Util for new versions, where it is required. For older versions, we can not link to it because there will be duplicated symbols. Ref D14128 --- build_files/cmake/Modules/FindOpenImageIO.cmake | 41 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/build_files/cmake/Modules/FindOpenImageIO.cmake b/build_files/cmake/Modules/FindOpenImageIO.cmake index aac5b5ce0a8..54b6d95e809 100644 --- a/build_files/cmake/Modules/FindOpenImageIO.cmake +++ b/build_files/cmake/Modules/FindOpenImageIO.cmake @@ -48,6 +48,8 @@ FIND_LIBRARY(OPENIMAGEIO_LIBRARY lib64 lib ) +set(_openimageio_LIBRARIES ${OPENIMAGEIO_LIBRARY}) + FIND_FILE(OPENIMAGEIO_IDIFF NAMES idiff @@ -57,14 +59,47 @@ FIND_FILE(OPENIMAGEIO_IDIFF bin ) +# Additionally find util library if needed. In old versions this library was +# included in libOpenImageIO and linking to both would duplicate symbols. In +# new versions we need to link to both. +FIND_FILE(_openimageio_export + NAMES + export.h + PATHS + ${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO + NO_DEFAULT_PATH +) + +# Use existence of OIIO_UTIL_API to check if it's a separate lib. +FILE(STRINGS "${_openimageio_export}" _openimageio_util_define + REGEX "^[ \t]*#[ \t]*define[ \t]+OIIO_UTIL_API.*$") + +IF(_openimageio_util_define) + FIND_LIBRARY(OPENIMAGEIO_UTIL_LIBRARY + NAMES + OpenImageIO_Util + HINTS + ${_openimageio_SEARCH_DIRS} + PATH_SUFFIXES + lib64 lib + ) + + LIST(APPEND _openimageio_LIBRARIES ${OPENIMAGEIO_UTIL_LIBRARY}) +ENDIF() + +# In cmake version 3.21 and up, we can instead use the NO_CACHE option for +# FIND_FILE so we don't need to clear it from the cache here. +UNSET(_openimageio_export CACHE) +UNSET(_openimageio_util_define) + # handle the QUIETLY and REQUIRED arguments and set OPENIMAGEIO_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenImageIO DEFAULT_MSG - OPENIMAGEIO_LIBRARY OPENIMAGEIO_INCLUDE_DIR) + _openimageio_LIBRARIES OPENIMAGEIO_INCLUDE_DIR) IF(OPENIMAGEIO_FOUND) - SET(OPENIMAGEIO_LIBRARIES ${OPENIMAGEIO_LIBRARY}) + SET(OPENIMAGEIO_LIBRARIES ${_openimageio_LIBRARIES}) SET(OPENIMAGEIO_INCLUDE_DIRS ${OPENIMAGEIO_INCLUDE_DIR}) IF(EXISTS ${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO/pugixml.hpp) SET(OPENIMAGEIO_PUGIXML_FOUND TRUE) @@ -78,7 +113,9 @@ ENDIF() MARK_AS_ADVANCED( OPENIMAGEIO_INCLUDE_DIR OPENIMAGEIO_LIBRARY + OPENIMAGEIO_UTIL_LIBRARY OPENIMAGEIO_IDIFF ) UNSET(_openimageio_SEARCH_DIRS) +UNSET(_openimageio_LIBRARIES) -- cgit v1.2.3 From e240c8c5dbfffca7edde50c3ee09be3967920ca5 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Sun, 13 Feb 2022 10:08:23 -0300 Subject: Camera: Simplify View Frame code - No need for `normal_tx` array if we normalize the planes in `plane_tx`. - No need to calculate the distance squared to a plane (with `dist_signed_squared_to_plane_v3`) if the plane is normalized. `plane_point_side_v3` gets the real distance, accurately, efficiently and also signed. So normalize the planes of the member `CameraViewFrameData::plane_tx`. --- source/blender/blenkernel/intern/camera.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 57a95891a92..b840fb1e665 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -550,9 +550,8 @@ void BKE_camera_view_frame(const Scene *scene, const Camera *camera, float r_vec #define CAMERA_VIEWFRAME_NUM_PLANES 4 typedef struct CameraViewFrameData { - float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes */ - float normal_tx[CAMERA_VIEWFRAME_NUM_PLANES][3]; - float dist_vals_sq[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance squared (signed) */ + float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes normalized */ + float dist_vals[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance (signed) */ unsigned int tot; /* Ortho camera only. */ @@ -569,8 +568,8 @@ static void camera_to_frame_view_cb(const float co[3], void *user_data) CameraViewFrameData *data = (CameraViewFrameData *)user_data; for (uint i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { - const float nd = dist_signed_squared_to_plane_v3(co, data->plane_tx[i]); - CLAMP_MAX(data->dist_vals_sq[i], nd); + const float nd = plane_point_side_v3(data->plane_tx[i], co); + CLAMP_MAX(data->dist_vals[i], nd); } if (data->is_ortho) { @@ -625,10 +624,11 @@ static void camera_frame_fit_data_init(const Scene *scene, /* Rotate planes and get normals from them */ for (uint i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { mul_m4_v4(camera_rotmat_transposed_inversed, data->plane_tx[i]); - normalize_v3_v3(data->normal_tx[i], data->plane_tx[i]); + /* Normalize. */ + data->plane_tx[i][3] /= normalize_v3(data->plane_tx[i]); } - copy_v4_fl(data->dist_vals_sq, FLT_MAX); + copy_v4_fl(data->dist_vals, FLT_MAX); data->tot = 0; data->is_ortho = params->is_ortho; if (params->is_ortho) { @@ -653,14 +653,9 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, const float *cam_axis_x = data->camera_rotmat[0]; const float *cam_axis_y = data->camera_rotmat[1]; const float *cam_axis_z = data->camera_rotmat[2]; - float dists[CAMERA_VIEWFRAME_NUM_PLANES]; + const float *dists = data->dist_vals; float scale_diff; - /* apply the dist-from-plane's to the transformed plane points */ - for (int i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { - dists[i] = sqrtf_signed(data->dist_vals_sq[i]); - } - if ((dists[0] + dists[2]) > (dists[1] + dists[3])) { scale_diff = (dists[1] + dists[3]) * (BLI_rctf_size_x(¶ms->viewplane) / BLI_rctf_size_y(¶ms->viewplane)); @@ -687,8 +682,8 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, /* apply the dist-from-plane's to the transformed plane points */ for (int i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { float co[3]; - mul_v3_v3fl(co, data->normal_tx[i], sqrtf_signed(data->dist_vals_sq[i])); - plane_from_point_normal_v3(plane_tx[i], co, data->normal_tx[i]); + mul_v3_v3fl(co, data->plane_tx[i], data->dist_vals[i]); + plane_from_point_normal_v3(plane_tx[i], co, data->plane_tx[i]); } if ((!isect_plane_plane_v3(plane_tx[0], plane_tx[2], plane_isect_1, plane_isect_1_no)) || -- cgit v1.2.3 From 114cc47b78769e5aac45f6234eabe5fabc762aed Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 17 Feb 2022 09:04:58 -0600 Subject: Fix: Memory leak in recently added curves copy function Specify that the destination curve must be initialized, and free the existing attributes (which `CustomData_copy` doesn't do). --- source/blender/blenkernel/intern/curves_geometry.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 38cf3d351bd..ac40b7a7aa1 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -48,8 +48,13 @@ CurvesGeometry::CurvesGeometry(const int point_size, const int curve_size) this->runtime = MEM_new(__func__); } +/** + * \note Expects `dst` to be initialized, since the original attributes must be freed. + */ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) { + CustomData_free(&dst.point_data, dst.point_size); + CustomData_free(&dst.curve_data, dst.curve_size); dst.point_size = src.point_size; dst.curve_size = src.curve_size; CustomData_copy(&src.point_data, &dst.point_data, CD_MASK_ALL, CD_DUPLICATE, dst.point_size); -- cgit v1.2.3 From c99d1d5d0db8ac7af0d479341e1bd87ead681387 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 17 Feb 2022 16:15:31 +0100 Subject: Fix build errors on Linux/clang after recent changes --- source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc | 1 + source/blender/nodes/function/nodes/node_fn_compare.cc | 1 + source/blender/nodes/function/nodes/node_fn_input_color.cc | 2 ++ 3 files changed, 4 insertions(+) diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index 15e7163d7db..162ef07a6dd 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -10,6 +10,7 @@ #include "BLI_assert.h" #include "BLI_dynstr.h" #include "BLI_hash_mm3.h" +#include "BLI_math_vector.h" #include "BLI_string_ref.hh" #include "BLI_utildefines.h" diff --git a/source/blender/nodes/function/nodes/node_fn_compare.cc b/source/blender/nodes/function/nodes/node_fn_compare.cc index 2e2e5227a65..e6fdf1820fa 100644 --- a/source/blender/nodes/function/nodes/node_fn_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_compare.cc @@ -3,6 +3,7 @@ #include #include "BLI_listbase.h" +#include "BLI_math_vector.h" #include "BLI_string.h" #include "UI_interface.h" diff --git a/source/blender/nodes/function/nodes/node_fn_input_color.cc b/source/blender/nodes/function/nodes/node_fn_input_color.cc index f76bb904153..46787f7575d 100644 --- a/source/blender/nodes/function/nodes/node_fn_input_color.cc +++ b/source/blender/nodes/function/nodes/node_fn_input_color.cc @@ -2,6 +2,8 @@ #include "node_function_util.hh" +#include "BLI_math_vector.h" + #include "UI_interface.h" #include "UI_resources.h" -- cgit v1.2.3 From b5e3700b793f730b404315493ceadb60e4af708d Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 17 Feb 2022 17:07:25 +0100 Subject: Cleanup: Replace direct `id.lib` pointer checks with `ID_IS_LINKED` macro usages. --- source/blender/blenkernel/intern/brush.c | 2 +- source/blender/blenkernel/intern/lib_id_delete.c | 4 ++-- source/blender/blenkernel/intern/lib_override.c | 2 +- source/blender/blenloader/intern/versioning_common.cc | 2 +- source/blender/editors/util/ed_util_ops.cc | 2 +- source/blender/makesdna/DNA_scene_types.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index f091ebe1e32..4531783c72a 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -147,7 +147,7 @@ static void brush_make_local(Main *bmain, ID *id, const int flags) /* 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); + BLI_assert(!ID_IS_LINKED(brush->clone.image) && brush->clone.image->id.newid == NULL); } if (force_local) { diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c index cf25af1c637..ba5556c8b2d 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.c +++ b/source/blender/blenkernel/intern/lib_id_delete.c @@ -234,7 +234,7 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion) for (id = lb->first; id; id = id_next) { id_next = id->next; /* NOTE: in case we delete a library, we also delete all its datablocks! */ - if ((id->tag & tag) || (id->lib != NULL && (id->lib->id.tag & tag))) { + if ((id->tag & tag) || (ID_IS_LINKED(id) && (id->lib->id.tag & tag))) { BLI_remlink(lb, id); BLI_addtail(&tagged_deleted_ids, id); /* Do not tag as no_main now, we want to unlink it first (lower-level ID management @@ -290,7 +290,7 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion) for (id = lb->first; id; id = id_next) { id_next = id->next; /* NOTE: in case we delete a library, we also delete all its datablocks! */ - if ((id->tag & tag) || (id->lib != NULL && (id->lib->id.tag & tag))) { + if ((id->tag & tag) || (ID_IS_LINKED(id) && (id->lib->id.tag & tag))) { id->tag |= tag; BKE_id_remapper_add(remapper, id, NULL); } diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 02cdd6fcd20..8a988bd30b5 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -2127,7 +2127,7 @@ static void lib_override_library_main_resync_on_library_indirect_level( "ID override %s from library level %d still found as needing resync, when all " "IDs from that level should have been processed after tackling library level %d", id->name, - id->lib != NULL ? id->lib->temp_index : 0, + ID_IS_LINKED(id) ? id->lib->temp_index : 0, library_indirect_level); id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; } diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 6aac76642d5..281769410bd 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -56,7 +56,7 @@ ID *do_versions_rename_id(Main *bmain, ListBase *lb = which_libbase(bmain, id_type); ID *id = nullptr; LISTBASE_FOREACH (ID *, idtest, lb) { - if (idtest->lib == nullptr) { + if (!ID_IS_LINKED(idtest)) { if (STREQ(idtest->name + 2, name_src)) { id = idtest; } diff --git a/source/blender/editors/util/ed_util_ops.cc b/source/blender/editors/util/ed_util_ops.cc index 014944da916..25deacbcdd1 100644 --- a/source/blender/editors/util/ed_util_ops.cc +++ b/source/blender/editors/util/ed_util_ops.cc @@ -226,7 +226,7 @@ static int lib_id_fake_user_toggle_exec(bContext *C, wmOperator *op) ID *id = (ID *)idptr.data; - if ((id->lib != nullptr) || (ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS))) { + if (ID_IS_LINKED(id) || (ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS))) { BKE_report(op->reports, RPT_ERROR, "Data-block type does not support fake user"); return OPERATOR_CANCELLED; } diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 0d42abdb363..672af019177 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1988,7 +1988,7 @@ extern const char *RE_engine_id_CYCLES; ((v3d == NULL) || (((1 << (base)->object->type) & (v3d)->object_type_exclude_select) == 0)) && \ (((base)->flag & BASE_SELECTABLE) != 0)) #define BASE_SELECTED(v3d, base) (BASE_VISIBLE(v3d, base) && (((base)->flag & BASE_SELECTED) != 0)) -#define BASE_EDITABLE(v3d, base) (BASE_VISIBLE(v3d, base) && ((base)->object->id.lib == NULL)) +#define BASE_EDITABLE(v3d, base) (BASE_VISIBLE(v3d, base) && !ID_IS_LINKED((base)->object)) #define BASE_SELECTED_EDITABLE(v3d, base) \ (BASE_EDITABLE(v3d, base) && (((base)->flag & BASE_SELECTED) != 0)) -- cgit v1.2.3 From 9281ba5812c2f6d155649aae6142b06bf00e84f7 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 17 Feb 2022 17:21:06 +0100 Subject: Fix part of T95654: Cycles crash with text objects in excluded view layers This is a bug on the Blender side, where the depsgraph does not have proper relations for text object duplis and fails to include the required materials in the dependency graph. But at least Cycles should not crash. --- intern/cycles/blender/shader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 39e49ac3478..418393c2be7 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -45,7 +45,8 @@ typedef map ProxyMap; void BlenderSync::find_shader(BL::ID &id, array &used_shaders, Shader *default_shader) { - Shader *shader = (id) ? shader_map.find(id) : default_shader; + Shader *synced_shader = (id) ? shader_map.find(id) : nullptr; + Shader *shader = (synced_shader) ? synced_shader : default_shader; used_shaders.push_back_slow(shader); shader->tag_used(scene); -- cgit v1.2.3 From 37fa1bc254b880d9e8477aec28520e0bdb25b734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 17 Feb 2022 17:03:23 +0100 Subject: OCIO: Port shader creation logic to use GPUShaderCreateInfo This commit should suffice to make the shader API agnostic now (given that all users of it use the GPU API). This makes the shaders not trigger a false positive error anymore since the binding slots are now garanteed by the backend and not changed at after compilation. This also bundles all uniforms into UBOs. Making them extendable without limitations of push constants. The generated uniforms from OCIO are not densely packed in the UBO to avoid complexity. Another approach would be to use GPU_uniformbuf_create_from_list but this requires converting uniforms to GPUInputs which is too complex for what it is. Reviewed by: brecht, jbakker Differential Revision: https://developer.blender.org/D14123 --- intern/opencolorio/CMakeLists.txt | 36 ++- .../opencolorio/gpu_shader_display_transform.glsl | 210 -------------- .../gpu_shader_display_transform_frag.glsl | 195 +++++++++++++ .../gpu_shader_display_transform_vert.glsl | 6 + .../gpu_shader_display_transform_vertex.glsl | 12 - intern/opencolorio/ocio_impl_glsl.cc | 323 ++++++++++++--------- intern/opencolorio/ocio_shader_shared.hh | 41 +++ source/blender/gpu/CMakeLists.txt | 9 + source/blender/gpu/intern/gpu_shader_dependency.cc | 6 + 9 files changed, 482 insertions(+), 356 deletions(-) delete mode 100644 intern/opencolorio/gpu_shader_display_transform.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_frag.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_vert.glsl delete mode 100644 intern/opencolorio/gpu_shader_display_transform_vertex.glsl create mode 100644 intern/opencolorio/ocio_shader_shared.hh diff --git a/intern/opencolorio/CMakeLists.txt b/intern/opencolorio/CMakeLists.txt index dfccb9301ac..be6ccc5c2c5 100644 --- a/intern/opencolorio/CMakeLists.txt +++ b/intern/opencolorio/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../guardedalloc ../../source/blender/blenlib ../../source/blender/gpu + ../../source/blender/gpu/intern ../../source/blender/makesdna ) @@ -20,6 +21,7 @@ set(SRC ocio_capi.h ocio_impl.h + ocio_shader_shared.hh ) set(LIB @@ -56,8 +58,38 @@ if(WITH_OPENCOLORIO) ) endif() - data_to_c_simple(gpu_shader_display_transform.glsl SRC) - data_to_c_simple(gpu_shader_display_transform_vertex.glsl SRC) + set(GLSL_SRC + gpu_shader_display_transform_vert.glsl + gpu_shader_display_transform_frag.glsl + + ocio_shader_shared.hh + ) + + set(GLSL_C) + foreach(GLSL_FILE ${GLSL_SRC}) + data_to_c_simple(${GLSL_FILE} GLSL_C) + endforeach() + + blender_add_lib(bf_ocio_shaders "${GLSL_C}" "" "" "") + + list(APPEND LIB + bf_ocio_shaders + ) + + set(GLSL_SOURCE_CONTENT "") + foreach(GLSL_FILE ${GLSL_SRC}) + get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME) + string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME}) + string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(datatoc_${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n") + endforeach() + + set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_ocio_source_list.h") + file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}") + list(APPEND SRC ${glsl_source_list_file}) + list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR}) + + target_include_directories(bf_ocio_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + endif() diff --git a/intern/opencolorio/gpu_shader_display_transform.glsl b/intern/opencolorio/gpu_shader_display_transform.glsl deleted file mode 100644 index f5a7a7bf45d..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform.glsl +++ /dev/null @@ -1,210 +0,0 @@ -/* Blender OpenColorIO implementation */ - -uniform sampler2D image_texture; -uniform sampler2D overlay_texture; - -uniform float dither; -uniform float scale; -uniform float exponent; -uniform bool predivide; -uniform bool overlay; - -#ifdef USE_CURVE_MAPPING -uniform sampler1D curve_mapping_texture; - -layout(std140) uniform OCIO_GPUCurveMappingParameters -{ - /* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ - vec4 curve_mapping_mintable; - vec4 curve_mapping_range; - vec4 curve_mapping_ext_in_x; - vec4 curve_mapping_ext_in_y; - vec4 curve_mapping_ext_out_x; - vec4 curve_mapping_ext_out_y; - vec4 curve_mapping_first_x; - vec4 curve_mapping_first_y; - vec4 curve_mapping_last_x; - vec4 curve_mapping_last_y; - vec4 curve_mapping_black; - vec4 curve_mapping_bwmul; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; -}; - -float read_curve_mapping(int table, int index) -{ - return texelFetch(curve_mapping_texture, index, 0)[table]; -} - -float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) -{ - if (x <= first[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return first[1]; - } - else { - float fac = (curve_mapping_ext_in_x[table] != 0.0) ? - ((x - first[0]) / curve_mapping_ext_in_x[table]) : - 10000.0; - return first[1] + curve_mapping_ext_in_y[table] * fac; - } - } - else if (x >= last[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return last[1]; - } - else { - float fac = (curve_mapping_ext_out_x[table] != 0.0) ? - ((x - last[0]) / curve_mapping_ext_out_x[table]) : - -10000.0; - return last[1] + curve_mapping_ext_out_y[table] * fac; - } - } - return 0.0; -} - -float curvemap_evaluateF(int table, float value) -{ - float mintable_ = curve_mapping_mintable[table]; - float range = curve_mapping_range[table]; - float mintable = 0.0; - int CM_TABLE = curve_mapping_lut_size - 1; - - float fi; - int i; - - /* index in table */ - fi = (value - mintable) * range; - i = int(fi); - - /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ - if (fi < 0.0 || fi > float(CM_TABLE)) { - return curvemap_calc_extend(table, - value, - vec2(curve_mapping_first_x[table], curve_mapping_first_y[table]), - vec2(curve_mapping_last_x[table], curve_mapping_last_y[table])); - } - else { - if (i < 0) { - return read_curve_mapping(table, 0); - } - if (i >= CM_TABLE) { - return read_curve_mapping(table, CM_TABLE); - } - fi = fi - float(i); - float cm1 = read_curve_mapping(table, i); - float cm2 = read_curve_mapping(table, i + 1); - return mix(cm1, cm2, fi); - } -} - -vec4 curvemapping_evaluate_premulRGBF(vec4 col) -{ - col.rgb = (col.rgb - curve_mapping_black.rgb) * curve_mapping_bwmul.rgb; - - vec4 result; - result.r = curvemap_evaluateF(0, col.r); - result.g = curvemap_evaluateF(1, col.g); - result.b = curvemap_evaluateF(2, col.b); - result.a = col.a; - return result; -} -#endif /* USE_CURVE_MAPPING */ - -/* Using a triangle distribution which gives a more final uniform noise. - * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ -/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ -/* Return triangle noise in [-1..1[ range */ -float dither_random_value(vec2 co) -{ - /* Original code from https://www.shadertoy.com/view/4t2SDh */ - /* Uniform noise in [0..1[ range */ - float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); - /* Convert uniform distribution into triangle-shaped distribution. */ - float orig = nrnd0 * 2.0 - 1.0; - nrnd0 = orig * inversesqrt(abs(orig)); - nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ - return nrnd0 - sign(orig); -} - -vec2 round_to_pixel(sampler2D tex, vec2 uv) -{ - vec2 size = textureSize(tex, 0); - return vec2(ivec2(uv * size)) / size; -} - -vec4 apply_dither(vec4 col, vec2 uv) -{ - col.rgb += dither_random_value(uv) * 0.0033 * dither; - return col; -} - -vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay, vec2 noise_uv) -{ -#ifdef USE_CURVE_MAPPING - col = curvemapping_evaluate_premulRGBF(col); -#endif - - if (predivide) { - if (col.a > 0.0 && col.a < 1.0) { - col.rgb *= 1.0 / col.a; - } - } - - /* NOTE: This is true we only do de-premul here and NO premul - * and the reason is simple -- opengl is always configured - * for straight alpha at this moment - */ - - /* Convert to scene linear (usually a no-op). */ - col = OCIO_to_scene_linear(col); - - /* Apply exposure in scene linear. */ - col.rgb *= scale; - - /* Convert to display space. */ - col = OCIO_to_display(col); - - /* Blend with overlay in UI colorspace. - * - * UI colorspace here refers to the display linear color space, - * i.e: The linear color space w.r.t. display chromaticity and radiometry. - * We separate the colormanagement process into two steps to be able to - * merge UI using alpha blending in the correct color space. */ - if (overlay) { - col.rgb = pow(col.rgb, vec3(exponent * 2.2)); - col = clamp(col, 0.0, 1.0); - col *= 1.0 - col_overlay.a; - col += col_overlay; /* Assumed unassociated alpha. */ - col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); - } - else { - col.rgb = pow(col.rgb, vec3(exponent)); - } - - if (dither > 0.0) { - col = apply_dither(col, noise_uv); - } - - return col; -} - -/* ------------------------------------------------------------------------ */ - -in vec2 texCoord_interp; -out vec4 fragColor; - -void main() -{ - vec4 col = texture(image_texture, texCoord_interp.st); - vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); - vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); - - fragColor = OCIO_ProcessColor(col, col_overlay, noise_uv); -} diff --git a/intern/opencolorio/gpu_shader_display_transform_frag.glsl b/intern/opencolorio/gpu_shader_display_transform_frag.glsl new file mode 100644 index 00000000000..3c2352c13ba --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_frag.glsl @@ -0,0 +1,195 @@ +/* Blender OpenColorIO implementation */ + +/* -------------------------------------------------------------------- */ +/** \name Curve Mapping Implementation + * \{ */ + +#ifdef USE_CURVE_MAPPING + +float read_curve_mapping(int table, int index) +{ + return texelFetch(curve_mapping_texture, index, 0)[table]; +} + +float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) +{ + if (x <= first[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return first[1]; + } + else { + float fac = (curve_mapping.ext_in_x[table] != 0.0) ? + ((x - first[0]) / curve_mapping.ext_in_x[table]) : + 10000.0; + return first[1] + curve_mapping.ext_in_y[table] * fac; + } + } + else if (x >= last[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return last[1]; + } + else { + float fac = (curve_mapping.ext_out_x[table] != 0.0) ? + ((x - last[0]) / curve_mapping.ext_out_x[table]) : + -10000.0; + return last[1] + curve_mapping.ext_out_y[table] * fac; + } + } + return 0.0; +} + +float curvemap_evaluateF(int table, float value) +{ + float mintable_ = curve_mapping.mintable[table]; + float range = curve_mapping.range[table]; + float mintable = 0.0; + int CM_TABLE = curve_mapping.lut_size - 1; + + float fi; + int i; + + /* index in table */ + fi = (value - mintable) * range; + i = int(fi); + + /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ + if (fi < 0.0 || fi > float(CM_TABLE)) { + return curvemap_calc_extend(table, + value, + vec2(curve_mapping.first_x[table], curve_mapping.first_y[table]), + vec2(curve_mapping.last_x[table], curve_mapping.last_y[table])); + } + else { + if (i < 0) { + return read_curve_mapping(table, 0); + } + if (i >= CM_TABLE) { + return read_curve_mapping(table, CM_TABLE); + } + fi = fi - float(i); + float cm1 = read_curve_mapping(table, i); + float cm2 = read_curve_mapping(table, i + 1); + return mix(cm1, cm2, fi); + } +} + +vec4 curvemapping_evaluate_premulRGBF(vec4 col) +{ + col.rgb = (col.rgb - curve_mapping.black.rgb) * curve_mapping.bwmul.rgb; + + vec4 result; + result.r = curvemap_evaluateF(0, col.r); + result.g = curvemap_evaluateF(1, col.g); + result.b = curvemap_evaluateF(2, col.b); + result.a = col.a; + return result; +} + +#endif /* USE_CURVE_MAPPING */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dithering + * \{ */ + +/* Using a triangle distribution which gives a more final uniform noise. + * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ +/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ +/* Return triangle noise in [-1..1[ range */ +float dither_random_value(vec2 co) +{ + /* Original code from https://www.shadertoy.com/view/4t2SDh */ + /* Uniform noise in [0..1[ range */ + float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); + /* Convert uniform distribution into triangle-shaped distribution. */ + float orig = nrnd0 * 2.0 - 1.0; + nrnd0 = orig * inversesqrt(abs(orig)); + nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ + return nrnd0 - sign(orig); +} + +vec2 round_to_pixel(sampler2D tex, vec2 uv) +{ + vec2 size = vec2(textureSize(tex, 0)); + return floor(uv * size) / size; +} + +vec4 apply_dither(vec4 col, vec2 uv) +{ + col.rgb += dither_random_value(uv) * 0.0033 * parameters.dither; + return col; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Main Processing + * \{ */ + +/* Prototypes: Implementation is generaterd and defined after. */ +vec4 OCIO_to_scene_linear(vec4 pixel); +vec4 OCIO_to_display(vec4 pixel); + +vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay) +{ +#ifdef USE_CURVE_MAPPING + col = curvemapping_evaluate_premulRGBF(col); +#endif + + if (parameters.use_predivide) { + if (col.a > 0.0 && col.a < 1.0) { + col.rgb *= 1.0 / col.a; + } + } + + /* NOTE: This is true we only do de-premul here and NO premul + * and the reason is simple -- opengl is always configured + * for straight alpha at this moment + */ + + /* Convert to scene linear (usually a no-op). */ + col = OCIO_to_scene_linear(col); + + /* Apply exposure in scene linear. */ + col.rgb *= parameters.scale; + + /* Convert to display space. */ + col = OCIO_to_display(col); + + /* Blend with overlay in UI colorspace. + * + * UI colorspace here refers to the display linear color space, + * i.e: The linear color space w.r.t. display chromaticity and radiometry. + * We separate the colormanagement process into two steps to be able to + * merge UI using alpha blending in the correct color space. */ + if (parameters.use_overlay) { + col.rgb = pow(col.rgb, vec3(parameters.exponent * 2.2)); + col = clamp(col, 0.0, 1.0); + col *= 1.0 - col_overlay.a; + col += col_overlay; /* Assumed unassociated alpha. */ + col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); + } + else { + col.rgb = pow(col.rgb, vec3(parameters.exponent)); + } + + if (parameters.dither > 0.0) { + vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); + col = apply_dither(col, noise_uv); + } + + return col; +} + +/** \} */ + +void main() +{ + vec4 col = texture(image_texture, texCoord_interp.st); + vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); + + fragColor = OCIO_ProcessColor(col, col_overlay); +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vert.glsl b/intern/opencolorio/gpu_shader_display_transform_vert.glsl new file mode 100644 index 00000000000..06788be11de --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_vert.glsl @@ -0,0 +1,6 @@ + +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + texCoord_interp = texCoord; +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl b/intern/opencolorio/gpu_shader_display_transform_vertex.glsl deleted file mode 100644 index 8cf9628b06b..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl +++ /dev/null @@ -1,12 +0,0 @@ - -uniform mat4 ModelViewProjectionMatrix; - -in vec2 texCoord; -in vec2 pos; -out vec2 texCoord_interp; - -void main() -{ - gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); - texCoord_interp = texCoord; -} diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index 09803cd8038..47c965dbc8e 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -21,14 +21,14 @@ #include "GPU_shader.h" #include "GPU_uniform_buffer.h" +#include "gpu_shader_create_info.hh" + using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "ocio_impl.h" - -extern "C" char datatoc_gpu_shader_display_transform_glsl[]; -extern "C" char datatoc_gpu_shader_display_transform_vertex_glsl[]; +#include "ocio_shader_shared.hh" /* **** OpenGL drawing routines using GLSL for color space transform ***** */ @@ -39,41 +39,19 @@ enum OCIO_GPUTextureSlots { TEXTURE_SLOT_LUTS_OFFSET = 3, }; -/* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ -struct OCIO_GPUCurveMappingParameters { - float curve_mapping_mintable[4]; - float curve_mapping_range[4]; - float curve_mapping_ext_in_x[4]; - float curve_mapping_ext_in_y[4]; - float curve_mapping_ext_out_x[4]; - float curve_mapping_ext_out_y[4]; - float curve_mapping_first_x[4]; - float curve_mapping_first_y[4]; - float curve_mapping_last_x[4]; - float curve_mapping_last_y[4]; - float curve_mapping_black[4]; - float curve_mapping_bwmul[4]; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; - int _pad[2]; - /** WARNING: Needs to be 16byte aligned. Used as UBO data. */ +enum OCIO_GPUUniformBufSlots { + UNIFORMBUF_SLOT_DISPLAY = 0, + UNIFORMBUF_SLOT_CURVEMAP = 1, + UNIFORMBUF_SLOT_LUTS = 2, }; struct OCIO_GPUShader { /* GPU shader. */ struct GPUShader *shader = nullptr; - /** Uniform locations. */ - int scale_loc = 0; - int exponent_loc = 0; - int dither_loc = 0; - int overlay_loc = 0; - int predivide_loc = 0; - int ubo_bind = 0; + /** Uniform parameters. */ + OCIO_GPUParameters parameters = {}; + GPUUniformBuf *parameters_buffer = nullptr; /* Destructor. */ ~OCIO_GPUShader() @@ -81,6 +59,9 @@ struct OCIO_GPUShader { if (shader) { GPU_shader_free(shader); } + if (parameters_buffer) { + GPU_uniformbuf_free(parameters_buffer); + } } }; @@ -103,6 +84,7 @@ struct OCIO_GPUTextures { /* Uniforms */ std::vector uniforms; + GPUUniformBuf *uniforms_buffer = nullptr; /* Destructor. */ ~OCIO_GPUTextures() @@ -113,6 +95,9 @@ struct OCIO_GPUTextures { if (dummy) { GPU_texture_free(dummy); } + if (uniforms_buffer) { + GPU_uniformbuf_free(uniforms_buffer); + } } }; @@ -165,97 +150,134 @@ static bool createGPUShader(OCIO_GPUShader &shader, const GpuShaderDescRcPtr &shaderdesc_to_display, const bool use_curve_mapping) { - std::ostringstream os; - { - /* Fragment shader */ + using namespace blender::gpu::shader; - /* Work around OpenColorIO not supporting latest GLSL yet. */ - os << "#define texture2D texture\n"; - os << "#define texture3D texture\n"; + std::string source; + source += shaderdesc_to_scene_linear->getShaderText(); + source += "\n"; + source += shaderdesc_to_display->getShaderText(); + source += "\n"; - if (use_curve_mapping) { - os << "#define USE_CURVE_MAPPING\n"; + { + /* Replace all uniform declarations by a comment. + * This avoids double declarations from the backend. */ + size_t index = 0; + while (true) { + index = source.find("uniform ", index); + if (index == -1) { + break; + } + source.replace(index, 2, "//"); + index += 2; } - - os << shaderdesc_to_scene_linear->getShaderText() << "\n"; - os << shaderdesc_to_display->getShaderText() << "\n"; - - os << datatoc_gpu_shader_display_transform_glsl; } - shader.shader = GPU_shader_create(datatoc_gpu_shader_display_transform_vertex_glsl, - os.str().c_str(), - nullptr, - nullptr, - nullptr, - "OCIOShader"); - - if (shader.shader == nullptr) { - return false; - } - - shader.scale_loc = GPU_shader_get_uniform(shader.shader, "scale"); - shader.exponent_loc = GPU_shader_get_uniform(shader.shader, "exponent"); - shader.dither_loc = GPU_shader_get_uniform(shader.shader, "dither"); - shader.overlay_loc = GPU_shader_get_uniform(shader.shader, "overlay"); - shader.predivide_loc = GPU_shader_get_uniform(shader.shader, "predivide"); - shader.ubo_bind = GPU_shader_get_uniform_block_binding(shader.shader, - "OCIO_GPUCurveMappingParameters"); - - GPU_shader_bind(shader.shader); - - /* Set texture bind point uniform once. This is saved by the shader. */ - GPUShader *sh = shader.shader; - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "image_texture"), TEXTURE_SLOT_IMAGE); - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "overlay_texture"), TEXTURE_SLOT_OVERLAY); + StageInterfaceInfo iface("OCIO_Interface", ""); + iface.smooth(Type::VEC2, "texCoord_interp"); + + ShaderCreateInfo info("OCIO_Display"); + /* Work around OpenColorIO not supporting latest GLSL yet. */ + info.define("texture2D", "texture"); + info.define("texture3D", "texture"); + info.typedef_source("ocio_shader_shared.hh"); + info.sampler(TEXTURE_SLOT_IMAGE, ImageType::FLOAT_2D, "image_texture"); + info.sampler(TEXTURE_SLOT_OVERLAY, ImageType::FLOAT_2D, "overlay_texture"); + info.uniform_buf(UNIFORMBUF_SLOT_DISPLAY, "OCIO_GPUParameters", "parameters"); + info.push_constant(Type::MAT4, "ModelViewProjectionMatrix"); + info.vertex_in(0, Type::VEC2, "pos"); + info.vertex_in(1, Type::VEC2, "texCoord"); + info.vertex_out(iface); + info.fragment_out(0, Type::VEC4, "fragColor"); + info.vertex_source("gpu_shader_display_transform_vert.glsl"); + info.fragment_source("gpu_shader_display_transform_frag.glsl"); + info.fragment_source_generated = source; if (use_curve_mapping) { - GPU_shader_uniform_int( - sh, GPU_shader_get_uniform(sh, "curve_mapping_texture"), TEXTURE_SLOT_CURVE_MAPPING); + info.define("USE_CURVE_MAPPING"); + info.uniform_buf(UNIFORMBUF_SLOT_CURVEMAP, "OCIO_GPUCurveMappingParameters", "curve_mapping"); + info.sampler(TEXTURE_SLOT_CURVE_MAPPING, ImageType::FLOAT_1D, "curve_mapping_texture"); } /* Set LUT textures. */ - for (int i = 0; i < textures.luts.size(); i++) { - GPU_shader_uniform_int(sh, - GPU_shader_get_uniform(sh, textures.luts[i].sampler_name.c_str()), - TEXTURE_SLOT_LUTS_OFFSET + i); - } + int slot = TEXTURE_SLOT_LUTS_OFFSET; + for (OCIO_GPULutTexture &texture : textures.luts) { + ImageType type = GPU_texture_dimensions(texture.texture) == 2 ? ImageType::FLOAT_2D : + ImageType::FLOAT_3D; + info.sampler(slot++, type, texture.sampler_name.c_str()); + } + + /* Set LUT uniforms. */ + if (!textures.uniforms.empty()) { + /* NOTE: For simplicity, we pad everything to size of vec4 avoiding sorting and alignment + * issues. It is unlikely that this becomes a real issue. */ + size_t ubo_size = textures.uniforms.size() * sizeof(float) * 4; + void *ubo_data_buf = malloc(ubo_size); + + uint32_t *ubo_data = reinterpret_cast(ubo_data_buf); + + std::stringstream ss; + ss << "struct OCIO_GPULutParameters {\n"; + + int index = 0; + for (OCIO_GPUUniform &uniform : textures.uniforms) { + index += 1; + const GpuShaderDesc::UniformData &data = uniform.data; + const char *name = uniform.name.c_str(); + char prefix = ' '; + int vec_len; + switch (data.m_type) { + case UNIFORM_DOUBLE: { + vec_len = 1; + float value = float(data.m_getDouble()); + memcpy(ubo_data, &value, sizeof(float)); + break; + } + case UNIFORM_BOOL: { + prefix = 'b'; + vec_len = 1; + int value = int(data.m_getBool()); + memcpy(ubo_data, &value, sizeof(int)); + break; + } + case UNIFORM_FLOAT3: + vec_len = 3; + memcpy(ubo_data, data.m_getFloat3().data(), sizeof(float) * 3); + break; + case UNIFORM_VECTOR_FLOAT: + vec_len = data.m_vectorFloat.m_getSize(); + memcpy(ubo_data, data.m_vectorFloat.m_getVector(), sizeof(float) * vec_len); + break; + case UNIFORM_VECTOR_INT: + prefix = 'i'; + vec_len = data.m_vectorInt.m_getSize(); + memcpy(ubo_data, data.m_vectorInt.m_getVector(), sizeof(int) * vec_len); + break; + default: + continue; + } + /* Align every member to 16bytes. */ + ubo_data += 4; + /* Use a generic variable name because some GLSL compilers can interpret the preprocessor + * define as recursive. */ + ss << " " << prefix << "vec4 var" << index << ";\n"; + /* Use a define to keep the generated code working. */ + blender::StringRef suffix = blender::StringRefNull("xyzw").substr(0, vec_len); + ss << "#define " << name << " lut_parameters.var" << index << "." << suffix << "\n"; + } + ss << "};\n"; + info.typedef_source_generated = ss.str(); - /* Set uniforms. */ - for (OCIO_GPUUniform &uniform : textures.uniforms) { - const GpuShaderDesc::UniformData &data = uniform.data; - const char *name = uniform.name.c_str(); + info.uniform_buf(UNIFORMBUF_SLOT_LUTS, "OCIO_GPULutParameters", "lut_parameters"); - if (data.m_getDouble) { - GPU_shader_uniform_1f(sh, name, (float)data.m_getDouble()); - } - else if (data.m_getBool) { - GPU_shader_uniform_1f(sh, name, (float)(data.m_getBool() ? 1.0f : 0.0f)); - } - else if (data.m_getFloat3) { - GPU_shader_uniform_3f(sh, - name, - (float)data.m_getFloat3()[0], - (float)data.m_getFloat3()[1], - (float)data.m_getFloat3()[2]); - } - else if (data.m_vectorFloat.m_getSize && data.m_vectorFloat.m_getVector) { - GPU_shader_uniform_vector(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorFloat.m_getSize(), - 1, - (float *)data.m_vectorFloat.m_getVector()); - } - else if (data.m_vectorInt.m_getSize && data.m_vectorInt.m_getVector) { - GPU_shader_uniform_vector_int(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorInt.m_getSize(), - 1, - (int *)data.m_vectorInt.m_getVector()); - } + textures.uniforms_buffer = GPU_uniformbuf_create_ex( + ubo_size, ubo_data_buf, "OCIO_LutParameters"); + + free(ubo_data_buf); } - return true; + shader.shader = GPU_shader_create_from_info(reinterpret_cast(&info)); + + return (shader.shader != nullptr); } /** \} */ @@ -438,27 +460,65 @@ static void updateGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, /* Update uniforms. */ OCIO_GPUCurveMappingParameters data; for (int i = 0; i < 4; i++) { - data.curve_mapping_range[i] = curve_mapping_settings->range[i]; - data.curve_mapping_mintable[i] = curve_mapping_settings->mintable[i]; - data.curve_mapping_ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; - data.curve_mapping_ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; - data.curve_mapping_ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; - data.curve_mapping_ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; - data.curve_mapping_first_x[i] = curve_mapping_settings->first_x[i]; - data.curve_mapping_first_y[i] = curve_mapping_settings->first_y[i]; - data.curve_mapping_last_x[i] = curve_mapping_settings->last_x[i]; - data.curve_mapping_last_y[i] = curve_mapping_settings->last_y[i]; + data.range[i] = curve_mapping_settings->range[i]; + data.mintable[i] = curve_mapping_settings->mintable[i]; + data.ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; + data.ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; + data.ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; + data.ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; + data.first_x[i] = curve_mapping_settings->first_x[i]; + data.first_y[i] = curve_mapping_settings->first_y[i]; + data.last_x[i] = curve_mapping_settings->last_x[i]; + data.last_y[i] = curve_mapping_settings->last_y[i]; } for (int i = 0; i < 3; i++) { - data.curve_mapping_black[i] = curve_mapping_settings->black[i]; - data.curve_mapping_bwmul[i] = curve_mapping_settings->bwmul[i]; + data.black[i] = curve_mapping_settings->black[i]; + data.bwmul[i] = curve_mapping_settings->bwmul[i]; } - data.curve_mapping_lut_size = curve_mapping_settings->lut_size; - data.curve_mapping_use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; + data.lut_size = curve_mapping_settings->lut_size; + data.use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; GPU_uniformbuf_update(curvemap.buffer, &data); } +static void updateGPUDisplayParameters(OCIO_GPUShader &shader, + float scale, + float exponent, + float dither, + bool use_predivide, + bool use_overlay) +{ + bool do_update = false; + if (shader.parameters_buffer == nullptr) { + shader.parameters_buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUParameters)); + do_update = true; + } + OCIO_GPUParameters &data = shader.parameters; + if (data.scale != scale) { + data.scale = scale; + do_update = true; + } + if (data.exponent != exponent) { + data.exponent = exponent; + do_update = true; + } + if (data.dither != dither) { + data.dither = dither; + do_update = true; + } + if (data.use_predivide != use_predivide) { + data.use_predivide = use_predivide; + do_update = true; + } + if (data.use_overlay != use_overlay) { + data.use_overlay = use_overlay; + do_update = true; + } + if (do_update) { + GPU_uniformbuf_update(shader.parameters_buffer, &data); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -609,7 +669,7 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, /* Update and bind curve mapping data. */ if (curve_mapping_settings) { updateGPUCurveMapping(curvemap, curve_mapping_settings); - GPU_uniformbuf_bind(curvemap.buffer, shader.ubo_bind); + GPU_uniformbuf_bind(curvemap.buffer, UNIFORMBUF_SLOT_CURVEMAP); GPU_texture_bind(curvemap.texture, TEXTURE_SLOT_CURVE_MAPPING); } @@ -623,17 +683,16 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, GPU_texture_bind(textures.luts[i].texture, TEXTURE_SLOT_LUTS_OFFSET + i); } + if (textures.uniforms_buffer) { + GPU_uniformbuf_bind(textures.uniforms_buffer, UNIFORMBUF_SLOT_LUTS); + } + + updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay); + GPU_uniformbuf_bind(shader.parameters_buffer, UNIFORMBUF_SLOT_DISPLAY); + /* TODO(fclem): remove remains of IMM. */ immBindShader(shader.shader); - /* Bind Shader and set uniforms. */ - // GPU_shader_bind(shader.shader); - GPU_shader_uniform_float(shader.shader, shader.scale_loc, scale); - GPU_shader_uniform_float(shader.shader, shader.exponent_loc, exponent); - GPU_shader_uniform_float(shader.shader, shader.dither_loc, dither); - GPU_shader_uniform_int(shader.shader, shader.overlay_loc, use_overlay); - GPU_shader_uniform_int(shader.shader, shader.predivide_loc, use_predivide); - return true; } diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh new file mode 100644 index 00000000000..c7045217196 --- /dev/null +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#ifndef GPU_SHADER +# include "GPU_shader_shared_utils.h" +#endif + +struct OCIO_GPUCurveMappingParameters { + /* Curve mapping parameters + * + * See documentation for OCIO_CurveMappingSettings to get fields descriptions. + * (this ones pretty much copies stuff from C structure.) + */ + float4 mintable; + float4 range; + float4 ext_in_x; + float4 ext_in_y; + float4 ext_out_x; + float4 ext_out_y; + float4 first_x; + float4 first_y; + float4 last_x; + float4 last_y; + float4 black; + float4 bwmul; + int lut_size; + int use_extend_extrapolate; + int _pad0; + int _pad1; +}; + +struct OCIO_GPUParameters { + float dither; + float scale; + float exponent; + bool1 use_predivide; + bool1 use_overlay; + int _pad0; + int _pad1; + int _pad2; +}; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index e883a12a5b2..f2e189777f0 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -452,12 +452,21 @@ if(WITH_IMAGE_DDS) add_definitions(-DWITH_DDS) endif() +if(WITH_OPENCOLORIO) + add_definitions(-DWITH_OCIO) +endif() + blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") target_link_libraries(bf_gpu PUBLIC bf_draw_shaders bf_gpu_shaders ) +if(WITH_OPENCOLORIO) + target_link_libraries(bf_gpu PUBLIC bf_ocio_shaders) +endif() + + if(CXX_WARN_NO_SUGGEST_OVERRIDE) target_compile_options(bf_gpu PRIVATE $<$:-Wsuggest-override>) endif() diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 8a842ef4d7c..5b7df035acd 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -21,6 +21,9 @@ extern "C" { #define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[]; #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE } @@ -348,6 +351,9 @@ void gpu_shader_dependency_init() g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc)); #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE int errors = 0; -- cgit v1.2.3 From 8355ac97b4327acde8b656e38e5f4911918ef521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 17 Feb 2022 18:14:03 +0100 Subject: GPU: Remove runtime sampler texture slot assignment This avoid potential shader recompilation and is more in line with vulkan design. --- source/blender/editors/space_sequencer/sequencer_draw.c | 1 - source/blender/gpu/intern/gpu_viewport.c | 4 ---- .../gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh | 1 + source/blender/windowmanager/intern/wm_playanim.c | 1 - source/blender/windowmanager/intern/wm_stereo.c | 4 ++-- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 8c12193fb88..1e810a9c59e 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -1926,7 +1926,6 @@ static void sequencer_draw_display_buffer(const bContext *C, if (!glsl_used) { immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); - immUniform1i("image", 0); } immBegin(GPU_PRIM_TRI_FAN, 4); diff --git a/source/blender/gpu/intern/gpu_viewport.c b/source/blender/gpu/intern/gpu_viewport.c index fd4a87bc544..b5a572bccbe 100644 --- a/source/blender/gpu/intern/gpu_viewport.c +++ b/source/blender/gpu/intern/gpu_viewport.c @@ -277,8 +277,6 @@ void GPU_viewport_stereo_composite(GPUViewport *viewport, Stereo3dFormat *stereo GPU_matrix_identity_set(); GPU_matrix_identity_projection_set(); immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_OVERLAYS_STEREO_MERGE); - immUniform1i("overlayTexture", 0); - immUniform1i("imageTexture", 1); int settings = stereo_format->display_mode; if (settings == S3D_DISPLAY_ANAGLYPH) { switch (stereo_format->anaglyph_type) { @@ -432,8 +430,6 @@ static void gpu_viewport_draw_colormanaged(GPUViewport *viewport, GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE); GPU_batch_uniform_1i(batch, "overlay", do_overlay_merge); GPU_batch_uniform_1i(batch, "display_transform", display_colorspace); - GPU_batch_uniform_1i(batch, "image_texture", 0); - GPU_batch_uniform_1i(batch, "overlays_texture", 1); } GPU_texture_bind(color, 0); diff --git a/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh index d3b70cb67f9..2798846b310 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh @@ -16,6 +16,7 @@ GPU_SHADER_CREATE_INFO(gpu_shader_2D_image_overlays_merge) .push_constant(Type::MAT4, "ModelViewProjectionMatrix") .push_constant(Type::BOOL, "display_transform") .push_constant(Type::BOOL, "overlay") + /* Sampler slots should match OCIO's. */ .sampler(0, ImageType::FLOAT_2D, "image_texture") .sampler(1, ImageType::FLOAT_2D, "overlays_texture") .vertex_source("gpu_shader_2D_image_vert.glsl") diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 2ee608c0755..95879829d42 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -483,7 +483,6 @@ static void draw_display_buffer(PlayState *ps, ImBuf *ibuf) if (!glsl_used) { immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); immUniformColor3f(1.0f, 1.0f, 1.0f); - immUniform1i("image", 0); } immBegin(GPU_PRIM_TRI_FAN, 4); diff --git a/source/blender/windowmanager/intern/wm_stereo.c b/source/blender/windowmanager/intern/wm_stereo.c index 6bdd11df776..182308cbe5e 100644 --- a/source/blender/windowmanager/intern/wm_stereo.c +++ b/source/blender/windowmanager/intern/wm_stereo.c @@ -67,7 +67,7 @@ void wm_stereo3d_draw_sidebyside(wmWindow *win, int view) const float halfx = GLA_PIXEL_OFS / sizex; const float halfy = GLA_PIXEL_OFS / sizex; - immUniform1i("image", 0); /* texture is already bound to GL_TEXTURE0 unit */ + /* Texture is already bound to GL_TEXTURE0 unit. */ immBegin(GPU_PRIM_TRI_FAN, 4); @@ -111,7 +111,7 @@ void wm_stereo3d_draw_topbottom(wmWindow *win, int view) const float halfx = GLA_PIXEL_OFS / sizex; const float halfy = GLA_PIXEL_OFS / sizex; - immUniform1i("image", 0); /* texture is already bound to GL_TEXTURE0 unit */ + /* Texture is already bound to GL_TEXTURE0 unit. */ immBegin(GPU_PRIM_TRI_FAN, 4); -- cgit v1.2.3 From a04300c436d77bf47b94d033c1d832d86425945c Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Thu, 17 Feb 2022 18:42:06 +0100 Subject: Cleanup: Move more cmake library variables to be advanced I noticed that there were a few variables that should not be visible per default. It seems to me to simply be an oversight, so I went ahead and cleaned them up. Reviewed By: Sybren, Ray molenkamp Differential Revision: http://developer.blender.org/D14132 --- build_files/cmake/Modules/FindFFmpeg.cmake | 4 +++- build_files/cmake/Modules/FindOSL.cmake | 2 ++ build_files/cmake/Modules/FindOpenColorIO.cmake | 8 +++++--- build_files/cmake/Modules/FindOpenImageDenoise.cmake | 1 + build_files/cmake/platform/platform_unix.cmake | 1 + source/blender/io/collada/CMakeLists.txt | 4 ++++ 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/build_files/cmake/Modules/FindFFmpeg.cmake b/build_files/cmake/Modules/FindFFmpeg.cmake index 5f506a33e13..8d939f3ad85 100644 --- a/build_files/cmake/Modules/FindFFmpeg.cmake +++ b/build_files/cmake/Modules/FindFFmpeg.cmake @@ -82,4 +82,6 @@ mark_as_advanced( unset(_ffmpeg_SEARCH_DIRS) unset(_ffmpeg_LIBRARIES) -unset(_ffmpeg_INCLUDE_DIR) +# In cmake version 3.21 and up, we can instead use the NO_CACHE option for +# find_path so we don't need to clear it from the cache here. +unset(_ffmpeg_INCLUDE_DIR CACHE) diff --git a/build_files/cmake/Modules/FindOSL.cmake b/build_files/cmake/Modules/FindOSL.cmake index b21c7ad50a3..b67ce515ff5 100644 --- a/build_files/cmake/Modules/FindOSL.cmake +++ b/build_files/cmake/Modules/FindOSL.cmake @@ -76,6 +76,7 @@ FIND_PATH(OSL_SHADER_DIR /usr/include/OSL/ PATH_SUFFIXES share/OSL/shaders + shaders ) # handle the QUIETLY and REQUIRED arguments and set OSL_FOUND to TRUE if @@ -99,6 +100,7 @@ ENDIF() MARK_AS_ADVANCED( OSL_INCLUDE_DIR + OSL_SHADER_DIR ) FOREACH(COMPONENT ${_osl_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) diff --git a/build_files/cmake/Modules/FindOpenColorIO.cmake b/build_files/cmake/Modules/FindOpenColorIO.cmake index 21118533ebe..8aa7795b611 100644 --- a/build_files/cmake/Modules/FindOpenColorIO.cmake +++ b/build_files/cmake/Modules/FindOpenColorIO.cmake @@ -87,12 +87,14 @@ ENDIF() MARK_AS_ADVANCED( OPENCOLORIO_INCLUDE_DIR OPENCOLORIO_LIBRARY - OPENCOLORIO_OPENCOLORIO_LIBRARY - OPENCOLORIO_TINYXML_LIBRARY - OPENCOLORIO_YAML-CPP_LIBRARY OPENCOLORIO_VERSION ) +FOREACH(COMPONENT ${_opencolorio_FIND_COMPONENTS}) + STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) + MARK_AS_ADVANCED(OPENCOLORIO_${UPPERCOMPONENT}_LIBRARY) +ENDFOREACH() + UNSET(COMPONENT) UNSET(UPPERCOMPONENT) UNSET(_opencolorio_FIND_COMPONENTS) diff --git a/build_files/cmake/Modules/FindOpenImageDenoise.cmake b/build_files/cmake/Modules/FindOpenImageDenoise.cmake index 3facadbb9be..6ad45c8cf61 100644 --- a/build_files/cmake/Modules/FindOpenImageDenoise.cmake +++ b/build_files/cmake/Modules/FindOpenImageDenoise.cmake @@ -110,6 +110,7 @@ ENDIF() MARK_AS_ADVANCED( OPENIMAGEDENOISE_INCLUDE_DIR + OPENIMAGEDENOISE_LIBRARY ) FOREACH(COMPONENT ${_openimagedenoise_FIND_COMPONENTS}) diff --git a/build_files/cmake/platform/platform_unix.cmake b/build_files/cmake/platform/platform_unix.cmake index 6a896709cc2..95d0e4de380 100644 --- a/build_files/cmake/platform/platform_unix.cmake +++ b/build_files/cmake/platform/platform_unix.cmake @@ -362,6 +362,7 @@ if(WITH_BOOST) find_package(IcuLinux) endif() mark_as_advanced(Boost_DIR) # why doesn't boost do this? + mark_as_advanced(Boost_INCLUDE_DIR) # why doesn't boost do this? endif() set(BOOST_INCLUDE_DIR ${Boost_INCLUDE_DIRS}) diff --git a/source/blender/io/collada/CMakeLists.txt b/source/blender/io/collada/CMakeLists.txt index e1645083116..25c20accaa2 100644 --- a/source/blender/io/collada/CMakeLists.txt +++ b/source/blender/io/collada/CMakeLists.txt @@ -31,6 +31,10 @@ if(OPENCOLLADA_ANIMATION_CLIP) add_definitions(-DWITH_OPENCOLLADA_ANIMATION_CLIP) endif() +# In cmake version 3.21 and up, we can instead use the NO_CACHE option for +# find_file so we don't need to clear it from the cache here. +unset(OPENCOLLADA_ANIMATION_CLIP CACHE) + set(INC . ../../blenkernel -- cgit v1.2.3 From 48432c1c92824a0da4bb73b1ca7b45290a4b3aaf Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 17 Feb 2022 12:36:41 -0600 Subject: Fix: Debug build error with vector type division The idea is to keep `is_any_zero` in the `blender::math` namespace, so instead of trying to be clever, just move it there and expand the function where it was used in the class. --- source/blender/blenlib/BLI_math_vec_types.hh | 22 ++++++++----------- source/blender/blenlib/BLI_math_vector.hh | 10 +++++++++ .../blenlib/tests/BLI_math_vec_types_test.cc | 25 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/source/blender/blenlib/BLI_math_vec_types.hh b/source/blender/blenlib/BLI_math_vec_types.hh index b6b3d15aefe..389307e331d 100644 --- a/source/blender/blenlib/BLI_math_vec_types.hh +++ b/source/blender/blenlib/BLI_math_vec_types.hh @@ -64,16 +64,6 @@ template uint64_t vector_hash(const T &vec) return result; } -template inline bool is_any_zero(const vec_struct_base &a) -{ - for (int i = 0; i < Size; i++) { - if (a[i] == T(0)) { - return true; - } - } - return false; -} - } // namespace math template struct vec_base : public vec_struct_base { @@ -353,7 +343,9 @@ template struct vec_base : public vec_struct_base friend vec_base operator/(const vec_base &a, const vec_base &b) { - BLI_assert(!math::is_any_zero(b)); + for (int i = 0; i < Size; i++) { + BLI_assert(b[i] != T(0)); + } BLI_VEC_OP_IMPL(ret, i, ret[i] = a[i] / b[i]); } @@ -365,7 +357,9 @@ template struct vec_base : public vec_struct_base friend vec_base operator/(T a, const vec_base &b) { - BLI_assert(!math::is_any_zero(b)); + for (int i = 0; i < Size; i++) { + BLI_assert(b[i] != T(0)); + } BLI_VEC_OP_IMPL(ret, i, ret[i] = a / b[i]); } @@ -509,7 +503,9 @@ template struct vec_base : public vec_struct_base BLI_INT_OP(T) friend vec_base operator%(const vec_base &a, const vec_base &b) { - BLI_assert(!math::is_any_zero(b)); + for (int i = 0; i < Size; i++) { + BLI_assert(b[i] != T(0)); + } BLI_VEC_OP_IMPL(ret, i, ret[i] = a[i] % b[i]); } diff --git a/source/blender/blenlib/BLI_math_vector.hh b/source/blender/blenlib/BLI_math_vector.hh index 3bb89bb26b2..7c848eeb145 100644 --- a/source/blender/blenlib/BLI_math_vector.hh +++ b/source/blender/blenlib/BLI_math_vector.hh @@ -39,6 +39,16 @@ template inline bool is_zero(const vec_base &a) return true; } +template inline bool is_any_zero(const vec_base &a) +{ + for (int i = 0; i < Size; i++) { + if (a[i] == T(0)) { + return true; + } + } + return false; +} + template inline vec_base abs(const vec_base &a) { vec_base result; diff --git a/source/blender/blenlib/tests/BLI_math_vec_types_test.cc b/source/blender/blenlib/tests/BLI_math_vec_types_test.cc index 07eb6b29a20..7590d77525b 100644 --- a/source/blender/blenlib/tests/BLI_math_vec_types_test.cc +++ b/source/blender/blenlib/tests/BLI_math_vec_types_test.cc @@ -146,4 +146,29 @@ TEST(math_vec_types, VectorTypeConversion) EXPECT_EQ(d[1], -1.0); } +TEST(math_vec_types, Divide) +{ + float2 a(1.0f, 2.0f); + float2 b(0.5f, 2.0f); + float2 result = a / b; + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 1.0f); +} + +TEST(math_vec_types, DivideFloatByVector) +{ + float a = 2.0f; + float2 b(0.5f, 2.0f); + float2 result = a / b; + EXPECT_FLOAT_EQ(result.x, 4.0f); + EXPECT_FLOAT_EQ(result.y, 1.0f); +} + +TEST(math_vec_types, DivideFloatByVectorSmall) +{ + float2 result = 2.0f / float2(2.0f); + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 1.0f); +} + } // namespace blender::tests -- cgit v1.2.3 From da6b534274d88de590ab978c596a5d8b10a70a69 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 17 Feb 2022 19:37:55 +0100 Subject: Fix T95368: wrong white point adaptation for Linear ACES color space This affected loading of EXR files with set to Linear ACES colorspace, as well as the sky texture for in some custom OpenColorIO configurations. Use the builtin OpenColorIO transform from ACES AP0 to XYZ D65 to fix this. --- intern/cycles/scene/shader.cpp | 34 +++++++++++++-------------- intern/opencolorio/ocio_impl.cc | 18 +++++++------- release/datafiles/colormanagement/config.ocio | 5 ++-- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index 0b286aba9cf..e6c79c5faf9 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -830,28 +830,28 @@ void ShaderManager::init_xyz_transforms() Transform xyz_to_rgb; if (config->hasRole("aces_interchange")) { - /* Standard OpenColorIO role, defined as ACES2065-1. */ - const Transform xyz_E_to_aces = make_transform(1.0498110175f, - 0.0f, - -0.0000974845f, - 0.0f, - -0.4959030231f, - 1.3733130458f, - 0.0982400361f, - 0.0f, - 0.0f, - 0.0f, - 0.9912520182f, - 0.0f); - const Transform xyz_D65_to_E = make_transform( - 1.0521111f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9184170f, 0.0f); - + /* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */ Transform aces_to_rgb; if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) { return; } - xyz_to_rgb = aces_to_rgb * xyz_E_to_aces * xyz_D65_to_E; + /* This is the OpenColorIO builtin transform: + * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ + const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280, + -0.004451, + 0.016628, + 0.000000, + 0.337369, + 0.729522, + -0.066890, + 0.000000, + 0.001174, + -0.003711, + 1.091595, + 0.000000); + const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65); + xyz_to_rgb = aces_to_rgb * xyz_to_aces; } else if (config->hasRole("XYZ")) { /* Custom role used before the standard existed. */ diff --git a/intern/opencolorio/ocio_impl.cc b/intern/opencolorio/ocio_impl.cc index b4e48c013c0..7c2f4017143 100644 --- a/intern/opencolorio/ocio_impl.cc +++ b/intern/opencolorio/ocio_impl.cc @@ -336,16 +336,18 @@ void OCIOImpl::configGetXYZtoRGB(OCIO_ConstConfigRcPtr *config_, float xyz_to_rg } if (config->hasRole("aces_interchange")) { - /* Standard OpenColorIO role, defined as ACES2065-1. */ - const float xyz_E_to_aces[3][3] = {{1.0498110175f, -0.4959030231f, 0.0f}, - {0.0f, 1.3733130458f, 0.0f}, - {-0.0000974845f, 0.0982400361f, 0.9912520182f}}; - const float xyz_D65_to_E[3][3] = { - {1.0521111f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.9184170f}}; - + /* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */ float aces_to_rgb[3][3]; if (to_scene_linear_matrix(config, "aces_interchange", aces_to_rgb)) { - mul_m3_series(xyz_to_rgb, aces_to_rgb, xyz_E_to_aces, xyz_D65_to_E); + /* This is the OpenColorIO builtin transform: + * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ + const float ACES_AP0_to_xyz_D65[3][3] = {{0.938280, 0.337369, 0.001174}, + {-0.004451, 0.729522, -0.003711}, + {0.016628, -0.066890, 1.091595}}; + float xyz_to_aces[3][3]; + invert_m3_m3(xyz_to_aces, ACES_AP0_to_xyz_D65); + + mul_m3_m3m3(xyz_to_rgb, aces_to_rgb, xyz_to_aces); } } else if (config->hasRole("XYZ")) { diff --git a/release/datafiles/colormanagement/config.ocio b/release/datafiles/colormanagement/config.ocio index bd342a0577e..bdb04cbf9ce 100644 --- a/release/datafiles/colormanagement/config.ocio +++ b/release/datafiles/colormanagement/config.ocio @@ -9,7 +9,7 @@ # # See ocio-license.txt for details. -ocio_profile_version: 1 +ocio_profile_version: 2 search_path: "luts:filmic" strictparsing: true @@ -100,8 +100,7 @@ colorspaces: from_reference: ! children: - ! {src: srgb_to_xyz.spimtx, interpolation: linear} - - ! {src: xyz_D65_to_E.spimtx, interpolation: linear} - - ! {src: xyz_to_aces.spimtx, interpolation: linear} + - ! {style: "UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD", direction: inverse} - ! name: nuke_rec709 -- cgit v1.2.3 From 3cdbeb32d3779a0e6a89d2529c15f737cc32eae9 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 17 Feb 2022 20:27:45 +0100 Subject: Fix build error on some compilers after recent bugfix --- intern/cycles/scene/shader.cpp | 24 ++++++++++++------------ intern/opencolorio/ocio_impl.cc | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index e6c79c5faf9..9ac7edea7e8 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -838,18 +838,18 @@ void ShaderManager::init_xyz_transforms() /* This is the OpenColorIO builtin transform: * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ - const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280, - -0.004451, - 0.016628, - 0.000000, - 0.337369, - 0.729522, - -0.066890, - 0.000000, - 0.001174, - -0.003711, - 1.091595, - 0.000000); + const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f, + -0.004451f, + 0.016628f, + 0.000000f, + 0.337369f, + 0.729522f, + -0.066890f, + 0.000000f, + 0.001174f, + -0.003711f, + 1.091595f, + 0.000000f); const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65); xyz_to_rgb = aces_to_rgb * xyz_to_aces; } diff --git a/intern/opencolorio/ocio_impl.cc b/intern/opencolorio/ocio_impl.cc index 7c2f4017143..8b3c2dae6b0 100644 --- a/intern/opencolorio/ocio_impl.cc +++ b/intern/opencolorio/ocio_impl.cc @@ -341,9 +341,9 @@ void OCIOImpl::configGetXYZtoRGB(OCIO_ConstConfigRcPtr *config_, float xyz_to_rg if (to_scene_linear_matrix(config, "aces_interchange", aces_to_rgb)) { /* This is the OpenColorIO builtin transform: * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ - const float ACES_AP0_to_xyz_D65[3][3] = {{0.938280, 0.337369, 0.001174}, - {-0.004451, 0.729522, -0.003711}, - {0.016628, -0.066890, 1.091595}}; + const float ACES_AP0_to_xyz_D65[3][3] = {{0.938280f, 0.337369f, 0.001174f}, + {-0.004451f, 0.729522f, -0.003711f}, + {0.016628f, -0.066890f, 1.091595f}}; float xyz_to_aces[3][3]; invert_m3_m3(xyz_to_aces, ACES_AP0_to_xyz_D65); -- cgit v1.2.3 From 59aaf2036eeea0f32a9b65384ca73e5dbf229978 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Thu, 17 Feb 2022 22:11:08 -0300 Subject: Fix wrong method name in gpu documentation `use_program_point_size` --> `program_point_size_set`. --- source/blender/python/gpu/gpu_py_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 2c5012216f2..059e6ceb749 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -326,7 +326,7 @@ static PyObject *pygpu_state_front_facing_set(PyObject *UNUSED(self), PyObject * } PyDoc_STRVAR(pygpu_state_program_point_size_set_doc, - ".. function:: use_program_point_size(enable)\n" + ".. function:: program_point_size_set(enable)\n" "\n" " If enabled, the derived point size is taken from the (potentially clipped) " "shader builtin gl_PointSize.\n" -- cgit v1.2.3 From 1d0d810331b0ced05acdba9c75a08948f7b6eaf8 Mon Sep 17 00:00:00 2001 From: Pratik Borhade Date: Thu, 17 Feb 2022 23:44:16 -0600 Subject: Fix T93526: Missing tooltip for attribute search button For the attribute search button, the tooltip was missing if the input socket type has attribute toggle activated. Differential Revision: https://developer.blender.org/D14142 --- source/blender/modifiers/intern/MOD_nodes.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index c5ee4b9fb52..1c755a7e2d9 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -1362,7 +1362,7 @@ static void add_attribute_search_button(const bContext &C, 0.0f, 0.0f, 0.0f, - ""); + socket.description); const Object *object = ED_object_context(&C); BLI_assert(object != nullptr); -- cgit v1.2.3 From f33e6e0d8c607eb33b1eb0c587b4e43cde30d5e1 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 18 Feb 2022 16:43:10 +1100 Subject: Cleanup: move camera-view pan/zoom into utility functions --- source/blender/editors/include/ED_view3d.h | 14 +++++++++ .../blender/editors/space_view3d/view3d_navigate.c | 10 +++---- source/blender/editors/space_view3d/view3d_utils.c | 33 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 46c9a1973eb..18b63403fcb 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -1078,6 +1078,20 @@ bool ED_view3d_persp_ensure(const struct Depsgraph *depsgraph, struct View3D *v3d, struct ARegion *region); +/* Camera view functions. */ + +/** + * Utility to scale zoom level when in camera-view #RegionView3D.camzoom and apply limits. + * \return true a change was made. + */ +bool ED_view3d_camera_view_zoom_scale(struct RegionView3D *rv3d, const float scale); +/** + * Utility to pan when in camera view. + * \param event_ofs: The offset the pan in screen (pixel) coordinates. + * \return true when a change was made. + */ +bool ED_view3d_camera_view_pan(struct ARegion *region, const float event_ofs[2]); + /* Camera lock functions */ /** diff --git a/source/blender/editors/space_view3d/view3d_navigate.c b/source/blender/editors/space_view3d/view3d_navigate.c index 0305989d142..d1e7b5891e9 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.c +++ b/source/blender/editors/space_view3d/view3d_navigate.c @@ -549,11 +549,11 @@ void viewmove_apply(ViewOpsData *vod, int x, int y) vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->region->winy; } else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) { - const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f; - vod->rv3d->camdx += (vod->prev.event_xy[0] - x) / (vod->region->winx * zoomfac); - vod->rv3d->camdy += (vod->prev.event_xy[1] - y) / (vod->region->winy * zoomfac); - CLAMP(vod->rv3d->camdx, -1.0f, 1.0f); - CLAMP(vod->rv3d->camdy, -1.0f, 1.0f); + const float event_ofs[2] = { + vod->prev.event_xy[0] - x, + vod->prev.event_xy[1] - y, + }; + ED_view3d_camera_view_pan(vod->region, event_ofs); } else { float dvec[3]; diff --git a/source/blender/editors/space_view3d/view3d_utils.c b/source/blender/editors/space_view3d/view3d_utils.c index 8a219cd96d1..3e788f2d643 100644 --- a/source/blender/editors/space_view3d/view3d_utils.c +++ b/source/blender/editors/space_view3d/view3d_utils.c @@ -509,6 +509,39 @@ bool ED_view3d_persp_ensure(const Depsgraph *depsgraph, View3D *v3d, ARegion *re /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Camera View Utilities + * + * Utilities for manipulating the camera-view. + * \{ */ + +bool ED_view3d_camera_view_zoom_scale(RegionView3D *rv3d, const float scale) +{ + const float camzoom_init = rv3d->camzoom; + float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom); + /* Clamp both before and after conversion to prevent NAN on negative values. */ + + zoomfac = zoomfac * scale; + CLAMP(zoomfac, RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR); + rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(zoomfac); + CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX); + return (rv3d->camzoom != camzoom_init); +} + +bool ED_view3d_camera_view_pan(ARegion *region, const float event_ofs[2]) +{ + RegionView3D *rv3d = region->regiondata; + const float camdxy_init[2] = {rv3d->camdx, rv3d->camdy}; + const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f; + rv3d->camdx += event_ofs[0] / (region->winx * zoomfac); + rv3d->camdy += event_ofs[1] / (region->winy * zoomfac); + CLAMP(rv3d->camdx, -1.0f, 1.0f); + CLAMP(rv3d->camdy, -1.0f, 1.0f); + return (camdxy_init[0] != rv3d->camdx) || (camdxy_init[1] != rv3d->camdy); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Camera Lock API * -- cgit v1.2.3 From 51975b89edfcc02131f1f8248e1b3442ea2778fa Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 18 Feb 2022 16:43:30 +1100 Subject: 3D View: Add camera view pan/zoom support for NDOF NDOF navigation in a camera view now behaves like orthographic pan/zoom. Note that NDOF orbiting out of the camera view has been disabled, see code comment for details. Resolves T93666. --- .../editors/space_view3d/view3d_navigate_ndof.c | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/source/blender/editors/space_view3d/view3d_navigate_ndof.c b/source/blender/editors/space_view3d/view3d_navigate_ndof.c index ced8eca710b..67b48154e8c 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_ndof.c +++ b/source/blender/editors/space_view3d/view3d_navigate_ndof.c @@ -346,6 +346,70 @@ void view3d_ndof_fly(const wmNDOFMotionData *ndof, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name NDOF Camera View Support + * \{ */ + +/** + * 2D orthographic style NDOF navigation within the camera view. + * Support navigating the camera view instead of leaving the camera-view and navigating in 3D. + */ +static int view3d_ndof_cameraview_pan_zoom(bContext *C, const wmEvent *event) +{ + const wmNDOFMotionData *ndof = event->customdata; + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + + ED_view3d_smooth_view_force_finish(C, v3d, region); + + if ((v3d->camera && (rv3d->persp == RV3D_CAMOB) && (v3d->flag2 & V3D_LOCK_CAMERA) == 0)) { + /* pass */ + } + else { + return OPERATOR_PASS_THROUGH; + } + + const bool has_translate = !is_zero_v2(ndof->tvec); + const bool has_zoom = ndof->tvec[2] != 0.0f; + + /* NOTE(@campbellbarton): In principle rotating could pass through to regular + * non-camera NDOF behavior (exiting the camera-view and rotating). + * Disabled this block since in practice it's difficult to control NDOF devices + * to perform some rotation with absolutely no translation. Causing rotation to + * randomly exit from the user perspective. Adjusting the dead-zone could avoid + * the motion feeling *glitchy* although in my own tests even then it didn't work reliably. + * Leave rotating out of camera-view disabled unless it can be made to work reliably. */ + if (!(has_translate || has_zoom)) { + // return OPERATOR_PASS_THROUGH; + } + + bool changed = false; + + if (has_translate) { + const float speed = ndof->dt * NDOF_PIXELS_PER_SECOND; + float event_ofs[2] = {ndof->tvec[0] * speed, ndof->tvec[1] * speed}; + if (ED_view3d_camera_view_pan(region, event_ofs)) { + changed = true; + } + } + + if (has_zoom) { + const float scale = 1.0f + (ndof->dt * ndof->tvec[2]); + if (ED_view3d_camera_view_zoom_scale(rv3d, scale)) { + changed = true; + } + } + + if (changed) { + ED_region_tag_redraw(region); + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name NDOF Orbit/Translate Operator * \{ */ @@ -436,6 +500,11 @@ static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *ev return OPERATOR_CANCELLED; } + const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event); + if (camera_retval != OPERATOR_PASS_THROUGH) { + return camera_retval; + } + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewOpsData *vod; View3D *v3d; @@ -550,6 +619,11 @@ static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *e return OPERATOR_CANCELLED; } + const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event); + if (camera_retval != OPERATOR_PASS_THROUGH) { + return camera_retval; + } + const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); View3D *v3d = CTX_wm_view3d(C); RegionView3D *rv3d = CTX_wm_region_view3d(C); -- cgit v1.2.3 From ca1f879c0254ba40188a7cdd40f564b12a82cd43 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 18 Feb 2022 17:16:05 +1100 Subject: Cleanup: simplify viewmove_apply Check for a camera-view before checking if the view is locked to the cursor/object since the camera-view takes priority, it reads better to check that first. Also reuse the event offset variable. --- .../blender/editors/space_view3d/view3d_navigate.c | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/source/blender/editors/space_view3d/view3d_navigate.c b/source/blender/editors/space_view3d/view3d_navigate.c index d1e7b5891e9..21e0459f346 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.c +++ b/source/blender/editors/space_view3d/view3d_navigate.c @@ -544,26 +544,24 @@ static void axis_set_view(bContext *C, void viewmove_apply(ViewOpsData *vod, int x, int y) { - if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) { - vod->rv3d->ofs_lock[0] -= ((vod->prev.event_xy[0] - x) * 2.0f) / (float)vod->region->winx; - vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->region->winy; - } - else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) { - const float event_ofs[2] = { - vod->prev.event_xy[0] - x, - vod->prev.event_xy[1] - y, - }; + const float event_ofs[2] = { + vod->prev.event_xy[0] - x, + vod->prev.event_xy[1] - y, + }; + + if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) { ED_view3d_camera_view_pan(vod->region, event_ofs); } + else if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) { + vod->rv3d->ofs_lock[0] -= (event_ofs[0] * 2.0f) / (float)vod->region->winx; + vod->rv3d->ofs_lock[1] -= (event_ofs[1] * 2.0f) / (float)vod->region->winy; + } else { float dvec[3]; - float mval_f[2]; - mval_f[0] = x - vod->prev.event_xy[0]; - mval_f[1] = y - vod->prev.event_xy[1]; - ED_view3d_win_to_delta(vod->region, mval_f, dvec, vod->init.zfac); + ED_view3d_win_to_delta(vod->region, event_ofs, dvec, vod->init.zfac); - add_v3_v3(vod->rv3d->ofs, dvec); + sub_v3_v3(vod->rv3d->ofs, dvec); if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) { view3d_boxview_sync(vod->area, vod->region); -- cgit v1.2.3 From 6efdfeb8866a5583ad64e1c74f4089f0b16d55b4 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 18 Feb 2022 07:55:36 +0100 Subject: Image Engine: Store image usage to identity changes. Previous implementation had a copy of the image user, which doesn't contain all the data to identify changes. This patch introduces a new struct to store the data and can be extended with other data as well (color spaces, alpha settings). --- .../draw/engines/image/image_drawing_mode.hh | 2 +- .../draw/engines/image/image_instance_data.hh | 25 +++------- source/blender/draw/engines/image/image_usage.hh | 53 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 source/blender/draw/engines/image/image_usage.hh diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh index 795f0c0c45e..124f8057cfe 100644 --- a/source/blender/draw/engines/image/image_drawing_mode.hh +++ b/source/blender/draw/engines/image/image_drawing_mode.hh @@ -475,7 +475,7 @@ template class ScreenSpaceDrawingMode : public AbstractD method.update_screen_uv_bounds(); /* Check for changes in the image user compared to the last time. */ - instance_data->update_image_user(iuser); + instance_data->update_image_usage(iuser); /* Step: Update the GPU textures based on the changes in the image. */ instance_data->update_gpu_texture_allocations(); diff --git a/source/blender/draw/engines/image/image_instance_data.hh b/source/blender/draw/engines/image/image_instance_data.hh index 68c61a82e96..e5e19593e23 100644 --- a/source/blender/draw/engines/image/image_instance_data.hh +++ b/source/blender/draw/engines/image/image_instance_data.hh @@ -27,6 +27,7 @@ #include "image_private.hh" #include "image_shader_params.hh" #include "image_texture_info.hh" +#include "image_usage.hh" #include "image_wrappers.hh" #include "DRW_render.h" @@ -40,8 +41,8 @@ constexpr int SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN = 1; struct IMAGE_InstanceData { struct Image *image; - /** Copy of the last image user to detect iuser differences that require a full update. */ - struct ImageUser last_image_user; + /** Usage data of the previous time, to identify changes that require a full update. */ + ImageUsage last_usage; PartialImageUpdater partial_update; @@ -110,23 +111,11 @@ struct IMAGE_InstanceData { } } - void update_image_user(const ImageUser *image_user) + void update_image_usage(const ImageUser *image_user) { - short requested_pass = image_user ? image_user->pass : 0; - short requested_layer = image_user ? image_user->layer : 0; - short requested_view = image_user ? image_user->multi_index : 0; - /* 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 > 1) { - requested_view = 0; - } - - if (last_image_user.pass != requested_pass || last_image_user.layer != requested_layer || - last_image_user.multi_index != requested_view) { - - last_image_user.pass = requested_pass; - last_image_user.layer = requested_layer; - last_image_user.multi_index = requested_view; + ImageUsage usage(image_user); + if (last_usage != usage) { + last_usage = usage; reset_dirty_flag(true); } } diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh new file mode 100644 index 00000000000..fb85d88cd11 --- /dev/null +++ b/source/blender/draw/engines/image/image_usage.hh @@ -0,0 +1,53 @@ +/* + * 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 2022, Blender Foundation. + */ + +/** \file + * \ingroup draw_engine + */ + +#pragma once + +/** + * ImageUsage contains data of the image and image user to identify changes that require a rebuild + * of the texture slots. + */ +struct ImageUsage { + /** Render pass of the image that is used. */ + short pass = 0; + /** Layer of the image that is used.*/ + short layer = 0; + /** View of the image that is used. */ + short view = 0; + + ImageUsage() = default; + ImageUsage(const struct ImageUser *image_user) + { + pass = image_user ? image_user->pass : 0; + layer = image_user ? image_user->layer : 0; + view = image_user ? image_user->multi_index : 0; + } + + bool operator==(const ImageUsage &other) const + { + return memcmp(this, &other, sizeof(ImageUsage)) == 0; + } + bool operator!=(const ImageUsage &other) const + { + return !(*this == other); + } +}; -- cgit v1.2.3 From 400e57b64acc5fee1859a815d96db91e4e89aaa0 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 18 Feb 2022 08:11:51 +0100 Subject: Fix T95809: Compositor Node not directly updated in image editor. Image wasn't tagged to be dirty. --- source/blender/compositor/operations/COM_CompositorOperation.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/blender/compositor/operations/COM_CompositorOperation.cc b/source/blender/compositor/operations/COM_CompositorOperation.cc index 696dbb1807c..cffbf152f27 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.cc +++ b/source/blender/compositor/operations/COM_CompositorOperation.cc @@ -105,11 +105,10 @@ void CompositorOperation::deinit_execution() re = nullptr; } + Image *image = BKE_image_ensure_viewer(G.main, IMA_TYPE_R_RESULT, "Render Result"); + BKE_image_partial_update_mark_full_update(image); BLI_thread_lock(LOCK_DRAW_IMAGE); - BKE_image_signal(G.main, - BKE_image_ensure_viewer(G.main, IMA_TYPE_R_RESULT, "Render Result"), - nullptr, - IMA_SIGNAL_FREE); + BKE_image_signal(G.main, image, nullptr, IMA_SIGNAL_FREE); BLI_thread_unlock(LOCK_DRAW_IMAGE); } else { -- cgit v1.2.3 From fe26d188891f95d199e2559929204fddacea2ba8 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 18 Feb 2022 08:22:18 +0100 Subject: Fix T95809: Check color space changes to refresh image engine. Previous commit fixed the compositor node, this commit is related to the shader tree. Also checks if the color space or alpha mode have changed. --- source/blender/draw/engines/image/image_instance_data.hh | 2 +- source/blender/draw/engines/image/image_usage.hh | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/image/image_instance_data.hh b/source/blender/draw/engines/image/image_instance_data.hh index e5e19593e23..420f9396f3f 100644 --- a/source/blender/draw/engines/image/image_instance_data.hh +++ b/source/blender/draw/engines/image/image_instance_data.hh @@ -113,7 +113,7 @@ struct IMAGE_InstanceData { void update_image_usage(const ImageUser *image_user) { - ImageUsage usage(image_user); + ImageUsage usage(image, image_user); if (last_usage != usage) { last_usage = usage; reset_dirty_flag(true); diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh index fb85d88cd11..1eadee4481d 100644 --- a/source/blender/draw/engines/image/image_usage.hh +++ b/source/blender/draw/engines/image/image_usage.hh @@ -34,12 +34,18 @@ struct ImageUsage { /** View of the image that is used. */ short view = 0; + ColorManagedColorspaceSettings colorspace_settings; + /** IMA_ALPHA_* */ + char alpha_mode; + ImageUsage() = default; - ImageUsage(const struct ImageUser *image_user) + ImageUsage(const struct Image *image, const struct ImageUser *image_user) { pass = image_user ? image_user->pass : 0; layer = image_user ? image_user->layer : 0; view = image_user ? image_user->multi_index : 0; + colorspace_settings = image->colorspace_settings; + alpha_mode = image->alpha_mode; } bool operator==(const ImageUsage &other) const -- cgit v1.2.3 From 4ad4d8a59c0ab2f6fe13aca40e35c442b3761308 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 18 Feb 2022 08:30:10 +0100 Subject: Cleanup: Use compact license header. New file introduced in blender-v3.1-release branch. --- source/blender/draw/engines/image/image_usage.hh | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh index 1eadee4481d..2f3f0d23b6a 100644 --- a/source/blender/draw/engines/image/image_usage.hh +++ b/source/blender/draw/engines/image/image_usage.hh @@ -1,20 +1,5 @@ -/* - * 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 2022, Blender Foundation. - */ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ /** \file * \ingroup draw_engine -- cgit v1.2.3 From 964d3a38fac8fd9ba90e6a94d26546f2d0651116 Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Fri, 18 Feb 2022 17:00:43 +0900 Subject: XR: Enable Vive Focus 3 controller extension Allows controller-specific action bindings for the HTC Vive Focus 3 controller. Currently not supported by any OpenXR runtimes (save for the dedicated Focus 3 runtime in developer early-access: https://forum.vive.com/topic/11354-openxr-support-for-focus-3-early-access-application-form), but useful to have for the future. --- intern/ghost/intern/GHOST_XrContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index 5d8feb8e48a..716b60799f9 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -412,6 +412,7 @@ void GHOST_XrContext::getExtensionsToEnable( /* Interaction profile extensions. */ try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME); try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME); + try_ext.push_back(XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME); try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME); /* Controller model extension. */ -- cgit v1.2.3 From 61aaeb3745ad72c681c17f4535dd06ffaaf78c58 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 18 Feb 2022 09:12:41 +0100 Subject: Curves: initial brush system integration for curves sculpt mode This adds the boilerplate code that is necessary to use the tool/brush/paint systems in the new sculpt curves mode. Two temporary dummy tools are part of this patch. They do nothing and only serve to test the boilerplate. When the first actual tool is added, those dummy tools will be removed. Differential Revision: https://developer.blender.org/D14117 --- .../modules/bl_keymap_utils/keymap_from_toolbar.py | 1 + .../keyconfig/keymap_data/blender_default.py | 16 ++++ .../startup/bl_ui/space_toolsystem_toolbar.py | 16 ++++ source/blender/blenkernel/BKE_paint.h | 4 +- source/blender/blenkernel/intern/paint.c | 24 ++++++ source/blender/blenkernel/intern/paint_toolslots.c | 3 + source/blender/blenkernel/intern/scene.c | 26 +++++++ .../blender/draw/engines/overlay/overlay_engine.c | 3 + source/blender/editors/curves/CMakeLists.txt | 1 + source/blender/editors/curves/intern/curves_ops.cc | 7 ++ source/blender/editors/include/ED_curves_sculpt.h | 17 +++++ source/blender/editors/sculpt_paint/CMakeLists.txt | 2 + .../editors/sculpt_paint/curves_sculpt_intern.h | 15 ++++ .../editors/sculpt_paint/curves_sculpt_ops.cc | 85 ++++++++++++++++++++++ source/blender/editors/sculpt_paint/paint_ops.c | 6 ++ source/blender/editors/space_api/spacetypes.c | 2 + source/blender/editors/space_view3d/space_view3d.c | 6 ++ source/blender/editors/util/CMakeLists.txt | 1 + source/blender/makesdna/DNA_brush_enums.h | 6 ++ source/blender/makesdna/DNA_brush_types.h | 4 +- source/blender/makesdna/DNA_scene_types.h | 6 ++ source/blender/makesrna/RNA_enum_items.h | 1 + source/blender/makesrna/intern/rna_brush.c | 11 +++ source/blender/makesrna/intern/rna_sculpt_paint.c | 6 ++ .../blender/windowmanager/intern/wm_keymap_utils.c | 2 +- .../blender/windowmanager/intern/wm_toolsystem.c | 2 + 26 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 source/blender/editors/include/ED_curves_sculpt.h create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_intern.h create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_ops.cc diff --git a/release/scripts/modules/bl_keymap_utils/keymap_from_toolbar.py b/release/scripts/modules/bl_keymap_utils/keymap_from_toolbar.py index 604d1ffd547..6d41e290512 100644 --- a/release/scripts/modules/bl_keymap_utils/keymap_from_toolbar.py +++ b/release/scripts/modules/bl_keymap_utils/keymap_from_toolbar.py @@ -189,6 +189,7 @@ def generate(context, space_type, *, use_fallback_keys=True, use_reset=True): 'VERTEX_GPENCIL': "gpencil_vertex_tool", 'SCULPT_GPENCIL': "gpencil_sculpt_tool", 'WEIGHT_GPENCIL': "gpencil_weight_tool", + 'SCULPT_CURVES': "curves_sculpt_tool", }.get(mode, None) else: attr = None diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 8884b835130..7373615d5ff 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5450,6 +5450,21 @@ def km_font(params): return keymap +def km_sculpt_curves(params): + items = [] + keymap = ( + "Sculpt Curves", + {"space_type": 'EMPTY', "region_type": 'WINDOW'}, + {"items": items}, + ) + + items.extend([ + ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), + ]) + + return keymap + + def km_object_non_modal(params): items = [] keymap = ( @@ -7771,6 +7786,7 @@ def generate_keymaps(params=None): km_lattice(params), km_particle(params), km_font(params), + km_sculpt_curves(params), km_object_non_modal(params), # Modal maps. diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 338bf5a9d5d..f4aa4c50d76 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2306,6 +2306,19 @@ class _defs_gpencil_weight: ) +class _defs_curves_sculpt: + + @staticmethod + def generate_from_brushes(context): + return generate_from_enum_ex( + context, + idname_prefix="builtin_brush.", + icon_prefix="ops.curves.sculpt_", + type= bpy.types.Brush, + attr="curves_sculpt_tool", + ) + + class _defs_gpencil_vertex: @staticmethod @@ -3065,6 +3078,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): else () ), ], + 'SCULPT_CURVES': [ + _defs_curves_sculpt.generate_from_brushes, + ], } diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 1f37e95a023..8ab89b6c244 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -74,9 +74,11 @@ typedef enum ePaintMode { PAINT_MODE_VERTEX_GPENCIL = 7, PAINT_MODE_SCULPT_GPENCIL = 8, PAINT_MODE_WEIGHT_GPENCIL = 9, + /** Curves. */ + PAINT_MODE_SCULPT_CURVES = 10, /** Keep last. */ - PAINT_MODE_INVALID = 10, + PAINT_MODE_INVALID = 11, } ePaintMode; #define PAINT_MODE_HAS_BRUSH(mode) !ELEM(mode, PAINT_MODE_SCULPT_UV) diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index ffd0a03fc51..d42c8ea37d5 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -327,6 +327,9 @@ bool BKE_paint_ensure_from_paintmode(Scene *sce, ePaintMode mode) case PAINT_MODE_WEIGHT_GPENCIL: paint_ptr = (Paint **)&ts->gp_weightpaint; break; + case PAINT_MODE_SCULPT_CURVES: + paint_ptr = (Paint **)&ts->curves_sculpt; + break; case PAINT_MODE_INVALID: break; } @@ -362,6 +365,8 @@ Paint *BKE_paint_get_active_from_paintmode(Scene *sce, ePaintMode mode) return &ts->gp_sculptpaint->paint; case PAINT_MODE_WEIGHT_GPENCIL: return &ts->gp_weightpaint->paint; + case PAINT_MODE_SCULPT_CURVES: + return &ts->curves_sculpt->paint; case PAINT_MODE_INVALID: return NULL; default: @@ -394,6 +399,8 @@ const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(ePaintMode mode) return rna_enum_brush_gpencil_sculpt_types_items; case PAINT_MODE_WEIGHT_GPENCIL: return rna_enum_brush_gpencil_weight_types_items; + case PAINT_MODE_SCULPT_CURVES: + return rna_enum_brush_curves_sculpt_tool_items; case PAINT_MODE_INVALID: break; } @@ -422,6 +429,8 @@ const char *BKE_paint_get_tool_prop_id_from_paintmode(ePaintMode mode) return "gpencil_sculpt_tool"; case PAINT_MODE_WEIGHT_GPENCIL: return "gpencil_weight_tool"; + case PAINT_MODE_SCULPT_CURVES: + return "curves_sculpt_tool"; case PAINT_MODE_INVALID: break; } @@ -453,6 +462,8 @@ Paint *BKE_paint_get_active(Scene *sce, ViewLayer *view_layer) return &ts->gp_sculptpaint->paint; case OB_MODE_WEIGHT_GPENCIL: return &ts->gp_weightpaint->paint; + case OB_MODE_SCULPT_CURVES: + return &ts->curves_sculpt->paint; case OB_MODE_EDIT: return ts->uvsculpt ? &ts->uvsculpt->paint : NULL; default: @@ -573,6 +584,8 @@ ePaintMode BKE_paintmode_get_from_tool(const struct bToolRef *tref) return PAINT_MODE_SCULPT_GPENCIL; case CTX_MODE_WEIGHT_GPENCIL: return PAINT_MODE_WEIGHT_GPENCIL; + case CTX_MODE_SCULPT_CURVES: + return PAINT_MODE_SCULPT_CURVES; } } else if (tref->space_type == SPACE_IMAGE) { @@ -641,6 +654,10 @@ void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint) paint->runtime.tool_offset = offsetof(Brush, gpencil_weight_tool); paint->runtime.ob_mode = OB_MODE_WEIGHT_GPENCIL; } + else if (ts->curves_sculpt && paint == &ts->curves_sculpt->paint) { + paint->runtime.tool_offset = offsetof(Brush, curves_sculpt_tool); + paint->runtime.ob_mode = OB_MODE_SCULPT_CURVES; + } else { BLI_assert_unreachable(); } @@ -668,6 +685,8 @@ uint BKE_paint_get_brush_tool_offset_from_paintmode(const ePaintMode mode) return offsetof(Brush, gpencil_sculpt_tool); case PAINT_MODE_WEIGHT_GPENCIL: return offsetof(Brush, gpencil_weight_tool); + case PAINT_MODE_SCULPT_CURVES: + return offsetof(Brush, curves_sculpt_tool); case PAINT_MODE_INVALID: break; /* We don't use these yet. */ } @@ -1028,6 +1047,7 @@ bool BKE_paint_ensure(ToolSettings *ts, struct Paint **r_paint) (Paint *)ts->vpaint, (Paint *)ts->wpaint, (Paint *)ts->uvsculpt, + (Paint *)ts->curves_sculpt, (Paint *)&ts->imapaint)); #ifdef DEBUG struct Paint paint_test = **r_paint; @@ -1075,6 +1095,10 @@ bool BKE_paint_ensure(ToolSettings *ts, struct Paint **r_paint) UvSculpt *data = MEM_callocN(sizeof(*data), __func__); paint = &data->paint; } + else if ((CurvesSculpt **)r_paint == &ts->curves_sculpt) { + CurvesSculpt *data = MEM_callocN(sizeof(*data), __func__); + paint = &data->paint; + } else if (*r_paint == &ts->imapaint.paint) { paint = &ts->imapaint.paint; } diff --git a/source/blender/blenkernel/intern/paint_toolslots.c b/source/blender/blenkernel/intern/paint_toolslots.c index 04b70aae199..f35755021d2 100644 --- a/source/blender/blenkernel/intern/paint_toolslots.c +++ b/source/blender/blenkernel/intern/paint_toolslots.c @@ -98,6 +98,9 @@ void BKE_paint_toolslots_init_from_main(struct Main *bmain) if (ts->gp_weightpaint) { paint_toolslots_init_with_runtime(bmain, ts, &ts->gp_weightpaint->paint); } + if (ts->curves_sculpt) { + paint_toolslots_init_with_runtime(bmain, ts, &ts->curves_sculpt->paint); + } } } diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 7827c40e2c2..2eb4497aa0e 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -718,6 +718,16 @@ static void scene_foreach_toolsettings(LibraryForeachIDData *data, reader, &toolsett_old->gp_weightpaint->paint)); } + if (toolsett->curves_sculpt) { + BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_FUNCTION_CALL( + data, + do_undo_restore, + scene_foreach_paint(data, + &toolsett->curves_sculpt->paint, + do_undo_restore, + reader, + &toolsett_old->curves_sculpt->paint)); + } BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER(data, toolsett->gp_sculpt.guide.reference_object, @@ -972,6 +982,10 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres BLO_write_struct(writer, GpWeightPaint, tos->gp_weightpaint); BKE_paint_blend_write(writer, &tos->gp_weightpaint->paint); } + if (tos->curves_sculpt) { + BLO_write_struct(writer, CurvesSculpt, tos->curves_sculpt); + BKE_paint_blend_write(writer, &tos->curves_sculpt->paint); + } /* write grease-pencil custom ipo curve to file */ if (tos->gp_interpolate.custom_ipo) { BKE_curvemapping_blend_write(writer, tos->gp_interpolate.custom_ipo); @@ -1148,6 +1162,7 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id) direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->gp_vertexpaint); direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->gp_sculptpaint); direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->gp_weightpaint); + direct_link_paint_helper(reader, sce, (Paint **)&sce->toolsettings->curves_sculpt); BKE_paint_blend_read_data(reader, sce, &sce->toolsettings->imapaint.paint); @@ -1406,6 +1421,9 @@ static void scene_blend_read_lib(BlendLibReader *reader, ID *id) if (sce->toolsettings->gp_weightpaint) { BKE_paint_blend_read_lib(reader, sce, &sce->toolsettings->gp_weightpaint->paint); } + if (sce->toolsettings->curves_sculpt) { + BKE_paint_blend_read_lib(reader, sce, &sce->toolsettings->curves_sculpt->paint); + } if (sce->toolsettings->sculpt) { BLO_read_id_address(reader, sce->id.lib, &sce->toolsettings->sculpt->gravity_object); @@ -1726,6 +1744,10 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) ts->gp_weightpaint = MEM_dupallocN(ts->gp_weightpaint); BKE_paint_copy(&ts->gp_weightpaint->paint, &ts->gp_weightpaint->paint, flag); } + if (ts->curves_sculpt) { + ts->curves_sculpt = MEM_dupallocN(ts->curves_sculpt); + BKE_paint_copy(&ts->curves_sculpt->paint, &ts->curves_sculpt->paint, flag); + } BKE_paint_copy(&ts->imapaint.paint, &ts->imapaint.paint, flag); ts->particle.paintcursor = NULL; @@ -1781,6 +1803,10 @@ void BKE_toolsettings_free(ToolSettings *toolsettings) BKE_paint_free(&toolsettings->gp_weightpaint->paint); MEM_freeN(toolsettings->gp_weightpaint); } + if (toolsettings->curves_sculpt) { + BKE_paint_free(&toolsettings->curves_sculpt->paint); + MEM_freeN(toolsettings->curves_sculpt); + } BKE_paint_free(&toolsettings->imapaint.paint); /* free Grease Pencil interpolation curve */ diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index b41d9ce69ef..0fdf5b6e472 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -182,6 +182,7 @@ static void OVERLAY_cache_init(void *vedata) case CTX_MODE_WEIGHT_GPENCIL: OVERLAY_edit_gpencil_cache_init(vedata); break; + case CTX_MODE_SCULPT_CURVES: case CTX_MODE_OBJECT: break; default: @@ -661,6 +662,8 @@ static void OVERLAY_draw_scene(void *vedata) case CTX_MODE_WEIGHT_GPENCIL: OVERLAY_edit_gpencil_draw(vedata); break; + case CTX_MODE_SCULPT_CURVES: + break; default: break; } diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index d2b7dacbc20..93b21a46916 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -6,6 +6,7 @@ set(INC ../../blenlib ../../blentranslation ../../depsgraph + ../../functions ../../makesdna ../../makesrna ../../windowmanager diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index fdda8e636f7..0aa8c4b253e 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -10,9 +10,13 @@ #include "ED_object.h" #include "WM_api.h" +#include "WM_toolsystem.h" #include "WM_types.h" #include "BKE_context.h" +#include "BKE_paint.h" + +#include "DNA_scene_types.h" #include "RNA_access.h" #include "RNA_define.h" @@ -32,6 +36,7 @@ static bool curves_sculptmode_toggle_poll(bContext *C) static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); const bool is_mode_set = ob->mode == OB_MODE_SCULPT_CURVES; @@ -45,9 +50,11 @@ static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) ob->mode = OB_MODE_OBJECT; } else { + BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); ob->mode = OB_MODE_SCULPT_CURVES; } + WM_toolsystem_update_from_context_view3d(C); WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/include/ED_curves_sculpt.h b/source/blender/editors/include/ED_curves_sculpt.h new file mode 100644 index 00000000000..8aab1533e25 --- /dev/null +++ b/source/blender/editors/include/ED_curves_sculpt.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup editors + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void ED_operatortypes_sculpt_curves(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 55099e2e750..fcde780fc58 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -24,6 +24,7 @@ set(INC ) set(SRC + curves_sculpt_ops.cc paint_cursor.c paint_curve.c paint_curve_undo.c @@ -66,6 +67,7 @@ set(SRC sculpt_undo.c sculpt_uv.c + curves_sculpt_intern.h paint_intern.h sculpt_intern.h ) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.h b/source/blender/editors/sculpt_paint/curves_sculpt_intern.h new file mode 100644 index 00000000000..6a96a8e0e9f --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +struct bContext; + +#ifdef __cplusplus +extern "C" { +#endif + +bool CURVES_SCULPT_mode_poll(struct bContext *C); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc new file mode 100644 index 00000000000..ead016174c9 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_paint.h" + +#include "WM_api.h" + +#include "ED_curves_sculpt.h" + +#include "curves_sculpt_intern.h" +#include "paint_intern.h" + +bool CURVES_SCULPT_mode_poll(struct bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && ob->mode & OB_MODE_SCULPT_CURVES; +} + +static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) +{ + out[0] = mouse[0]; + out[1] = mouse[1]; + out[2] = 0; + UNUSED_VARS(C); + return true; +} + +static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +{ + UNUSED_VARS(C, op, mouse); + return true; +} + +static void stroke_update_step(bContext *C, PaintStroke *stroke, PointerRNA *itemptr) +{ + UNUSED_VARS(C, stroke, itemptr); +} + +static void stroke_done(const bContext *C, PaintStroke *stroke) +{ + UNUSED_VARS(C, stroke); +} + +static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PaintStroke *stroke = paint_stroke_new(C, + op, + stroke_get_location, + stroke_test_start, + stroke_update_step, + nullptr, + stroke_done, + event->type); + op->customdata = stroke; + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static void sculpt_curves_stroke_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op); +} + +static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) +{ + ot->name = "Stroke Curves Sculpt"; + ot->idname = "SCULPT_CURVES_OT_brush_stroke"; + ot->description = "Sculpt curves using a brush"; + + ot->invoke = sculpt_curves_stroke_invoke; + ot->modal = paint_stroke_modal; + ot->cancel = sculpt_curves_stroke_cancel; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + paint_stroke_operator_properties(ot); +} + +void ED_operatortypes_sculpt_curves() +{ + WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); +} diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index 1b876235ad0..926a564184a 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -39,6 +39,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "curves_sculpt_intern.h" #include "paint_intern.h" #include "sculpt_intern.h" @@ -758,6 +759,7 @@ static const ePaintMode brush_select_paint_modes[] = { PAINT_MODE_VERTEX_GPENCIL, PAINT_MODE_SCULPT_GPENCIL, PAINT_MODE_WEIGHT_GPENCIL, + PAINT_MODE_SCULPT_CURVES, }; static int brush_select_exec(bContext *C, wmOperator *op) @@ -1388,6 +1390,10 @@ void ED_keymap_paint(wmKeyConfig *keyconf) keymap = paint_stroke_modal_keymap(keyconf); WM_modalkeymap_assign(keymap, "SCULPT_OT_brush_stroke"); + /* Curves Sculpt mode. */ + keymap = WM_keymap_ensure(keyconf, "Sculpt Curves", 0, 0); + keymap->poll = CURVES_SCULPT_mode_poll; + /* sculpt expand. */ sculpt_expand_modal_keymap(keyconf); } diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 897091731a4..d53fe2efb03 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -29,6 +29,7 @@ #include "ED_clip.h" #include "ED_curve.h" #include "ED_curves.h" +#include "ED_curves_sculpt.h" #include "ED_fileselect.h" #include "ED_geometry.h" #include "ED_gizmo_library.h" @@ -96,6 +97,7 @@ void ED_spacetypes_init(void) ED_operatortypes_mesh(); ED_operatortypes_geometry(); ED_operatortypes_sculpt(); + ED_operatortypes_sculpt_curves(); ED_operatortypes_uvedit(); ED_operatortypes_paint(); ED_operatortypes_physics(); diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 7addda0f8c3..056e8953c81 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -413,6 +413,9 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region) keymap = WM_keymap_ensure(wm->defaultconf, "Particle", 0, 0); WM_event_add_keymap_handler(®ion->handlers, keymap); + keymap = WM_keymap_ensure(wm->defaultconf, "Sculpt Curves", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + /* editfont keymap swallows all... */ keymap = WM_keymap_ensure(wm->defaultconf, "Font", 0, 0); WM_event_add_keymap_handler(®ion->handlers, keymap); @@ -1523,6 +1526,9 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C, case CTX_MODE_VERTEX_GPENCIL: ARRAY_SET_ITEMS(contexts, ".greasepencil_vertex"); break; + case CTX_MODE_SCULPT_CURVES: + ARRAY_SET_ITEMS(contexts, ".curves_sculpt"); + break; default: break; } diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index 2300e664dfa..7b4551fdb0c 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -40,6 +40,7 @@ set(SRC ../include/ED_clip.h ../include/ED_curve.h ../include/ED_curves.h + ../include/ED_curves_sculpt.h ../include/ED_datafiles.h ../include/ED_file_indexer.h ../include/ED_fileselect.h diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 0667e508a82..bca177f6d7a 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -455,6 +455,12 @@ typedef enum eBrushUVSculptTool { UV_SCULPT_TOOL_PINCH = 2, } eBrushUVSculptTool; +/* Brush.curves_sculpt_tool. */ +typedef enum eBrushCurvesSculptTool { + CURVES_SCULPT_TOOL_TEST1 = 0, + CURVES_SCULPT_TOOL_TEST2 = 1, +} eBrushCurvesSculptTool; + /** When #BRUSH_ACCUMULATE is used */ #define SCULPT_TOOL_HAS_ACCUMULATE(t) \ ELEM(t, \ diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 77c49393029..fe80220b1dd 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -256,7 +256,9 @@ typedef struct Brush { char gpencil_sculpt_tool; /** Active grease pencil weight tool. */ char gpencil_weight_tool; - char _pad1[6]; + /** Active curves sculpt tool. */ + char curves_sculpt_tool; + char _pad1[5]; float autosmooth_factor; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 672af019177..92ddc1a71c4 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -995,6 +995,10 @@ typedef struct Sculpt { struct Object *gravity_object; } Sculpt; +typedef struct CurvesSculpt { + Paint paint; +} CurvesSculpt; + typedef struct UvSculpt { Paint paint; } UvSculpt; @@ -1380,6 +1384,8 @@ typedef struct ToolSettings { GpSculptPaint *gp_sculptpaint; /** Gpencil weight paint. */ GpWeightPaint *gp_weightpaint; + /** Curves sculpt. */ + CurvesSculpt *curves_sculpt; /* Vertex group weight - used only for editmode, not weight * paint */ diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 4b68416f5d7..eb899aae3de 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -107,6 +107,7 @@ DEF_ENUM(rna_enum_brush_gpencil_types_items) DEF_ENUM(rna_enum_brush_gpencil_vertex_types_items) DEF_ENUM(rna_enum_brush_gpencil_sculpt_types_items) DEF_ENUM(rna_enum_brush_gpencil_weight_types_items) +DEF_ENUM(rna_enum_brush_curves_sculpt_tool_items); DEF_ENUM(rna_enum_brush_image_tool_items) DEF_ENUM(rna_enum_axis_xy_items) diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index f3c7d2747ac..667114a3598 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -243,6 +243,12 @@ const EnumPropertyItem rna_enum_brush_gpencil_weight_types_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = { + {CURVES_SCULPT_TOOL_TEST1, "TEST1", ICON_NONE, "Test 1", ""}, + {CURVES_SCULPT_TOOL_TEST2, "TEST2", ICON_NONE, "Test 2", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #ifndef RNA_RUNTIME static EnumPropertyItem rna_enum_gpencil_brush_eraser_modes_items[] = { {GP_BRUSH_ERASER_SOFT, @@ -2312,6 +2318,11 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Grease Pencil Weight Paint Tool", ""); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "curves_sculpt_tool", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_brush_curves_sculpt_tool_items); + RNA_def_property_ui_text(prop, "Curves Sculpt Tool", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + /** End per mode tool properties. */ prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 1e7c67ef95e..cb105c22987 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -352,6 +352,12 @@ static bool rna_Brush_mode_with_tool_poll(PointerRNA *ptr, PointerRNA value) } mode = OB_MODE_WEIGHT_GPENCIL; } + else if (paint_contains_brush_slot(&ts->curves_sculpt->paint, tslot, &slot_index)) { + if (slot_index != brush->curves_sculpt_tool) { + return false; + } + mode = OB_MODE_SCULPT_CURVES; + } return brush->ob_mode & mode; } diff --git a/source/blender/windowmanager/intern/wm_keymap_utils.c b/source/blender/windowmanager/intern/wm_keymap_utils.c index 162246798de..b6982b7a947 100644 --- a/source/blender/windowmanager/intern/wm_keymap_utils.c +++ b/source/blender/windowmanager/intern/wm_keymap_utils.c @@ -158,7 +158,7 @@ wmKeyMap *WM_keymap_guess_from_context(const bContext *C) km_id = "Grease Pencil Stroke Vertex Mode"; break; case CTX_MODE_SCULPT_CURVES: - km_id = "Curves Sculpt Mode"; + km_id = "Curves Sculpt"; break; } } diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c index 4ae935b14f2..e911a2801e5 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.c +++ b/source/blender/windowmanager/intern/wm_toolsystem.c @@ -663,6 +663,8 @@ static const char *toolsystem_default_tool(const bToolKey *tkey) return "builtin_brush.Weight"; case CTX_MODE_VERTEX_GPENCIL: return "builtin_brush.Draw"; + case CTX_MODE_SCULPT_CURVES: + return "builtin_brush.Test 1"; /* end temporary hack. */ case CTX_MODE_PARTICLE: -- cgit v1.2.3 From 8b4da9a191b9a35c743209d38c99c000f8490501 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 18 Feb 2022 10:14:34 +0100 Subject: Fix strict compilation warnings --- intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc | 4 ++-- source/blender/draw/intern/draw_cache_impl_subdivision.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc index ee4eaeaec5d..acf628c7035 100644 --- a/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc +++ b/intern/opensubdiv/internal/evaluator/gl_compute_evaluator.cc @@ -140,8 +140,8 @@ GLStencilTableSSBO::~GLStencilTableSSBO() GLComputeEvaluator::GLComputeEvaluator() : _workGroupSize(64), _patchArraysSSBO(0) { - memset(&_stencilKernel, 0, sizeof(_stencilKernel)); - memset(&_patchKernel, 0, sizeof(_patchKernel)); + memset((void *)&_stencilKernel, 0, sizeof(_stencilKernel)); + memset((void *)&_patchKernel, 0, sizeof(_patchKernel)); } GLComputeEvaluator::~GLComputeEvaluator() diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc index 60c07db90b7..e8ff9a508a2 100644 --- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc +++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc @@ -1818,9 +1818,9 @@ static bool draw_subdiv_create_requested_buffers(const Scene *scene, const float obmat[4][4], const bool do_final, const bool do_uvedit, - const bool use_subsurf_fdots, + const bool /*use_subsurf_fdots*/, const ToolSettings *ts, - const bool use_hide, + const bool /*use_hide*/, OpenSubdiv_EvaluatorCache *evaluator_cache) { SubsurfModifierData *smd = BKE_object_get_last_subsurf_modifier(ob); -- cgit v1.2.3 From 5f16e24cc9ac4f620df2ab65d5a7b9ae4f99f203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Fri, 18 Feb 2022 11:16:02 +0100 Subject: Curves: add initial edit mode support This adds initial support for edit mode for the experimental new curves object. For now we can only toggle in and out of the mode, no real interraction is possible. This patch also adds empty menus in edit mode. Those were added mainly to quiet warnings as the menus are programmatically added to the edit mode based on the object type and context. Ref T95769 Reviewed By: JacquesLucke, HooglyBoogly Maniphest Tasks: T95769 Differential Revision: https://developer.blender.org/D14136 --- release/scripts/startup/bl_ui/space_view3d.py | 16 ++++++++++++++++ source/blender/blenkernel/BKE_context.h | 1 + source/blender/blenkernel/intern/context.c | 3 +++ source/blender/draw/engines/overlay/overlay_engine.c | 1 + source/blender/editors/object/object_edit.c | 4 ++++ source/blender/editors/object/object_modes.c | 2 +- source/blender/editors/space_view3d/space_view3d.c | 3 +++ source/blender/makesdna/DNA_object_types.h | 2 +- source/blender/makesrna/intern/rna_context.c | 1 + source/blender/windowmanager/WM_types.h | 1 + source/blender/windowmanager/intern/wm_keymap_utils.c | 3 +++ 11 files changed, 35 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 61ba6189be4..4174ec95e83 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -1899,6 +1899,13 @@ class VIEW3D_MT_select_paint_mask_vertex(Menu): layout.operator("paint.vert_select_ungrouped", text="Ungrouped Vertices") +class VIEW3D_MT_select_edit_curves(Menu): + bl_label = "Select" + + def draw(self, _context): + pass + + class VIEW3D_MT_angle_control(Menu): bl_label = "Angle Control" @@ -5123,6 +5130,13 @@ class VIEW3D_MT_edit_gpencil_showhide(Menu): layout.operator("gpencil.hide", text="Hide Inactive Layers").unselected = True +class VIEW3D_MT_edit_curves(Menu): + bl_label = "Curves" + + def draw(self, _context): + pass + + class VIEW3D_MT_object_mode_pie(Menu): bl_label = "Mode" @@ -7544,6 +7558,7 @@ classes = ( VIEW3D_MT_select_gpencil, VIEW3D_MT_select_paint_mask, VIEW3D_MT_select_paint_mask_vertex, + VIEW3D_MT_select_edit_curves, VIEW3D_MT_angle_control, VIEW3D_MT_mesh_add, VIEW3D_MT_curve_add, @@ -7666,6 +7681,7 @@ classes = ( VIEW3D_MT_edit_armature_names, VIEW3D_MT_edit_armature_delete, VIEW3D_MT_edit_gpencil_transform, + VIEW3D_MT_edit_curves, VIEW3D_MT_object_mode_pie, VIEW3D_MT_view_pie, VIEW3D_MT_transform_gizmo_pie, diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h index 52de39f3ed9..584568b4a5e 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -106,6 +106,7 @@ typedef enum eContextObjectMode { CTX_MODE_EDIT_ARMATURE, CTX_MODE_EDIT_METABALL, CTX_MODE_EDIT_LATTICE, + CTX_MODE_EDIT_CURVES, CTX_MODE_POSE, CTX_MODE_SCULPT, CTX_MODE_PAINT_WEIGHT, diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index d1374958763..7fcbbfd316f 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -1160,6 +1160,8 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit, return CTX_MODE_EDIT_METABALL; case OB_LATTICE: return CTX_MODE_EDIT_LATTICE; + case OB_CURVES: + return CTX_MODE_EDIT_CURVES; } } else { @@ -1227,6 +1229,7 @@ static const char *data_mode_strings[] = { "armature_edit", "mball_edit", "lattice_edit", + "curves_edit", "posemode", "sculpt_mode", "weightpaint", diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index 0fdf5b6e472..e7bb1bf3862 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -184,6 +184,7 @@ static void OVERLAY_cache_init(void *vedata) break; case CTX_MODE_SCULPT_CURVES: case CTX_MODE_OBJECT: + case CTX_MODE_EDIT_CURVES: break; default: BLI_assert_msg(0, "Draw mode invalid"); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index fd40ac7bc7c..e4a026d3205 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -805,6 +805,10 @@ bool ED_object_editmode_enter_ex(Main *bmain, Scene *scene, Object *ob, int flag WM_main_add_notifier(NC_SCENE | ND_MODE | NS_EDITMODE_CURVE, scene); } + else if (ob->type == OB_CURVES) { + ok = true; + WM_main_add_notifier(NC_SCENE | ND_MODE | NS_EDITMODE_CURVES, scene); + } if (ok) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index 509e496c39a..b12dd00c658 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -143,7 +143,7 @@ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) } break; case OB_CURVES: - if (mode & (OB_MODE_SCULPT_CURVES)) { + if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT_CURVES)) { return true; } break; diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 056e8953c81..4656540c19b 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1478,6 +1478,9 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C, case CTX_MODE_EDIT_CURVE: ARRAY_SET_ITEMS(contexts, ".curve_edit"); break; + case CTX_MODE_EDIT_CURVES: + ARRAY_SET_ITEMS(contexts, ".curves_edit"); + break; case CTX_MODE_EDIT_SURFACE: ARRAY_SET_ITEMS(contexts, ".curve_edit"); break; diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 96c60fcac97..8c235f403f0 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -515,7 +515,7 @@ enum { OB_VOLUME)) #define OB_TYPE_SUPPORT_VGROUP(_type) (ELEM(_type, OB_MESH, OB_LATTICE, OB_GPENCIL)) #define OB_TYPE_SUPPORT_EDITMODE(_type) \ - (ELEM(_type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_LATTICE, OB_ARMATURE)) + (ELEM(_type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_LATTICE, OB_ARMATURE, OB_CURVES)) #define OB_TYPE_SUPPORT_PARVERT(_type) (ELEM(_type, OB_MESH, OB_SURF, OB_CURVE, OB_LATTICE)) /** Matches #OB_TYPE_SUPPORT_EDITMODE. */ diff --git a/source/blender/makesrna/intern/rna_context.c b/source/blender/makesrna/intern/rna_context.c index bc4bd9ad3d6..e723be2ab71 100644 --- a/source/blender/makesrna/intern/rna_context.c +++ b/source/blender/makesrna/intern/rna_context.c @@ -21,6 +21,7 @@ const EnumPropertyItem rna_enum_context_mode_items[] = { {CTX_MODE_EDIT_MESH, "EDIT_MESH", 0, "Mesh Edit", ""}, {CTX_MODE_EDIT_CURVE, "EDIT_CURVE", 0, "Curve Edit", ""}, + {CTX_MODE_EDIT_CURVES, "EDIT_CURVES", 0, "Curves Edit", ""}, {CTX_MODE_EDIT_SURFACE, "EDIT_SURFACE", 0, "Surface Edit", ""}, {CTX_MODE_EDIT_TEXT, "EDIT_TEXT", 0, "Text Edit", ""}, /* PARSKEL reuse will give issues */ diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 4799483157c..a95c2ee2bb2 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -470,6 +470,7 @@ typedef struct wmNotifier { #define NS_EDITMODE_ARMATURE (8 << 8) #define NS_MODE_POSE (9 << 8) #define NS_MODE_PARTICLE (10 << 8) +#define NS_EDITMODE_CURVES (11 << 8) /* subtype 3d view editing */ #define NS_VIEW3D_GPU (16 << 8) diff --git a/source/blender/windowmanager/intern/wm_keymap_utils.c b/source/blender/windowmanager/intern/wm_keymap_utils.c index b6982b7a947..eac8a1f5211 100644 --- a/source/blender/windowmanager/intern/wm_keymap_utils.c +++ b/source/blender/windowmanager/intern/wm_keymap_utils.c @@ -106,6 +106,9 @@ wmKeyMap *WM_keymap_guess_from_context(const bContext *C) case CTX_MODE_EDIT_CURVE: km_id = "Curve"; break; + case CTX_MODE_EDIT_CURVES: + km_id = "Curves"; + break; case CTX_MODE_EDIT_SURFACE: km_id = "Curve"; break; -- cgit v1.2.3 From 02f4d63dcc7b74fbacfb4e5bcdd01766563573f5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 18 Feb 2022 12:11:59 +0100 Subject: Fix broken shapekeys: check for 'NULL' `from` pointer too. Add check for `NULL` `from` pointer to `BLO_main_validate_shapekeys`, and delete these shapekeys, as they are fully invalid and impossible to recover. Found in a studio production file (`animation test/snow_parkour/shots/0040/0040.lighting.blend`, svn rev `1111`). Would be nice to know how this was generated too... --- source/blender/blenloader/intern/blend_validate.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/blender/blenloader/intern/blend_validate.c b/source/blender/blenloader/intern/blend_validate.c index 333f6e341c6..c8dd27f0c9c 100644 --- a/source/blender/blenloader/intern/blend_validate.c +++ b/source/blender/blenloader/intern/blend_validate.c @@ -195,5 +195,20 @@ bool BLO_main_validate_shapekeys(Main *bmain, ReportList *reports) BKE_main_unlock(bmain); + /* NOTE: #BKE_id_delete also locks `bmain`, so we need to do this loop outside of the lock here. + */ + LISTBASE_FOREACH_MUTABLE (Key *, shapekey, &bmain->shapekeys) { + if (shapekey->from != NULL) { + continue; + } + + BKE_reportf(reports, + RPT_ERROR, + "Shapekey %s has an invalid 'from' pointer (%p), it will be deleted", + shapekey->id.name, + shapekey->from); + BKE_id_delete(bmain, shapekey); + } + return is_valid; } -- cgit v1.2.3 From 40cddcd917a7de26a89908b82c6583ace8f76641 Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Fri, 18 Feb 2022 23:21:53 +0900 Subject: Cleanup: Fix incompatible pointer types warnings --- source/blender/windowmanager/xr/intern/wm_xr_action.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/windowmanager/xr/intern/wm_xr_action.c b/source/blender/windowmanager/xr/intern/wm_xr_action.c index 0ce0837d83a..6750e7a7d77 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_action.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_action.c @@ -254,7 +254,7 @@ bool WM_xr_action_create(wmXrData *xr, GHOST_XrActionInfo info = { .name = action_name, .count_subaction_paths = count, - .subaction_paths = subaction_paths, + .subaction_paths = (const char **)subaction_paths, .states = action->states, .float_thresholds = action->float_thresholds, .axis_flags = (int16_t *)action->axis_flags, @@ -369,7 +369,7 @@ bool WM_xr_action_binding_create(wmXrData *xr, .action_name = action_name, .profile_path = profile_path, .count_subaction_paths = count, - .subaction_paths = subaction_paths, + .subaction_paths = (const char **)subaction_paths, .bindings = binding_infos, }; -- cgit v1.2.3 From e4b7d52fe4fe3076f5f68ff575c200f5cf16e416 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 18 Feb 2022 14:31:39 +0100 Subject: Fix graphics interop resources leak in Cycles When new display driver is given to the PathTrace ensure that there are no GPU resources used from it by the work. This solves graphics interop descriptors leak. This aqlso fixes Invalid graphics context in cuGraphicsUnregisterResource error when doing final render on the display GPU. Fixes T95837: Regression: GPU memory accumulation in Cycles render Fixes T95733: Cycles Cuda/Optix error message with multi GPU devices. (Invalid graphics context in cuGraphicsUnregisterResource) Fixes T95651: GPU error (Invalid graphics context in cuGraphicsUnregisterResource) Fixes T95631: VRAM is not being freed when rendering (Invalid graphics context in cuGraphicsUnregisterResource) Fixes T89747: Cycles Render - Textures Disappear then Crashes the Render Maniphest Tasks: T95837, T95733, T95651, T95631, T89747 Differential Revision: https://developer.blender.org/D14146 --- intern/cycles/blender/session.cpp | 9 +++++++-- intern/cycles/integrator/path_trace.cpp | 26 ++++++++++++++++++-------- intern/cycles/integrator/path_trace.h | 3 +++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/intern/cycles/blender/session.cpp b/intern/cycles/blender/session.cpp index c81a0f4edb2..405f22ad67a 100644 --- a/intern/cycles/blender/session.cpp +++ b/intern/cycles/blender/session.cpp @@ -506,8 +506,13 @@ void BlenderSession::render_frame_finish() session->set_output_driver(nullptr); session->full_buffer_written_cb = function_null; - /* The display driver holds OpenGL resources which belong to an OpenGL context held by the render - * engine on Blender side. Force destruction of those resources. */ + /* The display driver is the source of drawing context for both drawing and possible graphics + * interop objects in the path trace. Once the frame is finished the OpenGL context might be + * freed form Blender side. Need to ensure that all GPU resources are freed prior to that + * point. + * Ideally would only do this when OpenGL context is actually destroyed, but there is no way to + * know when this happens (at least in the code at the time when this comment was written). + * The penalty of re-creating resources on every frame is unlikely to be noticed. */ display_driver_ = nullptr; session->set_display_driver(nullptr); diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index fd697836f52..d8ee1883a06 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -67,14 +67,7 @@ PathTrace::PathTrace(Device *device, PathTrace::~PathTrace() { - /* Destroy any GPU resource which was used for graphics interop. - * Need to have access to the PathTraceDisplay as it is the only source of drawing context which - * is used for interop. */ - if (display_) { - for (auto &&path_trace_work : path_trace_works_) { - path_trace_work->destroy_gpu_resources(display_.get()); - } - } + destroy_gpu_resources(); } void PathTrace::load_kernels() @@ -572,6 +565,11 @@ void PathTrace::set_output_driver(unique_ptr driver) void PathTrace::set_display_driver(unique_ptr driver) { + /* The display driver is the source of the drawing context which might be used by + * path trace works. Make sure there is no graphics interop using resources from + * the old display, as it might no longer be available after this call. */ + destroy_gpu_resources(); + if (driver) { display_ = make_unique(move(driver)); } @@ -1088,6 +1086,18 @@ bool PathTrace::has_denoised_result() const return render_state_.has_denoised_result; } +void PathTrace::destroy_gpu_resources() +{ + /* Destroy any GPU resource which was used for graphics interop. + * Need to have access to the PathTraceDisplay as it is the only source of drawing context which + * is used for interop. */ + if (display_) { + for (auto &&path_trace_work : path_trace_works_) { + path_trace_work->destroy_gpu_resources(display_.get()); + } + } +} + /* -------------------------------------------------------------------- * Report generation. */ diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index bb41c8c3210..7849d69b646 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -239,6 +239,9 @@ class PathTrace { void progress_set_status(const string &status, const string &substatus = ""); + /* Destroy GPU resources (such as graphics interop) used by work. */ + void destroy_gpu_resources(); + /* Pointer to a device which is configured to be used for path tracing. If multiple devices * are configured this is a `MultiDevice`. */ Device *device_ = nullptr; -- cgit v1.2.3 From d9fe565c852135edbe51ec632df593db2579cc9b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 18 Feb 2022 16:06:15 +0100 Subject: LibOverride: Fix potential broken cases re ShapeKeys when finding hierarchy root ID. In some cases broken files could lead to selecting a shapekey as hierarchy root ID, which is not allowed. Found while investigating Blender studio issues in Snow parkour short. --- source/blender/blenkernel/intern/lib_override.c | 44 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 8a988bd30b5..08ff1eb80de 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -72,12 +72,19 @@ static void lib_override_library_property_operation_clear( IDOverrideLibraryPropertyOperation *opop); /** Get override data for a given ID. Needed because of our beloved shape keys snowflake. */ -BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id) +BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id, ID **r_owner_id) { + if (r_owner_id != NULL) { + *r_owner_id = id; + } if (id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); if (id_type->owner_get != NULL) { - return id_type->owner_get(bmain, id)->override_library; + ID *owner_id = id_type->owner_get(bmain, id); + if (r_owner_id != NULL) { + *r_owner_id = owner_id; + } + return owner_id->override_library; } BLI_assert_msg(0, "IDTypeInfo of liboverride-embedded ID with no owner getter"); } @@ -832,8 +839,8 @@ static void lib_override_overrides_group_tag_recursive(LibOverrideGroupTagData * continue; } - Library *reference_lib = lib_override_get(bmain, id_owner)->reference->lib; - ID *to_id_reference = lib_override_get(bmain, to_id)->reference; + Library *reference_lib = lib_override_get(bmain, id_owner, NULL)->reference->lib; + ID *to_id_reference = lib_override_get(bmain, to_id, NULL)->reference; if (to_id_reference->lib != reference_lib) { /* We do not override data-blocks from other libraries, nor do we process them. */ continue; @@ -1102,11 +1109,18 @@ static ID *lib_override_root_find(Main *bmain, ID *id, const int curr_level, int MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id); BLI_assert(entry != NULL); - if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { - /* This ID has already been processed. */ - BLI_assert(id->override_library != NULL); - *r_best_level = curr_level; - return id->override_library->hierarchy_root; + if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) { + if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + /* This ID has already been processed. */ + *r_best_level = curr_level; + return id->override_library->hierarchy_root; + } + + BLI_assert(id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE); + ID *id_owner; + int best_level_placeholder = 0; + lib_override_get(bmain, id, &id_owner); + return lib_override_root_find(bmain, id_owner, curr_level + 1, &best_level_placeholder); } /* This way we won't process again that ID, should we encounter it again through another * relationship hierarchy. */ @@ -1140,7 +1154,17 @@ static ID *lib_override_root_find(Main *bmain, ID *id, const int curr_level, int } } + if (!ID_IS_OVERRIDE_LIBRARY_REAL(best_root_id_candidate)) { + BLI_assert(id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE); + ID *id_owner; + int best_level_placeholder = 0; + lib_override_get(bmain, best_root_id_candidate, &id_owner); + best_root_id_candidate = lib_override_root_find( + bmain, id_owner, curr_level + 1, &best_level_placeholder); + } + BLI_assert(best_root_id_candidate != NULL); + BLI_assert((best_root_id_candidate->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) == 0); *r_best_level = best_level_candidate; return best_root_id_candidate; @@ -1430,7 +1454,7 @@ static bool lib_override_library_resync(Main *bmain, /* While this should not happen in typical cases (and won't be properly supported here), * user is free to do all kind of very bad things, including having different local * overrides of a same linked ID in a same hierarchy. */ - IDOverrideLibrary *id_override_library = lib_override_get(bmain, id); + IDOverrideLibrary *id_override_library = lib_override_get(bmain, id, NULL); ID *reference_id = id_override_library->reference; if (GS(reference_id->name) != GS(id->name)) { switch (GS(id->name)) { -- cgit v1.2.3 From aab15616908c770c40dcb0f5ba7903ee5ee895a5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 18 Feb 2022 16:08:43 +0100 Subject: LibOverride: Fix resync sometimes deleting root ID of other liboverrides. While this should not happen in theory, very bad/broken/dirty files can lead to such situations. So we need to re-ensure valid root IDs after resync (for now, done after each 'library indirect level' pass of resync, this may not be 100% bulletproof though, time will say). Found while investigating Blender studio issues in Snow parkour short. --- source/blender/blenkernel/intern/lib_override.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 08ff1eb80de..459e64c55f9 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -2160,6 +2160,10 @@ static void lib_override_library_main_resync_on_library_indirect_level( BLI_ghash_free(id_roots, NULL, MEM_freeN); + /* In some fairly rare (and degenerate) cases, some root ID from other liboverrides may have been + * freed, and therefore set to NULL. Attempt to fix this as best as possible. */ + BKE_lib_override_library_main_hierarchy_root_ensure(bmain); + if (do_reports_recursive_resync_timing) { reports->duration.lib_overrides_recursive_resync += PIL_check_seconds_timer() - init_time; } -- cgit v1.2.3 From ceea3d0f809b0579eb634fc5206a678d996327f6 Mon Sep 17 00:00:00 2001 From: Sayed Amin Date: Fri, 18 Feb 2022 16:23:22 +0100 Subject: Fix T95135: improve error filtering non-existant anim data If there is no animation at all, or it's all hidden, the Euler Filter operators poll now fails with a message that explains this a bit more, instead of just the generic "context is wrong" error. Reviewed By: sybren Maniphest Tasks: T95135 Differential Revision: https://developer.blender.org/D13967 --- source/blender/editors/space_graph/graph_utils.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/editors/space_graph/graph_utils.c b/source/blender/editors/space_graph/graph_utils.c index 4351186dc6f..9f934e47ebb 100644 --- a/source/blender/editors/space_graph/graph_utils.c +++ b/source/blender/editors/space_graph/graph_utils.c @@ -185,6 +185,7 @@ bool graphop_editable_keyframes_poll(bContext *C) filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); if (items == 0) { + CTX_wm_operator_poll_msg_set(C, "There is no animation data to operate on"); return found; } -- cgit v1.2.3 From 734c6a4405f21078270e71a4c2f0e98e74173ad7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 09:24:28 -0600 Subject: Nodes: Update dependency graph when removing some nodes When removing a node that has a dependence on an ID, like the object info node, the dependency graph relations weren't updated. This can cause unexpected performance issues if a complex node tree continues to depend on an ID that it doesn't actually use anymore. To fix this case, tag relations for an update if the node has a data-block socket. Fixes part of T88332 Differential Revision: https://developer.blender.org/D14121 --- source/blender/blenkernel/intern/node.cc | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index c8e82302787..ad24b3c0eff 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1562,13 +1562,15 @@ static void socket_id_user_increment(bNodeSocket *sock) } } -static void socket_id_user_decrement(bNodeSocket *sock) +/** \return True if the socket had an ID default value. */ +static bool socket_id_user_decrement(bNodeSocket *sock) { switch ((eNodeSocketDatatype)sock->type) { case SOCK_OBJECT: { bNodeSocketValueObject *default_value = (bNodeSocketValueObject *)sock->default_value; if (default_value->value != nullptr) { id_us_min(&default_value->value->id); + return true; } break; } @@ -1576,6 +1578,7 @@ static void socket_id_user_decrement(bNodeSocket *sock) bNodeSocketValueImage *default_value = (bNodeSocketValueImage *)sock->default_value; if (default_value->value != nullptr) { id_us_min(&default_value->value->id); + return true; } break; } @@ -1584,6 +1587,7 @@ static void socket_id_user_decrement(bNodeSocket *sock) sock->default_value; if (default_value->value != nullptr) { id_us_min(&default_value->value->id); + return true; } break; } @@ -1591,6 +1595,7 @@ static void socket_id_user_decrement(bNodeSocket *sock) bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; if (default_value->value != nullptr) { id_us_min(&default_value->value->id); + return true; } break; } @@ -1598,6 +1603,7 @@ static void socket_id_user_decrement(bNodeSocket *sock) bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; if (default_value->value != nullptr) { id_us_min(&default_value->value->id); + return true; } break; } @@ -1613,6 +1619,7 @@ static void socket_id_user_decrement(bNodeSocket *sock) case SOCK_GEOMETRY: break; } + return false; } void nodeModifySocketType(bNodeTree *ntree, @@ -2972,6 +2979,8 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) * do to ID user refcounting and removal of animdation data then. */ BLI_assert((ntree->id.tag & LIB_TAG_LOCALIZED) == 0); + bool node_has_id = false; + if (do_id_user) { /* Free callback for NodeCustomGroup. */ if (node->typeinfo->freefunc_api) { @@ -2984,13 +2993,14 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) /* Do user counting. */ if (node->id) { id_us_min(node->id); + node_has_id = true; } LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - socket_id_user_decrement(sock); + node_has_id |= socket_id_user_decrement(sock); } LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { - socket_id_user_decrement(sock); + node_has_id |= socket_id_user_decrement(sock); } } @@ -3007,6 +3017,12 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) } } + if (node_has_id) { + if (bmain != nullptr) { + DEG_relations_tag_update(bmain); + } + } + nodeUnlinkNode(ntree, node); node_unlink_attached(ntree, node); -- cgit v1.2.3 From 1b47d07d7661b26b462f0e6fe87dabeb24d85168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 18 Feb 2022 16:32:33 +0100 Subject: Fix T95724: boundary error in `BLI_str_unescape_ex` Fix boundary error in `BLI_str_unescape_ex`. The `dst_maxncpy` parameter indicates the maximum buffer size, not the maximum number of characters. As these are strings, the loop has to stop one byte early to allow space for the trailing zero byte. Thanks @mano-wii for the patch! --- source/blender/blenlib/intern/string.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index 2c626773871..2e23bacfb28 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -323,8 +323,9 @@ size_t BLI_str_unescape_ex(char *__restrict dst, { size_t len = 0; bool is_complete = true; + const size_t max_strlen = dst_maxncpy - 1; /* Account for trailing zero byte. */ for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) { - if (UNLIKELY(len == dst_maxncpy)) { + if (UNLIKELY(len == max_strlen)) { is_complete = false; break; } -- cgit v1.2.3 From ddf189892c596d939228cc531b775bfd6708bb2d Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 09:50:29 -0600 Subject: Cleanup: Rename original curve object type enum This commit renames enums related the "Curve" object type and ID type to add `_LEGACY` to the end. The idea is to make our aspirations clearer in the code and to avoid ambiguities between `CURVE` and `CURVES`. Ref T95355 To summarize for the record, the plans are: - In the short/medium term, replace the `Curve` object data type with `Curves` - In the longer term (no immediate plans), use a proper data block for 3D text and surfaces. Differential Revision: https://developer.blender.org/D14114 --- source/blender/blenkernel/BKE_idtype.h | 2 +- source/blender/blenkernel/intern/action.c | 2 +- source/blender/blenkernel/intern/anim_path.c | 4 +- source/blender/blenkernel/intern/armature_update.c | 2 +- source/blender/blenkernel/intern/constraint.c | 6 +- source/blender/blenkernel/intern/context.c | 2 +- source/blender/blenkernel/intern/curve.cc | 16 ++--- source/blender/blenkernel/intern/curve_bevel.c | 2 +- source/blender/blenkernel/intern/curve_convert.c | 4 +- source/blender/blenkernel/intern/curve_deform.c | 4 +- source/blender/blenkernel/intern/displist.cc | 6 +- source/blender/blenkernel/intern/effect.c | 4 +- .../blenkernel/intern/geometry_component_curve.cc | 2 +- source/blender/blenkernel/intern/geometry_set.cc | 2 +- source/blender/blenkernel/intern/idtype.c | 10 +-- source/blender/blenkernel/intern/ipo.c | 2 +- source/blender/blenkernel/intern/key.c | 26 +++---- source/blender/blenkernel/intern/lib_query.c | 6 +- source/blender/blenkernel/intern/lib_remap.c | 4 +- source/blender/blenkernel/intern/main.c | 4 +- source/blender/blenkernel/intern/material.c | 18 ++--- source/blender/blenkernel/intern/mesh_convert.cc | 12 ++-- source/blender/blenkernel/intern/modifier.c | 2 +- source/blender/blenkernel/intern/object.cc | 50 ++++++++------ source/blender/blenkernel/intern/object_dupli.cc | 2 +- source/blender/blenkernel/intern/object_update.c | 6 +- source/blender/blenkernel/intern/softbody.c | 6 +- source/blender/blenkernel/intern/vfont.c | 4 +- source/blender/blenloader/intern/readfile.c | 2 +- source/blender/blenloader/intern/versioning_250.c | 2 +- .../blender/blenloader/intern/versioning_legacy.c | 2 +- source/blender/blentranslation/BLT_translation.h | 4 +- .../depsgraph/intern/builder/deg_builder_nodes.cc | 6 +- .../intern/builder/deg_builder_relations.cc | 15 ++-- source/blender/depsgraph/intern/depsgraph_tag.cc | 8 +-- .../intern/eval/deg_eval_copy_on_write.cc | 12 ++-- .../intern/eval/deg_eval_runtime_backup_object.cc | 2 +- .../blender/draw/engines/overlay/overlay_engine.c | 8 +-- .../blender/draw/engines/overlay/overlay_extra.c | 4 +- .../draw/engines/overlay/overlay_wireframe.c | 4 +- .../draw/engines/select/select_draw_utils.c | 2 +- source/blender/draw/intern/draw_cache.c | 26 +++---- .../blender/draw/intern/draw_cache_impl_curve.cc | 2 +- source/blender/draw/intern/draw_common.c | 2 +- source/blender/draw/intern/draw_manager.c | 2 +- source/blender/draw/intern/draw_manager_data.c | 4 +- .../editors/animation/anim_channels_defines.c | 2 +- source/blender/editors/animation/anim_filter.c | 6 +- source/blender/editors/curve/editcurve.c | 16 ++--- source/blender/editors/curve/editcurve_add.c | 32 ++++----- source/blender/editors/curve/editcurve_undo.c | 2 +- source/blender/editors/gpencil/gpencil_convert.c | 4 +- source/blender/editors/interface/interface_icons.c | 2 +- .../editors/interface/interface_templates.c | 4 +- .../blender/editors/mesh/editmesh_knife_project.c | 2 +- source/blender/editors/mesh/editmesh_loopcut.c | 3 +- source/blender/editors/mesh/editmesh_tools.c | 3 +- source/blender/editors/object/object_add.c | 32 +++++---- source/blender/editors/object/object_bake_api.c | 2 +- source/blender/editors/object/object_constraint.c | 5 +- .../blender/editors/object/object_data_transform.c | 10 +-- source/blender/editors/object/object_edit.c | 8 +-- source/blender/editors/object/object_hook.c | 4 +- source/blender/editors/object/object_modes.c | 2 +- source/blender/editors/object/object_modifier.c | 4 +- source/blender/editors/object/object_relations.c | 14 ++-- source/blender/editors/object/object_transform.c | 12 ++-- source/blender/editors/object/object_utils.c | 2 +- source/blender/editors/render/render_internal.cc | 3 +- source/blender/editors/render/render_opengl.cc | 48 ++++++------- source/blender/editors/render/render_shading.cc | 4 +- source/blender/editors/screen/screen_ops.c | 6 +- source/blender/editors/sculpt_paint/paint_utils.c | 3 +- .../editors/space_buttons/buttons_context.c | 4 +- source/blender/editors/space_info/info_stats.cc | 4 +- .../editors/space_node/node_context_path.cc | 2 +- .../editors/space_outliner/outliner_draw.cc | 4 +- .../editors/space_outliner/outliner_intern.hh | 2 +- .../editors/space_outliner/outliner_select.cc | 2 +- .../editors/space_outliner/outliner_tools.cc | 4 +- .../editors/space_outliner/outliner_tree.cc | 2 +- .../editors/space_outliner/tree/tree_element_id.cc | 2 +- .../editors/space_spreadsheet/space_spreadsheet.cc | 2 +- .../blender/editors/space_view3d/view3d_buttons.c | 4 +- source/blender/editors/space_view3d/view3d_draw.c | 2 +- .../blender/editors/space_view3d/view3d_select.c | 8 +-- source/blender/editors/transform/transform.c | 2 +- .../blender/editors/transform/transform_convert.c | 6 +- .../transform/transform_convert_object_texspace.c | 2 +- .../blender/editors/transform/transform_generics.c | 3 +- .../blender/editors/transform/transform_gizmo_3d.c | 2 +- .../editors/transform/transform_gizmo_extrude_3d.c | 2 +- source/blender/editors/transform/transform_mode.c | 2 +- source/blender/editors/transform/transform_ops.c | 2 +- .../editors/transform/transform_orientations.c | 4 +- source/blender/editors/transform/transform_snap.c | 6 +- .../editors/transform/transform_snap_object.c | 6 +- source/blender/editors/util/ed_transverts.c | 7 +- .../gpencil_modifiers/intern/MOD_gpencillineart.c | 2 +- .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 2 +- .../io/alembic/exporter/abc_hierarchy_iterator.cc | 2 +- .../io/alembic/exporter/abc_writer_nurbs.cc | 2 +- .../blender/io/alembic/intern/abc_reader_curves.cc | 6 +- .../blender/io/alembic/intern/abc_reader_nurbs.cc | 2 +- .../io/usd/intern/usd_hierarchy_iterator.cc | 2 +- source/blender/io/usd/intern/usd_reader_curve.cc | 4 +- source/blender/io/usd/intern/usd_reader_nurbs.cc | 4 +- .../io/wavefront_obj/exporter/obj_exporter.cc | 2 +- .../io/wavefront_obj/exporter/obj_exporter.hh | 11 +-- source/blender/makesdna/DNA_ID.h | 16 ++--- source/blender/makesdna/DNA_ID_enums.h | 80 +++++++++++----------- source/blender/makesdna/DNA_ipo_types.h | 2 +- source/blender/makesdna/DNA_object_types.h | 22 ++++-- source/blender/makesrna/intern/rna_ID.c | 8 +-- source/blender/makesrna/intern/rna_curve.c | 6 +- .../blender/makesrna/intern/rna_gpencil_modifier.c | 3 +- source/blender/makesrna/intern/rna_key.c | 10 +-- source/blender/makesrna/intern/rna_main_api.c | 4 +- source/blender/makesrna/intern/rna_mask.c | 3 +- source/blender/makesrna/intern/rna_modifier.c | 16 +++-- source/blender/makesrna/intern/rna_nodetree.c | 6 +- source/blender/makesrna/intern/rna_object.c | 10 +-- source/blender/makesrna/intern/rna_object_api.c | 6 +- source/blender/makesrna/intern/rna_object_force.c | 4 +- source/blender/makesrna/intern/rna_scene.c | 4 +- source/blender/makesrna/intern/rna_space.c | 8 ++- source/blender/modifiers/intern/MOD_array.c | 2 +- source/blender/modifiers/intern/MOD_curve.c | 2 +- source/blender/modifiers/intern/MOD_ui_common.c | 2 +- source/blender/modifiers/intern/MOD_util.c | 2 +- 130 files changed, 466 insertions(+), 427 deletions(-) diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index e9abbb4575e..bd64b03cc7d 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -230,7 +230,7 @@ extern IDTypeInfo IDType_ID_SCE; extern IDTypeInfo IDType_ID_LI; extern IDTypeInfo IDType_ID_OB; extern IDTypeInfo IDType_ID_ME; -extern IDTypeInfo IDType_ID_CU; +extern IDTypeInfo IDType_ID_CU_LEGACY; extern IDTypeInfo IDType_ID_MB; extern IDTypeInfo IDType_ID_MA; extern IDTypeInfo IDType_ID_TE; diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 6a999cde6eb..55aba1d22c3 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -1244,7 +1244,7 @@ void BKE_pose_update_constraint_flags(bPose *pose) /* if we have a valid target, make sure that this will get updated on frame-change * (needed for when there is no anim-data for this pose) */ - if ((data->tar) && (data->tar->type == OB_CURVE)) { + if ((data->tar) && (data->tar->type == OB_CURVES_LEGACY)) { pose->flag |= POSE_CONSTRAINTS_TIMEDEPEND; } } diff --git a/source/blender/blenkernel/intern/anim_path.c b/source/blender/blenkernel/intern/anim_path.c index 57f64d7a0f8..1f8c6df6147 100644 --- a/source/blender/blenkernel/intern/anim_path.c +++ b/source/blender/blenkernel/intern/anim_path.c @@ -55,7 +55,7 @@ float BKE_anim_path_get_length(const CurveCache *curve_cache) void BKE_anim_path_calc_data(Object *ob) { - if (ob == NULL || ob->type != OB_CURVE) { + if (ob == NULL || ob->type != OB_CURVES_LEGACY) { return; } if (ob->runtime.curve_cache == NULL) { @@ -222,7 +222,7 @@ bool BKE_where_on_path(const Object *ob, float *r_radius, float *r_weight) { - if (ob == NULL || ob->type != OB_CURVE) { + if (ob == NULL || ob->type != OB_CURVES_LEGACY) { return false; } Curve *cu = ob->data; diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 099588a0e14..361ab176abd 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -70,7 +70,7 @@ static void splineik_init_tree_from_pchan(Scene *UNUSED(scene), ik_data = con->data; /* Target can only be a curve. */ - if ((ik_data->tar == NULL) || (ik_data->tar->type != OB_CURVE)) { + if ((ik_data->tar == NULL) || (ik_data->tar->type != OB_CURVES_LEGACY)) { continue; } /* Skip if disabled. */ diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 69002a71f1d..47669387fd1 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -1493,7 +1493,7 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph), { bFollowPathConstraint *data = con->data; - if (VALID_CONS_TARGET(ct) && (ct->tar->type == OB_CURVE)) { + if (VALID_CONS_TARGET(ct) && (ct->tar->type == OB_CURVES_LEGACY)) { Curve *cu = ct->tar->data; float vec[4], dir[3], radius; float curvetime; @@ -2479,7 +2479,7 @@ static void pycon_get_tarmat(struct Depsgraph *UNUSED(depsgraph), #endif if (VALID_CONS_TARGET(ct)) { - if (ct->tar->type == OB_CURVE && ct->tar->runtime.curve_cache == NULL) { + if (ct->tar->type == OB_CURVES_LEGACY && ct->tar->runtime.curve_cache == NULL) { unit_m4(ct->matrix); return; } @@ -3867,7 +3867,7 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar bConstraintTarget *ct = targets->first; /* only evaluate if there is a target and it is a curve */ - if (VALID_CONS_TARGET(ct) && (ct->tar->type == OB_CURVE)) { + if (VALID_CONS_TARGET(ct) && (ct->tar->type == OB_CURVES_LEGACY)) { float obmat[4][4], ownLoc[3]; float curveMin[3], curveMax[3]; float targetMatrix[4][4]; diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index 7fcbbfd316f..28bcd961e26 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -1148,7 +1148,7 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit, switch (obedit->type) { case OB_MESH: return CTX_MODE_EDIT_MESH; - case OB_CURVE: + case OB_CURVES_LEGACY: return CTX_MODE_EDIT_CURVE; case OB_SURF: return CTX_MODE_EDIT_SURFACE; diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc index c6c0111780e..b0f58dd4ec9 100644 --- a/source/blender/blenkernel/intern/curve.cc +++ b/source/blender/blenkernel/intern/curve.cc @@ -293,14 +293,14 @@ static void curve_blend_read_expand(BlendExpander *expander, ID *id) BLO_expand(expander, cu->textoncurve); } -IDTypeInfo IDType_ID_CU = { - /* id_code */ ID_CU, - /* id_filter */ FILTER_ID_CU, - /* main_listbase_index */ INDEX_ID_CU, +IDTypeInfo IDType_ID_CU_LEGACY = { + /* id_code */ ID_CU_LEGACY, + /* id_filter */ FILTER_ID_CU_LEGACY, + /* main_listbase_index */ INDEX_ID_CU_LEGACY, /* struct_size */ sizeof(Curve), /* name */ "Curve", /* name_plural */ "curves", - /* translation_context */ BLT_I18NCONTEXT_ID_CURVE, + /* translation_context */ BLT_I18NCONTEXT_ID_CURVE_LEGACY, /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, /* asset_type_info */ nullptr, @@ -406,7 +406,7 @@ Curve *BKE_curve_add(Main *bmain, const char *name, int type) Curve *cu; /* We cannot use #BKE_id_new here as we need some custom initialization code. */ - cu = (Curve *)BKE_libblock_alloc(bmain, ID_CU, name, 0); + cu = (Curve *)BKE_libblock_alloc(bmain, ID_CU_LEGACY, name, 0); BKE_curve_init(cu, type); @@ -440,7 +440,7 @@ short BKE_curve_type_get(const Curve *cu) } if (!cu->type) { - type = OB_CURVE; + type = OB_CURVES_LEGACY; LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { if (nu->pntsv > 1) { @@ -473,7 +473,7 @@ void BKE_curve_type_test(Object *ob) { ob->type = BKE_curve_type_get((Curve *)ob->data); - if (ob->type == OB_CURVE) { + if (ob->type == OB_CURVES_LEGACY) { Curve *cu = (Curve *)ob->data; if (CU_IS_2D(cu)) { BKE_curve_dimension_update(cu); diff --git a/source/blender/blenkernel/intern/curve_bevel.c b/source/blender/blenkernel/intern/curve_bevel.c index ffef22fb498..6f32f0f5e6f 100644 --- a/source/blender/blenkernel/intern/curve_bevel.c +++ b/source/blender/blenkernel/intern/curve_bevel.c @@ -228,7 +228,7 @@ static void curve_bevel_make_from_object(const Curve *cu, ListBase *disp) if (cu->bevobj == NULL) { return; } - if (cu->bevobj->type != OB_CURVE) { + if (cu->bevobj->type != OB_CURVES_LEGACY) { return; } diff --git a/source/blender/blenkernel/intern/curve_convert.c b/source/blender/blenkernel/intern/curve_convert.c index 285e6978522..129e930a21e 100644 --- a/source/blender/blenkernel/intern/curve_convert.c +++ b/source/blender/blenkernel/intern/curve_convert.c @@ -27,7 +27,7 @@ static Curve *curve_from_font_object(Object *object, Depsgraph *depsgraph) Object *evaluated_object = DEG_get_evaluated_object(depsgraph, object); BKE_vfont_to_curve_nubase(evaluated_object, FO_EDIT, &new_curve->nurb); - new_curve->type = OB_CURVE; + new_curve->type = OB_CURVES_LEGACY; new_curve->flag &= ~CU_3D; BKE_curve_dimension_update(new_curve); @@ -55,7 +55,7 @@ static Curve *curve_from_curve_object(Object *object, Depsgraph *depsgraph, bool Curve *BKE_curve_new_from_object(Object *object, Depsgraph *depsgraph, bool apply_modifiers) { - if (!ELEM(object->type, OB_FONT, OB_CURVE)) { + if (!ELEM(object->type, OB_FONT, OB_CURVES_LEGACY)) { return NULL; } diff --git a/source/blender/blenkernel/intern/curve_deform.c b/source/blender/blenkernel/intern/curve_deform.c index f76e4202994..fb082fccc0b 100644 --- a/source/blender/blenkernel/intern/curve_deform.c +++ b/source/blender/blenkernel/intern/curve_deform.c @@ -211,7 +211,7 @@ static void curve_deform_coords_impl(const Object *ob_curve, bool use_dverts = false; int cd_dvert_offset; - if (ob_curve->type != OB_CURVE) { + if (ob_curve->type != OB_CURVES_LEGACY) { return; } @@ -404,7 +404,7 @@ void BKE_curve_deform_co(const Object *ob_curve, CurveDeform cd; float quat[4]; - if (ob_curve->type != OB_CURVE) { + if (ob_curve->type != OB_CURVES_LEGACY) { unit_m3(r_mat); return; } diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 793af80ec74..5c761e94bb9 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -590,7 +590,7 @@ static float displist_calc_taper(Depsgraph *depsgraph, Object *taperobj, float fac) { - if (taperobj == nullptr || taperobj->type != OB_CURVE) { + if (taperobj == nullptr || taperobj->type != OB_CURVES_LEGACY) { return 1.0; } @@ -1263,7 +1263,7 @@ static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, const bool for_render, ListBase *r_dispbase) { - BLI_assert(ELEM(ob->type, OB_CURVE, OB_FONT)); + BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT)); const Curve *cu = (const Curve *)ob->data; ListBase *deformed_nurbs = &ob->runtime.curve_cache->deformed_nurbs; @@ -1473,7 +1473,7 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, Object *ob, const bool for_render) { - BLI_assert(ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)); + BLI_assert(ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)); Curve &cow_curve = *(Curve *)ob->data; BKE_object_free_derived_caches(ob); diff --git a/source/blender/blenkernel/intern/effect.c b/source/blender/blenkernel/intern/effect.c index 2f760597e1a..f2915a97746 100644 --- a/source/blender/blenkernel/intern/effect.c +++ b/source/blender/blenkernel/intern/effect.c @@ -143,7 +143,7 @@ static void precalculate_effector(struct Depsgraph *depsgraph, EffectorCache *ef BLI_rng_srandom(eff->pd->rng, eff->pd->seed + cfra); } - if (eff->pd->forcefield == PFIELD_GUIDE && eff->ob->type == OB_CURVE) { + if (eff->pd->forcefield == PFIELD_GUIDE && eff->ob->type == OB_CURVES_LEGACY) { Curve *cu = eff->ob->data; if (cu->flag & CU_PATH) { if (eff->ob->runtime.curve_cache == NULL || @@ -161,7 +161,7 @@ static void precalculate_effector(struct Depsgraph *depsgraph, EffectorCache *ef } else if (eff->pd->shape == PFIELD_SHAPE_SURFACE) { eff->surmd = (SurfaceModifierData *)BKE_modifiers_findby_type(eff->ob, eModifierType_Surface); - if (eff->ob->type == OB_CURVE) { + if (eff->ob->type == OB_CURVES_LEGACY) { eff->flag |= PE_USE_NORMAL_DATA; } } diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 5921f853389..fff77dbd367 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -128,7 +128,7 @@ const Curve *CurveComponent::get_curve_for_render() const return curve_for_render_; } - curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU, nullptr); + curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU_LEGACY, nullptr); curve_for_render_->curve_eval = curve_; return curve_for_render_; diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 9c7cfa04e0b..13441b4914a 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -632,7 +632,7 @@ bool BKE_object_has_geometry_set_instances(const Object *ob) is_instance = ob->type != OB_VOLUME; break; case GEO_COMPONENT_TYPE_CURVE: - is_instance = !ELEM(ob->type, OB_CURVE, OB_FONT); + is_instance = !ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT); break; } if (is_instance) { diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index 2551bb12511..5b9dfa55c45 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -59,7 +59,7 @@ static void id_type_init(void) INIT_TYPE(ID_LI); INIT_TYPE(ID_OB); INIT_TYPE(ID_ME); - INIT_TYPE(ID_CU); + INIT_TYPE(ID_CU_LEGACY); INIT_TYPE(ID_MB); INIT_TYPE(ID_MA); INIT_TYPE(ID_TE); @@ -215,7 +215,7 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(BR); CASE_IDFILTER(CA); CASE_IDFILTER(CF); - CASE_IDFILTER(CU); + CASE_IDFILTER(CU_LEGACY); CASE_IDFILTER(GD); CASE_IDFILTER(GR); CASE_IDFILTER(CV); @@ -264,7 +264,7 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(BR); CASE_IDFILTER(CA); CASE_IDFILTER(CF); - CASE_IDFILTER(CU); + CASE_IDFILTER(CU_LEGACY); CASE_IDFILTER(GD); CASE_IDFILTER(GR); CASE_IDFILTER(CV); @@ -312,7 +312,7 @@ int BKE_idtype_idcode_to_index(const short idcode) CASE_IDINDEX(BR); CASE_IDINDEX(CA); CASE_IDINDEX(CF); - CASE_IDINDEX(CU); + CASE_IDINDEX(CU_LEGACY); CASE_IDINDEX(GD); CASE_IDINDEX(GR); CASE_IDINDEX(CV); @@ -371,7 +371,7 @@ short BKE_idtype_idcode_from_index(const int index) CASE_IDCODE(BR); CASE_IDCODE(CA); CASE_IDCODE(CF); - CASE_IDCODE(CU); + CASE_IDCODE(CU_LEGACY); CASE_IDCODE(GD); CASE_IDCODE(GR); CASE_IDCODE(CV); diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index e5c1cf96f8c..abd6505456e 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -1090,7 +1090,7 @@ static char *get_rna_access(ID *id, propname = particle_adrcodes_to_paths(adrcode, &dummy_index); break; - case ID_CU: /* curve */ + case ID_CU_LEGACY: /* curve */ /* this used to be a 'dummy' curve which got evaluated on the fly... * now we've got real var for this! */ diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index b28d9db92cf..e28094c0abc 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -281,7 +281,7 @@ Key *BKE_key_add(Main *bmain, ID *id) /* common function */ key->elemsize = sizeof(float[KEYELEM_FLOAT_LEN_COORD]); break; - case ID_CU: + case ID_CU_LEGACY: el = key->elemstr; el[0] = KEYELEM_ELEM_SIZE_CURVE; @@ -659,7 +659,7 @@ static bool key_pointer_size(const Key *key, const int mode, int *poinsize, int *ofs = sizeof(float[KEYELEM_FLOAT_LEN_COORD]); *poinsize = *ofs; break; - case ID_CU: + case ID_CU_LEGACY: if (mode == KEY_MODE_BPOINT) { *ofs = sizeof(float[KEYELEM_FLOAT_LEN_BPOINT]); *step = KEYELEM_ELEM_LEN_BPOINT; @@ -1524,7 +1524,7 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t tot = lt->pntsu * lt->pntsv * lt->pntsw; size = tot * sizeof(float[KEYELEM_FLOAT_LEN_COORD]); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = ob->data; tot = BKE_keyblock_curve_element_count(&cu->nurb); @@ -1570,7 +1570,7 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t MEM_freeN(weights); } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { cp_cu_key(ob->data, key, actkb, kb, 0, tot, out, tot); } } @@ -1582,7 +1582,7 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t else if (ob->type == OB_LATTICE) { do_latt_key(ob, key, out, tot); } - else if (ob->type == OB_CURVE) { + else if (ob->type == OB_CURVES_LEGACY) { do_curve_key(ob, key, out, tot); } else if (ob->type == OB_SURF) { @@ -1714,7 +1714,7 @@ bool BKE_key_idtype_support(const short id_type) { switch (id_type) { case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_LT: return true; default: @@ -1729,7 +1729,7 @@ Key **BKE_key_from_id_p(ID *id) Mesh *me = (Mesh *)id; return &me->key; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)id; if (cu->vfont == NULL) { return &cu->key; @@ -2269,7 +2269,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve Lattice *lt = ob->data; BLI_assert((lt->pntsu * lt->pntsv * lt->pntsw) == kb->totelem); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = ob->data; BLI_assert(BKE_keyblock_curve_element_count(&cu->nurb) == kb->totelem); } @@ -2293,7 +2293,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve copy_v3_v3(fp, *co); } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = (Curve *)ob->data; Nurb *nu; BezTriple *bezt; @@ -2335,7 +2335,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v tot = lt->pntsu * lt->pntsv * lt->pntsw; elemsize = lt->key->elemsize; } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = (Curve *)ob->data; elemsize = cu->key->elemsize; tot = BKE_keyblock_curve_element_count(&cu->nurb); @@ -2366,7 +2366,7 @@ float (*BKE_keyblock_convert_to_vertcos(Object *ob, KeyBlock *kb))[3] Lattice *lt = (Lattice *)ob->data; tot = lt->pntsu * lt->pntsv * lt->pntsw; } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = (Curve *)ob->data; tot = BKE_nurbList_verts_count(&cu->nurb); } @@ -2383,7 +2383,7 @@ float (*BKE_keyblock_convert_to_vertcos(Object *ob, KeyBlock *kb))[3] copy_v3_v3(*co, fp); } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = (Curve *)ob->data; Nurb *nu; BezTriple *bezt; @@ -2422,7 +2422,7 @@ void BKE_keyblock_update_from_offset(Object *ob, KeyBlock *kb, const float (*ofs add_v3_v3(fp, *ofs); } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = (Curve *)ob->data; Nurb *nu; BezTriple *bezt; diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index ba009072db8..0103e40e90d 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -402,7 +402,7 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner) return FILTER_ID_ALL; case ID_ME: return FILTER_ID_ME | FILTER_ID_MA | FILTER_ID_IM; - case ID_CU: + case ID_CU_LEGACY: return FILTER_ID_OB | FILTER_ID_MA | FILTER_ID_VF; case ID_MB: return FILTER_ID_MA; @@ -418,7 +418,7 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner) return FILTER_ID_OB | FILTER_ID_IM; case ID_KE: /* Warning! key->from, could be more types in future? */ - return FILTER_ID_ME | FILTER_ID_CU | FILTER_ID_LT; + return FILTER_ID_ME | FILTER_ID_CU_LEGACY | FILTER_ID_LT; case ID_SCR: return FILTER_ID_SCE; case ID_WO: @@ -490,7 +490,7 @@ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used) /* Exception: ID_KE aren't available as filter_id. */ if (id_type_used == ID_KE) { - return ELEM(id_type_owner, ID_ME, ID_CU, ID_LT); + return ELEM(id_type_owner, ID_ME, ID_CU_LEGACY, ID_LT); } /* Exception: ID_SCR aren't available as filter_id. */ diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 9329a09f1b6..24e7178dd63 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -380,7 +380,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o case ID_ME: multires_force_sculpt_rebuild(ob); break; - case ID_CU: + case ID_CU_LEGACY: BKE_curve_type_test(ob); break; default: @@ -573,7 +573,7 @@ static void libblock_remap_foreach_idpair_cb(ID *old_id, ID *new_id, void *user_ bmain, NULL, (Collection *)old_id, (Collection *)new_id); break; case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_MB: case ID_CV: case ID_PT: diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index b25432780ed..03e03dacfbc 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -563,7 +563,7 @@ ListBase *which_libbase(Main *bmain, short type) return &(bmain->objects); case ID_ME: return &(bmain->meshes); - case ID_CU: + case ID_CU_LEGACY: return &(bmain->curves); case ID_MB: return &(bmain->metaballs); @@ -670,7 +670,7 @@ int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/]) lb[INDEX_ID_CF] = &(bmain->cachefiles); lb[INDEX_ID_ME] = &(bmain->meshes); - lb[INDEX_ID_CU] = &(bmain->curves); + lb[INDEX_ID_CU_LEGACY] = &(bmain->curves); lb[INDEX_ID_MB] = &(bmain->metaballs); lb[INDEX_ID_CV] = &(bmain->hair_curves); lb[INDEX_ID_PT] = &(bmain->pointclouds); diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index c559c6fe807..7d01a92e829 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -313,7 +313,7 @@ Material ***BKE_object_material_array_p(Object *ob) Mesh *me = ob->data; return &(me->mat); } - if (ELEM(ob->type, OB_CURVE, OB_FONT, OB_SURF)) { + if (ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { Curve *cu = ob->data; return &(cu->mat); } @@ -346,7 +346,7 @@ short *BKE_object_material_len_p(Object *ob) Mesh *me = ob->data; return &(me->totcol); } - if (ELEM(ob->type, OB_CURVE, OB_FONT, OB_SURF)) { + if (ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { Curve *cu = ob->data; return &(cu->totcol); } @@ -381,7 +381,7 @@ Material ***BKE_id_material_array_p(ID *id) switch (GS(id->name)) { case ID_ME: return &(((Mesh *)id)->mat); - case ID_CU: + case ID_CU_LEGACY: return &(((Curve *)id)->mat); case ID_MB: return &(((MetaBall *)id)->mat); @@ -407,7 +407,7 @@ short *BKE_id_material_len_p(ID *id) switch (GS(id->name)) { case ID_ME: return &(((Mesh *)id)->totcol); - case ID_CU: + case ID_CU_LEGACY: return &(((Curve *)id)->totcol); case ID_MB: return &(((MetaBall *)id)->totcol); @@ -434,7 +434,7 @@ static void material_data_index_remove_id(ID *id, short index) case ID_ME: BKE_mesh_material_index_remove((Mesh *)id, index); break; - case ID_CU: + case ID_CU_LEGACY: BKE_curve_material_index_remove((Curve *)id, index); break; case ID_MB: @@ -468,7 +468,7 @@ bool BKE_object_material_slot_used(Object *object, short actcol) switch (GS(ob_data->name)) { case ID_ME: return BKE_mesh_material_index_used((Mesh *)ob_data, actcol - 1); - case ID_CU: + case ID_CU_LEGACY: return BKE_curve_material_index_used((Curve *)ob_data, actcol - 1); case ID_MB: /* Meta-elements don't support materials at the moment. */ @@ -489,7 +489,7 @@ static void material_data_index_clear_id(ID *id) case ID_ME: BKE_mesh_material_index_clear((Mesh *)id); break; - case ID_CU: + case ID_CU_LEGACY: BKE_curve_material_index_clear((Curve *)id); break; case ID_MB: @@ -1062,7 +1062,7 @@ void BKE_object_material_remap(Object *ob, const unsigned int *remap) if (ob->type == OB_MESH) { BKE_mesh_material_remap(ob->data, remap, ob->totcol); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { BKE_curve_material_remap(ob->data, remap, ob->totcol); } else if (ob->type == OB_GPENCIL) { @@ -1314,7 +1314,7 @@ bool BKE_object_material_slot_remove(Main *bmain, Object *ob) } /* check indices from mesh */ - if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT)) { + if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { material_data_index_remove_id((ID *)ob->data, actcol - 1); if (ob->runtime.curve_cache) { BKE_displist_free(&ob->runtime.curve_cache->disp); diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 121bfbff394..26ef467fae5 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -739,14 +739,14 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene), BKE_mesh_to_curve_nurblist(me_eval, &nurblist, 1); if (nurblist.first) { - Curve *cu = BKE_curve_add(bmain, ob->id.name + 2, OB_CURVE); + Curve *cu = BKE_curve_add(bmain, ob->id.name + 2, OB_CURVES_LEGACY); cu->flag |= CU_3D; cu->nurb = nurblist; id_us_min(&((Mesh *)ob->data)->id); ob->data = cu; - ob->type = OB_CURVE; + ob->type = OB_CURVES_LEGACY; BKE_object_free_derived_caches(ob); } @@ -886,7 +886,7 @@ static void object_for_curve_to_mesh_free(Object *temp_object) { /* Clear edit mode pointers that were explicitly copied to the temporary curve. */ ID *final_object_data = static_cast(temp_object->data); - if (GS(final_object_data->name) == ID_CU) { + if (GS(final_object_data->name) == ID_CU_LEGACY) { Curve &curve = *reinterpret_cast(final_object_data); curve.editfont = nullptr; curve.editnurb = nullptr; @@ -901,7 +901,7 @@ static void object_for_curve_to_mesh_free(Object *temp_object) */ static void curve_to_mesh_eval_ensure(Object &object) { - BLI_assert(GS(static_cast(object.data)->name) == ID_CU); + BLI_assert(GS(static_cast(object.data)->name) == ID_CU_LEGACY); Curve &curve = *static_cast(object.data); /* Clear all modifiers for the bevel object. * @@ -1110,7 +1110,7 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Mesh *new_mesh = nullptr; switch (object->type) { case OB_FONT: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: new_mesh = mesh_new_from_curve_type_object(object); break; @@ -1182,7 +1182,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, Object *object, bool preserve_all_data_layers) { - BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH)); + BLI_assert(ELEM(object->type, OB_FONT, OB_CURVES_LEGACY, OB_SURF, OB_MBALL, OB_MESH)); Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); if (mesh == nullptr) { diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 837c1fe179f..5af8dfc2b72 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -668,7 +668,7 @@ ModifierData *BKE_modifiers_get_virtual_modifierlist(const Object *ob, virtualModifierData->amd.deformflag = ((bArmature *)(ob->parent->data))->deformflag; md = &virtualModifierData->amd.modifier; } - else if (ob->parent->type == OB_CURVE && ob->partype == PARSKEL) { + else if (ob->parent->type == OB_CURVES_LEGACY && ob->partype == PARSKEL) { virtualModifierData->cmd.object = ob->parent; virtualModifierData->cmd.defaxis = ob->trackflag + 1; virtualModifierData->cmd.modifier.next = md; diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index c5d1853ba6c..985c9edac1a 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -1385,8 +1385,14 @@ ModifierData *BKE_object_active_modifier(const Object *ob) bool BKE_object_supports_modifiers(const Object *ob) { - return ( - ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE, OB_POINTCLOUD, OB_VOLUME)); + return (ELEM(ob->type, + OB_MESH, + OB_CURVES_LEGACY, + OB_SURF, + OB_FONT, + OB_LATTICE, + OB_POINTCLOUD, + OB_VOLUME)); } bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) @@ -1402,7 +1408,7 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) if (ELEM(ob->type, OB_POINTCLOUD, OB_VOLUME, OB_CURVES)) { return (mti->modifyGeometrySet != nullptr); } - if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { + if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_FONT, OB_LATTICE)) { if (ob->type == OB_LATTICE && (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly) == 0) { return false; } @@ -1871,7 +1877,7 @@ bool BKE_object_is_in_editmode(const Object *ob) case OB_LATTICE: return ((Lattice *)ob->data)->editlatt != nullptr; case OB_SURF: - case OB_CURVE: + case OB_CURVES_LEGACY: return ((Curve *)ob->data)->editnurb != nullptr; case OB_GPENCIL: /* Grease Pencil object has no edit mode data. */ @@ -1893,7 +1899,7 @@ bool BKE_object_data_is_in_editmode(const ID *id) switch (type) { case ID_ME: return ((const Mesh *)id)->edit_mesh != nullptr; - case ID_CU: + case ID_CU_LEGACY: return ((((const Curve *)id)->editnurb != nullptr) || (((const Curve *)id)->editfont != nullptr)); case ID_MB: @@ -1919,7 +1925,7 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id) } break; } - case ID_CU: { + case ID_CU_LEGACY: { if (((Curve *)id)->vfont != nullptr) { EditFont *ef = ((Curve *)id)->editfont; if (ef != nullptr) { @@ -2062,7 +2068,7 @@ static const char *get_obdata_defname(int type) switch (type) { case OB_MESH: return DATA_("Mesh"); - case OB_CURVE: + case OB_CURVES_LEGACY: return DATA_("Curve"); case OB_SURF: return DATA_("Surf"); @@ -2133,8 +2139,8 @@ void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name) switch (type) { case OB_MESH: return BKE_mesh_add(bmain, name); - case OB_CURVE: - return BKE_curve_add(bmain, name, OB_CURVE); + case OB_CURVES_LEGACY: + return BKE_curve_add(bmain, name, OB_CURVES_LEGACY); case OB_SURF: return BKE_curve_add(bmain, name, OB_SURF); case OB_FONT: @@ -2175,7 +2181,7 @@ int BKE_object_obdata_to_type(const ID *id) switch (GS(id->name)) { case ID_ME: return OB_MESH; - case ID_CU: + case ID_CU_LEGACY: return BKE_curve_type_get((const Curve *)id); case ID_MB: return OB_MBALL; @@ -2656,7 +2662,7 @@ Object *BKE_object_duplicate(Main *bmain, Object *ob, uint dupflag, uint duplica id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; - case OB_CURVE: + case OB_CURVES_LEGACY: if (dupflag & USER_DUP_CURVE) { id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } @@ -3220,7 +3226,7 @@ static void give_parvert(Object *par, int nr, float vec[3]) "object position can be wrong now"); } } - else if (ELEM(par->type, OB_CURVE, OB_SURF)) { + else if (ELEM(par->type, OB_CURVES_LEGACY, OB_SURF)) { ListBase *nurb; /* Unless there's some weird depsgraph failure the cache should exist. */ @@ -3293,7 +3299,7 @@ void BKE_object_get_parent_matrix(Object *ob, Object *par, float r_parentmat[4][ switch (ob->partype & PARTYPE) { case PAROBJECT: { bool ok = false; - if (par->type == OB_CURVE) { + if (par->type == OB_CURVES_LEGACY) { if ((((Curve *)par->data)->flag & CU_PATH) && (ob_parcurve(ob, par, tmat))) { ok = true; } @@ -3589,7 +3595,7 @@ BoundBox *BKE_object_boundbox_get(Object *ob) case OB_MESH: bb = BKE_mesh_boundbox_get(ob); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: bb = BKE_curve_boundbox_get(ob); @@ -3758,7 +3764,7 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us bool changed = false; switch (ob->type) { - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: case OB_SURF: { BoundBox bb = *BKE_curve_boundbox_get(ob); @@ -4187,7 +4193,7 @@ bool BKE_object_obdata_texspace_get(Object *ob, char **r_texflag, float **r_loc, BKE_mesh_texspace_get_reference((Mesh *)ob->data, r_texflag, r_loc, r_size); break; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)ob->data; BKE_curve_texspace_ensure(cu); if (r_texflag) { @@ -4555,7 +4561,7 @@ KeyBlock *BKE_object_shapekey_insert(Main *bmain, case OB_MESH: key = insert_meshkey(bmain, ob, name, from_mix); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: key = insert_curvekey(bmain, ob, name, from_mix); break; @@ -4627,7 +4633,7 @@ bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb) case OB_MESH: BKE_keyblock_convert_to_mesh(key->refkey, (Mesh *)ob->data); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: BKE_keyblock_convert_to_curve( key->refkey, (Curve *)ob->data, BKE_curve_nurbs_get((Curve *)ob->data)); @@ -4842,7 +4848,7 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob) flag |= eModifierMode_Realtime | eModifierMode_Render; } - if (ob->type == OB_CURVE) { + if (ob->type == OB_CURVES_LEGACY) { Curve *cu = (Curve *)ob->data; if (cu->taperobj != nullptr && object_deforms_in_time(cu->taperobj)) { flag |= eModifierMode_Realtime | eModifierMode_Render; @@ -4919,7 +4925,7 @@ bool BKE_object_supports_material_slots(struct Object *ob) { return ELEM(ob->type, OB_MESH, - OB_CURVE, + OB_CURVES_LEGACY, OB_SURF, OB_FONT, OB_MBALL, @@ -5153,7 +5159,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot) BLI_kdtree_3d_balance(tree); break; } - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: { /* TODO: take deformation into account */ Curve *cu = (Curve *)ob->data; @@ -5460,7 +5466,7 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph, } /* for curve following objects, parented curve has to be updated too */ - if (ob->type == OB_CURVE) { + if (ob->type == OB_CURVES_LEGACY) { Curve *cu = (Curve *)ob->data; BKE_animsys_evaluate_animdata( &cu->id, cu->adt, &anim_eval_context, ADT_RECALC_ANIM, flush_to_original); diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 18dd61004f5..009a7bd70be 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -851,7 +851,7 @@ static void make_duplis_geometry_set_impl(const DupliContext *ctx, dupli->ob_data = (ID *)volume; } } - if (!ELEM(ctx->object->type, OB_CURVE, OB_FONT) || geometry_set_is_instance) { + if (!ELEM(ctx->object->type, OB_CURVES_LEGACY, OB_FONT) || geometry_set_is_instance) { const CurveComponent *curve_component = geometry_set.get_component_for_read(); if (curve_component != nullptr) { const Curve *curve = curve_component->get_curve_for_render(); diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c index 2b0e04d0bd0..3bc2139ca0c 100644 --- a/source/blender/blenkernel/intern/object_update.c +++ b/source/blender/blenkernel/intern/object_update.c @@ -181,7 +181,7 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o BKE_displist_make_mball(depsgraph, scene, ob); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: { bool for_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); @@ -300,7 +300,7 @@ void BKE_object_data_batch_cache_dirty_tag(ID *object_data) BKE_lattice_batch_cache_dirty_tag((struct Lattice *)object_data, BKE_LATTICE_BATCH_DIRTY_ALL); break; - case ID_CU: + case ID_CU_LEGACY: BKE_curve_batch_cache_dirty_tag((struct Curve *)object_data, BKE_CURVE_BATCH_DIRTY_ALL); break; case ID_MB: @@ -364,7 +364,7 @@ void BKE_object_data_select_update(Depsgraph *depsgraph, ID *object_data) case ID_ME: BKE_mesh_batch_cache_dirty_tag((Mesh *)object_data, BKE_MESH_BATCH_DIRTY_SELECT); break; - case ID_CU: + case ID_CU_LEGACY: BKE_curve_batch_cache_dirty_tag((Curve *)object_data, BKE_CURVE_BATCH_DIRTY_SELECT); break; case ID_LT: diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index 7b8e5a1409a..38066f95084 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -2967,7 +2967,7 @@ static void curve_surf_to_softbody(Object *ob) totvert = BKE_nurbList_verts_count(&cu->nurb); if (ob->softflag & OB_SB_EDGES) { - if (ob->type == OB_CURVE) { + if (ob->type == OB_CURVES_LEGACY) { totspring = totvert - BLI_listbase_count(&cu->nurb); } } @@ -3320,7 +3320,7 @@ static void softbody_reset(Object *ob, SoftBody *sb, float (*vertexCos)[3], int break; case OB_LATTICE: break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: break; default: @@ -3537,7 +3537,7 @@ void sbObjectStep(struct Depsgraph *depsgraph, case OB_LATTICE: lattice_to_softbody(ob); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: curve_surf_to_softbody(ob); break; diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c index cb5e351e78a..5f1bb7db231 100644 --- a/source/blender/blenkernel/intern/vfont.c +++ b/source/blender/blenkernel/intern/vfont.c @@ -1302,8 +1302,8 @@ static bool vfont_to_curve(Object *ob, MEM_freeN(i_textbox_array); /* TEXT ON CURVE */ - /* NOTE: Only OB_CURVE objects could have a path. */ - if (cu->textoncurve && cu->textoncurve->type == OB_CURVE) { + /* NOTE: Only OB_CURVES_LEGACY objects could have a path. */ + if (cu->textoncurve && cu->textoncurve->type == OB_CURVES_LEGACY) { BLI_assert(cu->textoncurve->runtime.curve_cache != NULL); if (cu->textoncurve->runtime.curve_cache != NULL && cu->textoncurve->runtime.curve_cache->anim_path_accum_length != NULL) { diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 84625fea6fc..7dd35203a89 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -2913,7 +2913,7 @@ static const char *dataname(short id_code) return "Data from MA"; case ID_TE: return "Data from TE"; - case ID_CU: + case ID_CU_LEGACY: return "Data from CU"; case ID_GR: return "Data from GR"; diff --git a/source/blender/blenloader/intern/versioning_250.c b/source/blender/blenloader/intern/versioning_250.c index 6bf3402eafe..ac59e3efc72 100644 --- a/source/blender/blenloader/intern/versioning_250.c +++ b/source/blender/blenloader/intern/versioning_250.c @@ -1644,7 +1644,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain) BLI_addtail((ListBase *)&ob->modifiers, lmd); ob->partype = PAROBJECT; } - else if (parent->type == OB_CURVE && ob->partype == PARCURVE) { + else if (parent->type == OB_CURVES_LEGACY && ob->partype == PARCURVE) { CurveModifierData *cmd; cmd = (CurveModifierData *)BKE_modifier_new(eModifierType_Curve); diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index cd4efa95d5e..2908b2b151b 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -1356,7 +1356,7 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) bFollowPathConstraint *data = con->data; Object *obc = blo_do_versions_newlibadr(fd, lib, data->tar); - if (obc && obc->type == OB_CURVE) { + if (obc && obc->type == OB_CURVES_LEGACY) { Curve *cu = blo_do_versions_newlibadr(fd, lib, obc->data); if (cu) { cu->flag |= CU_PATH; diff --git a/source/blender/blentranslation/BLT_translation.h b/source/blender/blentranslation/BLT_translation.h index a7873d5d2d0..08a0c9605e5 100644 --- a/source/blender/blentranslation/BLT_translation.h +++ b/source/blender/blentranslation/BLT_translation.h @@ -92,7 +92,7 @@ bool BLT_lang_is_ime_supported(void); #define BLT_I18NCONTEXT_ID_CAMERA "Camera" #define BLT_I18NCONTEXT_ID_CACHEFILE "CacheFile" #define BLT_I18NCONTEXT_ID_COLLECTION "Collection" -#define BLT_I18NCONTEXT_ID_CURVE "Curve" +#define BLT_I18NCONTEXT_ID_CURVE_LEGACY "Curve" #define BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE "FreestyleLineStyle" #define BLT_I18NCONTEXT_ID_GPENCIL "GPencil" #define BLT_I18NCONTEXT_ID_CURVES "Curves" @@ -154,7 +154,7 @@ typedef struct { BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CAMERA, "id_camera"), \ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CACHEFILE, "id_cachefile"), \ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_COLLECTION, "id_collection"), \ - BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVE, "id_curve"), \ + BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "id_curve"), \ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, "id_fs_linestyle"), \ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_GPENCIL, "id_gpencil"), \ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVES, "id_curves"), \ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 2a0d5ce9116..940c0bfc93c 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -580,7 +580,7 @@ void DepsgraphNodeBuilder::build_id(ID *id) break; case ID_ME: case ID_MB: - case ID_CU: + case ID_CU_LEGACY: case ID_LT: case ID_GD: case ID_CV: @@ -872,7 +872,7 @@ void DepsgraphNodeBuilder::build_object_data(Object *object) /* type-specific data. */ switch (object->type) { case OB_MESH: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: case OB_SURF: case OB_MBALL: @@ -1504,7 +1504,7 @@ void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata) op_node->set_as_entry(); break; } - case ID_CU: { + case ID_CU_LEGACY: { op_node = add_operation_node(obdata, NodeType::GEOMETRY, OperationCode::GEOMETRY_EVAL, diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 25d7a0a6ac2..7006ddc43a8 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -221,7 +221,8 @@ OperationCode bone_target_opcode(ID *target, bool object_have_geometry_component(const Object *object) { - return ELEM(object->type, OB_MESH, OB_CURVE, OB_FONT, OB_SURF, OB_MBALL, OB_LATTICE, OB_GPENCIL); + return ELEM( + object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_MBALL, OB_LATTICE, OB_GPENCIL); } } // namespace @@ -537,7 +538,7 @@ void DepsgraphRelationBuilder::build_id(ID *id) break; case ID_ME: case ID_MB: - case ID_CU: + case ID_CU_LEGACY: case ID_LT: case ID_CV: case ID_PT: @@ -827,7 +828,7 @@ void DepsgraphRelationBuilder::build_object_data(Object *object) /* type-specific data. */ switch (object->type) { case OB_MESH: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: case OB_SURF: case OB_MBALL: @@ -983,7 +984,7 @@ void DepsgraphRelationBuilder::build_object_parent(Object *object) add_relation(parent_key, object_transform_key, "Lattice Deform Parent"); add_relation(geom_key, object_transform_key, "Lattice Deform Parent Geom"); } - else if (object->parent->type == OB_CURVE) { + else if (object->parent->type == OB_CURVES_LEGACY) { Curve *cu = (Curve *)object->parent->data; if (cu->flag & CU_PATH) { @@ -2040,7 +2041,7 @@ void DepsgraphRelationBuilder::build_shapekeys(Key *key) * Therefore, each user of a piece of shared geometry data ends up evaluating * its own version of the stuff, complete with whatever modifiers it may use. * - * - The data-blocks for the geometry data - "obdata" (e.g. ID_ME, ID_CU, ID_LT.) + * - The data-blocks for the geometry data - "obdata" (e.g. ID_ME, ID_CU_LEGACY, ID_LT.) * are used for * 1) calculating the bounding boxes of the geometry data, * 2) aggregating inward links from other objects (e.g. for text on curve) @@ -2125,7 +2126,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) /* Materials. */ build_materials(object->mat, object->totcol); /* Geometry collision. */ - if (ELEM(object->type, OB_MESH, OB_CURVE, OB_LATTICE)) { + if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_LATTICE)) { // add geometry collider relations } /* Make sure uber update is the last in the dependencies. */ @@ -2220,7 +2221,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry_datablock(ID *obdata) break; case ID_MB: break; - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)obdata; if (cu->bevobj != nullptr) { ComponentKey bevob_geom_key(&cu->bevobj->id, NodeType::GEOMETRY); diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc index b5968cbaeca..3a810c8155f 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.cc +++ b/source/blender/depsgraph/intern/depsgraph_tag.cc @@ -71,7 +71,7 @@ void depsgraph_geometry_tag_to_component(const ID *id, NodeType *component_type) bool is_selectable_data_id_type(const ID_Type id_type) { - return ELEM(id_type, ID_ME, ID_CU, ID_MB, ID_LT, ID_GD, ID_CV, ID_PT, ID_VO); + return ELEM(id_type, ID_ME, ID_CU_LEGACY, ID_MB, ID_LT, ID_GD, ID_CV, ID_PT, ID_VO); } void depsgraph_select_tag_to_component_opcode(const ID *id, @@ -332,7 +332,7 @@ void deg_graph_id_tag_legacy_compat( } break; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *curve = (Curve *)id; if (curve->key != nullptr) { ID *key_id = &curve->key->id; @@ -569,7 +569,7 @@ NodeType geometry_tag_to_component(const ID *id) const Object *object = (Object *)id; switch (object->type) { case OB_MESH: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: case OB_LATTICE: @@ -586,7 +586,7 @@ NodeType geometry_tag_to_component(const ID *id) break; } case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_LT: case ID_MB: case ID_CV: diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc index 646e4d6d6d6..6346bab1fe8 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc @@ -125,7 +125,7 @@ void nested_id_hack_discard_pointers(ID *id_cow) SPECIAL_CASE(ID_WO, World, nodetree) SPECIAL_CASE(ID_SIM, Simulation, nodetree) - SPECIAL_CASE(ID_CU, Curve, key) + SPECIAL_CASE(ID_CU_LEGACY, Curve, key) SPECIAL_CASE(ID_LT, Lattice, key) SPECIAL_CASE(ID_ME, Mesh, key) @@ -174,7 +174,7 @@ const ID *nested_id_hack_get_discarded_pointers(NestedIDHackTempStorage *storage SPECIAL_CASE(ID_WO, World, nodetree, world) SPECIAL_CASE(ID_SIM, Simulation, nodetree, simulation) - SPECIAL_CASE(ID_CU, Curve, key, curve) + SPECIAL_CASE(ID_CU_LEGACY, Curve, key, curve) SPECIAL_CASE(ID_LT, Lattice, key, lattice) SPECIAL_CASE(ID_ME, Mesh, key, mesh) @@ -214,7 +214,7 @@ void nested_id_hack_restore_pointers(const ID *old_id, ID *new_id) SPECIAL_CASE(ID_WO, World, nodetree) SPECIAL_CASE(ID_SIM, Simulation, nodetree) - SPECIAL_CASE(ID_CU, Curve, key) + SPECIAL_CASE(ID_CU_LEGACY, Curve, key) SPECIAL_CASE(ID_LT, Lattice, key) SPECIAL_CASE(ID_ME, Mesh, key) @@ -252,7 +252,7 @@ void ntree_hack_remap_pointers(const Depsgraph *depsgraph, ID *id_cow) SPECIAL_CASE(ID_WO, World, nodetree, bNodeTree) SPECIAL_CASE(ID_SIM, Simulation, nodetree, bNodeTree) - SPECIAL_CASE(ID_CU, Curve, key, Key) + SPECIAL_CASE(ID_CU_LEGACY, Curve, key, Key) SPECIAL_CASE(ID_LT, Lattice, key, Key) SPECIAL_CASE(ID_ME, Mesh, key, Key) @@ -578,7 +578,7 @@ void update_edit_mode_pointers(const Depsgraph *depsgraph, const ID *id_orig, ID case ID_ME: update_mesh_edit_mode_pointers(id_orig, id_cow); break; - case ID_CU: + case ID_CU_LEGACY: update_curve_edit_mode_pointers(depsgraph, id_orig, id_cow); break; case ID_MB: @@ -953,7 +953,7 @@ void discard_edit_mode_pointers(ID *id_cow) case ID_ME: discard_mesh_edit_mode_pointers(id_cow); break; - case ID_CU: + case ID_CU_LEGACY: discard_curve_edit_mode_pointers(id_cow); break; case ID_MB: 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 0992e242c7a..50012350036 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 @@ -81,7 +81,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, OB_CURVE, OB_FONT) && data_eval != nullptr) { + if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_CURVES_LEGACY, 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 diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index e7bb1bf3862..ad0d939e99a 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -212,7 +212,7 @@ BLI_INLINE OVERLAY_DupliData *OVERLAY_duplidata_get(Object *ob, void *vedata, bo { OVERLAY_DupliData **dupli_data = (OVERLAY_DupliData **)DRW_duplidata_get(vedata); *do_init = false; - if (!ELEM(ob->type, OB_MESH, OB_SURF, OB_LATTICE, OB_CURVE, OB_FONT)) { + if (!ELEM(ob->type, OB_MESH, OB_SURF, OB_LATTICE, OB_CURVES_LEGACY, OB_FONT)) { return NULL; } @@ -239,7 +239,7 @@ static bool overlay_object_is_edit_mode(const OVERLAY_PrivateData *pd, const Obj return pd->ctx_mode == CTX_MODE_EDIT_MESH; case OB_ARMATURE: return pd->ctx_mode == CTX_MODE_EDIT_ARMATURE; - case OB_CURVE: + case OB_CURVES_LEGACY: return pd->ctx_mode == CTX_MODE_EDIT_CURVE; case OB_SURF: return pd->ctx_mode == CTX_MODE_EDIT_SURFACE; @@ -298,7 +298,7 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) (ob->sculpt->mode_type == OB_MODE_SCULPT); const bool has_surface = ELEM(ob->type, OB_MESH, - OB_CURVE, + OB_CURVES_LEGACY, OB_SURF, OB_MBALL, OB_FONT, @@ -368,7 +368,7 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) OVERLAY_edit_armature_cache_populate(vedata, ob); } break; - case OB_CURVE: + case OB_CURVES_LEGACY: OVERLAY_edit_curve_cache_populate(vedata, ob); break; case OB_SURF: diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c index e370873c234..aae12e5513e 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.c +++ b/source/blender/draw/engines/overlay/overlay_extra.c @@ -456,7 +456,7 @@ static void OVERLAY_texture_space(OVERLAY_ExtraCallBuffers *cb, Object *ob, cons case ID_ME: BKE_mesh_texspace_get_reference((Mesh *)ob_data, NULL, &texcoloc, &texcosize); break; - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)ob_data; BKE_curve_texspace_ensure(cu); texcoloc = cu->loc; @@ -499,7 +499,7 @@ static void OVERLAY_forcefield(OVERLAY_ExtraCallBuffers *cb, Object *ob, ViewLay int theme_id = DRW_object_wire_theme_get(ob, view_layer, NULL); float *color = DRW_color_background_blend_get(theme_id); PartDeflect *pd = ob->pd; - Curve *cu = (ob->type == OB_CURVE) ? ob->data : NULL; + Curve *cu = (ob->type == OB_CURVES_LEGACY) ? ob->data : NULL; union { float mat[4][4]; diff --git a/source/blender/draw/engines/overlay/overlay_wireframe.c b/source/blender/draw/engines/overlay/overlay_wireframe.c index 24eceb30441..2636d7876d5 100644 --- a/source/blender/draw/engines/overlay/overlay_wireframe.c +++ b/source/blender/draw/engines/overlay/overlay_wireframe.c @@ -196,14 +196,14 @@ void OVERLAY_wireframe_cache_populate(OVERLAY_Data *vedata, } } - if (ELEM(ob->type, OB_CURVE, OB_FONT, OB_SURF)) { + if (ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { OVERLAY_ExtraCallBuffers *cb = OVERLAY_extra_call_buffer_get(vedata, ob); float *color; DRW_object_wire_theme_get(ob, draw_ctx->view_layer, &color); struct GPUBatch *geom = NULL; switch (ob->type) { - case OB_CURVE: + case OB_CURVES_LEGACY: geom = DRW_cache_curve_edge_wire_get(ob); break; case OB_FONT: diff --git a/source/blender/draw/engines/select/select_draw_utils.c b/source/blender/draw/engines/select/select_draw_utils.c index 82812ef98a5..7615b5bb39c 100644 --- a/source/blender/draw/engines/select/select_draw_utils.c +++ b/source/blender/draw/engines/select/select_draw_utils.c @@ -225,7 +225,7 @@ void select_id_draw_object(void *vedata, stl, ob, select_mode, initial_offset, r_vert_offset, r_edge_offset, r_face_offset); } break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: break; } diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index ce8d3136432..8fc97ddcfc2 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -813,7 +813,7 @@ GPUBatch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold) switch (ob->type) { case OB_MESH: return DRW_cache_mesh_edge_detection_get(ob, r_is_manifold); - case OB_CURVE: + case OB_CURVES_LEGACY: return NULL; case OB_SURF: return DRW_cache_surf_edge_detection_get(ob, r_is_manifold); @@ -837,7 +837,7 @@ GPUBatch *DRW_cache_object_face_wireframe_get(Object *ob) switch (ob->type) { case OB_MESH: return DRW_cache_mesh_face_wireframe_get(ob); - case OB_CURVE: + case OB_CURVES_LEGACY: return NULL; case OB_SURF: return DRW_cache_surf_face_wireframe_get(ob); @@ -864,7 +864,7 @@ GPUBatch *DRW_cache_object_loose_edges_get(struct Object *ob) switch (ob->type) { case OB_MESH: return DRW_cache_mesh_loose_edges_get(ob); - case OB_CURVE: + case OB_CURVES_LEGACY: return NULL; case OB_SURF: return DRW_cache_surf_loose_edges_get(ob); @@ -888,7 +888,7 @@ GPUBatch *DRW_cache_object_surface_get(Object *ob) switch (ob->type) { case OB_MESH: return DRW_cache_mesh_surface_get(ob); - case OB_CURVE: + case OB_CURVES_LEGACY: return NULL; case OB_SURF: return DRW_cache_surf_surface_get(ob); @@ -915,7 +915,7 @@ GPUVertBuf *DRW_cache_object_pos_vertbuf_get(Object *ob) switch (type) { case OB_MESH: return DRW_mesh_batch_cache_pos_vertbuf_get((me != NULL) ? me : ob->data); - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: return DRW_curve_batch_cache_pos_vertbuf_get(ob->data); @@ -947,7 +947,7 @@ int DRW_cache_object_material_count_get(struct Object *ob) switch (type) { case OB_MESH: return DRW_mesh_material_count_get(ob, (me != NULL) ? me : ob->data); - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: return DRW_curve_material_count_get(ob->data); @@ -972,7 +972,7 @@ GPUBatch **DRW_cache_object_surface_material_get(struct Object *ob, switch (ob->type) { case OB_MESH: return DRW_cache_mesh_surface_shaded_get(ob, gpumat_array, gpumat_array_len); - case OB_CURVE: + case OB_CURVES_LEGACY: return NULL; case OB_SURF: return DRW_cache_surf_surface_shaded_get(ob, gpumat_array, gpumat_array_len); @@ -2922,21 +2922,21 @@ 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); + BLI_assert(ob->type == OB_CURVES_LEGACY); struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_wire_edge(cu); } GPUBatch *DRW_cache_curve_edge_normal_get(Object *ob) { - BLI_assert(ob->type == OB_CURVE); + BLI_assert(ob->type == OB_CURVES_LEGACY); struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_normal_edge(cu); } GPUBatch *DRW_cache_curve_edge_overlay_get(Object *ob) { - BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF)); + BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)); struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_edit_edges(cu); @@ -2944,7 +2944,7 @@ GPUBatch *DRW_cache_curve_edge_overlay_get(Object *ob) GPUBatch *DRW_cache_curve_vert_overlay_get(Object *ob) { - BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF)); + BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)); struct Curve *cu = ob->data; return DRW_curve_batch_cache_get_edit_verts(cu); @@ -3373,7 +3373,7 @@ void drw_batch_cache_validate(Object *ob) case OB_MESH: DRW_mesh_batch_cache_validate(ob, (Mesh *)ob->data); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: DRW_curve_batch_cache_validate((Curve *)ob->data); break; @@ -3423,7 +3423,7 @@ void drw_batch_cache_generate_requested(Object *ob) DRW_mesh_batch_cache_create_requested( DST.task_graph, ob, (Mesh *)ob->data, scene, is_paint_mode, use_hide); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: DRW_curve_batch_cache_create_requested(ob, scene); break; diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc index abba3beb893..6a3d3fa5e9e 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.cc +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -945,7 +945,7 @@ int DRW_curve_material_count_get(Curve *cu) void DRW_curve_batch_cache_create_requested(Object *ob, const struct Scene *scene) { - BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)); + BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT)); Curve *cu = (Curve *)ob->data; CurveBatchCache *cache = curve_batch_cache_get(cu); diff --git a/source/blender/draw/intern/draw_common.c b/source/blender/draw/intern/draw_common.c index fcfaf404fc2..2897234f4dc 100644 --- a/source/blender/draw/intern/draw_common.c +++ b/source/blender/draw/intern/draw_common.c @@ -412,7 +412,7 @@ bool DRW_object_is_flat(Object *ob, int *r_axis) if (!ELEM(ob->type, OB_MESH, - OB_CURVE, + OB_CURVES_LEGACY, OB_SURF, OB_FONT, OB_MBALL, diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 440f74af64b..2886fe53879 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -2774,7 +2774,7 @@ void DRW_draw_depth_object( GPU_uniformbuf_free(ubo); } break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: break; } diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 3b35b8c1f9d..0b97323f18f 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -514,7 +514,7 @@ static void drw_call_calc_orco(Object *ob, float (*r_orcofacs)[4]) case ID_ME: BKE_mesh_texspace_get_reference((Mesh *)ob_data, NULL, &texcoloc, &texcosize); break; - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)ob_data; BKE_curve_texspace_ensure(cu); texcoloc = cu->loc; @@ -566,7 +566,7 @@ static void drw_call_obinfos_init(DRWObjectInfos *ob_infos, Object *ob) drw_call_calc_orco(ob, ob_infos->orcotexfac); /* Random float value. */ uint random = (DST.dupli_source) ? - DST.dupli_source->random_id : + DST.dupli_source->random_id : /* TODO(fclem): this is rather costly to do at runtime. Maybe we can * put it in ob->runtime and make depsgraph ensure it is up to date. */ BLI_hash_int_2d(BLI_hash_string(ob->id.name + 2), 0); diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index 4780352e5dc..3d768b84846 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -668,7 +668,7 @@ static int acf_object_icon(bAnimListElem *ale) return ICON_OUTLINER_OB_MESH; case OB_CAMERA: return ICON_OUTLINER_OB_CAMERA; - case OB_CURVE: + case OB_CURVES_LEGACY: return ICON_OUTLINER_OB_CURVE; case OB_MBALL: return ICON_OUTLINER_OB_META; diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index d6163f21c79..0389e57627a 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -2530,9 +2530,9 @@ static size_t animdata_filter_ds_obdata( expanded = FILTER_LAM_OBJD(la); break; } - case OB_CURVE: /* ------- Curve ---------- */ - case OB_SURF: /* ------- Nurbs Surface ---------- */ - case OB_FONT: /* ------- Text Curve ---------- */ + case OB_CURVES_LEGACY: /* ------- Curve ---------- */ + case OB_SURF: /* ------- Nurbs Surface ---------- */ + case OB_FONT: /* ------- Text Curve ---------- */ { Curve *cu = (Curve *)ob->data; diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index 20a2251b6e1..a33fbb29f85 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -72,7 +72,7 @@ static bool curve_delete_vertices(Object *obedit, View3D *v3d); ListBase *object_editcurve_get(Object *ob) { - if (ob && ELEM(ob->type, OB_CURVE, OB_SURF)) { + if (ob && ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = ob->data; return &cu->editnurb->nurbs; } @@ -1238,7 +1238,7 @@ void ED_curve_editnurb_load(Main *bmain, Object *obedit) return; } - if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; ListBase newnurb = {NULL, NULL}, oldnurb = cu->nurb; @@ -1273,7 +1273,7 @@ void ED_curve_editnurb_make(Object *obedit) EditNurb *editnurb = cu->editnurb; KeyBlock *actkey; - if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { actkey = BKE_keyblock_from_object(obedit); if (actkey) { @@ -5637,7 +5637,7 @@ static int curve_extrude_exec(bContext *C, wmOperator *UNUSED(op)) } /* First test: curve? */ - if (obedit->type != OB_CURVE) { + if (obedit->type != OB_CURVES_LEGACY) { LISTBASE_FOREACH (Nurb *, nu, &editnurb->nurbs) { if ((nu->pntsv == 1) && (ED_curve_nurb_select_count(v3d, nu) < nu->pntsu)) { as_curve = true; @@ -5646,7 +5646,7 @@ static int curve_extrude_exec(bContext *C, wmOperator *UNUSED(op)) } } - if (obedit->type == OB_CURVE || as_curve) { + if (obedit->type == OB_CURVES_LEGACY || as_curve) { changed = ed_editcurve_extrude(cu, editnurb, v3d); } else { @@ -6715,7 +6715,7 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) Object *obedit = objects[ob_index]; ListBase *editnurb = object_editcurve_get(obedit); - if (obedit->type != OB_CURVE) { + if (obedit->type != OB_CURVES_LEGACY) { continue; } @@ -6874,7 +6874,7 @@ int ED_curve_join_objects_exec(bContext *C, wmOperator *op) cu = ob_active->data; BLI_movelisttolist(&cu->nurb, &tempbase); - if (ob_active->type == OB_CURVE && CU_IS_2D(cu)) { + if (ob_active->type == OB_CURVES_LEGACY && CU_IS_2D(cu)) { /* Account for mixed 2D/3D curves when joining */ BKE_curve_dimension_update(cu); } @@ -6984,7 +6984,7 @@ static bool match_texture_space_poll(bContext *C) { Object *object = CTX_data_active_object(C); - return object && ELEM(object->type, OB_CURVE, OB_SURF, OB_FONT); + return object && ELEM(object->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT); } static int match_texture_space_exec(bContext *C, wmOperator *UNUSED(op)) diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index 2aaebf494a6..d7201495f75 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -53,25 +53,25 @@ static const char *get_curve_defname(int type) if ((type & CU_TYPE) == CU_BEZIER) { switch (stype) { case CU_PRIM_CURVE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "BezierCurve"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "BezierCurve"); case CU_PRIM_CIRCLE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "BezierCircle"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "BezierCircle"); case CU_PRIM_PATH: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "CurvePath"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "CurvePath"); default: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "Curve"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "Curve"); } } else { switch (stype) { case CU_PRIM_CURVE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "NurbsCurve"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "NurbsCurve"); case CU_PRIM_CIRCLE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "NurbsCircle"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "NurbsCircle"); case CU_PRIM_PATH: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "NurbsPath"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "NurbsPath"); default: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "Curve"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "Curve"); } } } @@ -82,17 +82,17 @@ static const char *get_surf_defname(int type) switch (stype) { case CU_PRIM_CURVE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "SurfCurve"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCurve"); case CU_PRIM_CIRCLE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "SurfCircle"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCircle"); case CU_PRIM_PATCH: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "SurfPatch"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfPatch"); case CU_PRIM_SPHERE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "SurfSphere"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfSphere"); case CU_PRIM_DONUT: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "SurfTorus"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfTorus"); default: - return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE, "Surface"); + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "Surface"); } } @@ -510,11 +510,11 @@ static int curvesurf_prim_add(bContext *C, wmOperator *op, int type, int isSurf) } if (!isSurf) { /* adding curve */ - if (obedit == NULL || obedit->type != OB_CURVE) { + if (obedit == NULL || obedit->type != OB_CURVES_LEGACY) { const char *name = get_curve_defname(type); Curve *cu; - obedit = ED_object_add_type(C, OB_CURVE, name, loc, rot, true, local_view_bits); + obedit = ED_object_add_type(C, OB_CURVES_LEGACY, name, loc, rot, true, local_view_bits); newob = true; cu = (Curve *)obedit->data; diff --git a/source/blender/editors/curve/editcurve_undo.c b/source/blender/editors/curve/editcurve_undo.c index 7b68c859b43..888bb2169e0 100644 --- a/source/blender/editors/curve/editcurve_undo.c +++ b/source/blender/editors/curve/editcurve_undo.c @@ -162,7 +162,7 @@ static Object *editcurve_object_from_context(bContext *C) { ViewLayer *view_layer = CTX_data_view_layer(C); Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); - if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) { + if (obedit && ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; if (BKE_curve_editNurbs_get(cu) != NULL) { return obedit; diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 63239fd6341..d4518f21586 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1300,8 +1300,8 @@ static void gpencil_layer_to_curve(bContext *C, /* init the curve object (remove rotation and get curve data from it) * - must clear transforms set on object, as those skew our results */ - ob = BKE_object_add_only_object(bmain, OB_CURVE, gpl->info); - cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVE); + ob = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, gpl->info); + cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVES_LEGACY); BKE_collection_object_add(bmain, collection, ob); base_new = BKE_view_layer_base_find(view_layer, ob); DEG_relations_tag_update(bmain); /* added object */ diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index e277aa2e629..630f83cf828 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -2271,7 +2271,7 @@ int UI_icon_from_idcode(const int idcode) return ICON_CAMERA_DATA; case ID_CF: return ICON_FILE; - case ID_CU: + case ID_CU_LEGACY: return ICON_CURVE_DATA; case ID_GD: return ICON_OUTLINER_DATA_GREASEPENCIL; diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 2b7ca1f8b71..f8fbc2d01a6 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -731,7 +731,7 @@ static const char *template_id_browse_tip(const StructRNA *type) return N_("Browse Object to be linked"); case ID_ME: return N_("Browse Mesh Data to be linked"); - case ID_CU: + case ID_CU_LEGACY: return N_("Browse Curve Data to be linked"); case ID_MB: return N_("Browse Metaball Data to be linked"); @@ -844,7 +844,7 @@ static uiBut *template_id_def_new_but(uiBlock *block, BLT_I18NCONTEXT_ID_SCENE, BLT_I18NCONTEXT_ID_OBJECT, BLT_I18NCONTEXT_ID_MESH, - BLT_I18NCONTEXT_ID_CURVE, + BLT_I18NCONTEXT_ID_CURVE_LEGACY, BLT_I18NCONTEXT_ID_METABALL, BLT_I18NCONTEXT_ID_MATERIAL, BLT_I18NCONTEXT_ID_TEXTURE, diff --git a/source/blender/editors/mesh/editmesh_knife_project.c b/source/blender/editors/mesh/editmesh_knife_project.c index 84bda411d4a..bce46dd7cf7 100644 --- a/source/blender/editors/mesh/editmesh_knife_project.c +++ b/source/blender/editors/mesh/editmesh_knife_project.c @@ -58,7 +58,7 @@ static LinkNode *knifeproject_poly_from_object(const bContext *C, } me_eval_needs_free = false; } - else if (ELEM(ob->type, OB_FONT, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_FONT, OB_CURVES_LEGACY, OB_SURF)) { Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); me_eval = BKE_mesh_new_nomain_from_curve(ob_eval); me_eval_needs_free = true; diff --git a/source/blender/editors/mesh/editmesh_loopcut.c b/source/blender/editors/mesh/editmesh_loopcut.c index 72844908685..b2ed6780443 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.c +++ b/source/blender/editors/mesh/editmesh_loopcut.c @@ -755,7 +755,8 @@ void MESH_OT_loopcut(wmOperatorType *ot) RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items); RNA_def_property_enum_default(prop, PROP_INVSQUARE); RNA_def_property_ui_text(prop, "Falloff", "Falloff type the feather"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ /* For redo only. */ prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "Object Index", "", 0, INT_MAX); diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index a1e661cf2ac..9757dd1367f 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -269,7 +269,8 @@ static void mesh_operator_edgering_props(wmOperatorType *ot, RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items); RNA_def_property_enum_default(prop, PROP_SMOOTH); RNA_def_property_ui_text(prop, "Profile Shape", "Shape of the profile"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ } static void mesh_operator_edgering_props_get(wmOperator *op, struct EdgeRingOpSubdProps *op_props) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index c2d811f56dc..7771012f2a1 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -841,7 +841,7 @@ static int effector_add_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Curve *cu; ob = ED_object_add_type( - C, OB_CURVE, get_effector_defname(type), loc, rot, false, local_view_bits); + C, OB_CURVES_LEGACY, get_effector_defname(type), loc, rot, false, local_view_bits); cu = ob->data; cu->flag |= CU_PATH | CU_3D; @@ -2525,7 +2525,11 @@ void OBJECT_OT_duplicates_make_real(wmOperatorType *ot) * \{ */ static const EnumPropertyItem convert_target_items[] = { - {OB_CURVE, "CURVE", ICON_OUTLINER_OB_CURVE, "Curve", "Curve from Mesh or Text objects"}, + {OB_CURVES_LEGACY, + "CURVE", + ICON_OUTLINER_OB_CURVE, + "Curve", + "Curve from Mesh or Text objects"}, {OB_MESH, "MESH", ICON_OUTLINER_OB_MESH, @@ -2558,7 +2562,7 @@ static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene * * Also happens in case we are working on a copy of the object * (all its caches have been nuked then). */ - if (ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)) { + if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)) { /* We need 'for render' ON here, to enable computing bevel dipslist if needed. * Also makes sense anyway, we would not want e.g. to lose hidden parts etc. */ BKE_displist_make_curveTypes(depsgraph, scene, ob, true); @@ -2773,7 +2777,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) } } } - else if (ob->type == OB_MESH && target == OB_CURVE) { + else if (ob->type == OB_MESH && target == OB_CURVES_LEGACY) { ob->flag |= OB_DONE; if (keep_original) { @@ -2793,7 +2797,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) BKE_mesh_to_curve(bmain, depsgraph, scene, newob); - if (newob->type == OB_CURVE) { + if (newob->type == OB_CURVES_LEGACY) { BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */ if (newob->rigidbody_object != NULL) { ED_rigidbody_object_remove(bmain, scene, newob); @@ -2930,8 +2934,8 @@ static int object_convert_exec(bContext *C, wmOperator *op) Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); BKE_vfont_to_curve_ex(ob_eval, ob_eval->data, FO_EDIT, &cu->nurb, NULL, NULL, NULL, NULL); - newob->type = OB_CURVE; - cu->type = OB_CURVE; + newob->type = OB_CURVES_LEGACY; + cu->type = OB_CURVES_LEGACY; if (cu->vfont) { id_us_min(&cu->vfont->id); @@ -2955,7 +2959,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) if (ID_REAL_USERS(&cu->id) > 1) { for (ob1 = bmain->objects.first; ob1; ob1 = ob1->id.next) { if (ob1->data == ob->data) { - ob1->type = OB_CURVE; + ob1->type = OB_CURVES_LEGACY; DEG_id_tag_update(&ob1->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); } @@ -2988,7 +2992,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) basen = NULL; } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { ob->flag |= OB_DONE; if (target == OB_MESH) { @@ -3013,7 +3017,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) BKE_object_free_curve_cache(newob); } else if (target == OB_GPENCIL) { - if (ob->type != OB_CURVE) { + if (ob->type != OB_CURVES_LEGACY) { ob->flag &= ~OB_DONE; BKE_report(op->reports, RPT_ERROR, "Convert Surfaces to Grease Pencil is not supported"); } @@ -3159,7 +3163,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* Remove curves and meshes converted to Grease Pencil object. */ if (gpencilConverted) { FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) { - if (ELEM(ob_delete->type, OB_CURVE, OB_MESH)) { + if (ELEM(ob_delete->type, OB_CURVES_LEGACY, OB_MESH)) { if (ob_delete->flag & OB_DONE) { ED_object_base_free_and_unlink(bmain, scene, ob_delete); } @@ -3172,7 +3176,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* Remove Text curves converted to Grease Pencil object to avoid duplicated curves. */ if (gpencilCurveConverted) { FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) { - if (ELEM(ob_delete->type, OB_CURVE) && (ob_delete->flag & OB_DONE)) { + if (ELEM(ob_delete->type, OB_CURVES_LEGACY) && (ob_delete->flag & OB_DONE)) { ED_object_base_free_and_unlink(bmain, scene, ob_delete); } } @@ -3701,7 +3705,7 @@ static bool object_join_poll(bContext *C) return false; } - if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_ARMATURE, OB_GPENCIL)) { + if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_ARMATURE, OB_GPENCIL)) { return ED_operator_screenactive(C); } return false; @@ -3740,7 +3744,7 @@ static int object_join_exec(bContext *C, wmOperator *op) if (ob->type == OB_MESH) { ret = ED_mesh_join_objects_exec(C, op); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { ret = ED_curve_join_objects_exec(C, op); } else if (ob->type == OB_ARMATURE) { diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 30e3f2b0c69..8cbe1cf5595 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -605,7 +605,7 @@ static bool bake_objects_check(Main *bmain, continue; } - if (ELEM(ob_iter->type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL) == false) { + if (ELEM(ob_iter->type, OB_MESH, OB_FONT, OB_CURVES_LEGACY, OB_SURF, OB_MBALL) == false) { BKE_reportf(reports, RPT_ERROR, "Object \"%s\" is not a mesh or can't be converted to a mesh (Curve, Text, " diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index 4ccd35b512b..d717271b9cb 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -511,7 +511,7 @@ static void test_constraint( * * In other cases it should be impossible to have a type mismatch. */ - if (ct->tar->type != OB_CURVE) { + if (ct->tar->type != OB_CURVES_LEGACY) { con->flag |= CONSTRAINT_DISABLE; } else { @@ -2275,7 +2275,8 @@ static bool get_new_constraint_target( break; } - if (((!only_curve) || (ob->type == OB_CURVE)) && ((!only_mesh) || (ob->type == OB_MESH))) { + if (((!only_curve) || (ob->type == OB_CURVES_LEGACY)) && + ((!only_mesh) || (ob->type == OB_MESH))) { /* set target */ *tar_ob = ob; found = true; diff --git a/source/blender/editors/object/object_data_transform.c b/source/blender/editors/object/object_data_transform.c index f74556e3639..63513eac965 100644 --- a/source/blender/editors/object/object_data_transform.c +++ b/source/blender/editors/object/object_data_transform.c @@ -382,7 +382,7 @@ struct XFormObjectData *ED_object_data_xform_create_ex(ID *id, bool is_edit_mode break; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)id; struct Key *key = cu->key; @@ -505,7 +505,7 @@ void ED_object_data_xform_destroy(struct XFormObjectData *xod_base) } break; } - case ID_CU: { + case ID_CU_LEGACY: { struct XFormObjectData_Curve *xod = (struct XFormObjectData_Curve *)xod_base; if (xod->key_data != NULL) { MEM_freeN(xod->key_data); @@ -565,7 +565,7 @@ void ED_object_data_xform_by_mat4(struct XFormObjectData *xod_base, const float break; } - case ID_CU: { + case ID_CU_LEGACY: { BLI_assert(xod_base->is_edit_mode == false); /* Not used currently. */ Curve *cu = (Curve *)xod_base->id; @@ -670,7 +670,7 @@ void ED_object_data_xform_restore(struct XFormObjectData *xod_base) break; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)xod_base->id; struct Key *key = cu->key; @@ -745,7 +745,7 @@ void ED_object_data_xform_tag_update(struct XFormObjectData *xod_base) DEG_id_tag_update(<->id, ID_RECALC_GEOMETRY); break; } - case ID_CU: { + case ID_CU_LEGACY: { /* Generic update. */ Curve *cu = (Curve *)xod_base->id; DEG_id_tag_update(&cu->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index e4a026d3205..58594affc05 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -568,7 +568,7 @@ static bool ED_object_editmode_load_free_ex(Main *bmain, */ DEG_relations_tag_update(bmain); } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { const Curve *cu = obedit->data; if (cu->editnurb == NULL) { return false; @@ -799,7 +799,7 @@ bool ED_object_editmode_enter_ex(Main *bmain, Scene *scene, Object *ob, int flag WM_main_add_notifier(NC_SCENE | ND_MODE | NS_EDITMODE_LATTICE, scene); } - else if (ELEM(ob->type, OB_SURF, OB_CURVE)) { + else if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY)) { ok = true; ED_curve_editnurb_make(ob); @@ -1023,7 +1023,7 @@ void ED_object_check_force_modifiers(Main *bmain, Scene *scene, Object *object) if (!md) { if (pd && (pd->shape == PFIELD_SHAPE_SURFACE) && !ELEM(pd->forcefield, 0, PFIELD_GUIDE, PFIELD_TEXTURE)) { - if (ELEM(object->type, OB_MESH, OB_SURF, OB_FONT, OB_CURVE)) { + if (ELEM(object->type, OB_MESH, OB_SURF, OB_FONT, OB_CURVES_LEGACY)) { ED_object_modifier_add(NULL, bmain, scene, object, NULL, eModifierType_Surface); } } @@ -1552,7 +1552,7 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); changed = true; } - else if (ELEM(ob->type, OB_SURF, OB_CURVE)) { + else if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY)) { BKE_curve_smooth_flag_set(ob->data, use_smooth); changed = true; } diff --git a/source/blender/editors/object/object_hook.c b/source/blender/editors/object/object_hook.c index 338307dc8ca..dffbb3bedd5 100644 --- a/source/blender/editors/object/object_hook.c +++ b/source/blender/editors/object/object_hook.c @@ -342,7 +342,7 @@ static bool object_hook_index_array(Main *bmain, } return true; } - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: ED_curve_editnurb_load(bmain, obedit); ED_curve_editnurb_make(obedit); @@ -447,7 +447,7 @@ static void object_hook_select(Object *ob, HookModifierData *hmd) else if (ob->type == OB_LATTICE) { select_editlattice_hook(ob, hmd); } - else if (ob->type == OB_CURVE) { + else if (ob->type == OB_CURVES_LEGACY) { select_editcurve_hook(ob, hmd); } else if (ob->type == OB_SURF) { diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index b12dd00c658..8e9e8558016 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -118,7 +118,7 @@ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) } } break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: case OB_MBALL: diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index 38a955dca0a..e0d25baec16 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -110,7 +110,7 @@ static void object_force_modifier_update_for_bind(Depsgraph *depsgraph, Object * else if (ob->type == OB_MBALL) { BKE_displist_make_mball(depsgraph, scene_eval, ob_eval); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { BKE_displist_make_curveTypes(depsgraph, scene_eval, ob_eval, false); } else if (ob->type == OB_GPENCIL) { @@ -757,7 +757,7 @@ static bool modifier_apply_obdata( } } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); Curve *curve = ob->data; Curve *curve_eval = (Curve *)object_eval->data; diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 8834941d083..1204a57d59f 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -169,7 +169,7 @@ static int vertex_parent_set_exec(bContext *C, wmOperator *op) } } } - else if (ELEM(obedit->type, OB_SURF, OB_CURVE)) { + else if (ELEM(obedit->type, OB_SURF, OB_CURVES_LEGACY)) { ListBase *editnurb = object_editcurve_get(obedit); for (Nurb *nu = editnurb->first; nu != NULL; nu = nu->next) { @@ -341,7 +341,7 @@ EnumPropertyItem prop_clear_parent_types[] = { /* Helper for ED_object_parent_clear() - Remove deform-modifiers associated with parent */ static void object_remove_parent_deform_modifiers(Object *ob, const Object *par) { - if (ELEM(par->type, OB_ARMATURE, OB_LATTICE, OB_CURVE)) { + if (ELEM(par->type, OB_ARMATURE, OB_LATTICE, OB_CURVES_LEGACY)) { ModifierData *md, *mdn; /* assume that we only need to remove the first instance of matching deform modifier here */ @@ -363,7 +363,7 @@ static void object_remove_parent_deform_modifiers(Object *ob, const Object *par) free = true; } } - else if ((md->type == eModifierType_Curve) && (par->type == OB_CURVE)) { + else if ((md->type == eModifierType_Curve) && (par->type == OB_CURVES_LEGACY)) { CurveModifierData *cmd = (CurveModifierData *)md; if (cmd->object == par) { free = true; @@ -531,7 +531,7 @@ bool ED_object_parent_set(ReportList *reports, switch (partype) { case PAR_FOLLOW: case PAR_PATH_CONST: { - if (par->type != OB_CURVE) { + if (par->type != OB_CURVES_LEGACY) { return false; } Curve *cu = par->data; @@ -626,7 +626,7 @@ bool ED_object_parent_set(ReportList *reports, */ /* XXX currently this should only happen for meshes, curves, surfaces, * and lattices - this stuff isn't available for meta-balls yet. */ - if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { + if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_FONT, OB_LATTICE)) { ModifierData *md; switch (partype) { @@ -968,7 +968,7 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_BONE); uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_BONE_RELATIVE); } - else if (parent->type == OB_CURVE) { + else if (parent->type == OB_CURVES_LEGACY) { uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_CURVE); uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_FOLLOW); uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_PATH_CONST); @@ -1820,7 +1820,7 @@ static void single_obdata_users( ob->data, BKE_id_copy_ex(bmain, ob->data, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_ACTIONS)); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: ob->data = cu = ID_NEW_SET( diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c index f017fea7cdf..94aa7d59f9d 100644 --- a/source/blender/editors/object/object_transform.c +++ b/source/blender/editors/object/object_transform.c @@ -613,7 +613,7 @@ static int apply_objects_internal(bContext *C, OB_ARMATURE, OB_LATTICE, OB_MBALL, - OB_CURVE, + OB_CURVES_LEGACY, OB_SURF, OB_FONT, OB_GPENCIL)) { @@ -639,13 +639,13 @@ static int apply_objects_internal(bContext *C, } } - if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { ID *obdata = ob->data; Curve *cu; cu = ob->data; - if (((ob->type == OB_CURVE) && !(cu->flag & CU_3D)) && (apply_rot || apply_loc)) { + if (((ob->type == OB_CURVES_LEGACY) && !(cu->flag & CU_3D)) && (apply_rot || apply_loc)) { BKE_reportf( reports, RPT_ERROR, @@ -811,7 +811,7 @@ static int apply_objects_internal(bContext *C, MetaBall *mb = ob->data; BKE_mball_transform(mb, mat, do_props); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = ob->data; scale = mat3_to_scale(rsmat); BKE_curve_transform_ex(cu, mat, true, do_props, scale); @@ -1209,7 +1209,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) do_inverse_offset = true; } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = ob->data; if (centermode == ORIGIN_TO_CURSOR) { @@ -1223,7 +1223,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) } /* don't allow Z change if curve is 2D */ - if ((ob->type == OB_CURVE) && !(cu->flag & CU_3D)) { + if ((ob->type == OB_CURVES_LEGACY) && !(cu->flag & CU_3D)) { cent[2] = 0.0; } diff --git a/source/blender/editors/object/object_utils.c b/source/blender/editors/object/object_utils.c index 19940fbb0fc..cb9c8a92abe 100644 --- a/source/blender/editors/object/object_utils.c +++ b/source/blender/editors/object/object_utils.c @@ -64,7 +64,7 @@ bool ED_object_calc_active_center_for_editmode(Object *obedit, break; } - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: { Curve *cu = obedit->data; diff --git a/source/blender/editors/render/render_internal.cc b/source/blender/editors/render/render_internal.cc index 5308d4611e7..13f2eaad324 100644 --- a/source/blender/editors/render/render_internal.cc +++ b/source/blender/editors/render/render_internal.cc @@ -1188,5 +1188,6 @@ void RENDER_OT_shutter_curve_preset(wmOperatorType *ot) ot->exec = render_shutter_curve_preset_exec; prop = RNA_def_enum(ot->srna, "shape", prop_shape_items, CURVE_PRESET_SMOOTH, "Mode", ""); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ } diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 1ca2b01a2cb..be66e87f2e5 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -599,30 +599,30 @@ static int gather_frames_to_render_for_id(LibraryIDLinkCallbackData *cb_data) const ID_Type id_type = GS(id->name); switch (id_type) { /* Whitelist: */ - case ID_ME: /* Mesh */ - case ID_CU: /* Curve */ - case ID_MB: /* MetaBall */ - case ID_MA: /* Material */ - case ID_TE: /* Tex (Texture) */ - case ID_IM: /* Image */ - case ID_LT: /* Lattice */ - case ID_LA: /* Light */ - case ID_CA: /* Camera */ - case ID_KE: /* Key (shape key) */ - case ID_VF: /* VFont (Vector Font) */ - case ID_TXT: /* Text */ - case ID_SPK: /* Speaker */ - case ID_SO: /* Sound */ - case ID_AR: /* bArmature */ - case ID_NT: /* bNodeTree */ - case ID_PA: /* ParticleSettings */ - case ID_MC: /* MovieClip */ - case ID_MSK: /* Mask */ - case ID_LP: /* LightProbe */ - case ID_CV: /* Curves */ - case ID_PT: /* PointCloud */ - case ID_VO: /* Volume */ - case ID_SIM: /* Simulation */ + case ID_ME: /* Mesh */ + case ID_CU_LEGACY: /* Curve */ + case ID_MB: /* MetaBall */ + case ID_MA: /* Material */ + case ID_TE: /* Tex (Texture) */ + case ID_IM: /* Image */ + case ID_LT: /* Lattice */ + case ID_LA: /* Light */ + case ID_CA: /* Camera */ + case ID_KE: /* Key (shape key) */ + case ID_VF: /* VFont (Vector Font) */ + case ID_TXT: /* Text */ + case ID_SPK: /* Speaker */ + case ID_SO: /* Sound */ + case ID_AR: /* bArmature */ + case ID_NT: /* bNodeTree */ + case ID_PA: /* ParticleSettings */ + case ID_MC: /* MovieClip */ + case ID_MSK: /* Mask */ + case ID_LP: /* LightProbe */ + case ID_CV: /* Curves */ + case ID_PT: /* PointCloud */ + case ID_VO: /* Volume */ + case ID_SIM: /* Simulation */ break; /* Blacklist: */ diff --git a/source/blender/editors/render/render_shading.cc b/source/blender/editors/render/render_shading.cc index bad4208cf19..cd395674177 100644 --- a/source/blender/editors/render/render_shading.cc +++ b/source/blender/editors/render/render_shading.cc @@ -310,7 +310,7 @@ static int material_slot_assign_exec(bContext *C, wmOperator *UNUSED(op)) } } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { Nurb *nu; ListBase *nurbs = BKE_curve_editNurbs_get((Curve *)ob->data); @@ -411,7 +411,7 @@ static int material_slot_de_select(bContext *C, bool select) changed = EDBM_deselect_by_material(em, mat_nr_active, select); } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { ListBase *nurbs = BKE_curve_editNurbs_get((Curve *)ob->data); Nurb *nu; BPoint *bp; diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 7171aa7ac3b..75c704b5f7b 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -585,7 +585,7 @@ bool ED_operator_uvmap(bContext *C) bool ED_operator_editsurfcurve(bContext *C) { Object *obedit = CTX_data_edit_object(C); - if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) { + if (obedit && ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { return NULL != ((Curve *)obedit->data)->editnurb; } return false; @@ -604,7 +604,7 @@ bool ED_operator_editsurfcurve_region_view3d(bContext *C) bool ED_operator_editcurve(bContext *C) { Object *obedit = CTX_data_edit_object(C); - if (obedit && obedit->type == OB_CURVE) { + if (obedit && obedit->type == OB_CURVES_LEGACY) { return NULL != ((Curve *)obedit->data)->editnurb; } return false; @@ -613,7 +613,7 @@ bool ED_operator_editcurve(bContext *C) bool ED_operator_editcurve_3d(bContext *C) { Object *obedit = CTX_data_edit_object(C); - if (obedit && obedit->type == OB_CURVE) { + if (obedit && obedit->type == OB_CURVES_LEGACY) { Curve *cu = (Curve *)obedit->data; return (cu->flag & CU_3D) && (NULL != cu->editnurb); diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index f54b012b8dd..d470e6a3050 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -585,7 +585,8 @@ void BRUSH_OT_curve_preset(wmOperatorType *ot) ot->poll = brush_curve_preset_poll; prop = RNA_def_enum(ot->srna, "shape", prop_shape_items, CURVE_PRESET_SMOOTH, "Mode", ""); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ } /* face-select ops */ diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 287aef178ae..4e80e7ea5c2 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -230,7 +230,7 @@ static bool buttons_context_path_data(ButsContextPath *path, int type) return true; } if (RNA_struct_is_a(ptr->type, &RNA_Curve) && - (type == -1 || ELEM(type, OB_CURVE, OB_SURF, OB_FONT))) { + (type == -1 || ELEM(type, OB_CURVES_LEGACY, OB_SURF, OB_FONT))) { return true; } if (RNA_struct_is_a(ptr->type, &RNA_Armature) && (ELEM(type, -1, OB_ARMATURE))) { @@ -293,7 +293,7 @@ static bool buttons_context_path_modifier(ButsContextPath *path) if (ELEM(ob->type, OB_MESH, - OB_CURVE, + OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE, diff --git a/source/blender/editors/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index da899ef4c9a..6f0d5c2dbe9 100644 --- a/source/blender/editors/space_info/info_stats.cc +++ b/source/blender/editors/space_info/info_stats.cc @@ -152,7 +152,7 @@ static void stats_object(Object *ob, } break; case OB_SURF: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_FONT: { const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob); if ((me_eval != nullptr) && !BLI_gset_add(objects_gset, (void *)me_eval)) { @@ -260,7 +260,7 @@ static void stats_object_edit(Object *obedit, SceneStats *stats) stats->totvert += 2; } } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { /* OB_FONT has no cu->editnurb */ + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { /* OB_FONT has no cu->editnurb */ /* Curve Edit */ Curve *cu = static_cast(obedit->data); BezTriple *bezt; diff --git a/source/blender/editors/space_node/node_context_path.cc b/source/blender/editors/space_node/node_context_path.cc index a76988a60d0..349fa92d06d 100644 --- a/source/blender/editors/space_node/node_context_path.cc +++ b/source/blender/editors/space_node/node_context_path.cc @@ -47,7 +47,7 @@ static void context_path_add_object_data(Vector &path, Obje Light *light = (Light *)object.data; ui::context_path_add_generic(path, RNA_Light, light); } - if (ELEM(object.type, OB_CURVE, OB_FONT, OB_SURF) && object.data) { + if (ELEM(object.type, OB_CURVES_LEGACY, OB_FONT, OB_SURF) && object.data) { Curve *curve = (Curve *)object.data; ui::context_path_add_generic(path, RNA_Curve, curve); } diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index dd7ca128282..bc58ad226ee 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -2592,7 +2592,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case OB_CAMERA: data.icon = ICON_OUTLINER_OB_CAMERA; break; - case OB_CURVE: + case OB_CURVES_LEGACY: data.icon = ICON_OUTLINER_OB_CURVE; break; case OB_MBALL: @@ -2655,7 +2655,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case ID_ME: data.icon = ICON_OUTLINER_DATA_MESH; break; - case ID_CU: + case ID_CU_LEGACY: data.icon = ICON_OUTLINER_DATA_CURVE; break; case ID_MB: diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index bba4bac0e37..0516758e887 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -108,7 +108,7 @@ typedef struct TreeElementIcon { ID_LI, \ ID_OB, \ ID_ME, \ - ID_CU, \ + ID_CU_LEGACY, \ ID_MB, \ ID_NT, \ ID_MA, \ diff --git a/source/blender/editors/space_outliner/outliner_select.cc b/source/blender/editors/space_outliner/outliner_select.cc index df10ce002c3..c6b9d9577b5 100644 --- a/source/blender/editors/space_outliner/outliner_select.cc +++ b/source/blender/editors/space_outliner/outliner_select.cc @@ -1171,7 +1171,7 @@ static void outliner_set_properties_tab(bContext *C, TreeElement *te, TreeStoreE context = BCONTEXT_OBJECT; break; case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_MB: case ID_IM: case ID_LT: diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 18c37e08eff..53a81cf161e 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -117,7 +117,7 @@ static void get_element_operation_type( break; case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_MB: case ID_LT: case ID_LA: @@ -236,7 +236,7 @@ static void unlink_material_fn(bContext *UNUSED(C), totcol = me->totcol; matar = me->mat; } - else if (GS(tsep->id->name) == ID_CU) { + else if (GS(tsep->id->name) == ID_CU_LEGACY) { Curve *cu = (Curve *)tsep->id; totcol = cu->totcol; matar = cu->mat; diff --git a/source/blender/editors/space_outliner/outliner_tree.cc b/source/blender/editors/space_outliner/outliner_tree.cc index 1605d5874ae..06a5918f25c 100644 --- a/source/blender/editors/space_outliner/outliner_tree.cc +++ b/source/blender/editors/space_outliner/outliner_tree.cc @@ -579,7 +579,7 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner, * would require going over all tfaces, sort images in use. etc... */ break; } - case ID_CU: { + case ID_CU_LEGACY: { Curve *cu = (Curve *)id; if (outliner_animdata_test(cu->adt)) { diff --git a/source/blender/editors/space_outliner/tree/tree_element_id.cc b/source/blender/editors/space_outliner/tree/tree_element_id.cc index e126b024d52..64c73f57107 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_id.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_id.cc @@ -34,7 +34,7 @@ std::unique_ptr TreeElementID::createFromID(TreeElement &legacy_t return std::make_unique(legacy_te, (Scene &)id); case ID_OB: case ID_ME: - case ID_CU: + case ID_CU_LEGACY: case ID_MB: case ID_MA: case ID_TE: diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 3be890bfcc5..fbdc451cf06 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -264,7 +264,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, OB_CURVE, OB_FONT)) { + if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVES_LEGACY, OB_FONT)) { return nullptr; } diff --git a/source/blender/editors/space_view3d/view3d_buttons.c b/source/blender/editors/space_view3d/view3d_buttons.c index b77994e28cb..cf52134f5ab 100644 --- a/source/blender/editors/space_view3d/view3d_buttons.c +++ b/source/blender/editors/space_view3d/view3d_buttons.c @@ -355,7 +355,7 @@ static void v3d_editvertex_buts(uiLayout *layout, View3D *v3d, Object *ob, float has_meshdata = (tot || totedgedata); } - else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) { TransformMedian_Curve *median = &median_basis.curve; Curve *cu = ob->data; BPoint *bp; @@ -1089,7 +1089,7 @@ static void v3d_editvertex_buts(uiLayout *layout, View3D *v3d, Object *ob, float } } } - else if (ELEM(ob->type, OB_CURVE, OB_SURF) && + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF) && (apply_vcos || median_basis.curve.b_weight || median_basis.curve.weight || median_basis.curve.radius || median_basis.curve.tilt)) { const TransformMedian_Curve *median = &median_basis.curve, diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index c4078c4a690..593c4f6e755 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1351,7 +1351,7 @@ static void draw_selected_name( } } } - else if (ELEM(ob->type, OB_MESH, OB_LATTICE, OB_CURVE)) { + else if (ELEM(ob->type, OB_MESH, OB_LATTICE, OB_CURVES_LEGACY)) { /* try to display active bone and active shapekey too (if they exist) */ if (ob->type == OB_MESH && ob->mode & OB_MODE_WEIGHT_PAINT) { diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index f08c53fff47..e380a08dcc7 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -1313,7 +1313,7 @@ static bool view3d_lasso_select(bContext *C, case OB_MESH: changed = do_lasso_select_mesh(vc, wm_userdata, mcoords, mcoords_len, sel_op); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: changed = do_lasso_select_curve(vc, mcoords, mcoords_len, sel_op); break; @@ -2695,7 +2695,7 @@ static int view3d_select_exec(bContext *C, wmOperator *op) retval = ED_lattice_deselect_all_multi(C); } } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { retval = ED_curve_editnurb_select_pick(C, location, extend, deselect, toggle); if (!retval && deselect_all) { retval = ED_curve_deselect_all_multi(C); @@ -3586,7 +3586,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); } break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: changed = do_nurbs_box_select(&vc, &rect, sel_op); if (changed) { @@ -4342,7 +4342,7 @@ static bool obedit_circle_select(bContext *C, case OB_MESH: changed = mesh_circle_select(vc, wm_userdata, sel_op, mval, rad); break; - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: changed = nurbscurve_circle_select(vc, sel_op, mval, rad); break; diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 3bcc1b2968e..923084854e1 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -1967,7 +1967,7 @@ bool checkUseAxisMatrix(TransInfo *t) /* currently only checks for editmode */ if (t->flag & T_EDIT) { if ((t->around == V3D_AROUND_LOCAL_ORIGINS) && - (ELEM(t->obedit_type, OB_MESH, OB_CURVE, OB_MBALL, OB_ARMATURE))) { + (ELEM(t->obedit_type, OB_MESH, OB_CURVES_LEGACY, OB_MBALL, OB_ARMATURE))) { /* not all editmode supports axis-matrix */ return true; } diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 95b810daeaf..4a2169b381e 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -1040,7 +1040,7 @@ static void init_proportional_edit(TransInfo *t) /* Already calculated by uv_set_connectivity_distance. */ } else if (convert_type == TC_CURVE_VERTS) { - BLI_assert(t->obedit_type == OB_CURVE); + BLI_assert(t->obedit_type == OB_CURVES_LEGACY); set_prop_dist(t, false); } else { @@ -1049,7 +1049,7 @@ static void init_proportional_edit(TransInfo *t) sort_trans_data_dist(t); } - else if (ELEM(t->obedit_type, OB_CURVE)) { + else if (ELEM(t->obedit_type, OB_CURVES_LEGACY)) { /* Needed because bezier handles can be partially selected * and are still added into transform data. */ sort_trans_data_selected_first(t); @@ -1286,7 +1286,7 @@ static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armatur convert_type = TC_MESH_VERTS; } } - else if (ELEM(t->obedit_type, OB_CURVE, OB_SURF)) { + else if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF)) { convert_type = TC_CURVE_VERTS; } else if (t->obedit_type == OB_LATTICE) { diff --git a/source/blender/editors/transform/transform_convert_object_texspace.c b/source/blender/editors/transform/transform_convert_object_texspace.c index e12d0db8758..763af1f3384 100644 --- a/source/blender/editors/transform/transform_convert_object_texspace.c +++ b/source/blender/editors/transform/transform_convert_object_texspace.c @@ -44,7 +44,7 @@ void createTransTexspace(TransInfo *t) } id = ob->data; - if (id == NULL || !ELEM(GS(id->name), ID_ME, ID_CU, ID_MB)) { + if (id == NULL || !ELEM(GS(id->name), ID_ME, ID_CU_LEGACY, ID_MB)) { BKE_report(t->reports, RPT_ERROR, "Unsupported object type for text-space transform"); return; } diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 1d86a8f9413..2b523461800 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -731,7 +731,8 @@ void postTrans(bContext *C, TransInfo *t) if (t->data_len_all != 0) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { /* free data malloced per trans-data */ - if (ELEM(t->obedit_type, OB_CURVE, OB_SURF, OB_GPENCIL) || (t->spacetype == SPACE_GRAPH)) { + if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF, OB_GPENCIL) || + (t->spacetype == SPACE_GRAPH)) { TransData *td = tc->data; for (int a = 0; a < tc->data_len; a++, td++) { if (td->flag & TD_BEZTRIPLE) { diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 7e121a717aa..329840e064b 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -816,7 +816,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, } FOREACH_EDIT_OBJECT_END(); } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) { Curve *cu = ob_iter->data; Nurb *nu; diff --git a/source/blender/editors/transform/transform_gizmo_extrude_3d.c b/source/blender/editors/transform/transform_gizmo_extrude_3d.c index 6873ea862ce..f6f43f867ae 100644 --- a/source/blender/editors/transform/transform_gizmo_extrude_3d.c +++ b/source/blender/editors/transform/transform_gizmo_extrude_3d.c @@ -153,7 +153,7 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup) op_idname = "ARMATURE_OT_extrude_move"; ggd->normal_axis = 1; } - else if (obact->type == OB_CURVE) { + else if (obact->type == OB_CURVES_LEGACY) { op_idname = "CURVE_OT_extrude_move"; ggd->normal_axis = 2; } diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index b6c4002b1c7..6162dfc9bb5 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -57,7 +57,7 @@ bool transdata_check_local_center(const TransInfo *t, short around) return ((around == V3D_AROUND_LOCAL_ORIGINS) && ((t->options & (CTX_OBJECT | CTX_POSE_BONE)) || /* implicit: (t->flag & T_EDIT) */ - (ELEM(t->obedit_type, OB_MESH, OB_CURVE, OB_MBALL, OB_ARMATURE, OB_GPENCIL)) || + (ELEM(t->obedit_type, OB_MESH, OB_CURVES_LEGACY, OB_MBALL, OB_ARMATURE, OB_GPENCIL)) || (t->spacetype == SPACE_GRAPH) || (t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE | CTX_SEQUENCER_IMAGE)))); } diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index 3722e83e451..936aca7d2e0 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -628,7 +628,7 @@ void Transform_Properties(struct wmOperatorType *ot, int flags) "Proportional Falloff", "Falloff type for proportional editing mode"); /* Abusing id_curve :/ */ - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE_LEGACY); RNA_def_float(ot->srna, "proportional_size", 1, diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c index 6ca61d415ea..c0d943e17ee 100644 --- a/source/blender/editors/transform/transform_orientations.c +++ b/source/blender/editors/transform/transform_orientations.c @@ -350,7 +350,7 @@ bool BIF_createTransformOrientation(bContext *C, else if (obedit->type == OB_ARMATURE) { ts = createBoneSpace(C, reports, name, overwrite); } - else if (obedit->type == OB_CURVE) { + else if (obedit->type == OB_CURVES_LEGACY) { ts = createCurveSpace(C, reports, name, overwrite); } } @@ -984,7 +984,7 @@ int getTransformOrientation_ex(ViewLayer *view_layer, negate_v3(plane); } /* end editmesh */ - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; Nurb *nu = NULL; int a; diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index ce0cf3aa6cb..37bc753517f 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -86,7 +86,7 @@ int BIF_snappingSupported(Object *obedit) int status = 0; /* only support object mesh, armature, curves */ - if (obedit == NULL || ELEM(obedit->type, OB_MESH, OB_ARMATURE, OB_CURVE, OB_LATTICE, OB_MBALL)) { + if (obedit == NULL || ELEM(obedit->type, OB_MESH, OB_ARMATURE, OB_CURVES_LEGACY, OB_LATTICE, OB_MBALL)) { status = 1; } @@ -583,7 +583,7 @@ static short snap_mode_from_scene(TransInfo *t) /* All obedit types will match. */ const int obedit_type = t->obedit_type; if ((t->options & (CTX_GPENCIL_STROKES | CTX_CURSOR | CTX_OBMODE_XFORM_OBDATA)) || - ELEM(obedit_type, OB_MESH, OB_ARMATURE, OB_CURVE, OB_LATTICE, OB_MBALL, -1)) { + ELEM(obedit_type, OB_MESH, OB_ARMATURE, OB_CURVES_LEGACY, OB_LATTICE, OB_MBALL, -1)) { r_snap_mode = ts->snap_mode; if ((r_snap_mode & SCE_SNAP_MODE_INCREMENT) && (ts->snap_flag & SCE_SNAP_ABS_GRID) && (t->mode == TFM_TRANSLATION)) { @@ -617,7 +617,7 @@ static short snap_select_type_get(TransInfo *t) * When we're moving the origins, allow snapping onto our own geometry (see T69132). */ } else if ((obedit_type != -1) && - ELEM(obedit_type, OB_MESH, OB_ARMATURE, OB_CURVE, OB_LATTICE, OB_MBALL)) { + ELEM(obedit_type, OB_MESH, OB_ARMATURE, OB_CURVES_LEGACY, OB_LATTICE, OB_MBALL)) { /* Edit mode */ /* Temporary limited to edit mode meshes, armature, curves, metaballs. */ diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index ca6940040b2..f30da3ff6f3 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -1054,7 +1054,7 @@ static void raycast_obj_fn(SnapObjectContext *sctx, dt->r_hit_list); break; } - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: { if (!is_object_active) { @@ -2743,7 +2743,7 @@ static void snap_obj_fn(SnapObjectContext *sctx, dt->r_no, dt->r_index); break; - case OB_CURVE: + case OB_CURVES_LEGACY: retval = snapCurve( sctx, params, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); break; /* Use ATTR_FALLTHROUGH if we want to snap to the generated mesh. */ @@ -3117,7 +3117,7 @@ static short transform_snap_context_project_view3d_mixed_impl( sctx->runtime.has_occlusion_plane = false; /* By convention we only snap to the original elements of a curve. */ - if (has_hit && ob_eval->type != OB_CURVE) { + if (has_hit && ob_eval->type != OB_CURVES_LEGACY) { /* Compute the new clip_pane but do not add it yet. */ float new_clipplane[4]; BLI_ASSERT_UNIT_V3(no); diff --git a/source/blender/editors/util/ed_transverts.c b/source/blender/editors/util/ed_transverts.c index 0523a58825e..c1e093d5555 100644 --- a/source/blender/editors/util/ed_transverts.c +++ b/source/blender/editors/util/ed_transverts.c @@ -45,7 +45,7 @@ void ED_transverts_update_obedit(TransVertStore *tvs, Object *obedit) BMEditMesh *em = BKE_editmesh_from_object(obedit); BM_mesh_normals_update(em->bm); } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; ListBase *nurbs = BKE_curve_editNurbs_get(cu); Nurb *nu = nurbs->first; @@ -181,7 +181,8 @@ static void set_mapped_co(void *vuserdata, int index, const float co[3], const f bool ED_transverts_check_obedit(const Object *obedit) { - return (ELEM(obedit->type, OB_ARMATURE, OB_LATTICE, OB_MESH, OB_SURF, OB_CURVE, OB_MBALL)); + return ( + ELEM(obedit->type, OB_ARMATURE, OB_LATTICE, OB_MESH, OB_SURF, OB_CURVES_LEGACY, OB_MBALL)); } void ED_transverts_create_from_obedit(TransVertStore *tvs, const Object *obedit, const int mode) @@ -351,7 +352,7 @@ void ED_transverts_create_from_obedit(TransVertStore *tvs, const Object *obedit, } } } - else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { + else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) { Curve *cu = obedit->data; int totmalloc = 0; ListBase *nurbs = BKE_curve_editNurbs_get(cu); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index ba61ea148ca..ea7a676b157 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -219,7 +219,7 @@ static void add_this_collection(Collection *c, return; } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (c, ob, mode) { - if (ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVE, OB_SURF, OB_FONT)) { + if (ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { if (ob->lineart.usage != OBJECT_LRT_EXCLUDE) { DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier"); DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 6177b9ac366..e24452b1072 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -2193,7 +2193,7 @@ static void lineart_main_load_geometries( mul_m4db_m4db_m4fl_uniq(obi->model_view_proj, rb->view_projection, ob->obmat); mul_m4db_m4db_m4fl_uniq(obi->model_view, rb->view, ob->obmat); - if (!ELEM(use_ob->type, OB_MESH, OB_MBALL, OB_CURVE, OB_SURF, OB_FONT)) { + if (!ELEM(use_ob->type, OB_MESH, OB_MBALL, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { continue; } diff --git a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc index 4723b89de08..d33adfdb4b9 100644 --- a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc +++ b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc @@ -190,7 +190,7 @@ ABCAbstractWriter *ABCHierarchyIterator::create_data_writer_for_object_type( return new ABCMeshWriter(writer_args); case OB_CAMERA: return new ABCCameraWriter(writer_args); - case OB_CURVE: + case OB_CURVES_LEGACY: if (params_.curves_as_mesh) { return new ABCCurveMeshWriter(writer_args); } diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc index 5e307d59ed4..89ed7c5ce06 100644 --- a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc @@ -84,7 +84,7 @@ bool ABCNurbsWriter::check_is_animated(const HierarchyContext &context) const bool ABCNurbsWriter::is_supported(const HierarchyContext *context) const { - return ELEM(context->object->type, OB_SURF, OB_CURVE); + return ELEM(context->object->type, OB_SURF, OB_CURVES_LEGACY); } static void get_knots(std::vector &knots, const int num_knots, float *nu_knots) diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index a6400dc6f6c..d8859acdf5f 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -66,7 +66,7 @@ bool AbcCurveReader::accepts_object_type( return false; } - if (ob->type != OB_CURVE) { + if (ob->type != OB_CURVES_LEGACY) { *err_str = "Object type mismatch, Alembic object path points to Curves."; return false; } @@ -76,7 +76,7 @@ bool AbcCurveReader::accepts_object_type( void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) { - Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); + Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVES_LEGACY); cu->flag |= CU_3D; cu->actvert = CU_ACT_NONE; @@ -91,7 +91,7 @@ void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele } } - m_object = BKE_object_add_only_object(bmain, OB_CURVE, m_object_name.c_str()); + m_object = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, m_object_name.c_str()); m_object->data = cu; read_curve_sample(cu, m_curves_schema, sample_sel); diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc index b744106ff95..78f9c0a85b4 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.cc +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -69,7 +69,7 @@ bool AbcNurbsReader::accepts_object_type( return false; } - if (ob->type != OB_CURVE) { + if (ob->type != OB_CURVES_LEGACY) { *err_str = "Object type mismatch, Alembic object path points to NURBS."; return false; } diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc index a86deb7c9f0..7a0d896fb3e 100644 --- a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc @@ -95,7 +95,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch break; case OB_EMPTY: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: case OB_SPEAKER: diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc index e331030164e..0d3c2feb8f3 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.cc +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -26,13 +26,13 @@ namespace blender::io::usd { void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTime */) { - curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVES_LEGACY); curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; - object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_ = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, name_.c_str()); object_->data = curve_; } diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc index c7fd788e072..d0a5dc1c4b4 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.cc +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -43,13 +43,13 @@ namespace blender::io::usd { void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime */) { - curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVES_LEGACY); curve_->flag |= CU_3D; curve_->actvert = CU_ACT_NONE; curve_->resolu = 2; - object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_ = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, name_.c_str()); object_->data = curve_; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 9ea75e6eb4c..2cef5192337 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -93,7 +93,7 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par case OB_MESH: r_exportable_meshes.append(std::make_unique(depsgraph, export_params, object)); break; - case OB_CURVE: { + case OB_CURVES_LEGACY: { Curve *curve = static_cast(object->data); Nurb *nurb{static_cast(curve->nurb.first)}; if (!nurb) { diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh index 9f9ec5a6083..676b1f3598c 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -64,11 +64,12 @@ void export_frame(Depsgraph *depsgraph, * Find the objects to be exported in the `view_layer` of the dependency graph`depsgraph`, * and return them in vectors `unique_ptr`s of `OBJMesh` and `OBJCurve`. * If `export_params.export_selected_objects` is set, then only selected objects are to be - * exported, else all objects are to be exported. But only objects of type `OB_MESH`, `OB_CURVE`, - * and `OB_SURF` are supported; the rest will be ignored. If `export_params.export_curves_as_nurbs` - * is set, then curves of type `CU_NURBS` are exported in curve form in the .obj file, otherwise - * they are converted to mesh and returned in the `OBJMesh` vector. All other exportable types are - * always converted to mesh and returned in the `OBJMesh` vector. + * exported, else all objects are to be exported. But only objects of type `OB_MESH`, + * `OB_CURVES_LEGACY`, and `OB_SURF` are supported; the rest will be ignored. If + * `export_params.export_curves_as_nurbs` is set, then curves of type `CU_NURBS` are exported in + * curve form in the .obj file, otherwise they are converted to mesh and returned in the `OBJMesh` + * vector. All other exportable types are always converted to mesh and returned in the `OBJMesh` + * vector. */ std::pair>, Vector>> filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params); diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index cf11e303234..011f3618e15 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -879,7 +879,7 @@ typedef enum IDRecalcFlag { #define FILTER_ID_AR (1ULL << 1) #define FILTER_ID_BR (1ULL << 2) #define FILTER_ID_CA (1ULL << 3) -#define FILTER_ID_CU (1ULL << 4) +#define FILTER_ID_CU_LEGACY (1ULL << 4) #define FILTER_ID_GD (1ULL << 5) #define FILTER_ID_GR (1ULL << 6) #define FILTER_ID_IM (1ULL << 7) @@ -912,12 +912,12 @@ typedef enum IDRecalcFlag { #define FILTER_ID_SIM (1ULL << 35) #define FILTER_ID_ALL \ - (FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU | FILTER_ID_GD | \ - FILTER_ID_GR | FILTER_ID_IM | FILTER_ID_LA | FILTER_ID_LS | FILTER_ID_LT | FILTER_ID_MA | \ - FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | FILTER_ID_OB | \ - FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | FILTER_ID_SO | \ - FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF | FILTER_ID_WS | \ - FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | FILTER_ID_SIM) + (FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU_LEGACY | \ + FILTER_ID_GD | FILTER_ID_GR | FILTER_ID_IM | FILTER_ID_LA | FILTER_ID_LS | FILTER_ID_LT | \ + FILTER_ID_MA | FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | \ + FILTER_ID_OB | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | \ + FILTER_ID_SO | FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF | \ + FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | FILTER_ID_SIM) /** * This enum defines the index assigned to each type of IDs in the array returned by @@ -998,7 +998,7 @@ enum { /* Object data types. */ INDEX_ID_AR, INDEX_ID_ME, - INDEX_ID_CU, + INDEX_ID_CU_LEGACY, INDEX_ID_MB, INDEX_ID_CV, INDEX_ID_PT, diff --git a/source/blender/makesdna/DNA_ID_enums.h b/source/blender/makesdna/DNA_ID_enums.h index a6f950517eb..b0ca13615b8 100644 --- a/source/blender/makesdna/DNA_ID_enums.h +++ b/source/blender/makesdna/DNA_ID_enums.h @@ -40,46 +40,46 @@ enum eIconSizes { * and the first 2 bytes of #ID.name (for runtime checks, see #GS macro). */ typedef enum ID_Type { - ID_SCE = MAKE_ID2('S', 'C'), /* Scene */ - ID_LI = MAKE_ID2('L', 'I'), /* Library */ - ID_OB = MAKE_ID2('O', 'B'), /* Object */ - ID_ME = MAKE_ID2('M', 'E'), /* Mesh */ - ID_CU = MAKE_ID2('C', 'U'), /* Curve */ - ID_MB = MAKE_ID2('M', 'B'), /* MetaBall */ - ID_MA = MAKE_ID2('M', 'A'), /* Material */ - ID_TE = MAKE_ID2('T', 'E'), /* Tex (Texture) */ - ID_IM = MAKE_ID2('I', 'M'), /* Image */ - ID_LT = MAKE_ID2('L', 'T'), /* Lattice */ - ID_LA = MAKE_ID2('L', 'A'), /* Light */ - ID_CA = MAKE_ID2('C', 'A'), /* Camera */ - ID_IP = MAKE_ID2('I', 'P'), /* Ipo (depreciated, replaced by FCurves) */ - ID_KE = MAKE_ID2('K', 'E'), /* Key (shape key) */ - ID_WO = MAKE_ID2('W', 'O'), /* World */ - ID_SCR = MAKE_ID2('S', 'R'), /* Screen */ - ID_VF = MAKE_ID2('V', 'F'), /* VFont (Vector Font) */ - ID_TXT = MAKE_ID2('T', 'X'), /* Text */ - ID_SPK = MAKE_ID2('S', 'K'), /* Speaker */ - ID_SO = MAKE_ID2('S', 'O'), /* Sound */ - ID_GR = MAKE_ID2('G', 'R'), /* Collection */ - ID_AR = MAKE_ID2('A', 'R'), /* bArmature */ - ID_AC = MAKE_ID2('A', 'C'), /* bAction */ - ID_NT = MAKE_ID2('N', 'T'), /* bNodeTree */ - ID_BR = MAKE_ID2('B', 'R'), /* Brush */ - ID_PA = MAKE_ID2('P', 'A'), /* ParticleSettings */ - ID_GD = MAKE_ID2('G', 'D'), /* bGPdata, (Grease Pencil) */ - ID_WM = MAKE_ID2('W', 'M'), /* WindowManager */ - ID_MC = MAKE_ID2('M', 'C'), /* MovieClip */ - ID_MSK = MAKE_ID2('M', 'S'), /* Mask */ - ID_LS = MAKE_ID2('L', 'S'), /* FreestyleLineStyle */ - ID_PAL = MAKE_ID2('P', 'L'), /* Palette */ - ID_PC = MAKE_ID2('P', 'C'), /* PaintCurve */ - ID_CF = MAKE_ID2('C', 'F'), /* CacheFile */ - ID_WS = MAKE_ID2('W', 'S'), /* WorkSpace */ - ID_LP = MAKE_ID2('L', 'P'), /* LightProbe */ - ID_CV = MAKE_ID2('C', 'V'), /* Curves */ - ID_PT = MAKE_ID2('P', 'T'), /* PointCloud */ - ID_VO = MAKE_ID2('V', 'O'), /* Volume */ - ID_SIM = MAKE_ID2('S', 'I'), /* Simulation (geometry node groups) */ + ID_SCE = MAKE_ID2('S', 'C'), /* Scene */ + ID_LI = MAKE_ID2('L', 'I'), /* Library */ + ID_OB = MAKE_ID2('O', 'B'), /* Object */ + ID_ME = MAKE_ID2('M', 'E'), /* Mesh */ + ID_CU_LEGACY = MAKE_ID2('C', 'U'), /* Curve. ID_CV should be used in the future (see T95355). */ + ID_MB = MAKE_ID2('M', 'B'), /* MetaBall */ + ID_MA = MAKE_ID2('M', 'A'), /* Material */ + ID_TE = MAKE_ID2('T', 'E'), /* Tex (Texture) */ + ID_IM = MAKE_ID2('I', 'M'), /* Image */ + ID_LT = MAKE_ID2('L', 'T'), /* Lattice */ + ID_LA = MAKE_ID2('L', 'A'), /* Light */ + ID_CA = MAKE_ID2('C', 'A'), /* Camera */ + ID_IP = MAKE_ID2('I', 'P'), /* Ipo (depreciated, replaced by FCurves) */ + ID_KE = MAKE_ID2('K', 'E'), /* Key (shape key) */ + ID_WO = MAKE_ID2('W', 'O'), /* World */ + ID_SCR = MAKE_ID2('S', 'R'), /* Screen */ + ID_VF = MAKE_ID2('V', 'F'), /* VFont (Vector Font) */ + ID_TXT = MAKE_ID2('T', 'X'), /* Text */ + ID_SPK = MAKE_ID2('S', 'K'), /* Speaker */ + ID_SO = MAKE_ID2('S', 'O'), /* Sound */ + ID_GR = MAKE_ID2('G', 'R'), /* Collection */ + ID_AR = MAKE_ID2('A', 'R'), /* bArmature */ + ID_AC = MAKE_ID2('A', 'C'), /* bAction */ + ID_NT = MAKE_ID2('N', 'T'), /* bNodeTree */ + ID_BR = MAKE_ID2('B', 'R'), /* Brush */ + ID_PA = MAKE_ID2('P', 'A'), /* ParticleSettings */ + ID_GD = MAKE_ID2('G', 'D'), /* bGPdata, (Grease Pencil) */ + ID_WM = MAKE_ID2('W', 'M'), /* WindowManager */ + ID_MC = MAKE_ID2('M', 'C'), /* MovieClip */ + ID_MSK = MAKE_ID2('M', 'S'), /* Mask */ + ID_LS = MAKE_ID2('L', 'S'), /* FreestyleLineStyle */ + ID_PAL = MAKE_ID2('P', 'L'), /* Palette */ + ID_PC = MAKE_ID2('P', 'C'), /* PaintCurve */ + ID_CF = MAKE_ID2('C', 'F'), /* CacheFile */ + ID_WS = MAKE_ID2('W', 'S'), /* WorkSpace */ + ID_LP = MAKE_ID2('L', 'P'), /* LightProbe */ + ID_CV = MAKE_ID2('C', 'V'), /* Curves */ + ID_PT = MAKE_ID2('P', 'T'), /* PointCloud */ + ID_VO = MAKE_ID2('V', 'O'), /* Volume */ + ID_SIM = MAKE_ID2('S', 'I'), /* Simulation (geometry node groups) */ } ID_Type; /* Only used as 'placeholder' in .blend files for directly linked data-blocks. */ diff --git a/source/blender/makesdna/DNA_ipo_types.h b/source/blender/makesdna/DNA_ipo_types.h index f40fb55e72e..70ee7c99d01 100644 --- a/source/blender/makesdna/DNA_ipo_types.h +++ b/source/blender/makesdna/DNA_ipo_types.h @@ -272,7 +272,7 @@ typedef struct Ipo { #define SEQ_FAC_SPEED 2 #define SEQ_FAC_OPACITY 3 -/* ********* Curve (ID_CU) *********** */ +/* ********* Curve (ID_CU_LEGACY) *********** */ #define CU_TOTIPO 1 #define CU_TOTNAM 1 diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 8c235f403f0..9e0bf7dcc5a 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -471,7 +471,8 @@ typedef struct ObHook { enum { OB_EMPTY = 0, OB_MESH = 1, - OB_CURVE = 2, + /** Curve object is still used but replaced by "Curves" for the future (see T95355). */ + OB_CURVES_LEGACY = 2, OB_SURF = 3, OB_FONT = 4, OB_MBALL = 5, @@ -515,17 +516,26 @@ enum { OB_VOLUME)) #define OB_TYPE_SUPPORT_VGROUP(_type) (ELEM(_type, OB_MESH, OB_LATTICE, OB_GPENCIL)) #define OB_TYPE_SUPPORT_EDITMODE(_type) \ - (ELEM(_type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_LATTICE, OB_ARMATURE, OB_CURVES)) -#define OB_TYPE_SUPPORT_PARVERT(_type) (ELEM(_type, OB_MESH, OB_SURF, OB_CURVE, OB_LATTICE)) + (ELEM(_type, \ + OB_MESH, \ + OB_FONT, \ + OB_CURVES_LEGACY, \ + OB_SURF, \ + OB_MBALL, \ + OB_LATTICE, \ + OB_ARMATURE, \ + OB_CURVES)) +#define OB_TYPE_SUPPORT_PARVERT(_type) \ + (ELEM(_type, OB_MESH, OB_SURF, OB_CURVES_LEGACY, OB_LATTICE)) /** Matches #OB_TYPE_SUPPORT_EDITMODE. */ -#define OB_DATA_SUPPORT_EDITMODE(_type) (ELEM(_type, ID_ME, ID_CU, ID_MB, ID_LT, ID_AR)) +#define OB_DATA_SUPPORT_EDITMODE(_type) (ELEM(_type, ID_ME, ID_CU_LEGACY, ID_MB, ID_LT, ID_AR)) /* is this ID type used as object data */ #define OB_DATA_SUPPORT_ID(_id_type) \ (ELEM(_id_type, \ ID_ME, \ - ID_CU, \ + ID_CU_LEGACY, \ ID_MB, \ ID_LA, \ ID_SPK, \ @@ -540,7 +550,7 @@ enum { #define OB_DATA_SUPPORT_ID_CASE \ ID_ME: \ - case ID_CU: \ + case ID_CU_LEGACY: \ case ID_MB: \ case ID_LA: \ case ID_SPK: \ diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 90e979e9fbe..78960803182 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -35,7 +35,7 @@ const EnumPropertyItem rna_enum_id_type_items[] = { {ID_BR, "BRUSH", ICON_BRUSH_DATA, "Brush", ""}, {ID_CA, "CAMERA", ICON_CAMERA_DATA, "Camera", ""}, {ID_CF, "CACHEFILE", ICON_FILE, "Cache File", ""}, - {ID_CU, "CURVE", ICON_CURVE_DATA, "Curve", ""}, + {ID_CU_LEGACY, "CURVE", ICON_CURVE_DATA, "Curve", ""}, {ID_VF, "FONT", ICON_FONT_DATA, "Font", ""}, {ID_GD, "GREASEPENCIL", ICON_GREASEPENCIL, "Grease Pencil", ""}, {ID_GR, "COLLECTION", ICON_OUTLINER_COLLECTION, "Collection", ""}, @@ -126,7 +126,7 @@ const struct IDFilterEnumPropertyItem rna_enum_id_type_filter_items[] = { {FILTER_ID_BR, "filter_brush", ICON_BRUSH_DATA, "Brushes", "Show Brushes data-blocks"}, {FILTER_ID_CA, "filter_camera", ICON_CAMERA_DATA, "Cameras", "Show Camera data-blocks"}, {FILTER_ID_CF, "filter_cachefile", ICON_FILE, "Cache Files", "Show Cache File data-blocks"}, - {FILTER_ID_CU, "filter_curve", ICON_CURVE_DATA, "Curves", "Show Curve data-blocks"}, + {FILTER_ID_CU_LEGACY, "filter_curve", ICON_CURVE_DATA, "Curves", "Show Curve data-blocks"}, {FILTER_ID_GD, "filter_grease_pencil", ICON_GREASEPENCIL, @@ -348,7 +348,7 @@ short RNA_type_to_ID_code(const StructRNA *type) return ID_CA; } if (base_type == &RNA_Curve) { - return ID_CU; + return ID_CU_LEGACY; } if (base_type == &RNA_GreasePencil) { return ID_GD; @@ -472,7 +472,7 @@ StructRNA *ID_code_to_RNA_type(short idcode) return &RNA_Camera; case ID_CF: return &RNA_CacheFile; - case ID_CU: + case ID_CU_LEGACY: return &RNA_Curve; case ID_GD: return &RNA_GreasePencil; diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c index b37a6e5fd0e..891de95a2a2 100644 --- a/source/blender/makesrna/intern/rna_curve.c +++ b/source/blender/makesrna/intern/rna_curve.c @@ -441,7 +441,7 @@ static void rna_Curve_bevelObject_set(PointerRNA *ptr, if (ob) { /* If bevel object has got the save curve, as object, for which it's set as bevobj, * there could be infinity loop in #DispList calculation. */ - if (ob->type == OB_CURVE && ob->data != cu) { + if (ob->type == OB_CURVES_LEGACY && ob->data != cu) { cu->bevobj = ob; id_lib_extern((ID *)ob); } @@ -486,7 +486,7 @@ static bool rna_Curve_otherObject_poll(PointerRNA *ptr, PointerRNA value) Object *ob = (Object *)value.data; if (ob) { - if (ob->type == OB_CURVE && ob->data != cu) { + if (ob->type == OB_CURVES_LEGACY && ob->data != cu) { return 1; } } @@ -516,7 +516,7 @@ static void rna_Curve_taperObject_set(PointerRNA *ptr, if (ob) { /* If taper object has got the save curve, as object, for which it's set as bevobj, * there could be infinity loop in #DispList calculation. */ - if (ob->type == OB_CURVE && ob->data != cu) { + if (ob->type == OB_CURVES_LEGACY && ob->data != cu) { cu->taperobj = ob; id_lib_extern((ID *)ob); } diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 6a0e5156234..9c755bbb053 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -2482,7 +2482,8 @@ static void rna_def_modifier_gpencilhook(BlenderRNA *brna) prop = RNA_def_property(srna, "falloff_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, modifier_gphook_falloff_items); /* share the enum */ RNA_def_property_ui_text(prop, "Falloff Type", ""); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "falloff_radius", PROP_FLOAT, PROP_DISTANCE); diff --git a/source/blender/makesrna/intern/rna_key.c b/source/blender/makesrna/intern/rna_key.c index 51dcfd13657..7687dcbb11f 100644 --- a/source/blender/makesrna/intern/rna_key.c +++ b/source/blender/makesrna/intern/rna_key.c @@ -47,7 +47,7 @@ static Key *rna_ShapeKey_find_key(ID *id) { switch (GS(id->name)) { - case ID_CU: + case ID_CU_LEGACY: return ((Curve *)id)->key; case ID_KE: return (Key *)id; @@ -556,7 +556,7 @@ static void rna_ShapeKey_data_begin(CollectionPropertyIterator *iter, PointerRNA KeyBlock *kb = (KeyBlock *)ptr->data; int tot = kb->totelem, size = key->elemsize; - if (GS(key->from->name) == ID_CU && tot > 0) { + if (GS(key->from->name) == ID_CU_LEGACY && tot > 0) { Curve *cu = (Curve *)key->from; StructRNA *type = NULL; NurbInfo info = {0}; @@ -593,7 +593,7 @@ static int rna_ShapeKey_data_length(PointerRNA *ptr) KeyBlock *kb = (KeyBlock *)ptr->data; int tot = kb->totelem; - if (GS(key->from->name) == ID_CU) { + if (GS(key->from->name) == ID_CU_LEGACY) { tot = rna_ShapeKey_curve_find_index(key, tot); } @@ -613,7 +613,7 @@ static PointerRNA rna_ShapeKey_data_get(CollectionPropertyIterator *iter) return rna_pointer_inherit_refine(&iter->parent, point->type, point->data); } - if (GS(key->from->name) == ID_CU) { + if (GS(key->from->name) == ID_CU_LEGACY) { Curve *cu = (Curve *)key->from; type = rna_ShapeKey_curve_point_type(cu->nurb.first); @@ -635,7 +635,7 @@ int rna_ShapeKey_data_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr) return false; } - if (GS(key->from->name) == ID_CU) { + if (GS(key->from->name) == ID_CU_LEGACY) { NurbInfo info; rna_ShapeKey_NurbInfo_find_index(key, index, false, &info); diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index e9d39d708a9..b239260c8eb 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -322,7 +322,7 @@ static Mesh *rna_Main_meshes_new_from_object(Main *bmain, { switch (object->type) { case OB_FONT: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_MBALL: case OB_MESH: @@ -822,7 +822,7 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(screens, screens, ID_SCR) RNA_MAIN_ID_TAG_FUNCS_DEF(window_managers, wm, ID_WM) RNA_MAIN_ID_TAG_FUNCS_DEF(images, images, ID_IM) RNA_MAIN_ID_TAG_FUNCS_DEF(lattices, lattices, ID_LT) -RNA_MAIN_ID_TAG_FUNCS_DEF(curves, curves, ID_CU) +RNA_MAIN_ID_TAG_FUNCS_DEF(curves, curves, ID_CU_LEGACY) RNA_MAIN_ID_TAG_FUNCS_DEF(metaballs, metaballs, ID_MB) RNA_MAIN_ID_TAG_FUNCS_DEF(fonts, fonts, ID_VF) RNA_MAIN_ID_TAG_FUNCS_DEF(textures, textures, ID_TE) diff --git a/source/blender/makesrna/intern/rna_mask.c b/source/blender/makesrna/intern/rna_mask.c index e8b8c6e3274..2247a16a7a0 100644 --- a/source/blender/makesrna/intern/rna_mask.c +++ b/source/blender/makesrna/intern/rna_mask.c @@ -1025,7 +1025,8 @@ static void rna_def_mask_layer(BlenderRNA *brna) RNA_def_property_enum_sdna(prop, NULL, "falloff"); RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items); RNA_def_property_ui_text(prop, "Falloff", "Falloff type the feather"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, NC_MASK | NA_EDITED, NULL); /* filling options */ diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 45d22981205..426f37dfb66 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -869,10 +869,10 @@ static void modifier_object_set(Object *self, Object **ob_p, int type, PointerRN RNA_MOD_OBJECT_SET(Armature, object, OB_ARMATURE); RNA_MOD_OBJECT_SET(Array, start_cap, OB_MESH); RNA_MOD_OBJECT_SET(Array, end_cap, OB_MESH); -RNA_MOD_OBJECT_SET(Array, curve_ob, OB_CURVE); +RNA_MOD_OBJECT_SET(Array, curve_ob, OB_CURVES_LEGACY); RNA_MOD_OBJECT_SET(Boolean, object, OB_MESH); RNA_MOD_OBJECT_SET(Cast, object, OB_EMPTY); -RNA_MOD_OBJECT_SET(Curve, object, OB_CURVE); +RNA_MOD_OBJECT_SET(Curve, object, OB_CURVES_LEGACY); RNA_MOD_OBJECT_SET(DataTransfer, ob_source, OB_MESH); RNA_MOD_OBJECT_SET(Lattice, object, OB_LATTICE); RNA_MOD_OBJECT_SET(Mask, ob_arm, OB_ARMATURE); @@ -1849,7 +1849,8 @@ static void rna_def_modifier_warp(BlenderRNA *brna) prop = RNA_def_property(srna, "falloff_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, modifier_warp_falloff_items); RNA_def_property_ui_text(prop, "Falloff Type", ""); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "falloff_radius", PROP_FLOAT, PROP_DISTANCE); @@ -2567,7 +2568,8 @@ static void rna_def_modifier_hook(BlenderRNA *brna) prop = RNA_def_property(srna, "falloff_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, modifier_warp_falloff_items); /* share the enum */ RNA_def_property_ui_text(prop, "Falloff Type", ""); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "falloff_radius", PROP_FLOAT, PROP_DISTANCE); @@ -5076,7 +5078,8 @@ static void rna_def_modifier_weightvgedit(BlenderRNA *brna) prop = RNA_def_property(srna, "falloff_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, weightvg_edit_falloff_type_items); RNA_def_property_ui_text(prop, "Falloff Type", "How weights are mapped to their new values"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "invert_falloff", PROP_BOOLEAN, PROP_NONE); @@ -5364,7 +5367,8 @@ static void rna_def_modifier_weightvgproximity(BlenderRNA *brna) prop = RNA_def_property(srna, "falloff_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, weightvg_proximity_falloff_type_items); RNA_def_property_ui_text(prop, "Falloff Type", "How weights are mapped to their new values"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "invert_falloff", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 3ae9ab3cec2..85bf98914eb 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -7196,7 +7196,8 @@ static void def_cmp_dilate_erode(StructRNA *srna) RNA_def_property_enum_sdna(prop, NULL, "falloff"); RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items); RNA_def_property_ui_text(prop, "Falloff", "Falloff type the feather"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } @@ -8974,7 +8975,8 @@ static void def_cmp_keying(StructRNA *srna) RNA_def_property_enum_sdna(prop, NULL, "feather_falloff"); RNA_def_property_enum_items(prop, rna_enum_proportional_falloff_curve_only_items); RNA_def_property_ui_text(prop, "Feather Falloff", "Falloff type the feather"); - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); /* Abusing id_curve :/ */ + RNA_def_property_translation_context(prop, + BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "feather_distance", PROP_INT, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 9c4ebf79a08..c598e63a32a 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -241,7 +241,7 @@ const EnumPropertyItem rna_enum_lightprobes_type_items[] = { /* used for 2 enums */ #define OBTYPE_CU_CURVE \ { \ - OB_CURVE, "CURVE", ICON_OUTLINER_OB_CURVE, "Curve", "" \ + OB_CURVES_LEGACY, "CURVE", ICON_OUTLINER_OB_CURVE, "Curve", "" \ } #define OBTYPE_CU_SURF \ { \ @@ -479,7 +479,7 @@ static void rna_Object_active_shape_update(Main *bmain, Scene *UNUSED(scene), Po BKE_editmesh_looptri_and_normals_calc(em); break; } - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: ED_curve_editnurb_load(bmain, ob); ED_curve_editnurb_make(ob); @@ -571,7 +571,7 @@ static void rna_Object_data_set(PointerRNA *ptr, PointerRNA value, struct Report ob->data = id; BKE_object_materials_test(G_MAIN, ob, id); - if (GS(id->name) == ID_CU) { + if (GS(id->name) == ID_CU_LEGACY) { BKE_curve_type_test(ob); } else if (ob->type == OB_ARMATURE) { @@ -590,7 +590,7 @@ static StructRNA *rna_Object_data_typef(PointerRNA *ptr) return &RNA_Image; case OB_MESH: return &RNA_Mesh; - case OB_CURVE: + case OB_CURVES_LEGACY: return &RNA_Curve; case OB_SURF: return &RNA_Curve; @@ -2182,7 +2182,7 @@ bool rna_Lattice_object_poll(PointerRNA *UNUSED(ptr), PointerRNA value) bool rna_Curve_object_poll(PointerRNA *UNUSED(ptr), PointerRNA value) { - return ((Object *)value.owner_id)->type == OB_CURVE; + return ((Object *)value.owner_id)->type == OB_CURVES_LEGACY; } bool rna_Armature_object_poll(PointerRNA *UNUSED(ptr), PointerRNA value) diff --git a/source/blender/makesrna/intern/rna_object_api.c b/source/blender/makesrna/intern/rna_object_api.c index 9676be69d05..ece1c5e5815 100644 --- a/source/blender/makesrna/intern/rna_object_api.c +++ b/source/blender/makesrna/intern/rna_object_api.c @@ -407,7 +407,7 @@ static Mesh *rna_Object_to_mesh(Object *object, * rna_Main_meshes_new_from_object. */ switch (object->type) { case OB_FONT: - case OB_CURVE: + case OB_CURVES_LEGACY: case OB_SURF: case OB_MBALL: case OB_MESH: @@ -430,7 +430,7 @@ static Curve *rna_Object_to_curve(Object *object, Depsgraph *depsgraph, bool apply_modifiers) { - if (!ELEM(object->type, OB_FONT, OB_CURVE)) { + if (!ELEM(object->type, OB_FONT, OB_CURVES_LEGACY)) { BKE_report(reports, RPT_ERROR, "Object is not a curve or a text"); return NULL; } @@ -785,7 +785,7 @@ bool rna_Object_generate_gpencil_strokes(Object *ob, float scale_thickness, float sample) { - if (ob->type != OB_CURVE) { + if (ob->type != OB_CURVES_LEGACY) { BKE_reportf(reports, RPT_ERROR, "Object '%s' is not valid for this operation! Only curves are supported", diff --git a/source/blender/makesrna/intern/rna_object_force.c b/source/blender/makesrna/intern/rna_object_force.c index 9ca78033199..8579f188428 100644 --- a/source/blender/makesrna/intern/rna_object_force.c +++ b/source/blender/makesrna/intern/rna_object_force.c @@ -691,7 +691,7 @@ static void rna_FieldSettings_dependency_update(Main *bmain, Scene *scene, Point rna_FieldSettings_shape_update(bmain, scene, ptr); - if (ob->type == OB_CURVE && ob->pd->forcefield == PFIELD_GUIDE) { + if (ob->type == OB_CURVES_LEGACY && ob->pd->forcefield == PFIELD_GUIDE) { DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); } else { @@ -899,7 +899,7 @@ static const EnumPropertyItem *rna_Effector_shape_itemf(bContext *UNUSED(C), ob = (Object *)ptr->owner_id; - if (ob->type == OB_CURVE) { + if (ob->type == OB_CURVES_LEGACY) { if (ob->pd->forcefield == PFIELD_VORTEX) { return curve_vortex_shape_items; } diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 178029ef340..ef767ddcc95 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -1659,7 +1659,7 @@ static void rna_Scene_mesh_quality_update(Main *bmain, Scene *UNUSED(scene), Poi Scene *scene = (Scene *)ptr->owner_id; FOREACH_SCENE_OBJECT_BEGIN (scene, ob) { - if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_VOLUME, OB_MBALL)) { + if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_VOLUME, OB_MBALL)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } } @@ -3072,7 +3072,7 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Proportional Editing Falloff", "Falloff type for proportional editing mode"); /* Abusing id_curve :/ */ - RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVE_LEGACY); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ prop = RNA_def_property(srna, "proportional_size", PROP_FLOAT, PROP_DISTANCE); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index c51f0c00498..88378542a9f 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -3263,8 +3263,8 @@ static struct IDFilterEnumPropertyItem rna_enum_space_file_id_filter_categories[ ICON_OUTLINER_COLLECTION, "Objects & Collections", "Show objects and collections"}, - {FILTER_ID_AR | FILTER_ID_CU | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME | FILTER_ID_CV | - FILTER_ID_PT | FILTER_ID_VO, + {FILTER_ID_AR | FILTER_ID_CU_LEGACY | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME | + FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO, "category_geometry", ICON_NODETREE, "Geometry", @@ -4959,7 +4959,9 @@ static void rna_def_space_view3d(BlenderRNA *brna) const char *identifier[2]; } info[] = { {"Mesh", (1 << OB_MESH), {"show_object_viewport_mesh", "show_object_select_mesh"}}, - {"Curve", (1 << OB_CURVE), {"show_object_viewport_curve", "show_object_select_curve"}}, + {"Curve", + (1 << OB_CURVES_LEGACY), + {"show_object_viewport_curve", "show_object_select_curve"}}, {"Surface", (1 << OB_SURF), {"show_object_viewport_surf", "show_object_select_surf"}}, {"Meta", (1 << OB_MBALL), {"show_object_viewport_meta", "show_object_select_meta"}}, {"Font", (1 << OB_FONT), {"show_object_viewport_font", "show_object_select_font"}}, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 2cc3bb8c916..7d119b4b112 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -819,7 +819,7 @@ static bool isDisabled(const struct Scene *UNUSED(scene), * In other cases it should be impossible to have a type mismatch. */ - if (amd->curve_ob && amd->curve_ob->type != OB_CURVE) { + if (amd->curve_ob && amd->curve_ob->type != OB_CURVES_LEGACY) { return true; } if (amd->start_cap && amd->start_cap->type != OB_MESH) { diff --git a/source/blender/modifiers/intern/MOD_curve.c b/source/blender/modifiers/intern/MOD_curve.c index ca5d59433f0..d1c7dd43f15 100644 --- a/source/blender/modifiers/intern/MOD_curve.c +++ b/source/blender/modifiers/intern/MOD_curve.c @@ -71,7 +71,7 @@ static bool isDisabled(const Scene *UNUSED(scene), ModifierData *md, bool UNUSED * * In other cases it should be impossible to have a type mismatch. */ - return !cmd->object || cmd->object->type != OB_CURVE; + return !cmd->object || cmd->object->type != OB_CURVES_LEGACY; } static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) diff --git a/source/blender/modifiers/intern/MOD_ui_common.c b/source/blender/modifiers/intern/MOD_ui_common.c index 94482742831..90652c180c6 100644 --- a/source/blender/modifiers/intern/MOD_ui_common.c +++ b/source/blender/modifiers/intern/MOD_ui_common.c @@ -324,7 +324,7 @@ static void modifier_panel_header(const bContext *C, Panel *panel) buttons_number++; } } /* Tessellation point for curve-typed objects. */ - else if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { + else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { /* Some modifiers can work with pre-tessellated curves only. */ if (ELEM(md->type, eModifierType_Hook, eModifierType_Softbody, eModifierType_MeshDeform)) { /* Add button (appearing to be ON) and add tip why this can't be changed. */ diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index 41b600e386b..a8c52108cc0 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -204,7 +204,7 @@ Mesh *MOD_deform_mesh_eval_get(Object *ob, BKE_mesh_orco_ensure(ob, mesh); } } - else if (ELEM(ob->type, OB_FONT, OB_CURVE, OB_SURF)) { + else if (ELEM(ob->type, OB_FONT, OB_CURVES_LEGACY, OB_SURF)) { /* TODO(sybren): get evaluated mesh from depsgraph once * that's properly generated for curves. */ mesh = BKE_mesh_new_nomain_from_curve(ob); -- cgit v1.2.3 From 82c3bef7655bb115f739842b815a2ee1c40a9320 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 10:34:00 -0600 Subject: Fix T94495: Split edges node leads to a crash in edit mode If original indices exist on the input mesh, also copy them to the BMesh used for the edge split operation so they aren't lost. Part of D14018 --- source/blender/nodes/geometry/nodes/node_geo_edge_split.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 9376789cf2c..17503c3ea96 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -39,6 +39,9 @@ static Mesh *mesh_edge_split(const Mesh &mesh, const IndexMask selection) BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params); BMeshFromMeshParams bmesh_from_mesh_params{}; + bmesh_from_mesh_params.cd_mask_extra.vmask = CD_MASK_ORIGINDEX; + bmesh_from_mesh_params.cd_mask_extra.emask = CD_MASK_ORIGINDEX; + bmesh_from_mesh_params.cd_mask_extra.pmask = CD_MASK_ORIGINDEX; BM_mesh_bm_from_me(bm, &mesh, &bmesh_from_mesh_params); BM_mesh_elem_table_ensure(bm, BM_EDGE); -- cgit v1.2.3 From 1850a0b2ab1230d79cc56a74e877a9b371ae3773 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 10:51:00 -0600 Subject: Mesh: Avoid creating incorrect original index layers Currently, whenever any BMesh is converted to a Mesh (except for edit mode switching), original index (`CD_ORIGINDEX`) layers are added. This is incorrect, because many operations just convert some Mesh into a BMesh and then back, but they shouldn't make any assumption about where their input mesh came from. It might even come from a primitive in geometry nodes, where there are no original indices at all. Conceptually, mesh original indices should be filled by the modifier stack when first creating the evaluated mesh. So that's where they're moved in this patch. A separate function now fills the indices with their default (0,1,2,3...) values. The way the mesh wrapper system defers the BMesh to Mesh conversion makes this a bit less obvious though. The old behavior is incorrect, but it's also slower, because three arrays the size of the mesh's vertices, edges, and faces had to be allocated and filled during the BMesh to Mesh conversion, which just ends up putting more pressure on the cache. In the many cases where original indices aren't used, I measured an **8% speedup** for the conversion (from 76.5ms to 70.7ms). Generally there is an assumption that BMesh is "original" and Mesh is "evaluated". After this patch, that assumption isn't quite as strong, but it still exists for two reasons. First, original indices are added whenever converting a BMesh "wrapper" to a Mesh. Second, original indices are not added to the BMesh at the beginning of evaluation, which assumes that every BMesh in the viewport is original and doesn't need the mapping. Differential Revision: https://developer.blender.org/D14018 --- source/blender/blenkernel/BKE_mesh.h | 7 +++++++ source/blender/blenkernel/intern/DerivedMesh.cc | 8 ++++++++ source/blender/blenkernel/intern/mesh.cc | 17 +++++++++++++++++ source/blender/blenkernel/intern/mesh_wrapper.c | 10 ++++++++++ source/blender/bmesh/intern/bmesh_mesh_convert.cc | 23 ----------------------- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index a84f6058d67..2335ff7ad28 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -69,6 +69,13 @@ struct Mesh *BKE_mesh_from_bmesh_for_eval_nomain(struct BMesh *bm, const struct CustomData_MeshMasks *cd_mask_extra, const struct Mesh *me_settings); +/** + * Add original index (#CD_ORIGINDEX) layers if they don't already exist. This is meant to be used + * when creating an evaluated mesh from an original edit mode mesh, to allow mapping from the + * evaluated vertices to the originals. + */ +void BKE_mesh_ensure_default_orig_index_customdata(struct Mesh *mesh); + /** * Find the index of the loop in 'poly' which references vertex, * returns -1 if not found diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 542be4027bc..633873adf33 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -546,6 +546,7 @@ static Mesh *create_orco_mesh(Object *ob, Mesh *me, BMEditMesh *em, int layer) if (em) { mesh = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, nullptr, me); + BKE_mesh_ensure_default_orig_index_customdata(mesh); } else { mesh = BKE_mesh_copy_for_eval(me, true); @@ -1364,6 +1365,12 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, em_input, &final_datamask, nullptr, mesh_input); } + /* The mesh from edit mode should not have any original index layers already, since those + * are added during evaluation when necessary and are redundant on an original mesh. */ + BLI_assert(CustomData_get_layer(&em_input->bm->pdata, CD_ORIGINDEX) == nullptr && + CustomData_get_layer(&em_input->bm->edata, CD_ORIGINDEX) == nullptr && + CustomData_get_layer(&em_input->bm->pdata, CD_ORIGINDEX) == nullptr); + /* Clear errors before evaluation. */ BKE_modifiers_clear_errors(ob); @@ -1402,6 +1409,7 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, else if (isPrevDeform && mti->dependsOnNormals && mti->dependsOnNormals(md)) { if (mesh_final == nullptr) { mesh_final = BKE_mesh_from_bmesh_for_eval_nomain(em_input->bm, nullptr, mesh_input); + BKE_mesh_ensure_default_orig_index_customdata(mesh_final); ASSERT_IS_VALID_MESH(mesh_final); } BLI_assert(deformed_verts != nullptr); diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 6ca5babbf13..9893a9d298d 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1204,6 +1204,23 @@ Mesh *BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, return mesh; } +static void ensure_orig_index_layer(CustomData &data, const int size) +{ + if (CustomData_has_layer(&data, CD_ORIGINDEX)) { + return; + } + int *indices = (int *)CustomData_add_layer(&data, CD_ORIGINDEX, CD_DEFAULT, nullptr, size); + range_vn_i(indices, size, 0); +} + +void BKE_mesh_ensure_default_orig_index_customdata(Mesh *mesh) +{ + BLI_assert(mesh->runtime.wrapper_type == ME_WRAPPER_TYPE_MDATA); + ensure_orig_index_layer(mesh->vdata, mesh->totvert); + ensure_orig_index_layer(mesh->edata, mesh->totedge); + ensure_orig_index_layer(mesh->pdata, mesh->totpoly); +} + BoundBox *BKE_mesh_boundbox_get(Object *ob) { /* This is Object-level data access, diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c index 267020fb675..f9fcaa0dceb 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.c +++ b/source/blender/blenkernel/intern/mesh_wrapper.c @@ -115,6 +115,16 @@ static void mesh_wrapper_ensure_mdata_isolated(void *userdata) BMEditMesh *em = me->edit_mesh; BM_mesh_bm_to_me_for_eval(em->bm, me, &me->runtime.cd_mask_extra); + /* Adding original index layers assumes that all BMesh mesh wrappers are created from + * original edit mode meshes (the only case where adding original indices makes sense). + * If that assumption is broken, the layers might be incorrect in that they might not + * actually be "original". + * + * There is also a performance aspect, where this also assumes that original indices are + * always needed when converting an edit mesh to a mesh. That might be wrong, but it's not + * harmful. */ + BKE_mesh_ensure_default_orig_index_customdata(me); + EditMeshData *edit_data = me->runtime.edit_data; if (edit_data->vertexCos) { BKE_mesh_vert_coords_apply(me, edit_data->vertexCos); diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index ae6b4da6003..40a82fdd96f 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -1028,10 +1028,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * me->totloop = bm->totloop; me->totpoly = bm->totface; - CustomData_add_layer(&me->vdata, CD_ORIGINDEX, CD_CALLOC, nullptr, bm->totvert); - CustomData_add_layer(&me->edata, CD_ORIGINDEX, CD_CALLOC, nullptr, bm->totedge); - CustomData_add_layer(&me->pdata, CD_ORIGINDEX, CD_CALLOC, nullptr, bm->totface); - CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, bm->totvert); CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, nullptr, bm->totedge); CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, nullptr, bm->totloop); @@ -1059,7 +1055,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * MEdge *medge = me->medge; MLoop *mloop = me->mloop; MPoly *mpoly = me->mpoly; - int *index, add_orig; unsigned int i, j; const int cd_vert_bweight_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); @@ -1070,11 +1065,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * me->runtime.deformed_only = true; - /* Don't add origindex layer if one already exists. */ - add_orig = !CustomData_has_layer(&bm->pdata, CD_ORIGINDEX); - - index = (int *)CustomData_get_layer(&me->vdata, CD_ORIGINDEX); - BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { MVert *mv = &mvert[i]; @@ -1088,15 +1078,10 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * mv->bweight = BM_ELEM_CD_GET_FLOAT_AS_UCHAR(eve, cd_vert_bweight_offset); } - if (add_orig) { - *index++ = i; - } - CustomData_from_bmesh_block(&bm->vdata, &me->vdata, eve->head.data, i); } bm->elem_index_dirty &= ~BM_VERT; - index = (int *)CustomData_get_layer(&me->edata, CD_ORIGINDEX); BM_ITER_MESH_INDEX (eed, &iter, bm, BM_EDGES_OF_MESH, i) { MEdge *med = &medge[i]; @@ -1123,13 +1108,9 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * } CustomData_from_bmesh_block(&bm->edata, &me->edata, eed->head.data, i); - if (add_orig) { - *index++ = i; - } } bm->elem_index_dirty &= ~BM_EDGE; - index = (int *)CustomData_get_layer(&me->pdata, CD_ORIGINDEX); j = 0; BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { BMLoop *l_iter; @@ -1156,10 +1137,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * } while ((l_iter = l_iter->next) != l_first); CustomData_from_bmesh_block(&bm->pdata, &me->pdata, efa->head.data, i); - - if (add_orig) { - *index++ = i; - } } bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP); -- cgit v1.2.3 From 16ab6111f758ef77bd0e53ce2379ee8281629776 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 18 Feb 2022 18:11:16 +0100 Subject: UI: Speedup preview icon loading from hard drive Significantly improves loading speed of preview images from disk, e.g. custom previews loaded using `bpy.utils.previews.ImagePreviewCollection.load()`. See D14144 for details & comparison videos. Differential Revision: https://developer.blender.org/D14144 Reviewed by: Bastien Montagne --- source/blender/editors/include/ED_render.h | 12 +- source/blender/editors/interface/interface_icons.c | 6 +- source/blender/editors/render/render_preview.cc | 367 ++++++++++++++++----- source/blender/windowmanager/WM_api.h | 2 + 4 files changed, 290 insertions(+), 97 deletions(-) diff --git a/source/blender/editors/include/ED_render.h b/source/blender/editors/include/ED_render.h index e1b6a935d6d..ee40cc75e1e 100644 --- a/source/blender/editors/include/ED_render.h +++ b/source/blender/editors/include/ED_render.h @@ -24,6 +24,7 @@ struct Scene; struct ScrArea; struct bContext; struct bScreen; +struct PreviewImage; struct wmWindow; struct wmWindowManager; @@ -87,16 +88,13 @@ void ED_preview_shader_job(const struct bContext *C, ePreviewRenderMethod method); void ED_preview_icon_render(const struct bContext *C, struct Scene *scene, + struct PreviewImage *prv_img, struct ID *id, - unsigned int *rect, - int sizex, - int sizey); + enum eIconSizes icon_size); void ED_preview_icon_job(const struct bContext *C, - void *owner, + struct PreviewImage *prv_img, struct ID *id, - unsigned int *rect, - int sizex, - int sizey, + enum eIconSizes icon_size, bool delay); void ED_preview_restart_queue_free(void); diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index 630f83cf828..e99f6978f4c 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -1399,19 +1399,17 @@ static void icon_set_image(const bContext *C, const bool delay = prv_img->rect[size] != NULL; icon_create_rect(prv_img, size); - prv_img->flag[size] |= PRV_RENDERING; if (use_job && (!id || BKE_previewimg_id_supports_jobs(id))) { /* Job (background) version */ - ED_preview_icon_job( - C, prv_img, id, prv_img->rect[size], prv_img->w[size], prv_img->h[size], delay); + ED_preview_icon_job(C, prv_img, id, size, delay); } else { if (!scene) { scene = CTX_data_scene(C); } /* Immediate version */ - ED_preview_icon_render(C, scene, id, prv_img->rect[size], prv_img->w[size], prv_img->h[size]); + ED_preview_icon_render(C, scene, prv_img, id, size); } } diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc index 6aaf551a88a..eca30a6ac25 100644 --- a/source/blender/editors/render/render_preview.cc +++ b/source/blender/editors/render/render_preview.cc @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef WIN32 # include @@ -1370,89 +1371,73 @@ static void icon_preview_startjob(void *customdata, short *stop, short *do_updat ShaderPreview *sp = static_cast(customdata); if (sp->pr_method == PR_ICON_DEFERRED) { - PreviewImage *prv = static_cast(sp->owner); - ImBuf *thumb; - char *deferred_data = static_cast(PRV_DEFERRED_DATA(prv)); - ThumbSource source = static_cast(deferred_data[0]); - char *path = &deferred_data[1]; - - // printf("generating deferred %d×%d preview for %s\n", sp->sizex, sp->sizey, path); - - thumb = IMB_thumb_manage(path, THB_LARGE, source); - - if (thumb) { - /* PreviewImage assumes premultiplied alhpa... */ - IMB_premultiply_alpha(thumb); - - icon_copy_rect(thumb, sp->sizex, sp->sizey, sp->pr_rect); - IMB_freeImBuf(thumb); - } + BLI_assert_unreachable(); + return; } - else { - ID *id = sp->id; - short idtype = GS(id->name); - BLI_assert(id != nullptr); - - if (idtype == ID_IM) { - Image *ima = (Image *)id; - ImBuf *ibuf = nullptr; - ImageUser iuser; - BKE_imageuser_default(&iuser); + ID *id = sp->id; + short idtype = GS(id->name); - if (ima == nullptr) { - return; - } + BLI_assert(id != nullptr); - /* setup dummy image user */ - iuser.framenr = 1; - iuser.scene = sp->scene; - - /* NOTE(@elubie): this needs to be changed: here image is always loaded if not - * already there. Very expensive for large images. Need to find a way to - * only get existing `ibuf`. */ - ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr); - if (ibuf == nullptr || (ibuf->rect == nullptr && ibuf->rect_float == nullptr)) { - BKE_image_release_ibuf(ima, ibuf, nullptr); - return; - } + if (idtype == ID_IM) { + Image *ima = (Image *)id; + ImBuf *ibuf = nullptr; + ImageUser iuser; + BKE_imageuser_default(&iuser); - icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); + if (ima == nullptr) { + return; + } - *do_update = true; + /* setup dummy image user */ + iuser.framenr = 1; + iuser.scene = sp->scene; + /* NOTE(@elubie): this needs to be changed: here image is always loaded if not + * already there. Very expensive for large images. Need to find a way to + * only get existing `ibuf`. */ + ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr); + if (ibuf == nullptr || (ibuf->rect == nullptr && ibuf->rect_float == nullptr)) { BKE_image_release_ibuf(ima, ibuf, nullptr); + return; } - else if (idtype == ID_BR) { - Brush *br = (Brush *)id; - br->icon_imbuf = icon_preview_imbuf_from_brush(br); + icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); - memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint)); + *do_update = true; - if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) { - return; - } + BKE_image_release_ibuf(ima, ibuf, nullptr); + } + else if (idtype == ID_BR) { + Brush *br = (Brush *)id; - icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect); + br->icon_imbuf = icon_preview_imbuf_from_brush(br); - *do_update = true; - } - else if (idtype == ID_SCR) { - bScreen *screen = (bScreen *)id; + memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint)); - ED_screen_preview_render(screen, sp->sizex, sp->sizey, sp->pr_rect); - *do_update = true; + if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) { + return; } - else { - /* re-use shader job */ - shader_preview_startjob(customdata, stop, do_update); - /* world is rendered with alpha=0, so it wasn't displayed - * this could be render option for sky to, for later */ - if (idtype == ID_WO) { - set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255); - } + icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect); + + *do_update = true; + } + else if (idtype == ID_SCR) { + bScreen *screen = (bScreen *)id; + + ED_screen_preview_render(screen, sp->sizex, sp->sizey, sp->pr_rect); + *do_update = true; + } + else { + /* re-use shader job */ + shader_preview_startjob(customdata, stop, do_update); + + /* world is rendered with alpha=0, so it wasn't displayed + * this could be render option for sky to, for later */ + if (idtype == ID_WO) { + set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255); } } } @@ -1670,6 +1655,197 @@ static void icon_preview_endjob(void *customdata) } } +/** + * Background job to manage requests for deferred loading of previews from the hard drive. + * + * Launches a single job to manage all incoming preview requests. The job is kept running until all + * preview requests are done loading (or it's otherwise aborted, e.g. by closing Blender). + * + * Note that this will use the OS thumbnail cache, i.e. load a preview from there or add it if not + * there yet. These two cases may lead to different performance. + */ +class PreviewLoadJob { + struct RequestedPreview { + PreviewImage *preview; + /** Requested size. */ + eIconSizes icon_size; + }; + + /** The previews that are still to be loaded. */ + ThreadQueue *todo_queue_; /* RequestedPreview * */ + /** All unfinished preview requests, #update_fn() calls #finish_preview_request() on loaded + * previews and removes them from this list. Only access from the main thread! */ + std::list requested_previews_; + + public: + PreviewLoadJob(); + ~PreviewLoadJob(); + + static PreviewLoadJob &ensure_job(wmWindowManager *, wmWindow *); + static void load_jobless(PreviewImage *, eIconSizes); + + void push_load_request(PreviewImage *, eIconSizes); + + private: + static void run_fn(void *, short *, short *, float *); + static void update_fn(void *); + static void end_fn(void *); + static void free_fn(void *); + + /** Mark a single requested preview as being done, remove the request. */ + static void finish_request(RequestedPreview &); +}; + +PreviewLoadJob::PreviewLoadJob() : todo_queue_(BLI_thread_queue_init()) +{ +} + +PreviewLoadJob::~PreviewLoadJob() +{ + BLI_thread_queue_free(todo_queue_); +} + +PreviewLoadJob &PreviewLoadJob::ensure_job(wmWindowManager *wm, wmWindow *win) +{ + wmJob *wm_job = WM_jobs_get(wm, win, nullptr, "Load Previews", 0, WM_JOB_TYPE_LOAD_PREVIEW); + + if (!WM_jobs_is_running(wm_job)) { + PreviewLoadJob *job_data = MEM_new("PreviewLoadJobData"); + + WM_jobs_customdata_set(wm_job, job_data, free_fn); + WM_jobs_timer(wm_job, 0.1, NC_WINDOW, NC_WINDOW); + WM_jobs_callbacks(wm_job, run_fn, nullptr, update_fn, end_fn); + + WM_jobs_start(wm, wm_job); + } + + return *reinterpret_cast(WM_jobs_customdata_get(wm_job)); +} + +void PreviewLoadJob::load_jobless(PreviewImage *preview, const eIconSizes icon_size) +{ + PreviewLoadJob job_data{}; + + job_data.push_load_request(preview, icon_size); + + short stop = 0, do_update = 0; + float progress = 0; + run_fn(&job_data, &stop, &do_update, &progress); + update_fn(&job_data); + end_fn(&job_data); +} + +void PreviewLoadJob::push_load_request(PreviewImage *preview, const eIconSizes icon_size) +{ + BLI_assert(preview->tag & PRV_TAG_DEFFERED); + RequestedPreview requested_preview{}; + requested_preview.preview = preview; + requested_preview.icon_size = icon_size; + + preview->flag[icon_size] |= PRV_RENDERING; + /* Warn main thread code that this preview is being rendered and cannot be freed. */ + preview->tag |= PRV_TAG_DEFFERED_RENDERING; + + requested_previews_.push_back(requested_preview); + BLI_thread_queue_push(todo_queue_, &requested_previews_.back()); +} + +void PreviewLoadJob::run_fn(void *customdata, + short *stop, + short *do_update, + float *UNUSED(progress)) +{ + PreviewLoadJob *job_data = reinterpret_cast(customdata); + + IMB_thumb_locks_acquire(); + + while (RequestedPreview *request = reinterpret_cast( + BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100))) { + if (*stop) { + break; + } + + PreviewImage *preview = request->preview; + + const char *deferred_data = static_cast(PRV_DEFERRED_DATA(preview)); + const ThumbSource source = static_cast(deferred_data[0]); + const char *path = &deferred_data[1]; + + // printf("loading deferred %d×%d preview for %s\n", request->sizex, request->sizey, path); + + IMB_thumb_path_lock(path); + ImBuf *thumb = IMB_thumb_manage(path, THB_LARGE, source); + IMB_thumb_path_unlock(path); + + if (thumb) { + /* PreviewImage assumes premultiplied alpha... */ + IMB_premultiply_alpha(thumb); + + icon_copy_rect(thumb, + preview->w[request->icon_size], + preview->h[request->icon_size], + preview->rect[request->icon_size]); + IMB_freeImBuf(thumb); + } + + *do_update = true; + } + + IMB_thumb_locks_release(); +} + +/* Only execute on the main thread! */ +void PreviewLoadJob::finish_request(RequestedPreview &request) +{ + PreviewImage *preview = request.preview; + + preview->tag &= ~PRV_TAG_DEFFERED_RENDERING; + BKE_previewimg_finish(preview, request.icon_size); + + BLI_assert_msg(BLI_thread_is_main(), + "Deferred releasing of preview images should only run on the main thread"); + if (preview->tag & PRV_TAG_DEFFERED_DELETE) { + BLI_assert(preview->tag & PRV_TAG_DEFFERED); + BKE_previewimg_deferred_release(preview); + } +} + +void PreviewLoadJob::update_fn(void *customdata) +{ + PreviewLoadJob *job_data = reinterpret_cast(customdata); + + for (auto request_it = job_data->requested_previews_.begin(); + request_it != job_data->requested_previews_.end();) { + RequestedPreview &requested = *request_it; + /* Skip items that are not done loading yet. */ + if (requested.preview->tag & PRV_TAG_DEFFERED_RENDERING) { + ++request_it; + continue; + } + finish_request(requested); + + /* Remove properly finished previews from the job data. */ + auto next_it = job_data->requested_previews_.erase(request_it); + request_it = next_it; + } +} + +void PreviewLoadJob::end_fn(void *customdata) +{ + PreviewLoadJob *job_data = reinterpret_cast(customdata); + + /* Finish any possibly remaining queued previews. */ + for (RequestedPreview &request : job_data->requested_previews_) { + finish_request(request); + } + job_data->requested_previews_.clear(); +} + +void PreviewLoadJob::free_fn(void *customdata) +{ + MEM_delete(reinterpret_cast(customdata)); +} + static void icon_preview_free(void *customdata) { IconPreview *ip = (IconPreview *)customdata; @@ -1698,8 +1874,19 @@ bool ED_preview_id_is_supported(const ID *id) } void ED_preview_icon_render( - const bContext *C, Scene *scene, ID *id, uint *rect, int sizex, int sizey) + const bContext *C, Scene *scene, PreviewImage *prv_img, ID *id, eIconSizes icon_size) { + /* Deferred loading of previews from the file system. */ + if (prv_img->tag & PRV_TAG_DEFFERED) { + if (prv_img->flag[icon_size] & PRV_RENDERING) { + /* Already in the queue, don't add it again. */ + return; + } + + PreviewLoadJob::load_jobless(prv_img, icon_size); + return; + } + IconPreview ip = {nullptr}; short stop = false, update = false; float progress = 0.0f; @@ -1716,7 +1903,10 @@ void ED_preview_icon_render( ip.id_copy = duplicate_ids(id, true); ip.active_object = CTX_data_active_object(C); - icon_preview_add_size(&ip, rect, sizex, sizey); + prv_img->flag[icon_size] |= PRV_RENDERING; + + icon_preview_add_size( + &ip, prv_img->rect[icon_size], prv_img->w[icon_size], prv_img->h[icon_size]); icon_preview_startjob_all_sizes(&ip, &stop, &update, &progress); @@ -1729,20 +1919,31 @@ void ED_preview_icon_render( } void ED_preview_icon_job( - const bContext *C, void *owner, ID *id, uint *rect, int sizex, int sizey, const bool delay) + const bContext *C, PreviewImage *prv_img, ID *id, eIconSizes icon_size, const bool delay) { - wmJob *wm_job; + /* Deferred loading of previews from the file system. */ + if (prv_img->tag & PRV_TAG_DEFFERED) { + if (prv_img->flag[icon_size] & PRV_RENDERING) { + /* Already in the queue, don't add it again. */ + return; + } + PreviewLoadJob &load_job = PreviewLoadJob::ensure_job(CTX_wm_manager(C), CTX_wm_window(C)); + load_job.push_load_request(prv_img, icon_size); + + return; + } + IconPreview *ip, *old_ip; ED_preview_ensure_dbase(); /* suspended start means it starts after 1 timer step, see WM_jobs_timer below */ - wm_job = WM_jobs_get(CTX_wm_manager(C), - CTX_wm_window(C), - owner, - "Icon Preview", - WM_JOB_EXCL_RENDER, - WM_JOB_TYPE_RENDER_PREVIEW); + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + prv_img, + "Icon Preview", + WM_JOB_EXCL_RENDER, + WM_JOB_TYPE_RENDER_PREVIEW); ip = MEM_cnew("icon preview"); @@ -1757,20 +1958,14 @@ void ED_preview_icon_job( ip->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ip->scene = DEG_get_input_scene(ip->depsgraph); ip->active_object = CTX_data_active_object(C); - ip->owner = owner; + ip->owner = prv_img; ip->id = id; ip->id_copy = duplicate_ids(id, false); - icon_preview_add_size(ip, rect, sizex, sizey); + prv_img->flag[icon_size] |= PRV_RENDERING; - /* Special threading hack: - * warn main code that this preview is being rendered and cannot be freed... */ - { - PreviewImage *prv_img = static_cast(owner); - if (prv_img->tag & PRV_TAG_DEFFERED) { - prv_img->tag |= PRV_TAG_DEFFERED_RENDERING; - } - } + icon_preview_add_size( + ip, prv_img->rect[icon_size], prv_img->w[icon_size], prv_img->h[icon_size]); /* setup job */ WM_jobs_customdata_set(wm_job, ip, icon_preview_free); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 49fbf2c27e1..b00d441dd5d 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1247,6 +1247,8 @@ enum { WM_JOB_TYPE_COMPOSITE, WM_JOB_TYPE_RENDER, WM_JOB_TYPE_RENDER_PREVIEW, /* UI preview */ + /** Job for the UI to load previews from the file system (uses OS thumbnail cache). */ + WM_JOB_TYPE_LOAD_PREVIEW, /* UI preview */ WM_JOB_TYPE_OBJECT_SIM_OCEAN, WM_JOB_TYPE_OBJECT_SIM_FLUID, WM_JOB_TYPE_OBJECT_BAKE_TEXTURE, -- cgit v1.2.3 From af6a1b08e3f0d0070ac9423868d2d3f81057717a Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Fri, 18 Feb 2022 18:20:06 +0100 Subject: VSE: Refactor our code to be compatible with ffmpeg 5.0 In ffmpeg 5.0, several variables were made const to try to prevent bad API usage. Removed some dead code that wasn't used anymore as well. Reviewed By: Richard Antalik Differential Revision: http://developer.blender.org/D14063 --- extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp | 2 +- extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp | 39 +- source/blender/blenkernel/BKE_writeffmpeg.h | 4 - source/blender/blenkernel/intern/scene.c | 22 -- source/blender/blenkernel/intern/writeffmpeg.c | 434 +++-------------------- source/blender/imbuf/intern/IMB_anim.h | 2 +- source/blender/imbuf/intern/anim_movie.c | 2 +- source/blender/imbuf/intern/indexer.c | 13 +- source/blender/imbuf/intern/util.c | 2 +- source/blender/makesdna/DNA_scene_types.h | 5 +- source/blender/makesrna/intern/rna_scene.c | 16 - 11 files changed, 85 insertions(+), 456 deletions(-) diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp index de3ca099696..69bb45119a6 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp +++ b/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp @@ -177,7 +177,7 @@ void FFMPEGReader::init(int stream) // get a decoder and open it #ifndef FFMPEG_OLD_CODE - AVCodec* aCodec = avcodec_find_decoder(m_formatCtx->streams[m_stream]->codecpar->codec_id); + const AVCodec* aCodec = avcodec_find_decoder(m_formatCtx->streams[m_stream]->codecpar->codec_id); if(!aCodec) AUD_THROW(FileException, "File couldn't be read, no decoder found with ffmpeg."); diff --git a/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp b/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp index 10517d1d596..32eb2330594 100644 --- a/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp +++ b/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp @@ -23,6 +23,7 @@ extern "C" { #include #include +#include } AUD_NAMESPACE_BEGIN @@ -171,66 +172,66 @@ FFMPEGWriter::FFMPEGWriter(std::string filename, DeviceSpecs specs, Container fo if(avformat_alloc_output_context2(&m_formatCtx, nullptr, formats[format], filename.c_str()) < 0) AUD_THROW(FileException, "File couldn't be written, format couldn't be found with ffmpeg."); - AVOutputFormat* outputFmt = m_formatCtx->oformat; + const AVOutputFormat* outputFmt = m_formatCtx->oformat; if(!outputFmt) { avformat_free_context(m_formatCtx); AUD_THROW(FileException, "File couldn't be written, output format couldn't be found with ffmpeg."); } - outputFmt->audio_codec = AV_CODEC_ID_NONE; + AVCodecID audio_codec = AV_CODEC_ID_NONE; switch(codec) { case CODEC_AAC: - outputFmt->audio_codec = AV_CODEC_ID_AAC; + audio_codec = AV_CODEC_ID_AAC; break; case CODEC_AC3: - outputFmt->audio_codec = AV_CODEC_ID_AC3; + audio_codec = AV_CODEC_ID_AC3; break; case CODEC_FLAC: - outputFmt->audio_codec = AV_CODEC_ID_FLAC; + audio_codec = AV_CODEC_ID_FLAC; break; case CODEC_MP2: - outputFmt->audio_codec = AV_CODEC_ID_MP2; + audio_codec = AV_CODEC_ID_MP2; break; case CODEC_MP3: - outputFmt->audio_codec = AV_CODEC_ID_MP3; + audio_codec = AV_CODEC_ID_MP3; break; case CODEC_OPUS: - outputFmt->audio_codec = AV_CODEC_ID_OPUS; + audio_codec = AV_CODEC_ID_OPUS; break; case CODEC_PCM: switch(specs.format) { case FORMAT_U8: - outputFmt->audio_codec = AV_CODEC_ID_PCM_U8; + audio_codec = AV_CODEC_ID_PCM_U8; break; case FORMAT_S16: - outputFmt->audio_codec = AV_CODEC_ID_PCM_S16LE; + audio_codec = AV_CODEC_ID_PCM_S16LE; break; case FORMAT_S24: - outputFmt->audio_codec = AV_CODEC_ID_PCM_S24LE; + audio_codec = AV_CODEC_ID_PCM_S24LE; break; case FORMAT_S32: - outputFmt->audio_codec = AV_CODEC_ID_PCM_S32LE; + audio_codec = AV_CODEC_ID_PCM_S32LE; break; case FORMAT_FLOAT32: - outputFmt->audio_codec = AV_CODEC_ID_PCM_F32LE; + audio_codec = AV_CODEC_ID_PCM_F32LE; break; case FORMAT_FLOAT64: - outputFmt->audio_codec = AV_CODEC_ID_PCM_F64LE; + audio_codec = AV_CODEC_ID_PCM_F64LE; break; default: - outputFmt->audio_codec = AV_CODEC_ID_NONE; + audio_codec = AV_CODEC_ID_NONE; break; } break; case CODEC_VORBIS: - outputFmt->audio_codec = AV_CODEC_ID_VORBIS; + audio_codec = AV_CODEC_ID_VORBIS; break; default: - outputFmt->audio_codec = AV_CODEC_ID_NONE; + audio_codec = AV_CODEC_ID_NONE; break; } @@ -268,10 +269,10 @@ FFMPEGWriter::FFMPEGWriter(std::string filename, DeviceSpecs specs, Container fo try { - if(outputFmt->audio_codec == AV_CODEC_ID_NONE) + if(audio_codec == AV_CODEC_ID_NONE) AUD_THROW(FileException, "File couldn't be written, audio codec not found with ffmpeg."); - AVCodec* codec = avcodec_find_encoder(outputFmt->audio_codec); + const AVCodec* codec = avcodec_find_encoder(audio_codec); if(!codec) AUD_THROW(FileException, "File couldn't be written, audio encoder couldn't be found with ffmpeg."); diff --git a/source/blender/blenkernel/BKE_writeffmpeg.h b/source/blender/blenkernel/BKE_writeffmpeg.h index 4c966c55e41..d959bb85c81 100644 --- a/source/blender/blenkernel/BKE_writeffmpeg.h +++ b/source/blender/blenkernel/BKE_writeffmpeg.h @@ -85,12 +85,8 @@ void BKE_ffmpeg_filepath_get(char *string, void BKE_ffmpeg_preset_set(struct RenderData *rd, int preset); void BKE_ffmpeg_image_type_verify(struct RenderData *rd, struct ImageFormatData *imf); -void BKE_ffmpeg_codec_settings_verify(struct RenderData *rd); bool BKE_ffmpeg_alpha_channel_is_supported(const struct RenderData *rd); -int BKE_ffmpeg_property_add_string(struct RenderData *rd, const char *type, const char *str); -void BKE_ffmpeg_property_del(struct RenderData *rd, void *type, void *prop_); - void *BKE_ffmpeg_context_create(void); void BKE_ffmpeg_context_free(void *context_v); diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 203676d0dd8..6d5abbd90d3 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -333,12 +333,6 @@ static void scene_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int scene_dst->r.avicodecdata->lpParms = MEM_dupallocN(scene_dst->r.avicodecdata->lpParms); } - if (scene_src->r.ffcodecdata.properties) { - /* intentionally check sce_dst not sce_src. */ /* XXX ??? comment outdated... */ - scene_dst->r.ffcodecdata.properties = IDP_CopyProperty_ex(scene_src->r.ffcodecdata.properties, - flag_subdata); - } - if (scene_src->display.shading.prop) { scene_dst->display.shading.prop = IDP_CopyProperty(scene_src->display.shading.prop); } @@ -409,10 +403,6 @@ static void scene_free_data(ID *id) MEM_freeN(scene->r.avicodecdata); scene->r.avicodecdata = NULL; } - if (scene->r.ffcodecdata.properties) { - IDP_FreeProperty(scene->r.ffcodecdata.properties); - scene->r.ffcodecdata.properties = NULL; - } scene_free_markers(scene, do_id_user); BLI_freelistN(&scene->transform_spaces); @@ -1030,9 +1020,6 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres BLO_write_raw(writer, (size_t)sce->r.avicodecdata->cbParms, sce->r.avicodecdata->lpParms); } } - if (sce->r.ffcodecdata.properties) { - IDP_BlendWrite(writer, sce->r.ffcodecdata.properties); - } /* writing dynamic list of TimeMarkers to the blend file */ LISTBASE_FOREACH (TimeMarker *, marker, &sce->markers) { @@ -1272,11 +1259,6 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id) BLO_read_data_address(reader, &sce->r.avicodecdata->lpFormat); BLO_read_data_address(reader, &sce->r.avicodecdata->lpParms); } - if (sce->r.ffcodecdata.properties) { - BLO_read_data_address(reader, &sce->r.ffcodecdata.properties); - IDP_BlendDataRead(reader, &sce->r.ffcodecdata.properties); - } - BLO_read_list(reader, &(sce->markers)); LISTBASE_FOREACH (TimeMarker *, marker, &sce->markers) { BLO_read_data_address(reader, &marker->prop); @@ -1889,10 +1871,6 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) sce_copy->r.avicodecdata->lpParms = MEM_dupallocN(sce_copy->r.avicodecdata->lpParms); } - if (sce->r.ffcodecdata.properties) { /* intentionally check scen not sce. */ - sce_copy->r.ffcodecdata.properties = IDP_CopyProperty(sce->r.ffcodecdata.properties); - } - BKE_sound_reset_scene_runtime(sce_copy); /* grease pencil */ diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 9effeb831b6..45bd977c109 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -56,6 +56,7 @@ * like M_SQRT1_2 leading to warnings with MSVC */ # include # include +# include # include # include # include @@ -115,8 +116,6 @@ typedef struct FFMpegContext { printf static void ffmpeg_dict_set_int(AVDictionary **dict, const char *key, int value); -static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float value); -static void ffmpeg_set_expert_options(RenderData *rd); static void ffmpeg_filepath_get(FFMpegContext *context, char *string, const struct RenderData *rd, @@ -428,99 +427,6 @@ static AVFrame *generate_video_frame(FFMpegContext *context, const uint8_t *pixe return context->current_frame; } -static void set_ffmpeg_property_option(IDProperty *prop, AVDictionary **dictionary) -{ - char name[128]; - char *param; - - PRINT("FFMPEG expert option: %s: ", prop->name); - - BLI_strncpy(name, prop->name, sizeof(name)); - - param = strchr(name, ':'); - - if (param) { - *param++ = '\0'; - } - - switch (prop->type) { - case IDP_STRING: - PRINT("%s.\n", IDP_String(prop)); - av_dict_set(dictionary, name, IDP_String(prop), 0); - break; - case IDP_FLOAT: - PRINT("%g.\n", IDP_Float(prop)); - ffmpeg_dict_set_float(dictionary, prop->name, IDP_Float(prop)); - break; - case IDP_INT: - PRINT("%d.\n", IDP_Int(prop)); - - if (param) { - if (IDP_Int(prop)) { - av_dict_set(dictionary, name, param, 0); - } - else { - return; - } - } - else { - ffmpeg_dict_set_int(dictionary, prop->name, IDP_Int(prop)); - } - break; - } -} - -static int ffmpeg_proprty_valid(AVCodecContext *c, const char *prop_name, IDProperty *curr) -{ - int valid = 1; - - if (STREQ(prop_name, "video")) { - if (STREQ(curr->name, "bf")) { - /* flash codec doesn't support b frames */ - valid &= c->codec_id != AV_CODEC_ID_FLV1; - } - } - - return valid; -} - -static void set_ffmpeg_properties(RenderData *rd, - AVCodecContext *c, - const char *prop_name, - AVDictionary **dictionary) -{ - IDProperty *prop; - IDProperty *curr; - - /* TODO(sergey): This is actually rather stupid, because changing - * codec settings in render panel would also set expert options. - * - * But we need ti here in order to get rid of deprecated settings - * when opening old files in new blender. - * - * For as long we don't allow editing properties in the interface - * it's all good. bug if we allow editing them, we'll need to - * replace it with some smarter code which would port settings - * from deprecated to new one. - */ - ffmpeg_set_expert_options(rd); - - if (!rd->ffcodecdata.properties) { - return; - } - - prop = IDP_GetPropertyFromGroup(rd->ffcodecdata.properties, prop_name); - if (!prop) { - return; - } - - for (curr = prop->data.group.first; curr; curr = curr->next) { - if (ffmpeg_proprty_valid(c, prop_name, curr)) { - set_ffmpeg_property_option(curr, dictionary); - } - } -} - static AVRational calc_time_base(uint den, double num, int codec_id) { /* Convert the input 'num' to an integer. Simply shift the decimal places until we get an integer @@ -575,7 +481,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodec *codec; + const AVCodec *codec; AVDictionary *opts = NULL; error[0] = '\0'; @@ -588,21 +494,15 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Set up the codec context */ - context->video_codec = avcodec_alloc_context3(NULL); - AVCodecContext *c = context->video_codec; - c->codec_id = codec_id; - c->codec_type = AVMEDIA_TYPE_VIDEO; - - codec = avcodec_find_encoder(c->codec_id); + codec = avcodec_find_encoder(codec_id); if (!codec) { fprintf(stderr, "Couldn't find valid video codec\n"); - avcodec_free_context(&c); context->video_codec = NULL; return NULL; } - /* Load codec defaults into 'c'. */ - avcodec_get_context_defaults3(c, codec); + context->video_codec = avcodec_alloc_context3(codec); + AVCodecContext *c = context->video_codec; /* Get some values from the current render settings */ @@ -716,6 +616,13 @@ static AVStream *alloc_video_stream(FFMpegContext *context, } } + if (codec_id == AV_CODEC_ID_DNXHD) { + if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { + /* Set the block decision algorithm to be of the highest quality ("rd" == 2). */ + c->mb_decision = 2; + } + } + if (codec_id == AV_CODEC_ID_FFV1) { c->pix_fmt = AV_PIX_FMT_RGB32; } @@ -752,8 +659,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, 255); st->avg_frame_rate = av_inv_q(c->time_base); - set_ffmpeg_properties(rd, c, "video", &opts); - if (codec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { c->thread_count = 0; } @@ -818,8 +723,7 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodec *codec; - AVDictionary *opts = NULL; + const AVCodec *codec; error[0] = '\0'; @@ -829,24 +733,17 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, } st->id = 1; - context->audio_codec = avcodec_alloc_context3(NULL); - AVCodecContext *c = context->audio_codec; - c->thread_count = BLI_system_thread_count(); - c->thread_type = FF_THREAD_SLICE; - - c->codec_id = codec_id; - c->codec_type = AVMEDIA_TYPE_AUDIO; - - codec = avcodec_find_encoder(c->codec_id); + codec = avcodec_find_encoder(codec_id); if (!codec) { fprintf(stderr, "Couldn't find valid audio codec\n"); - avcodec_free_context(&c); context->audio_codec = NULL; return NULL; } - /* Load codec defaults into 'c'. */ - avcodec_get_context_defaults3(c, codec); + context->audio_codec = avcodec_alloc_context3(codec); + AVCodecContext *c = context->audio_codec; + c->thread_count = BLI_system_thread_count(); + c->thread_type = FF_THREAD_SLICE; c->sample_rate = rd->ffcodecdata.audio_mixrate; c->bit_rate = context->ffmpeg_audio_bitrate * 1000; @@ -914,19 +811,15 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } - set_ffmpeg_properties(rd, c, "audio", &opts); - - int ret = avcodec_open2(c, codec, &opts); + int ret = avcodec_open2(c, codec, NULL); if (ret < 0) { fprintf(stderr, "Couldn't initialize audio codec: %s\n", av_err2str(ret)); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); - av_dict_free(&opts); avcodec_free_context(&c); context->audio_codec = NULL; return NULL; } - av_dict_free(&opts); /* need to prevent floating point exception when using vorbis audio codec, * initialize this value in the same way as it's done in FFmpeg itself (sergey) */ @@ -972,15 +865,6 @@ static void ffmpeg_dict_set_int(AVDictionary **dict, const char *key, int value) av_dict_set(dict, key, buffer, 0); } -static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float value) -{ - char buffer[32]; - - BLI_snprintf(buffer, sizeof(buffer), "%.8f", value); - - av_dict_set(dict, key, buffer, 0); -} - static void ffmpeg_add_metadata_callback(void *data, const char *propname, char *propvalue, @@ -999,8 +883,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, { /* Handle to the output file */ AVFormatContext *of; - AVOutputFormat *fmt; - AVDictionary *opts = NULL; + const AVOutputFormat *fmt; char name[FILE_MAX], error[1024]; const char **exts; @@ -1037,11 +920,13 @@ static int start_ffmpeg_impl(FFMpegContext *context, rectx, recty); + /* Sanity checks for the output file extensions. */ exts = get_file_extensions(context->ffmpeg_type); if (!exts) { BKE_report(reports, RPT_ERROR, "No valid formats found"); return 0; } + fmt = av_guess_format(NULL, exts[0], NULL); if (!fmt) { BKE_report(reports, RPT_ERROR, "No valid formats found"); @@ -1050,66 +935,50 @@ static int start_ffmpeg_impl(FFMpegContext *context, of = avformat_alloc_context(); if (!of) { - BKE_report(reports, RPT_ERROR, "Error opening output file"); + BKE_report(reports, RPT_ERROR, "Can't allocate ffmpeg format context"); return 0; } - /* Returns after this must 'goto fail;' */ - - of->oformat = fmt; - - /* Only bother with setting packet size & mux rate when CRF is not used. */ - if (context->ffmpeg_crf == 0) { - of->packet_size = rd->ffcodecdata.mux_packet_size; - if (context->ffmpeg_audio_codec != AV_CODEC_ID_NONE) { - ffmpeg_dict_set_int(&opts, "muxrate", rd->ffcodecdata.mux_rate); - } - else { - av_dict_set(&opts, "muxrate", "0", 0); - } - } - - ffmpeg_dict_set_int(&opts, "preload", (int)(0.5 * AV_TIME_BASE)); - - of->max_delay = (int)(0.7 * AV_TIME_BASE); - - fmt->audio_codec = context->ffmpeg_audio_codec; + enum AVCodecID audio_codec = context->ffmpeg_audio_codec; + enum AVCodecID video_codec = context->ffmpeg_codec; of->url = av_strdup(name); - /* set the codec to the user's selection */ + /* Check if we need to force change the codec because of file type codec restrictions */ switch (context->ffmpeg_type) { - case FFMPEG_AVI: - case FFMPEG_MOV: - case FFMPEG_MKV: - fmt->video_codec = context->ffmpeg_codec; - break; case FFMPEG_OGG: - fmt->video_codec = AV_CODEC_ID_THEORA; + video_codec = AV_CODEC_ID_THEORA; break; case FFMPEG_DV: - fmt->video_codec = AV_CODEC_ID_DVVIDEO; + video_codec = AV_CODEC_ID_DVVIDEO; break; case FFMPEG_MPEG1: - fmt->video_codec = AV_CODEC_ID_MPEG1VIDEO; + video_codec = AV_CODEC_ID_MPEG1VIDEO; break; case FFMPEG_MPEG2: - fmt->video_codec = AV_CODEC_ID_MPEG2VIDEO; + video_codec = AV_CODEC_ID_MPEG2VIDEO; break; case FFMPEG_H264: - fmt->video_codec = AV_CODEC_ID_H264; + video_codec = AV_CODEC_ID_H264; break; case FFMPEG_XVID: - fmt->video_codec = AV_CODEC_ID_MPEG4; + video_codec = AV_CODEC_ID_MPEG4; break; case FFMPEG_FLV: - fmt->video_codec = AV_CODEC_ID_FLV1; + video_codec = AV_CODEC_ID_FLV1; break; - case FFMPEG_MPEG4: default: - fmt->video_codec = context->ffmpeg_codec; + /* These containers are not restricted to any specific codec types. + * Currently we expect these to be .avi, .mov, .mkv, and .mp4. + */ + video_codec = context->ffmpeg_codec; break; } - if (fmt->video_codec == AV_CODEC_ID_DVVIDEO) { + + /* Returns after this must 'goto fail;' */ + + of->oformat = fmt; + + if (video_codec == AV_CODEC_ID_DVVIDEO) { if (rectx != 720) { BKE_report(reports, RPT_ERROR, "Render width has to be 720 pixels for DV!"); goto fail; @@ -1125,7 +994,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, } if (context->ffmpeg_type == FFMPEG_DV) { - fmt->audio_codec = AV_CODEC_ID_PCM_S16LE; + audio_codec = AV_CODEC_ID_PCM_S16LE; if (context->ffmpeg_audio_codec != AV_CODEC_ID_NONE && rd->ffcodecdata.audio_mixrate != 48000 && rd->ffcodecdata.audio_channels != 2) { BKE_report(reports, RPT_ERROR, "FFMPEG only supports 48khz / stereo audio for DV!"); @@ -1133,9 +1002,9 @@ static int start_ffmpeg_impl(FFMpegContext *context, } } - if (fmt->video_codec != AV_CODEC_ID_NONE) { + if (video_codec != AV_CODEC_ID_NONE) { context->video_stream = alloc_video_stream( - context, rd, fmt->video_codec, of, rectx, recty, error, sizeof(error)); + context, rd, video_codec, of, rectx, recty, error, sizeof(error)); PRINT("alloc video stream %p\n", context->video_stream); if (!context->video_stream) { if (error[0]) { @@ -1151,8 +1020,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, } if (context->ffmpeg_audio_codec != AV_CODEC_ID_NONE) { - context->audio_stream = alloc_audio_stream( - context, rd, fmt->audio_codec, of, error, sizeof(error)); + context->audio_stream = alloc_audio_stream(context, rd, audio_codec, of, error, sizeof(error)); if (!context->audio_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); @@ -1189,7 +1057,6 @@ static int start_ffmpeg_impl(FFMpegContext *context, context->outfile = of; av_dump_format(of, 0, name, 1); - av_dict_free(&opts); return 1; @@ -1206,7 +1073,6 @@ fail: context->audio_stream = NULL; } - av_dict_free(&opts); avformat_free_context(of); return 0; } @@ -1540,198 +1406,17 @@ void BKE_ffmpeg_end(void *context_v) end_ffmpeg_impl(context, false); } -/* properties */ - -void BKE_ffmpeg_property_del(RenderData *rd, void *type, void *prop_) -{ - struct IDProperty *prop = (struct IDProperty *)prop_; - IDProperty *group; - - if (!rd->ffcodecdata.properties) { - return; - } - - group = IDP_GetPropertyFromGroup(rd->ffcodecdata.properties, type); - if (group && prop) { - IDP_FreeFromGroup(group, prop); - } -} - -static IDProperty *BKE_ffmpeg_property_add(RenderData *rd, - const char *type, - const AVOption *o, - const AVOption *parent) -{ - AVCodecContext c; - IDProperty *group; - IDProperty *prop; - IDPropertyTemplate val; - int idp_type; - char name[256]; - - val.i = 0; - - avcodec_get_context_defaults3(&c, NULL); - - if (!rd->ffcodecdata.properties) { - rd->ffcodecdata.properties = IDP_New(IDP_GROUP, &val, "ffmpeg"); - } - - group = IDP_GetPropertyFromGroup(rd->ffcodecdata.properties, type); - - if (!group) { - group = IDP_New(IDP_GROUP, &val, type); - IDP_AddToGroup(rd->ffcodecdata.properties, group); - } - - if (parent) { - BLI_snprintf(name, sizeof(name), "%s:%s", parent->name, o->name); - } - else { - BLI_strncpy(name, o->name, sizeof(name)); - } - - PRINT("ffmpeg_property_add: %s %s\n", type, name); - - prop = IDP_GetPropertyFromGroup(group, name); - if (prop) { - return prop; - } - - switch (o->type) { - case AV_OPT_TYPE_INT: - case AV_OPT_TYPE_INT64: - val.i = o->default_val.i64; - idp_type = IDP_INT; - break; - case AV_OPT_TYPE_DOUBLE: - case AV_OPT_TYPE_FLOAT: - val.f = o->default_val.dbl; - idp_type = IDP_FLOAT; - break; - case AV_OPT_TYPE_STRING: - val.string.str = - (char - *)" "; - val.string.len = 80; - idp_type = IDP_STRING; - break; - case AV_OPT_TYPE_CONST: - val.i = 1; - idp_type = IDP_INT; - break; - default: - return NULL; - } - prop = IDP_New(idp_type, &val, name); - IDP_AddToGroup(group, prop); - return prop; -} - -/* not all versions of ffmpeg include that, so here we go ... */ - -int BKE_ffmpeg_property_add_string(RenderData *rd, const char *type, const char *str) -{ - AVCodecContext c; - const AVOption *o = NULL; - const AVOption *p = NULL; - char name_[128]; - char *name; - char *param; - IDProperty *prop = NULL; - - avcodec_get_context_defaults3(&c, NULL); - - BLI_strncpy(name_, str, sizeof(name_)); - - name = name_; - while (*name == ' ') { - name++; - } - - param = strchr(name, ':'); - - if (!param) { - param = strchr(name, ' '); - } - if (param) { - *param++ = '\0'; - while (*param == ' ') { - param++; - } - } - - o = av_opt_find(&c, name, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!o) { - PRINT("Ignoring unknown expert option %s\n", str); - return 0; - } - if (param && o->type == AV_OPT_TYPE_CONST) { - return 0; - } - if (param && o->type != AV_OPT_TYPE_CONST && o->unit) { - p = av_opt_find(&c, param, o->unit, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (p) { - prop = BKE_ffmpeg_property_add(rd, (char *)type, p, o); - } - else { - PRINT("Ignoring unknown expert option %s\n", str); - } - } - else { - prop = BKE_ffmpeg_property_add(rd, (char *)type, o, NULL); - } - - if (!prop) { - return 0; - } - - if (param && !p) { - switch (prop->type) { - case IDP_INT: - IDP_Int(prop) = atoi(param); - break; - case IDP_FLOAT: - IDP_Float(prop) = atof(param); - break; - case IDP_STRING: - strncpy(IDP_String(prop), param, prop->len); - break; - } - } - return 1; -} - -static void ffmpeg_set_expert_options(RenderData *rd) -{ - int codec_id = rd->ffcodecdata.codec; - - if (rd->ffcodecdata.properties) { - IDP_FreePropertyContent(rd->ffcodecdata.properties); - } - - if (codec_id == AV_CODEC_ID_DNXHD) { - if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { - BKE_ffmpeg_property_add_string(rd, "video", "mbd:rd"); - } - } -} - void BKE_ffmpeg_preset_set(RenderData *rd, int preset) { - int isntsc = (rd->frs_sec != 25); - - if (rd->ffcodecdata.properties) { - IDP_FreePropertyContent(rd->ffcodecdata.properties); - } + bool is_ntsc = (rd->frs_sec != 25); switch (preset) { case FFMPEG_PRESET_VCD: rd->ffcodecdata.type = FFMPEG_MPEG1; rd->ffcodecdata.video_bitrate = 1150; rd->xsch = 352; - rd->ysch = isntsc ? 240 : 288; - rd->ffcodecdata.gop_size = isntsc ? 18 : 15; + rd->ysch = is_ntsc ? 240 : 288; + rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15; rd->ffcodecdata.rc_max_rate = 1150; rd->ffcodecdata.rc_min_rate = 1150; rd->ffcodecdata.rc_buffer_size = 40 * 8; @@ -1743,8 +1428,8 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) rd->ffcodecdata.type = FFMPEG_MPEG2; rd->ffcodecdata.video_bitrate = 2040; rd->xsch = 480; - rd->ysch = isntsc ? 480 : 576; - rd->ffcodecdata.gop_size = isntsc ? 18 : 15; + rd->ysch = is_ntsc ? 480 : 576; + rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15; rd->ffcodecdata.rc_max_rate = 2516; rd->ffcodecdata.rc_min_rate = 0; rd->ffcodecdata.rc_buffer_size = 224 * 8; @@ -1761,7 +1446,7 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) rd->ysch = isntsc ? 480 : 576; # endif - rd->ffcodecdata.gop_size = isntsc ? 18 : 15; + rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15; rd->ffcodecdata.rc_max_rate = 9000; rd->ffcodecdata.rc_min_rate = 0; rd->ffcodecdata.rc_buffer_size = 224 * 8; @@ -1772,14 +1457,14 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) case FFMPEG_PRESET_DV: rd->ffcodecdata.type = FFMPEG_DV; rd->xsch = 720; - rd->ysch = isntsc ? 480 : 576; + rd->ysch = is_ntsc ? 480 : 576; break; case FFMPEG_PRESET_H264: rd->ffcodecdata.type = FFMPEG_AVI; rd->ffcodecdata.codec = AV_CODEC_ID_H264; rd->ffcodecdata.video_bitrate = 6000; - rd->ffcodecdata.gop_size = isntsc ? 18 : 15; + rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15; rd->ffcodecdata.rc_max_rate = 9000; rd->ffcodecdata.rc_min_rate = 0; rd->ffcodecdata.rc_buffer_size = 224 * 8; @@ -1800,7 +1485,7 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) } rd->ffcodecdata.video_bitrate = 6000; - rd->ffcodecdata.gop_size = isntsc ? 18 : 15; + rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15; rd->ffcodecdata.rc_max_rate = 9000; rd->ffcodecdata.rc_min_rate = 0; rd->ffcodecdata.rc_buffer_size = 224 * 8; @@ -1808,8 +1493,6 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) rd->ffcodecdata.mux_rate = 10080000; break; } - - ffmpeg_set_expert_options(rd); } void BKE_ffmpeg_image_type_verify(RenderData *rd, ImageFormatData *imf) @@ -1855,11 +1538,6 @@ void BKE_ffmpeg_image_type_verify(RenderData *rd, ImageFormatData *imf) } } -void BKE_ffmpeg_codec_settings_verify(RenderData *rd) -{ - ffmpeg_set_expert_options(rd); -} - bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd) { int codec = rd->ffcodecdata.codec; diff --git a/source/blender/imbuf/intern/IMB_anim.h b/source/blender/imbuf/intern/IMB_anim.h index c4e2ad9da7f..67fdb841317 100644 --- a/source/blender/imbuf/intern/IMB_anim.h +++ b/source/blender/imbuf/intern/IMB_anim.h @@ -124,7 +124,7 @@ struct anim { #ifdef WITH_FFMPEG AVFormatContext *pFormatCtx; AVCodecContext *pCodecCtx; - AVCodec *pCodec; + const AVCodec *pCodec; AVFrame *pFrame; int pFrameComplete; AVFrame *pFrameRGB; diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c index 38dbb9bfc47..0d0ac798d3a 100644 --- a/source/blender/imbuf/intern/anim_movie.c +++ b/source/blender/imbuf/intern/anim_movie.c @@ -508,7 +508,7 @@ static int startffmpeg(struct anim *anim) { int i, video_stream_index; - AVCodec *pCodec; + const AVCodec *pCodec; AVFormatContext *pFormatCtx = NULL; AVCodecContext *pCodecCtx; AVRational frame_rate; diff --git a/source/blender/imbuf/intern/indexer.c b/source/blender/imbuf/intern/indexer.c index 00e96e7840b..55f1eced70f 100644 --- a/source/blender/imbuf/intern/indexer.c +++ b/source/blender/imbuf/intern/indexer.c @@ -493,7 +493,7 @@ struct proxy_output_ctx { AVFormatContext *of; AVStream *st; AVCodecContext *c; - AVCodec *codec; + const AVCodec *codec; struct SwsContext *sws_ctx; AVFrame *frame; int cfra; @@ -525,12 +525,9 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( rv->st = avformat_new_stream(rv->of, NULL); rv->st->id = 0; - rv->c = avcodec_alloc_context3(NULL); - rv->c->codec_type = AVMEDIA_TYPE_VIDEO; - rv->c->codec_id = AV_CODEC_ID_H264; + rv->codec = avcodec_find_encoder(AV_CODEC_ID_H264); - rv->of->oformat->video_codec = rv->c->codec_id; - rv->codec = avcodec_find_encoder(rv->c->codec_id); + rv->c = avcodec_alloc_context3(rv->codec); if (!rv->codec) { fprintf(stderr, @@ -542,8 +539,6 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( return NULL; } - avcodec_get_context_defaults3(rv->c, rv->codec); - rv->c->width = width; rv->c->height = height; rv->c->gop_size = 10; @@ -794,7 +789,7 @@ typedef struct FFmpegIndexBuilderContext { AVFormatContext *iFormatCtx; AVCodecContext *iCodecCtx; - AVCodec *iCodec; + const AVCodec *iCodec; AVStream *iStream; int videoStream; diff --git a/source/blender/imbuf/intern/util.c b/source/blender/imbuf/intern/util.c index 18ed4710e78..96005ed0a0d 100644 --- a/source/blender/imbuf/intern/util.c +++ b/source/blender/imbuf/intern/util.c @@ -267,7 +267,7 @@ static int isffmpeg(const char *filepath) AVFormatContext *pFormatCtx = NULL; unsigned int i; int videoStream; - AVCodec *pCodec; + const AVCodec *pCodec; if (BLI_path_extension_check_n(filepath, ".swf", diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 864358e040c..45fd4895f77 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -157,7 +157,6 @@ typedef struct FFMpegCodecData { int audio_bitrate; int audio_mixrate; int audio_channels; - char _pad0[4]; float audio_volume; int gop_size; /** Only used if FFMPEG_USE_MAX_B_FRAMES flag is set. */ @@ -172,9 +171,7 @@ typedef struct FFMpegCodecData { int rc_buffer_size; int mux_packet_size; int mux_rate; - char _pad1[4]; - - IDProperty *properties; + void *_pad1; } FFMpegCodecData; /* ************************************************************* */ diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 201ea5469cc..e9b9b43422c 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -1480,18 +1480,6 @@ static void rna_FFmpegSettings_lossless_output_set(PointerRNA *ptr, bool value) else { rd->ffcodecdata.flags &= ~FFMPEG_LOSSLESS_OUTPUT; } - - BKE_ffmpeg_codec_settings_verify(rd); -} - -static void rna_FFmpegSettings_codec_settings_update(Main *UNUSED(bmain), - Scene *UNUSED(scene_unused), - PointerRNA *ptr) -{ - Scene *scene = (Scene *)ptr->owner_id; - RenderData *rd = &scene->r; - - BKE_ffmpeg_codec_settings_verify(rd); } # endif @@ -5715,8 +5703,6 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) RNA_def_property_enum_items(prop, ffmpeg_format_items); RNA_def_property_enum_default(prop, FFMPEG_MKV); RNA_def_property_ui_text(prop, "Container", "Output file container"); - RNA_def_property_update( - prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_FFmpegSettings_codec_settings_update"); prop = RNA_def_property(srna, "codec", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, NULL, "codec"); @@ -5724,8 +5710,6 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) RNA_def_property_enum_items(prop, ffmpeg_codec_items); RNA_def_property_enum_default(prop, AV_CODEC_ID_H264); RNA_def_property_ui_text(prop, "Video Codec", "FFmpeg codec to use for video output"); - RNA_def_property_update( - prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_FFmpegSettings_codec_settings_update"); prop = RNA_def_property(srna, "video_bitrate", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "video_bitrate"); -- cgit v1.2.3 From ddc52f2e1d0d15c2176c54691dbc24c549453c3b Mon Sep 17 00:00:00 2001 From: Leon Schittek Date: Fri, 18 Feb 2022 11:25:25 -0600 Subject: Fix: Curve to Mesh node creates caps when curve is cyclic The "Fill Caps" option on the Curve to Mesh node introduced in rBbc2f4dd8b408ee makes it possible to fill the open ends of the sweep to create a manifold mesh. This patch fixes an edge case, where caps were created even when the rail curve (the curve used in the "Curve" input socket) was cyclic making the resulting mesh non-manifold. Differential Revision: https://developer.blender.org/D14124 --- source/blender/blenkernel/intern/curve_to_mesh_convert.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 833b2fe99ec..097414cced1 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -186,7 +186,8 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } } - if (fill_caps && profile.is_cyclic()) { + const bool has_caps = fill_caps && profile.is_cyclic() && !spline.is_cyclic(); + if (has_caps) { const int poly_size = info.spline_edge_len * info.profile_edge_len; const int cap_loop_offset = info.loop_offset + poly_size * 4; const int cap_poly_offset = info.poly_offset + poly_size; @@ -270,7 +271,8 @@ static inline int spline_extrude_loop_size(const Spline &curve, const bool fill_caps) { const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; - const int caps = (fill_caps && profile.is_cyclic()) ? profile.evaluated_edges_size() * 2 : 0; + const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); + const int caps = has_caps ? profile.evaluated_edges_size() * 2 : 0; return tube + caps; } @@ -279,7 +281,8 @@ static inline int spline_extrude_poly_size(const Spline &curve, const bool fill_caps) { const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size(); - const int caps = (fill_caps && profile.is_cyclic()) ? 2 : 0; + const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); + const int caps = has_caps ? 2 : 0; return tube + caps; } -- cgit v1.2.3 From 82fc68ed90bf1429579eddd5df14caf60b791b45 Mon Sep 17 00:00:00 2001 From: Wannes Malfait Date: Fri, 18 Feb 2022 11:35:08 -0600 Subject: Fix T95542: Dual Mesh crashes with some non-manifold vertices The problem was that the code for sorting polygons around a vertex assumed that it was a manifold or boundary vertex. However in some cases the vertex could still be nonmanifold causing the crash. The cases where the sorting fails are now detected and these vertices are then marked as nonmanifold. Differential Revision: https://developer.blender.org/D14065 --- .../nodes/geometry/nodes/node_geo_dual_mesh.cc | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc index f6be6c1e7fb..41de84f7ad1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -286,7 +286,7 @@ static void create_vertex_poly_map(const Mesh &mesh, * boundary vertex, the first and last polygon have a boundary edge connected to the vertex. The * `r_shared_edges` array at index i is set to the index of the shared edge between the i-th and * `(i+1)-th` sorted polygon. Similarly the `r_sorted_corners` array at index i is set to the - * corner in the i-th sorted polygon. + * corner in the i-th sorted polygon. If the polygons couldn't be sorted, `false` is returned. * * How the faces are sorted (see diagrams below): * (For this explanation we'll assume all faces are oriented clockwise) @@ -335,7 +335,7 @@ static void create_vertex_poly_map(const Mesh &mesh, * - Finally if we are in the normal case we also need to add the last "shared edge" to close the * loop. */ -static void sort_vertex_polys(const Mesh &mesh, +static bool sort_vertex_polys(const Mesh &mesh, const int vertex_index, const bool boundary_vertex, const Span edge_types, @@ -344,7 +344,7 @@ static void sort_vertex_polys(const Mesh &mesh, MutableSpan r_sorted_corners) { if (connected_polygons.size() <= 2 && (!boundary_vertex || connected_polygons.size() == 0)) { - return; + return true; } /* For each polygon store the two corners whose edge contains the vertex. */ @@ -448,8 +448,11 @@ static void sort_vertex_polys(const Mesh &mesh, break; } } - - BLI_assert(j != connected_polygons.size()); + if (j == connected_polygons.size()) { + /* The vertex is not manifold because the polygons around the vertex don't form a loop, and + * hence can't be sorted. */ + return false; + } std::swap(connected_polygons[i + 1], connected_polygons[j]); std::swap(poly_vertex_corners[i + 1], poly_vertex_corners[j]); @@ -459,6 +462,7 @@ static void sort_vertex_polys(const Mesh &mesh, /* Shared edge between first and last polygon. */ r_shared_edges.last() = shared_edge_i; } + return true; } /** @@ -651,18 +655,25 @@ static void calc_dual_mesh(GeometrySet &geometry_set, } MutableSpan loop_indices = vertex_poly_indices[i]; Array sorted_corners(loop_indices.size()); + bool vertex_ok = true; if (vertex_types[i] == VertexType::Normal) { Array shared_edges(loop_indices.size()); - sort_vertex_polys( + vertex_ok = sort_vertex_polys( mesh_in, i, false, edge_types, loop_indices, shared_edges, sorted_corners); vertex_shared_edges[i] = shared_edges; } else { Array shared_edges(loop_indices.size() - 1); - sort_vertex_polys( + vertex_ok = sort_vertex_polys( mesh_in, i, true, edge_types, loop_indices, shared_edges, sorted_corners); vertex_shared_edges[i] = shared_edges; } + if (!vertex_ok) { + /* The sorting failed which means that the vertex is non-manifold and should be ignored + * further on. */ + vertex_types[i] = VertexType::NonManifold; + continue; + } vertex_corners[i] = sorted_corners; } }); -- cgit v1.2.3 From d9d97db018d28f4c1ce7543ba275a9809d56294a Mon Sep 17 00:00:00 2001 From: Dominik Fill Date: Fri, 18 Feb 2022 12:22:51 -0600 Subject: Fix T87829, T95331: Issues when nodes too close together This patch aims to fix the issues presented in T87829 and T95331, namely precision issues while connecting two nodes when being too close together in the node editor editors, in a few cases even resulting in the complete inability to connect nodes. Sockets are found by intersecting a padded rect around the cursor with the nodes' sockets' location. That creates ambiguities, as it's possible for the padded rect to intersect with the wrong node, as the distance between two nodes is smaller than the rect is padded. The fix in this patch is checking against an unpadded rectangle in visible_node(). Differential Revision: https://developer.blender.org/D14122 --- source/blender/editors/space_node/node_edit.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 7b7aaef518b..64f6e8bdf18 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -1160,12 +1160,16 @@ bool node_find_indicated_socket(SpaceNode &snode, { rctf rect; + const float size_sock_padded = NODE_SOCKSIZE + 4; + *nodep = nullptr; *sockp = nullptr; /* check if we click in a socket */ LISTBASE_FOREACH (bNode *, node, &snode.edittree->nodes) { - BLI_rctf_init_pt_radius(&rect, cursor, NODE_SOCKSIZE + 4); + BLI_rctf_init_pt_radius(&rect, cursor, size_sock_padded); + rctf node_visible; + BLI_rctf_init_pt_radius(&node_visible, cursor, size_sock_padded); if (!(node->flag & NODE_HIDDEN)) { /* extra padding inside and out - allow dragging on the text areas too */ @@ -1184,7 +1188,7 @@ bool node_find_indicated_socket(SpaceNode &snode, if (!nodeSocketIsHidden(sock)) { if (sock->flag & SOCK_MULTI_INPUT && !(node->flag & NODE_HIDDEN)) { if (cursor_isect_multi_input_socket(cursor, *sock)) { - if (node == visible_node(snode, rect)) { + if (node == visible_node(snode, node_visible)) { *nodep = node; *sockp = sock; return true; @@ -1192,7 +1196,7 @@ bool node_find_indicated_socket(SpaceNode &snode, } } else if (BLI_rctf_isect_pt(&rect, sock->locx, sock->locy)) { - if (node == visible_node(snode, rect)) { + if (node == visible_node(snode, node_visible)) { *nodep = node; *sockp = sock; return true; @@ -1205,7 +1209,7 @@ bool node_find_indicated_socket(SpaceNode &snode, LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (!nodeSocketIsHidden(sock)) { if (BLI_rctf_isect_pt(&rect, sock->locx, sock->locy)) { - if (node == visible_node(snode, rect)) { + if (node == visible_node(snode, node_visible)) { *nodep = node; *sockp = sock; return true; -- cgit v1.2.3 From b04d42022f7816dc1cfbf8d4e66733bd78eebbc9 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 18 Feb 2022 18:43:46 +0100 Subject: Fix T95338: missing image editor refresh after render compositing This was an old issue, but recent image partial update changes made this more likely to happen in some cases. Now ensure that whenever the rendered scene switches the image is updated. --- source/blender/editors/render/render_internal.cc | 6 ++++++ source/blender/render/intern/pipeline.c | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/source/blender/editors/render/render_internal.cc b/source/blender/editors/render/render_internal.cc index 8e9a052381c..cd3b8183d48 100644 --- a/source/blender/editors/render/render_internal.cc +++ b/source/blender/editors/render/render_internal.cc @@ -634,6 +634,12 @@ static void image_rect_update(void *rjv, RenderResult *rr, volatile rcti *renrec static void current_scene_update(void *rjv, Scene *scene) { RenderJob *rj = static_cast(rjv); + + if (rj->current_scene != scene) { + /* Image must be updated when rendered scene changes. */ + BKE_image_partial_update_mark_full_update(rj->image); + } + rj->current_scene = scene; rj->iuser.scene = scene; } diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 739202564af..c2b719e46a7 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1122,6 +1122,8 @@ static void do_render_compositor_scenes(Render *re) return; } + bool changed_scene = false; + /* now foreach render-result node we do a full render */ /* results are stored in a way compositor will find it */ GSet *scenes_rendered = BLI_gset_ptr_new(__func__); @@ -1134,11 +1136,20 @@ static void do_render_compositor_scenes(Render *re) do_render_compositor_scene(re, scene, cfra); BLI_gset_add(scenes_rendered, scene); node->typeinfo->updatefunc(restore_scene->nodetree, node); + + if (scene != re->scene) { + changed_scene = true; + } } } } } BLI_gset_free(scenes_rendered, NULL); + + if (changed_scene) { + /* If rendered another scene, switch back to the current scene with compositing nodes. */ + re->current_scene_update(re->suh, re->scene); + } } /* bad call... need to think over proper method still */ -- cgit v1.2.3 From eaa4aa864427e8aaa0bb1d4f0ce930b562aabc05 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 17 Feb 2022 20:43:04 +0100 Subject: Fix part of T95848: missing updates editing light object nodes Make relation match material and world nodes. Does not address the reported issue regarding muted nodes, but another missing update found investigating. --- source/blender/depsgraph/intern/builder/deg_builder_relations.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 462e61c5671..d63d1bafb3e 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2420,8 +2420,9 @@ void DepsgraphRelationBuilder::build_light(Light *lamp) /* light's nodetree */ if (lamp->nodetree != nullptr) { build_nodetree(lamp->nodetree); - ComponentKey nodetree_key(&lamp->nodetree->id, NodeType::NTREE_OUTPUT); - add_relation(nodetree_key, shading_key, "NTree->Light Parameters"); + OperationKey ntree_key( + &lamp->nodetree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); + add_relation(ntree_key, shading_key, "NTree->Light Parameters"); build_nested_nodetree(&lamp->id, lamp->nodetree); } } -- cgit v1.2.3 From 969c4a45ce09100edc961fd0dc6f37d4689373c7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 13:21:36 -0600 Subject: Cleanup: Use functions for accessing mesh normal dirty state It's better not to expose the details of where the dirty flags are stored to every place that wants to know if the normals are dirty. Some of these places are relics from before vertex normals were computed lazily anyway, so this is more of an incrememtal cleanup. This will make part of the fix for T95839 simpler. --- source/blender/blenkernel/intern/DerivedMesh.cc | 5 ++--- source/blender/blenkernel/intern/cdderivedmesh.c | 2 +- source/blender/blenkernel/intern/mesh.cc | 8 +++----- source/blender/blenkernel/intern/mesh_convert.cc | 8 +++----- source/blender/modifiers/intern/MOD_array.c | 2 +- source/blender/modifiers/intern/MOD_displace.c | 3 +-- source/blender/modifiers/intern/MOD_solidify_extrude.c | 6 +++--- 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 6056b69b5c8..411e5f81262 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -1981,9 +1981,8 @@ Mesh *mesh_get_eval_final(struct Depsgraph *depsgraph, mesh_eval = BKE_object_get_evaluated_mesh(ob); } - if (mesh_eval != nullptr) { - BLI_assert(!(mesh_eval->runtime.cd_dirty_vert & CD_MASK_NORMAL)); - } + BKE_mesh_assert_normals_dirty_or_calculated(mesh_eval); + return mesh_eval; } diff --git a/source/blender/blenkernel/intern/cdderivedmesh.c b/source/blender/blenkernel/intern/cdderivedmesh.c index a4f3e84a2bf..474478922f8 100644 --- a/source/blender/blenkernel/intern/cdderivedmesh.c +++ b/source/blender/blenkernel/intern/cdderivedmesh.c @@ -265,7 +265,7 @@ static DerivedMesh *cdDM_from_mesh_ex(Mesh *mesh, dm->deformedOnly = 1; dm->cd_flag = mesh->cd_flag; - if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) { + if (BKE_mesh_vertex_normals_are_dirty(mesh)) { dm->dirty |= DM_DIRTY_NORMALS; } /* TODO: DM_DIRTY_TESS_CDLAYERS ? Maybe not though, diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 76a15fd0a1c..351535a6f78 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1116,11 +1116,9 @@ Mesh *BKE_mesh_new_nomain_from_template_ex(const Mesh *me_src, /* Ensure that when no normal layers exist, they are marked dirty, because * normals might not have been included in the mask of copied layers. */ - if (!CustomData_has_layer(&me_dst->vdata, CD_NORMAL)) { - me_dst->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - } - if (!CustomData_has_layer(&me_dst->pdata, CD_NORMAL)) { - me_dst->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + if (!CustomData_has_layer(&me_dst->vdata, CD_NORMAL) || + !CustomData_has_layer(&me_dst->pdata, CD_NORMAL)) { + BKE_mesh_normals_tag_dirty(me_dst); } /* The destination mesh should at least have valid primary CD layers, diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 22e4c3bf13c..3562f6c6b17 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -1500,11 +1500,9 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, /* Ensure that when no normal layers exist, they are marked dirty, because * normals might not have been included in the mask of copied layers. */ - if (!CustomData_has_layer(&tmp.vdata, CD_NORMAL)) { - tmp.runtime.cd_dirty_vert |= CD_MASK_NORMAL; - } - if (!CustomData_has_layer(&tmp.pdata, CD_NORMAL)) { - tmp.runtime.cd_dirty_poly |= CD_MASK_NORMAL; + if (!CustomData_has_layer(&tmp.vdata, CD_NORMAL) || + !CustomData_has_layer(&tmp.pdata, CD_NORMAL)) { + BKE_mesh_normals_tag_dirty(&tmp); } if (CustomData_has_layer(&mesh_src->vdata, CD_SHAPEKEY)) { diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 56db68b163c..de129888060 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -387,7 +387,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, int tot_doubles; const bool use_merge = (amd->flags & MOD_ARR_MERGE) != 0; - const bool use_recalc_normals = (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) || use_merge; + const bool use_recalc_normals = BKE_mesh_vertex_normals_are_dirty(mesh) || use_merge; const bool use_offset_ob = ((amd->offset_type & MOD_ARR_OFF_OBJ) && amd->offset_ob != NULL); int start_cap_nverts = 0, start_cap_nedges = 0, start_cap_npolys = 0, start_cap_nloops = 0; diff --git a/source/blender/modifiers/intern/MOD_displace.c b/source/blender/modifiers/intern/MOD_displace.c index c81e6cafa39..cdb7035570e 100644 --- a/source/blender/modifiers/intern/MOD_displace.c +++ b/source/blender/modifiers/intern/MOD_displace.c @@ -329,8 +329,7 @@ static void displaceModifier_do(DisplaceModifierData *dmd, if (CustomData_has_layer(ldata, CD_CUSTOMLOOPNORMAL)) { float(*clnors)[3] = NULL; - if ((mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) || - !CustomData_has_layer(ldata, CD_NORMAL)) { + if (!CustomData_has_layer(ldata, CD_NORMAL)) { BKE_mesh_calc_normals_split(mesh); } diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index fa8d08bf839..a9aec2f1c15 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -967,7 +967,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) { + if (BKE_mesh_vertex_normals_are_dirty(mesh) || do_rim || dvert) { BKE_mesh_normals_tag_dirty(result); } else if (do_shell) { @@ -1023,9 +1023,9 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex #define SOLIDIFY_SIDE_NORMALS #ifdef SOLIDIFY_SIDE_NORMALS - /* NOTE(@sybren): due to the code setting cd_dirty_vert a few lines above, + /* NOTE(@sybren): due to the code setting normals dirty a few lines above, * do_side_normals is always false. */ - const bool do_side_normals = !(result->runtime.cd_dirty_vert & CD_MASK_NORMAL); + const bool do_side_normals = !BKE_mesh_vertex_normals_are_dirty(result); /* annoying to allocate these since we only need the edge verts, */ float(*edge_vert_nos)[3] = do_side_normals ? MEM_calloc_arrayN(numVerts, sizeof(float[3]), __func__) : -- cgit v1.2.3 From ef0e21f0ae71d9ec4ba3cdf6f6a16c9575459ced Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Feb 2022 13:39:16 -0600 Subject: Cleanup: Remove unused argument to mesh tessellation This removes manual handling of normals that was hard-coded to false in the one place the function was called. This change will help to make a fix to T95839 simpler. --- source/blender/blenkernel/BKE_mesh.h | 3 +- source/blender/blenkernel/intern/mesh_tessellate.c | 32 ++++++---------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 376bb5c8d2e..2b32c6a5420 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -348,8 +348,7 @@ int BKE_mesh_tessface_calc_ex(struct CustomData *fdata, struct MVert *mvert, int totface, int totloop, - int totpoly, - bool do_face_nor_copy); + int totpoly); void BKE_mesh_tessface_calc(struct Mesh *mesh); /** diff --git a/source/blender/blenkernel/intern/mesh_tessellate.c b/source/blender/blenkernel/intern/mesh_tessellate.c index 241aefc418c..4c4e008f615 100644 --- a/source/blender/blenkernel/intern/mesh_tessellate.c +++ b/source/blender/blenkernel/intern/mesh_tessellate.c @@ -160,8 +160,7 @@ int BKE_mesh_tessface_calc_ex(CustomData *fdata, MVert *mvert, int totface, int totloop, - int totpoly, - const bool do_face_nor_copy) + int totpoly) { #define USE_TESSFACE_SPEEDUP #define USE_TESSFACE_QUADS @@ -363,18 +362,6 @@ int BKE_mesh_tessface_calc_ex(CustomData *fdata, CustomData_add_layer(fdata, CD_ORIGINDEX, CD_ASSIGN, mface_to_poly_map, totface); CustomData_from_bmeshpoly(fdata, ldata, totface); - if (do_face_nor_copy) { - /* If polys have a normals layer, copying that to faces can help - * avoid the need to recalculate normals later. */ - if (CustomData_has_layer(pdata, CD_NORMAL)) { - float(*pnors)[3] = CustomData_get_layer(pdata, CD_NORMAL); - float(*fnors)[3] = CustomData_add_layer(fdata, CD_NORMAL, CD_CALLOC, NULL, totface); - for (mface_index = 0; mface_index < totface; mface_index++) { - copy_v3_v3(fnors[mface_index], pnors[mface_to_poly_map[mface_index]]); - } - } - } - /* NOTE: quad detection issue - fourth vertidx vs fourth loopidx: * Polygons take care of their loops ordering, hence not of their vertices ordering. * Currently, our tfaces' fourth vertex index might be 0 even for a quad. @@ -411,16 +398,13 @@ int BKE_mesh_tessface_calc_ex(CustomData *fdata, void BKE_mesh_tessface_calc(Mesh *mesh) { - mesh->totface = BKE_mesh_tessface_calc_ex( - &mesh->fdata, - &mesh->ldata, - &mesh->pdata, - mesh->mvert, - mesh->totface, - mesh->totloop, - mesh->totpoly, - /* Calculate normals right after, don't copy from polys here. */ - false); + mesh->totface = BKE_mesh_tessface_calc_ex(&mesh->fdata, + &mesh->ldata, + &mesh->pdata, + mesh->mvert, + mesh->totface, + mesh->totloop, + mesh->totpoly); BKE_mesh_update_customdata_pointers(mesh, true); } -- cgit v1.2.3 From 7f7c614ecddbcb66de0bff1657366970dede99be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 17 Feb 2022 17:03:23 +0100 Subject: OCIO: Port shader creation logic to use GPUShaderCreateInfo This commit should suffice to make the shader API agnostic now (given that all users of it use the GPU API). This makes the shaders not trigger a false positive error anymore since the binding slots are now garanteed by the backend and not changed at after compilation. This also bundles all uniforms into UBOs. Making them extendable without limitations of push constants. The generated uniforms from OCIO are not densely packed in the UBO to avoid complexity. Another approach would be to use GPU_uniformbuf_create_from_list but this requires converting uniforms to GPUInputs which is too complex for what it is. Reviewed by: brecht, jbakker Differential Revision: https://developer.blender.org/D14123 --- intern/opencolorio/CMakeLists.txt | 36 ++- .../opencolorio/gpu_shader_display_transform.glsl | 210 -------------- .../gpu_shader_display_transform_frag.glsl | 195 +++++++++++++ .../gpu_shader_display_transform_vert.glsl | 6 + .../gpu_shader_display_transform_vertex.glsl | 12 - intern/opencolorio/ocio_impl_glsl.cc | 323 ++++++++++++--------- intern/opencolorio/ocio_shader_shared.hh | 41 +++ source/blender/gpu/CMakeLists.txt | 9 + source/blender/gpu/intern/gpu_shader_dependency.cc | 6 + 9 files changed, 482 insertions(+), 356 deletions(-) delete mode 100644 intern/opencolorio/gpu_shader_display_transform.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_frag.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_vert.glsl delete mode 100644 intern/opencolorio/gpu_shader_display_transform_vertex.glsl create mode 100644 intern/opencolorio/ocio_shader_shared.hh diff --git a/intern/opencolorio/CMakeLists.txt b/intern/opencolorio/CMakeLists.txt index 0b46ae471d2..a2ef0d46a40 100644 --- a/intern/opencolorio/CMakeLists.txt +++ b/intern/opencolorio/CMakeLists.txt @@ -24,6 +24,7 @@ set(INC ../guardedalloc ../../source/blender/blenlib ../../source/blender/gpu + ../../source/blender/gpu/intern ../../source/blender/makesdna ) @@ -37,6 +38,7 @@ set(SRC ocio_capi.h ocio_impl.h + ocio_shader_shared.hh ) set(LIB @@ -73,8 +75,38 @@ if(WITH_OPENCOLORIO) ) endif() - data_to_c_simple(gpu_shader_display_transform.glsl SRC) - data_to_c_simple(gpu_shader_display_transform_vertex.glsl SRC) + set(GLSL_SRC + gpu_shader_display_transform_vert.glsl + gpu_shader_display_transform_frag.glsl + + ocio_shader_shared.hh + ) + + set(GLSL_C) + foreach(GLSL_FILE ${GLSL_SRC}) + data_to_c_simple(${GLSL_FILE} GLSL_C) + endforeach() + + blender_add_lib(bf_ocio_shaders "${GLSL_C}" "" "" "") + + list(APPEND LIB + bf_ocio_shaders + ) + + set(GLSL_SOURCE_CONTENT "") + foreach(GLSL_FILE ${GLSL_SRC}) + get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME) + string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME}) + string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(datatoc_${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n") + endforeach() + + set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_ocio_source_list.h") + file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}") + list(APPEND SRC ${glsl_source_list_file}) + list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR}) + + target_include_directories(bf_ocio_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + endif() diff --git a/intern/opencolorio/gpu_shader_display_transform.glsl b/intern/opencolorio/gpu_shader_display_transform.glsl deleted file mode 100644 index f5a7a7bf45d..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform.glsl +++ /dev/null @@ -1,210 +0,0 @@ -/* Blender OpenColorIO implementation */ - -uniform sampler2D image_texture; -uniform sampler2D overlay_texture; - -uniform float dither; -uniform float scale; -uniform float exponent; -uniform bool predivide; -uniform bool overlay; - -#ifdef USE_CURVE_MAPPING -uniform sampler1D curve_mapping_texture; - -layout(std140) uniform OCIO_GPUCurveMappingParameters -{ - /* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ - vec4 curve_mapping_mintable; - vec4 curve_mapping_range; - vec4 curve_mapping_ext_in_x; - vec4 curve_mapping_ext_in_y; - vec4 curve_mapping_ext_out_x; - vec4 curve_mapping_ext_out_y; - vec4 curve_mapping_first_x; - vec4 curve_mapping_first_y; - vec4 curve_mapping_last_x; - vec4 curve_mapping_last_y; - vec4 curve_mapping_black; - vec4 curve_mapping_bwmul; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; -}; - -float read_curve_mapping(int table, int index) -{ - return texelFetch(curve_mapping_texture, index, 0)[table]; -} - -float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) -{ - if (x <= first[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return first[1]; - } - else { - float fac = (curve_mapping_ext_in_x[table] != 0.0) ? - ((x - first[0]) / curve_mapping_ext_in_x[table]) : - 10000.0; - return first[1] + curve_mapping_ext_in_y[table] * fac; - } - } - else if (x >= last[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return last[1]; - } - else { - float fac = (curve_mapping_ext_out_x[table] != 0.0) ? - ((x - last[0]) / curve_mapping_ext_out_x[table]) : - -10000.0; - return last[1] + curve_mapping_ext_out_y[table] * fac; - } - } - return 0.0; -} - -float curvemap_evaluateF(int table, float value) -{ - float mintable_ = curve_mapping_mintable[table]; - float range = curve_mapping_range[table]; - float mintable = 0.0; - int CM_TABLE = curve_mapping_lut_size - 1; - - float fi; - int i; - - /* index in table */ - fi = (value - mintable) * range; - i = int(fi); - - /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ - if (fi < 0.0 || fi > float(CM_TABLE)) { - return curvemap_calc_extend(table, - value, - vec2(curve_mapping_first_x[table], curve_mapping_first_y[table]), - vec2(curve_mapping_last_x[table], curve_mapping_last_y[table])); - } - else { - if (i < 0) { - return read_curve_mapping(table, 0); - } - if (i >= CM_TABLE) { - return read_curve_mapping(table, CM_TABLE); - } - fi = fi - float(i); - float cm1 = read_curve_mapping(table, i); - float cm2 = read_curve_mapping(table, i + 1); - return mix(cm1, cm2, fi); - } -} - -vec4 curvemapping_evaluate_premulRGBF(vec4 col) -{ - col.rgb = (col.rgb - curve_mapping_black.rgb) * curve_mapping_bwmul.rgb; - - vec4 result; - result.r = curvemap_evaluateF(0, col.r); - result.g = curvemap_evaluateF(1, col.g); - result.b = curvemap_evaluateF(2, col.b); - result.a = col.a; - return result; -} -#endif /* USE_CURVE_MAPPING */ - -/* Using a triangle distribution which gives a more final uniform noise. - * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ -/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ -/* Return triangle noise in [-1..1[ range */ -float dither_random_value(vec2 co) -{ - /* Original code from https://www.shadertoy.com/view/4t2SDh */ - /* Uniform noise in [0..1[ range */ - float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); - /* Convert uniform distribution into triangle-shaped distribution. */ - float orig = nrnd0 * 2.0 - 1.0; - nrnd0 = orig * inversesqrt(abs(orig)); - nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ - return nrnd0 - sign(orig); -} - -vec2 round_to_pixel(sampler2D tex, vec2 uv) -{ - vec2 size = textureSize(tex, 0); - return vec2(ivec2(uv * size)) / size; -} - -vec4 apply_dither(vec4 col, vec2 uv) -{ - col.rgb += dither_random_value(uv) * 0.0033 * dither; - return col; -} - -vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay, vec2 noise_uv) -{ -#ifdef USE_CURVE_MAPPING - col = curvemapping_evaluate_premulRGBF(col); -#endif - - if (predivide) { - if (col.a > 0.0 && col.a < 1.0) { - col.rgb *= 1.0 / col.a; - } - } - - /* NOTE: This is true we only do de-premul here and NO premul - * and the reason is simple -- opengl is always configured - * for straight alpha at this moment - */ - - /* Convert to scene linear (usually a no-op). */ - col = OCIO_to_scene_linear(col); - - /* Apply exposure in scene linear. */ - col.rgb *= scale; - - /* Convert to display space. */ - col = OCIO_to_display(col); - - /* Blend with overlay in UI colorspace. - * - * UI colorspace here refers to the display linear color space, - * i.e: The linear color space w.r.t. display chromaticity and radiometry. - * We separate the colormanagement process into two steps to be able to - * merge UI using alpha blending in the correct color space. */ - if (overlay) { - col.rgb = pow(col.rgb, vec3(exponent * 2.2)); - col = clamp(col, 0.0, 1.0); - col *= 1.0 - col_overlay.a; - col += col_overlay; /* Assumed unassociated alpha. */ - col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); - } - else { - col.rgb = pow(col.rgb, vec3(exponent)); - } - - if (dither > 0.0) { - col = apply_dither(col, noise_uv); - } - - return col; -} - -/* ------------------------------------------------------------------------ */ - -in vec2 texCoord_interp; -out vec4 fragColor; - -void main() -{ - vec4 col = texture(image_texture, texCoord_interp.st); - vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); - vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); - - fragColor = OCIO_ProcessColor(col, col_overlay, noise_uv); -} diff --git a/intern/opencolorio/gpu_shader_display_transform_frag.glsl b/intern/opencolorio/gpu_shader_display_transform_frag.glsl new file mode 100644 index 00000000000..3c2352c13ba --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_frag.glsl @@ -0,0 +1,195 @@ +/* Blender OpenColorIO implementation */ + +/* -------------------------------------------------------------------- */ +/** \name Curve Mapping Implementation + * \{ */ + +#ifdef USE_CURVE_MAPPING + +float read_curve_mapping(int table, int index) +{ + return texelFetch(curve_mapping_texture, index, 0)[table]; +} + +float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) +{ + if (x <= first[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return first[1]; + } + else { + float fac = (curve_mapping.ext_in_x[table] != 0.0) ? + ((x - first[0]) / curve_mapping.ext_in_x[table]) : + 10000.0; + return first[1] + curve_mapping.ext_in_y[table] * fac; + } + } + else if (x >= last[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return last[1]; + } + else { + float fac = (curve_mapping.ext_out_x[table] != 0.0) ? + ((x - last[0]) / curve_mapping.ext_out_x[table]) : + -10000.0; + return last[1] + curve_mapping.ext_out_y[table] * fac; + } + } + return 0.0; +} + +float curvemap_evaluateF(int table, float value) +{ + float mintable_ = curve_mapping.mintable[table]; + float range = curve_mapping.range[table]; + float mintable = 0.0; + int CM_TABLE = curve_mapping.lut_size - 1; + + float fi; + int i; + + /* index in table */ + fi = (value - mintable) * range; + i = int(fi); + + /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ + if (fi < 0.0 || fi > float(CM_TABLE)) { + return curvemap_calc_extend(table, + value, + vec2(curve_mapping.first_x[table], curve_mapping.first_y[table]), + vec2(curve_mapping.last_x[table], curve_mapping.last_y[table])); + } + else { + if (i < 0) { + return read_curve_mapping(table, 0); + } + if (i >= CM_TABLE) { + return read_curve_mapping(table, CM_TABLE); + } + fi = fi - float(i); + float cm1 = read_curve_mapping(table, i); + float cm2 = read_curve_mapping(table, i + 1); + return mix(cm1, cm2, fi); + } +} + +vec4 curvemapping_evaluate_premulRGBF(vec4 col) +{ + col.rgb = (col.rgb - curve_mapping.black.rgb) * curve_mapping.bwmul.rgb; + + vec4 result; + result.r = curvemap_evaluateF(0, col.r); + result.g = curvemap_evaluateF(1, col.g); + result.b = curvemap_evaluateF(2, col.b); + result.a = col.a; + return result; +} + +#endif /* USE_CURVE_MAPPING */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dithering + * \{ */ + +/* Using a triangle distribution which gives a more final uniform noise. + * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ +/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ +/* Return triangle noise in [-1..1[ range */ +float dither_random_value(vec2 co) +{ + /* Original code from https://www.shadertoy.com/view/4t2SDh */ + /* Uniform noise in [0..1[ range */ + float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); + /* Convert uniform distribution into triangle-shaped distribution. */ + float orig = nrnd0 * 2.0 - 1.0; + nrnd0 = orig * inversesqrt(abs(orig)); + nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ + return nrnd0 - sign(orig); +} + +vec2 round_to_pixel(sampler2D tex, vec2 uv) +{ + vec2 size = vec2(textureSize(tex, 0)); + return floor(uv * size) / size; +} + +vec4 apply_dither(vec4 col, vec2 uv) +{ + col.rgb += dither_random_value(uv) * 0.0033 * parameters.dither; + return col; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Main Processing + * \{ */ + +/* Prototypes: Implementation is generaterd and defined after. */ +vec4 OCIO_to_scene_linear(vec4 pixel); +vec4 OCIO_to_display(vec4 pixel); + +vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay) +{ +#ifdef USE_CURVE_MAPPING + col = curvemapping_evaluate_premulRGBF(col); +#endif + + if (parameters.use_predivide) { + if (col.a > 0.0 && col.a < 1.0) { + col.rgb *= 1.0 / col.a; + } + } + + /* NOTE: This is true we only do de-premul here and NO premul + * and the reason is simple -- opengl is always configured + * for straight alpha at this moment + */ + + /* Convert to scene linear (usually a no-op). */ + col = OCIO_to_scene_linear(col); + + /* Apply exposure in scene linear. */ + col.rgb *= parameters.scale; + + /* Convert to display space. */ + col = OCIO_to_display(col); + + /* Blend with overlay in UI colorspace. + * + * UI colorspace here refers to the display linear color space, + * i.e: The linear color space w.r.t. display chromaticity and radiometry. + * We separate the colormanagement process into two steps to be able to + * merge UI using alpha blending in the correct color space. */ + if (parameters.use_overlay) { + col.rgb = pow(col.rgb, vec3(parameters.exponent * 2.2)); + col = clamp(col, 0.0, 1.0); + col *= 1.0 - col_overlay.a; + col += col_overlay; /* Assumed unassociated alpha. */ + col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); + } + else { + col.rgb = pow(col.rgb, vec3(parameters.exponent)); + } + + if (parameters.dither > 0.0) { + vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); + col = apply_dither(col, noise_uv); + } + + return col; +} + +/** \} */ + +void main() +{ + vec4 col = texture(image_texture, texCoord_interp.st); + vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); + + fragColor = OCIO_ProcessColor(col, col_overlay); +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vert.glsl b/intern/opencolorio/gpu_shader_display_transform_vert.glsl new file mode 100644 index 00000000000..06788be11de --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_vert.glsl @@ -0,0 +1,6 @@ + +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + texCoord_interp = texCoord; +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl b/intern/opencolorio/gpu_shader_display_transform_vertex.glsl deleted file mode 100644 index 8cf9628b06b..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl +++ /dev/null @@ -1,12 +0,0 @@ - -uniform mat4 ModelViewProjectionMatrix; - -in vec2 texCoord; -in vec2 pos; -out vec2 texCoord_interp; - -void main() -{ - gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); - texCoord_interp = texCoord; -} diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index fc45ec4e301..e4840eb05f6 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -49,14 +49,14 @@ #include "GPU_shader.h" #include "GPU_uniform_buffer.h" +#include "gpu_shader_create_info.hh" + using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "ocio_impl.h" - -extern "C" char datatoc_gpu_shader_display_transform_glsl[]; -extern "C" char datatoc_gpu_shader_display_transform_vertex_glsl[]; +#include "ocio_shader_shared.hh" /* **** OpenGL drawing routines using GLSL for color space transform ***** */ @@ -67,41 +67,19 @@ enum OCIO_GPUTextureSlots { TEXTURE_SLOT_LUTS_OFFSET = 3, }; -/* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ -struct OCIO_GPUCurveMappingParameters { - float curve_mapping_mintable[4]; - float curve_mapping_range[4]; - float curve_mapping_ext_in_x[4]; - float curve_mapping_ext_in_y[4]; - float curve_mapping_ext_out_x[4]; - float curve_mapping_ext_out_y[4]; - float curve_mapping_first_x[4]; - float curve_mapping_first_y[4]; - float curve_mapping_last_x[4]; - float curve_mapping_last_y[4]; - float curve_mapping_black[4]; - float curve_mapping_bwmul[4]; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; - int _pad[2]; - /** WARNING: Needs to be 16byte aligned. Used as UBO data. */ +enum OCIO_GPUUniformBufSlots { + UNIFORMBUF_SLOT_DISPLAY = 0, + UNIFORMBUF_SLOT_CURVEMAP = 1, + UNIFORMBUF_SLOT_LUTS = 2, }; struct OCIO_GPUShader { /* GPU shader. */ struct GPUShader *shader = nullptr; - /** Uniform locations. */ - int scale_loc = 0; - int exponent_loc = 0; - int dither_loc = 0; - int overlay_loc = 0; - int predivide_loc = 0; - int ubo_bind = 0; + /** Uniform parameters. */ + OCIO_GPUParameters parameters = {}; + GPUUniformBuf *parameters_buffer = nullptr; /* Destructor. */ ~OCIO_GPUShader() @@ -109,6 +87,9 @@ struct OCIO_GPUShader { if (shader) { GPU_shader_free(shader); } + if (parameters_buffer) { + GPU_uniformbuf_free(parameters_buffer); + } } }; @@ -131,6 +112,7 @@ struct OCIO_GPUTextures { /* Uniforms */ std::vector uniforms; + GPUUniformBuf *uniforms_buffer = nullptr; /* Destructor. */ ~OCIO_GPUTextures() @@ -141,6 +123,9 @@ struct OCIO_GPUTextures { if (dummy) { GPU_texture_free(dummy); } + if (uniforms_buffer) { + GPU_uniformbuf_free(uniforms_buffer); + } } }; @@ -193,97 +178,134 @@ static bool createGPUShader(OCIO_GPUShader &shader, const GpuShaderDescRcPtr &shaderdesc_to_display, const bool use_curve_mapping) { - std::ostringstream os; - { - /* Fragment shader */ + using namespace blender::gpu::shader; - /* Work around OpenColorIO not supporting latest GLSL yet. */ - os << "#define texture2D texture\n"; - os << "#define texture3D texture\n"; + std::string source; + source += shaderdesc_to_scene_linear->getShaderText(); + source += "\n"; + source += shaderdesc_to_display->getShaderText(); + source += "\n"; - if (use_curve_mapping) { - os << "#define USE_CURVE_MAPPING\n"; + { + /* Replace all uniform declarations by a comment. + * This avoids double declarations from the backend. */ + size_t index = 0; + while (true) { + index = source.find("uniform ", index); + if (index == -1) { + break; + } + source.replace(index, 2, "//"); + index += 2; } - - os << shaderdesc_to_scene_linear->getShaderText() << "\n"; - os << shaderdesc_to_display->getShaderText() << "\n"; - - os << datatoc_gpu_shader_display_transform_glsl; } - shader.shader = GPU_shader_create(datatoc_gpu_shader_display_transform_vertex_glsl, - os.str().c_str(), - nullptr, - nullptr, - nullptr, - "OCIOShader"); - - if (shader.shader == nullptr) { - return false; - } - - shader.scale_loc = GPU_shader_get_uniform(shader.shader, "scale"); - shader.exponent_loc = GPU_shader_get_uniform(shader.shader, "exponent"); - shader.dither_loc = GPU_shader_get_uniform(shader.shader, "dither"); - shader.overlay_loc = GPU_shader_get_uniform(shader.shader, "overlay"); - shader.predivide_loc = GPU_shader_get_uniform(shader.shader, "predivide"); - shader.ubo_bind = GPU_shader_get_uniform_block_binding(shader.shader, - "OCIO_GPUCurveMappingParameters"); - - GPU_shader_bind(shader.shader); - - /* Set texture bind point uniform once. This is saved by the shader. */ - GPUShader *sh = shader.shader; - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "image_texture"), TEXTURE_SLOT_IMAGE); - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "overlay_texture"), TEXTURE_SLOT_OVERLAY); + StageInterfaceInfo iface("OCIO_Interface", ""); + iface.smooth(Type::VEC2, "texCoord_interp"); + + ShaderCreateInfo info("OCIO_Display"); + /* Work around OpenColorIO not supporting latest GLSL yet. */ + info.define("texture2D", "texture"); + info.define("texture3D", "texture"); + info.typedef_source("ocio_shader_shared.hh"); + info.sampler(TEXTURE_SLOT_IMAGE, ImageType::FLOAT_2D, "image_texture"); + info.sampler(TEXTURE_SLOT_OVERLAY, ImageType::FLOAT_2D, "overlay_texture"); + info.uniform_buf(UNIFORMBUF_SLOT_DISPLAY, "OCIO_GPUParameters", "parameters"); + info.push_constant(Type::MAT4, "ModelViewProjectionMatrix"); + info.vertex_in(0, Type::VEC2, "pos"); + info.vertex_in(1, Type::VEC2, "texCoord"); + info.vertex_out(iface); + info.fragment_out(0, Type::VEC4, "fragColor"); + info.vertex_source("gpu_shader_display_transform_vert.glsl"); + info.fragment_source("gpu_shader_display_transform_frag.glsl"); + info.fragment_source_generated = source; if (use_curve_mapping) { - GPU_shader_uniform_int( - sh, GPU_shader_get_uniform(sh, "curve_mapping_texture"), TEXTURE_SLOT_CURVE_MAPPING); + info.define("USE_CURVE_MAPPING"); + info.uniform_buf(UNIFORMBUF_SLOT_CURVEMAP, "OCIO_GPUCurveMappingParameters", "curve_mapping"); + info.sampler(TEXTURE_SLOT_CURVE_MAPPING, ImageType::FLOAT_1D, "curve_mapping_texture"); } /* Set LUT textures. */ - for (int i = 0; i < textures.luts.size(); i++) { - GPU_shader_uniform_int(sh, - GPU_shader_get_uniform(sh, textures.luts[i].sampler_name.c_str()), - TEXTURE_SLOT_LUTS_OFFSET + i); - } + int slot = TEXTURE_SLOT_LUTS_OFFSET; + for (OCIO_GPULutTexture &texture : textures.luts) { + ImageType type = GPU_texture_dimensions(texture.texture) == 2 ? ImageType::FLOAT_2D : + ImageType::FLOAT_3D; + info.sampler(slot++, type, texture.sampler_name.c_str()); + } + + /* Set LUT uniforms. */ + if (!textures.uniforms.empty()) { + /* NOTE: For simplicity, we pad everything to size of vec4 avoiding sorting and alignment + * issues. It is unlikely that this becomes a real issue. */ + size_t ubo_size = textures.uniforms.size() * sizeof(float) * 4; + void *ubo_data_buf = malloc(ubo_size); + + uint32_t *ubo_data = reinterpret_cast(ubo_data_buf); + + std::stringstream ss; + ss << "struct OCIO_GPULutParameters {\n"; + + int index = 0; + for (OCIO_GPUUniform &uniform : textures.uniforms) { + index += 1; + const GpuShaderDesc::UniformData &data = uniform.data; + const char *name = uniform.name.c_str(); + char prefix = ' '; + int vec_len; + switch (data.m_type) { + case UNIFORM_DOUBLE: { + vec_len = 1; + float value = float(data.m_getDouble()); + memcpy(ubo_data, &value, sizeof(float)); + break; + } + case UNIFORM_BOOL: { + prefix = 'b'; + vec_len = 1; + int value = int(data.m_getBool()); + memcpy(ubo_data, &value, sizeof(int)); + break; + } + case UNIFORM_FLOAT3: + vec_len = 3; + memcpy(ubo_data, data.m_getFloat3().data(), sizeof(float) * 3); + break; + case UNIFORM_VECTOR_FLOAT: + vec_len = data.m_vectorFloat.m_getSize(); + memcpy(ubo_data, data.m_vectorFloat.m_getVector(), sizeof(float) * vec_len); + break; + case UNIFORM_VECTOR_INT: + prefix = 'i'; + vec_len = data.m_vectorInt.m_getSize(); + memcpy(ubo_data, data.m_vectorInt.m_getVector(), sizeof(int) * vec_len); + break; + default: + continue; + } + /* Align every member to 16bytes. */ + ubo_data += 4; + /* Use a generic variable name because some GLSL compilers can interpret the preprocessor + * define as recursive. */ + ss << " " << prefix << "vec4 var" << index << ";\n"; + /* Use a define to keep the generated code working. */ + blender::StringRef suffix = blender::StringRefNull("xyzw").substr(0, vec_len); + ss << "#define " << name << " lut_parameters.var" << index << "." << suffix << "\n"; + } + ss << "};\n"; + info.typedef_source_generated = ss.str(); - /* Set uniforms. */ - for (OCIO_GPUUniform &uniform : textures.uniforms) { - const GpuShaderDesc::UniformData &data = uniform.data; - const char *name = uniform.name.c_str(); + info.uniform_buf(UNIFORMBUF_SLOT_LUTS, "OCIO_GPULutParameters", "lut_parameters"); - if (data.m_getDouble) { - GPU_shader_uniform_1f(sh, name, (float)data.m_getDouble()); - } - else if (data.m_getBool) { - GPU_shader_uniform_1f(sh, name, (float)(data.m_getBool() ? 1.0f : 0.0f)); - } - else if (data.m_getFloat3) { - GPU_shader_uniform_3f(sh, - name, - (float)data.m_getFloat3()[0], - (float)data.m_getFloat3()[1], - (float)data.m_getFloat3()[2]); - } - else if (data.m_vectorFloat.m_getSize && data.m_vectorFloat.m_getVector) { - GPU_shader_uniform_vector(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorFloat.m_getSize(), - 1, - (float *)data.m_vectorFloat.m_getVector()); - } - else if (data.m_vectorInt.m_getSize && data.m_vectorInt.m_getVector) { - GPU_shader_uniform_vector_int(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorInt.m_getSize(), - 1, - (int *)data.m_vectorInt.m_getVector()); - } + textures.uniforms_buffer = GPU_uniformbuf_create_ex( + ubo_size, ubo_data_buf, "OCIO_LutParameters"); + + free(ubo_data_buf); } - return true; + shader.shader = GPU_shader_create_from_info(reinterpret_cast(&info)); + + return (shader.shader != nullptr); } /** \} */ @@ -466,27 +488,65 @@ static void updateGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, /* Update uniforms. */ OCIO_GPUCurveMappingParameters data; for (int i = 0; i < 4; i++) { - data.curve_mapping_range[i] = curve_mapping_settings->range[i]; - data.curve_mapping_mintable[i] = curve_mapping_settings->mintable[i]; - data.curve_mapping_ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; - data.curve_mapping_ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; - data.curve_mapping_ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; - data.curve_mapping_ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; - data.curve_mapping_first_x[i] = curve_mapping_settings->first_x[i]; - data.curve_mapping_first_y[i] = curve_mapping_settings->first_y[i]; - data.curve_mapping_last_x[i] = curve_mapping_settings->last_x[i]; - data.curve_mapping_last_y[i] = curve_mapping_settings->last_y[i]; + data.range[i] = curve_mapping_settings->range[i]; + data.mintable[i] = curve_mapping_settings->mintable[i]; + data.ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; + data.ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; + data.ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; + data.ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; + data.first_x[i] = curve_mapping_settings->first_x[i]; + data.first_y[i] = curve_mapping_settings->first_y[i]; + data.last_x[i] = curve_mapping_settings->last_x[i]; + data.last_y[i] = curve_mapping_settings->last_y[i]; } for (int i = 0; i < 3; i++) { - data.curve_mapping_black[i] = curve_mapping_settings->black[i]; - data.curve_mapping_bwmul[i] = curve_mapping_settings->bwmul[i]; + data.black[i] = curve_mapping_settings->black[i]; + data.bwmul[i] = curve_mapping_settings->bwmul[i]; } - data.curve_mapping_lut_size = curve_mapping_settings->lut_size; - data.curve_mapping_use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; + data.lut_size = curve_mapping_settings->lut_size; + data.use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; GPU_uniformbuf_update(curvemap.buffer, &data); } +static void updateGPUDisplayParameters(OCIO_GPUShader &shader, + float scale, + float exponent, + float dither, + bool use_predivide, + bool use_overlay) +{ + bool do_update = false; + if (shader.parameters_buffer == nullptr) { + shader.parameters_buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUParameters)); + do_update = true; + } + OCIO_GPUParameters &data = shader.parameters; + if (data.scale != scale) { + data.scale = scale; + do_update = true; + } + if (data.exponent != exponent) { + data.exponent = exponent; + do_update = true; + } + if (data.dither != dither) { + data.dither = dither; + do_update = true; + } + if (data.use_predivide != use_predivide) { + data.use_predivide = use_predivide; + do_update = true; + } + if (data.use_overlay != use_overlay) { + data.use_overlay = use_overlay; + do_update = true; + } + if (do_update) { + GPU_uniformbuf_update(shader.parameters_buffer, &data); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -637,7 +697,7 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, /* Update and bind curve mapping data. */ if (curve_mapping_settings) { updateGPUCurveMapping(curvemap, curve_mapping_settings); - GPU_uniformbuf_bind(curvemap.buffer, shader.ubo_bind); + GPU_uniformbuf_bind(curvemap.buffer, UNIFORMBUF_SLOT_CURVEMAP); GPU_texture_bind(curvemap.texture, TEXTURE_SLOT_CURVE_MAPPING); } @@ -651,17 +711,16 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, GPU_texture_bind(textures.luts[i].texture, TEXTURE_SLOT_LUTS_OFFSET + i); } + if (textures.uniforms_buffer) { + GPU_uniformbuf_bind(textures.uniforms_buffer, UNIFORMBUF_SLOT_LUTS); + } + + updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay); + GPU_uniformbuf_bind(shader.parameters_buffer, UNIFORMBUF_SLOT_DISPLAY); + /* TODO(fclem): remove remains of IMM. */ immBindShader(shader.shader); - /* Bind Shader and set uniforms. */ - // GPU_shader_bind(shader.shader); - GPU_shader_uniform_float(shader.shader, shader.scale_loc, scale); - GPU_shader_uniform_float(shader.shader, shader.exponent_loc, exponent); - GPU_shader_uniform_float(shader.shader, shader.dither_loc, dither); - GPU_shader_uniform_int(shader.shader, shader.overlay_loc, use_overlay); - GPU_shader_uniform_int(shader.shader, shader.predivide_loc, use_predivide); - return true; } diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh new file mode 100644 index 00000000000..c7045217196 --- /dev/null +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#ifndef GPU_SHADER +# include "GPU_shader_shared_utils.h" +#endif + +struct OCIO_GPUCurveMappingParameters { + /* Curve mapping parameters + * + * See documentation for OCIO_CurveMappingSettings to get fields descriptions. + * (this ones pretty much copies stuff from C structure.) + */ + float4 mintable; + float4 range; + float4 ext_in_x; + float4 ext_in_y; + float4 ext_out_x; + float4 ext_out_y; + float4 first_x; + float4 first_y; + float4 last_x; + float4 last_y; + float4 black; + float4 bwmul; + int lut_size; + int use_extend_extrapolate; + int _pad0; + int _pad1; +}; + +struct OCIO_GPUParameters { + float dither; + float scale; + float exponent; + bool1 use_predivide; + bool1 use_overlay; + int _pad0; + int _pad1; + int _pad2; +}; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 2b6b28bd649..bb0b8640767 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -469,12 +469,21 @@ if(WITH_IMAGE_DDS) add_definitions(-DWITH_DDS) endif() +if(WITH_OPENCOLORIO) + add_definitions(-DWITH_OCIO) +endif() + blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") target_link_libraries(bf_gpu PUBLIC bf_draw_shaders bf_gpu_shaders ) +if(WITH_OPENCOLORIO) + target_link_libraries(bf_gpu PUBLIC bf_ocio_shaders) +endif() + + if(CXX_WARN_NO_SUGGEST_OVERRIDE) target_compile_options(bf_gpu PRIVATE $<$:-Wsuggest-override>) endif() diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 5e03f7d0767..15ca7d0c3b1 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -37,6 +37,9 @@ extern "C" { #define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[]; #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE } @@ -360,6 +363,9 @@ void gpu_shader_dependency_init() g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc)); #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE int errors = 0; -- cgit v1.2.3 From eba3ffc31a88a53d12868d26202908c4a3435966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 18 Feb 2022 21:33:04 +0100 Subject: GL: Fix possible shift by -1 This can happen when the attribute has been optimized out by the compiler. --- source/blender/gpu/opengl/gl_vertex_array.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/opengl/gl_vertex_array.cc b/source/blender/gpu/opengl/gl_vertex_array.cc index 282ede5ba9b..d8d301b0f75 100644 --- a/source/blender/gpu/opengl/gl_vertex_array.cc +++ b/source/blender/gpu/opengl/gl_vertex_array.cc @@ -70,7 +70,7 @@ static uint16_t vbo_bind(const ShaderInterface *interface, const char *name = GPU_vertformat_attr_name_get(format, a, n_idx); const ShaderInput *input = interface->attr_get(name); - if (input == nullptr) { + if (input == nullptr || input->location == -1) { continue; } -- cgit v1.2.3 From 3cebfadb27eb4056d28211708158f9ad8a7459b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 18 Feb 2022 21:52:29 +0100 Subject: OCIO: Fix gpu include file Was the cause by ef0e21f0ae71d9ec4ba3cdf6f6a16c9575459ced --- intern/opencolorio/ocio_shader_shared.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh index c7045217196..86a710acf5a 100644 --- a/intern/opencolorio/ocio_shader_shared.hh +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -2,7 +2,7 @@ * Copyright 2022 Blender Foundation. All rights reserved. */ #ifndef GPU_SHADER -# include "GPU_shader_shared_utils.h" +# include "gpu_shader_shared_utils.h" #endif struct OCIO_GPUCurveMappingParameters { -- cgit v1.2.3 From 32660382f54819b5b21d3279ca0540d2df17b252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 18 Feb 2022 22:28:01 +0100 Subject: Revert "OCIO: Fix gpu include file" This reverts commit 3cebfadb27eb4056d28211708158f9ad8a7459b7. --- intern/opencolorio/ocio_shader_shared.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh index 86a710acf5a..c7045217196 100644 --- a/intern/opencolorio/ocio_shader_shared.hh +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -2,7 +2,7 @@ * Copyright 2022 Blender Foundation. All rights reserved. */ #ifndef GPU_SHADER -# include "gpu_shader_shared_utils.h" +# include "GPU_shader_shared_utils.h" #endif struct OCIO_GPUCurveMappingParameters { -- cgit v1.2.3 From 93cc892470109ab505f00f0b9d2bddb1a0a8179d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 18 Feb 2022 22:28:05 +0100 Subject: Revert "OCIO: Port shader creation logic to use GPUShaderCreateInfo" This reverts commit 7f7c614ecddbcb66de0bff1657366970dede99be. --- intern/opencolorio/CMakeLists.txt | 36 +-- .../opencolorio/gpu_shader_display_transform.glsl | 210 ++++++++++++++ .../gpu_shader_display_transform_frag.glsl | 195 ------------- .../gpu_shader_display_transform_vert.glsl | 6 - .../gpu_shader_display_transform_vertex.glsl | 12 + intern/opencolorio/ocio_impl_glsl.cc | 323 +++++++++------------ intern/opencolorio/ocio_shader_shared.hh | 41 --- source/blender/gpu/CMakeLists.txt | 9 - source/blender/gpu/intern/gpu_shader_dependency.cc | 6 - 9 files changed, 356 insertions(+), 482 deletions(-) create mode 100644 intern/opencolorio/gpu_shader_display_transform.glsl delete mode 100644 intern/opencolorio/gpu_shader_display_transform_frag.glsl delete mode 100644 intern/opencolorio/gpu_shader_display_transform_vert.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_vertex.glsl delete mode 100644 intern/opencolorio/ocio_shader_shared.hh diff --git a/intern/opencolorio/CMakeLists.txt b/intern/opencolorio/CMakeLists.txt index a2ef0d46a40..0b46ae471d2 100644 --- a/intern/opencolorio/CMakeLists.txt +++ b/intern/opencolorio/CMakeLists.txt @@ -24,7 +24,6 @@ set(INC ../guardedalloc ../../source/blender/blenlib ../../source/blender/gpu - ../../source/blender/gpu/intern ../../source/blender/makesdna ) @@ -38,7 +37,6 @@ set(SRC ocio_capi.h ocio_impl.h - ocio_shader_shared.hh ) set(LIB @@ -75,38 +73,8 @@ if(WITH_OPENCOLORIO) ) endif() - set(GLSL_SRC - gpu_shader_display_transform_vert.glsl - gpu_shader_display_transform_frag.glsl - - ocio_shader_shared.hh - ) - - set(GLSL_C) - foreach(GLSL_FILE ${GLSL_SRC}) - data_to_c_simple(${GLSL_FILE} GLSL_C) - endforeach() - - blender_add_lib(bf_ocio_shaders "${GLSL_C}" "" "" "") - - list(APPEND LIB - bf_ocio_shaders - ) - - set(GLSL_SOURCE_CONTENT "") - foreach(GLSL_FILE ${GLSL_SRC}) - get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME) - string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME}) - string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(datatoc_${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n") - endforeach() - - set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_ocio_source_list.h") - file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}") - list(APPEND SRC ${glsl_source_list_file}) - list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR}) - - target_include_directories(bf_ocio_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) - + data_to_c_simple(gpu_shader_display_transform.glsl SRC) + data_to_c_simple(gpu_shader_display_transform_vertex.glsl SRC) endif() diff --git a/intern/opencolorio/gpu_shader_display_transform.glsl b/intern/opencolorio/gpu_shader_display_transform.glsl new file mode 100644 index 00000000000..f5a7a7bf45d --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform.glsl @@ -0,0 +1,210 @@ +/* Blender OpenColorIO implementation */ + +uniform sampler2D image_texture; +uniform sampler2D overlay_texture; + +uniform float dither; +uniform float scale; +uniform float exponent; +uniform bool predivide; +uniform bool overlay; + +#ifdef USE_CURVE_MAPPING +uniform sampler1D curve_mapping_texture; + +layout(std140) uniform OCIO_GPUCurveMappingParameters +{ + /* Curve mapping parameters + * + * See documentation for OCIO_CurveMappingSettings to get fields descriptions. + * (this ones pretty much copies stuff from C structure.) + */ + vec4 curve_mapping_mintable; + vec4 curve_mapping_range; + vec4 curve_mapping_ext_in_x; + vec4 curve_mapping_ext_in_y; + vec4 curve_mapping_ext_out_x; + vec4 curve_mapping_ext_out_y; + vec4 curve_mapping_first_x; + vec4 curve_mapping_first_y; + vec4 curve_mapping_last_x; + vec4 curve_mapping_last_y; + vec4 curve_mapping_black; + vec4 curve_mapping_bwmul; + int curve_mapping_lut_size; + int curve_mapping_use_extend_extrapolate; +}; + +float read_curve_mapping(int table, int index) +{ + return texelFetch(curve_mapping_texture, index, 0)[table]; +} + +float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) +{ + if (x <= first[0]) { + if (curve_mapping_use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return first[1]; + } + else { + float fac = (curve_mapping_ext_in_x[table] != 0.0) ? + ((x - first[0]) / curve_mapping_ext_in_x[table]) : + 10000.0; + return first[1] + curve_mapping_ext_in_y[table] * fac; + } + } + else if (x >= last[0]) { + if (curve_mapping_use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return last[1]; + } + else { + float fac = (curve_mapping_ext_out_x[table] != 0.0) ? + ((x - last[0]) / curve_mapping_ext_out_x[table]) : + -10000.0; + return last[1] + curve_mapping_ext_out_y[table] * fac; + } + } + return 0.0; +} + +float curvemap_evaluateF(int table, float value) +{ + float mintable_ = curve_mapping_mintable[table]; + float range = curve_mapping_range[table]; + float mintable = 0.0; + int CM_TABLE = curve_mapping_lut_size - 1; + + float fi; + int i; + + /* index in table */ + fi = (value - mintable) * range; + i = int(fi); + + /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ + if (fi < 0.0 || fi > float(CM_TABLE)) { + return curvemap_calc_extend(table, + value, + vec2(curve_mapping_first_x[table], curve_mapping_first_y[table]), + vec2(curve_mapping_last_x[table], curve_mapping_last_y[table])); + } + else { + if (i < 0) { + return read_curve_mapping(table, 0); + } + if (i >= CM_TABLE) { + return read_curve_mapping(table, CM_TABLE); + } + fi = fi - float(i); + float cm1 = read_curve_mapping(table, i); + float cm2 = read_curve_mapping(table, i + 1); + return mix(cm1, cm2, fi); + } +} + +vec4 curvemapping_evaluate_premulRGBF(vec4 col) +{ + col.rgb = (col.rgb - curve_mapping_black.rgb) * curve_mapping_bwmul.rgb; + + vec4 result; + result.r = curvemap_evaluateF(0, col.r); + result.g = curvemap_evaluateF(1, col.g); + result.b = curvemap_evaluateF(2, col.b); + result.a = col.a; + return result; +} +#endif /* USE_CURVE_MAPPING */ + +/* Using a triangle distribution which gives a more final uniform noise. + * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ +/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ +/* Return triangle noise in [-1..1[ range */ +float dither_random_value(vec2 co) +{ + /* Original code from https://www.shadertoy.com/view/4t2SDh */ + /* Uniform noise in [0..1[ range */ + float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); + /* Convert uniform distribution into triangle-shaped distribution. */ + float orig = nrnd0 * 2.0 - 1.0; + nrnd0 = orig * inversesqrt(abs(orig)); + nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ + return nrnd0 - sign(orig); +} + +vec2 round_to_pixel(sampler2D tex, vec2 uv) +{ + vec2 size = textureSize(tex, 0); + return vec2(ivec2(uv * size)) / size; +} + +vec4 apply_dither(vec4 col, vec2 uv) +{ + col.rgb += dither_random_value(uv) * 0.0033 * dither; + return col; +} + +vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay, vec2 noise_uv) +{ +#ifdef USE_CURVE_MAPPING + col = curvemapping_evaluate_premulRGBF(col); +#endif + + if (predivide) { + if (col.a > 0.0 && col.a < 1.0) { + col.rgb *= 1.0 / col.a; + } + } + + /* NOTE: This is true we only do de-premul here and NO premul + * and the reason is simple -- opengl is always configured + * for straight alpha at this moment + */ + + /* Convert to scene linear (usually a no-op). */ + col = OCIO_to_scene_linear(col); + + /* Apply exposure in scene linear. */ + col.rgb *= scale; + + /* Convert to display space. */ + col = OCIO_to_display(col); + + /* Blend with overlay in UI colorspace. + * + * UI colorspace here refers to the display linear color space, + * i.e: The linear color space w.r.t. display chromaticity and radiometry. + * We separate the colormanagement process into two steps to be able to + * merge UI using alpha blending in the correct color space. */ + if (overlay) { + col.rgb = pow(col.rgb, vec3(exponent * 2.2)); + col = clamp(col, 0.0, 1.0); + col *= 1.0 - col_overlay.a; + col += col_overlay; /* Assumed unassociated alpha. */ + col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); + } + else { + col.rgb = pow(col.rgb, vec3(exponent)); + } + + if (dither > 0.0) { + col = apply_dither(col, noise_uv); + } + + return col; +} + +/* ------------------------------------------------------------------------ */ + +in vec2 texCoord_interp; +out vec4 fragColor; + +void main() +{ + vec4 col = texture(image_texture, texCoord_interp.st); + vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); + vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); + + fragColor = OCIO_ProcessColor(col, col_overlay, noise_uv); +} diff --git a/intern/opencolorio/gpu_shader_display_transform_frag.glsl b/intern/opencolorio/gpu_shader_display_transform_frag.glsl deleted file mode 100644 index 3c2352c13ba..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform_frag.glsl +++ /dev/null @@ -1,195 +0,0 @@ -/* Blender OpenColorIO implementation */ - -/* -------------------------------------------------------------------- */ -/** \name Curve Mapping Implementation - * \{ */ - -#ifdef USE_CURVE_MAPPING - -float read_curve_mapping(int table, int index) -{ - return texelFetch(curve_mapping_texture, index, 0)[table]; -} - -float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) -{ - if (x <= first[0]) { - if (curve_mapping.use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return first[1]; - } - else { - float fac = (curve_mapping.ext_in_x[table] != 0.0) ? - ((x - first[0]) / curve_mapping.ext_in_x[table]) : - 10000.0; - return first[1] + curve_mapping.ext_in_y[table] * fac; - } - } - else if (x >= last[0]) { - if (curve_mapping.use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return last[1]; - } - else { - float fac = (curve_mapping.ext_out_x[table] != 0.0) ? - ((x - last[0]) / curve_mapping.ext_out_x[table]) : - -10000.0; - return last[1] + curve_mapping.ext_out_y[table] * fac; - } - } - return 0.0; -} - -float curvemap_evaluateF(int table, float value) -{ - float mintable_ = curve_mapping.mintable[table]; - float range = curve_mapping.range[table]; - float mintable = 0.0; - int CM_TABLE = curve_mapping.lut_size - 1; - - float fi; - int i; - - /* index in table */ - fi = (value - mintable) * range; - i = int(fi); - - /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ - if (fi < 0.0 || fi > float(CM_TABLE)) { - return curvemap_calc_extend(table, - value, - vec2(curve_mapping.first_x[table], curve_mapping.first_y[table]), - vec2(curve_mapping.last_x[table], curve_mapping.last_y[table])); - } - else { - if (i < 0) { - return read_curve_mapping(table, 0); - } - if (i >= CM_TABLE) { - return read_curve_mapping(table, CM_TABLE); - } - fi = fi - float(i); - float cm1 = read_curve_mapping(table, i); - float cm2 = read_curve_mapping(table, i + 1); - return mix(cm1, cm2, fi); - } -} - -vec4 curvemapping_evaluate_premulRGBF(vec4 col) -{ - col.rgb = (col.rgb - curve_mapping.black.rgb) * curve_mapping.bwmul.rgb; - - vec4 result; - result.r = curvemap_evaluateF(0, col.r); - result.g = curvemap_evaluateF(1, col.g); - result.b = curvemap_evaluateF(2, col.b); - result.a = col.a; - return result; -} - -#endif /* USE_CURVE_MAPPING */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Dithering - * \{ */ - -/* Using a triangle distribution which gives a more final uniform noise. - * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ -/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ -/* Return triangle noise in [-1..1[ range */ -float dither_random_value(vec2 co) -{ - /* Original code from https://www.shadertoy.com/view/4t2SDh */ - /* Uniform noise in [0..1[ range */ - float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); - /* Convert uniform distribution into triangle-shaped distribution. */ - float orig = nrnd0 * 2.0 - 1.0; - nrnd0 = orig * inversesqrt(abs(orig)); - nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ - return nrnd0 - sign(orig); -} - -vec2 round_to_pixel(sampler2D tex, vec2 uv) -{ - vec2 size = vec2(textureSize(tex, 0)); - return floor(uv * size) / size; -} - -vec4 apply_dither(vec4 col, vec2 uv) -{ - col.rgb += dither_random_value(uv) * 0.0033 * parameters.dither; - return col; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Main Processing - * \{ */ - -/* Prototypes: Implementation is generaterd and defined after. */ -vec4 OCIO_to_scene_linear(vec4 pixel); -vec4 OCIO_to_display(vec4 pixel); - -vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay) -{ -#ifdef USE_CURVE_MAPPING - col = curvemapping_evaluate_premulRGBF(col); -#endif - - if (parameters.use_predivide) { - if (col.a > 0.0 && col.a < 1.0) { - col.rgb *= 1.0 / col.a; - } - } - - /* NOTE: This is true we only do de-premul here and NO premul - * and the reason is simple -- opengl is always configured - * for straight alpha at this moment - */ - - /* Convert to scene linear (usually a no-op). */ - col = OCIO_to_scene_linear(col); - - /* Apply exposure in scene linear. */ - col.rgb *= parameters.scale; - - /* Convert to display space. */ - col = OCIO_to_display(col); - - /* Blend with overlay in UI colorspace. - * - * UI colorspace here refers to the display linear color space, - * i.e: The linear color space w.r.t. display chromaticity and radiometry. - * We separate the colormanagement process into two steps to be able to - * merge UI using alpha blending in the correct color space. */ - if (parameters.use_overlay) { - col.rgb = pow(col.rgb, vec3(parameters.exponent * 2.2)); - col = clamp(col, 0.0, 1.0); - col *= 1.0 - col_overlay.a; - col += col_overlay; /* Assumed unassociated alpha. */ - col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); - } - else { - col.rgb = pow(col.rgb, vec3(parameters.exponent)); - } - - if (parameters.dither > 0.0) { - vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); - col = apply_dither(col, noise_uv); - } - - return col; -} - -/** \} */ - -void main() -{ - vec4 col = texture(image_texture, texCoord_interp.st); - vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); - - fragColor = OCIO_ProcessColor(col, col_overlay); -} diff --git a/intern/opencolorio/gpu_shader_display_transform_vert.glsl b/intern/opencolorio/gpu_shader_display_transform_vert.glsl deleted file mode 100644 index 06788be11de..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform_vert.glsl +++ /dev/null @@ -1,6 +0,0 @@ - -void main() -{ - gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); - texCoord_interp = texCoord; -} diff --git a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl b/intern/opencolorio/gpu_shader_display_transform_vertex.glsl new file mode 100644 index 00000000000..8cf9628b06b --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_vertex.glsl @@ -0,0 +1,12 @@ + +uniform mat4 ModelViewProjectionMatrix; + +in vec2 texCoord; +in vec2 pos; +out vec2 texCoord_interp; + +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + texCoord_interp = texCoord; +} diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index e4840eb05f6..fc45ec4e301 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -49,14 +49,14 @@ #include "GPU_shader.h" #include "GPU_uniform_buffer.h" -#include "gpu_shader_create_info.hh" - using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "ocio_impl.h" -#include "ocio_shader_shared.hh" + +extern "C" char datatoc_gpu_shader_display_transform_glsl[]; +extern "C" char datatoc_gpu_shader_display_transform_vertex_glsl[]; /* **** OpenGL drawing routines using GLSL for color space transform ***** */ @@ -67,19 +67,41 @@ enum OCIO_GPUTextureSlots { TEXTURE_SLOT_LUTS_OFFSET = 3, }; -enum OCIO_GPUUniformBufSlots { - UNIFORMBUF_SLOT_DISPLAY = 0, - UNIFORMBUF_SLOT_CURVEMAP = 1, - UNIFORMBUF_SLOT_LUTS = 2, +/* Curve mapping parameters + * + * See documentation for OCIO_CurveMappingSettings to get fields descriptions. + * (this ones pretty much copies stuff from C structure.) + */ +struct OCIO_GPUCurveMappingParameters { + float curve_mapping_mintable[4]; + float curve_mapping_range[4]; + float curve_mapping_ext_in_x[4]; + float curve_mapping_ext_in_y[4]; + float curve_mapping_ext_out_x[4]; + float curve_mapping_ext_out_y[4]; + float curve_mapping_first_x[4]; + float curve_mapping_first_y[4]; + float curve_mapping_last_x[4]; + float curve_mapping_last_y[4]; + float curve_mapping_black[4]; + float curve_mapping_bwmul[4]; + int curve_mapping_lut_size; + int curve_mapping_use_extend_extrapolate; + int _pad[2]; + /** WARNING: Needs to be 16byte aligned. Used as UBO data. */ }; struct OCIO_GPUShader { /* GPU shader. */ struct GPUShader *shader = nullptr; - /** Uniform parameters. */ - OCIO_GPUParameters parameters = {}; - GPUUniformBuf *parameters_buffer = nullptr; + /** Uniform locations. */ + int scale_loc = 0; + int exponent_loc = 0; + int dither_loc = 0; + int overlay_loc = 0; + int predivide_loc = 0; + int ubo_bind = 0; /* Destructor. */ ~OCIO_GPUShader() @@ -87,9 +109,6 @@ struct OCIO_GPUShader { if (shader) { GPU_shader_free(shader); } - if (parameters_buffer) { - GPU_uniformbuf_free(parameters_buffer); - } } }; @@ -112,7 +131,6 @@ struct OCIO_GPUTextures { /* Uniforms */ std::vector uniforms; - GPUUniformBuf *uniforms_buffer = nullptr; /* Destructor. */ ~OCIO_GPUTextures() @@ -123,9 +141,6 @@ struct OCIO_GPUTextures { if (dummy) { GPU_texture_free(dummy); } - if (uniforms_buffer) { - GPU_uniformbuf_free(uniforms_buffer); - } } }; @@ -178,134 +193,97 @@ static bool createGPUShader(OCIO_GPUShader &shader, const GpuShaderDescRcPtr &shaderdesc_to_display, const bool use_curve_mapping) { - using namespace blender::gpu::shader; + std::ostringstream os; + { + /* Fragment shader */ - std::string source; - source += shaderdesc_to_scene_linear->getShaderText(); - source += "\n"; - source += shaderdesc_to_display->getShaderText(); - source += "\n"; + /* Work around OpenColorIO not supporting latest GLSL yet. */ + os << "#define texture2D texture\n"; + os << "#define texture3D texture\n"; - { - /* Replace all uniform declarations by a comment. - * This avoids double declarations from the backend. */ - size_t index = 0; - while (true) { - index = source.find("uniform ", index); - if (index == -1) { - break; - } - source.replace(index, 2, "//"); - index += 2; + if (use_curve_mapping) { + os << "#define USE_CURVE_MAPPING\n"; } + + os << shaderdesc_to_scene_linear->getShaderText() << "\n"; + os << shaderdesc_to_display->getShaderText() << "\n"; + + os << datatoc_gpu_shader_display_transform_glsl; } - StageInterfaceInfo iface("OCIO_Interface", ""); - iface.smooth(Type::VEC2, "texCoord_interp"); - - ShaderCreateInfo info("OCIO_Display"); - /* Work around OpenColorIO not supporting latest GLSL yet. */ - info.define("texture2D", "texture"); - info.define("texture3D", "texture"); - info.typedef_source("ocio_shader_shared.hh"); - info.sampler(TEXTURE_SLOT_IMAGE, ImageType::FLOAT_2D, "image_texture"); - info.sampler(TEXTURE_SLOT_OVERLAY, ImageType::FLOAT_2D, "overlay_texture"); - info.uniform_buf(UNIFORMBUF_SLOT_DISPLAY, "OCIO_GPUParameters", "parameters"); - info.push_constant(Type::MAT4, "ModelViewProjectionMatrix"); - info.vertex_in(0, Type::VEC2, "pos"); - info.vertex_in(1, Type::VEC2, "texCoord"); - info.vertex_out(iface); - info.fragment_out(0, Type::VEC4, "fragColor"); - info.vertex_source("gpu_shader_display_transform_vert.glsl"); - info.fragment_source("gpu_shader_display_transform_frag.glsl"); - info.fragment_source_generated = source; + shader.shader = GPU_shader_create(datatoc_gpu_shader_display_transform_vertex_glsl, + os.str().c_str(), + nullptr, + nullptr, + nullptr, + "OCIOShader"); - if (use_curve_mapping) { - info.define("USE_CURVE_MAPPING"); - info.uniform_buf(UNIFORMBUF_SLOT_CURVEMAP, "OCIO_GPUCurveMappingParameters", "curve_mapping"); - info.sampler(TEXTURE_SLOT_CURVE_MAPPING, ImageType::FLOAT_1D, "curve_mapping_texture"); + if (shader.shader == nullptr) { + return false; } - /* Set LUT textures. */ - int slot = TEXTURE_SLOT_LUTS_OFFSET; - for (OCIO_GPULutTexture &texture : textures.luts) { - ImageType type = GPU_texture_dimensions(texture.texture) == 2 ? ImageType::FLOAT_2D : - ImageType::FLOAT_3D; - info.sampler(slot++, type, texture.sampler_name.c_str()); - } - - /* Set LUT uniforms. */ - if (!textures.uniforms.empty()) { - /* NOTE: For simplicity, we pad everything to size of vec4 avoiding sorting and alignment - * issues. It is unlikely that this becomes a real issue. */ - size_t ubo_size = textures.uniforms.size() * sizeof(float) * 4; - void *ubo_data_buf = malloc(ubo_size); - - uint32_t *ubo_data = reinterpret_cast(ubo_data_buf); - - std::stringstream ss; - ss << "struct OCIO_GPULutParameters {\n"; - - int index = 0; - for (OCIO_GPUUniform &uniform : textures.uniforms) { - index += 1; - const GpuShaderDesc::UniformData &data = uniform.data; - const char *name = uniform.name.c_str(); - char prefix = ' '; - int vec_len; - switch (data.m_type) { - case UNIFORM_DOUBLE: { - vec_len = 1; - float value = float(data.m_getDouble()); - memcpy(ubo_data, &value, sizeof(float)); - break; - } - case UNIFORM_BOOL: { - prefix = 'b'; - vec_len = 1; - int value = int(data.m_getBool()); - memcpy(ubo_data, &value, sizeof(int)); - break; - } - case UNIFORM_FLOAT3: - vec_len = 3; - memcpy(ubo_data, data.m_getFloat3().data(), sizeof(float) * 3); - break; - case UNIFORM_VECTOR_FLOAT: - vec_len = data.m_vectorFloat.m_getSize(); - memcpy(ubo_data, data.m_vectorFloat.m_getVector(), sizeof(float) * vec_len); - break; - case UNIFORM_VECTOR_INT: - prefix = 'i'; - vec_len = data.m_vectorInt.m_getSize(); - memcpy(ubo_data, data.m_vectorInt.m_getVector(), sizeof(int) * vec_len); - break; - default: - continue; - } - /* Align every member to 16bytes. */ - ubo_data += 4; - /* Use a generic variable name because some GLSL compilers can interpret the preprocessor - * define as recursive. */ - ss << " " << prefix << "vec4 var" << index << ";\n"; - /* Use a define to keep the generated code working. */ - blender::StringRef suffix = blender::StringRefNull("xyzw").substr(0, vec_len); - ss << "#define " << name << " lut_parameters.var" << index << "." << suffix << "\n"; - } - ss << "};\n"; - info.typedef_source_generated = ss.str(); + shader.scale_loc = GPU_shader_get_uniform(shader.shader, "scale"); + shader.exponent_loc = GPU_shader_get_uniform(shader.shader, "exponent"); + shader.dither_loc = GPU_shader_get_uniform(shader.shader, "dither"); + shader.overlay_loc = GPU_shader_get_uniform(shader.shader, "overlay"); + shader.predivide_loc = GPU_shader_get_uniform(shader.shader, "predivide"); + shader.ubo_bind = GPU_shader_get_uniform_block_binding(shader.shader, + "OCIO_GPUCurveMappingParameters"); - info.uniform_buf(UNIFORMBUF_SLOT_LUTS, "OCIO_GPULutParameters", "lut_parameters"); + GPU_shader_bind(shader.shader); - textures.uniforms_buffer = GPU_uniformbuf_create_ex( - ubo_size, ubo_data_buf, "OCIO_LutParameters"); + /* Set texture bind point uniform once. This is saved by the shader. */ + GPUShader *sh = shader.shader; + GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "image_texture"), TEXTURE_SLOT_IMAGE); + GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "overlay_texture"), TEXTURE_SLOT_OVERLAY); - free(ubo_data_buf); + if (use_curve_mapping) { + GPU_shader_uniform_int( + sh, GPU_shader_get_uniform(sh, "curve_mapping_texture"), TEXTURE_SLOT_CURVE_MAPPING); } - shader.shader = GPU_shader_create_from_info(reinterpret_cast(&info)); + /* Set LUT textures. */ + for (int i = 0; i < textures.luts.size(); i++) { + GPU_shader_uniform_int(sh, + GPU_shader_get_uniform(sh, textures.luts[i].sampler_name.c_str()), + TEXTURE_SLOT_LUTS_OFFSET + i); + } - return (shader.shader != nullptr); + /* Set uniforms. */ + for (OCIO_GPUUniform &uniform : textures.uniforms) { + const GpuShaderDesc::UniformData &data = uniform.data; + const char *name = uniform.name.c_str(); + + if (data.m_getDouble) { + GPU_shader_uniform_1f(sh, name, (float)data.m_getDouble()); + } + else if (data.m_getBool) { + GPU_shader_uniform_1f(sh, name, (float)(data.m_getBool() ? 1.0f : 0.0f)); + } + else if (data.m_getFloat3) { + GPU_shader_uniform_3f(sh, + name, + (float)data.m_getFloat3()[0], + (float)data.m_getFloat3()[1], + (float)data.m_getFloat3()[2]); + } + else if (data.m_vectorFloat.m_getSize && data.m_vectorFloat.m_getVector) { + GPU_shader_uniform_vector(sh, + GPU_shader_get_uniform(sh, name), + (int)data.m_vectorFloat.m_getSize(), + 1, + (float *)data.m_vectorFloat.m_getVector()); + } + else if (data.m_vectorInt.m_getSize && data.m_vectorInt.m_getVector) { + GPU_shader_uniform_vector_int(sh, + GPU_shader_get_uniform(sh, name), + (int)data.m_vectorInt.m_getSize(), + 1, + (int *)data.m_vectorInt.m_getVector()); + } + } + + return true; } /** \} */ @@ -488,65 +466,27 @@ static void updateGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, /* Update uniforms. */ OCIO_GPUCurveMappingParameters data; for (int i = 0; i < 4; i++) { - data.range[i] = curve_mapping_settings->range[i]; - data.mintable[i] = curve_mapping_settings->mintable[i]; - data.ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; - data.ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; - data.ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; - data.ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; - data.first_x[i] = curve_mapping_settings->first_x[i]; - data.first_y[i] = curve_mapping_settings->first_y[i]; - data.last_x[i] = curve_mapping_settings->last_x[i]; - data.last_y[i] = curve_mapping_settings->last_y[i]; + data.curve_mapping_range[i] = curve_mapping_settings->range[i]; + data.curve_mapping_mintable[i] = curve_mapping_settings->mintable[i]; + data.curve_mapping_ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; + data.curve_mapping_ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; + data.curve_mapping_ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; + data.curve_mapping_ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; + data.curve_mapping_first_x[i] = curve_mapping_settings->first_x[i]; + data.curve_mapping_first_y[i] = curve_mapping_settings->first_y[i]; + data.curve_mapping_last_x[i] = curve_mapping_settings->last_x[i]; + data.curve_mapping_last_y[i] = curve_mapping_settings->last_y[i]; } for (int i = 0; i < 3; i++) { - data.black[i] = curve_mapping_settings->black[i]; - data.bwmul[i] = curve_mapping_settings->bwmul[i]; + data.curve_mapping_black[i] = curve_mapping_settings->black[i]; + data.curve_mapping_bwmul[i] = curve_mapping_settings->bwmul[i]; } - data.lut_size = curve_mapping_settings->lut_size; - data.use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; + data.curve_mapping_lut_size = curve_mapping_settings->lut_size; + data.curve_mapping_use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; GPU_uniformbuf_update(curvemap.buffer, &data); } -static void updateGPUDisplayParameters(OCIO_GPUShader &shader, - float scale, - float exponent, - float dither, - bool use_predivide, - bool use_overlay) -{ - bool do_update = false; - if (shader.parameters_buffer == nullptr) { - shader.parameters_buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUParameters)); - do_update = true; - } - OCIO_GPUParameters &data = shader.parameters; - if (data.scale != scale) { - data.scale = scale; - do_update = true; - } - if (data.exponent != exponent) { - data.exponent = exponent; - do_update = true; - } - if (data.dither != dither) { - data.dither = dither; - do_update = true; - } - if (data.use_predivide != use_predivide) { - data.use_predivide = use_predivide; - do_update = true; - } - if (data.use_overlay != use_overlay) { - data.use_overlay = use_overlay; - do_update = true; - } - if (do_update) { - GPU_uniformbuf_update(shader.parameters_buffer, &data); - } -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -697,7 +637,7 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, /* Update and bind curve mapping data. */ if (curve_mapping_settings) { updateGPUCurveMapping(curvemap, curve_mapping_settings); - GPU_uniformbuf_bind(curvemap.buffer, UNIFORMBUF_SLOT_CURVEMAP); + GPU_uniformbuf_bind(curvemap.buffer, shader.ubo_bind); GPU_texture_bind(curvemap.texture, TEXTURE_SLOT_CURVE_MAPPING); } @@ -711,16 +651,17 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, GPU_texture_bind(textures.luts[i].texture, TEXTURE_SLOT_LUTS_OFFSET + i); } - if (textures.uniforms_buffer) { - GPU_uniformbuf_bind(textures.uniforms_buffer, UNIFORMBUF_SLOT_LUTS); - } - - updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay); - GPU_uniformbuf_bind(shader.parameters_buffer, UNIFORMBUF_SLOT_DISPLAY); - /* TODO(fclem): remove remains of IMM. */ immBindShader(shader.shader); + /* Bind Shader and set uniforms. */ + // GPU_shader_bind(shader.shader); + GPU_shader_uniform_float(shader.shader, shader.scale_loc, scale); + GPU_shader_uniform_float(shader.shader, shader.exponent_loc, exponent); + GPU_shader_uniform_float(shader.shader, shader.dither_loc, dither); + GPU_shader_uniform_int(shader.shader, shader.overlay_loc, use_overlay); + GPU_shader_uniform_int(shader.shader, shader.predivide_loc, use_predivide); + return true; } diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh deleted file mode 100644 index c7045217196..00000000000 --- a/intern/opencolorio/ocio_shader_shared.hh +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2022 Blender Foundation. All rights reserved. */ - -#ifndef GPU_SHADER -# include "GPU_shader_shared_utils.h" -#endif - -struct OCIO_GPUCurveMappingParameters { - /* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ - float4 mintable; - float4 range; - float4 ext_in_x; - float4 ext_in_y; - float4 ext_out_x; - float4 ext_out_y; - float4 first_x; - float4 first_y; - float4 last_x; - float4 last_y; - float4 black; - float4 bwmul; - int lut_size; - int use_extend_extrapolate; - int _pad0; - int _pad1; -}; - -struct OCIO_GPUParameters { - float dither; - float scale; - float exponent; - bool1 use_predivide; - bool1 use_overlay; - int _pad0; - int _pad1; - int _pad2; -}; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index bb0b8640767..2b6b28bd649 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -469,21 +469,12 @@ if(WITH_IMAGE_DDS) add_definitions(-DWITH_DDS) endif() -if(WITH_OPENCOLORIO) - add_definitions(-DWITH_OCIO) -endif() - blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") target_link_libraries(bf_gpu PUBLIC bf_draw_shaders bf_gpu_shaders ) -if(WITH_OPENCOLORIO) - target_link_libraries(bf_gpu PUBLIC bf_ocio_shaders) -endif() - - if(CXX_WARN_NO_SUGGEST_OVERRIDE) target_compile_options(bf_gpu PRIVATE $<$:-Wsuggest-override>) endif() diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 15ca7d0c3b1..5e03f7d0767 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -37,9 +37,6 @@ extern "C" { #define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[]; #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" -#ifdef WITH_OCIO -# include "glsl_ocio_source_list.h" -#endif #undef SHADER_SOURCE } @@ -363,9 +360,6 @@ void gpu_shader_dependency_init() g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc)); #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" -#ifdef WITH_OCIO -# include "glsl_ocio_source_list.h" -#endif #undef SHADER_SOURCE int errors = 0; -- cgit v1.2.3 From 16d565836588de15b739ae5965d12d7794b87f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 17 Feb 2022 17:03:23 +0100 Subject: OCIO: Port shader creation logic to use GPUShaderCreateInfo This commit should suffice to make the shader API agnostic now (given that all users of it use the GPU API). This makes the shaders not trigger a false positive error anymore since the binding slots are now garanteed by the backend and not changed at after compilation. This also bundles all uniforms into UBOs. Making them extendable without limitations of push constants. The generated uniforms from OCIO are not densely packed in the UBO to avoid complexity. Another approach would be to use GPU_uniformbuf_create_from_list but this requires converting uniforms to GPUInputs which is too complex for what it is. Reviewed by: brecht, jbakker Differential Revision: https://developer.blender.org/D14123 --- intern/opencolorio/CMakeLists.txt | 36 ++- .../opencolorio/gpu_shader_display_transform.glsl | 210 -------------- .../gpu_shader_display_transform_frag.glsl | 195 +++++++++++++ .../gpu_shader_display_transform_vert.glsl | 6 + .../gpu_shader_display_transform_vertex.glsl | 12 - intern/opencolorio/ocio_impl_glsl.cc | 323 ++++++++++++--------- intern/opencolorio/ocio_shader_shared.hh | 41 +++ source/blender/gpu/CMakeLists.txt | 9 + source/blender/gpu/intern/gpu_shader_dependency.cc | 6 + 9 files changed, 482 insertions(+), 356 deletions(-) delete mode 100644 intern/opencolorio/gpu_shader_display_transform.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_frag.glsl create mode 100644 intern/opencolorio/gpu_shader_display_transform_vert.glsl delete mode 100644 intern/opencolorio/gpu_shader_display_transform_vertex.glsl create mode 100644 intern/opencolorio/ocio_shader_shared.hh diff --git a/intern/opencolorio/CMakeLists.txt b/intern/opencolorio/CMakeLists.txt index dfccb9301ac..be6ccc5c2c5 100644 --- a/intern/opencolorio/CMakeLists.txt +++ b/intern/opencolorio/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../guardedalloc ../../source/blender/blenlib ../../source/blender/gpu + ../../source/blender/gpu/intern ../../source/blender/makesdna ) @@ -20,6 +21,7 @@ set(SRC ocio_capi.h ocio_impl.h + ocio_shader_shared.hh ) set(LIB @@ -56,8 +58,38 @@ if(WITH_OPENCOLORIO) ) endif() - data_to_c_simple(gpu_shader_display_transform.glsl SRC) - data_to_c_simple(gpu_shader_display_transform_vertex.glsl SRC) + set(GLSL_SRC + gpu_shader_display_transform_vert.glsl + gpu_shader_display_transform_frag.glsl + + ocio_shader_shared.hh + ) + + set(GLSL_C) + foreach(GLSL_FILE ${GLSL_SRC}) + data_to_c_simple(${GLSL_FILE} GLSL_C) + endforeach() + + blender_add_lib(bf_ocio_shaders "${GLSL_C}" "" "" "") + + list(APPEND LIB + bf_ocio_shaders + ) + + set(GLSL_SOURCE_CONTENT "") + foreach(GLSL_FILE ${GLSL_SRC}) + get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME) + string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME}) + string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(datatoc_${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n") + endforeach() + + set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_ocio_source_list.h") + file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}") + list(APPEND SRC ${glsl_source_list_file}) + list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR}) + + target_include_directories(bf_ocio_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + endif() diff --git a/intern/opencolorio/gpu_shader_display_transform.glsl b/intern/opencolorio/gpu_shader_display_transform.glsl deleted file mode 100644 index f5a7a7bf45d..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform.glsl +++ /dev/null @@ -1,210 +0,0 @@ -/* Blender OpenColorIO implementation */ - -uniform sampler2D image_texture; -uniform sampler2D overlay_texture; - -uniform float dither; -uniform float scale; -uniform float exponent; -uniform bool predivide; -uniform bool overlay; - -#ifdef USE_CURVE_MAPPING -uniform sampler1D curve_mapping_texture; - -layout(std140) uniform OCIO_GPUCurveMappingParameters -{ - /* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ - vec4 curve_mapping_mintable; - vec4 curve_mapping_range; - vec4 curve_mapping_ext_in_x; - vec4 curve_mapping_ext_in_y; - vec4 curve_mapping_ext_out_x; - vec4 curve_mapping_ext_out_y; - vec4 curve_mapping_first_x; - vec4 curve_mapping_first_y; - vec4 curve_mapping_last_x; - vec4 curve_mapping_last_y; - vec4 curve_mapping_black; - vec4 curve_mapping_bwmul; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; -}; - -float read_curve_mapping(int table, int index) -{ - return texelFetch(curve_mapping_texture, index, 0)[table]; -} - -float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) -{ - if (x <= first[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return first[1]; - } - else { - float fac = (curve_mapping_ext_in_x[table] != 0.0) ? - ((x - first[0]) / curve_mapping_ext_in_x[table]) : - 10000.0; - return first[1] + curve_mapping_ext_in_y[table] * fac; - } - } - else if (x >= last[0]) { - if (curve_mapping_use_extend_extrapolate == 0) { - /* horizontal extrapolation */ - return last[1]; - } - else { - float fac = (curve_mapping_ext_out_x[table] != 0.0) ? - ((x - last[0]) / curve_mapping_ext_out_x[table]) : - -10000.0; - return last[1] + curve_mapping_ext_out_y[table] * fac; - } - } - return 0.0; -} - -float curvemap_evaluateF(int table, float value) -{ - float mintable_ = curve_mapping_mintable[table]; - float range = curve_mapping_range[table]; - float mintable = 0.0; - int CM_TABLE = curve_mapping_lut_size - 1; - - float fi; - int i; - - /* index in table */ - fi = (value - mintable) * range; - i = int(fi); - - /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ - if (fi < 0.0 || fi > float(CM_TABLE)) { - return curvemap_calc_extend(table, - value, - vec2(curve_mapping_first_x[table], curve_mapping_first_y[table]), - vec2(curve_mapping_last_x[table], curve_mapping_last_y[table])); - } - else { - if (i < 0) { - return read_curve_mapping(table, 0); - } - if (i >= CM_TABLE) { - return read_curve_mapping(table, CM_TABLE); - } - fi = fi - float(i); - float cm1 = read_curve_mapping(table, i); - float cm2 = read_curve_mapping(table, i + 1); - return mix(cm1, cm2, fi); - } -} - -vec4 curvemapping_evaluate_premulRGBF(vec4 col) -{ - col.rgb = (col.rgb - curve_mapping_black.rgb) * curve_mapping_bwmul.rgb; - - vec4 result; - result.r = curvemap_evaluateF(0, col.r); - result.g = curvemap_evaluateF(1, col.g); - result.b = curvemap_evaluateF(2, col.b); - result.a = col.a; - return result; -} -#endif /* USE_CURVE_MAPPING */ - -/* Using a triangle distribution which gives a more final uniform noise. - * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ -/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ -/* Return triangle noise in [-1..1[ range */ -float dither_random_value(vec2 co) -{ - /* Original code from https://www.shadertoy.com/view/4t2SDh */ - /* Uniform noise in [0..1[ range */ - float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); - /* Convert uniform distribution into triangle-shaped distribution. */ - float orig = nrnd0 * 2.0 - 1.0; - nrnd0 = orig * inversesqrt(abs(orig)); - nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ - return nrnd0 - sign(orig); -} - -vec2 round_to_pixel(sampler2D tex, vec2 uv) -{ - vec2 size = textureSize(tex, 0); - return vec2(ivec2(uv * size)) / size; -} - -vec4 apply_dither(vec4 col, vec2 uv) -{ - col.rgb += dither_random_value(uv) * 0.0033 * dither; - return col; -} - -vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay, vec2 noise_uv) -{ -#ifdef USE_CURVE_MAPPING - col = curvemapping_evaluate_premulRGBF(col); -#endif - - if (predivide) { - if (col.a > 0.0 && col.a < 1.0) { - col.rgb *= 1.0 / col.a; - } - } - - /* NOTE: This is true we only do de-premul here and NO premul - * and the reason is simple -- opengl is always configured - * for straight alpha at this moment - */ - - /* Convert to scene linear (usually a no-op). */ - col = OCIO_to_scene_linear(col); - - /* Apply exposure in scene linear. */ - col.rgb *= scale; - - /* Convert to display space. */ - col = OCIO_to_display(col); - - /* Blend with overlay in UI colorspace. - * - * UI colorspace here refers to the display linear color space, - * i.e: The linear color space w.r.t. display chromaticity and radiometry. - * We separate the colormanagement process into two steps to be able to - * merge UI using alpha blending in the correct color space. */ - if (overlay) { - col.rgb = pow(col.rgb, vec3(exponent * 2.2)); - col = clamp(col, 0.0, 1.0); - col *= 1.0 - col_overlay.a; - col += col_overlay; /* Assumed unassociated alpha. */ - col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); - } - else { - col.rgb = pow(col.rgb, vec3(exponent)); - } - - if (dither > 0.0) { - col = apply_dither(col, noise_uv); - } - - return col; -} - -/* ------------------------------------------------------------------------ */ - -in vec2 texCoord_interp; -out vec4 fragColor; - -void main() -{ - vec4 col = texture(image_texture, texCoord_interp.st); - vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); - vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); - - fragColor = OCIO_ProcessColor(col, col_overlay, noise_uv); -} diff --git a/intern/opencolorio/gpu_shader_display_transform_frag.glsl b/intern/opencolorio/gpu_shader_display_transform_frag.glsl new file mode 100644 index 00000000000..3c2352c13ba --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_frag.glsl @@ -0,0 +1,195 @@ +/* Blender OpenColorIO implementation */ + +/* -------------------------------------------------------------------- */ +/** \name Curve Mapping Implementation + * \{ */ + +#ifdef USE_CURVE_MAPPING + +float read_curve_mapping(int table, int index) +{ + return texelFetch(curve_mapping_texture, index, 0)[table]; +} + +float curvemap_calc_extend(int table, float x, vec2 first, vec2 last) +{ + if (x <= first[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return first[1]; + } + else { + float fac = (curve_mapping.ext_in_x[table] != 0.0) ? + ((x - first[0]) / curve_mapping.ext_in_x[table]) : + 10000.0; + return first[1] + curve_mapping.ext_in_y[table] * fac; + } + } + else if (x >= last[0]) { + if (curve_mapping.use_extend_extrapolate == 0) { + /* horizontal extrapolation */ + return last[1]; + } + else { + float fac = (curve_mapping.ext_out_x[table] != 0.0) ? + ((x - last[0]) / curve_mapping.ext_out_x[table]) : + -10000.0; + return last[1] + curve_mapping.ext_out_y[table] * fac; + } + } + return 0.0; +} + +float curvemap_evaluateF(int table, float value) +{ + float mintable_ = curve_mapping.mintable[table]; + float range = curve_mapping.range[table]; + float mintable = 0.0; + int CM_TABLE = curve_mapping.lut_size - 1; + + float fi; + int i; + + /* index in table */ + fi = (value - mintable) * range; + i = int(fi); + + /* fi is table float index and should check against table range i.e. [0.0 CM_TABLE] */ + if (fi < 0.0 || fi > float(CM_TABLE)) { + return curvemap_calc_extend(table, + value, + vec2(curve_mapping.first_x[table], curve_mapping.first_y[table]), + vec2(curve_mapping.last_x[table], curve_mapping.last_y[table])); + } + else { + if (i < 0) { + return read_curve_mapping(table, 0); + } + if (i >= CM_TABLE) { + return read_curve_mapping(table, CM_TABLE); + } + fi = fi - float(i); + float cm1 = read_curve_mapping(table, i); + float cm2 = read_curve_mapping(table, i + 1); + return mix(cm1, cm2, fi); + } +} + +vec4 curvemapping_evaluate_premulRGBF(vec4 col) +{ + col.rgb = (col.rgb - curve_mapping.black.rgb) * curve_mapping.bwmul.rgb; + + vec4 result; + result.r = curvemap_evaluateF(0, col.r); + result.g = curvemap_evaluateF(1, col.g); + result.b = curvemap_evaluateF(2, col.b); + result.a = col.a; + return result; +} + +#endif /* USE_CURVE_MAPPING */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dithering + * \{ */ + +/* Using a triangle distribution which gives a more final uniform noise. + * See Banding in Games:A Noisy Rant(revision 5) Mikkel Gjøl, Playdead (slide 27) */ +/* GPUs are rounding before writing to framebuffer so we center the distribution around 0.0. */ +/* Return triangle noise in [-1..1[ range */ +float dither_random_value(vec2 co) +{ + /* Original code from https://www.shadertoy.com/view/4t2SDh */ + /* Uniform noise in [0..1[ range */ + float nrnd0 = fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); + /* Convert uniform distribution into triangle-shaped distribution. */ + float orig = nrnd0 * 2.0 - 1.0; + nrnd0 = orig * inversesqrt(abs(orig)); + nrnd0 = max(-1.0, nrnd0); /* Removes nan's */ + return nrnd0 - sign(orig); +} + +vec2 round_to_pixel(sampler2D tex, vec2 uv) +{ + vec2 size = vec2(textureSize(tex, 0)); + return floor(uv * size) / size; +} + +vec4 apply_dither(vec4 col, vec2 uv) +{ + col.rgb += dither_random_value(uv) * 0.0033 * parameters.dither; + return col; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Main Processing + * \{ */ + +/* Prototypes: Implementation is generaterd and defined after. */ +vec4 OCIO_to_scene_linear(vec4 pixel); +vec4 OCIO_to_display(vec4 pixel); + +vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay) +{ +#ifdef USE_CURVE_MAPPING + col = curvemapping_evaluate_premulRGBF(col); +#endif + + if (parameters.use_predivide) { + if (col.a > 0.0 && col.a < 1.0) { + col.rgb *= 1.0 / col.a; + } + } + + /* NOTE: This is true we only do de-premul here and NO premul + * and the reason is simple -- opengl is always configured + * for straight alpha at this moment + */ + + /* Convert to scene linear (usually a no-op). */ + col = OCIO_to_scene_linear(col); + + /* Apply exposure in scene linear. */ + col.rgb *= parameters.scale; + + /* Convert to display space. */ + col = OCIO_to_display(col); + + /* Blend with overlay in UI colorspace. + * + * UI colorspace here refers to the display linear color space, + * i.e: The linear color space w.r.t. display chromaticity and radiometry. + * We separate the colormanagement process into two steps to be able to + * merge UI using alpha blending in the correct color space. */ + if (parameters.use_overlay) { + col.rgb = pow(col.rgb, vec3(parameters.exponent * 2.2)); + col = clamp(col, 0.0, 1.0); + col *= 1.0 - col_overlay.a; + col += col_overlay; /* Assumed unassociated alpha. */ + col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); + } + else { + col.rgb = pow(col.rgb, vec3(parameters.exponent)); + } + + if (parameters.dither > 0.0) { + vec2 noise_uv = round_to_pixel(image_texture, texCoord_interp.st); + col = apply_dither(col, noise_uv); + } + + return col; +} + +/** \} */ + +void main() +{ + vec4 col = texture(image_texture, texCoord_interp.st); + vec4 col_overlay = texture(overlay_texture, texCoord_interp.st); + + fragColor = OCIO_ProcessColor(col, col_overlay); +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vert.glsl b/intern/opencolorio/gpu_shader_display_transform_vert.glsl new file mode 100644 index 00000000000..06788be11de --- /dev/null +++ b/intern/opencolorio/gpu_shader_display_transform_vert.glsl @@ -0,0 +1,6 @@ + +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + texCoord_interp = texCoord; +} diff --git a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl b/intern/opencolorio/gpu_shader_display_transform_vertex.glsl deleted file mode 100644 index 8cf9628b06b..00000000000 --- a/intern/opencolorio/gpu_shader_display_transform_vertex.glsl +++ /dev/null @@ -1,12 +0,0 @@ - -uniform mat4 ModelViewProjectionMatrix; - -in vec2 texCoord; -in vec2 pos; -out vec2 texCoord_interp; - -void main() -{ - gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); - texCoord_interp = texCoord; -} diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index 09803cd8038..47c965dbc8e 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -21,14 +21,14 @@ #include "GPU_shader.h" #include "GPU_uniform_buffer.h" +#include "gpu_shader_create_info.hh" + using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "ocio_impl.h" - -extern "C" char datatoc_gpu_shader_display_transform_glsl[]; -extern "C" char datatoc_gpu_shader_display_transform_vertex_glsl[]; +#include "ocio_shader_shared.hh" /* **** OpenGL drawing routines using GLSL for color space transform ***** */ @@ -39,41 +39,19 @@ enum OCIO_GPUTextureSlots { TEXTURE_SLOT_LUTS_OFFSET = 3, }; -/* Curve mapping parameters - * - * See documentation for OCIO_CurveMappingSettings to get fields descriptions. - * (this ones pretty much copies stuff from C structure.) - */ -struct OCIO_GPUCurveMappingParameters { - float curve_mapping_mintable[4]; - float curve_mapping_range[4]; - float curve_mapping_ext_in_x[4]; - float curve_mapping_ext_in_y[4]; - float curve_mapping_ext_out_x[4]; - float curve_mapping_ext_out_y[4]; - float curve_mapping_first_x[4]; - float curve_mapping_first_y[4]; - float curve_mapping_last_x[4]; - float curve_mapping_last_y[4]; - float curve_mapping_black[4]; - float curve_mapping_bwmul[4]; - int curve_mapping_lut_size; - int curve_mapping_use_extend_extrapolate; - int _pad[2]; - /** WARNING: Needs to be 16byte aligned. Used as UBO data. */ +enum OCIO_GPUUniformBufSlots { + UNIFORMBUF_SLOT_DISPLAY = 0, + UNIFORMBUF_SLOT_CURVEMAP = 1, + UNIFORMBUF_SLOT_LUTS = 2, }; struct OCIO_GPUShader { /* GPU shader. */ struct GPUShader *shader = nullptr; - /** Uniform locations. */ - int scale_loc = 0; - int exponent_loc = 0; - int dither_loc = 0; - int overlay_loc = 0; - int predivide_loc = 0; - int ubo_bind = 0; + /** Uniform parameters. */ + OCIO_GPUParameters parameters = {}; + GPUUniformBuf *parameters_buffer = nullptr; /* Destructor. */ ~OCIO_GPUShader() @@ -81,6 +59,9 @@ struct OCIO_GPUShader { if (shader) { GPU_shader_free(shader); } + if (parameters_buffer) { + GPU_uniformbuf_free(parameters_buffer); + } } }; @@ -103,6 +84,7 @@ struct OCIO_GPUTextures { /* Uniforms */ std::vector uniforms; + GPUUniformBuf *uniforms_buffer = nullptr; /* Destructor. */ ~OCIO_GPUTextures() @@ -113,6 +95,9 @@ struct OCIO_GPUTextures { if (dummy) { GPU_texture_free(dummy); } + if (uniforms_buffer) { + GPU_uniformbuf_free(uniforms_buffer); + } } }; @@ -165,97 +150,134 @@ static bool createGPUShader(OCIO_GPUShader &shader, const GpuShaderDescRcPtr &shaderdesc_to_display, const bool use_curve_mapping) { - std::ostringstream os; - { - /* Fragment shader */ + using namespace blender::gpu::shader; - /* Work around OpenColorIO not supporting latest GLSL yet. */ - os << "#define texture2D texture\n"; - os << "#define texture3D texture\n"; + std::string source; + source += shaderdesc_to_scene_linear->getShaderText(); + source += "\n"; + source += shaderdesc_to_display->getShaderText(); + source += "\n"; - if (use_curve_mapping) { - os << "#define USE_CURVE_MAPPING\n"; + { + /* Replace all uniform declarations by a comment. + * This avoids double declarations from the backend. */ + size_t index = 0; + while (true) { + index = source.find("uniform ", index); + if (index == -1) { + break; + } + source.replace(index, 2, "//"); + index += 2; } - - os << shaderdesc_to_scene_linear->getShaderText() << "\n"; - os << shaderdesc_to_display->getShaderText() << "\n"; - - os << datatoc_gpu_shader_display_transform_glsl; } - shader.shader = GPU_shader_create(datatoc_gpu_shader_display_transform_vertex_glsl, - os.str().c_str(), - nullptr, - nullptr, - nullptr, - "OCIOShader"); - - if (shader.shader == nullptr) { - return false; - } - - shader.scale_loc = GPU_shader_get_uniform(shader.shader, "scale"); - shader.exponent_loc = GPU_shader_get_uniform(shader.shader, "exponent"); - shader.dither_loc = GPU_shader_get_uniform(shader.shader, "dither"); - shader.overlay_loc = GPU_shader_get_uniform(shader.shader, "overlay"); - shader.predivide_loc = GPU_shader_get_uniform(shader.shader, "predivide"); - shader.ubo_bind = GPU_shader_get_uniform_block_binding(shader.shader, - "OCIO_GPUCurveMappingParameters"); - - GPU_shader_bind(shader.shader); - - /* Set texture bind point uniform once. This is saved by the shader. */ - GPUShader *sh = shader.shader; - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "image_texture"), TEXTURE_SLOT_IMAGE); - GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "overlay_texture"), TEXTURE_SLOT_OVERLAY); + StageInterfaceInfo iface("OCIO_Interface", ""); + iface.smooth(Type::VEC2, "texCoord_interp"); + + ShaderCreateInfo info("OCIO_Display"); + /* Work around OpenColorIO not supporting latest GLSL yet. */ + info.define("texture2D", "texture"); + info.define("texture3D", "texture"); + info.typedef_source("ocio_shader_shared.hh"); + info.sampler(TEXTURE_SLOT_IMAGE, ImageType::FLOAT_2D, "image_texture"); + info.sampler(TEXTURE_SLOT_OVERLAY, ImageType::FLOAT_2D, "overlay_texture"); + info.uniform_buf(UNIFORMBUF_SLOT_DISPLAY, "OCIO_GPUParameters", "parameters"); + info.push_constant(Type::MAT4, "ModelViewProjectionMatrix"); + info.vertex_in(0, Type::VEC2, "pos"); + info.vertex_in(1, Type::VEC2, "texCoord"); + info.vertex_out(iface); + info.fragment_out(0, Type::VEC4, "fragColor"); + info.vertex_source("gpu_shader_display_transform_vert.glsl"); + info.fragment_source("gpu_shader_display_transform_frag.glsl"); + info.fragment_source_generated = source; if (use_curve_mapping) { - GPU_shader_uniform_int( - sh, GPU_shader_get_uniform(sh, "curve_mapping_texture"), TEXTURE_SLOT_CURVE_MAPPING); + info.define("USE_CURVE_MAPPING"); + info.uniform_buf(UNIFORMBUF_SLOT_CURVEMAP, "OCIO_GPUCurveMappingParameters", "curve_mapping"); + info.sampler(TEXTURE_SLOT_CURVE_MAPPING, ImageType::FLOAT_1D, "curve_mapping_texture"); } /* Set LUT textures. */ - for (int i = 0; i < textures.luts.size(); i++) { - GPU_shader_uniform_int(sh, - GPU_shader_get_uniform(sh, textures.luts[i].sampler_name.c_str()), - TEXTURE_SLOT_LUTS_OFFSET + i); - } + int slot = TEXTURE_SLOT_LUTS_OFFSET; + for (OCIO_GPULutTexture &texture : textures.luts) { + ImageType type = GPU_texture_dimensions(texture.texture) == 2 ? ImageType::FLOAT_2D : + ImageType::FLOAT_3D; + info.sampler(slot++, type, texture.sampler_name.c_str()); + } + + /* Set LUT uniforms. */ + if (!textures.uniforms.empty()) { + /* NOTE: For simplicity, we pad everything to size of vec4 avoiding sorting and alignment + * issues. It is unlikely that this becomes a real issue. */ + size_t ubo_size = textures.uniforms.size() * sizeof(float) * 4; + void *ubo_data_buf = malloc(ubo_size); + + uint32_t *ubo_data = reinterpret_cast(ubo_data_buf); + + std::stringstream ss; + ss << "struct OCIO_GPULutParameters {\n"; + + int index = 0; + for (OCIO_GPUUniform &uniform : textures.uniforms) { + index += 1; + const GpuShaderDesc::UniformData &data = uniform.data; + const char *name = uniform.name.c_str(); + char prefix = ' '; + int vec_len; + switch (data.m_type) { + case UNIFORM_DOUBLE: { + vec_len = 1; + float value = float(data.m_getDouble()); + memcpy(ubo_data, &value, sizeof(float)); + break; + } + case UNIFORM_BOOL: { + prefix = 'b'; + vec_len = 1; + int value = int(data.m_getBool()); + memcpy(ubo_data, &value, sizeof(int)); + break; + } + case UNIFORM_FLOAT3: + vec_len = 3; + memcpy(ubo_data, data.m_getFloat3().data(), sizeof(float) * 3); + break; + case UNIFORM_VECTOR_FLOAT: + vec_len = data.m_vectorFloat.m_getSize(); + memcpy(ubo_data, data.m_vectorFloat.m_getVector(), sizeof(float) * vec_len); + break; + case UNIFORM_VECTOR_INT: + prefix = 'i'; + vec_len = data.m_vectorInt.m_getSize(); + memcpy(ubo_data, data.m_vectorInt.m_getVector(), sizeof(int) * vec_len); + break; + default: + continue; + } + /* Align every member to 16bytes. */ + ubo_data += 4; + /* Use a generic variable name because some GLSL compilers can interpret the preprocessor + * define as recursive. */ + ss << " " << prefix << "vec4 var" << index << ";\n"; + /* Use a define to keep the generated code working. */ + blender::StringRef suffix = blender::StringRefNull("xyzw").substr(0, vec_len); + ss << "#define " << name << " lut_parameters.var" << index << "." << suffix << "\n"; + } + ss << "};\n"; + info.typedef_source_generated = ss.str(); - /* Set uniforms. */ - for (OCIO_GPUUniform &uniform : textures.uniforms) { - const GpuShaderDesc::UniformData &data = uniform.data; - const char *name = uniform.name.c_str(); + info.uniform_buf(UNIFORMBUF_SLOT_LUTS, "OCIO_GPULutParameters", "lut_parameters"); - if (data.m_getDouble) { - GPU_shader_uniform_1f(sh, name, (float)data.m_getDouble()); - } - else if (data.m_getBool) { - GPU_shader_uniform_1f(sh, name, (float)(data.m_getBool() ? 1.0f : 0.0f)); - } - else if (data.m_getFloat3) { - GPU_shader_uniform_3f(sh, - name, - (float)data.m_getFloat3()[0], - (float)data.m_getFloat3()[1], - (float)data.m_getFloat3()[2]); - } - else if (data.m_vectorFloat.m_getSize && data.m_vectorFloat.m_getVector) { - GPU_shader_uniform_vector(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorFloat.m_getSize(), - 1, - (float *)data.m_vectorFloat.m_getVector()); - } - else if (data.m_vectorInt.m_getSize && data.m_vectorInt.m_getVector) { - GPU_shader_uniform_vector_int(sh, - GPU_shader_get_uniform(sh, name), - (int)data.m_vectorInt.m_getSize(), - 1, - (int *)data.m_vectorInt.m_getVector()); - } + textures.uniforms_buffer = GPU_uniformbuf_create_ex( + ubo_size, ubo_data_buf, "OCIO_LutParameters"); + + free(ubo_data_buf); } - return true; + shader.shader = GPU_shader_create_from_info(reinterpret_cast(&info)); + + return (shader.shader != nullptr); } /** \} */ @@ -438,27 +460,65 @@ static void updateGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, /* Update uniforms. */ OCIO_GPUCurveMappingParameters data; for (int i = 0; i < 4; i++) { - data.curve_mapping_range[i] = curve_mapping_settings->range[i]; - data.curve_mapping_mintable[i] = curve_mapping_settings->mintable[i]; - data.curve_mapping_ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; - data.curve_mapping_ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; - data.curve_mapping_ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; - data.curve_mapping_ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; - data.curve_mapping_first_x[i] = curve_mapping_settings->first_x[i]; - data.curve_mapping_first_y[i] = curve_mapping_settings->first_y[i]; - data.curve_mapping_last_x[i] = curve_mapping_settings->last_x[i]; - data.curve_mapping_last_y[i] = curve_mapping_settings->last_y[i]; + data.range[i] = curve_mapping_settings->range[i]; + data.mintable[i] = curve_mapping_settings->mintable[i]; + data.ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; + data.ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; + data.ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; + data.ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; + data.first_x[i] = curve_mapping_settings->first_x[i]; + data.first_y[i] = curve_mapping_settings->first_y[i]; + data.last_x[i] = curve_mapping_settings->last_x[i]; + data.last_y[i] = curve_mapping_settings->last_y[i]; } for (int i = 0; i < 3; i++) { - data.curve_mapping_black[i] = curve_mapping_settings->black[i]; - data.curve_mapping_bwmul[i] = curve_mapping_settings->bwmul[i]; + data.black[i] = curve_mapping_settings->black[i]; + data.bwmul[i] = curve_mapping_settings->bwmul[i]; } - data.curve_mapping_lut_size = curve_mapping_settings->lut_size; - data.curve_mapping_use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; + data.lut_size = curve_mapping_settings->lut_size; + data.use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; GPU_uniformbuf_update(curvemap.buffer, &data); } +static void updateGPUDisplayParameters(OCIO_GPUShader &shader, + float scale, + float exponent, + float dither, + bool use_predivide, + bool use_overlay) +{ + bool do_update = false; + if (shader.parameters_buffer == nullptr) { + shader.parameters_buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUParameters)); + do_update = true; + } + OCIO_GPUParameters &data = shader.parameters; + if (data.scale != scale) { + data.scale = scale; + do_update = true; + } + if (data.exponent != exponent) { + data.exponent = exponent; + do_update = true; + } + if (data.dither != dither) { + data.dither = dither; + do_update = true; + } + if (data.use_predivide != use_predivide) { + data.use_predivide = use_predivide; + do_update = true; + } + if (data.use_overlay != use_overlay) { + data.use_overlay = use_overlay; + do_update = true; + } + if (do_update) { + GPU_uniformbuf_update(shader.parameters_buffer, &data); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -609,7 +669,7 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, /* Update and bind curve mapping data. */ if (curve_mapping_settings) { updateGPUCurveMapping(curvemap, curve_mapping_settings); - GPU_uniformbuf_bind(curvemap.buffer, shader.ubo_bind); + GPU_uniformbuf_bind(curvemap.buffer, UNIFORMBUF_SLOT_CURVEMAP); GPU_texture_bind(curvemap.texture, TEXTURE_SLOT_CURVE_MAPPING); } @@ -623,17 +683,16 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, GPU_texture_bind(textures.luts[i].texture, TEXTURE_SLOT_LUTS_OFFSET + i); } + if (textures.uniforms_buffer) { + GPU_uniformbuf_bind(textures.uniforms_buffer, UNIFORMBUF_SLOT_LUTS); + } + + updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay); + GPU_uniformbuf_bind(shader.parameters_buffer, UNIFORMBUF_SLOT_DISPLAY); + /* TODO(fclem): remove remains of IMM. */ immBindShader(shader.shader); - /* Bind Shader and set uniforms. */ - // GPU_shader_bind(shader.shader); - GPU_shader_uniform_float(shader.shader, shader.scale_loc, scale); - GPU_shader_uniform_float(shader.shader, shader.exponent_loc, exponent); - GPU_shader_uniform_float(shader.shader, shader.dither_loc, dither); - GPU_shader_uniform_int(shader.shader, shader.overlay_loc, use_overlay); - GPU_shader_uniform_int(shader.shader, shader.predivide_loc, use_predivide); - return true; } diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh new file mode 100644 index 00000000000..c7045217196 --- /dev/null +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#ifndef GPU_SHADER +# include "GPU_shader_shared_utils.h" +#endif + +struct OCIO_GPUCurveMappingParameters { + /* Curve mapping parameters + * + * See documentation for OCIO_CurveMappingSettings to get fields descriptions. + * (this ones pretty much copies stuff from C structure.) + */ + float4 mintable; + float4 range; + float4 ext_in_x; + float4 ext_in_y; + float4 ext_out_x; + float4 ext_out_y; + float4 first_x; + float4 first_y; + float4 last_x; + float4 last_y; + float4 black; + float4 bwmul; + int lut_size; + int use_extend_extrapolate; + int _pad0; + int _pad1; +}; + +struct OCIO_GPUParameters { + float dither; + float scale; + float exponent; + bool1 use_predivide; + bool1 use_overlay; + int _pad0; + int _pad1; + int _pad2; +}; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index e883a12a5b2..f2e189777f0 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -452,12 +452,21 @@ if(WITH_IMAGE_DDS) add_definitions(-DWITH_DDS) endif() +if(WITH_OPENCOLORIO) + add_definitions(-DWITH_OCIO) +endif() + blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") target_link_libraries(bf_gpu PUBLIC bf_draw_shaders bf_gpu_shaders ) +if(WITH_OPENCOLORIO) + target_link_libraries(bf_gpu PUBLIC bf_ocio_shaders) +endif() + + if(CXX_WARN_NO_SUGGEST_OVERRIDE) target_compile_options(bf_gpu PRIVATE $<$:-Wsuggest-override>) endif() diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 8a842ef4d7c..5b7df035acd 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -21,6 +21,9 @@ extern "C" { #define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[]; #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE } @@ -348,6 +351,9 @@ void gpu_shader_dependency_init() g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc)); #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" +#ifdef WITH_OCIO +# include "glsl_ocio_source_list.h" +#endif #undef SHADER_SOURCE int errors = 0; -- cgit v1.2.3 From 385d9488e606023bc0edf50a45992dc85405e95b Mon Sep 17 00:00:00 2001 From: Peter Kim Date: Sat, 19 Feb 2022 15:23:34 +0900 Subject: Fix incorrect copying of XR action paths After using MEM_dupallocN() on the original item/binding, the user/component path ListBase for the new item/binding needs to be cleared and each path copied separately. --- source/blender/windowmanager/xr/intern/wm_xr_actionmap.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c index 267fb0481a8..8a1982fa8b5 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c @@ -103,6 +103,12 @@ static XrActionMapBinding *wm_xr_actionmap_binding_copy(XrActionMapBinding *amb_ XrActionMapBinding *amb_dst = MEM_dupallocN(amb_src); amb_dst->prev = amb_dst->next = NULL; + BLI_listbase_clear(&amb_dst->component_paths); + LISTBASE_FOREACH (XrComponentPath *, path, &amb_src->component_paths) { + XrComponentPath *path_new = MEM_dupallocN(path); + BLI_addtail(&amb_dst->component_paths, path_new); + } + return amb_dst; } @@ -318,6 +324,12 @@ static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami_src) ami_dst->op_properties_ptr = NULL; } + BLI_listbase_clear(&ami_dst->user_paths); + LISTBASE_FOREACH (XrUserPath *, path, &ami_src->user_paths) { + XrUserPath *path_new = MEM_dupallocN(path); + BLI_addtail(&ami_dst->user_paths, path_new); + } + return ami_dst; } -- cgit v1.2.3 From 991781c8eabefbcadc534bce3af32c4a7c61b59f Mon Sep 17 00:00:00 2001 From: YimingWu Date: Sat, 19 Feb 2022 22:35:22 +0800 Subject: LineArt: GPU subdiv fix. Use evaluated mesh instead of ob->data. Reviewed by: Antonio Vazquez (antoniov) Differential Revision: https://developer.blender.org/D14040 --- source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index e24452b1072..a5af66cbbc5 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -2205,7 +2205,7 @@ static void lineart_main_load_geometries( } if (use_ob->type == OB_MESH) { - use_mesh = use_ob->data; + use_mesh = BKE_object_get_evaluated_mesh(use_ob); } else { /* If DEG_ITER_OBJECT_FLAG_DUPLI is set, some curve objects may also have an evaluated mesh @@ -4453,7 +4453,7 @@ static void lineart_gpencil_generate(LineartCache *cache, if ((match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0)) { if (eval_ob && eval_ob->type == OB_MESH) { int dindex = 0; - Mesh *me = (Mesh *)eval_ob->data; + Mesh *me = BKE_object_get_evaluated_mesh(eval_ob); if (me->dvert) { LISTBASE_FOREACH (bDeformGroup *, db, &me->vertex_group_names) { if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { -- cgit v1.2.3 From 1f7913228779d7ae20a57ffe1dfdfa6465f9972d Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Sat, 19 Feb 2022 21:31:09 -0500 Subject: UI: Do not include the text stating an enum item is the default This adds maintence overhead and it is not that useful when we have reset to default. If this is something that we want it should be added dynamically. Reviewed By: HooglyBoogly, Severin, #user_interface Differential Revision: https://developer.blender.org/D14151 --- release/scripts/startup/bl_ui/space_topbar.py | 6 ++---- source/blender/editors/io/io_obj.c | 6 +++--- source/blender/makesrna/intern/rna_constraint.c | 2 +- source/blender/makesrna/intern/rna_fcurve.c | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index 493cad6d2db..e6d2d1403b5 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -448,8 +448,7 @@ class TOPBAR_MT_file_import(Menu): def draw(self, _context): if bpy.app.build_options.collada: - self.layout.operator("wm.collada_import", - text="Collada (Default) (.dae)") + self.layout.operator("wm.collada_import", text="Collada (.dae)") if bpy.app.build_options.alembic: self.layout.operator("wm.alembic_import", text="Alembic (.abc)") if bpy.app.build_options.usd: @@ -467,8 +466,7 @@ class TOPBAR_MT_file_export(Menu): def draw(self, _context): self.layout.operator("wm.obj_export", text="Wavefront OBJ (.obj)") if bpy.app.build_options.collada: - self.layout.operator("wm.collada_export", - text="Collada (Default) (.dae)") + self.layout.operator("wm.collada_export", text="Collada (.dae)") if bpy.app.build_options.alembic: self.layout.operator("wm.alembic_export", text="Alembic (.abc)") if bpy.app.build_options.usd: diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c index 28e14a14f5f..1c821eebdee 100644 --- a/source/blender/editors/io/io_obj.c +++ b/source/blender/editors/io/io_obj.c @@ -38,12 +38,12 @@ static const EnumPropertyItem io_obj_transform_axis_forward[] = { {OBJ_AXIS_Z_FORWARD, "Z_FORWARD", 0, "Z", "Positive Z axis"}, {OBJ_AXIS_NEGATIVE_X_FORWARD, "NEGATIVE_X_FORWARD", 0, "-X", "Negative X axis"}, {OBJ_AXIS_NEGATIVE_Y_FORWARD, "NEGATIVE_Y_FORWARD", 0, "-Y", "Negative Y axis"}, - {OBJ_AXIS_NEGATIVE_Z_FORWARD, "NEGATIVE_Z_FORWARD", 0, "-Z (Default)", "Negative Z axis"}, + {OBJ_AXIS_NEGATIVE_Z_FORWARD, "NEGATIVE_Z_FORWARD", 0, "-Z", "Negative Z axis"}, {0, NULL, 0, NULL, NULL}}; static const EnumPropertyItem io_obj_transform_axis_up[] = { {OBJ_AXIS_X_UP, "X_UP", 0, "X", "Positive X axis"}, - {OBJ_AXIS_Y_UP, "Y_UP", 0, "Y (Default)", "Positive Y axis"}, + {OBJ_AXIS_Y_UP, "Y_UP", 0, "Y", "Positive Y axis"}, {OBJ_AXIS_Z_UP, "Z_UP", 0, "Z", "Positive Z axis"}, {OBJ_AXIS_NEGATIVE_X_UP, "NEGATIVE_X_UP", 0, "-X", "Negative X axis"}, {OBJ_AXIS_NEGATIVE_Y_UP, "NEGATIVE_Y_UP", 0, "-Y", "Negative Y axis"}, @@ -55,7 +55,7 @@ static const EnumPropertyItem io_obj_export_evaluation_mode[] = { {DAG_EVAL_VIEWPORT, "DAG_EVAL_VIEWPORT", 0, - "Viewport (Default)", + "Viewport", "Export objects as they appear in the viewport"}, {0, NULL, 0, NULL, NULL}}; diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index edcf121a8c4..114fe30acf7 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -2969,7 +2969,7 @@ static void rna_def_constraint_spline_ik(BlenderRNA *brna) PropertyRNA *prop; static const EnumPropertyItem splineik_xz_scale_mode[] = { - {CONSTRAINT_SPLINEIK_XZS_NONE, "NONE", 0, "None", "Don't scale the X and Z axes (Default)"}, + {CONSTRAINT_SPLINEIK_XZS_NONE, "NONE", 0, "None", "Don't scale the X and Z axes"}, {CONSTRAINT_SPLINEIK_XZS_ORIGINAL, "BONE_ORIGINAL", 0, diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index f24803af654..8f418fcc7c7 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -1880,7 +1880,7 @@ static void rna_def_drivervar(BlenderRNA *brna) "SINGLE_PROP", ICON_RNA, "Single Property", - "Use the value from some RNA property (Default)"}, + "Use the value from some RNA property"}, {DVAR_TYPE_TRANSFORM_CHAN, "TRANSFORMS", ICON_DRIVER_TRANSFORM, -- cgit v1.2.3 From de886884c07f1fcb5ad9473035cc2e471d3b13c8 Mon Sep 17 00:00:00 2001 From: YimingWu Date: Sat, 19 Feb 2022 22:35:22 +0800 Subject: Fix T95470: LineArt GPU subdiv fix. Use evaluated mesh instead of ob->data. Reviewed by: Antonio Vazquez (antoniov) Differential Revision: https://developer.blender.org/D14040 --- source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index a9ec136831d..19548f6ffa3 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -2221,7 +2221,7 @@ static void lineart_main_load_geometries( } if (use_ob->type == OB_MESH) { - use_mesh = use_ob->data; + use_mesh = BKE_object_get_evaluated_mesh(use_ob); } else { /* If DEG_ITER_OBJECT_FLAG_DUPLI is set, some curve objects may also have an evaluated mesh @@ -4469,7 +4469,7 @@ static void lineart_gpencil_generate(LineartCache *cache, if ((match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0)) { if (eval_ob && eval_ob->type == OB_MESH) { int dindex = 0; - Mesh *me = (Mesh *)eval_ob->data; + Mesh *me = BKE_object_get_evaluated_mesh(eval_ob); if (me->dvert) { LISTBASE_FOREACH (bDeformGroup *, db, &me->vertex_group_names) { if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { -- cgit v1.2.3 From 1a0a22f95a22496dec2791441249265d553dc2d7 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Wed, 9 Feb 2022 20:17:48 +0100 Subject: Fix T95413: Blur node size input crash Bug was introduced in D12167. Reading input size from an input socket is not possible in tiled compositor before execution is initialized. A similar change was done for the base class, see BlurBaseOperation::init_data(). Reviewed by: Jeroen Bakker Differential Revision: https://developer.blender.org/D14067 --- .../operations/COM_GaussianBokehBlurOperation.cc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc index db5f9c7c35d..49e761d6221 100644 --- a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc @@ -44,8 +44,10 @@ void GaussianBokehBlurOperation::init_data() const float width = this->get_width(); const float height = this->get_height(); - if (!sizeavailable_) { - update_size(); + if(execution_model_ == eExecutionModel::FullFrame) { + if (!sizeavailable_) { + update_size(); + } } radxf_ = size_ * (float)data_.sizex; @@ -111,6 +113,22 @@ void GaussianBokehBlurOperation::update_gauss() void GaussianBokehBlurOperation::execute_pixel(float output[4], int x, int y, void *data) { + float result[4]; + input_size_->read_sampled(result, 0, 0, PixelSampler::Nearest); + size_ = result[0]; + + const float width = this->get_width(); + const float height = this->get_height(); + + radxf_ = size_ * (float)data_.sizex; + CLAMP(radxf_, 0.0f, width / 2.0f); + + radyf_ = size_ * (float)data_.sizey; + CLAMP(radyf_, 0.0f, height / 2.0f); + + radx_ = ceil(radxf_); + rady_ = ceil(radyf_); + float temp_color[4]; temp_color[0] = 0; temp_color[1] = 0; -- cgit v1.2.3 From 626fb290eb43eb43b9451c757b1e906f0b5eeb53 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 20 Feb 2022 21:28:55 +1100 Subject: Cleanup: quiet const cast warning --- source/blender/blenkernel/intern/writeffmpeg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index dee852ca33a..61efade9c93 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -869,7 +869,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, { /* Handle to the output file */ AVFormatContext *of; - const AVOutputFormat *fmt; + AVOutputFormat *fmt; char name[FILE_MAX], error[1024]; const char **exts; -- cgit v1.2.3 From e93a8c4f74d854b6e2b42cb10651333faed8f280 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 20 Feb 2022 21:28:57 +1100 Subject: Cleanup: avoid reallocating arrays at the same size --- source/blender/bmesh/intern/bmesh_query.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/blender/bmesh/intern/bmesh_query.c b/source/blender/bmesh/intern/bmesh_query.c index 45a42c29d84..1bc5b70f874 100644 --- a/source/blender/bmesh/intern/bmesh_query.c +++ b/source/blender/bmesh/intern/bmesh_query.c @@ -2234,7 +2234,9 @@ int BM_mesh_calc_face_groups(BMesh *bm, MEM_freeN(stack); /* reduce alloc to required size */ - group_index = MEM_reallocN(group_index, sizeof(*group_index) * group_curr); + if (group_index_len != group_curr) { + group_index = MEM_reallocN(group_index, sizeof(*group_index) * group_curr); + } *r_group_index = group_index; return group_curr; @@ -2354,7 +2356,9 @@ int BM_mesh_calc_edge_groups(BMesh *bm, MEM_freeN(stack); /* reduce alloc to required size */ - group_index = MEM_reallocN(group_index, sizeof(*group_index) * group_curr); + if (group_index_len != group_curr) { + group_index = MEM_reallocN(group_index, sizeof(*group_index) * group_curr); + } *r_group_index = group_index; return group_curr; -- cgit v1.2.3 From 2554ef986a8ccd1dc245478f99524dc68f8af435 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 20 Feb 2022 21:28:58 +1100 Subject: readfile: free & reallocate arrays in oldnewmap_clear Even though the size of the map was set back to DEFAULT_SIZE_EXP, the underlying arrays were left unchained. In some cases this caused further expansions to result in an unusual reallocation pattern where MEM_reallocN would run expand the entries into an array that was in fact the same size. --- source/blender/blenloader/intern/readfile.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 7dd35203a89..9539436cf69 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -320,15 +320,22 @@ static void oldnewmap_increase_size(OldNewMap *onm) /* Public OldNewMap API */ -static OldNewMap *oldnewmap_new(void) +static void oldnewmap_init_data(OldNewMap *onm, const int capacity_exp) { - OldNewMap *onm = MEM_callocN(sizeof(*onm), "OldNewMap"); + memset(onm, 0x0, sizeof(*onm)); - onm->capacity_exp = DEFAULT_SIZE_EXP; + onm->capacity_exp = capacity_exp; onm->entries = MEM_malloc_arrayN( ENTRIES_CAPACITY(onm), sizeof(*onm->entries), "OldNewMap.entries"); onm->map = MEM_malloc_arrayN(MAP_CAPACITY(onm), sizeof(*onm->map), "OldNewMap.map"); oldnewmap_clear_map(onm); +} + +static OldNewMap *oldnewmap_new(void) +{ + OldNewMap *onm = MEM_mallocN(sizeof(*onm), "OldNewMap"); + + oldnewmap_init_data(onm, DEFAULT_SIZE_EXP); return onm; } @@ -395,9 +402,10 @@ static void oldnewmap_clear(OldNewMap *onm) } } - onm->capacity_exp = DEFAULT_SIZE_EXP; - oldnewmap_clear_map(onm); - onm->nentries = 0; + MEM_freeN(onm->entries); + MEM_freeN(onm->map); + + oldnewmap_init_data(onm, DEFAULT_SIZE_EXP); } static void oldnewmap_free(OldNewMap *onm) -- cgit v1.2.3 From 7b11c717294341f5c288e2dc8432e84bc53663ff Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 20 Feb 2022 21:28:59 +1100 Subject: RNA: modify debug mode logic for setting invalid unset values The previous behavior called RNA_enum_item_add a second time, filled it's contents with invalid values then subtracted totitem, this caused an unusual reallocation pattern where MEM_recallocN would run in order to create the dummy item, then again when adding another itme (reallocating an array of the same size). Simply memset the array to 0xff instead. --- source/blender/makesrna/intern/rna_define.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/source/blender/makesrna/intern/rna_define.c b/source/blender/makesrna/intern/rna_define.c index 850accd0b24..5127b418b7f 100644 --- a/source/blender/makesrna/intern/rna_define.c +++ b/source/blender/makesrna/intern/rna_define.c @@ -4391,24 +4391,21 @@ void RNA_enum_item_add(EnumPropertyItem **items, int *totitem, const EnumPropert if (tot == 0) { *items = MEM_callocN(sizeof(EnumPropertyItem[8]), __func__); + /* Ensure we get crashes on missing calls to 'RNA_enum_item_end', see T74227. */ +#ifdef DEBUG + memset(*items, 0xff, sizeof(EnumPropertyItem[8])); +#endif } else if (tot >= 8 && (tot & (tot - 1)) == 0) { /* power of two > 8 */ *items = MEM_recallocN_id(*items, sizeof(EnumPropertyItem) * tot * 2, __func__); +#ifdef DEBUG + memset((*items) + tot, 0xff, sizeof(EnumPropertyItem) * tot); +#endif } (*items)[tot] = *item; *totitem = tot + 1; - - /* Ensure we get crashes on missing calls to 'RNA_enum_item_end', see T74227. */ -#ifdef DEBUG - static const EnumPropertyItem item_error = { - -1, POINTER_FROM_INT(-1), -1, POINTER_FROM_INT(-1), POINTER_FROM_INT(-1)}; - if (item != &item_error) { - RNA_enum_item_add(items, totitem, &item_error); - *totitem -= 1; - } -#endif } void RNA_enum_item_add_separator(EnumPropertyItem **items, int *totitem) -- cgit v1.2.3 From 1884f6e7296a3847f14331aadd6c520cc25e4d12 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 20 Feb 2022 21:29:00 +1100 Subject: BLI_array: add BLI_array_trim utility to re-allocate the current size Use this in BKE_view_layer_array_* functions. --- source/blender/blenkernel/intern/layer_utils.c | 14 +++++++++----- source/blender/blenlib/BLI_array.h | 11 +++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/layer_utils.c b/source/blender/blenkernel/intern/layer_utils.c index 057309d0896..0903c2a2cac 100644 --- a/source/blender/blenkernel/intern/layer_utils.c +++ b/source/blender/blenkernel/intern/layer_utils.c @@ -67,9 +67,11 @@ Object **BKE_view_layer_array_selected_objects_params( } FOREACH_SELECTED_OBJECT_END; - object_array = MEM_reallocN(object_array, sizeof(*object_array) * BLI_array_len(object_array)); - /* We always need a valid allocation (prevent crash on free). */ - if (object_array == NULL) { + if (object_array != NULL) { + BLI_array_trim(object_array); + } + else { + /* We always need a valid allocation (prevent crash on free). */ object_array = MEM_mallocN(0, __func__); } *r_len = BLI_array_len(object_array); @@ -121,9 +123,11 @@ Base **BKE_view_layer_array_from_bases_in_mode_params(ViewLayer *view_layer, } FOREACH_BASE_IN_MODE_END; - base_array = MEM_reallocN(base_array, sizeof(*base_array) * BLI_array_len(base_array)); /* We always need a valid allocation (prevent crash on free). */ - if (base_array == NULL) { + if (base_array != NULL) { + BLI_array_trim(base_array); + } + else { base_array = MEM_mallocN(0, __func__); } *r_len = BLI_array_len(base_array); diff --git a/source/blender/blenlib/BLI_array.h b/source/blender/blenlib/BLI_array.h index d3bb3401d7e..80e865ded62 100644 --- a/source/blender/blenlib/BLI_array.h +++ b/source/blender/blenlib/BLI_array.h @@ -146,6 +146,17 @@ void _bli_array_grow_func(void **arr_p, */ #define BLI_array_fake_user(arr) ((void)_##arr##_len, (void)_##arr##_static) +/** + * Trim excess items from the array (when they exist). + */ +#define BLI_array_trim(arr) \ + { \ + if (_bli_array_totalsize_dynamic(arr) != _##arr##_len) { \ + arr = MEM_reallocN(arr, sizeof(*arr) * _##arr##_len); \ + } \ + } \ + ((void)0) + /** \} */ /* -------------------------------------------------------------------- */ -- cgit v1.2.3 From 16da9c944cd4806569a26ade3049ea994493f7fd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 21 Feb 2022 11:45:33 +1100 Subject: Cleanup: remove duplicate key-map items --- .../keyconfig/keymap_data/blender_default.py | 23 ++++++++++++++-------- .../keymap_data/industry_compatible_data.py | 3 --- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 7373615d5ff..62afe16d106 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -387,6 +387,11 @@ def _template_items_uv_select_mode(params): ] else: return [ + # TODO(@campbellbarton): should this be kept? + # Seems it was included in the new key-map by accident, check on removing + # although it's not currently used for anything else. + op_menu("IMAGE_MT_uvs_select_mode", {"type": 'TAB', "value": 'PRESS', "ctrl": True}), + *_template_items_editmode_mesh_select_mode(params), # Hack to prevent fall-through, when sync select isn't enabled (and the island button isn't visible). ("mesh.select_mode", {"type": 'FOUR', "value": 'PRESS'}, None), @@ -861,7 +866,6 @@ def km_mask_editing(params): items.extend([ ("mask.select", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, {"properties": [("deselect_all", not params.legacy)]}), - ("transform.translate", {"type": 'EVT_TWEAK_R', "value": 'ANY'}, None), ]) items.extend([ @@ -1205,7 +1209,6 @@ def km_uv_editor(params): if not params.legacy else op_menu("IMAGE_MT_uvs_snap", {"type": 'S', "value": 'PRESS', "shift": True}) ), - op_menu("IMAGE_MT_uvs_select_mode", {"type": 'TAB', "value": 'PRESS', "ctrl": True}), *_template_items_proportional_editing( params, connected=False, toggle_data_path='tool_settings.use_proportional_edit'), ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None), @@ -1370,7 +1373,6 @@ def km_view3d(params): *(() if not params.use_pie_click_drag else (("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'CLICK'}, None),)), ("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "shift": True}, None), - ("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "shift": True}, None), # Numpad views. ("view3d.view_camera", {"type": 'NUMPAD_0', "value": 'PRESS'}, None), ("view3d.view_axis", {"type": 'NUMPAD_1', "value": 'PRESS'}, @@ -2062,14 +2064,19 @@ def km_node_editor(params): ("node.translate_attach", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}), - ("node.translate_attach", - {"type": params.select_tweak, "value": 'ANY'}, - {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}), + # Avoid duplicating the previous item. + *([] if params.select_tweak == 'EVT_TWEAK_L' else ( + ("node.translate_attach", {"type": params.select_tweak, "value": 'ANY'}, + {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}), + )), ("transform.translate", {"type": 'G', "value": 'PRESS'}, {"properties": [("view2d_edge_pan", True)]}), ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, {"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}), - ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, - {"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}), + # Avoid duplicating the previous item. + *([] if params.select_tweak == 'EVT_TWEAK_L' else ( + ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, + {"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}), + )), ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None), ("transform.resize", {"type": 'S', "value": 'PRESS'}, None), ("node.move_detach_links", 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 c9dea8ab2e7..7a55ce5e5f0 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -3408,9 +3408,6 @@ def km_sculpt(params): ("sculpt.set_detail_size", {"type": 'D', "value": 'PRESS', "shift": True, "alt": True}, None), # Remesh ("object.voxel_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None), - ("object.quadriflow_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True, "alt": True}, None), - # Remesh - ("object.voxel_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None), ("object.voxel_size_edit", {"type": 'R', "value": 'PRESS', "shift": True}, None), ("object.quadriflow_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True, "alt": True}, None), # Brush properties -- cgit v1.2.3 From 167c4c6962c96b281f40ea1b540b1bf1223a5277 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 21 Feb 2022 11:53:23 +1100 Subject: Tests: add duplicate key-map test, also test multiple configurations Duplicate key-map items (while harmless in most cases) may cause unexpected behavior or point to mistakes in the key-map, so best avoid these. --- tests/python/bl_keymap_validate.py | 167 +++++++++++++++++++++++++++++-------- 1 file changed, 132 insertions(+), 35 deletions(-) diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py index ce2022f6ae7..9d613fc90f4 100644 --- a/tests/python/bl_keymap_validate.py +++ b/tests/python/bl_keymap_validate.py @@ -16,6 +16,7 @@ This catches the following kinds of issues: - Unused keymaps (keymaps which are defined but not used anywhere). - Event values that don't make sense for the event type, e.g. An escape key could have the value "NORTH" instead of "PRESS". +- Identical key-map items. This works by taking the keymap data (before it's loaded into Blender), then comparing it with that same keymap after exporting and importing. @@ -29,14 +30,28 @@ NOTE: import types import typing +import os import contextlib -import bpy +import bpy # type: ignore # Useful for diffing the output to see what changed in context. # this writes keymaps into the current directory with `.orig.py` & `.rewrite.py` extensions. -WRITE_OUTPUT_DIR = None # "/tmp", defaults to the systems temp directory. +WRITE_OUTPUT_DIR = "/tmp/test" # "/tmp", defaults to the systems temp directory. +# For each preset, test all of these options. +# The key is the preset name, containing a sequence of (attribute, value) pairs to test. +# +# NOTE(@campbellbarton): only add these for preferences which impact multiple keys as exposing all preferences +# this way would create too many combinations making the tests take too long to complete. +PRESET_PREFS = { + "Blender": ( + (("select_mouse", 'LEFT'), ("use_alt_tool", False)), + (("select_mouse", 'LEFT'), ("use_alt_tool", True)), + (("select_mouse", 'RIGHT'), ("rmb_action", 'TWEAK')), + (("select_mouse", 'RIGHT'), ("rmb_action", 'FALLBACK_TOOL')), + ), +} # ----------------------------------------------------------------------------- # Generic Utilities @@ -153,14 +168,49 @@ def keymap_data_clean(keyconfig_data: typing.List, *, relaxed: bool) -> None: items[i] = item_op, item_event, None -def keyconfig_activate_and_extract_data(filepath: str, *, relaxed: bool) -> typing.List: +def keyconfig_config_as_filename_component(values: typing.Sequence[typing.Tuple[str, typing.Any]]): + """ + Takes a configuration, eg: + + [("select_mouse", 'LEFT'), ("rmb_action", 'TWEAK')] + + And returns a filename compatible path: + """ + from urllib.parse import quote + if not values: + return "" + + return "(" + quote( + ".".join([ + "-".join((str(key), str(val))) + for key, val in values + ]), + # Needed so forward slashes aren't included in the resulting name. + safe="", + ) + ")" + + +def keyconfig_activate_and_extract_data( + filepath: str, + *, + relaxed: bool, + config: typing.Sequence[typing.Tuple[str, typing.Any]], +) -> typing.List: """ Activate the key-map by filepath, return the key-config data (cleaned for comparison). """ - import bl_keymap_utils.io + import bl_keymap_utils.io # type: ignore + + if config: + bpy.ops.preferences.keyconfig_activate(filepath=filepath) + km_prefs = bpy.context.window_manager.keyconfigs.active.preferences + for attr, value in config: + setattr(km_prefs, attr, value) + with temp_fn_argument_extractor(bl_keymap_utils.io, "keyconfig_init_from_data") as args_collected: bpy.ops.preferences.keyconfig_activate(filepath=filepath) + # If called multiple times, something strange is happening. assert(len(args_collected) == 1) args, _kw = args_collected[0] @@ -169,6 +219,29 @@ def keyconfig_activate_and_extract_data(filepath: str, *, relaxed: bool) -> typi return keyconfig_data +def keyconfig_report_duplicates(keyconfig_data: typing.List) -> str: + """ + Return true if any of the key-maps have duplicate items. + + Duplicate items are reported so they can be resolved. + """ + error_text = [] + for km_idname, km_args, km_items_data in keyconfig_data: + items = tuple(km_items_data["items"]) + unique: typing.Dict[str, typing.List[int]] = {} + for i, (item_op, item_event, item_prop) in enumerate(items): + # Ensure stable order as `repr` will use order of definition. + item_event = {key: item_event[key] for key in sorted(item_event.keys())} + if item_prop is not None: + item_prop = {key: item_prop[key] for key in sorted(item_prop.keys())} + item_repr = repr((item_op, item_event, item_prop)) + unique.setdefault(item_repr, []).append(i) + for key, value in unique.items(): + if len(value) > 1: + error_text.append("\"%s\" %r indices %r for item %r" % (km_idname, km_args, value, key)) + return "\n".join(error_text) + + def main() -> None: import os import sys @@ -186,37 +259,61 @@ def main() -> None: for filepath in presets: name_only = os.path.splitext(os.path.basename(filepath))[0] - print("KeyMap Validate:", name_only, end=" ... ") - - data_orig = keyconfig_activate_and_extract_data(filepath, relaxed=relaxed) - - with tempfile.TemporaryDirectory() as dir_temp: - filepath_temp = os.path.join(dir_temp, name_only + ".test.py") - bpy.ops.preferences.keyconfig_export(filepath=filepath_temp, all=True) - data_reimport = keyconfig_activate_and_extract_data(filepath_temp, relaxed=relaxed) - - # Comparing a pretty printed string tends to give more useful - # text output compared to the data-structure. Both will work. - if (cmp_message := report_humanly_readable_difference( - pprint.pformat(data_orig, indent=0, width=120), - pprint.pformat(data_reimport, indent=0, width=120), - )): - print("FAILED!") - sys.stdout.write(( - "Keymap %s has inconsistency on re-importing:\n" - " %r" - ) % (filepath, cmp_message)) - has_error = True - else: - print("OK!") - - if WRITE_OUTPUT_DIR: - name_only_temp = os.path.join(WRITE_OUTPUT_DIR, name_only) - print("Writing data to:", name_only_temp + ".*.py") - with open(name_only_temp + ".orig.py", 'w') as fh: - fh.write(pprint.pformat(data_orig, indent=0, width=120)) - with open(name_only_temp + ".rewrite.py", 'w') as fh: - fh.write(pprint.pformat(data_reimport, indent=0, width=120)) + for config in PRESET_PREFS.get(name_only, ((),)): + name_only_with_config = name_only + keyconfig_config_as_filename_component(config) + print("KeyMap Validate:", name_only_with_config, end=" ... ") + data_orig = keyconfig_activate_and_extract_data( + filepath, + relaxed=relaxed, + config=config, + ) + + with tempfile.TemporaryDirectory() as dir_temp: + filepath_temp = os.path.join( + dir_temp, + name_only_with_config + ".test" + ".py", + ) + + bpy.ops.preferences.keyconfig_export(filepath=filepath_temp, all=True) + data_reimport = keyconfig_activate_and_extract_data( + filepath_temp, + relaxed=relaxed, + # No configuration supported when loading exported key-maps. + config=(), + ) + + # Comparing a pretty printed string tends to give more useful + # text output compared to the data-structure. Both will work. + if (cmp_message := report_humanly_readable_difference( + pprint.pformat(data_orig, indent=0, width=120), + pprint.pformat(data_reimport, indent=0, width=120), + )): + error_text_consistency = "Keymap %s has inconsistency on re-importing." % cmp_message + else: + error_text_consistency = "" + + # Perform an additional sanity check: + # That there are no identical key-map items. + error_text_duplicates = keyconfig_report_duplicates(data_orig) + + if error_text_consistency or error_text_duplicates: + print("FAILED!") + print("%r has errors!" % filepath) + if error_text_consistency: + print(error_text_consistency) + if error_text_duplicates: + print(error_text_duplicates) + else: + print("OK!") + + if WRITE_OUTPUT_DIR: + os.makedirs(WRITE_OUTPUT_DIR, exist_ok=True) + name_only_temp = os.path.join(WRITE_OUTPUT_DIR, name_only_with_config) + print("Writing data to:", name_only_temp + ".*.py") + with open(name_only_temp + ".orig.py", 'w') as fh: + fh.write(pprint.pformat(data_orig, indent=0, width=120)) + with open(name_only_temp + ".rewrite.py", 'w') as fh: + fh.write(pprint.pformat(data_reimport, indent=0, width=120)) if has_error: sys.exit(1) -- cgit v1.2.3 From 813895f7133a0f07115362f90e6ffb4df1d21216 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 21 Feb 2022 12:21:47 +1100 Subject: Cleanup: use strict type checking for bl_keymap_validate --- tests/python/bl_keymap_validate.py | 43 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py index 9d613fc90f4..9408f641718 100644 --- a/tests/python/bl_keymap_validate.py +++ b/tests/python/bl_keymap_validate.py @@ -28,7 +28,17 @@ NOTE: """ import types -import typing +from typing import ( + Any, + Dict, + Generator, + List, + Optional, + Sequence, + Tuple, +) + +KeyConfigData = List[Tuple[str, Tuple[Any], Dict[str, Any]]] import os import contextlib @@ -60,7 +70,7 @@ PRESET_PREFS = { def temp_fn_argument_extractor( mod: types.ModuleType, mod_attr: str, -) -> typing.Iterator[typing.List[typing.Tuple[list, dict]]]: +) -> Generator[List[Tuple[Tuple[Tuple[Any], ...], Dict[str, Dict[str, Any]]]], None, None]: """ Temporarily intercept a function, so it's arguments can be extracted. The context manager gives us a list where each item is a tuple of @@ -69,7 +79,7 @@ def temp_fn_argument_extractor( args_collected = [] real_fn = getattr(mod, mod_attr) - def wrap_fn(*args, **kw): + def wrap_fn(*args: Tuple[Any], **kw: Dict[str, Any]) -> Any: args_collected.append((args, kw)) return real_fn(*args, **kw) setattr(mod, mod_attr, wrap_fn) @@ -81,10 +91,10 @@ def temp_fn_argument_extractor( def round_float_32(f: float) -> float: from struct import pack, unpack - return unpack("f", pack("f", f))[0] + return unpack("f", pack("f", f))[0] # type: ignore -def report_humanly_readable_difference(a: typing.Any, b: typing.Any) -> typing.Optional[str]: +def report_humanly_readable_difference(a: Any, b: Any) -> Optional[str]: """ Compare strings, return None whrn they match, otherwise a humanly readable difference message. @@ -101,7 +111,7 @@ def report_humanly_readable_difference(a: typing.Any, b: typing.Any) -> typing.O # ----------------------------------------------------------------------------- # Keymap Utilities. -def keyconfig_preset_scan() -> typing.List[str]: +def keyconfig_preset_scan() -> List[str]: """ Return all bundled presets (keymaps), not user presets. """ @@ -119,7 +129,7 @@ def keyconfig_preset_scan() -> typing.List[str]: ] -def keymap_item_property_clean(value: typing.Any) -> typing.Any: +def keymap_item_property_clean(value: Any) -> Any: """ Recursive property sanitize. @@ -133,12 +143,13 @@ def keymap_item_property_clean(value: typing.Any) -> typing.Any: return sorted( # Convert to `dict` to de-duplicate. dict([(k, keymap_item_property_clean(v)) for k, v in value]).items(), - key=lambda item: item[0], + # Ignore type checking, these are strings which we know can be sorted. + key=lambda item: item[0], # type: ignore ) return value -def keymap_data_clean(keyconfig_data: typing.List, *, relaxed: bool) -> None: +def keymap_data_clean(keyconfig_data: KeyConfigData, *, relaxed: bool) -> None: """ Order & sanitize keymap data so the result from the hand written Python script is comparable with data exported & imported. @@ -168,7 +179,7 @@ def keymap_data_clean(keyconfig_data: typing.List, *, relaxed: bool) -> None: items[i] = item_op, item_event, None -def keyconfig_config_as_filename_component(values: typing.Sequence[typing.Tuple[str, typing.Any]]): +def keyconfig_config_as_filename_component(values: Sequence[Tuple[str, Any]]) -> str: """ Takes a configuration, eg: @@ -194,8 +205,8 @@ def keyconfig_activate_and_extract_data( filepath: str, *, relaxed: bool, - config: typing.Sequence[typing.Tuple[str, typing.Any]], -) -> typing.List: + config: Sequence[Tuple[str, Any]], +) -> KeyConfigData: """ Activate the key-map by filepath, return the key-config data (cleaned for comparison). @@ -214,12 +225,14 @@ def keyconfig_activate_and_extract_data( # If called multiple times, something strange is happening. assert(len(args_collected) == 1) args, _kw = args_collected[0] - keyconfig_data = args[1] + # Ignore the type check as `temp_fn_argument_extractor` is a generic function + # which doesn't contain type information of the function being wrapped. + keyconfig_data: KeyConfigData = args[1] # type: ignore keymap_data_clean(keyconfig_data, relaxed=relaxed) return keyconfig_data -def keyconfig_report_duplicates(keyconfig_data: typing.List) -> str: +def keyconfig_report_duplicates(keyconfig_data: KeyConfigData) -> str: """ Return true if any of the key-maps have duplicate items. @@ -228,7 +241,7 @@ def keyconfig_report_duplicates(keyconfig_data: typing.List) -> str: error_text = [] for km_idname, km_args, km_items_data in keyconfig_data: items = tuple(km_items_data["items"]) - unique: typing.Dict[str, typing.List[int]] = {} + unique: Dict[str, List[int]] = {} for i, (item_op, item_event, item_prop) in enumerate(items): # Ensure stable order as `repr` will use order of definition. item_event = {key: item_event[key] for key in sorted(item_event.keys())} -- cgit v1.2.3 From 9189191c5b1665ae91a4a9582bd4ff762e44072b Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Sun, 20 Feb 2022 23:31:08 -0300 Subject: Fix errors in 'gpu.state' documentation `blend_depth_test_get` --> `depth_test_get` `depth_mask_set_get` --> `depth_mask_get` Thanks to @SBCV for pointing out these inconsistencies. --- source/blender/python/gpu/gpu_py_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 059e6ceb749..e3ffd3cc823 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -151,7 +151,7 @@ static PyObject *pygpu_state_depth_test_set(PyObject *UNUSED(self), PyObject *va } PyDoc_STRVAR(pygpu_state_depth_test_get_doc, - ".. function:: blend_depth_test_get()\n" + ".. function:: depth_test_get()\n" "\n" " Current depth_test equation.\n" "\n"); @@ -179,7 +179,7 @@ static PyObject *pygpu_state_depth_mask_set(PyObject *UNUSED(self), PyObject *va } PyDoc_STRVAR(pygpu_state_depth_mask_get_doc, - ".. function:: depth_mask_set_get()\n" + ".. function:: depth_mask_get()\n" "\n" " Writing status in the depth component.\n"); static PyObject *pygpu_state_depth_mask_get(PyObject *UNUSED(self)) -- cgit v1.2.3 From bce810f05763b3ccf9f7331e96541be2df38f29b Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 21 Feb 2022 07:48:49 +0100 Subject: Fix T95871: Non-float textures report as float. Althought the float buffers are only used as cache, current code paths don't look at the flags to identify which kind of image it is. Actual fix would be to check flags, but that wouldn't be something to add one week before release. This commit fixes it by removing the buffers after use in the image engine. --- source/blender/draw/engines/image/image_drawing_mode.hh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh index 124f8057cfe..ccb0f3e963a 100644 --- a/source/blender/draw/engines/image/image_drawing_mode.hh +++ b/source/blender/draw/engines/image/image_drawing_mode.hh @@ -223,7 +223,7 @@ template class ScreenSpaceDrawingMode : public AbstractD if (iterator.tile_data.tile_buffer == nullptr) { continue; } - ensure_float_buffer(*iterator.tile_data.tile_buffer); + const bool do_free_float_buffer = ensure_float_buffer(*iterator.tile_data.tile_buffer); const float tile_width = static_cast(iterator.tile_data.tile_buffer->x); const float tile_height = static_cast(iterator.tile_data.tile_buffer->y); @@ -330,6 +330,10 @@ template class ScreenSpaceDrawingMode : public AbstractD 0); imb_freerectImbuf_all(&extracted_buffer); } + + if (do_free_float_buffer) { + imb_freerectfloatImBuf(iterator.tile_data.tile_buffer); + } } } @@ -405,10 +409,7 @@ template class ScreenSpaceDrawingMode : public AbstractD { const int texture_width = texture_buffer.x; const int texture_height = texture_buffer.y; - const bool float_buffer_created = ensure_float_buffer(tile_buffer); - /* TODO(jbakker): Find leak when rendering VSE and don't free here. */ - const bool do_free_float_buffer = float_buffer_created && - instance_data.image->type == IMA_TYPE_R_RESULT; + const bool do_free_float_buffer = ensure_float_buffer(tile_buffer); /* IMB_transform works in a non-consistent space. This should be documented or fixed!. * Construct a variant of the info_uv_to_texture that adds the texel space -- cgit v1.2.3 From 5be74160c06a51fa61dce6293ddbd538562e766b Mon Sep 17 00:00:00 2001 From: Ankit Meel Date: Mon, 21 Feb 2022 15:19:19 +0530 Subject: macOS/ blender_icons_update.py: prioritise environment variables Also fix binary name for mac Reviewed By: campbellbarton Differential Revision: https://developer.blender.org/D14161 --- release/datafiles/blender_icons_update.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/release/datafiles/blender_icons_update.py b/release/datafiles/blender_icons_update.py index f4299fc510f..c1cf8d6fcb0 100755 --- a/release/datafiles/blender_icons_update.py +++ b/release/datafiles/blender_icons_update.py @@ -26,8 +26,8 @@ if sys.platform[:3] == "win": env["SystemDrive"] = os.environ.get("SystemDrive", "") env["SystemRoot"] = os.environ.get("SystemRoot", "") -inkscape_bin = os.environ.get("INKSCAPE_BIN", "inkscape") -blender_bin = os.environ.get("BLENDER_BIN", "blender") +inkscape_bin = "inkscape" +blender_bin = "blender" if sys.platform == 'darwin': inkscape_app_path = '/Applications/Inkscape.app/Contents/MacOS/inkscape' @@ -36,6 +36,11 @@ if sys.platform == 'darwin': blender_app_path = '/Applications/Blender.app/Contents/MacOS/Blender' if os.path.exists(blender_app_path): blender_bin = blender_app_path + else: + blender_bin = "Blender" + +inkscape_bin = os.environ.get("INKSCAPE_BIN", inkscape_bin) +blender_bin = os.environ.get("BLENDER_BIN", blender_bin) cmd = ( inkscape_bin, -- cgit v1.2.3 From 141d5851d7d240dbe96854553c7a2c076a1b68bd Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 21 Feb 2022 11:52:46 +0100 Subject: Paint: decouple op->customdata from PaintStroke Previously, all operators using `PaintStroke` would have to store the stroke in `op->customdata`. That made it impossible to store other operator specific data in `op->customdata` that was unrelated to the stroke. This patch changes it so that the `PaintStroke` is passed to api functions as a separate argument, which allows storing the stroke as a subfield of some other struct in `op->customdata`. --- .../editors/sculpt_paint/curves_sculpt_ops.cc | 9 +++- source/blender/editors/sculpt_paint/paint_image.c | 18 +++++-- source/blender/editors/sculpt_paint/paint_intern.h | 11 +++-- source/blender/editors/sculpt_paint/paint_stroke.c | 56 ++++++++++------------ source/blender/editors/sculpt_paint/paint_vertex.c | 26 ++++++---- source/blender/editors/sculpt_paint/sculpt.c | 15 ++++-- 6 files changed, 80 insertions(+), 55 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index ead016174c9..c4421210379 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -59,9 +59,14 @@ static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEven return OPERATOR_RUNNING_MODAL; } +static int sculpt_curves_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, static_cast(op->customdata)); +} + static void sculpt_curves_stroke_cancel(bContext *C, wmOperator *op) { - paint_stroke_cancel(C, op); + paint_stroke_cancel(C, op, static_cast(op->customdata)); } static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) @@ -71,7 +76,7 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) ot->description = "Sculpt curves using a brush"; ot->invoke = sculpt_curves_stroke_invoke; - ot->modal = paint_stroke_modal; + ot->modal = sculpt_curves_stroke_modal; ot->cancel = sculpt_curves_stroke_cancel; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index df9e2cf136f..ad8303418f4 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -685,7 +685,7 @@ static int paint_invoke(bContext *C, wmOperator *op, const wmEvent *event) event->type); if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op); + paint_stroke_free(C, op, op->customdata); return OPERATOR_FINISHED; } /* add modal handler */ @@ -720,7 +720,17 @@ static int paint_exec(bContext *C, wmOperator *op) paint_stroke_done, 0); /* frees op->customdata */ - return paint_stroke_exec(C, op); + return paint_stroke_exec(C, op, op->customdata); +} + +static int paint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, op->customdata); +} + +static void paint_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op, op->customdata); } void PAINT_OT_image_paint(wmOperatorType *ot) @@ -732,10 +742,10 @@ void PAINT_OT_image_paint(wmOperatorType *ot) /* api callbacks */ ot->invoke = paint_invoke; - ot->modal = paint_stroke_modal; + ot->modal = paint_modal; ot->exec = paint_exec; ot->poll = image_paint_poll; - ot->cancel = paint_stroke_cancel; + ot->cancel = paint_cancel; /* flags */ ot->flag = OPTYPE_BLOCKING; diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index f1e79b98b83..f7447ec31b8 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -62,7 +62,7 @@ struct PaintStroke *paint_stroke_new(struct bContext *C, StrokeRedraw redraw, StrokeDone done, int event_type); -void paint_stroke_free(struct bContext *C, struct wmOperator *op); +void paint_stroke_free(struct bContext *C, struct wmOperator *op, struct PaintStroke *stroke); /** * Returns zero if the stroke dots should not be spaced, non-zero otherwise. @@ -84,9 +84,12 @@ bool paint_supports_jitter(enum ePaintMode mode); * Called in paint_ops.c, on each regeneration of key-maps. */ struct wmKeyMap *paint_stroke_modal_keymap(struct wmKeyConfig *keyconf); -int paint_stroke_modal(struct bContext *C, struct wmOperator *op, const struct wmEvent *event); -int paint_stroke_exec(struct bContext *C, struct wmOperator *op); -void paint_stroke_cancel(struct bContext *C, struct wmOperator *op); +int paint_stroke_modal(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *event, + struct PaintStroke *stroke); +int paint_stroke_exec(struct bContext *C, struct wmOperator *op, struct PaintStroke *stroke); +void paint_stroke_cancel(struct bContext *C, struct wmOperator *op, struct PaintStroke *stroke); bool paint_stroke_flipped(struct PaintStroke *stroke); bool paint_stroke_inverted(struct PaintStroke *stroke); struct ViewContext *paint_stroke_view_context(struct PaintStroke *stroke); diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 9bbfb81e08e..ebb5db8d712 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -505,16 +505,13 @@ static bool paint_stroke_use_jitter(ePaintMode mode, Brush *brush, bool invert) } /* Put the location of the next stroke dot into the stroke RNA and apply it to the mesh */ -static void paint_brush_stroke_add_step(bContext *C, - wmOperator *op, - const float mouse_in[2], - float pressure) +static void paint_brush_stroke_add_step( + bContext *C, wmOperator *op, PaintStroke *stroke, const float mouse_in[2], float pressure) { Scene *scene = CTX_data_scene(C); Paint *paint = BKE_paint_get_active_from_context(C); ePaintMode mode = BKE_paintmode_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); - PaintStroke *stroke = op->customdata; UnifiedPaintSettings *ups = stroke->ups; float mouse_out[2]; PointerRNA itemptr; @@ -785,12 +782,12 @@ static float paint_space_stroke_spacing_variable(bContext *C, * towards the final mouse location. */ static int paint_space_stroke(bContext *C, wmOperator *op, + PaintStroke *stroke, const float final_mouse[2], float final_pressure) { const Scene *scene = CTX_data_scene(C); ARegion *region = CTX_wm_region(C); - PaintStroke *stroke = op->customdata; UnifiedPaintSettings *ups = stroke->ups; Paint *paint = BKE_paint_get_active_from_context(C); ePaintMode mode = BKE_paintmode_get_active_from_context(C); @@ -852,7 +849,7 @@ static int paint_space_stroke(bContext *C, spacing / no_pressure_spacing); stroke->stroke_distance += spacing / stroke->zoom_2d; - paint_brush_stroke_add_step(C, op, mouse, pressure); + paint_brush_stroke_add_step(C, op, stroke, mouse, pressure); length -= spacing; pressure = stroke->last_pressure; @@ -929,7 +926,7 @@ PaintStroke *paint_stroke_new(bContext *C, return stroke; } -void paint_stroke_free(bContext *C, wmOperator *op) +void paint_stroke_free(bContext *C, wmOperator *UNUSED(op), PaintStroke *stroke) { RegionView3D *rv3d = CTX_wm_region_view3d(C); if (rv3d) { @@ -938,7 +935,6 @@ void paint_stroke_free(bContext *C, wmOperator *op) BKE_paint_set_overlay_override(0); - PaintStroke *stroke = op->customdata; if (stroke == NULL) { return; } @@ -961,12 +957,11 @@ void paint_stroke_free(bContext *C, wmOperator *op) BLI_freelistN(&stroke->line); - MEM_SAFE_FREE(op->customdata); + MEM_SAFE_FREE(stroke); } -static void stroke_done(bContext *C, wmOperator *op) +static void stroke_done(bContext *C, wmOperator *op, PaintStroke *stroke) { - PaintStroke *stroke = op->customdata; UnifiedPaintSettings *ups = stroke->ups; /* reset rotation here to avoid doing so in cursor display */ @@ -988,7 +983,7 @@ static void stroke_done(bContext *C, wmOperator *op) } } - paint_stroke_free(C, op); + paint_stroke_free(C, op, stroke); } bool paint_space_stroke_enabled(Brush *br, ePaintMode mode) @@ -1230,7 +1225,7 @@ static void paint_line_strokes_spacing(bContext *C, ups->overlap_factor = paint_stroke_integrate_overlap(stroke->brush, 1.0); stroke->stroke_distance += spacing / stroke->zoom_2d; - paint_brush_stroke_add_step(C, op, mouse, 1.0); + paint_brush_stroke_add_step(C, op, stroke, mouse, 1.0); length -= spacing; spacing_final = spacing; @@ -1252,8 +1247,8 @@ static void paint_stroke_line_end(bContext *C, if (stroke->stroke_started && (br->flag & BRUSH_LINE)) { stroke->ups->overlap_factor = paint_stroke_integrate_overlap(br, 1.0); - paint_brush_stroke_add_step(C, op, stroke->last_mouse_position, 1.0); - paint_space_stroke(C, op, mouse, 1.0); + paint_brush_stroke_add_step(C, op, stroke, stroke->last_mouse_position, 1.0); + paint_space_stroke(C, op, stroke, mouse, 1.0); } } @@ -1331,7 +1326,7 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str stroke->stroke_started = stroke->test_start(C, op, stroke->last_mouse_position); if (stroke->stroke_started) { - paint_brush_stroke_add_step(C, op, data + 2 * j, 1.0); + paint_brush_stroke_add_step(C, op, stroke, data + 2 * j, 1.0); paint_line_strokes_spacing( C, op, stroke, spacing, &length_residue, data + 2 * j, data + 2 * (j + 1)); } @@ -1343,7 +1338,7 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str } } - stroke_done(C, op); + stroke_done(C, op, stroke); #ifdef DEBUG_TIME TIMEIT_END_AVERAGED(whole_stroke); @@ -1384,11 +1379,10 @@ static void paint_stroke_line_constrain(PaintStroke *stroke, float mouse[2]) } } -int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintStroke *stroke) { Paint *p = BKE_paint_get_active_from_context(C); ePaintMode mode = BKE_paintmode_get_active_from_context(C); - PaintStroke *stroke = op->customdata; Brush *br = stroke->brush = BKE_paint_brush(p); PaintSample sample_average; float mouse[2]; @@ -1482,7 +1476,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) op->type->cancel(C, op); } else { - paint_stroke_cancel(C, op); + paint_stroke_cancel(C, op, stroke); } return OPERATOR_CANCELLED; } @@ -1492,13 +1486,13 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) copy_v2_fl2(mouse, event->mval[0], event->mval[1]); paint_stroke_line_constrain(stroke, mouse); paint_stroke_line_end(C, op, stroke, mouse); - stroke_done(C, op); + stroke_done(C, op, stroke); return OPERATOR_FINISHED; } } else if (ELEM(event->type, EVT_RETKEY, EVT_SPACEKEY)) { paint_stroke_line_end(C, op, stroke, sample_average.mouse); - stroke_done(C, op); + stroke_done(C, op, stroke); return OPERATOR_FINISHED; } else if (br->flag & BRUSH_LINE) { @@ -1530,7 +1524,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) if (paint_smooth_stroke(stroke, &sample_average, mode, mouse, &pressure)) { if (stroke->stroke_started) { if (paint_space_stroke_enabled(br, mode)) { - if (paint_space_stroke(C, op, mouse, pressure)) { + if (paint_space_stroke(C, op, stroke, mouse, pressure)) { redraw = true; } } @@ -1538,7 +1532,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) float dmouse[2]; sub_v2_v2v2(dmouse, mouse, stroke->last_mouse_position); stroke->stroke_distance += len_v2(dmouse); - paint_brush_stroke_add_step(C, op, mouse, pressure); + paint_brush_stroke_add_step(C, op, stroke, mouse, pressure); redraw = true; } } @@ -1549,7 +1543,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) * instead of waiting till we have moved the space distance */ if (first_dab && paint_space_stroke_enabled(br, mode) && !(br->flag & BRUSH_SMOOTH_STROKE)) { stroke->ups->overlap_factor = paint_stroke_integrate_overlap(br, 1.0); - paint_brush_stroke_add_step(C, op, sample_average.mouse, sample_average.pressure); + paint_brush_stroke_add_step(C, op, stroke, sample_average.mouse, sample_average.pressure); redraw = true; } @@ -1572,10 +1566,8 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } -int paint_stroke_exec(bContext *C, wmOperator *op) +int paint_stroke_exec(bContext *C, wmOperator *op, PaintStroke *stroke) { - PaintStroke *stroke = op->customdata; - /* only when executed for the first time */ if (stroke->stroke_started == 0) { PropertyRNA *strokeprop; @@ -1599,14 +1591,14 @@ int paint_stroke_exec(bContext *C, wmOperator *op) bool ok = (stroke->stroke_started != 0); - stroke_done(C, op); + stroke_done(C, op, stroke); return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } -void paint_stroke_cancel(bContext *C, wmOperator *op) +void paint_stroke_cancel(bContext *C, wmOperator *op, PaintStroke *stroke) { - stroke_done(C, op); + stroke_done(C, op, stroke); } ViewContext *paint_stroke_view_context(PaintStroke *stroke) diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index a82636023f8..8373f028109 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -2551,7 +2551,7 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) event->type); if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op); + paint_stroke_free(C, op, op->customdata); return OPERATOR_FINISHED; } /* add modal handler */ @@ -2575,7 +2575,7 @@ static int wpaint_exec(bContext *C, wmOperator *op) 0); /* frees op->customdata */ - paint_stroke_exec(C, op); + paint_stroke_exec(C, op, op->customdata); return OPERATOR_FINISHED; } @@ -2588,7 +2588,12 @@ static void wpaint_cancel(bContext *C, wmOperator *op) ob->sculpt->cache = NULL; } - paint_stroke_cancel(C, op); + paint_stroke_cancel(C, op, op->customdata); +} + +static int wpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, op->customdata); } void PAINT_OT_weight_paint(wmOperatorType *ot) @@ -2600,7 +2605,7 @@ void PAINT_OT_weight_paint(wmOperatorType *ot) /* api callbacks */ ot->invoke = wpaint_invoke; - ot->modal = paint_stroke_modal; + ot->modal = wpaint_modal; ot->exec = wpaint_exec; ot->poll = weight_paint_poll; ot->cancel = wpaint_cancel; @@ -3497,7 +3502,7 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) event->type); if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op); + paint_stroke_free(C, op, op->customdata); return OPERATOR_FINISHED; } @@ -3522,7 +3527,7 @@ static int vpaint_exec(bContext *C, wmOperator *op) 0); /* frees op->customdata */ - paint_stroke_exec(C, op); + paint_stroke_exec(C, op, op->customdata); return OPERATOR_FINISHED; } @@ -3535,7 +3540,12 @@ static void vpaint_cancel(bContext *C, wmOperator *op) ob->sculpt->cache = NULL; } - paint_stroke_cancel(C, op); + paint_stroke_cancel(C, op, op->customdata); +} + +static int vpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, op->customdata); } void PAINT_OT_vertex_paint(wmOperatorType *ot) @@ -3547,7 +3557,7 @@ void PAINT_OT_vertex_paint(wmOperatorType *ot) /* api callbacks */ ot->invoke = vpaint_invoke; - ot->modal = paint_stroke_modal; + ot->modal = vpaint_modal; ot->exec = vpaint_exec; ot->poll = vertex_paint_poll; ot->cancel = vpaint_cancel; diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 68edf9cd54a..63611b473cb 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -5333,12 +5333,12 @@ static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); if (ignore_background_click && !over_mesh(C, op, event->xy[0], event->xy[1])) { - paint_stroke_free(C, op); + paint_stroke_free(C, op, op->customdata); return OPERATOR_PASS_THROUGH; } if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op); + paint_stroke_free(C, op, op->customdata); return OPERATOR_FINISHED; } /* Add modal handler. */ @@ -5364,7 +5364,7 @@ static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) 0); /* Frees op->customdata. */ - paint_stroke_exec(C, op); + paint_stroke_exec(C, op, op->customdata); return OPERATOR_FINISHED; } @@ -5382,7 +5382,7 @@ static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) paint_mesh_restore_co(sd, ob); } - paint_stroke_cancel(C, op); + paint_stroke_cancel(C, op, op->customdata); if (ss->cache) { SCULPT_cache_free(ss->cache); @@ -5392,6 +5392,11 @@ static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) sculpt_brush_exit_tex(sd); } +static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, op->customdata); +} + void SCULPT_OT_brush_stroke(wmOperatorType *ot) { /* Identifiers. */ @@ -5401,7 +5406,7 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot) /* API callbacks. */ ot->invoke = sculpt_brush_stroke_invoke; - ot->modal = paint_stroke_modal; + ot->modal = sculpt_brush_stroke_modal; ot->exec = sculpt_brush_stroke_exec; ot->poll = SCULPT_poll; ot->cancel = sculpt_brush_stroke_cancel; -- cgit v1.2.3 From 7f2beb79c656bcacc7d0492da13df8379cf44311 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 21 Feb 2022 11:57:43 +0100 Subject: Cleanup: move curves sculpt mode toggle operator to sculpt/paint module This is necessary, because the operator will have to use functions that are currently private within this module. E.g. `paint_cursor_start`. --- source/blender/editors/curves/intern/curves_ops.cc | 66 ---------------------- .../editors/sculpt_paint/curves_sculpt_ops.cc | 52 +++++++++++++++++ 2 files changed, 52 insertions(+), 66 deletions(-) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 0aa8c4b253e..52a55ae1760 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -4,74 +4,8 @@ * \ingroup edcurves */ -#include "BLI_utildefines.h" - #include "ED_curves.h" -#include "ED_object.h" - -#include "WM_api.h" -#include "WM_toolsystem.h" -#include "WM_types.h" - -#include "BKE_context.h" -#include "BKE_paint.h" - -#include "DNA_scene_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_types.h" - -static bool curves_sculptmode_toggle_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == nullptr) { - return false; - } - if (ob->type != OB_CURVES) { - return false; - } - return true; -} - -static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - const bool is_mode_set = ob->mode == OB_MODE_SCULPT_CURVES; - - if (is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, OB_MODE_SCULPT_CURVES, op->reports)) { - return OPERATOR_CANCELLED; - } - } - - if (is_mode_set) { - ob->mode = OB_MODE_OBJECT; - } - else { - BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); - ob->mode = OB_MODE_SCULPT_CURVES; - } - - WM_toolsystem_update_from_context_view3d(C); - WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); - return OPERATOR_CANCELLED; -} - -static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) -{ - ot->name = "Curve Sculpt Mode Toggle"; - ot->idname = "CURVES_OT_sculptmode_toggle"; - ot->description = "Enter/Exit sculpt mode for curves"; - - ot->exec = curves_sculptmode_toggle_exec; - ot->poll = curves_sculptmode_toggle_poll; - - ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; -} void ED_operatortypes_curves() { - WM_operatortype_append(CURVES_OT_sculptmode_toggle); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index c4421210379..137a43ea661 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -6,8 +6,10 @@ #include "BKE_paint.h" #include "WM_api.h" +#include "WM_toolsystem.h" #include "ED_curves_sculpt.h" +#include "ED_object.h" #include "curves_sculpt_intern.h" #include "paint_intern.h" @@ -84,7 +86,57 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) paint_stroke_operator_properties(ot); } +static bool curves_sculptmode_toggle_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr) { + return false; + } + if (ob->type != OB_CURVES) { + return false; + } + return true; +} + +static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + const bool is_mode_set = ob->mode == OB_MODE_SCULPT_CURVES; + + if (is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, OB_MODE_SCULPT_CURVES, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (is_mode_set) { + ob->mode = OB_MODE_OBJECT; + } + else { + BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); + ob->mode = OB_MODE_SCULPT_CURVES; + } + + WM_toolsystem_update_from_context_view3d(C); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); + return OPERATOR_CANCELLED; +} + +static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) +{ + ot->name = "Curve Sculpt Mode Toggle"; + ot->idname = "CURVES_OT_sculptmode_toggle"; + ot->description = "Enter/Exit sculpt mode for curves"; + + ot->exec = curves_sculptmode_toggle_exec; + ot->poll = curves_sculptmode_toggle_poll; + + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; +} + void ED_operatortypes_sculpt_curves() { WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); + WM_operatortype_append(CURVES_OT_sculptmode_toggle); } -- cgit v1.2.3 From fcb84e32e007fffeb9bcc573917d31857c9dd0cc Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 21 Feb 2022 12:01:30 +0100 Subject: Cleanup: use namespace and code sections --- .../blender/editors/sculpt_paint/curves_sculpt_ops.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 137a43ea661..3f732f6ac79 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -20,6 +20,12 @@ bool CURVES_SCULPT_mode_poll(struct bContext *C) return ob && ob->mode & OB_MODE_SCULPT_CURVES; } +namespace blender::ed::sculpt_paint { + +/* -------------------------------------------------------------------- + * SCULPT_CURVES_OT_brush_stroke. + */ + static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) { out[0] = mouse[0]; @@ -86,6 +92,10 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) paint_stroke_operator_properties(ot); } +/* -------------------------------------------------------------------- + * CURVES_OT_sculptmode_toggle. + */ + static bool curves_sculptmode_toggle_poll(bContext *C) { Object *ob = CTX_data_active_object(C); @@ -135,8 +145,15 @@ static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; } +} // namespace blender::ed::sculpt_paint + +/* -------------------------------------------------------------------- + * Registration. + */ + void ED_operatortypes_sculpt_curves() { + using namespace blender::ed::sculpt_paint; WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); WM_operatortype_append(CURVES_OT_sculptmode_toggle); } -- cgit v1.2.3 From e2ffe88983938651a641e3d1be65f43c65a54901 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 21 Feb 2022 12:49:36 +0100 Subject: Curves: use paint cursor in curves sculpt mode Also adds radius and strength control to the tool settings in the ui. --- .../keyconfig/keymap_data/blender_default.py | 1 + .../startup/bl_ui/properties_paint_common.py | 2 ++ release/scripts/startup/bl_ui/space_view3d.py | 32 +++++++++++++++++++ .../editors/sculpt_paint/curves_sculpt_intern.h | 1 + .../editors/sculpt_paint/curves_sculpt_ops.cc | 37 +++++++++++++++++++--- source/blender/makesrna/intern/rna_scene.c | 4 +++ source/blender/makesrna/intern/rna_sculpt_paint.c | 15 +++++++++ 7 files changed, 87 insertions(+), 5 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 62afe16d106..a9de17af496 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5467,6 +5467,7 @@ def km_sculpt_curves(params): items.extend([ ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), + *_template_paint_radial_control("curves_sculpt"), ]) return keymap diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index ca623797c49..9e40a8d364a 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -79,6 +79,8 @@ class UnifiedPaintPanel: return tool_settings.gpencil_weight_paint elif mode == 'VERTEX_GPENCIL': return tool_settings.gpencil_vertex_paint + elif mode == 'SCULPT_CURVES': + return tool_settings.curves_sculpt return None @staticmethod diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 4174ec95e83..2ac6358bd9c 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -468,6 +468,38 @@ class _draw_tool_settings_context_mode: return True + @staticmethod + def SCULPT_CURVES(context, layout, tool): + if (tool is None) or (not tool.has_datablock): + return False + + paint = context.tool_settings.curves_sculpt + layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True) + + brush = paint.brush + if brush is None: + return False + + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "size", + unified_name="use_unified_size", + text="Radius", + slider=True, + header=True + ) + + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "strength", + unified_name="use_unified_strength", + header=True + ) + class VIEW3D_HT_header(Header): bl_space_type = 'VIEW_3D' diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.h b/source/blender/editors/sculpt_paint/curves_sculpt_intern.h index 6a96a8e0e9f..0d99e61192f 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.h @@ -9,6 +9,7 @@ extern "C" { #endif bool CURVES_SCULPT_mode_poll(struct bContext *C); +bool CURVES_SCULPT_mode_poll_view3d(struct bContext *C); #ifdef __cplusplus } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 3f732f6ac79..fb5b1d338a6 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -14,12 +14,23 @@ #include "curves_sculpt_intern.h" #include "paint_intern.h" -bool CURVES_SCULPT_mode_poll(struct bContext *C) +bool CURVES_SCULPT_mode_poll(bContext *C) { Object *ob = CTX_data_active_object(C); return ob && ob->mode & OB_MODE_SCULPT_CURVES; } +bool CURVES_SCULPT_mode_poll_view3d(bContext *C) +{ + if (!CURVES_SCULPT_mode_poll(C)) { + return false; + } + if (CTX_wm_region_view3d(C) == nullptr) { + return false; + } + return true; +} + namespace blender::ed::sculpt_paint { /* -------------------------------------------------------------------- @@ -108,9 +119,26 @@ static bool curves_sculptmode_toggle_poll(bContext *C) return true; } -static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) +static void curves_sculptmode_enter(bContext *C) { Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); + CurvesSculpt *curves_sculpt = scene->toolsettings->curves_sculpt; + + ob->mode = OB_MODE_SCULPT_CURVES; + + paint_cursor_start(&curves_sculpt->paint, CURVES_SCULPT_mode_poll_view3d); +} + +static void curves_sculptmode_exit(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + ob->mode = OB_MODE_OBJECT; +} + +static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) +{ Object *ob = CTX_data_active_object(C); const bool is_mode_set = ob->mode == OB_MODE_SCULPT_CURVES; @@ -121,11 +149,10 @@ static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) } if (is_mode_set) { - ob->mode = OB_MODE_OBJECT; + curves_sculptmode_exit(C); } else { - BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); - ob->mode = OB_MODE_SCULPT_CURVES; + curves_sculptmode_enter(C); } WM_toolsystem_update_from_context_view3d(C); diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 14c91bf7cd1..29b06060256 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2898,6 +2898,10 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_struct_type(prop, "Sculpt"); RNA_def_property_ui_text(prop, "Sculpt", ""); + prop = RNA_def_property(srna, "curves_sculpt", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurvesSculpt"); + RNA_def_property_ui_text(prop, "Curves Sculpt", ""); + prop = RNA_def_property(srna, "use_auto_normalize", PROP_BOOLEAN, PROP_NONE); RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); RNA_def_property_boolean_sdna(prop, NULL, "auto_normalize", 1); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index cb105c22987..473711fb74b 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -423,6 +423,11 @@ static char *rna_UvSculpt_path(PointerRNA *UNUSED(ptr)) return BLI_strdup("tool_settings.uv_sculpt"); } +static char *rna_CurvesSculpt_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("tool_settings.curves_sculpt"); +} + static char *rna_GpPaint_path(PointerRNA *UNUSED(ptr)) { return BLI_strdup("tool_settings.gpencil_paint"); @@ -1498,6 +1503,15 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); } +static void rna_def_curves_sculpt(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "CurvesSculpt", "Paint"); + RNA_def_struct_path_func(srna, "rna_CurvesSculpt_path"); + RNA_def_struct_ui_text(srna, "Curves Sculpt Paint", ""); +} + void RNA_def_sculpt_paint(BlenderRNA *brna) { /* *** Non-Animated *** */ @@ -1516,6 +1530,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna) rna_def_particle_edit(brna); rna_def_gpencil_guides(brna); rna_def_gpencil_sculpt(brna); + rna_def_curves_sculpt(brna); RNA_define_animate_sdna(true); } -- cgit v1.2.3 From 132f9a2e318da5de7432c78fa3b3084c9ec38a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 21 Feb 2022 13:02:11 +0100 Subject: Fix T95596: Crash in versioning of node animation The node animation versioning code passes `nullptr` to the `oldName` and `newName` parameters, but those weren't `NULL`-safe. I added an extra check for this. No functional changes, just a crash fix. --- source/blender/blenkernel/intern/anim_data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 42b72a7cd66..84a1f979082 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -779,7 +779,7 @@ static bool fcurves_path_rename_fix(ID *owner_id, if (fcu->rna_path != old_path) { bActionGroup *agrp = fcu->grp; is_changed = true; - if ((agrp != NULL) && STREQ(oldName, agrp->name)) { + if (oldName != NULL && (agrp != NULL) && STREQ(oldName, agrp->name)) { BLI_strncpy(agrp->name, newName, sizeof(agrp->name)); } } -- cgit v1.2.3 From 829812f180b011dab2c0f09cc3cfd155794b5b8b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 21 Feb 2022 23:09:34 +1100 Subject: Fix T92467: Path Selection broken when Drag is set to Tweak When RMB-select uses "Select Tweak" as a fallback tool, ignore all bindings mapped to the Control key as these are used for path selection. This was fixed in 2a2d873124111b5fcbc2c3c59f73fd1f946c3548 however that caused shift-select to fail (T93100). --- release/scripts/presets/keyconfig/keymap_data/blender_default.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 5e6145723f4..b4f684fa5cc 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4668,7 +4668,9 @@ def _template_paint_radial_control(paint, rotation=False, secondary_rotation=Fal return items -def _template_view3d_select(*, type, value, legacy): +def _template_view3d_select(*, type, value, legacy, exclude_mod=None): + # NOTE: `exclude_mod` is needed since we don't want this tool to exclude Control-RMB actions when this is used + # as a tool key-map with RMB-select and `use_fallback_tool_rmb` is enabled. See T92467. return [( "view3d.select", {"type": type, "value": value, **{m: True for m in mods}}, @@ -4682,7 +4684,7 @@ def _template_view3d_select(*, type, value, legacy): (("center", "enumerate"), ("ctrl", "alt")), (("toggle", "enumerate"), ("shift", "alt")), (("toggle", "center", "enumerate"), ("shift", "ctrl", "alt")), - )] + ) if exclude_mod is None or exclude_mod not in mods] def _template_view3d_gpencil_select(*, type, value, legacy, use_select_mouse=True): @@ -6490,7 +6492,7 @@ def km_3d_view_tool_select(params, *, fallback): *([] if (fallback and (params.select_mouse == 'RIGHTMOUSE')) else _template_items_tool_select( params, "view3d.select", "view3d.cursor3d", extend="toggle")), *([] if (not params.use_fallback_tool_rmb) else _template_view3d_select( - type=params.select_mouse, value=params.select_mouse_value, legacy=params.legacy)), + type=params.select_mouse, value=params.select_mouse_value, legacy=params.legacy, exclude_mod="ctrl")), ]}, ) -- cgit v1.2.3 From 0f242981ec35b44faca5d140502c0133a2631f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 21 Feb 2022 13:30:10 +0100 Subject: Workbench: Fix missing world_data ubo during opaque prepass --- source/blender/draw/engines/workbench/workbench_opaque.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/draw/engines/workbench/workbench_opaque.c b/source/blender/draw/engines/workbench/workbench_opaque.c index e4534d923ab..708e3e93686 100644 --- a/source/blender/draw/engines/workbench/workbench_opaque.c +++ b/source/blender/draw/engines/workbench/workbench_opaque.c @@ -88,11 +88,13 @@ void workbench_opaque_cache_init(WORKBENCH_Data *vedata) sh = workbench_shader_opaque_get(wpd, data); wpd->prepass[opaque][infront][data].common_shgrp = grp = DRW_shgroup_create(sh, pass); + DRW_shgroup_uniform_block(grp, "world_data", wpd->world_ubo); DRW_shgroup_uniform_block(grp, "materials_data", wpd->material_ubo_curr); DRW_shgroup_uniform_int_copy(grp, "materialIndex", -1); DRW_shgroup_uniform_bool_copy(grp, "useMatcap", use_matcap); wpd->prepass[opaque][infront][data].vcol_shgrp = grp = DRW_shgroup_create(sh, pass); + DRW_shgroup_uniform_block(grp, "world_data", wpd->world_ubo); DRW_shgroup_uniform_block(grp, "materials_data", wpd->material_ubo_curr); DRW_shgroup_uniform_int_copy(grp, "materialIndex", 0); /* Default material. (uses vcol) */ DRW_shgroup_uniform_bool_copy(grp, "useMatcap", use_matcap); @@ -100,6 +102,7 @@ void workbench_opaque_cache_init(WORKBENCH_Data *vedata) sh = workbench_shader_opaque_image_get(wpd, data, false); wpd->prepass[opaque][infront][data].image_shgrp = grp = DRW_shgroup_create(sh, pass); + DRW_shgroup_uniform_block(grp, "world_data", wpd->world_ubo); DRW_shgroup_uniform_block(grp, "materials_data", wpd->material_ubo_curr); DRW_shgroup_uniform_int_copy(grp, "materialIndex", 0); /* Default material. */ DRW_shgroup_uniform_bool_copy(grp, "useMatcap", use_matcap); @@ -107,6 +110,7 @@ void workbench_opaque_cache_init(WORKBENCH_Data *vedata) sh = workbench_shader_opaque_image_get(wpd, data, true); wpd->prepass[opaque][infront][data].image_tiled_shgrp = grp = DRW_shgroup_create(sh, pass); + DRW_shgroup_uniform_block(grp, "world_data", wpd->world_ubo); DRW_shgroup_uniform_block(grp, "materials_data", wpd->material_ubo_curr); DRW_shgroup_uniform_int_copy(grp, "materialIndex", 0); /* Default material. */ DRW_shgroup_uniform_bool_copy(grp, "useMatcap", use_matcap); -- cgit v1.2.3 From fa715a158a4c4576e944e79276864b1365a0ccc9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 20 Feb 2022 01:52:43 +0300 Subject: Vertex Weight Mix: support Minimum and Maximum mix modes. The modifier supports arithmetic operations, like Add or Multiply, but for some reason omits Minimum and Maximum. They are similarly simple and useful math functions and should be supported. Differential Revision: https://developer.blender.org/D14164 --- source/blender/makesdna/DNA_modifier_types.h | 4 ++++ source/blender/makesrna/intern/rna_modifier.c | 2 ++ source/blender/modifiers/intern/MOD_weightvgmix.c | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index db5a3d69e4d..36f14e7d357 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -1604,6 +1604,10 @@ enum { MOD_WVG_MIX_DIF = 6, /** Average of both weights. */ MOD_WVG_MIX_AVG = 7, + /** Minimum of both weights. */ + MOD_WVG_MIX_MIN = 8, + /** Maximum of both weights. */ + MOD_WVG_MIX_MAX = 9, }; /** #WeightVGMixModifierData.mix_set (what vertices to affect). */ diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 426f37dfb66..9f7e136ebf6 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -5169,6 +5169,8 @@ static void rna_def_modifier_weightvgmix(BlenderRNA *brna) "Difference", "Difference between VGroup A's and VGroup B's weights"}, {MOD_WVG_MIX_AVG, "AVG", 0, "Average", "Average value of VGroup A's and VGroup B's weights"}, + {MOD_WVG_MIX_MIN, "MIN", 0, "Minimum", "Minimum of VGroup A's and VGroup B's weights"}, + {MOD_WVG_MIX_MAX, "MAX", 0, "Maximum", "Maximum of VGroup A's and VGroup B's weights"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/modifiers/intern/MOD_weightvgmix.c b/source/blender/modifiers/intern/MOD_weightvgmix.c index 1078ebfaeb2..1b6472e2d42 100644 --- a/source/blender/modifiers/intern/MOD_weightvgmix.c +++ b/source/blender/modifiers/intern/MOD_weightvgmix.c @@ -105,6 +105,12 @@ static float mix_weight(float weight, float weight2, char mix_mode) if (mix_mode == MOD_WVG_MIX_AVG) { return (weight + weight2) * 0.5f; } + if (mix_mode == MOD_WVG_MIX_MIN) { + return (weight < weight2 ? weight : weight2); + } + if (mix_mode == MOD_WVG_MIX_MAX) { + return (weight > weight2 ? weight : weight2); + } return weight2; } -- cgit v1.2.3 From 68586d2c183bb36464d8de4c50b1c23c47631a83 Mon Sep 17 00:00:00 2001 From: Henrik Dick Date: Mon, 21 Feb 2022 14:05:00 +0100 Subject: Complex Solidify: improve constraints solver The constraints solver is now able to handle more cases correctly. Also the behavior of the boundary fixes is slightly changed if the constraints thickness mode is used. Differential Revision: https://developer.blender.org/D14143 --- .../modifiers/intern/MOD_solidify_nonmanifold.c | 300 ++++++++++++--------- 1 file changed, 176 insertions(+), 124 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c index ed7acef4cdc..e4f7a7b5473 100644 --- a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c +++ b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c @@ -1405,21 +1405,26 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, for (uint j = 0; g->valid; j++, g++) { if (!g->is_singularity) { float *nor = g->no; + /* During vertex position calculation, the algorithm decides if it wants to disable the + * boundary fix to maintain correct thickness. If the used algorithm does not produce a + * free move direction (move_nor), it can use approximate_free_direction to decide on + * a movement direction based on the connected edges. */ float move_nor[3] = {0, 0, 0}; bool disable_boundary_fix = (smd->nonmanifold_boundary_mode == MOD_SOLIDIFY_NONMANIFOLD_BOUNDARY_MODE_NONE || (g->is_orig_closed || g->split)); + bool approximate_free_direction = false; /* Constraints Method. */ if (smd->nonmanifold_offset_mode == MOD_SOLIDIFY_NONMANIFOLD_OFFSET_MODE_CONSTRAINTS) { NewEdgeRef *first_edge = NULL; NewEdgeRef **edge_ptr = g->edges; /* Contains normal and offset [nx, ny, nz, ofs]. */ - float(*normals_queue)[4] = MEM_malloc_arrayN( - g->edges_len + 1, sizeof(*normals_queue), "normals_queue in solidify"); + float(*planes_queue)[4] = MEM_malloc_arrayN( + g->edges_len + 1, sizeof(*planes_queue), "planes_queue in solidify"); uint queue_index = 0; - float face_nors[3][3]; - float nor_ofs[3]; + float fallback_nor[3]; + float fallback_ofs = 0.0f; const bool cycle = (g->is_orig_closed && !g->split) || g->is_even_split; for (uint k = 0; k < g->edges_len; k++, edge_ptr++) { @@ -1436,17 +1441,17 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, } if (!null_faces[face->index]) { - /* And normal to the queue. */ - mul_v3_v3fl(normals_queue[queue_index], + /* And plane to the queue. */ + mul_v3_v3fl(planes_queue[queue_index], poly_nors[face->index], face->reversed ? -1 : 1); - normals_queue[queue_index++][3] = ofs; + planes_queue[queue_index++][3] = ofs; } else { /* Just use this approximate normal of the null face if there is no other * normal to use. */ - mul_v3_v3fl(face_nors[0], poly_nors[face->index], face->reversed ? -1 : 1); - nor_ofs[0] = ofs; + mul_v3_v3fl(fallback_nor, poly_nors[face->index], face->reversed ? -1 : 1); + fallback_ofs = ofs; } } } @@ -1455,131 +1460,170 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, } } } - uint face_nors_len = 0; - const float stop_explosion = 0.999f - fabsf(smd->offset_fac) * 0.05f; - while (queue_index > 0) { - if (face_nors_len == 0) { - if (queue_index <= 2) { - for (uint k = 0; k < queue_index; k++) { - copy_v3_v3(face_nors[k], normals_queue[k]); - nor_ofs[k] = normals_queue[k][3]; + if (queue_index > 2) { + /* Find the two most different normals. */ + float min_p = 2.0f; + uint min_n0 = 0; + uint min_n1 = 0; + for (uint k = 0; k < queue_index; k++) { + for (uint m = k + 1; m < queue_index; m++) { + float p = dot_v3v3(planes_queue[k], planes_queue[m]); + if (p <= min_p + FLT_EPSILON) { + min_p = p; + min_n0 = m; + min_n1 = k; } - face_nors_len = queue_index; - queue_index = 0; } - else { - /* Find most different two normals. */ - float min_p = 2; - uint min_n0 = 0; - uint min_n1 = 0; - for (uint k = 0; k < queue_index; k++) { - for (uint m = k + 1; m < queue_index; m++) { - float p = dot_v3v3(normals_queue[k], normals_queue[m]); - if (p <= min_p + FLT_EPSILON) { - min_p = p; - min_n0 = m; - min_n1 = k; - } - } - } - copy_v3_v3(face_nors[0], normals_queue[min_n0]); - copy_v3_v3(face_nors[1], normals_queue[min_n1]); - nor_ofs[0] = normals_queue[min_n0][3]; - nor_ofs[1] = normals_queue[min_n1][3]; - face_nors_len = 2; - queue_index--; - memmove(normals_queue + min_n0, - normals_queue + min_n0 + 1, - (queue_index - min_n0) * sizeof(*normals_queue)); - queue_index--; - memmove(normals_queue + min_n1, - normals_queue + min_n1 + 1, - (queue_index - min_n1) * sizeof(*normals_queue)); - min_p = 1; - min_n1 = 0; - float max_p = -1; - for (uint k = 0; k < queue_index; k++) { - max_p = -1; - for (uint m = 0; m < face_nors_len; m++) { - float p = dot_v3v3(face_nors[m], normals_queue[k]); - if (p > max_p + FLT_EPSILON) { - max_p = p; - } - } - if (max_p <= min_p + FLT_EPSILON) { - min_p = max_p; - min_n1 = k; - } + } + /* Put the two found normals, first in the array queue. */ + if (min_n1 != 0) { + swap_v4_v4(planes_queue[min_n0], planes_queue[0]); + swap_v4_v4(planes_queue[min_n1], planes_queue[1]); + } + else { + swap_v4_v4(planes_queue[min_n0], planes_queue[1]); + } + /* Find the third most important/different normal. */ + min_p = 1; + min_n1 = 0; + float max_p = -1; + for (uint k = 2; k < queue_index; k++) { + max_p = -1; + for (uint m = 0; m < 2; m++) { + float p = dot_v3v3(planes_queue[m], planes_queue[k]); + if (p > max_p + FLT_EPSILON) { + max_p = p; } - if (min_p < 0.8) { - copy_v3_v3(face_nors[2], normals_queue[min_n1]); - nor_ofs[2] = normals_queue[min_n1][3]; - face_nors_len++; - queue_index--; - memmove(normals_queue + min_n1, - normals_queue + min_n1 + 1, - (queue_index - min_n1) * sizeof(*normals_queue)); + } + if (max_p <= min_p + FLT_EPSILON) { + min_p = max_p; + min_n1 = k; + } + } + swap_v4_v4(planes_queue[min_n1], planes_queue[2]); + } + /* Remove/average duplicate normals in planes_queue. */ + while (queue_index > 0) { + uint best_n0 = 0; + uint best_n1 = 0; + float best_p = -1.0f; + float best_ofs_diff = 0.0f; + for (uint k = 0; k < queue_index; k++) { + for (uint m = k + 1; m < queue_index; m++) { + float p = dot_v3v3(planes_queue[m], planes_queue[k]); + float ofs_diff = fabsf(planes_queue[m][3] - planes_queue[k][3]); + if (p > best_p + FLT_EPSILON || (p >= best_p && ofs_diff < best_ofs_diff)) { + best_p = p; + best_ofs_diff = ofs_diff; + best_n0 = m; + best_n1 = k; } } } - else { - uint best = 0; - uint best_group = 0; - float best_p = -1.0f; - for (uint k = 0; k < queue_index; k++) { - for (uint m = 0; m < face_nors_len; m++) { - float p = dot_v3v3(face_nors[m], normals_queue[k]); - if (p > best_p + FLT_EPSILON) { - best_p = p; - best = m; - best_group = k; + if (best_p < 0.999f) { + break; + } + add_v3_v3(planes_queue[best_n0], planes_queue[best_n1]); + normalize_v3(planes_queue[best_n0]); + planes_queue[best_n0][3] = (planes_queue[best_n0][3] + planes_queue[best_n1][3]) * + 0.5f; + queue_index--; + memmove(planes_queue + best_n1, + planes_queue + best_n1 + 1, + (queue_index - best_n1) * sizeof(*planes_queue)); + } + const uint size = queue_index; + /* If there is more than 2 planes at this vertex, the boundary fix should be disabled + * to stay at the correct thickness for all the faces. This is not very good in + * practice though, since that will almost always disable the boundary fix. Instead + * introduce a threshold which decides whether the boundary fix can be used without + * major thickness changes. If the following constant is 1.0, it would always + * prioritize correct thickness. At 0.7 the thickness is allowed to change a bit if + * necessary for the fix (~10%). Note this only applies if a boundary fix is used. */ + const float boundary_fix_threshold = 0.7f; + if (size > 3) { + /* Use the most general least squares method to find the best position. */ + float mat[3][3]; + zero_m3(mat); + for (int k = 0; k < 3; k++) { + for (int m = 0; m < size; m++) { + madd_v3_v3fl(mat[k], planes_queue[m], planes_queue[m][k]); + } + } + /* NOTE: this matrix invert fails if there is less than 3 different normals. */ + invert_m3(mat); + zero_v3(nor); + for (int k = 0; k < size; k++) { + madd_v3_v3fl(nor, planes_queue[k], planes_queue[k][3]); + } + mul_v3_m3v3(nor, mat, nor); + + if (!disable_boundary_fix) { + /* Figure out if the approximate boundary fix can get use here. */ + float greatest_angle_cos = 1.0f; + for (uint k = 0; k < 2; k++) { + for (uint m = 2; m < size; m++) { + float p = dot_v3v3(planes_queue[m], planes_queue[k]); + if (p < greatest_angle_cos) { + greatest_angle_cos = p; } } } - add_v3_v3(face_nors[best], normals_queue[best_group]); - normalize_v3(face_nors[best]); - nor_ofs[best] = (nor_ofs[best] + normals_queue[best_group][3]) * 0.5f; - queue_index--; - memmove(normals_queue + best_group, - normals_queue + best_group + 1, - (queue_index - best_group) * sizeof(*normals_queue)); + if (greatest_angle_cos > boundary_fix_threshold) { + approximate_free_direction = true; + } + else { + disable_boundary_fix = true; + } } } - MEM_freeN(normals_queue); - - /* When up to 3 constraint normals are found. */ - if (ELEM(face_nors_len, 2, 3)) { - const float q = dot_v3v3(face_nors[0], face_nors[1]); + else if (size > 1) { + /* When up to 3 constraint normals are found, there is a simple solution. */ + const float stop_explosion = 0.999f - fabsf(smd->offset_fac) * 0.05f; + const float q = dot_v3v3(planes_queue[0], planes_queue[1]); float d = 1.0f - q * q; - cross_v3_v3v3(move_nor, face_nors[0], face_nors[1]); + cross_v3_v3v3(move_nor, planes_queue[0], planes_queue[1]); + normalize_v3(move_nor); if (d > FLT_EPSILON * 10 && q < stop_explosion) { d = 1.0f / d; - mul_v3_fl(face_nors[0], (nor_ofs[0] - nor_ofs[1] * q) * d); - mul_v3_fl(face_nors[1], (nor_ofs[1] - nor_ofs[0] * q) * d); + mul_v3_fl(planes_queue[0], (planes_queue[0][3] - planes_queue[1][3] * q) * d); + mul_v3_fl(planes_queue[1], (planes_queue[1][3] - planes_queue[0][3] * q) * d); } else { d = 1.0f / (fabsf(q) + 1.0f); - mul_v3_fl(face_nors[0], nor_ofs[0] * d); - mul_v3_fl(face_nors[1], nor_ofs[1] * d); + mul_v3_fl(planes_queue[0], planes_queue[0][3] * d); + mul_v3_fl(planes_queue[1], planes_queue[1][3] * d); } - add_v3_v3v3(nor, face_nors[0], face_nors[1]); - if (face_nors_len == 3) { - float *free_nor = move_nor; - mul_v3_fl(face_nors[2], nor_ofs[2]); - d = dot_v3v3(face_nors[2], free_nor); + add_v3_v3v3(nor, planes_queue[0], planes_queue[1]); + if (size == 3) { + d = dot_v3v3(planes_queue[2], move_nor); if (LIKELY(fabsf(d) > FLT_EPSILON)) { - sub_v3_v3v3(face_nors[0], nor, face_nors[2]); /* Override face_nor[0]. */ - mul_v3_fl(free_nor, dot_v3v3(face_nors[2], face_nors[0]) / d); - sub_v3_v3(nor, free_nor); + float tmp[3]; + madd_v3_v3v3fl(tmp, nor, planes_queue[2], -planes_queue[2][3]); + mul_v3_v3fl(tmp, move_nor, dot_v3v3(planes_queue[2], tmp) / d); + sub_v3_v3(nor, tmp); + /* Disable boundary fix if the constraints would be majorly unsatisfied. */ + if (fabsf(d) > 1.0f - boundary_fix_threshold) { + disable_boundary_fix = true; + } } + } + approximate_free_direction = false; + } + else if (size == 1) { + /* Face corner case. */ + mul_v3_v3fl(nor, planes_queue[0], planes_queue[0][3]); + if (g->edges_len > 2) { disable_boundary_fix = true; + approximate_free_direction = true; } } else { - BLI_assert(face_nors_len < 2); - mul_v3_v3fl(nor, face_nors[0], nor_ofs[0]); + /* Fallback case for null faces. */ + mul_v3_v3fl(nor, fallback_nor, fallback_ofs); disable_boundary_fix = true; } + MEM_freeN(planes_queue); } /* Fixed/Even Method. */ else { @@ -1707,26 +1751,29 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, } /* Set move_nor for boundary fix. */ if (!disable_boundary_fix && g->edges_len > 2) { - edge_ptr = g->edges + 1; - float tmp[3]; - uint k; - for (k = 1; k + 1 < g->edges_len; k++, edge_ptr++) { - MEdge *e = orig_medge + (*edge_ptr)->old_edge; - sub_v3_v3v3( - tmp, orig_mvert_co[vm[e->v1] == i ? e->v2 : e->v1], orig_mvert_co[i]); - add_v3_v3(move_nor, tmp); - } - if (k == 1) { - disable_boundary_fix = true; - } - else { - disable_boundary_fix = normalize_v3(move_nor) == 0.0f; - } + approximate_free_direction = true; } else { disable_boundary_fix = true; } } + if (approximate_free_direction) { + /* Set move_nor for boundary fix. */ + NewEdgeRef **edge_ptr = g->edges + 1; + float tmp[3]; + int k; + for (k = 1; k + 1 < g->edges_len; k++, edge_ptr++) { + MEdge *e = orig_medge + (*edge_ptr)->old_edge; + sub_v3_v3v3(tmp, orig_mvert_co[vm[e->v1] == i ? e->v2 : e->v1], orig_mvert_co[i]); + add_v3_v3(move_nor, tmp); + } + if (k == 1) { + disable_boundary_fix = true; + } + else { + disable_boundary_fix = normalize_v3(move_nor) == 0.0f; + } + } /* Fix boundary verts. */ if (!disable_boundary_fix) { /* Constraint normal, nor * constr_nor == 0 after this fix. */ @@ -1743,8 +1790,11 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, orig_mvert_co[i]); if (smd->nonmanifold_boundary_mode == MOD_SOLIDIFY_NONMANIFOLD_BOUNDARY_MODE_FLAT) { cross_v3_v3v3(constr_nor, e0, e1); + normalize_v3(constr_nor); } else { + BLI_assert(smd->nonmanifold_boundary_mode == + MOD_SOLIDIFY_NONMANIFOLD_BOUNDARY_MODE_ROUND); float f0[3]; float f1[3]; if (g->edges[0]->faces[0]->reversed) { @@ -1766,9 +1816,11 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md, normalize_v3(n0); normalize_v3(n1); add_v3_v3v3(constr_nor, n0, n1); + normalize_v3(constr_nor); } float d = dot_v3v3(constr_nor, move_nor); - if (LIKELY(fabsf(d) > FLT_EPSILON)) { + /* Only allow the thickness to increase about 10 times. */ + if (fabsf(d) > 0.1f) { mul_v3_fl(move_nor, dot_v3v3(constr_nor, nor) / d); sub_v3_v3(nor, move_nor); } -- cgit v1.2.3 From 38ae311706309dacdff167245e018a77a06bca0e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 20 Feb 2022 15:17:54 +0300 Subject: Weight Modifiers: use the correct flags for the mask invert property. The code was using the same flag value for different modifiers, resulting in matching the toggle to random overlapping flags. Differential Revision: https://developer.blender.org/D14165 --- source/blender/makesrna/intern/rna_modifier.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 957b92f204a..bd6d543e54a 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -4961,6 +4961,7 @@ static void rna_def_modifier_uvwarp(BlenderRNA *brna) static void rna_def_modifier_weightvg_mask(BlenderRNA *UNUSED(brna), StructRNA *srna, const char *mask_flags, + const int invert_vgroup_mask_flag, const char *mask_vgroup_setter, const char *mask_uvlayer_setter) { @@ -5006,7 +5007,7 @@ static void rna_def_modifier_weightvg_mask(BlenderRNA *UNUSED(brna), RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "invert_mask_vertex_group", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, mask_flags, MOD_WVG_EDIT_INVERT_VGROUP_MASK); + RNA_def_property_boolean_sdna(prop, NULL, mask_flags, invert_vgroup_mask_flag); RNA_def_property_ui_text(prop, "Invert", "Invert vertex group mask influence"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); @@ -5162,6 +5163,7 @@ static void rna_def_modifier_weightvgedit(BlenderRNA *brna) rna_def_modifier_weightvg_mask(brna, srna, "edit_flags", + MOD_WVG_EDIT_INVERT_VGROUP_MASK, "rna_WeightVGEditModifier_mask_defgrp_name_set", "rna_WeightVGEditModifier_mask_tex_uvlayer_name_set"); } @@ -5277,6 +5279,7 @@ static void rna_def_modifier_weightvgmix(BlenderRNA *brna) rna_def_modifier_weightvg_mask(brna, srna, "flag", + MOD_WVG_MIX_INVERT_VGROUP_MASK, "rna_WeightVGMixModifier_mask_defgrp_name_set", "rna_WeightVGMixModifier_mask_tex_uvlayer_name_set"); } @@ -5406,6 +5409,7 @@ static void rna_def_modifier_weightvgproximity(BlenderRNA *brna) rna_def_modifier_weightvg_mask(brna, srna, "proximity_flags", + MOD_WVG_PROXIMITY_INVERT_VGROUP_MASK, "rna_WeightVGProximityModifier_mask_defgrp_name_set", "rna_WeightVGProximityModifier_mask_tex_uvlayer_name_set"); } -- cgit v1.2.3 From ed9f7630342f4d515b6d77aa1f77722b74676518 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 21 Feb 2022 16:07:43 +0100 Subject: Paint: pass operator to stroke update function This allows accessing properties of the operator that the stroke belongs to. --- source/blender/editors/sculpt_paint/curves_sculpt_ops.cc | 7 +++++-- source/blender/editors/sculpt_paint/paint_image.c | 5 ++++- source/blender/editors/sculpt_paint/paint_intern.h | 1 + source/blender/editors/sculpt_paint/paint_stroke.c | 4 ++-- source/blender/editors/sculpt_paint/paint_vertex.c | 10 ++++++++-- source/blender/editors/sculpt_paint/sculpt.c | 1 + 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index fb5b1d338a6..db81adbbbcd 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -52,9 +52,12 @@ static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mo return true; } -static void stroke_update_step(bContext *C, PaintStroke *stroke, PointerRNA *itemptr) +static void stroke_update_step(bContext *C, + wmOperator *op, + PaintStroke *stroke, + PointerRNA *itemptr) { - UNUSED_VARS(C, stroke, itemptr); + UNUSED_VARS(C, op, stroke, itemptr); } static void stroke_done(const bContext *C, PaintStroke *stroke) diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index ad8303418f4..4b8c7ae7808 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -502,7 +502,10 @@ static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const flo return pop; } -static void paint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) +static void paint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) { PaintOperation *pop = paint_stroke_mode_data(stroke); Scene *scene = CTX_data_scene(C); diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index f7447ec31b8..ba363f687dc 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -49,6 +49,7 @@ typedef struct CoNo { typedef bool (*StrokeGetLocation)(struct bContext *C, float location[3], const float mouse[2]); typedef bool (*StrokeTestStart)(struct bContext *C, struct wmOperator *op, const float mouse[2]); typedef void (*StrokeUpdateStep)(struct bContext *C, + struct wmOperator *op, struct PaintStroke *stroke, struct PointerRNA *itemptr); typedef void (*StrokeRedraw)(const struct bContext *C, struct PaintStroke *stroke, bool final); diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index ebb5db8d712..7c4623dd4b0 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -611,7 +611,7 @@ static void paint_brush_stroke_add_step( RNA_float_set(&itemptr, "x_tilt", stroke->x_tilt); RNA_float_set(&itemptr, "y_tilt", stroke->y_tilt); - stroke->update_step(C, stroke, &itemptr); + stroke->update_step(C, op, stroke, &itemptr); /* don't record this for now, it takes up a lot of memory when doing long * strokes with small brush size, and operators have register disabled */ @@ -1584,7 +1584,7 @@ int paint_stroke_exec(bContext *C, wmOperator *op, PaintStroke *stroke) if (stroke->stroke_started) { RNA_BEGIN (op->ptr, itemptr, "stroke") { - stroke->update_step(C, stroke, &itemptr); + stroke->update_step(C, op, stroke, &itemptr); } RNA_END; } diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 8373f028109..e2f8d81fe13 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -2370,7 +2370,10 @@ static void wpaint_do_symmetrical_brush_actions( cache->is_last_valid = true; } -static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) +static void wpaint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) { Scene *scene = CTX_data_scene(C); ToolSettings *ts = CTX_data_tool_settings(C); @@ -3400,7 +3403,10 @@ static void vpaint_do_symmetrical_brush_actions( cache->is_last_valid = true; } -static void vpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) +static void vpaint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) { Scene *scene = CTX_data_scene(C); ToolSettings *ts = CTX_data_tool_settings(C); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 63611b473cb..919385f82e7 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -5186,6 +5186,7 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f } static void sculpt_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), struct PaintStroke *UNUSED(stroke), PointerRNA *itemptr) { -- cgit v1.2.3 From 82ff0fa58673068ff842201bd89393d68c488148 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 21 Feb 2022 18:08:53 +0300 Subject: Weight Proximity: fix value overlap between Normalize and Invert VGroup. The flags overlapped ever since normalize was added, so this requires versioning to copy the flag value. Differential Revision: https://developer.blender.org/D14165 --- source/blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenloader/intern/versioning_300.c | 14 ++++++++++++++ source/blender/makesdna/DNA_modifier_types.h | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index d1f31e0d2f5..ba95ceb0998 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 6 +#define BLENDER_FILE_SUBVERSION 7 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 90730439c51..d478ffb732e 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -2549,6 +2549,20 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 301, 7)) { + /* Duplicate value for two flags that mistakenly had the same numeric value. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_WeightVGProximity) { + WeightVGProximityModifierData *wpmd = (WeightVGProximityModifierData *)md; + if (wpmd->proximity_flags & MOD_WVG_PROXIMITY_INVERT_VGROUP_MASK) { + wpmd->proximity_flags |= MOD_WVG_PROXIMITY_WEIGHTS_NORMALIZE; + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 8e38d52a4d7..146167a3770 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -1708,7 +1708,7 @@ enum { MOD_WVG_PROXIMITY_GEOM_FACES = (1 << 2), MOD_WVG_PROXIMITY_INVERT_VGROUP_MASK = (1 << 3), MOD_WVG_PROXIMITY_INVERT_FALLOFF = (1 << 4), - MOD_WVG_PROXIMITY_WEIGHTS_NORMALIZE = (1 << 3), + MOD_WVG_PROXIMITY_WEIGHTS_NORMALIZE = (1 << 5), }; /* Defines common to all WeightVG modifiers. */ -- cgit v1.2.3 From 68a2dc58dee719dc37a0ba2b84c5488999c2a458 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 21 Feb 2022 18:15:17 +0300 Subject: Weight Proximity: do flag split versioning both in 3.1 and 3.2 The flags overlapped ever since normalize was added, so this requires versioning to copy the flag value. This needs to be done both in Blender 3.1 and 3.2. Differential Revision: https://developer.blender.org/D14165 --- source/blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenloader/intern/versioning_300.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 21bbb4ce9ad..d09b0a02ad8 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -25,7 +25,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 3 +#define BLENDER_FILE_SUBVERSION 4 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 4f4127b1a8d..2a840ea585a 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -2544,7 +2544,8 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - if (!MAIN_VERSION_ATLEAST(bmain, 301, 7)) { + if (!MAIN_VERSION_ATLEAST(bmain, 301, 7) || + (bmain->versionfile == 302 && !MAIN_VERSION_ATLEAST(bmain, 302, 4))) { /* Duplicate value for two flags that mistakenly had the same numeric value. */ LISTBASE_FOREACH (Object *, ob, &bmain->objects) { LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { -- cgit v1.2.3 From 869dd2e6998f7c89f9c18cb90aaa38ac0b9ac91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 21 Feb 2022 17:18:37 +0100 Subject: Fix T95154 Eevee AO node: the "only local" option applies "inside" instead The `custom2` was not being used as a bitflag as it should. --- source/blender/nodes/shader/nodes/node_shader_ambient_occlusion.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/shader/nodes/node_shader_ambient_occlusion.cc b/source/blender/nodes/shader/nodes/node_shader_ambient_occlusion.cc index 9c64594aab8..3c0a0a26622 100644 --- a/source/blender/nodes/shader/nodes/node_shader_ambient_occlusion.cc +++ b/source/blender/nodes/shader/nodes/node_shader_ambient_occlusion.cc @@ -54,7 +54,7 @@ static int node_shader_gpu_ambient_occlusion(GPUMaterial *mat, GPU_material_flag_set(mat, GPU_MATFLAG_DIFFUSE); - float inverted = node->custom2 ? 1.0f : 0.0f; + float inverted = (node->custom2 & SHD_AO_INSIDE) ? 1.0f : 0.0f; float f_samples = divide_ceil_u(node->custom1, 4); return GPU_stack_link(mat, -- cgit v1.2.3 From a81cc5cbcb6c9e9ef4bee610ac1d325d33884e98 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 21 Feb 2022 11:40:59 -0500 Subject: Fix: Avoid potential use of dirty normals Instead of accessing the `CD_NORMAL` layer directly, use the proper API for accessing mesh normals. Even if the layer exists, the values might be incorrect due to a deformation. Related to ef0e21f0ae71d, 969c4a45ce09100e, and T95839. --- source/blender/python/mathutils/mathutils_bvhtree.c | 6 ++---- source/blender/render/intern/bake.c | 4 +++- source/blender/render/intern/multires_bake.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/blender/python/mathutils/mathutils_bvhtree.c b/source/blender/python/mathutils/mathutils_bvhtree.c index 588d3753eab..28687ce2d13 100644 --- a/source/blender/python/mathutils/mathutils_bvhtree.c +++ b/source/blender/python/mathutils/mathutils_bvhtree.c @@ -1180,10 +1180,8 @@ static PyObject *C_BVHTree_FromObject(PyObject *UNUSED(cls), PyObject *args, PyO tree = BLI_bvhtree_new((int)tris_len, epsilon, PY_BVH_TREE_TYPE_DEFAULT, PY_BVH_AXIS_DEFAULT); if (tree) { orig_index = MEM_mallocN(sizeof(*orig_index) * (size_t)tris_len, __func__); - CustomData *pdata = &mesh->pdata; - orig_normal = CustomData_get_layer(pdata, CD_NORMAL); /* can be NULL */ - if (orig_normal) { - orig_normal = MEM_dupallocN(orig_normal); + if (!BKE_mesh_poly_normals_are_dirty(mesh)) { + orig_normal = MEM_dupallocN(BKE_mesh_poly_normals_ensure(mesh)); } for (i = 0; i < tris_len; i++, lt++) { diff --git a/source/blender/render/intern/bake.c b/source/blender/render/intern/bake.c index 883e026472b..b11d9629e03 100644 --- a/source/blender/render/intern/bake.c +++ b/source/blender/render/intern/bake.c @@ -482,7 +482,9 @@ static TriTessFace *mesh_calc_tri_tessface(Mesh *me, bool tangent, Mesh *me_eval looptri = MEM_mallocN(sizeof(*looptri) * tottri, __func__); triangles = MEM_callocN(sizeof(TriTessFace) * tottri, __func__); - const float(*precomputed_normals)[3] = CustomData_get_layer(&me->pdata, CD_NORMAL); + const float(*precomputed_normals)[3] = BKE_mesh_poly_normals_are_dirty(me) ? + NULL : + BKE_mesh_poly_normals_ensure(me); const bool calculate_normal = precomputed_normals ? false : true; if (precomputed_normals != NULL) { diff --git a/source/blender/render/intern/multires_bake.c b/source/blender/render/intern/multires_bake.c index 9468e4c5b0f..73f925f5905 100644 --- a/source/blender/render/intern/multires_bake.c +++ b/source/blender/render/intern/multires_bake.c @@ -483,7 +483,6 @@ static void do_multires_bake(MultiresBakeRender *bkr, MPoly *mpoly = dm->getPolyArray(dm); MLoop *mloop = dm->getLoopArray(dm); MLoopUV *mloopuv = dm->getLoopDataArray(dm, CD_MLOOPUV); - const float *precomputed_normals = dm->getPolyDataArray(dm, CD_NORMAL); float *pvtangent = NULL; ListBase threads; @@ -498,6 +497,7 @@ static void do_multires_bake(MultiresBakeRender *bkr, memcpy(temp_mesh->mpoly, dm->getPolyArray(dm), temp_mesh->totpoly * sizeof(*temp_mesh->mpoly)); memcpy(temp_mesh->mloop, dm->getLoopArray(dm), temp_mesh->totloop * sizeof(*temp_mesh->mloop)); const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(temp_mesh); + const float(*poly_normals)[3] = BKE_mesh_poly_normals_ensure(temp_mesh); if (require_tangent) { if (CustomData_get_layer_index(&dm->loopData, CD_TANGENT) == -1) { @@ -513,7 +513,7 @@ static void do_multires_bake(MultiresBakeRender *bkr, NULL, 0, vert_normals, - (const float(*)[3])CustomData_get_layer(&dm->polyData, CD_NORMAL), + poly_normals, (const float(*)[3])dm->getLoopDataArray(dm, CD_NORMAL), (const float(*)[3])dm->getVertDataArray(dm, CD_ORCO), /* may be nullptr */ /* result */ @@ -558,7 +558,7 @@ static void do_multires_bake(MultiresBakeRender *bkr, handle->data.mlooptri = mlooptri; handle->data.mloop = mloop; handle->data.pvtangent = pvtangent; - handle->data.precomputed_normals = precomputed_normals; /* don't strictly need this */ + handle->data.precomputed_normals = (float *)poly_normals; /* don't strictly need this */ handle->data.w = ibuf->x; handle->data.h = ibuf->y; handle->data.lores_dm = dm; -- cgit v1.2.3 From b7171d1b822010f7ccc59631b2f2bda95ef6058a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 21 Feb 2022 12:17:41 -0500 Subject: Cleanup: Use function to check if normals are dirty This makes the fix for T95839 simpler. Similar to 969c4a45ce09100ed. --- source/blender/blenkernel/intern/data_transfer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c index 0ad7efb6347..79aecb4598a 100644 --- a/source/blender/blenkernel/intern/data_transfer.c +++ b/source/blender/blenkernel/intern/data_transfer.c @@ -1396,7 +1396,7 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, BLI_assert((ob_src != ob_dst) && (ob_src->type == OB_MESH) && (ob_dst->type == OB_MESH)); if (me_dst) { - dirty_nors_dst = (me_dst->runtime.cd_dirty_vert & CD_NORMAL) != 0; + dirty_nors_dst = BKE_mesh_vertex_normals_are_dirty(me_dst); /* Never create needed custom layers on passed destination mesh * (assumed to *not* be ob_dst->data, aka modifier case). */ use_create = false; -- cgit v1.2.3 From be6bcaa8c13dfca98e56d81afd3d19ba226c2699 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 21 Feb 2022 13:01:37 -0500 Subject: Fix T93873: Wrong limits for color socket exposed to modifier Limit the min and max of the IDProperty for the node group input from 0 to infinity, and the soft min and max between 0 and 1. Thanks to @PratikPB2123 for investigation. --- source/blender/modifiers/intern/MOD_nodes.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 1c755a7e2d9..a4b46b951a3 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -435,6 +435,10 @@ static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) ui_data->base.rna_subtype = PROP_COLOR; ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__); ui_data->default_array_len = 4; + ui_data->min = 0.0; + ui_data->max = FLT_MAX; + ui_data->soft_min = 0.0; + ui_data->soft_max = 1.0; for (const int i : IndexRange(4)) { ui_data->default_array[i] = double(value->value[i]); } -- cgit v1.2.3 From 49ae0b5b3c929e91aa9d4292fe9c002c6c9dfe5b Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Mon, 21 Feb 2022 19:48:11 +0100 Subject: Fix T95923: GPencil Array modifier constant offset works even disabled The problem was when the Object Offset was enabled because the Constant Offset flag was not checked and the offset always was added to the transformation matrix. --- source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c index 79d4f4dffec..3b52b7d6ee3 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c @@ -120,7 +120,9 @@ static void BKE_gpencil_instance_modifier_instance_tfm(Object *ob, float obinv[4][4]; unit_m4(mat_offset); - add_v3_v3(mat_offset[3], mmd->offset); + if (mmd->flag & GP_ARRAY_USE_OFFSET) { + add_v3_v3(mat_offset[3], mmd->offset); + } invert_m4_m4(obinv, ob->obmat); mul_m4_series(r_offset, mat_offset, obinv, mmd->object->obmat); -- cgit v1.2.3 From 284cef473fed06593ee9ce969c121fd2e007f6e3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 21 Feb 2022 13:59:02 -0500 Subject: Fix T95919: Apply Pose as Rest Pose Operator crashes A simple mistake with a null mesh in rBcfa53e0fbeed. --- source/blender/blenkernel/intern/constraint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index f013ef99dde..169015a238b 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -545,8 +545,8 @@ static void contarget_get_mesh_mat(Object *ob, const char *substring, float mat[ float vec[3] = {0.0f, 0.0f, 0.0f}; float normal[3] = {0.0f, 0.0f, 0.0f}; float weightsum = 0.0f; - const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me_eval); if (me_eval) { + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me_eval); const MDeformVert *dvert = CustomData_get_layer(&me_eval->vdata, CD_MDEFORMVERT); int numVerts = me_eval->totvert; -- cgit v1.2.3 From dde5cc6670f3f41e617f5b5fd49d7982c0304c21 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 21 Feb 2022 17:06:17 -0500 Subject: Cleanup: Use curves wrapper --- source/blender/blenkernel/intern/curves.cc | 18 +++++++++--------- source/blender/blenkernel/intern/curves_geometry.cc | 4 ++-- source/blender/draw/intern/draw_cache_impl_curves.cc | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index e4a57ee1897..9935166f874 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -358,16 +358,16 @@ static Curves *curves_evaluate_modifiers(struct Depsgraph *depsgraph, curves = BKE_curves_copy_for_eval(curves, true); } - /* Ensure we are not overwriting referenced data. */ - CustomData_duplicate_referenced_layer_named(&curves->geometry.point_data, - CD_PROP_FLOAT3, - ATTR_POSITION, - curves->geometry.point_size); - update_custom_data_pointers(*curves); - /* Created deformed coordinates array on demand. */ - mti->deformVerts( - md, &mectx, nullptr, curves->geometry.position, curves->geometry.point_size); + blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap( + curves->geometry); + MutableSpan positions = geometry.positions(); + + mti->deformVerts(md, + &mectx, + nullptr, + reinterpret_cast(positions.data()), + curves->geometry.point_size); } } diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index ac40b7a7aa1..3e45fce3776 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -142,8 +142,8 @@ MutableSpan CurvesGeometry::curve_types() MutableSpan CurvesGeometry::positions() { - CustomData_duplicate_referenced_layer(&this->point_data, CD_PROP_FLOAT3, this->point_size); - this->update_customdata_pointers(); + this->position = (float(*)[3])CustomData_duplicate_referenced_layer_named( + &this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str(), this->point_size); return {(float3 *)this->position, this->point_size}; } Span CurvesGeometry::positions() const diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index 2153b674463..df1ac12605a 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -133,12 +133,12 @@ static void curves_batch_cache_fill_segments_proc_pos(Curves *curves, { /* TODO: use hair radius layer if available. */ const int curve_size = curves->geometry.curve_size; - Span offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; - - Span positions{(float3 *)curves->geometry.position, curves->geometry.point_size}; + const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap( + curves->geometry); + Span positions = geometry.positions(); for (const int i : IndexRange(curve_size)) { - const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); + const IndexRange curve_range = geometry.range_for_curve(i); Span spline_positions = positions.slice(curve_range); float total_len = 0.0f; @@ -215,11 +215,11 @@ static void curves_batch_cache_fill_strands_data(Curves *curves, GPUVertBufRaw *data_step, GPUVertBufRaw *seg_step) { - const int curve_size = curves->geometry.curve_size; - Span offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; + const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap( + curves->geometry); - for (const int i : IndexRange(curve_size)) { - const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); + for (const int i : IndexRange(geometry.curves_size())) { + const IndexRange curve_range = geometry.range_for_curve(i); *(uint *)GPU_vertbuf_raw_step(data_step) = curve_range.start(); *(ushort *)GPU_vertbuf_raw_step(seg_step) = curve_range.size() - 1; -- cgit v1.2.3 From c3d36b71273ad330ad59f0f8a09a5c789bd52a48 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 09:56:43 +1100 Subject: Cleanup: clang-format --- .../evaluator/shaders/glsl_compute_kernel.glsl | 453 ++++++++++++--------- source/blender/blenkernel/BKE_animsys.h | 3 +- .../operations/COM_GaussianBokehBlurOperation.cc | 2 +- .../gizmo_library/gizmo_types/cage3d_gizmo.c | 3 +- .../io/wavefront_obj/tests/obj_exporter_tests.cc | 6 +- 5 files changed, 269 insertions(+), 198 deletions(-) diff --git a/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl b/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl index 2a58fa10ea0..2f60aee0999 100644 --- a/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl +++ b/intern/opensubdiv/internal/evaluator/shaders/glsl_compute_kernel.glsl @@ -24,56 +24,103 @@ //------------------------------------------------------------------------------ - -layout(local_size_x=WORK_GROUP_SIZE, local_size_y=1, local_size_z=1) in; +layout(local_size_x = WORK_GROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(std430) buffer; // source and destination buffers uniform int srcOffset = 0; uniform int dstOffset = 0; -layout(binding=0) buffer src_buffer { float srcVertexBuffer[]; }; -layout(binding=1) buffer dst_buffer { float dstVertexBuffer[]; }; +layout(binding = 0) buffer src_buffer +{ + float srcVertexBuffer[]; +}; +layout(binding = 1) buffer dst_buffer +{ + float dstVertexBuffer[]; +}; -// derivative buffers (if needed) + // derivative buffers (if needed) #if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) uniform ivec3 duDesc; uniform ivec3 dvDesc; -layout(binding=2) buffer du_buffer { float duBuffer[]; }; -layout(binding=3) buffer dv_buffer { float dvBuffer[]; }; +layout(binding = 2) buffer du_buffer +{ + float duBuffer[]; +}; +layout(binding = 3) buffer dv_buffer +{ + float dvBuffer[]; +}; #endif #if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) uniform ivec3 duuDesc; uniform ivec3 duvDesc; uniform ivec3 dvvDesc; -layout(binding=10) buffer duu_buffer { float duuBuffer[]; }; -layout(binding=11) buffer duv_buffer { float duvBuffer[]; }; -layout(binding=12) buffer dvv_buffer { float dvvBuffer[]; }; +layout(binding = 10) buffer duu_buffer +{ + float duuBuffer[]; +}; +layout(binding = 11) buffer duv_buffer +{ + float duvBuffer[]; +}; +layout(binding = 12) buffer dvv_buffer +{ + float dvvBuffer[]; +}; #endif -// stencil buffers + // stencil buffers #if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS) uniform int batchStart = 0; uniform int batchEnd = 0; -layout(binding=4) buffer stencilSizes { int _sizes[]; }; -layout(binding=5) buffer stencilOffsets { int _offsets[]; }; -layout(binding=6) buffer stencilIndices { int _indices[]; }; -layout(binding=7) buffer stencilWeights { float _weights[]; }; +layout(binding = 4) buffer stencilSizes +{ + int _sizes[]; +}; +layout(binding = 5) buffer stencilOffsets +{ + int _offsets[]; +}; +layout(binding = 6) buffer stencilIndices +{ + int _indices[]; +}; +layout(binding = 7) buffer stencilWeights +{ + float _weights[]; +}; -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) -layout(binding=8) buffer stencilDuWeights { float _duWeights[]; }; -layout(binding=9) buffer stencilDvWeights { float _dvWeights[]; }; -#endif +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) +layout(binding = 8) buffer stencilDuWeights +{ + float _duWeights[]; +}; +layout(binding = 9) buffer stencilDvWeights +{ + float _dvWeights[]; +}; +# endif -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) -layout(binding=13) buffer stencilDuuWeights { float _duuWeights[]; }; -layout(binding=14) buffer stencilDuvWeights { float _duvWeights[]; }; -layout(binding=15) buffer stencilDvvWeights { float _dvvWeights[]; }; -#endif +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) +layout(binding = 13) buffer stencilDuuWeights +{ + float _duuWeights[]; +}; +layout(binding = 14) buffer stencilDuvWeights +{ + float _duvWeights[]; +}; +layout(binding = 15) buffer stencilDvvWeights +{ + float _dvvWeights[]; +}; +# endif uint getGlobalInvocationIndex() { @@ -87,24 +134,36 @@ uint getGlobalInvocationIndex() #if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_PATCHES) -layout(binding=4) buffer patchArray_buffer { OsdPatchArray patchArrayBuffer[]; }; -layout(binding=5) buffer patchCoord_buffer { OsdPatchCoord patchCoords[]; }; -layout(binding=6) buffer patchIndex_buffer { int patchIndexBuffer[]; }; -layout(binding=7) buffer patchParam_buffer { OsdPatchParam patchParamBuffer[]; }; +layout(binding = 4) buffer patchArray_buffer +{ + OsdPatchArray patchArrayBuffer[]; +}; +layout(binding = 5) buffer patchCoord_buffer +{ + OsdPatchCoord patchCoords[]; +}; +layout(binding = 6) buffer patchIndex_buffer +{ + int patchIndexBuffer[]; +}; +layout(binding = 7) buffer patchParam_buffer +{ + OsdPatchParam patchParamBuffer[]; +}; OsdPatchCoord GetPatchCoord(int coordIndex) { - return patchCoords[coordIndex]; + return patchCoords[coordIndex]; } OsdPatchArray GetPatchArray(int arrayIndex) { - return patchArrayBuffer[arrayIndex]; + return patchArrayBuffer[arrayIndex]; } OsdPatchParam GetPatchParam(int patchIndex) { - return patchParamBuffer[patchIndex]; + return patchParamBuffer[patchIndex]; } #endif @@ -112,141 +171,149 @@ OsdPatchParam GetPatchParam(int patchIndex) //------------------------------------------------------------------------------ struct Vertex { - float vertexData[LENGTH]; + float vertexData[LENGTH]; }; -void clear(out Vertex v) { - for (int i = 0; i < LENGTH; ++i) { - v.vertexData[i] = 0; - } +void clear(out Vertex v) +{ + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] = 0; + } } -Vertex readVertex(int index) { - Vertex v; - int vertexIndex = srcOffset + index * SRC_STRIDE; - for (int i = 0; i < LENGTH; ++i) { - v.vertexData[i] = srcVertexBuffer[vertexIndex + i]; - } - return v; +Vertex readVertex(int index) +{ + Vertex v; + int vertexIndex = srcOffset + index * SRC_STRIDE; + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] = srcVertexBuffer[vertexIndex + i]; + } + return v; } -void writeVertex(int index, Vertex v) { - int vertexIndex = dstOffset + index * DST_STRIDE; - for (int i = 0; i < LENGTH; ++i) { - dstVertexBuffer[vertexIndex + i] = v.vertexData[i]; - } +void writeVertex(int index, Vertex v) +{ + int vertexIndex = dstOffset + index * DST_STRIDE; + for (int i = 0; i < LENGTH; ++i) { + dstVertexBuffer[vertexIndex + i] = v.vertexData[i]; + } } -void addWithWeight(inout Vertex v, const Vertex src, float weight) { - for (int i = 0; i < LENGTH; ++i) { - v.vertexData[i] += weight * src.vertexData[i]; - } +void addWithWeight(inout Vertex v, const Vertex src, float weight) +{ + for (int i = 0; i < LENGTH; ++i) { + v.vertexData[i] += weight * src.vertexData[i]; + } } #if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) -void writeDu(int index, Vertex du) { - int duIndex = duDesc.x + index * duDesc.z; - for (int i = 0; i < LENGTH; ++i) { - duBuffer[duIndex + i] = du.vertexData[i]; - } +void writeDu(int index, Vertex du) +{ + int duIndex = duDesc.x + index * duDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duBuffer[duIndex + i] = du.vertexData[i]; + } } -void writeDv(int index, Vertex dv) { - int dvIndex = dvDesc.x + index * dvDesc.z; - for (int i = 0; i < LENGTH; ++i) { - dvBuffer[dvIndex + i] = dv.vertexData[i]; - } +void writeDv(int index, Vertex dv) +{ + int dvIndex = dvDesc.x + index * dvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + dvBuffer[dvIndex + i] = dv.vertexData[i]; + } } #endif #if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) -void writeDuu(int index, Vertex duu) { - int duuIndex = duuDesc.x + index * duuDesc.z; - for (int i = 0; i < LENGTH; ++i) { - duuBuffer[duuIndex + i] = duu.vertexData[i]; - } +void writeDuu(int index, Vertex duu) +{ + int duuIndex = duuDesc.x + index * duuDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duuBuffer[duuIndex + i] = duu.vertexData[i]; + } } -void writeDuv(int index, Vertex duv) { - int duvIndex = duvDesc.x + index * duvDesc.z; - for (int i = 0; i < LENGTH; ++i) { - duvBuffer[duvIndex + i] = duv.vertexData[i]; - } +void writeDuv(int index, Vertex duv) +{ + int duvIndex = duvDesc.x + index * duvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + duvBuffer[duvIndex + i] = duv.vertexData[i]; + } } -void writeDvv(int index, Vertex dvv) { - int dvvIndex = dvvDesc.x + index * dvvDesc.z; - for (int i = 0; i < LENGTH; ++i) { - dvvBuffer[dvvIndex + i] = dvv.vertexData[i]; - } +void writeDvv(int index, Vertex dvv) +{ + int dvvIndex = dvvDesc.x + index * dvvDesc.z; + for (int i = 0; i < LENGTH; ++i) { + dvvBuffer[dvvIndex + i] = dvv.vertexData[i]; + } } #endif //------------------------------------------------------------------------------ #if defined(OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS) -void main() { - int current = int(getGlobalInvocationIndex()) + batchStart; - - if (current>=batchEnd) { - return; - } - - Vertex dst; - clear(dst); - - int offset = _offsets[current], - size = _sizes[current]; - - for (int stencil = 0; stencil < size; ++stencil) { - int vindex = offset + stencil; - addWithWeight( - dst, readVertex(_indices[vindex]), _weights[vindex]); - } - - writeVertex(current, dst); - -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) - Vertex du, dv; - clear(du); - clear(dv); - for (int i=0; i 0) { // length - writeDu(current, du); - } - if (dvDesc.y > 0) { - writeDv(current, dv); - } -#endif -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) - Vertex duu, duv, dvv; - clear(duu); - clear(duv); - clear(dvv); - for (int i=0; i 0) { // length - writeDuu(current, duu); - } - if (duvDesc.y > 0) { - writeDuv(current, duv); - } - if (dvvDesc.y > 0) { - writeDvv(current, dvv); - } -#endif +void main() +{ + int current = int(getGlobalInvocationIndex()) + batchStart; + + if (current >= batchEnd) { + return; + } + + Vertex dst; + clear(dst); + + int offset = _offsets[current], size = _sizes[current]; + + for (int stencil = 0; stencil < size; ++stencil) { + int vindex = offset + stencil; + addWithWeight(dst, readVertex(_indices[vindex]), _weights[vindex]); + } + + writeVertex(current, dst); + +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) + Vertex du, dv; + clear(du); + clear(dv); + for (int i = 0; i < size; ++i) { + // expects the compiler optimizes readVertex out here. + Vertex src = readVertex(_indices[offset + i]); + addWithWeight(du, src, _duWeights[offset + i]); + addWithWeight(dv, src, _dvWeights[offset + i]); + } + + if (duDesc.y > 0) { // length + writeDu(current, du); + } + if (dvDesc.y > 0) { + writeDv(current, dv); + } +# endif +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) + Vertex duu, duv, dvv; + clear(duu); + clear(duv); + clear(dvv); + for (int i = 0; i < size; ++i) { + // expects the compiler optimizes readVertex out here. + Vertex src = readVertex(_indices[offset + i]); + addWithWeight(duu, src, _duuWeights[offset + i]); + addWithWeight(duv, src, _duvWeights[offset + i]); + addWithWeight(dvv, src, _dvvWeights[offset + i]); + } + + if (duuDesc.y > 0) { // length + writeDuu(current, duu); + } + if (duvDesc.y > 0) { + writeDuv(current, duv); + } + if (dvvDesc.y > 0) { + writeDvv(current, dvv); + } +# endif } #endif @@ -256,61 +323,61 @@ void main() { // PERFORMANCE: stride could be constant, but not as significant as length -void main() { - - int current = int(gl_GlobalInvocationID.x); - - OsdPatchCoord coord = GetPatchCoord(current); - OsdPatchArray array = GetPatchArray(coord.arrayIndex); - OsdPatchParam param = GetPatchParam(coord.patchIndex); - - int patchType = OsdPatchParamIsRegular(param) ? array.regDesc : array.desc; - - float wP[20], wDu[20], wDv[20], wDuu[20], wDuv[20], wDvv[20]; - int nPoints = OsdEvaluatePatchBasis(patchType, param, - coord.s, coord.t, wP, wDu, wDv, wDuu, wDuv, wDvv); - - Vertex dst, du, dv, duu, duv, dvv; - clear(dst); - clear(du); - clear(dv); - clear(duu); - clear(duv); - clear(dvv); - - int indexBase = array.indexBase + array.stride * - (coord.patchIndex - array.primitiveIdBase); - - for (int cv = 0; cv < nPoints; ++cv) { - int index = patchIndexBuffer[indexBase + cv]; - addWithWeight(dst, readVertex(index), wP[cv]); - addWithWeight(du, readVertex(index), wDu[cv]); - addWithWeight(dv, readVertex(index), wDv[cv]); - addWithWeight(duu, readVertex(index), wDuu[cv]); - addWithWeight(duv, readVertex(index), wDuv[cv]); - addWithWeight(dvv, readVertex(index), wDvv[cv]); - } - writeVertex(current, dst); +void main() +{ -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) - if (duDesc.y > 0) { // length - writeDu(current, du); - } - if (dvDesc.y > 0) { - writeDv(current, dv); - } -#endif -#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) - if (duuDesc.y > 0) { // length - writeDuu(current, duu); - } - if (duvDesc.y > 0) { // length - writeDuv(current, duv); - } - if (dvvDesc.y > 0) { - writeDvv(current, dvv); - } -#endif + int current = int(gl_GlobalInvocationID.x); + + OsdPatchCoord coord = GetPatchCoord(current); + OsdPatchArray array = GetPatchArray(coord.arrayIndex); + OsdPatchParam param = GetPatchParam(coord.patchIndex); + + int patchType = OsdPatchParamIsRegular(param) ? array.regDesc : array.desc; + + float wP[20], wDu[20], wDv[20], wDuu[20], wDuv[20], wDvv[20]; + int nPoints = OsdEvaluatePatchBasis( + patchType, param, coord.s, coord.t, wP, wDu, wDv, wDuu, wDuv, wDvv); + + Vertex dst, du, dv, duu, duv, dvv; + clear(dst); + clear(du); + clear(dv); + clear(duu); + clear(duv); + clear(dvv); + + int indexBase = array.indexBase + array.stride * (coord.patchIndex - array.primitiveIdBase); + + for (int cv = 0; cv < nPoints; ++cv) { + int index = patchIndexBuffer[indexBase + cv]; + addWithWeight(dst, readVertex(index), wP[cv]); + addWithWeight(du, readVertex(index), wDu[cv]); + addWithWeight(dv, readVertex(index), wDv[cv]); + addWithWeight(duu, readVertex(index), wDuu[cv]); + addWithWeight(duv, readVertex(index), wDuv[cv]); + addWithWeight(dvv, readVertex(index), wDvv[cv]); + } + writeVertex(current, dst); + +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES) + if (duDesc.y > 0) { // length + writeDu(current, du); + } + if (dvDesc.y > 0) { + writeDv(current, dv); + } +# endif +# if defined(OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES) + if (duuDesc.y > 0) { // length + writeDuu(current, duu); + } + if (duvDesc.y > 0) { // length + writeDuv(current, duv); + } + if (dvvDesc.y > 0) { + writeDvv(current, dvv); + } +# endif } #endif diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h index 77e0de611d3..958029a7453 100644 --- a/source/blender/blenkernel/BKE_animsys.h +++ b/source/blender/blenkernel/BKE_animsys.h @@ -104,7 +104,8 @@ struct KS_Path *BKE_keyingset_find_path(struct KeyingSet *ks, void BKE_keyingsets_copy(struct ListBase *newlist, const struct ListBase *list); /** Process the ID pointers inside a scene's keyingsets, in see `BKE_lib_query.h` for details. */ -void BKE_keyingsets_foreach_id(struct LibraryForeachIDData *data, const struct ListBase *keyingsets); +void BKE_keyingsets_foreach_id(struct LibraryForeachIDData *data, + const struct ListBase *keyingsets); /* Free the given Keying Set path */ void BKE_keyingset_free_path(struct KeyingSet *ks, struct KS_Path *ksp); diff --git a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc index 49e761d6221..78b707ee1ea 100644 --- a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc @@ -44,7 +44,7 @@ void GaussianBokehBlurOperation::init_data() const float width = this->get_width(); const float height = this->get_height(); - if(execution_model_ == eExecutionModel::FullFrame) { + if (execution_model_ == eExecutionModel::FullFrame) { if (!sizeavailable_) { update_size(); } diff --git a/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c index 3635ceb8f13..38dd0dfc55e 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c @@ -381,7 +381,8 @@ static void gizmo_cage3d_draw_intern( } if (show) { - cage3d_draw_box_interaction(rv3d, matrix_final, gz->color, gz->highlight_part, size_real, margin); + cage3d_draw_box_interaction( + rv3d, matrix_final, gz->color, gz->highlight_part, size_real, margin); } } else if (draw_style == ED_GIZMO_CAGE2D_STYLE_CIRCLE) { diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index dbadc33906d..48340b9eea8 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -391,8 +391,10 @@ TEST_F(obj_exporter_regression_test, nurbs_curves_as_nurbs) _export.params.up_axis = OBJ_AXIS_Z_UP; _export.params.export_materials = false; _export.params.export_curves_as_nurbs = true; - compare_obj_export_to_golden( - "io_tests/blend_geometry/nurbs_curves.blend", "io_tests/obj/nurbs_curves.obj", "", _export.params); + compare_obj_export_to_golden("io_tests/blend_geometry/nurbs_curves.blend", + "io_tests/obj/nurbs_curves.obj", + "", + _export.params); } TEST_F(obj_exporter_regression_test, nurbs_as_mesh) -- cgit v1.2.3 From bfdbc78466ac14d45f353db9aa39cb21bb962701 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 09:57:07 +1100 Subject: Fix T44415: Shape keys get out of sync when using undo in edit-mode This is an alternate fix for T35170 since it caused T44415. Having the undo system manipulate the key-block coordinates is error prone as (in the case of T44415) there are situations when it's important to apply the difference with the original shape key. This reverts dab0bd9de65a9be5e8ababba0e2799f994d5d12f, and instead avoids the problem by not using the data in `Mesh.key` as a reference for updating shape-keys when exiting edit-mode. The assumption that the `Mesh.key` in edit-mode won't be modified until leaving edit-mode isn't always true. Leading to synchronization errors. (details noted in code-comments). Resolve this by using shape-key data stored in the BMesh. Resolving both T35170 & T44415. Details: - Remove use of the original vertices when exiting edit mode. - Remove use of the original shape-key coordinates when exiting edit-mode (except as a last resort). - Move shape-key synchronization into a separate function: `bm_to_mesh_key`. - Split the synchronization loop into two branches, depending on the existence of BMesh shape-key coordinates. - Always write shape-key values back to the BMesh CD_SHAPEKEY layers. This was only done in some cases but is now necessary for all shape-keys as these are used to calculate offsets where the `Mesh.key` was previously used. - Report a warning when the shape-key layer isn't found as this uses an imperfect method of restoring coordinates which should only be used as a last resort. Reviewed By: mont29 Ref D14127 --- source/blender/bmesh/CMakeLists.txt | 1 + source/blender/bmesh/intern/bmesh_mesh_convert.cc | 455 ++++++++++++++-------- source/blender/editors/mesh/editmesh_undo.c | 27 +- 3 files changed, 293 insertions(+), 190 deletions(-) diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index e2ed005cf9e..715fc298a86 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -26,6 +26,7 @@ set(INC ../depsgraph ../makesdna ../../../intern/atomic + ../../../intern/clog ../../../intern/eigen ../../../intern/guardedalloc ../../../extern/rangetree diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index d6c642ff80b..41ca96b109e 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -68,6 +68,19 @@ * * This has the effect from the users POV of leaving the mesh un-touched, * and only editing the active shape key-block. + * + * \subsection other_notes Other Notes + * + * Other details noted here which might not be so obvious: + * + * - The #CD_SHAPEKEY layer is only used in edit-mode, + * and the #Mesh.key is only used in object-mode. + * Although the #CD_SHAPEKEY custom-data layer is converted into #Key data-blocks for each + * undo-step while in edit-mode. + * - The #CD_SHAPE_KEYINDEX layer is used to check if vertices existed when entering edit-mode. + * Values of the indices are only used for shape-keys when the #CD_SHAPEKEY layer can't be found, + * allowing coordinates from the #Key to be used to prevent data-loss. + * These indices are also used to maintain correct indices for hook modifiers and vertex parents. */ #include "DNA_key_types.h" @@ -98,6 +111,10 @@ #include "bmesh.h" #include "intern/bmesh_private.h" /* For element checking. */ +#include "CLG_log.h" + +static CLG_LogRef LOG = {"bmesh.mesh.convert"}; + using blender::Array; using blender::IndexRange; using blender::Span; @@ -567,8 +584,89 @@ static BMVert **bm_to_mesh_vertex_map(BMesh *bm, int ototvert) return vertMap; } +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh to Shape Key Conversion + * + * There are some details relating to using data from shape keys that need to be + * considered carefully for shape key synchronization logic. + * + * Key Block Usage + * *************** + * + * Key blocks (data in #Mesh.key must be used carefully). + * + * They can be used to query which key blocks are relative to the basis + * since it's not possible to add/remove/reorder key blocks while in edit-mode. + * + * Key Block Coordinates + * ===================== + * + * Key blocks locations must *not* be used. This was done from v2.67 to 3.0, + * causing bugs T35170 & T44415. + * + * Shape key synchronizing could work under the assumption that the key-block is + * fixed-in-place when entering edit-mode allowing them to be used as a reference when exiting. + * It often does work but isn't reliable since for e.g. rendering may flush changes + * from the edit-mesh to the key-block (there are a handful of other situations where + * changes may be flushed, see #ED_editors_flush_edits and related functions). + * When using undo, it's not known if the data in key-block is from the past or future, + * so just don't use this data as it causes pain and suffering for users and developers alike. + * + * Instead, use the shape-key values stored in #CD_SHAPEKEY since they are reliably + * based on the original locations, unless explicitly manipulated. + * It's important to write the final shape-key values back to the #CD_SHAPEKEY so applying + * the difference between the original-basis and the new coordinates isn't done multiple times. + * Therefore #ED_editors_flush_edits and other flushing calls will update both the #Mesh.key + * and the edit-mode #CD_SHAPEKEY custom-data layers. + * + * WARNING: There is an exception to the rule of ignoring coordinates in the destination: + * that is when shape-key data in `bm` can't be found (which is itself an error/exception). + * In this case our own rule is violated as the alternative is loosing the shape-data entirely. + * + * Flushing Coordinates Back to the #BMesh + * --------------------------------------- + * + * The edit-mesh may be flushed back to the #Mesh and #Key used to generate it. + * When this is done, the new values are written back to the #BMesh's #CD_SHAPEKEY as well. + * This is necessary when editing basis-shapes so the difference in shape keys + * is not applied multiple times. If it were important to avoid it could be skipped while + * exiting edit-mode (as the entire #BMesh is freed in that case), however it's just copying + * back a `float[3]` so the work to check if it's necessary isn't worth the overhead. + * + * In general updating the #BMesh's #CD_SHAPEKEY makes shake-key logic easier to reason about + * since it means flushing data back to the mesh has the same behavior as exiting and entering + * edit-mode (a more common operation). Meaning there is one less corner-case to have to consider. + * + * Exceptional Cases + * ***************** + * + * There are some situations that should not happen in typical usage but are + * still handled in this code, since failure to handle them could loose user-data. + * These could be investigated further since if they never happen in practice, + * we might consider removing them. However, the possibility of an mesh directly + * being modified by Python or some other low level logic that changes key-blocks + * means there is a potential this to happen so keeping code to these cases remain supported. + * + * - Custom Data & Mesh Key Block Synchronization. + * Key blocks in `me->key->block` should always have an associated + * #CD_SHAPEKEY layer in `bm->vdata`. + * If they don't there are two fall-backs for setting the location, + * - Use the value from the original shape key + * WARNING: this is technically incorrect! (see note on "Key Block Usage"). + * - Use the current vertex location, + * Also not correct but it's better then having it zeroed for e.g. + * + * - Missing key-index layer. + * In this case the basis key wont apply it's deltas to other keys and in the case + * a shape-key layer is missing, its coordinates will be initialized from the edit-mesh + * vertex locations instead of attempting to remap the shape-keys coordinates. + * + * \note These cases are considered abnormal and shouldn't occur in typical usage. + * A warning is logged in this case to help troubleshooting bugs with shape-keys. + * \{ */ + /** - * Returns custom-data shapekey index from a keyblock or -1 + * Returns custom-data shape-key index from a key-block or -1 * \note could split this out into a more generic function. */ static int bm_to_mesh_shape_layer_index_from_kb(BMesh *bm, KeyBlock *currkey) @@ -587,6 +685,196 @@ static int bm_to_mesh_shape_layer_index_from_kb(BMesh *bm, KeyBlock *currkey) return -1; } +/** + * Update `key` with shape key data stored in `bm`. + * + * \param bm: The source BMesh. + * \param key: The destination key. + * \param mvert: The destination vertex array (in some situations it's coordinates are updated). + */ +static void bm_to_mesh_shape(BMesh *bm, Key *key, MVert *mvert) +{ + KeyBlock *actkey = static_cast(BLI_findlink(&key->block, bm->shapenr - 1)); + + /* It's unlikely this ever remains false, check for correctness. */ + bool actkey_has_layer = false; + + /* Go through and find any shape-key custom-data layers + * that might not have corresponding KeyBlocks, and add them if necessary. */ + for (int i = 0; i < bm->vdata.totlayer; i++) { + if (bm->vdata.layers[i].type != CD_SHAPEKEY) { + continue; + } + + KeyBlock *currkey; + for (currkey = (KeyBlock *)key->block.first; currkey; currkey = currkey->next) { + if (currkey->uid == bm->vdata.layers[i].uid) { + break; + } + } + + if (currkey) { + if (currkey == actkey) { + actkey_has_layer = true; + } + } + else { + currkey = BKE_keyblock_add(key, bm->vdata.layers[i].name); + currkey->uid = bm->vdata.layers[i].uid; + } + } + + const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX); + BMIter iter; + BMVert *eve; + float(*ofs)[3] = nullptr; + + /* Editing the basis key updates others. */ + if ((key->type == KEY_RELATIVE) && + /* The shape-key coordinates used from entering edit-mode are used. */ + (actkey_has_layer == true) && + /* Original key-indices are only used to check the vertex existed when entering edit-mode. */ + (cd_shape_keyindex_offset != -1) && + /* Offsets are only needed if the current shape is a basis for others. */ + BKE_keyblock_is_basis(key, bm->shapenr - 1)) { + + BLI_assert(actkey != nullptr); /* Assured by `actkey_has_layer` check. */ + const int actkey_uuid = bm_to_mesh_shape_layer_index_from_kb(bm, actkey); + + /* Since `actkey_has_layer == true`, this must never fail. */ + BLI_assert(actkey_uuid != -1); + + const int cd_shape_offset = CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, actkey_uuid); + + ofs = static_cast(MEM_mallocN(sizeof(float[3]) * bm->totvert, __func__)); + int i; + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { + const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset); + /* Check the vertex existed when entering edit-mode (otherwise don't apply an offset). */ + if (keyi != ORIGINDEX_NONE) { + float *co_orig = (float *)BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset); + /* Could use 'eve->co' or the destination #MVert.co, they're the same at this point. */ + sub_v3_v3v3(ofs[i], eve->co, co_orig); + } + else { + /* If there are new vertices in the mesh, we can't propagate the offset + * because it will only work for the existing vertices and not the new + * ones, creating a mess when doing e.g. subdivide + translate. */ + MEM_freeN(ofs); + ofs = nullptr; + break; + } + } + } + + LISTBASE_FOREACH (KeyBlock *, currkey, &key->block) { + int keyi; + float(*currkey_data)[3]; + + const int currkey_uuid = bm_to_mesh_shape_layer_index_from_kb(bm, currkey); + const int cd_shape_offset = (currkey_uuid == -1) ? + -1 : + CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, currkey_uuid); + + /* Common case, the layer data is available, use it where possible. */ + if (cd_shape_offset != -1) { + const bool apply_offset = (ofs != nullptr) && (currkey != actkey) && + (bm->shapenr - 1 == currkey->relative); + + if (currkey->data && (currkey->totelem == bm->totvert)) { + /* Use memory in-place. */ + } + else { + currkey->data = MEM_reallocN(currkey->data, key->elemsize * bm->totvert); + currkey->totelem = bm->totvert; + } + currkey_data = (float(*)[3])currkey->data; + + int i; + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { + float *co_orig = (float *)BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset); + + if (currkey == actkey) { + copy_v3_v3(currkey_data[i], eve->co); + + if (actkey != key->refkey) { + /* Without this, the real mesh coordinates (uneditable) as soon as you create + * the Basis shape, see: T30771 for details. */ + if (cd_shape_keyindex_offset != -1) { + keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset); + if (keyi != ORIGINDEX_NONE) { + copy_v3_v3(mvert[i].co, co_orig); + } + } + } + } + else { + copy_v3_v3(currkey_data[i], co_orig); + } + + /* Propagate edited basis offsets to other shapes. */ + if (apply_offset) { + add_v3_v3(currkey_data[i], ofs[i]); + } + + /* Apply back new coordinates shape-keys that have offset into #BMesh. + * Otherwise, in case we call again #BM_mesh_bm_to_me on same #BMesh, + * we'll apply diff from previous call to #BM_mesh_bm_to_me, + * to shape-key values from original creation of the #BMesh. See T50524. */ + copy_v3_v3(co_orig, currkey_data[i]); + } + } + else { + /* No original layer data, use fallback information. */ + if (currkey->data && (cd_shape_keyindex_offset != -1)) { + CLOG_WARN(&LOG, + "Found shape-key but no CD_SHAPEKEY layers to read from, " + "using existing shake-key data where possible"); + } + else { + CLOG_WARN(&LOG, + "Found shape-key but no CD_SHAPEKEY layers to read from, " + "using basis shape-key data"); + } + + currkey_data = static_cast( + MEM_mallocN(key->elemsize * bm->totvert, "currkey->data")); + + int i; + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { + + if ((currkey->data != nullptr) && (cd_shape_keyindex_offset != -1) && + ((keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset)) != ORIGINDEX_NONE) && + (keyi < currkey->totelem)) { + /* Reconstruct keys via vertices original key indices. + * WARNING(@campbellbarton): `currkey->data` is known to be unreliable as the edit-mesh + * coordinates may be flushed back to the shape-key when exporting or rendering. + * This is a last resort! If this branch is running as part of regular usage + * it can be considered a bug. */ + const float(*oldkey)[3] = static_cast(currkey->data); + copy_v3_v3(currkey_data[i], oldkey[keyi]); + } + else { + /* Fail! fill in with dummy value. */ + copy_v3_v3(currkey_data[i], eve->co); + } + } + + currkey->totelem = bm->totvert; + if (currkey->data) { + MEM_freeN(currkey->data); + } + currkey->data = currkey_data; + } + } + + if (ofs) { + MEM_freeN(ofs); + } +} + +/** \} */ + BLI_INLINE void bmesh_quick_edgedraw_flag(MEdge *med, BMEdge *e) { /* This is a cheap way to set the edge draw, its not precise and will @@ -618,23 +906,8 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh const int cd_edge_crease_offset = CustomData_get_offset(&bm->edata, CD_CREASE); const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX); - MVert *oldverts = nullptr; const int ototvert = me->totvert; - if (me->key && (cd_shape_keyindex_offset != -1)) { - /* Keep the old verts in case we are working on* a key, which is done at the end. */ - - /* Use the array in-place instead of duplicating the array. */ -#if 0 - oldverts = MEM_dupallocN(me->mvert); -#else - oldverts = me->mvert; - me->mvert = nullptr; - CustomData_update_typemap(&me->vdata); - CustomData_set_layer(&me->vdata, CD_MVERT, nullptr); -#endif - } - /* Free custom data. */ CustomData_free(&me->vdata, me->totvert); CustomData_free(&me->edata, me->totedge); @@ -860,152 +1133,8 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh } } - /* See comment below, this logic is in twice. */ - if (me->key) { - KeyBlock *currkey; - KeyBlock *actkey = static_cast(BLI_findlink(&me->key->block, bm->shapenr - 1)); - - float(*ofs)[3] = nullptr; - - /* Go through and find any shape-key custom-data layers - * that might not have corresponding KeyBlocks, and add them if necessary. */ - for (i = 0; i < bm->vdata.totlayer; i++) { - if (bm->vdata.layers[i].type != CD_SHAPEKEY) { - continue; - } - - for (currkey = (KeyBlock *)me->key->block.first; currkey; currkey = currkey->next) { - if (currkey->uid == bm->vdata.layers[i].uid) { - break; - } - } - - if (!currkey) { - currkey = BKE_keyblock_add(me->key, bm->vdata.layers[i].name); - currkey->uid = bm->vdata.layers[i].uid; - } - } - - /* Editing the base key should update others. */ - if (/* Only need offsets for relative shape keys. */ - (me->key->type == KEY_RELATIVE) && - - /* Unlikely, but the active key may not be valid if the - * BMesh and the mesh are out of sync. */ - (actkey != nullptr) && - - /* Not used here, but 'oldverts' is used later for applying 'ofs'. */ - (oldverts != nullptr) && - - /* Needed for referencing oldverts. */ - (cd_shape_keyindex_offset != -1)) { - - const bool act_is_basis = BKE_keyblock_is_basis(me->key, bm->shapenr - 1); - - /* Active key is a base. */ - if (act_is_basis) { - const float(*fp)[3] = static_cast(actkey->data); - - ofs = static_cast( - MEM_callocN(sizeof(float[3]) * bm->totvert, "currkey->data")); - mvert = me->mvert; - BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { - const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset); - - /* Could use 'eve->co' or 'mvert->co', they're the same at this point. */ - if (keyi != ORIGINDEX_NONE && keyi < actkey->totelem) { - sub_v3_v3v3(ofs[i], mvert->co, fp[keyi]); - } - else { - /* If there are new vertices in the mesh, we can't propagate the offset - * because it will only work for the existing vertices and not the new - * ones, creating a mess when doing e.g. subdivide + translate. */ - MEM_freeN(ofs); - ofs = nullptr; - break; - } - - mvert++; - } - } - } - - LISTBASE_FOREACH (KeyBlock *, currkey, &me->key->block) { - int keyi; - const float(*ofs_pt)[3] = ofs; - float *newkey, (*oldkey)[3], *fp; - - const int currkey_uuid = bm_to_mesh_shape_layer_index_from_kb(bm, currkey); - const int cd_shape_offset = (currkey_uuid == -1) ? -1 : - CustomData_get_n_offset(&bm->vdata, - CD_SHAPEKEY, - currkey_uuid); - const bool apply_offset = (cd_shape_offset != -1) && (ofs != nullptr) && - (currkey != actkey) && (bm->shapenr - 1 == currkey->relative); - - fp = newkey = static_cast( - MEM_callocN(me->key->elemsize * bm->totvert, "currkey->data")); - oldkey = static_cast(currkey->data); - - mvert = me->mvert; - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - - if (currkey == actkey) { - copy_v3_v3(fp, eve->co); - - if (actkey != me->key->refkey) { /* Important see bug T30771. */ - if (cd_shape_keyindex_offset != -1) { - if (oldverts) { - keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset); - if (keyi != ORIGINDEX_NONE && keyi < currkey->totelem) { /* Valid old vertex. */ - copy_v3_v3(mvert->co, oldverts[keyi].co); - } - } - } - } - } - else if (cd_shape_offset != -1) { - /* In most cases this runs. */ - copy_v3_v3(fp, (const float *)BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset)); - } - else if ((oldkey != nullptr) && (cd_shape_keyindex_offset != -1) && - ((keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset)) != ORIGINDEX_NONE) && - (keyi < currkey->totelem)) { - /* Old method of reconstructing keys via vertices original key indices, - * currently used if the new method above fails - * (which is theoretically possible in certain cases of undo). */ - copy_v3_v3(fp, oldkey[keyi]); - } - else { - /* Fail! fill in with dummy value. */ - copy_v3_v3(fp, mvert->co); - } - - /* Propagate edited basis offsets to other shapes. */ - if (apply_offset) { - add_v3_v3(fp, *ofs_pt++); - /* Apply back new coordinates shape-keys that have offset into BMesh. - * Otherwise, in case we call again #BM_mesh_bm_to_me on same BMesh, - * we'll apply diff from previous call to #BM_mesh_bm_to_me, - * to shape-key values from *original creation of the BMesh*. See T50524. */ - copy_v3_v3((float *)BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset), fp); - } - - fp += 3; - mvert++; - } - - currkey->totelem = bm->totvert; - if (currkey->data) { - MEM_freeN(currkey->data); - } - currkey->data = newkey; - } - - if (ofs) { - MEM_freeN(ofs); - } + bm_to_mesh_shape(bm, me->key, me->mvert); } /* Run this even when shape keys aren't used since it may be used for hooks or vertex parents. */ @@ -1019,10 +1148,6 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh } } - if (oldverts != nullptr) { - MEM_freeN(oldverts); - } - /* Topology could be changed, ensure #CD_MDISPS are ok. */ multires_topology_changed(me); diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index 59c357aa416..2960070714c 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -646,7 +646,7 @@ static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key, Undo return um; } -static void undomesh_to_editmesh(UndoMesh *um, Object *ob, BMEditMesh *em, Key *key) +static void undomesh_to_editmesh(UndoMesh *um, Object *ob, BMEditMesh *em) { BMEditMesh *em_tmp; BMesh *bm; @@ -702,29 +702,6 @@ static void undomesh_to_editmesh(UndoMesh *um, Object *ob, BMEditMesh *em, Key * bm->spacearr_dirty = BM_SPACEARR_DIRTY_ALL; - /* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens - * if the active is a basis for any other. */ - if (key && (key->type == KEY_RELATIVE)) { - /* Since we can't add, remove or reorder keyblocks in editmode, it's safe to assume - * shapenr from restored bmesh and keyblock indices are in sync. */ - const int kb_act_idx = ob->shapenr - 1; - - /* If it is, let's patch the current mesh key block to its restored value. - * Else, the offsets won't be computed and it won't matter. */ - if (BKE_keyblock_is_basis(key, kb_act_idx)) { - KeyBlock *kb_act = BLI_findlink(&key->block, kb_act_idx); - - if (kb_act->totelem != um->me.totvert) { - /* The current mesh has some extra/missing verts compared to the undo, adjust. */ - MEM_SAFE_FREE(kb_act->data); - kb_act->data = MEM_mallocN((size_t)(key->elemsize) * bm->totvert, __func__); - kb_act->totelem = um->me.totvert; - } - - BKE_keyblock_update_from_mesh(&um->me, kb_act); - } - } - ob->shapenr = um->shapenr; MEM_freeN(em_tmp); @@ -872,7 +849,7 @@ static void mesh_undosys_step_decode(struct bContext *C, continue; } BMEditMesh *em = me->edit_mesh; - undomesh_to_editmesh(&elem->data, obedit, em, me->key); + undomesh_to_editmesh(&elem->data, obedit, em); em->needs_flush_to_id = 1; DEG_id_tag_update(&me->id, ID_RECALC_GEOMETRY); } -- cgit v1.2.3 From 2a5df72d9b334c4ce4924bd86d00a4fd262b6da0 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 10:07:01 +1100 Subject: Revert "Cleanup: quiet const cast warning" This reverts commit 626fb290eb43eb43b9451c757b1e906f0b5eeb53. This was an intentional change for FFMPEG v5.0, removing the cast should be done in a version check instead. --- source/blender/blenkernel/intern/writeffmpeg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 61efade9c93..dee852ca33a 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -869,7 +869,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, { /* Handle to the output file */ AVFormatContext *of; - AVOutputFormat *fmt; + const AVOutputFormat *fmt; char name[FILE_MAX], error[1024]; const char **exts; -- cgit v1.2.3 From b13bf8556b8d4d8456deb8fa769ce963aed47065 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 10:08:38 +1100 Subject: Cleanup: clang-format --- source/blender/blenlib/intern/delaunay_2d.cc | 6 +++--- source/blender/draw/intern/draw_manager_data.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/blenlib/intern/delaunay_2d.cc b/source/blender/blenlib/intern/delaunay_2d.cc index cc5550ee34a..b7dbd7d679c 100644 --- a/source/blender/blenlib/intern/delaunay_2d.cc +++ b/source/blender/blenlib/intern/delaunay_2d.cc @@ -2557,9 +2557,9 @@ template void detect_holes(CDT_state *cdt_state) continue; /* Don't count hits on edges between faces in same region. */ } auto isect = isect_seg_seg(ray_end.exact, - mid.exact, - e->symedges[0].vert->co.exact, - e->symedges[1].vert->co.exact); + mid.exact, + e->symedges[0].vert->co.exact, + e->symedges[1].vert->co.exact); switch (isect.kind) { case isect_result>::LINE_LINE_CROSS: { hits++; diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 0b97323f18f..95691a0df68 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -566,7 +566,7 @@ static void drw_call_obinfos_init(DRWObjectInfos *ob_infos, Object *ob) drw_call_calc_orco(ob, ob_infos->orcotexfac); /* Random float value. */ uint random = (DST.dupli_source) ? - DST.dupli_source->random_id : + DST.dupli_source->random_id : /* TODO(fclem): this is rather costly to do at runtime. Maybe we can * put it in ob->runtime and make depsgraph ensure it is up to date. */ BLI_hash_int_2d(BLI_hash_string(ob->id.name + 2), 0); -- cgit v1.2.3 From ec5bbebf3ee165869eba5e980d22f261d4d06f68 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 10:16:25 +1100 Subject: Python: bump minimum version to 3.10 Since Python 3.10 is now supported on all platform, bump the minimum version to reduce the number of Python versions that need to be supported simultaneously. Reviewed By: LazyDodo, sybren, mont29, brecht Ref D13943 --- CMakeLists.txt | 4 ++-- build_files/build_environment/install_deps.sh | 2 +- source/blender/python/intern/bpy_capi_utils.h | 4 ++-- source/blender/python/mathutils/mathutils.c | 4 ---- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f73149adc04..85a9d633d3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -906,8 +906,8 @@ if(WITH_PYTHON) # Do this before main 'platform_*' checks, # because UNIX will search for the old Python paths which may not exist. # giving errors about missing paths before this case is met. - if(DEFINED PYTHON_VERSION AND "${PYTHON_VERSION}" VERSION_LESS "3.9") - message(FATAL_ERROR "At least Python 3.9 is required to build, but found Python ${PYTHON_VERSION}") + if(DEFINED PYTHON_VERSION AND "${PYTHON_VERSION}" VERSION_LESS "3.10") + message(FATAL_ERROR "At least Python 3.10 is required to build, but found Python ${PYTHON_VERSION}") endif() file(GLOB RESULT "${CMAKE_SOURCE_DIR}/release/scripts/addons") diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh index 03a75468400..bf38f4c2928 100755 --- a/build_files/build_environment/install_deps.sh +++ b/build_files/build_environment/install_deps.sh @@ -381,7 +381,7 @@ CLANG_FORMAT_VERSION_MEX="10.0" PYTHON_VERSION="3.10.2" PYTHON_VERSION_SHORT="3.10" -PYTHON_VERSION_MIN="3.9" +PYTHON_VERSION_MIN="3.10" PYTHON_VERSION_MEX="3.12" PYTHON_VERSION_INSTALLED=$PYTHON_VERSION_SHORT PYTHON_FORCE_BUILD=false diff --git a/source/blender/python/intern/bpy_capi_utils.h b/source/blender/python/intern/bpy_capi_utils.h index 0889dec36c8..a3cebea3fdb 100644 --- a/source/blender/python/intern/bpy_capi_utils.h +++ b/source/blender/python/intern/bpy_capi_utils.h @@ -20,8 +20,8 @@ #pragma once -#if PY_VERSION_HEX < 0x03090000 -# error "Python 3.9 or greater is required, you'll need to update your Python." +#if PY_VERSION_HEX < 0x030a0000 +# error "Python 3.10 or greater is required, you'll need to update your Python." #endif #ifdef __cplusplus diff --git a/source/blender/python/mathutils/mathutils.c b/source/blender/python/mathutils/mathutils.c index 6eee9ceebb8..22c4549575f 100644 --- a/source/blender/python/mathutils/mathutils.c +++ b/source/blender/python/mathutils/mathutils.c @@ -90,11 +90,7 @@ Py_hash_t mathutils_array_hash(const float *array, size_t array_len) x = 0x345678UL; i = 0; while (--len >= 0) { -#if PY_VERSION_HEX >= 0x30a0000 /* Version: 3.10. */ y = _Py_HashDouble(NULL, (double)(array[i++])); -#else - y = _Py_HashDouble((double)(array[i++])); -#endif if (y == -1) { return -1; } -- cgit v1.2.3 From fcda858e3200551a7ae12605c34e5931f6ee3c2e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 10:28:01 +1100 Subject: Cleanup: remove Python 3.9 annotation workarounds --- release/scripts/startup/bl_operators/wm.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 9ba5480cf5a..fd0671a2369 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -963,21 +963,19 @@ class WM_OT_url_open(Operator): return {'FINISHED'} -# NOTE: needed for Python 3.10 since there are name-space issues with annotations. -# This can be moved into the class as a static-method once Python 3.9x is dropped. -def _wm_url_open_preset_type_items(_self, _context): - return [item for (item, _) in WM_OT_url_open_preset.preset_items] - - class WM_OT_url_open_preset(Operator): """Open a preset website in the web browser""" bl_idname = "wm.url_open_preset" bl_label = "Open Preset Website" bl_options = {'INTERNAL'} + @staticmethod + def _wm_url_open_preset_type_items(_self, _context): + return [item for (item, _) in WM_OT_url_open_preset.preset_items] + type: EnumProperty( name="Site", - items=_wm_url_open_preset_type_items, + items=WM_OT_url_open_preset._wm_url_open_preset_type_items, ) id: StringProperty( @@ -1283,12 +1281,6 @@ rna_vector_subtype_items = ( ) -# NOTE: needed for Python 3.10 since there are name-space issues with annotations. -# This can be moved into the class as a static-method once Python 3.9x is dropped. -def _wm_properties_edit_subtype_items(_self, _context): - return WM_OT_properties_edit.subtype_items - - class WM_OT_properties_edit(Operator): """Change a custom property's type, or adjust how it is displayed in the interface""" bl_idname = "wm.properties_edit" @@ -1395,7 +1387,7 @@ class WM_OT_properties_edit(Operator): ) subtype: EnumProperty( name="Subtype", - items=_wm_properties_edit_subtype_items, + items=WM_OT_properties_edit.subtype_items, ) # String properties. -- cgit v1.2.3 From 459d9c1e3d88361c26479c88b75b83451e4d4794 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Sat, 12 Feb 2022 16:13:28 -0300 Subject: Fix (unreported): Issues with 'SNAP_NOT_SELECTED' for pose and edit modes This is a regression partially introduced in rB0a6f428be7f0. Bones being transformed into edit mode were snapping to themselves. And the bones of the pose mode weren't even snapping. (Curious that this was not reported). --- source/blender/editors/transform/transform_snap.c | 5 ++- .../editors/transform/transform_snap_object.c | 41 ++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 40be8e6e641..dd18e8d4656 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -644,13 +644,16 @@ static short snap_select_type_get(TransInfo *t) else if (!t->tsnap.snap_self) { r_snap_select = SNAP_NOT_ACTIVE; } + else { + r_snap_select = SNAP_NOT_SELECTED; + } } else if ((obedit_type == -1) && base_act && base_act->object && (base_act->object->mode & OB_MODE_PARTICLE_EDIT)) { /* Particles edit mode. */ } else if (obedit_type == -1) { - /* Object mode */ + /* Object or pose mode. */ r_snap_select = SNAP_NOT_SELECTED; } } diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index 9dc8b6cf69d..dc9315b6d5a 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -491,7 +491,10 @@ static void iter_snap_objects(SnapObjectContext *sctx, } } else if (snap_select == SNAP_NOT_SELECTED) { - if ((base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL)) { + if (is_object_active && !(base->object->mode & OB_MODE_OBJECT)) { + /* Pass. Consider the selection of elements being edited. */ + } + else if ((base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL)) { continue; } } @@ -1832,6 +1835,7 @@ static short snapArmature(SnapObjectContext *sctx, const struct SnapObjectParams *params, Object *ob_eval, const float obmat[4][4], + bool is_object_active, /* read/write args */ float *dist_px, /* return args */ @@ -1852,9 +1856,10 @@ static short snapArmature(SnapObjectContext *sctx, dist_squared_to_projected_aabb_precalc( &neasrest_precalc, lpmat, sctx->runtime.win_size, sctx->runtime.mval); - bool use_obedit = ((bArmature *)ob_eval->data)->edbo != NULL; + bArmature *arm = ob_eval->data; + const bool is_editmode = arm->edbo != NULL; - if (use_obedit == false) { + if (is_editmode == false) { /* Test BoundBox */ BoundBox *bb = BKE_armature_boundbox_get(ob_eval); if (bb && !snap_bound_box_check_dist(bb->vec[0], @@ -1873,10 +1878,11 @@ static short snapArmature(SnapObjectContext *sctx, mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]); } - const eSnapSelect snap_select = params->snap_select; - bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP; + const bool is_posemode = is_object_active && (ob_eval->mode & OB_MODE_POSE); + const bool skip_selected = (is_editmode || is_posemode) && + (params->snap_select == SNAP_NOT_SELECTED); + const bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP; - bArmature *arm = ob_eval->data; if (arm->edbo) { LISTBASE_FOREACH (EditBone *, eBone, arm->edbo) { if (eBone->layer & arm->layer) { @@ -1886,7 +1892,7 @@ static short snapArmature(SnapObjectContext *sctx, } const bool is_selected = (eBone->flag & (BONE_ROOTSEL | BONE_TIPSEL)) != 0; - if (is_selected && snap_select == SNAP_NOT_SELECTED) { + if (is_selected && skip_selected) { continue; } bool has_vert_snap = false; @@ -1930,10 +1936,16 @@ static short snapArmature(SnapObjectContext *sctx, else if (ob_eval->pose && ob_eval->pose->chanbase.first) { LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_eval->pose->chanbase) { Bone *bone = pchan->bone; - /* skip hidden bones */ if (!bone || (bone->flag & (BONE_HIDDEN_P | BONE_HIDDEN_PG))) { + /* Skip hidden bones. */ continue; } + + const bool is_selected = (bone->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL)) != 0; + if (is_selected && skip_selected) { + continue; + } + bool has_vert_snap = false; const float *head_vec = pchan->pose_head; const float *tail_vec = pchan->pose_tail; @@ -2699,7 +2711,7 @@ static void snap_obj_fn(SnapObjectContext *sctx, const struct SnapObjectParams *params, Object *ob_eval, float obmat[4][4], - bool UNUSED(is_object_active), + bool is_object_active, void *data) { struct SnapObjUserData *dt = data; @@ -2735,8 +2747,15 @@ static void snap_obj_fn(SnapObjectContext *sctx, break; } case OB_ARMATURE: - retval = snapArmature( - sctx, params, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + retval = snapArmature(sctx, + params, + ob_eval, + obmat, + is_object_active, + dt->dist_px, + dt->r_loc, + dt->r_no, + dt->r_index); break; case OB_CURVE: retval = snapCurve( -- cgit v1.2.3 From c73bacc36429a4973d73fc9340f18b5907756a4b Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Sun, 13 Feb 2022 10:08:23 -0300 Subject: Camera: Simplify View Frame code - No need for `normal_tx` array if we normalize the planes in `plane_tx`. - No need to calculate the distance squared to a plane (with `dist_signed_squared_to_plane_v3`) if the plane is normalized. `plane_point_side_v3` gets the real distance, accurately, efficiently and also signed. So normalize the planes of the member `CameraViewFrameData::plane_tx`. --- source/blender/blenkernel/intern/camera.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index d9c637eb177..910bacd7ced 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -566,9 +566,8 @@ void BKE_camera_view_frame(const Scene *scene, const Camera *camera, float r_vec #define CAMERA_VIEWFRAME_NUM_PLANES 4 typedef struct CameraViewFrameData { - float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes */ - float normal_tx[CAMERA_VIEWFRAME_NUM_PLANES][3]; - float dist_vals_sq[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance squared (signed) */ + float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes normalized */ + float dist_vals[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance (signed) */ unsigned int tot; /* Ortho camera only. */ @@ -585,8 +584,8 @@ static void camera_to_frame_view_cb(const float co[3], void *user_data) CameraViewFrameData *data = (CameraViewFrameData *)user_data; for (uint i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { - const float nd = dist_signed_squared_to_plane_v3(co, data->plane_tx[i]); - CLAMP_MAX(data->dist_vals_sq[i], nd); + const float nd = plane_point_side_v3(data->plane_tx[i], co); + CLAMP_MAX(data->dist_vals[i], nd); } if (data->is_ortho) { @@ -641,10 +640,11 @@ static void camera_frame_fit_data_init(const Scene *scene, /* Rotate planes and get normals from them */ for (uint i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { mul_m4_v4(camera_rotmat_transposed_inversed, data->plane_tx[i]); - normalize_v3_v3(data->normal_tx[i], data->plane_tx[i]); + /* Normalize. */ + data->plane_tx[i][3] /= normalize_v3(data->plane_tx[i]); } - copy_v4_fl(data->dist_vals_sq, FLT_MAX); + copy_v4_fl(data->dist_vals, FLT_MAX); data->tot = 0; data->is_ortho = params->is_ortho; if (params->is_ortho) { @@ -669,14 +669,9 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, const float *cam_axis_x = data->camera_rotmat[0]; const float *cam_axis_y = data->camera_rotmat[1]; const float *cam_axis_z = data->camera_rotmat[2]; - float dists[CAMERA_VIEWFRAME_NUM_PLANES]; + const float *dists = data->dist_vals; float scale_diff; - /* apply the dist-from-plane's to the transformed plane points */ - for (int i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { - dists[i] = sqrtf_signed(data->dist_vals_sq[i]); - } - if ((dists[0] + dists[2]) > (dists[1] + dists[3])) { scale_diff = (dists[1] + dists[3]) * (BLI_rctf_size_x(¶ms->viewplane) / BLI_rctf_size_y(¶ms->viewplane)); @@ -703,8 +698,8 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, /* apply the dist-from-plane's to the transformed plane points */ for (int i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { float co[3]; - mul_v3_v3fl(co, data->normal_tx[i], sqrtf_signed(data->dist_vals_sq[i])); - plane_from_point_normal_v3(plane_tx[i], co, data->normal_tx[i]); + mul_v3_v3fl(co, data->plane_tx[i], data->dist_vals[i]); + plane_from_point_normal_v3(plane_tx[i], co, data->plane_tx[i]); } if ((!isect_plane_plane_v3(plane_tx[0], plane_tx[2], plane_isect_1, plane_isect_1_no)) || -- cgit v1.2.3 From 18d3d283ecf548f22ea548cd01ffbf5afa93974d Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Thu, 17 Feb 2022 22:11:08 -0300 Subject: Fix wrong method name in gpu documentation `use_program_point_size` --> `program_point_size_set`. --- source/blender/python/gpu/gpu_py_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 26905c87de2..746074f0706 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -340,7 +340,7 @@ static PyObject *pygpu_state_front_facing_set(PyObject *UNUSED(self), PyObject * } PyDoc_STRVAR(pygpu_state_program_point_size_set_doc, - ".. function:: use_program_point_size(enable)\n" + ".. function:: program_point_size_set(enable)\n" "\n" " If enabled, the derived point size is taken from the (potentially clipped) " "shader builtin gl_PointSize.\n" -- cgit v1.2.3 From 75be58c63db4ecc365e56d7546e089494c3d2da0 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Sun, 20 Feb 2022 23:31:08 -0300 Subject: Fix errors in 'gpu.state' documentation `blend_depth_test_get` --> `depth_test_get` `depth_mask_set_get` --> `depth_mask_get` Thanks to @SBCV for pointing out these inconsistencies. --- source/blender/python/gpu/gpu_py_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 746074f0706..17b350deb14 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -165,7 +165,7 @@ static PyObject *pygpu_state_depth_test_set(PyObject *UNUSED(self), PyObject *va } PyDoc_STRVAR(pygpu_state_depth_test_get_doc, - ".. function:: blend_depth_test_get()\n" + ".. function:: depth_test_get()\n" "\n" " Current depth_test equation.\n" "\n"); @@ -193,7 +193,7 @@ static PyObject *pygpu_state_depth_mask_set(PyObject *UNUSED(self), PyObject *va } PyDoc_STRVAR(pygpu_state_depth_mask_get_doc, - ".. function:: depth_mask_set_get()\n" + ".. function:: depth_mask_get()\n" "\n" " Writing status in the depth component.\n"); static PyObject *pygpu_state_depth_mask_get(PyObject *UNUSED(self)) -- cgit v1.2.3 From 0f2e0a25e13d2d67f3d08d068ba11e255794bb6b Mon Sep 17 00:00:00 2001 From: YimingWu Date: Tue, 22 Feb 2022 12:51:49 +0800 Subject: LineArt: Consistent backface culling behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Now will remove lines if both adjacent faces are back face. 2. Added a check to respect material back face culling setting. 3. Changed label in the modifier to "Force Backface Culling" (which reflect more accurately with what the checkbox does). Reviewed By: Antonio Vazquez (antoniov), Aleš Jelovčan (frogstomp) Ref D14041 --- release/datafiles/locale | 2 +- release/scripts/addons | 2 +- .../gpencil_modifiers/intern/MOD_gpencillineart.c | 2 +- .../gpencil_modifiers/intern/lineart/MOD_lineart.h | 1 + .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 27 ++++++++++++++++++---- source/tools | 2 +- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/release/datafiles/locale b/release/datafiles/locale index 2d12637a69d..2a5095eed30 160000 --- a/release/datafiles/locale +++ b/release/datafiles/locale @@ -1 +1 @@ -Subproject commit 2d12637a69df7643484a8a3655b7eeb6faa170a7 +Subproject commit 2a5095eed3028e91624d27ca93e4c65f572b809d diff --git a/release/scripts/addons b/release/scripts/addons index 089aef61deb..bb62f10715a 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 089aef61debbece2baff6516e33fc7491629b1d0 +Subproject commit bb62f10715a871d7069d2b2c74b2efc97c3c350c diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index ea7a676b157..1058f861be3 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -393,7 +393,7 @@ static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) 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(col, ptr, "use_crease_on_sharp", 0, IFACE_("Crease On Sharp"), ICON_NONE); - uiItemR(col, ptr, "use_back_face_culling", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "use_back_face_culling", 0, IFACE_("Force Backface Culling"), ICON_NONE); } static void style_panel_draw(const bContext *UNUSED(C), Panel *panel) diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 2c7999699c7..5d952991cf7 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -343,6 +343,7 @@ typedef enum eLineartTriangleFlags { LRT_CULL_GENERATED = (1 << 2), LRT_TRIANGLE_INTERSECTION_ONLY = (1 << 3), LRT_TRIANGLE_NO_INTERSECTION = (1 << 4), + LRT_TRIANGLE_MAT_BACK_FACE_CULLING = (1 << 5), } eLineartTriangleFlags; /** diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index a5af66cbbc5..31dd37db1a7 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -1529,8 +1529,9 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, double *view_vector = vv; double dot_1 = 0, dot_2 = 0; double result; + bool material_back_face = ((tri1->flags | tri2->flags) & LRT_TRIANGLE_MAT_BACK_FACE_CULLING); - if (rb->use_contour || rb->use_back_face_culling) { + if (rb->use_contour || rb->use_back_face_culling || material_back_face) { if (rb->cam_is_persp) { sub_v3_v3v3_db(view_vector, rb->camera_pos, l->gloc); @@ -1555,14 +1556,19 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, tri2->flags |= LRT_CULL_DISCARD; } } + if (material_back_face) { + if (tri1->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_1 < 0) { + tri1->flags |= LRT_CULL_DISCARD; + } + if (tri2->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_2 < 0) { + tri2->flags |= LRT_CULL_DISCARD; + } + } } else { view_vector = rb->view_vector; } - dot_1 = dot_v3v3_db(view_vector, tri1->gn); - dot_2 = dot_v3v3_db(view_vector, tri2->gn); - if ((result = dot_1 * dot_2) <= 0 && (fabs(dot_1) + fabs(dot_2))) { edge_flag_result |= LRT_EDGE_FLAG_CONTOUR; } @@ -1573,6 +1579,16 @@ static uint16_t lineart_identify_feature_line(LineartRenderBuffer *rb, return edge_flag_result; } + /* Do not show lines other than contour on back face (because contour has one adjacent face that + * isn't a back face). + * TODO(Yiming): Do we need separate option for this? */ + if (rb->use_back_face_culling || + ((tri1->flags & tri2->flags) & LRT_TRIANGLE_MAT_BACK_FACE_CULLING)) { + if (dot_1 < 0 && dot_2 < 0) { + return edge_flag_result; + } + } + if (rb->use_crease) { if (rb->sharp_as_crease && !BM_elem_flag_test(e, BM_ELEM_SMOOTH)) { edge_flag_result |= LRT_EDGE_FLAG_CREASE; @@ -1859,6 +1875,9 @@ static void lineart_geometry_object_load(LineartObjectInfo *obi, LineartRenderBu mat->lineart.material_mask_bits : 0); tri->mat_occlusion |= (mat ? mat->lineart.mat_occlusion : 1); + tri->flags |= (mat && (mat->blend_flag & MA_BL_CULL_BACKFACE)) ? + LRT_TRIANGLE_MAT_BACK_FACE_CULLING : + 0; tri->intersection_mask = obi->override_intersection_mask; diff --git a/source/tools b/source/tools index 515e67c1932..7fd2ed908b4 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit 515e67c1932bc06f24cb50b621265c2a6e8a25a9 +Subproject commit 7fd2ed908b4f50140670caf6786e5ed245b79137 -- cgit v1.2.3 From 2234bfbcdb14299aa2ed28ed3ee6682c6d9a7a0c Mon Sep 17 00:00:00 2001 From: YimingWu Date: Tue, 22 Feb 2022 12:54:35 +0800 Subject: GPencil: Simplify sample modifier improvements. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Now handles cyclic strokes correctly. 2. Added a sharp threshold value to allow preservation of sharp corners. Reviewed By: Antonio Vazquez (antoniov), Aleš Jelovčan (frogstomp) Ref D14044 --- source/blender/blenkernel/BKE_gpencil_geom.h | 6 +- source/blender/blenkernel/intern/gpencil_curve.c | 2 +- source/blender/blenkernel/intern/gpencil_geom.cc | 98 ++++++++++++++++------ source/blender/editors/gpencil/gpencil_edit.c | 3 +- .../blender/editors/gpencil/gpencil_trace_utils.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilsimplify.c | 3 +- .../io/gpencil/intern/gpencil_io_export_pdf.cc | 2 +- .../io/gpencil/intern/gpencil_io_export_svg.cc | 2 +- .../blender/makesdna/DNA_gpencil_modifier_types.h | 3 +- .../blender/makesrna/intern/rna_gpencil_modifier.c | 8 ++ 10 files changed, 94 insertions(+), 35 deletions(-) diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index eb61bd32eee..4127030e96f 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -208,11 +208,13 @@ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd, * \param gpd: Grease pencil data-block * \param gps: Stroke to sample * \param dist: Distance of one segment + * \param sharp_threshold: Threshold for preserving sharp corners */ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd, struct bGPDstroke *gps, - float dist, - bool select); + const float dist, + const bool select, + const float sharp_threshold); /** * Apply smooth position to stroke point. * \param gps: Stroke to smooth diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 5ce45f6df5a..ee6b77e6463 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -443,7 +443,7 @@ static void gpencil_convert_spline(Main *bmain, } if (sample > 0.0f) { - BKE_gpencil_stroke_sample(gpd, gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false, 0); } /* Recalc fill geometry. */ diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index 365171b300f..1823d84d48d 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -202,8 +202,8 @@ static int stroke_march_next_point(const bGPDstroke *gps, int next_point_index = index_next_pt; bGPDspoint *pt = nullptr; - if (!(next_point_index < gps->totpoints)) { - return -1; + if (next_point_index == gps->totpoints) { + next_point_index = 0; } copy_v3_v3(step_start, current); @@ -211,15 +211,33 @@ static int stroke_march_next_point(const bGPDstroke *gps, copy_v3_v3(point, &pt->x); remaining_till_next = len_v3v3(point, step_start); - while (remaining_till_next < remaining_march) { + while (remaining_till_next < remaining_march && next_point_index) { remaining_march -= remaining_till_next; pt = &gps->points[next_point_index]; + if (pt->flag & GP_SPOINT_TEMP_TAG) { + pt = &gps->points[next_point_index]; + copy_v3_v3(result, &pt->x); + *pressure = gps->points[next_point_index].pressure; + *strength = gps->points[next_point_index].strength; + memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float[4])); + + *index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1); + *index_to = next_point_index; + *ratio_result = 1.0f; + next_point_index++; + return next_point_index == 0 ? gps->totpoints : next_point_index; + } + next_point_index++; copy_v3_v3(point, &pt->x); copy_v3_v3(step_start, point); - next_point_index++; if (!(next_point_index < gps->totpoints)) { - next_point_index = gps->totpoints - 1; - break; + if (gps->flag & GP_STROKE_CYCLIC) { + next_point_index = 0; + } + else { + next_point_index = gps->totpoints - 1; + break; + } } pt = &gps->points[next_point_index]; copy_v3_v3(point, &pt->x); @@ -232,35 +250,37 @@ static int stroke_march_next_point(const bGPDstroke *gps, *strength = gps->points[next_point_index].strength; memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float[4])); - *index_from = next_point_index - 1; + *index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1); *index_to = next_point_index; *ratio_result = 1.0f; return 0; } + *index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1); + *index_to = next_point_index; + float ratio = remaining_march / remaining_till_next; interp_v3_v3v3(result, step_start, point, ratio); + *ratio_result = ratio; + *pressure = interpf( - gps->points[next_point_index].pressure, gps->points[next_point_index - 1].pressure, ratio); + gps->points[next_point_index].pressure, gps->points[*index_from].pressure, ratio); *strength = interpf( - gps->points[next_point_index].strength, gps->points[next_point_index - 1].strength, ratio); + gps->points[next_point_index].strength, gps->points[*index_from].strength, ratio); interp_v4_v4v4(vert_color, - gps->points[next_point_index - 1].vert_color, + gps->points[*index_from].vert_color, gps->points[next_point_index].vert_color, ratio); - *index_from = next_point_index - 1; - *index_to = next_point_index; - *ratio_result = ratio; - - return next_point_index; + return next_point_index == 0 ? gps->totpoints : next_point_index; } static int stroke_march_next_point_no_interp(const bGPDstroke *gps, const int index_next_pt, const float *current, const float dist, + const float sharp_threshold, float *result) { float remaining_till_next = 0.0f; @@ -270,8 +290,8 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps, int next_point_index = index_next_pt; bGPDspoint *pt = nullptr; - if (!(next_point_index < gps->totpoints)) { - return -1; + if (next_point_index == gps->totpoints) { + next_point_index = 0; } copy_v3_v3(step_start, current); @@ -279,15 +299,29 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps, copy_v3_v3(point, &pt->x); remaining_till_next = len_v3v3(point, step_start); - while (remaining_till_next < remaining_march) { + while (remaining_till_next < remaining_march && next_point_index) { remaining_march -= remaining_till_next; pt = &gps->points[next_point_index]; + if (next_point_index < gps->totpoints - 1 && + angle_v3v3v3(&gps->points[next_point_index - 1].x, + &gps->points[next_point_index].x, + &gps->points[next_point_index + 1].x) < sharp_threshold) { + copy_v3_v3(result, &pt->x); + pt->flag |= GP_SPOINT_TEMP_TAG; + next_point_index++; + return next_point_index == 0 ? gps->totpoints : next_point_index; + } + next_point_index++; copy_v3_v3(point, &pt->x); copy_v3_v3(step_start, point); - next_point_index++; if (!(next_point_index < gps->totpoints)) { - next_point_index = gps->totpoints - 1; - break; + if (gps->flag & GP_STROKE_CYCLIC) { + next_point_index = 0; + } + else { + next_point_index = gps->totpoints - 1; + break; + } } pt = &gps->points[next_point_index]; copy_v3_v3(point, &pt->x); @@ -296,15 +330,16 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps, if (remaining_till_next < remaining_march) { pt = &gps->points[next_point_index]; copy_v3_v3(result, &pt->x); + /* Stroke marching only terminates here. */ return 0; } float ratio = remaining_march / remaining_till_next; interp_v3_v3v3(result, step_start, point, ratio); - return next_point_index; + return next_point_index == 0 ? gps->totpoints : next_point_index; } -static int stroke_march_count(const bGPDstroke *gps, const float dist) +static int stroke_march_count(const bGPDstroke *gps, const float dist, const float sharp_threshold) { int point_count = 0; float point[3]; @@ -315,8 +350,13 @@ static int stroke_march_count(const bGPDstroke *gps, const float dist) copy_v3_v3(point, &pt->x); point_count++; + /* Sharp points will be tagged by the stroke_march_next_point_no_interp() call below. */ + for (int i = 0; i < gps->totpoints; i++) { + gps->points[i].flag &= (~GP_SPOINT_TEMP_TAG); + } + while ((next_point_index = stroke_march_next_point_no_interp( - gps, next_point_index, point, dist, point)) > -1) { + gps, next_point_index, point, dist, sharp_threshold, point)) > -1) { point_count++; if (next_point_index == 0) { break; /* last point finished */ @@ -394,7 +434,11 @@ static void stroke_interpolate_deform_weights( } } -bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select) +bool BKE_gpencil_stroke_sample(bGPdata *gpd, + bGPDstroke *gps, + const float dist, + const bool select, + const float sharp_threshold) { bGPDspoint *pt = gps->points; bGPDspoint *pt1 = nullptr; @@ -406,7 +450,7 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, return false; } /* TODO: Implement feature point preservation. */ - int count = stroke_march_count(gps, dist); + int count = stroke_march_count(gps, dist, sharp_threshold); bGPDspoint *new_pt = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * count, "gp_stroke_points_sampled"); @@ -491,6 +535,8 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, gps->totpoints = i; + gps->flag &= (~GP_STROKE_CYCLIC); + /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, gps); diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 680b313c47b..d734fb2678e 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -4356,6 +4356,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); const float length = RNA_float_get(op->ptr, "length"); + const float sharp_threshold = RNA_float_get(op->ptr, "sharp_threshold"); /* sanity checks */ if (ELEM(NULL, gpd)) { @@ -4365,7 +4366,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op) /* Go through each editable + selected stroke */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_sample(gpd, gps, length, true); + BKE_gpencil_stroke_sample(gpd, gps, length, true, sharp_threshold); } } GP_EDITABLE_STROKES_END(gpstroke_iter); diff --git a/source/blender/editors/gpencil/gpencil_trace_utils.c b/source/blender/editors/gpencil/gpencil_trace_utils.c index 10be8c3e91e..735759d40ec 100644 --- a/source/blender/editors/gpencil/gpencil_trace_utils.c +++ b/source/blender/editors/gpencil/gpencil_trace_utils.c @@ -314,7 +314,7 @@ void ED_gpencil_trace_data_to_strokes(Main *bmain, if (sample > 0.0f) { /* Resample stroke. Don't need to call to BKE_gpencil_stroke_geometry_update() because * the sample function already call that. */ - BKE_gpencil_stroke_sample(gpd, gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false, 0); } else { BKE_gpencil_stroke_geometry_update(gpd, gps); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c index 3b7b82e3085..365b9afe348 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c @@ -88,7 +88,7 @@ static void deformStroke(GpencilModifierData *md, break; } case GP_SIMPLIFY_SAMPLE: { - BKE_gpencil_stroke_sample(gpd, gps, mmd->length, false); + BKE_gpencil_stroke_sample(gpd, gps, mmd->length, false, mmd->sharp_threshold); break; } case GP_SIMPLIFY_MERGE: { @@ -143,6 +143,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) } else if (mode == GP_SIMPLIFY_SAMPLE) { uiItemR(layout, ptr, "length", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "sharp_threshold", 0, NULL, ICON_NONE); } else if (mode == GP_SIMPLIFY_MERGE) { uiItemR(layout, ptr, "distance", 0, NULL, ICON_NONE); diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc index 3c4676beb75..cc3eab02e07 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc @@ -196,7 +196,7 @@ void GpencilExporterPDF::export_gpencil_layers() /* Sample stroke. */ if (params_.stroke_sample > 0.0f) { - BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false); + BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false, 0); } export_stroke_to_polyline(gpl, gps_perimeter, is_stroke, false, false); diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc index d21af4c09eb..f8d30546e39 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc @@ -221,7 +221,7 @@ void GpencilExporterSVG::export_gpencil_layers() /* Sample stroke. */ if (params_.stroke_sample > 0.0f) { - BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false); + BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false, 0); } export_stroke_to_path(gpl, gps_perimeter, node_gpl, false); diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index 64099de5a5e..e30dd4e1c8f 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -649,9 +649,10 @@ typedef struct SimplifyGpencilModifierData { int layer_pass; /** Sample length */ float length; + /** Sample sharp threshold */ + float sharp_threshold; /** Merge distance */ float distance; - char _pad[4]; } SimplifyGpencilModifierData; typedef enum eSimplifyGpencil_Flag { diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 9c755bbb053..a619d179a33 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -1223,6 +1223,14 @@ static void rna_def_modifier_gpencilsimplify(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Length", "Length of each segment"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "sharp_threshold", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "sharp_threshold"); + RNA_def_property_range(prop, 0, M_PI); + RNA_def_property_ui_range(prop, 0, M_PI, 1.0, 1); + RNA_def_property_ui_text( + prop, "Sharp Threshold", "Preserve corners that have sharper angle than this threshold"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + /* Merge */ prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "distance"); -- cgit v1.2.3 From c5b66560de75a54b5c6920fb675c0d804caeb724 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 16:36:59 +1100 Subject: Fix T93629: Reset to defaults undoes all steps when applied twice Reset Defaults left the undo stack in an invalid state, with the active undo step left at the previous state then it should have been. Now the buttons own undo logic is used to perform undo pushes. --- source/blender/editors/include/UI_interface.h | 2 +- .../blender/editors/interface/interface_handlers.c | 7 ++++++- source/blender/editors/interface/interface_ops.c | 21 ++++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 3796fa51499..2d3657f39e8 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2916,7 +2916,7 @@ uiBut *UI_context_active_but_prop_get(const struct bContext *C, struct PointerRNA *r_ptr, struct PropertyRNA **r_prop, int *r_index); -void UI_context_active_but_prop_handle(struct bContext *C); +void UI_context_active_but_prop_handle(struct bContext *C, bool handle_undo); void UI_context_active_but_clear(struct bContext *C, struct wmWindow *win, struct ARegion *region); struct wmOperator *UI_context_active_operator_get(const struct bContext *C); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 905fd452b6c..d947db463ee 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -8802,7 +8802,7 @@ uiBut *UI_context_active_but_prop_get(const bContext *C, return activebut; } -void UI_context_active_but_prop_handle(bContext *C) +void UI_context_active_but_prop_handle(bContext *C, const bool handle_undo) { uiBut *activebut = ui_context_rna_button_active(C); if (activebut) { @@ -8813,6 +8813,11 @@ void UI_context_active_but_prop_handle(bContext *C) if (block->handle_func) { block->handle_func(C, block->handle_func_arg, activebut->retval); } + if (handle_undo) { + /* Update the button so the undo text uses the correct value. */ + ui_but_update(activebut); + ui_apply_but_undo(activebut); + } } } diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 33c6e382f50..ed512f510e7 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -330,7 +330,7 @@ static int operator_button_property_finish(bContext *C, PointerRNA *ptr, Propert RNA_property_update(C, ptr, prop); /* as if we pressed the button */ - UI_context_active_but_prop_handle(C); + UI_context_active_but_prop_handle(C, false); /* Since we don't want to undo _all_ edits to settings, eg window * edits on the screen or on operator settings. @@ -342,6 +342,19 @@ static int operator_button_property_finish(bContext *C, PointerRNA *ptr, Propert return OPERATOR_CANCELLED; } +static int operator_button_property_finish_with_undo(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop) +{ + /* Perform updates required for this property. */ + RNA_property_update(C, ptr, prop); + + /* As if we pressed the button. */ + UI_context_active_but_prop_handle(C, true); + + return OPERATOR_FINISHED; +} + static bool reset_default_button_poll(bContext *C) { PointerRNA ptr; @@ -366,7 +379,7 @@ static int reset_default_button_exec(bContext *C, wmOperator *op) /* if there is a valid property that is editable... */ if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { - return operator_button_property_finish(C, &ptr, prop); + return operator_button_property_finish_with_undo(C, &ptr, prop); } } @@ -385,7 +398,9 @@ static void UI_OT_reset_default_button(wmOperatorType *ot) ot->exec = reset_default_button_exec; /* flags */ - ot->flag = OPTYPE_UNDO; + /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo + * is responsible for the undo push. */ + ot->flag = 0; /* properties */ RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); -- cgit v1.2.3 From 27427f3447539a006aee264e47c28dbc31a0dbf2 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 09:02:52 +0100 Subject: Cleanup: Use switch-statement for eTexPaintMode. --- source/blender/editors/sculpt_paint/paint_image.c | 123 ++++++++++++---------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index 4b8c7ae7808..498ba863b92 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -547,12 +547,15 @@ static void paint_stroke_update_step(bContext *C, ED_image_undo_restore(ustack->step_init); } - if (pop->mode == PAINT_MODE_3D_PROJECT) { - paint_proj_stroke( - C, pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); - } - else { - paint_2d_stroke(pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); + switch (pop->mode) { + case PAINT_MODE_2D: + paint_2d_stroke(pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); + break; + + case PAINT_MODE_3D_PROJECT: + paint_proj_stroke( + C, pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); + break; } copy_v2_v2(pop->prevmouse, mouse); @@ -565,11 +568,14 @@ static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, b { PaintOperation *pop = paint_stroke_mode_data(stroke); - if (pop->mode == PAINT_MODE_3D_PROJECT) { - paint_proj_redraw(C, pop->custom_paint, final); - } - else { - paint_2d_redraw(C, pop->custom_paint, final); + switch (pop->mode) { + case PAINT_MODE_2D: + paint_2d_redraw(C, pop->custom_paint, final); + break; + + case PAINT_MODE_3D_PROJECT: + paint_proj_redraw(C, pop->custom_paint, final); + break; } } @@ -584,54 +590,65 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) if (brush->imagepaint_tool == PAINT_TOOL_FILL) { if (brush->flag & BRUSH_USE_GRADIENT) { - if (pop->mode == PAINT_MODE_2D) { - paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); - } - else { - paint_proj_stroke(C, - pop->custom_paint, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->custom_paint, false); - paint_proj_redraw(C, pop->custom_paint, true); + switch (pop->mode) { + case PAINT_MODE_2D: + paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); + break; + + case PAINT_MODE_3D_PROJECT: + paint_proj_stroke(C, + pop->custom_paint, + pop->startmouse, + pop->prevmouse, + paint_stroke_flipped(stroke), + 1.0, + 0.0, + BKE_brush_size_get(scene, brush)); + /* two redraws, one for GPU update, one for notification */ + paint_proj_redraw(C, pop->custom_paint, false); + paint_proj_redraw(C, pop->custom_paint, true); + break; } } else { - if (pop->mode == PAINT_MODE_2D) { - float color[3]; - if (paint_stroke_inverted(stroke)) { - srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); - } - else { - srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); - } - paint_2d_bucket_fill(C, color, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); - } - else { - paint_proj_stroke(C, - pop->custom_paint, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->custom_paint, false); - paint_proj_redraw(C, pop->custom_paint, true); + switch (pop->mode) { + case PAINT_MODE_2D: + float color[3]; + if (paint_stroke_inverted(stroke)) { + srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); + } + else { + srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); + } + paint_2d_bucket_fill( + C, color, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); + break; + + case PAINT_MODE_3D_PROJECT: + paint_proj_stroke(C, + pop->custom_paint, + pop->startmouse, + pop->prevmouse, + paint_stroke_flipped(stroke), + 1.0, + 0.0, + BKE_brush_size_get(scene, brush)); + /* two redraws, one for GPU update, one for notification */ + paint_proj_redraw(C, pop->custom_paint, false); + paint_proj_redraw(C, pop->custom_paint, true); + break; } } } - if (pop->mode == PAINT_MODE_3D_PROJECT) { - paint_proj_stroke_done(pop->custom_paint); - } - else { - paint_2d_stroke_done(pop->custom_paint); + + switch (pop->mode) { + case PAINT_MODE_2D: + paint_2d_stroke_done(pop->custom_paint); + break; + + case PAINT_MODE_3D_PROJECT: + paint_proj_stroke_done(pop->custom_paint); + break; } if (pop->cursor) { -- cgit v1.2.3 From 765735844711294a2354521f41464429bed1db7c Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 18:45:14 +1100 Subject: Cleanup: use doxygen sections --- .../editors/sculpt_paint/curves_sculpt_ops.cc | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index db81adbbbcd..04a14712d03 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -14,6 +14,10 @@ #include "curves_sculpt_intern.h" #include "paint_intern.h" +/* -------------------------------------------------------------------- */ +/** \name Poll Functions + * \{ */ + bool CURVES_SCULPT_mode_poll(bContext *C) { Object *ob = CTX_data_active_object(C); @@ -33,9 +37,11 @@ bool CURVES_SCULPT_mode_poll_view3d(bContext *C) namespace blender::ed::sculpt_paint { -/* -------------------------------------------------------------------- - * SCULPT_CURVES_OT_brush_stroke. - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name * SCULPT_CURVES_OT_brush_stroke + * \{ */ static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) { @@ -106,9 +112,11 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) paint_stroke_operator_properties(ot); } -/* -------------------------------------------------------------------- - * CURVES_OT_sculptmode_toggle. - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name * CURVES_OT_sculptmode_toggle + * \{ */ static bool curves_sculptmode_toggle_poll(bContext *C) { @@ -177,9 +185,11 @@ static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) } // namespace blender::ed::sculpt_paint -/* -------------------------------------------------------------------- - * Registration. - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name * Registration + * \{ */ void ED_operatortypes_sculpt_curves() { @@ -187,3 +197,5 @@ void ED_operatortypes_sculpt_curves() WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); WM_operatortype_append(CURVES_OT_sculptmode_toggle); } + +/** \} */ -- cgit v1.2.3 From 0ad4d2694bae7f4db551138ec79a46f8f7e57bba Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 19:57:36 +1100 Subject: Python: change behavior for CONSOLE_OT_indent_or_autocomplete Checking only the previous character broke import auto-completion. --- source/blender/editors/space_console/console_ops.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/space_console/console_ops.c b/source/blender/editors/space_console/console_ops.c index c6fb2560dc0..0201b11f0bb 100644 --- a/source/blender/editors/space_console/console_ops.c +++ b/source/blender/editors/space_console/console_ops.c @@ -470,7 +470,18 @@ void CONSOLE_OT_insert(wmOperatorType *ot) static int console_indent_or_autocomplete_exec(bContext *C, wmOperator *UNUSED(op)) { ConsoleLine *ci = console_history_verify(C); - bool text_before_cursor = ci->cursor != 0 && !ELEM(ci->line[ci->cursor - 1], ' ', '\t'); + bool text_before_cursor = false; + + /* Check any text before cursor (not just the previous character) as is done for + * #TEXT_OT_indent_or_autocomplete because Python auto-complete operates on import + * statements such as completing possible sub-modules: `from bpy import `. */ + for (int i = 0; i < ci->cursor; i += BLI_str_utf8_size_safe(&ci->line[i])) { + if (!ELEM(ci->line[i], ' ', '\t')) { + text_before_cursor = true; + break; + } + } + if (text_before_cursor) { WM_operator_name_call(C, "CONSOLE_OT_autocomplete", WM_OP_INVOKE_DEFAULT, NULL); } -- cgit v1.2.3 From 1ec02b869587cf31414f8c2146614bc65a21725b Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 09:13:47 +0100 Subject: Cleanup: Renamed PaintOperation.custom_paint to stroke_handle. --- source/blender/editors/sculpt_paint/paint_image.c | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index 498ba863b92..2df42e6f532 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -315,7 +315,7 @@ typedef enum eTexPaintMode { typedef struct PaintOperation { eTexPaintMode mode; - void *custom_paint; + void *stroke_handle; float prevmouse[2]; float startmouse[2]; @@ -479,14 +479,14 @@ static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const flo return NULL; } pop->mode = PAINT_MODE_3D_PROJECT; - pop->custom_paint = paint_proj_new_stroke(C, ob, mouse, mode); + pop->stroke_handle = paint_proj_new_stroke(C, ob, mouse, mode); } else { pop->mode = PAINT_MODE_2D; - pop->custom_paint = paint_2d_new_stroke(C, op, mode); + pop->stroke_handle = paint_2d_new_stroke(C, op, mode); } - if (!pop->custom_paint) { + if (!pop->stroke_handle) { MEM_freeN(pop); return NULL; } @@ -549,12 +549,12 @@ static void paint_stroke_update_step(bContext *C, switch (pop->mode) { case PAINT_MODE_2D: - paint_2d_stroke(pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); + paint_2d_stroke(pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); break; case PAINT_MODE_3D_PROJECT: paint_proj_stroke( - C, pop->custom_paint, pop->prevmouse, mouse, eraser, pressure, distance, size); + C, pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); break; } @@ -570,11 +570,11 @@ static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, b switch (pop->mode) { case PAINT_MODE_2D: - paint_2d_redraw(C, pop->custom_paint, final); + paint_2d_redraw(C, pop->stroke_handle, final); break; case PAINT_MODE_3D_PROJECT: - paint_proj_redraw(C, pop->custom_paint, final); + paint_proj_redraw(C, pop->stroke_handle, final); break; } } @@ -592,12 +592,12 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) if (brush->flag & BRUSH_USE_GRADIENT) { switch (pop->mode) { case PAINT_MODE_2D: - paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); + paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); break; case PAINT_MODE_3D_PROJECT: paint_proj_stroke(C, - pop->custom_paint, + pop->stroke_handle, pop->startmouse, pop->prevmouse, paint_stroke_flipped(stroke), @@ -605,8 +605,8 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) 0.0, BKE_brush_size_get(scene, brush)); /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->custom_paint, false); - paint_proj_redraw(C, pop->custom_paint, true); + paint_proj_redraw(C, pop->stroke_handle, false); + paint_proj_redraw(C, pop->stroke_handle, true); break; } } @@ -621,12 +621,12 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); } paint_2d_bucket_fill( - C, color, brush, pop->startmouse, pop->prevmouse, pop->custom_paint); + C, color, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); break; case PAINT_MODE_3D_PROJECT: paint_proj_stroke(C, - pop->custom_paint, + pop->stroke_handle, pop->startmouse, pop->prevmouse, paint_stroke_flipped(stroke), @@ -634,8 +634,8 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) 0.0, BKE_brush_size_get(scene, brush)); /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->custom_paint, false); - paint_proj_redraw(C, pop->custom_paint, true); + paint_proj_redraw(C, pop->stroke_handle, false); + paint_proj_redraw(C, pop->stroke_handle, true); break; } } @@ -643,11 +643,11 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) switch (pop->mode) { case PAINT_MODE_2D: - paint_2d_stroke_done(pop->custom_paint); + paint_2d_stroke_done(pop->stroke_handle); break; case PAINT_MODE_3D_PROJECT: - paint_proj_stroke_done(pop->custom_paint); + paint_proj_stroke_done(pop->stroke_handle); break; } -- cgit v1.2.3 From 1d4ed6ba2964eb6ec9bf0e0648f8bb60ab7a914a Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 09:57:11 +0100 Subject: Convert paint_image to cc. --- source/blender/editors/sculpt_paint/CMakeLists.txt | 2 +- source/blender/editors/sculpt_paint/paint_image.c | 1381 ------------------- source/blender/editors/sculpt_paint/paint_image.cc | 1386 ++++++++++++++++++++ 3 files changed, 1387 insertions(+), 1382 deletions(-) delete mode 100644 source/blender/editors/sculpt_paint/paint_image.c create mode 100644 source/blender/editors/sculpt_paint/paint_image.cc diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index fcde780fc58..c1febe5c86b 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -29,7 +29,7 @@ set(SRC paint_curve.c paint_curve_undo.c paint_hide.c - paint_image.c + paint_image.cc paint_image_2d.c paint_image_2d_curve_mask.cc paint_image_proj.c diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c deleted file mode 100644 index 2df42e6f532..00000000000 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ /dev/null @@ -1,1381 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ - -/** \file - * \ingroup edsculpt - * \brief Functions to paint images in 2D and 3D. - */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "DNA_brush_types.h" -#include "DNA_material_types.h" -#include "DNA_mesh_types.h" -#include "DNA_node_types.h" -#include "DNA_object_types.h" - -#include "BKE_brush.h" -#include "BKE_colorband.h" -#include "BKE_context.h" -#include "BKE_image.h" -#include "BKE_main.h" -#include "BKE_material.h" -#include "BKE_mesh.h" -#include "BKE_node.h" -#include "BKE_paint.h" -#include "BKE_undo_system.h" - -#include "NOD_texture.h" - -#include "DEG_depsgraph.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "ED_image.h" -#include "ED_object.h" -#include "ED_paint.h" -#include "ED_screen.h" -#include "ED_view3d.h" - -#include "WM_api.h" -#include "WM_message.h" -#include "WM_toolsystem.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "GPU_immediate.h" -#include "GPU_state.h" - -#include "IMB_colormanagement.h" - -#include "paint_intern.h" - -/** - * This is a static resource for non-global access. - * Maybe it should be exposed as part of the paint operation, - * but for now just give a public interface. - */ -static ImagePaintPartialRedraw imapaintpartial = {{0}}; - -ImagePaintPartialRedraw *get_imapaintpartial(void) -{ - return &imapaintpartial; -} - -void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr) -{ - imapaintpartial = *ippr; -} - -/* Imagepaint Partial Redraw & Dirty Region */ - -void ED_imapaint_clear_partial_redraw(void) -{ - BLI_rcti_init_minmax(&imapaintpartial.dirty_region); -} - -void imapaint_region_tiles( - ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) -{ - int srcx = 0, srcy = 0; - - IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h); - - *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); - *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); - *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); - *ty = (y >> ED_IMAGE_UNDO_TILE_BITS); -} - -void ED_imapaint_dirty_region( - Image *ima, ImBuf *ibuf, ImageUser *iuser, int x, int y, int w, int h, bool find_old) -{ - ImBuf *tmpibuf = NULL; - int tilex, tiley, tilew, tileh, tx, ty; - int srcx = 0, srcy = 0; - - IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h); - - if (w == 0 || h == 0) { - return; - } - - rcti rect_to_merge; - BLI_rcti_init(&rect_to_merge, x, x + w, y, y + h); - BLI_rcti_do_minmax_rcti(&imapaintpartial.dirty_region, &rect_to_merge); - - imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); - - ListBase *undo_tiles = ED_image_paint_tile_list_get(); - - for (ty = tiley; ty <= tileh; ty++) { - for (tx = tilex; tx <= tilew; tx++) { - ED_image_paint_tile_push( - undo_tiles, ima, ibuf, &tmpibuf, iuser, tx, ty, NULL, NULL, false, find_old); - } - } - - BKE_image_mark_dirty(ima, ibuf); - - if (tmpibuf) { - IMB_freeImBuf(tmpibuf); - } -} - -void imapaint_image_update( - SpaceImage *sima, Image *image, ImBuf *ibuf, ImageUser *iuser, short texpaint) -{ - if (BLI_rcti_is_empty(&imapaintpartial.dirty_region)) { - return; - } - - if (ibuf->mipmap[0]) { - ibuf->userflags |= IB_MIPMAP_INVALID; - } - - IMB_partial_display_buffer_update_delayed(ibuf, - imapaintpartial.dirty_region.xmin, - imapaintpartial.dirty_region.ymin, - imapaintpartial.dirty_region.xmax, - imapaintpartial.dirty_region.ymax); - - /* TODO: should set_tpage create ->rect? */ - if (texpaint || (sima && sima->lock)) { - const int w = BLI_rcti_size_x(&imapaintpartial.dirty_region); - const int h = BLI_rcti_size_y(&imapaintpartial.dirty_region); - /* Testing with partial update in uv editor too */ - BKE_image_update_gputexture( - image, iuser, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, w, h); - } -} - -BlurKernel *paint_new_blur_kernel(Brush *br, bool proj) -{ - int i, j; - BlurKernel *kernel = MEM_mallocN(sizeof(BlurKernel), "blur kernel"); - float radius; - int side; - eBlurKernelType type = br->blur_mode; - - if (proj) { - radius = 0.5f; - - side = kernel->side = 2; - kernel->side_squared = kernel->side * kernel->side; - kernel->wdata = MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data"); - kernel->pixel_len = radius; - } - else { - if (br->blur_kernel_radius <= 0) { - br->blur_kernel_radius = 1; - } - - radius = br->blur_kernel_radius; - - side = kernel->side = radius * 2 + 1; - kernel->side_squared = kernel->side * kernel->side; - kernel->wdata = MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data"); - kernel->pixel_len = br->blur_kernel_radius; - } - - switch (type) { - case KERNEL_BOX: - for (i = 0; i < kernel->side_squared; i++) { - kernel->wdata[i] = 1.0; - } - break; - - case KERNEL_GAUSSIAN: { - /* at 3.0 standard deviations distance, kernel is about zero */ - float standard_dev = radius / 3.0f; - - /* make the necessary adjustment to the value for use in the normal distribution formula */ - standard_dev = -standard_dev * standard_dev * 2; - - for (i = 0; i < side; i++) { - for (j = 0; j < side; j++) { - float idist = radius - i; - float jdist = radius - j; - float value = exp((idist * idist + jdist * jdist) / standard_dev); - - kernel->wdata[i + j * side] = value; - } - } - - break; - } - - default: - printf("unidentified kernel type, aborting\n"); - MEM_freeN(kernel->wdata); - MEM_freeN(kernel); - return NULL; - } - - return kernel; -} - -void paint_delete_blur_kernel(BlurKernel *kernel) -{ - if (kernel->wdata) { - MEM_freeN(kernel->wdata); - } -} - -/************************ image paint poll ************************/ - -static Brush *image_paint_brush(bContext *C) -{ - Scene *scene = CTX_data_scene(C); - ToolSettings *settings = scene->toolsettings; - - return BKE_paint_brush(&settings->imapaint.paint); -} - -static bool image_paint_poll_ex(bContext *C, bool check_tool) -{ - Object *obact; - - if (!image_paint_brush(C)) { - return false; - } - - obact = CTX_data_active_object(C); - if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) { - if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) { - return true; - } - } - else { - SpaceImage *sima = CTX_wm_space_image(C); - - if (sima) { - if (sima->image != NULL && ID_IS_LINKED(sima->image)) { - return false; - } - ARegion *region = CTX_wm_region(C); - - if ((sima->mode == SI_MODE_PAINT) && region->regiontype == RGN_TYPE_WINDOW) { - return true; - } - } - } - - return false; -} - -static bool image_paint_poll(bContext *C) -{ - return image_paint_poll_ex(C, true); -} - -static bool image_paint_poll_ignore_tool(bContext *C) -{ - return image_paint_poll_ex(C, false); -} - -static bool image_paint_2d_clone_poll(bContext *C) -{ - Brush *brush = image_paint_brush(C); - - if (!CTX_wm_region_view3d(C) && image_paint_poll(C)) { - if (brush && (brush->imagepaint_tool == PAINT_TOOL_CLONE)) { - if (brush->clone.image) { - return true; - } - } - } - - return false; -} - -/************************ paint operator ************************/ -typedef enum eTexPaintMode { - PAINT_MODE_2D, - PAINT_MODE_3D_PROJECT, -} eTexPaintMode; - -typedef struct PaintOperation { - eTexPaintMode mode; - - void *stroke_handle; - - float prevmouse[2]; - float startmouse[2]; - double starttime; - - void *cursor; - ViewContext vc; -} PaintOperation; - -bool paint_use_opacity_masking(Brush *brush) -{ - return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || - (brush->flag & BRUSH_ANCHORED) || - (ELEM(brush->imagepaint_tool, PAINT_TOOL_SMEAR, PAINT_TOOL_SOFTEN)) || - (brush->imagepaint_tool == PAINT_TOOL_FILL) || - (brush->flag & BRUSH_USE_GRADIENT) || - (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, - MTEX_MAP_MODE_TILED, - MTEX_MAP_MODE_STENCIL, - MTEX_MAP_MODE_3D)) ? - false : - true); -} - -void paint_brush_color_get(struct Scene *scene, - struct Brush *br, - bool color_correction, - bool invert, - float distance, - float pressure, - float color[3], - struct ColorManagedDisplay *display) -{ - if (invert) { - copy_v3_v3(color, BKE_brush_secondary_color_get(scene, br)); - } - else { - if (br->flag & BRUSH_USE_GRADIENT) { - float color_gr[4]; - switch (br->gradient_stroke_mode) { - case BRUSH_GRADIENT_PRESSURE: - BKE_colorband_evaluate(br->gradient, pressure, color_gr); - break; - case BRUSH_GRADIENT_SPACING_REPEAT: { - float coord = fmod(distance / br->gradient_spacing, 1.0); - BKE_colorband_evaluate(br->gradient, coord, color_gr); - break; - } - case BRUSH_GRADIENT_SPACING_CLAMP: { - BKE_colorband_evaluate(br->gradient, distance / br->gradient_spacing, color_gr); - break; - } - } - /* Gradient / Colorband colors are not considered PROP_COLOR_GAMMA. - * Brush colors are expected to be in sRGB though. */ - IMB_colormanagement_scene_linear_to_srgb_v3(color_gr); - - copy_v3_v3(color, color_gr); - } - else { - copy_v3_v3(color, BKE_brush_color_get(scene, br)); - } - } - if (color_correction) { - IMB_colormanagement_display_to_scene_linear_v3(color, display); - } -} - -void paint_brush_init_tex(Brush *brush) -{ - /* init mtex nodes */ - if (brush) { - MTex *mtex = &brush->mtex; - if (mtex->tex && mtex->tex->nodetree) { - /* has internal flag to detect it only does it once */ - ntreeTexBeginExecTree(mtex->tex->nodetree); - } - mtex = &brush->mask_mtex; - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexBeginExecTree(mtex->tex->nodetree); - } - } -} - -void paint_brush_exit_tex(Brush *brush) -{ - if (brush) { - MTex *mtex = &brush->mtex; - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexEndExecTree(mtex->tex->nodetree->execdata); - } - mtex = &brush->mask_mtex; - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexEndExecTree(mtex->tex->nodetree->execdata); - } - } -} - -static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customdata) -{ - PaintOperation *pop = (PaintOperation *)customdata; - - if (pop) { - GPU_line_smooth(true); - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - - ARegion *region = pop->vc.region; - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - GPU_line_width(4.0); - immUniformColor4ub(0, 0, 0, 255); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2i(pos, x, y); - immVertex2i( - pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); - immEnd(); - - GPU_line_width(2.0); - immUniformColor4ub(255, 255, 255, 255); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2i(pos, x, y); - immVertex2i( - pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); - immEnd(); - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); - GPU_line_smooth(false); - } -} - -static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const float mouse[2]) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *settings = scene->toolsettings; - PaintOperation *pop = MEM_callocN(sizeof(PaintOperation), "PaintOperation"); /* caller frees */ - Brush *brush = BKE_paint_brush(&settings->imapaint.paint); - int mode = RNA_enum_get(op->ptr, "mode"); - ED_view3d_viewcontext_init(C, &pop->vc, depsgraph); - - copy_v2_v2(pop->prevmouse, mouse); - copy_v2_v2(pop->startmouse, mouse); - - /* initialize from context */ - if (CTX_wm_region_view3d(C)) { - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); - bool uvs, mat, tex, stencil; - if (!ED_paint_proj_mesh_data_check(scene, ob, &uvs, &mat, &tex, &stencil)) { - ED_paint_data_warning(op->reports, uvs, mat, tex, stencil); - MEM_freeN(pop); - WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL); - return NULL; - } - pop->mode = PAINT_MODE_3D_PROJECT; - pop->stroke_handle = paint_proj_new_stroke(C, ob, mouse, mode); - } - else { - pop->mode = PAINT_MODE_2D; - pop->stroke_handle = paint_2d_new_stroke(C, op, mode); - } - - if (!pop->stroke_handle) { - MEM_freeN(pop); - return NULL; - } - - if ((brush->imagepaint_tool == PAINT_TOOL_FILL) && (brush->flag & BRUSH_USE_GRADIENT)) { - pop->cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, image_paint_poll, gradient_draw_line, pop); - } - - settings->imapaint.flag |= IMAGEPAINT_DRAWING; - ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); - - return pop; -} - -static void paint_stroke_update_step(bContext *C, - wmOperator *UNUSED(op), - struct PaintStroke *stroke, - PointerRNA *itemptr) -{ - PaintOperation *pop = paint_stroke_mode_data(stroke); - Scene *scene = CTX_data_scene(C); - ToolSettings *toolsettings = CTX_data_tool_settings(C); - UnifiedPaintSettings *ups = &toolsettings->unified_paint_settings; - Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); - - float alphafac = (brush->flag & BRUSH_ACCUMULATE) ? ups->overlap_factor : 1.0f; - - /* initial brush values. Maybe it should be considered moving these to stroke system */ - float startalpha = BKE_brush_alpha_get(scene, brush); - - float mouse[2]; - float pressure; - float size; - float distance = paint_stroke_distance_get(stroke); - int eraser; - - RNA_float_get_array(itemptr, "mouse", mouse); - pressure = RNA_float_get(itemptr, "pressure"); - eraser = RNA_boolean_get(itemptr, "pen_flip"); - size = RNA_float_get(itemptr, "size"); - - /* stroking with fill tool only acts on stroke end */ - if (brush->imagepaint_tool == PAINT_TOOL_FILL) { - copy_v2_v2(pop->prevmouse, mouse); - return; - } - - if (BKE_brush_use_alpha_pressure(brush)) { - BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * pressure * alphafac)); - } - else { - BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * alphafac)); - } - - if ((brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED)) { - UndoStack *ustack = CTX_wm_manager(C)->undo_stack; - ED_image_undo_restore(ustack->step_init); - } - - switch (pop->mode) { - case PAINT_MODE_2D: - paint_2d_stroke(pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); - break; - - case PAINT_MODE_3D_PROJECT: - paint_proj_stroke( - C, pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); - break; - } - - copy_v2_v2(pop->prevmouse, mouse); - - /* restore brush values */ - BKE_brush_alpha_set(scene, brush, startalpha); -} - -static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, bool final) -{ - PaintOperation *pop = paint_stroke_mode_data(stroke); - - switch (pop->mode) { - case PAINT_MODE_2D: - paint_2d_redraw(C, pop->stroke_handle, final); - break; - - case PAINT_MODE_3D_PROJECT: - paint_proj_redraw(C, pop->stroke_handle, final); - break; - } -} - -static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) -{ - Scene *scene = CTX_data_scene(C); - ToolSettings *toolsettings = scene->toolsettings; - PaintOperation *pop = paint_stroke_mode_data(stroke); - Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); - - toolsettings->imapaint.flag &= ~IMAGEPAINT_DRAWING; - - if (brush->imagepaint_tool == PAINT_TOOL_FILL) { - if (brush->flag & BRUSH_USE_GRADIENT) { - switch (pop->mode) { - case PAINT_MODE_2D: - paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); - break; - - case PAINT_MODE_3D_PROJECT: - paint_proj_stroke(C, - pop->stroke_handle, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->stroke_handle, false); - paint_proj_redraw(C, pop->stroke_handle, true); - break; - } - } - else { - switch (pop->mode) { - case PAINT_MODE_2D: - float color[3]; - if (paint_stroke_inverted(stroke)) { - srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); - } - else { - srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); - } - paint_2d_bucket_fill( - C, color, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); - break; - - case PAINT_MODE_3D_PROJECT: - paint_proj_stroke(C, - pop->stroke_handle, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->stroke_handle, false); - paint_proj_redraw(C, pop->stroke_handle, true); - break; - } - } - } - - switch (pop->mode) { - case PAINT_MODE_2D: - paint_2d_stroke_done(pop->stroke_handle); - break; - - case PAINT_MODE_3D_PROJECT: - paint_proj_stroke_done(pop->stroke_handle); - break; - } - - if (pop->cursor) { - WM_paint_cursor_end(pop->cursor); - } - - ED_image_undo_push_end(); - - /* duplicate warning, see texpaint_init */ -#if 0 - if (pop->s.warnmultifile) { - BKE_reportf(op->reports, - RPT_WARNING, - "Image requires 4 color channels to paint: %s", - pop->s.warnmultifile); - } - if (pop->s.warnpackedfile) { - BKE_reportf(op->reports, - RPT_WARNING, - "Packed MultiLayer files cannot be painted: %s", - pop->s.warnpackedfile); - } -#endif - MEM_freeN(pop); -} - -static bool paint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) -{ - PaintOperation *pop; - - /* TODO: Should avoid putting this here. Instead, last position should be requested - * from stroke system. */ - - if (!(pop = texture_paint_init(C, op, mouse))) { - return false; - } - - paint_stroke_set_mode_data(op->customdata, pop); - - return true; -} - -static int paint_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - int retval; - - op->customdata = paint_stroke_new(C, - op, - NULL, - paint_stroke_test_start, - paint_stroke_update_step, - paint_stroke_redraw, - paint_stroke_done, - event->type); - - if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op, op->customdata); - return OPERATOR_FINISHED; - } - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - OPERATOR_RETVAL_CHECK(retval); - BLI_assert(retval == OPERATOR_RUNNING_MODAL); - - return OPERATOR_RUNNING_MODAL; -} - -static int paint_exec(bContext *C, wmOperator *op) -{ - PropertyRNA *strokeprop; - PointerRNA firstpoint; - float mouse[2]; - - strokeprop = RNA_struct_find_property(op->ptr, "stroke"); - - if (!RNA_property_collection_lookup_int(op->ptr, strokeprop, 0, &firstpoint)) { - return OPERATOR_CANCELLED; - } - - RNA_float_get_array(&firstpoint, "mouse", mouse); - - op->customdata = paint_stroke_new(C, - op, - NULL, - paint_stroke_test_start, - paint_stroke_update_step, - paint_stroke_redraw, - paint_stroke_done, - 0); - /* frees op->customdata */ - return paint_stroke_exec(C, op, op->customdata); -} - -static int paint_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - return paint_stroke_modal(C, op, event, op->customdata); -} - -static void paint_cancel(bContext *C, wmOperator *op) -{ - paint_stroke_cancel(C, op, op->customdata); -} - -void PAINT_OT_image_paint(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Image Paint"; - ot->idname = "PAINT_OT_image_paint"; - ot->description = "Paint a stroke into the image"; - - /* api callbacks */ - ot->invoke = paint_invoke; - ot->modal = paint_modal; - ot->exec = paint_exec; - ot->poll = image_paint_poll; - ot->cancel = paint_cancel; - - /* flags */ - ot->flag = OPTYPE_BLOCKING; - - paint_stroke_operator_properties(ot); -} - -bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy) -{ - ScrArea *area = CTX_wm_area(C); - if (area && area->spacetype == SPACE_IMAGE) { - SpaceImage *sima = area->spacedata.first; - if (sima->mode == SI_MODE_PAINT) { - ARegion *region = CTX_wm_region(C); - ED_space_image_get_zoom(sima, region, zoomx, zoomy); - return true; - } - } - - *zoomx = *zoomy = 1; - - return false; -} - -/************************ cursor drawing *******************************/ - -static void toggle_paint_cursor(Scene *scene, bool enable) -{ - ToolSettings *settings = scene->toolsettings; - Paint *p = &settings->imapaint.paint; - - if (p->paint_cursor && !enable) { - WM_paint_cursor_end(p->paint_cursor); - p->paint_cursor = NULL; - paint_cursor_delete_textures(); - } - else if (enable) { - paint_cursor_start(p, image_paint_poll); - } -} - -void ED_space_image_paint_update(Main *bmain, wmWindowManager *wm, Scene *scene) -{ - ToolSettings *settings = scene->toolsettings; - ImagePaintSettings *imapaint = &settings->imapaint; - bool enabled = false; - - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - bScreen *screen = WM_window_get_active_screen(win); - - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - if (area->spacetype == SPACE_IMAGE) { - if (((SpaceImage *)area->spacedata.first)->mode == SI_MODE_PAINT) { - enabled = true; - } - } - } - } - - if (enabled) { - BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_2D, PAINT_CURSOR_TEXTURE_PAINT); - - paint_cursor_start(&imapaint->paint, image_paint_poll); - } - else { - paint_cursor_delete_textures(); - } -} - -/************************ grab clone operator ************************/ - -typedef struct GrabClone { - float startoffset[2]; - int startx, starty; -} GrabClone; - -static void grab_clone_apply(bContext *C, wmOperator *op) -{ - Brush *brush = image_paint_brush(C); - float delta[2]; - - RNA_float_get_array(op->ptr, "delta", delta); - add_v2_v2(brush->clone.offset, delta); - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static int grab_clone_exec(bContext *C, wmOperator *op) -{ - grab_clone_apply(C, op); - - return OPERATOR_FINISHED; -} - -static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Brush *brush = image_paint_brush(C); - GrabClone *cmv; - - cmv = MEM_callocN(sizeof(GrabClone), "GrabClone"); - copy_v2_v2(cmv->startoffset, brush->clone.offset); - cmv->startx = event->xy[0]; - cmv->starty = event->xy[1]; - op->customdata = cmv; - - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; -} - -static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - Brush *brush = image_paint_brush(C); - ARegion *region = CTX_wm_region(C); - GrabClone *cmv = op->customdata; - float startfx, startfy, fx, fy, delta[2]; - int xmin = region->winrct.xmin, ymin = region->winrct.ymin; - - switch (event->type) { - case LEFTMOUSE: - case MIDDLEMOUSE: - case RIGHTMOUSE: /* XXX hardcoded */ - MEM_freeN(op->customdata); - return OPERATOR_FINISHED; - case MOUSEMOVE: - /* mouse moved, so move the clone image */ - UI_view2d_region_to_view( - ®ion->v2d, cmv->startx - xmin, cmv->starty - ymin, &startfx, &startfy); - UI_view2d_region_to_view(®ion->v2d, event->xy[0] - xmin, event->xy[1] - ymin, &fx, &fy); - - delta[0] = fx - startfx; - delta[1] = fy - startfy; - RNA_float_set_array(op->ptr, "delta", delta); - - copy_v2_v2(brush->clone.offset, cmv->startoffset); - - grab_clone_apply(C, op); - break; - } - - return OPERATOR_RUNNING_MODAL; -} - -static void grab_clone_cancel(bContext *UNUSED(C), wmOperator *op) -{ - MEM_freeN(op->customdata); -} - -void PAINT_OT_grab_clone(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Grab Clone"; - ot->idname = "PAINT_OT_grab_clone"; - ot->description = "Move the clone source image"; - - /* api callbacks */ - ot->exec = grab_clone_exec; - ot->invoke = grab_clone_invoke; - ot->modal = grab_clone_modal; - ot->cancel = grab_clone_cancel; - ot->poll = image_paint_2d_clone_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; - - /* properties */ - RNA_def_float_vector(ot->srna, - "delta", - 2, - NULL, - -FLT_MAX, - FLT_MAX, - "Delta", - "Delta offset of clone image in 0.0 to 1.0 coordinates", - -1.0f, - 1.0f); -} - -/******************** sample color operator ********************/ -typedef struct { - bool show_cursor; - short launch_event; - float initcolor[3]; - bool sample_palette; -} SampleColorData; - -static void sample_color_update_header(SampleColorData *data, bContext *C) -{ - char msg[UI_MAX_DRAW_STR]; - ScrArea *area = CTX_wm_area(C); - - if (area) { - BLI_snprintf(msg, - sizeof(msg), - TIP_("Sample color for %s"), - !data->sample_palette ? - TIP_("Brush. Use Left Click to sample for palette instead") : - TIP_("Palette. Use Left Click to sample more colors")); - ED_workspace_status_text(C, msg); - } -} - -static int sample_color_exec(bContext *C, wmOperator *op) -{ - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = BKE_paint_brush(paint); - ePaintMode mode = BKE_paintmode_get_active_from_context(C); - ARegion *region = CTX_wm_region(C); - wmWindow *win = CTX_wm_window(C); - const bool show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); - int location[2]; - paint->flags &= ~PAINT_SHOW_BRUSH; - - /* force redraw without cursor */ - WM_paint_cursor_tag_redraw(win, region); - WM_redraw_windows(C); - - RNA_int_get_array(op->ptr, "location", location); - const bool use_palette = RNA_boolean_get(op->ptr, "palette"); - const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && - !RNA_boolean_get(op->ptr, "merged"); - - paint_sample_color(C, region, location[0], location[1], use_sample_texture, use_palette); - - if (show_cursor) { - paint->flags |= PAINT_SHOW_BRUSH; - } - - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); - - return OPERATOR_FINISHED; -} - -static int sample_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Scene *scene = CTX_data_scene(C); - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = BKE_paint_brush(paint); - SampleColorData *data = MEM_mallocN(sizeof(SampleColorData), "sample color custom data"); - ARegion *region = CTX_wm_region(C); - wmWindow *win = CTX_wm_window(C); - - data->launch_event = WM_userdef_event_type_from_keymap_type(event->type); - data->show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); - copy_v3_v3(data->initcolor, BKE_brush_color_get(scene, brush)); - data->sample_palette = false; - op->customdata = data; - paint->flags &= ~PAINT_SHOW_BRUSH; - - sample_color_update_header(data, C); - - WM_event_add_modal_handler(C, op); - - /* force redraw without cursor */ - WM_paint_cursor_tag_redraw(win, region); - WM_redraw_windows(C); - - RNA_int_set_array(op->ptr, "location", event->mval); - - ePaintMode mode = BKE_paintmode_get_active_from_context(C); - const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && - !RNA_boolean_get(op->ptr, "merged"); - - paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); - - return OPERATOR_RUNNING_MODAL; -} - -static int sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - Scene *scene = CTX_data_scene(C); - SampleColorData *data = op->customdata; - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = BKE_paint_brush(paint); - - if ((event->type == data->launch_event) && (event->val == KM_RELEASE)) { - if (data->show_cursor) { - paint->flags |= PAINT_SHOW_BRUSH; - } - - if (data->sample_palette) { - BKE_brush_color_set(scene, brush, data->initcolor); - RNA_boolean_set(op->ptr, "palette", true); - } - WM_cursor_modal_restore(CTX_wm_window(C)); - MEM_freeN(data); - ED_workspace_status_text(C, NULL); - - return OPERATOR_FINISHED; - } - - ePaintMode mode = BKE_paintmode_get_active_from_context(C); - const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && - !RNA_boolean_get(op->ptr, "merged"); - - switch (event->type) { - case MOUSEMOVE: { - ARegion *region = CTX_wm_region(C); - RNA_int_set_array(op->ptr, "location", event->mval); - paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); - break; - } - - case LEFTMOUSE: - if (event->val == KM_PRESS) { - ARegion *region = CTX_wm_region(C); - RNA_int_set_array(op->ptr, "location", event->mval); - paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, true); - if (!data->sample_palette) { - data->sample_palette = true; - sample_color_update_header(data, C); - } - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); - } - break; - } - - return OPERATOR_RUNNING_MODAL; -} - -static bool sample_color_poll(bContext *C) -{ - return (image_paint_poll_ignore_tool(C) || vertex_paint_poll_ignore_tool(C)); -} - -void PAINT_OT_sample_color(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Sample Color"; - ot->idname = "PAINT_OT_sample_color"; - ot->description = "Use the mouse to sample a color in the image"; - - /* api callbacks */ - ot->exec = sample_color_exec; - ot->invoke = sample_color_invoke; - ot->modal = sample_color_modal; - ot->poll = sample_color_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - PropertyRNA *prop; - - prop = RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "", 0, 16384); - RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); - - RNA_def_boolean(ot->srna, "merged", 0, "Sample Merged", "Sample the output display color"); - RNA_def_boolean(ot->srna, "palette", 0, "Add to Palette", ""); -} - -/******************** texture paint toggle operator ********************/ - -void ED_object_texture_paint_mode_enter_ex(Main *bmain, Scene *scene, Object *ob) -{ - Image *ima = NULL; - ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; - - /* This has to stay here to regenerate the texture paint - * cache in case we are loading a file */ - BKE_texpaint_slots_refresh_object(scene, ob); - - ED_paint_proj_mesh_data_check(scene, ob, NULL, NULL, NULL, NULL); - - /* entering paint mode also sets image to editors */ - if (imapaint->mode == IMAGEPAINT_MODE_MATERIAL) { - /* set the current material active paint slot on image editor */ - Material *ma = BKE_object_material_get(ob, ob->actcol); - - if (ma && ma->texpaintslot) { - ima = ma->texpaintslot[ma->paint_active_slot].ima; - } - } - else if (imapaint->mode == IMAGEPAINT_MODE_IMAGE) { - ima = imapaint->canvas; - } - - if (ima) { - wmWindowManager *wm = bmain->wm.first; - for (wmWindow *win = wm->windows.first; win; win = win->next) { - const bScreen *screen = WM_window_get_active_screen(win); - for (ScrArea *area = screen->areabase.first; area; area = area->next) { - SpaceLink *sl = area->spacedata.first; - if (sl->spacetype == SPACE_IMAGE) { - SpaceImage *sima = (SpaceImage *)sl; - - if (!sima->pin) { - ED_space_image_set(bmain, sima, ima, true); - } - } - } - } - } - - ob->mode |= OB_MODE_TEXTURE_PAINT; - - BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_3D, PAINT_CURSOR_TEXTURE_PAINT); - - BKE_paint_toolslots_brush_validate(bmain, &imapaint->paint); - - if (U.glreslimit != 0) { - BKE_image_free_all_gputextures(bmain); - } - BKE_image_paint_set_mipmap(bmain, 0); - - toggle_paint_cursor(scene, true); - - Mesh *me = BKE_mesh_from_object(ob); - BLI_assert(me != NULL); - DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); - WM_main_add_notifier(NC_SCENE | ND_MODE, scene); -} - -void ED_object_texture_paint_mode_enter(bContext *C) -{ - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - Scene *scene = CTX_data_scene(C); - ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); -} - -void ED_object_texture_paint_mode_exit_ex(Main *bmain, Scene *scene, Object *ob) -{ - ob->mode &= ~OB_MODE_TEXTURE_PAINT; - - if (U.glreslimit != 0) { - BKE_image_free_all_gputextures(bmain); - } - BKE_image_paint_set_mipmap(bmain, 1); - toggle_paint_cursor(scene, false); - - Mesh *me = BKE_mesh_from_object(ob); - BLI_assert(me != NULL); - DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); - WM_main_add_notifier(NC_SCENE | ND_MODE, scene); -} - -void ED_object_texture_paint_mode_exit(bContext *C) -{ - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - Scene *scene = CTX_data_scene(C); - ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); -} - -static bool texture_paint_toggle_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == NULL || ob->type != OB_MESH) { - return false; - } - if (!ob->data || ID_IS_LINKED(ob->data)) { - return false; - } - - return true; -} - -static int texture_paint_toggle_exec(bContext *C, wmOperator *op) -{ - struct wmMsgBus *mbus = CTX_wm_message_bus(C); - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - const int mode_flag = OB_MODE_TEXTURE_PAINT; - const bool is_mode_set = (ob->mode & mode_flag) != 0; - - if (!is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { - return OPERATOR_CANCELLED; - } - } - - if (ob->mode & mode_flag) { - ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); - } - else { - ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); - } - - WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); - - WM_toolsystem_update_from_context_view3d(C); - - return OPERATOR_FINISHED; -} - -void PAINT_OT_texture_paint_toggle(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Texture Paint Toggle"; - ot->idname = "PAINT_OT_texture_paint_toggle"; - ot->description = "Toggle texture paint mode in 3D view"; - - /* api callbacks */ - ot->exec = texture_paint_toggle_exec; - ot->poll = texture_paint_toggle_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int brush_colors_flip_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Scene *scene = CTX_data_scene(C); - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *br = BKE_paint_brush(paint); - - if (ups->flag & UNIFIED_PAINT_COLOR) { - swap_v3_v3(ups->rgb, ups->secondary_rgb); - } - else if (br) { - swap_v3_v3(br->rgb, br->secondary_rgb); - } - else { - return OPERATOR_CANCELLED; - } - - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); - - return OPERATOR_FINISHED; -} - -static bool brush_colors_flip_poll(bContext *C) -{ - if (image_paint_poll(C)) { - Brush *br = image_paint_brush(C); - if (ELEM(br->imagepaint_tool, PAINT_TOOL_DRAW, PAINT_TOOL_FILL)) { - return true; - } - } - else { - Object *ob = CTX_data_active_object(C); - if (ob != NULL) { - if (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT | OB_MODE_SCULPT)) { - return true; - } - } - } - return false; -} - -void PAINT_OT_brush_colors_flip(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Swap Colors"; - ot->idname = "PAINT_OT_brush_colors_flip"; - ot->description = "Swap primary and secondary brush colors"; - - /* api callbacks */ - ot->exec = brush_colors_flip_exec; - ot->poll = brush_colors_flip_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -void ED_imapaint_bucket_fill(struct bContext *C, - float color[3], - wmOperator *op, - const int mouse[2]) -{ - SpaceImage *sima = CTX_wm_space_image(C); - - if (sima && sima->image) { - Image *ima = sima->image; - - ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); - - const float mouse_init[2] = {mouse[0], mouse[1]}; - paint_2d_bucket_fill(C, color, NULL, mouse_init, NULL, NULL); - - ED_image_undo_push_end(); - - DEG_id_tag_update(&ima->id, 0); - } -} - -static bool texture_paint_poll(bContext *C) -{ - if (texture_paint_toggle_poll(C)) { - if (CTX_data_active_object(C)->mode & OB_MODE_TEXTURE_PAINT) { - return true; - } - } - - return false; -} - -bool image_texture_paint_poll(bContext *C) -{ - return (texture_paint_poll(C) || image_paint_poll(C)); -} - -bool facemask_paint_poll(bContext *C) -{ - return BKE_paint_select_face_test(CTX_data_active_object(C)); -} - -bool vert_paint_poll(bContext *C) -{ - return BKE_paint_select_vert_test(CTX_data_active_object(C)); -} - -bool mask_paint_poll(bContext *C) -{ - return BKE_paint_select_elem_test(CTX_data_active_object(C)); -} diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc new file mode 100644 index 00000000000..dbc0398f807 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -0,0 +1,1386 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * \brief Functions to paint images in 2D and 3D. + */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "DNA_brush_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_colorband.h" +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_node.h" +#include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "NOD_texture.h" + +#include "DEG_depsgraph.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "ED_image.h" +#include "ED_object.h" +#include "ED_paint.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "GPU_immediate.h" +#include "GPU_state.h" + +#include "IMB_colormanagement.h" + +#include "paint_intern.h" + +/** + * This is a static resource for non-global access. + * Maybe it should be exposed as part of the paint operation, + * but for now just give a public interface. + */ +static ImagePaintPartialRedraw imapaintpartial = {{0}}; + +ImagePaintPartialRedraw *get_imapaintpartial(void) +{ + return &imapaintpartial; +} + +void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr) +{ + imapaintpartial = *ippr; +} + +/* Imagepaint Partial Redraw & Dirty Region */ + +void ED_imapaint_clear_partial_redraw(void) +{ + BLI_rcti_init_minmax(&imapaintpartial.dirty_region); +} + +void imapaint_region_tiles( + ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) +{ + int srcx = 0, srcy = 0; + + IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); + + *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); + *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); + *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); + *ty = (y >> ED_IMAGE_UNDO_TILE_BITS); +} + +void ED_imapaint_dirty_region( + Image *ima, ImBuf *ibuf, ImageUser *iuser, int x, int y, int w, int h, bool find_old) +{ + ImBuf *tmpibuf = nullptr; + int tilex, tiley, tilew, tileh, tx, ty; + int srcx = 0, srcy = 0; + + IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); + + if (w == 0 || h == 0) { + return; + } + + rcti rect_to_merge; + BLI_rcti_init(&rect_to_merge, x, x + w, y, y + h); + BLI_rcti_do_minmax_rcti(&imapaintpartial.dirty_region, &rect_to_merge); + + imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); + + ListBase *undo_tiles = ED_image_paint_tile_list_get(); + + for (ty = tiley; ty <= tileh; ty++) { + for (tx = tilex; tx <= tilew; tx++) { + ED_image_paint_tile_push( + undo_tiles, ima, ibuf, &tmpibuf, iuser, tx, ty, nullptr, nullptr, false, find_old); + } + } + + BKE_image_mark_dirty(ima, ibuf); + + if (tmpibuf) { + IMB_freeImBuf(tmpibuf); + } +} + +void imapaint_image_update( + SpaceImage *sima, Image *image, ImBuf *ibuf, ImageUser *iuser, short texpaint) +{ + if (BLI_rcti_is_empty(&imapaintpartial.dirty_region)) { + return; + } + + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; + } + + IMB_partial_display_buffer_update_delayed(ibuf, + imapaintpartial.dirty_region.xmin, + imapaintpartial.dirty_region.ymin, + imapaintpartial.dirty_region.xmax, + imapaintpartial.dirty_region.ymax); + + /* TODO: should set_tpage create ->rect? */ + if (texpaint || (sima && sima->lock)) { + const int w = BLI_rcti_size_x(&imapaintpartial.dirty_region); + const int h = BLI_rcti_size_y(&imapaintpartial.dirty_region); + /* Testing with partial update in uv editor too */ + BKE_image_update_gputexture( + image, iuser, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, w, h); + } +} + +BlurKernel *paint_new_blur_kernel(Brush *br, bool proj) +{ + int i, j; + BlurKernel *kernel = MEM_new("BlurKernel"); + + float radius; + int side; + eBlurKernelType type = static_cast(br->blur_mode); + + if (proj) { + radius = 0.5f; + + side = kernel->side = 2; + kernel->side_squared = kernel->side * kernel->side; + kernel->wdata = static_cast( + MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); + kernel->pixel_len = radius; + } + else { + if (br->blur_kernel_radius <= 0) { + br->blur_kernel_radius = 1; + } + + radius = br->blur_kernel_radius; + + side = kernel->side = radius * 2 + 1; + kernel->side_squared = kernel->side * kernel->side; + kernel->wdata = static_cast( + MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); + kernel->pixel_len = br->blur_kernel_radius; + } + + switch (type) { + case KERNEL_BOX: + for (i = 0; i < kernel->side_squared; i++) { + kernel->wdata[i] = 1.0; + } + break; + + case KERNEL_GAUSSIAN: { + /* at 3.0 standard deviations distance, kernel is about zero */ + float standard_dev = radius / 3.0f; + + /* make the necessary adjustment to the value for use in the normal distribution formula */ + standard_dev = -standard_dev * standard_dev * 2; + + for (i = 0; i < side; i++) { + for (j = 0; j < side; j++) { + float idist = radius - i; + float jdist = radius - j; + float value = exp((idist * idist + jdist * jdist) / standard_dev); + + kernel->wdata[i + j * side] = value; + } + } + + break; + } + + default: + printf("unidentified kernel type, aborting\n"); + paint_delete_blur_kernel(kernel); + MEM_delete(kernel); + return nullptr; + } + + return kernel; +} + +void paint_delete_blur_kernel(BlurKernel *kernel) +{ + if (kernel->wdata) { + MEM_freeN(kernel->wdata); + } +} + +/************************ image paint poll ************************/ + +static Brush *image_paint_brush(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + + return BKE_paint_brush(&settings->imapaint.paint); +} + +static bool image_paint_poll_ex(bContext *C, bool check_tool) +{ + Object *obact; + + if (!image_paint_brush(C)) { + return false; + } + + obact = CTX_data_active_object(C); + if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) { + if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) { + return true; + } + } + else { + SpaceImage *sima = CTX_wm_space_image(C); + + if (sima) { + if (sima->image != nullptr && ID_IS_LINKED(sima->image)) { + return false; + } + ARegion *region = CTX_wm_region(C); + + if ((sima->mode == SI_MODE_PAINT) && region->regiontype == RGN_TYPE_WINDOW) { + return true; + } + } + } + + return false; +} + +static bool image_paint_poll(bContext *C) +{ + return image_paint_poll_ex(C, true); +} + +static bool image_paint_poll_ignore_tool(bContext *C) +{ + return image_paint_poll_ex(C, false); +} + +static bool image_paint_2d_clone_poll(bContext *C) +{ + Brush *brush = image_paint_brush(C); + + if (!CTX_wm_region_view3d(C) && image_paint_poll(C)) { + if (brush && (brush->imagepaint_tool == PAINT_TOOL_CLONE)) { + if (brush->clone.image) { + return true; + } + } + } + + return false; +} + +/************************ paint operator ************************/ +enum class eTexPaintMode { + _2D, + _3D_PROJECT, +}; + +struct PaintOperation { + eTexPaintMode mode; + + void *stroke_handle; + + float prevmouse[2]; + float startmouse[2]; + double starttime; + + wmPaintCursor *cursor; + ViewContext vc; +}; + +bool paint_use_opacity_masking(Brush *brush) +{ + return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || + (brush->flag & BRUSH_ANCHORED) || + (ELEM(brush->imagepaint_tool, PAINT_TOOL_SMEAR, PAINT_TOOL_SOFTEN)) || + (brush->imagepaint_tool == PAINT_TOOL_FILL) || + (brush->flag & BRUSH_USE_GRADIENT) || + (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, + MTEX_MAP_MODE_TILED, + MTEX_MAP_MODE_STENCIL, + MTEX_MAP_MODE_3D)) ? + false : + true); +} + +void paint_brush_color_get(struct Scene *scene, + struct Brush *br, + bool color_correction, + bool invert, + float distance, + float pressure, + float color[3], + struct ColorManagedDisplay *display) +{ + if (invert) { + copy_v3_v3(color, BKE_brush_secondary_color_get(scene, br)); + } + else { + if (br->flag & BRUSH_USE_GRADIENT) { + float color_gr[4]; + switch (br->gradient_stroke_mode) { + case BRUSH_GRADIENT_PRESSURE: + BKE_colorband_evaluate(br->gradient, pressure, color_gr); + break; + case BRUSH_GRADIENT_SPACING_REPEAT: { + float coord = fmod(distance / br->gradient_spacing, 1.0); + BKE_colorband_evaluate(br->gradient, coord, color_gr); + break; + } + case BRUSH_GRADIENT_SPACING_CLAMP: { + BKE_colorband_evaluate(br->gradient, distance / br->gradient_spacing, color_gr); + break; + } + } + /* Gradient / Colorband colors are not considered PROP_COLOR_GAMMA. + * Brush colors are expected to be in sRGB though. */ + IMB_colormanagement_scene_linear_to_srgb_v3(color_gr); + + copy_v3_v3(color, color_gr); + } + else { + copy_v3_v3(color, BKE_brush_color_get(scene, br)); + } + } + if (color_correction) { + IMB_colormanagement_display_to_scene_linear_v3(color, display); + } +} + +void paint_brush_init_tex(Brush *brush) +{ + /* init mtex nodes */ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) { + /* has internal flag to detect it only does it once */ + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + mtex = &brush->mask_mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + } +} + +void paint_brush_exit_tex(Brush *brush) +{ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } + mtex = &brush->mask_mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } + } +} + +static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customdata) +{ + PaintOperation *pop = (PaintOperation *)customdata; + + if (pop) { + GPU_line_smooth(true); + GPU_blend(GPU_BLEND_ALPHA); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + + ARegion *region = pop->vc.region; + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + GPU_line_width(4.0); + immUniformColor4ub(0, 0, 0, 255); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2i(pos, x, y); + immVertex2i( + pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); + immEnd(); + + GPU_line_width(2.0); + immUniformColor4ub(255, 255, 255, 255); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2i(pos, x, y); + immVertex2i( + pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); + immEnd(); + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); + } +} + +static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const float mouse[2]) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + PaintOperation *pop = MEM_cnew("PaintOperation"); /* caller frees */ + Brush *brush = BKE_paint_brush(&settings->imapaint.paint); + int mode = RNA_enum_get(op->ptr, "mode"); + ED_view3d_viewcontext_init(C, &pop->vc, depsgraph); + + copy_v2_v2(pop->prevmouse, mouse); + copy_v2_v2(pop->startmouse, mouse); + + /* initialize from context */ + if (CTX_wm_region_view3d(C)) { + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = OBACT(view_layer); + bool uvs, mat, tex, stencil; + if (!ED_paint_proj_mesh_data_check(scene, ob, &uvs, &mat, &tex, &stencil)) { + ED_paint_data_warning(op->reports, uvs, mat, tex, stencil); + MEM_delete(pop); + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr); + return nullptr; + } + pop->mode = eTexPaintMode::_3D_PROJECT; + pop->stroke_handle = paint_proj_new_stroke(C, ob, mouse, mode); + } + else { + pop->mode = eTexPaintMode::_2D; + pop->stroke_handle = paint_2d_new_stroke(C, op, mode); + } + + if (!pop->stroke_handle) { + MEM_delete(pop); + return nullptr; + } + + if ((brush->imagepaint_tool == PAINT_TOOL_FILL) && (brush->flag & BRUSH_USE_GRADIENT)) { + pop->cursor = WM_paint_cursor_activate( + SPACE_TYPE_ANY, RGN_TYPE_ANY, image_paint_poll, gradient_draw_line, pop); + } + + settings->imapaint.flag |= IMAGEPAINT_DRAWING; + ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); + + return pop; +} + +static void paint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) +{ + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + Scene *scene = CTX_data_scene(C); + ToolSettings *toolsettings = CTX_data_tool_settings(C); + UnifiedPaintSettings *ups = &toolsettings->unified_paint_settings; + Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); + + float alphafac = (brush->flag & BRUSH_ACCUMULATE) ? ups->overlap_factor : 1.0f; + + /* initial brush values. Maybe it should be considered moving these to stroke system */ + float startalpha = BKE_brush_alpha_get(scene, brush); + + float mouse[2]; + float pressure; + float size; + float distance = paint_stroke_distance_get(stroke); + int eraser; + + RNA_float_get_array(itemptr, "mouse", mouse); + pressure = RNA_float_get(itemptr, "pressure"); + eraser = RNA_boolean_get(itemptr, "pen_flip"); + size = RNA_float_get(itemptr, "size"); + + /* stroking with fill tool only acts on stroke end */ + if (brush->imagepaint_tool == PAINT_TOOL_FILL) { + copy_v2_v2(pop->prevmouse, mouse); + return; + } + + if (BKE_brush_use_alpha_pressure(brush)) { + BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * pressure * alphafac)); + } + else { + BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * alphafac)); + } + + if ((brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED)) { + UndoStack *ustack = CTX_wm_manager(C)->undo_stack; + ED_image_undo_restore(ustack->step_init); + } + + switch (pop->mode) { + case eTexPaintMode::_2D: + paint_2d_stroke(pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); + break; + + case eTexPaintMode::_3D_PROJECT: + paint_proj_stroke( + C, pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); + break; + } + + copy_v2_v2(pop->prevmouse, mouse); + + /* restore brush values */ + BKE_brush_alpha_set(scene, brush, startalpha); +} + +static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, bool final) +{ + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + + switch (pop->mode) { + case eTexPaintMode::_2D: + paint_2d_redraw(C, pop->stroke_handle, final); + break; + + case eTexPaintMode::_3D_PROJECT: + paint_proj_redraw(C, pop->stroke_handle, final); + break; + } +} + +static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *toolsettings = scene->toolsettings; + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); + + toolsettings->imapaint.flag &= ~IMAGEPAINT_DRAWING; + + if (brush->imagepaint_tool == PAINT_TOOL_FILL) { + if (brush->flag & BRUSH_USE_GRADIENT) { + switch (pop->mode) { + case eTexPaintMode::_2D: + paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); + break; + + case eTexPaintMode::_3D_PROJECT: + paint_proj_stroke(C, + pop->stroke_handle, + pop->startmouse, + pop->prevmouse, + paint_stroke_flipped(stroke), + 1.0, + 0.0, + BKE_brush_size_get(scene, brush)); + /* two redraws, one for GPU update, one for notification */ + paint_proj_redraw(C, pop->stroke_handle, false); + paint_proj_redraw(C, pop->stroke_handle, true); + break; + } + } + else { + switch (pop->mode) { + case eTexPaintMode::_2D: + float color[3]; + if (paint_stroke_inverted(stroke)) { + srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); + } + else { + srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); + } + paint_2d_bucket_fill( + C, color, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); + break; + + case eTexPaintMode::_3D_PROJECT: + paint_proj_stroke(C, + pop->stroke_handle, + pop->startmouse, + pop->prevmouse, + paint_stroke_flipped(stroke), + 1.0, + 0.0, + BKE_brush_size_get(scene, brush)); + /* two redraws, one for GPU update, one for notification */ + paint_proj_redraw(C, pop->stroke_handle, false); + paint_proj_redraw(C, pop->stroke_handle, true); + break; + } + } + } + + switch (pop->mode) { + case eTexPaintMode::_2D: + paint_2d_stroke_done(pop->stroke_handle); + break; + + case eTexPaintMode::_3D_PROJECT: + paint_proj_stroke_done(pop->stroke_handle); + break; + } + + if (pop->cursor) { + WM_paint_cursor_end(pop->cursor); + } + + ED_image_undo_push_end(); + + /* duplicate warning, see texpaint_init */ +#if 0 + if (pop->s.warnmultifile) { + BKE_reportf(op->reports, + RPT_WARNING, + "Image requires 4 color channels to paint: %s", + pop->s.warnmultifile); + } + if (pop->s.warnpackedfile) { + BKE_reportf(op->reports, + RPT_WARNING, + "Packed MultiLayer files cannot be painted: %s", + pop->s.warnpackedfile); + } +#endif + MEM_delete(pop); +} + +static bool paint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) +{ + PaintOperation *pop; + + /* TODO: Should avoid putting this here. Instead, last position should be requested + * from stroke system. */ + + if (!(pop = texture_paint_init(C, op, mouse))) { + return false; + } + + paint_stroke_set_mode_data(static_cast(op->customdata), pop); + + return true; +} + +static int paint_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + int retval; + + op->customdata = paint_stroke_new(C, + op, + nullptr, + paint_stroke_test_start, + paint_stroke_update_step, + paint_stroke_redraw, + paint_stroke_done, + event->type); + + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return OPERATOR_FINISHED; + } + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); + + return OPERATOR_RUNNING_MODAL; +} + +static int paint_exec(bContext *C, wmOperator *op) +{ + PropertyRNA *strokeprop; + PointerRNA firstpoint; + float mouse[2]; + + strokeprop = RNA_struct_find_property(op->ptr, "stroke"); + + if (!RNA_property_collection_lookup_int(op->ptr, strokeprop, 0, &firstpoint)) { + return OPERATOR_CANCELLED; + } + + RNA_float_get_array(&firstpoint, "mouse", mouse); + + op->customdata = paint_stroke_new(C, + op, + nullptr, + paint_stroke_test_start, + paint_stroke_update_step, + paint_stroke_redraw, + paint_stroke_done, + 0); + /* frees op->customdata */ + return paint_stroke_exec(C, op, static_cast(op->customdata)); +} + +static int paint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, static_cast(op->customdata)); +} + +static void paint_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op, static_cast(op->customdata)); +} + +void PAINT_OT_image_paint(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Image Paint"; + ot->idname = "PAINT_OT_image_paint"; + ot->description = "Paint a stroke into the image"; + + /* api callbacks */ + ot->invoke = paint_invoke; + ot->modal = paint_modal; + ot->exec = paint_exec; + ot->poll = image_paint_poll; + ot->cancel = paint_cancel; + + /* flags */ + ot->flag = OPTYPE_BLOCKING; + + paint_stroke_operator_properties(ot); +} + +bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy) +{ + ScrArea *area = CTX_wm_area(C); + if (area && area->spacetype == SPACE_IMAGE) { + SpaceImage *sima = static_cast(area->spacedata.first); + if (sima->mode == SI_MODE_PAINT) { + ARegion *region = CTX_wm_region(C); + ED_space_image_get_zoom(sima, region, zoomx, zoomy); + return true; + } + } + + *zoomx = *zoomy = 1; + + return false; +} + +/************************ cursor drawing *******************************/ + +static void toggle_paint_cursor(Scene *scene, bool enable) +{ + ToolSettings *settings = scene->toolsettings; + Paint *p = &settings->imapaint.paint; + + if (p->paint_cursor && !enable) { + WM_paint_cursor_end(static_cast(p->paint_cursor)); + p->paint_cursor = nullptr; + paint_cursor_delete_textures(); + } + else if (enable) { + paint_cursor_start(p, image_paint_poll); + } +} + +void ED_space_image_paint_update(Main *bmain, wmWindowManager *wm, Scene *scene) +{ + ToolSettings *settings = scene->toolsettings; + ImagePaintSettings *imapaint = &settings->imapaint; + bool enabled = false; + + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + if (area->spacetype == SPACE_IMAGE) { + if (((SpaceImage *)area->spacedata.first)->mode == SI_MODE_PAINT) { + enabled = true; + } + } + } + } + + if (enabled) { + BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_2D, PAINT_CURSOR_TEXTURE_PAINT); + + paint_cursor_start(&imapaint->paint, image_paint_poll); + } + else { + paint_cursor_delete_textures(); + } +} + +/************************ grab clone operator ************************/ + +struct GrabClone { + float startoffset[2]; + int startx, starty; +}; + +static void grab_clone_apply(bContext *C, wmOperator *op) +{ + Brush *brush = image_paint_brush(C); + float delta[2]; + + RNA_float_get_array(op->ptr, "delta", delta); + add_v2_v2(brush->clone.offset, delta); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static int grab_clone_exec(bContext *C, wmOperator *op) +{ + grab_clone_apply(C, op); + + return OPERATOR_FINISHED; +} + +static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Brush *brush = image_paint_brush(C); + GrabClone *cmv; + + cmv = MEM_new("GrabClone"); + copy_v2_v2(cmv->startoffset, brush->clone.offset); + cmv->startx = event->xy[0]; + cmv->starty = event->xy[1]; + op->customdata = cmv; + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Brush *brush = image_paint_brush(C); + ARegion *region = CTX_wm_region(C); + GrabClone *cmv = static_cast(op->customdata); + float startfx, startfy, fx, fy, delta[2]; + int xmin = region->winrct.xmin, ymin = region->winrct.ymin; + + switch (event->type) { + case LEFTMOUSE: + case MIDDLEMOUSE: + case RIGHTMOUSE: /* XXX hardcoded */ + MEM_freeN(op->customdata); + return OPERATOR_FINISHED; + case MOUSEMOVE: + /* mouse moved, so move the clone image */ + UI_view2d_region_to_view( + ®ion->v2d, cmv->startx - xmin, cmv->starty - ymin, &startfx, &startfy); + UI_view2d_region_to_view(®ion->v2d, event->xy[0] - xmin, event->xy[1] - ymin, &fx, &fy); + + delta[0] = fx - startfx; + delta[1] = fy - startfy; + RNA_float_set_array(op->ptr, "delta", delta); + + copy_v2_v2(brush->clone.offset, cmv->startoffset); + + grab_clone_apply(C, op); + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static void grab_clone_cancel(bContext *UNUSED(C), wmOperator *op) +{ + GrabClone *cmv = static_cast(op->customdata); + MEM_delete(cmv); +} + +void PAINT_OT_grab_clone(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Grab Clone"; + ot->idname = "PAINT_OT_grab_clone"; + ot->description = "Move the clone source image"; + + /* api callbacks */ + ot->exec = grab_clone_exec; + ot->invoke = grab_clone_invoke; + ot->modal = grab_clone_modal; + ot->cancel = grab_clone_cancel; + ot->poll = image_paint_2d_clone_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + RNA_def_float_vector(ot->srna, + "delta", + 2, + nullptr, + -FLT_MAX, + FLT_MAX, + "Delta", + "Delta offset of clone image in 0.0 to 1.0 coordinates", + -1.0f, + 1.0f); +} + +/******************** sample color operator ********************/ +struct SampleColorData { + bool show_cursor; + short launch_event; + float initcolor[3]; + bool sample_palette; +}; + +static void sample_color_update_header(SampleColorData *data, bContext *C) +{ + char msg[UI_MAX_DRAW_STR]; + ScrArea *area = CTX_wm_area(C); + + if (area) { + BLI_snprintf(msg, + sizeof(msg), + TIP_("Sample color for %s"), + !data->sample_palette ? + TIP_("Brush. Use Left Click to sample for palette instead") : + TIP_("Palette. Use Left Click to sample more colors")); + ED_workspace_status_text(C, msg); + } +} + +static int sample_color_exec(bContext *C, wmOperator *op) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + ARegion *region = CTX_wm_region(C); + wmWindow *win = CTX_wm_window(C); + const bool show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); + int location[2]; + paint->flags &= ~PAINT_SHOW_BRUSH; + + /* force redraw without cursor */ + WM_paint_cursor_tag_redraw(win, region); + WM_redraw_windows(C); + + RNA_int_get_array(op->ptr, "location", location); + const bool use_palette = RNA_boolean_get(op->ptr, "palette"); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + paint_sample_color(C, region, location[0], location[1], use_sample_texture, use_palette); + + if (show_cursor) { + paint->flags |= PAINT_SHOW_BRUSH; + } + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + + return OPERATOR_FINISHED; +} + +static int sample_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + SampleColorData *data = MEM_new("sample color custom data"); + ARegion *region = CTX_wm_region(C); + wmWindow *win = CTX_wm_window(C); + + data->launch_event = WM_userdef_event_type_from_keymap_type(event->type); + data->show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); + copy_v3_v3(data->initcolor, BKE_brush_color_get(scene, brush)); + data->sample_palette = false; + op->customdata = data; + paint->flags &= ~PAINT_SHOW_BRUSH; + + sample_color_update_header(data, C); + + WM_event_add_modal_handler(C, op); + + /* force redraw without cursor */ + WM_paint_cursor_tag_redraw(win, region); + WM_redraw_windows(C); + + RNA_int_set_array(op->ptr, "location", event->mval); + + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + + return OPERATOR_RUNNING_MODAL; +} + +static int sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + SampleColorData *data = static_cast(op->customdata); + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + + if ((event->type == data->launch_event) && (event->val == KM_RELEASE)) { + if (data->show_cursor) { + paint->flags |= PAINT_SHOW_BRUSH; + } + + if (data->sample_palette) { + BKE_brush_color_set(scene, brush, data->initcolor); + RNA_boolean_set(op->ptr, "palette", true); + } + WM_cursor_modal_restore(CTX_wm_window(C)); + MEM_delete(data); + ED_workspace_status_text(C, nullptr); + + return OPERATOR_FINISHED; + } + + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + switch (event->type) { + case MOUSEMOVE: { + ARegion *region = CTX_wm_region(C); + RNA_int_set_array(op->ptr, "location", event->mval); + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + break; + } + + case LEFTMOUSE: + if (event->val == KM_PRESS) { + ARegion *region = CTX_wm_region(C); + RNA_int_set_array(op->ptr, "location", event->mval); + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, true); + if (!data->sample_palette) { + data->sample_palette = true; + sample_color_update_header(data, C); + } + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static bool sample_color_poll(bContext *C) +{ + return (image_paint_poll_ignore_tool(C) || vertex_paint_poll_ignore_tool(C)); +} + +void PAINT_OT_sample_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Sample Color"; + ot->idname = "PAINT_OT_sample_color"; + ot->description = "Use the mouse to sample a color in the image"; + + /* api callbacks */ + ot->exec = sample_color_exec; + ot->invoke = sample_color_invoke; + ot->modal = sample_color_modal; + ot->poll = sample_color_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop; + + prop = RNA_def_int_vector( + ot->srna, "location", 2, nullptr, 0, INT_MAX, "Location", "", 0, 16384); + RNA_def_property_flag(prop, static_cast(PROP_SKIP_SAVE | PROP_HIDDEN)); + + RNA_def_boolean(ot->srna, "merged", false, "Sample Merged", "Sample the output display color"); + RNA_def_boolean(ot->srna, "palette", false, "Add to Palette", ""); +} + +/******************** texture paint toggle operator ********************/ + +void ED_object_texture_paint_mode_enter_ex(Main *bmain, Scene *scene, Object *ob) +{ + Image *ima = nullptr; + ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; + + /* This has to stay here to regenerate the texture paint + * cache in case we are loading a file */ + BKE_texpaint_slots_refresh_object(scene, ob); + + ED_paint_proj_mesh_data_check(scene, ob, nullptr, nullptr, nullptr, nullptr); + + /* entering paint mode also sets image to editors */ + if (imapaint->mode == IMAGEPAINT_MODE_MATERIAL) { + /* set the current material active paint slot on image editor */ + Material *ma = BKE_object_material_get(ob, ob->actcol); + + if (ma && ma->texpaintslot) { + ima = ma->texpaintslot[ma->paint_active_slot].ima; + } + } + else if (imapaint->mode == IMAGEPAINT_MODE_IMAGE) { + ima = imapaint->canvas; + } + + if (ima) { + wmWindowManager *wm = static_cast(bmain->wm.first); + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + const bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype == SPACE_IMAGE) { + SpaceImage *sima = (SpaceImage *)sl; + + if (!sima->pin) { + ED_space_image_set(bmain, sima, ima, true); + } + } + } + } + } + + ob->mode |= OB_MODE_TEXTURE_PAINT; + + BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_3D, PAINT_CURSOR_TEXTURE_PAINT); + + BKE_paint_toolslots_brush_validate(bmain, &imapaint->paint); + + if (U.glreslimit != 0) { + BKE_image_free_all_gputextures(bmain); + } + BKE_image_paint_set_mipmap(bmain, false); + + toggle_paint_cursor(scene, true); + + Mesh *me = BKE_mesh_from_object(ob); + BLI_assert(me != nullptr); + DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_SCENE | ND_MODE, scene); +} + +void ED_object_texture_paint_mode_enter(bContext *C) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); +} + +void ED_object_texture_paint_mode_exit_ex(Main *bmain, Scene *scene, Object *ob) +{ + ob->mode &= ~OB_MODE_TEXTURE_PAINT; + + if (U.glreslimit != 0) { + BKE_image_free_all_gputextures(bmain); + } + BKE_image_paint_set_mipmap(bmain, true); + toggle_paint_cursor(scene, false); + + Mesh *me = BKE_mesh_from_object(ob); + BLI_assert(me != nullptr); + DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_SCENE | ND_MODE, scene); +} + +void ED_object_texture_paint_mode_exit(bContext *C) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); +} + +static bool texture_paint_toggle_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr || ob->type != OB_MESH) { + return false; + } + if (!ob->data || ID_IS_LINKED(ob->data)) { + return false; + } + + return true; +} + +static int texture_paint_toggle_exec(bContext *C, wmOperator *op) +{ + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + const eObjectMode mode_flag = OB_MODE_TEXTURE_PAINT; + const bool is_mode_set = (ob->mode & mode_flag) != 0; + + if (!is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (ob->mode & mode_flag) { + ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); + } + else { + ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); + } + + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + + WM_toolsystem_update_from_context_view3d(C); + + return OPERATOR_FINISHED; +} + +void PAINT_OT_texture_paint_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Texture Paint Toggle"; + ot->idname = "PAINT_OT_texture_paint_toggle"; + ot->description = "Toggle texture paint mode in 3D view"; + + /* api callbacks */ + ot->exec = texture_paint_toggle_exec; + ot->poll = texture_paint_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int brush_colors_flip_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *br = BKE_paint_brush(paint); + + if (ups->flag & UNIFIED_PAINT_COLOR) { + swap_v3_v3(ups->rgb, ups->secondary_rgb); + } + else if (br) { + swap_v3_v3(br->rgb, br->secondary_rgb); + } + else { + return OPERATOR_CANCELLED; + } + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); + + return OPERATOR_FINISHED; +} + +static bool brush_colors_flip_poll(bContext *C) +{ + if (image_paint_poll(C)) { + Brush *br = image_paint_brush(C); + if (ELEM(br->imagepaint_tool, PAINT_TOOL_DRAW, PAINT_TOOL_FILL)) { + return true; + } + } + else { + Object *ob = CTX_data_active_object(C); + if (ob != nullptr) { + if (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT | OB_MODE_SCULPT)) { + return true; + } + } + } + return false; +} + +void PAINT_OT_brush_colors_flip(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Swap Colors"; + ot->idname = "PAINT_OT_brush_colors_flip"; + ot->description = "Swap primary and secondary brush colors"; + + /* api callbacks */ + ot->exec = brush_colors_flip_exec; + ot->poll = brush_colors_flip_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +void ED_imapaint_bucket_fill(struct bContext *C, + float color[3], + wmOperator *op, + const int mouse[2]) +{ + SpaceImage *sima = CTX_wm_space_image(C); + + if (sima && sima->image) { + Image *ima = sima->image; + + ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); + + const float mouse_init[2] = {static_cast(mouse[0]), static_cast(mouse[1])}; + paint_2d_bucket_fill(C, color, nullptr, mouse_init, nullptr, nullptr); + + ED_image_undo_push_end(); + + DEG_id_tag_update(&ima->id, 0); + } +} + +static bool texture_paint_poll(bContext *C) +{ + if (texture_paint_toggle_poll(C)) { + if (CTX_data_active_object(C)->mode & OB_MODE_TEXTURE_PAINT) { + return true; + } + } + + return false; +} + +bool image_texture_paint_poll(bContext *C) +{ + return (texture_paint_poll(C) || image_paint_poll(C)); +} + +bool facemask_paint_poll(bContext *C) +{ + return BKE_paint_select_face_test(CTX_data_active_object(C)); +} + +bool vert_paint_poll(bContext *C) +{ + return BKE_paint_select_vert_test(CTX_data_active_object(C)); +} + +bool mask_paint_poll(bContext *C) +{ + return BKE_paint_select_elem_test(CTX_data_active_object(C)); +} -- cgit v1.2.3 From 4ba894cbb88a6cf312aece6432b84a5de6bb9aa3 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 09:59:26 +0100 Subject: Fix compilation error on certain platforms. Seems this isn't an issue on newer compilers. --- source/blender/editors/sculpt_paint/paint_image.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index dbc0398f807..139fc6a0409 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -615,7 +615,7 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) } else { switch (pop->mode) { - case eTexPaintMode::_2D: + case eTexPaintMode::_2D: { float color[3]; if (paint_stroke_inverted(stroke)) { srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); @@ -626,6 +626,7 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) paint_2d_bucket_fill( C, color, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); break; + } case eTexPaintMode::_3D_PROJECT: paint_proj_stroke(C, -- cgit v1.2.3 From 683a77256dc8845955a8592a730457e0bdc3c10c Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 10:30:02 +0100 Subject: Fix compilation issue under windows. --- source/blender/editors/sculpt_paint/paint_image.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 139fc6a0409..eda686d808d 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -67,6 +67,8 @@ #include "paint_intern.h" +extern "C" { + /** * This is a static resource for non-global access. * Maybe it should be exposed as part of the paint operation, @@ -1239,11 +1241,11 @@ static int texture_paint_toggle_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - const eObjectMode mode_flag = OB_MODE_TEXTURE_PAINT; + const int mode_flag = OB_MODE_TEXTURE_PAINT; const bool is_mode_set = (ob->mode & mode_flag) != 0; if (!is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + if (!ED_object_mode_compat_set(C, ob, static_cast(mode_flag), op->reports)) { return OPERATOR_CANCELLED; } } @@ -1385,3 +1387,4 @@ bool mask_paint_poll(bContext *C) { return BKE_paint_select_elem_test(CTX_data_active_object(C)); } +} -- cgit v1.2.3 From 0b4c56b01078b70b294208932d1e1d37a1ab3bd2 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 22 Feb 2022 13:42:44 +0100 Subject: Cleanup: Replace eTexPaintMode with classes. For an upcoming prototype we would introduced a new eTexPaintMode option. That would add more cases and if statements. This change migrate the eTexPaintMode to 3 classes. AbstractPaintMode contains a shared interface. ImagePaintMode for 2d painting and ProjectionPaintMode for 3d painting. --- source/blender/editors/sculpt_paint/CMakeLists.txt | 1 + source/blender/editors/sculpt_paint/paint_image.cc | 380 +-------------- .../editors/sculpt_paint/paint_image_ops_paint.cc | 530 +++++++++++++++++++++ source/blender/editors/sculpt_paint/paint_intern.h | 1 + 4 files changed, 533 insertions(+), 379 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/paint_image_ops_paint.cc diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index c1febe5c86b..de7888aa1e8 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -30,6 +30,7 @@ set(SRC paint_curve_undo.c paint_hide.c paint_image.cc + paint_image_ops_paint.cc paint_image_2d.c paint_image_2d_curve_mask.cc paint_image_proj.c diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index eda686d808d..9413f872c3a 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -286,7 +286,7 @@ static bool image_paint_poll_ex(bContext *C, bool check_tool) return false; } -static bool image_paint_poll(bContext *C) +bool image_paint_poll(bContext *C) { return image_paint_poll_ex(C, true); } @@ -312,24 +312,6 @@ static bool image_paint_2d_clone_poll(bContext *C) } /************************ paint operator ************************/ -enum class eTexPaintMode { - _2D, - _3D_PROJECT, -}; - -struct PaintOperation { - eTexPaintMode mode; - - void *stroke_handle; - - float prevmouse[2]; - float startmouse[2]; - double starttime; - - wmPaintCursor *cursor; - ViewContext vc; -}; - bool paint_use_opacity_masking(Brush *brush) { return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || @@ -419,366 +401,6 @@ void paint_brush_exit_tex(Brush *brush) } } -static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customdata) -{ - PaintOperation *pop = (PaintOperation *)customdata; - - if (pop) { - GPU_line_smooth(true); - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - - ARegion *region = pop->vc.region; - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - GPU_line_width(4.0); - immUniformColor4ub(0, 0, 0, 255); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2i(pos, x, y); - immVertex2i( - pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); - immEnd(); - - GPU_line_width(2.0); - immUniformColor4ub(255, 255, 255, 255); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2i(pos, x, y); - immVertex2i( - pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); - immEnd(); - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); - GPU_line_smooth(false); - } -} - -static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const float mouse[2]) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *settings = scene->toolsettings; - PaintOperation *pop = MEM_cnew("PaintOperation"); /* caller frees */ - Brush *brush = BKE_paint_brush(&settings->imapaint.paint); - int mode = RNA_enum_get(op->ptr, "mode"); - ED_view3d_viewcontext_init(C, &pop->vc, depsgraph); - - copy_v2_v2(pop->prevmouse, mouse); - copy_v2_v2(pop->startmouse, mouse); - - /* initialize from context */ - if (CTX_wm_region_view3d(C)) { - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); - bool uvs, mat, tex, stencil; - if (!ED_paint_proj_mesh_data_check(scene, ob, &uvs, &mat, &tex, &stencil)) { - ED_paint_data_warning(op->reports, uvs, mat, tex, stencil); - MEM_delete(pop); - WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr); - return nullptr; - } - pop->mode = eTexPaintMode::_3D_PROJECT; - pop->stroke_handle = paint_proj_new_stroke(C, ob, mouse, mode); - } - else { - pop->mode = eTexPaintMode::_2D; - pop->stroke_handle = paint_2d_new_stroke(C, op, mode); - } - - if (!pop->stroke_handle) { - MEM_delete(pop); - return nullptr; - } - - if ((brush->imagepaint_tool == PAINT_TOOL_FILL) && (brush->flag & BRUSH_USE_GRADIENT)) { - pop->cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, image_paint_poll, gradient_draw_line, pop); - } - - settings->imapaint.flag |= IMAGEPAINT_DRAWING; - ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); - - return pop; -} - -static void paint_stroke_update_step(bContext *C, - wmOperator *UNUSED(op), - struct PaintStroke *stroke, - PointerRNA *itemptr) -{ - PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); - Scene *scene = CTX_data_scene(C); - ToolSettings *toolsettings = CTX_data_tool_settings(C); - UnifiedPaintSettings *ups = &toolsettings->unified_paint_settings; - Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); - - float alphafac = (brush->flag & BRUSH_ACCUMULATE) ? ups->overlap_factor : 1.0f; - - /* initial brush values. Maybe it should be considered moving these to stroke system */ - float startalpha = BKE_brush_alpha_get(scene, brush); - - float mouse[2]; - float pressure; - float size; - float distance = paint_stroke_distance_get(stroke); - int eraser; - - RNA_float_get_array(itemptr, "mouse", mouse); - pressure = RNA_float_get(itemptr, "pressure"); - eraser = RNA_boolean_get(itemptr, "pen_flip"); - size = RNA_float_get(itemptr, "size"); - - /* stroking with fill tool only acts on stroke end */ - if (brush->imagepaint_tool == PAINT_TOOL_FILL) { - copy_v2_v2(pop->prevmouse, mouse); - return; - } - - if (BKE_brush_use_alpha_pressure(brush)) { - BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * pressure * alphafac)); - } - else { - BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * alphafac)); - } - - if ((brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED)) { - UndoStack *ustack = CTX_wm_manager(C)->undo_stack; - ED_image_undo_restore(ustack->step_init); - } - - switch (pop->mode) { - case eTexPaintMode::_2D: - paint_2d_stroke(pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); - break; - - case eTexPaintMode::_3D_PROJECT: - paint_proj_stroke( - C, pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); - break; - } - - copy_v2_v2(pop->prevmouse, mouse); - - /* restore brush values */ - BKE_brush_alpha_set(scene, brush, startalpha); -} - -static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, bool final) -{ - PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); - - switch (pop->mode) { - case eTexPaintMode::_2D: - paint_2d_redraw(C, pop->stroke_handle, final); - break; - - case eTexPaintMode::_3D_PROJECT: - paint_proj_redraw(C, pop->stroke_handle, final); - break; - } -} - -static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) -{ - Scene *scene = CTX_data_scene(C); - ToolSettings *toolsettings = scene->toolsettings; - PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); - Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); - - toolsettings->imapaint.flag &= ~IMAGEPAINT_DRAWING; - - if (brush->imagepaint_tool == PAINT_TOOL_FILL) { - if (brush->flag & BRUSH_USE_GRADIENT) { - switch (pop->mode) { - case eTexPaintMode::_2D: - paint_2d_gradient_fill(C, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); - break; - - case eTexPaintMode::_3D_PROJECT: - paint_proj_stroke(C, - pop->stroke_handle, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->stroke_handle, false); - paint_proj_redraw(C, pop->stroke_handle, true); - break; - } - } - else { - switch (pop->mode) { - case eTexPaintMode::_2D: { - float color[3]; - if (paint_stroke_inverted(stroke)) { - srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); - } - else { - srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); - } - paint_2d_bucket_fill( - C, color, brush, pop->startmouse, pop->prevmouse, pop->stroke_handle); - break; - } - - case eTexPaintMode::_3D_PROJECT: - paint_proj_stroke(C, - pop->stroke_handle, - pop->startmouse, - pop->prevmouse, - paint_stroke_flipped(stroke), - 1.0, - 0.0, - BKE_brush_size_get(scene, brush)); - /* two redraws, one for GPU update, one for notification */ - paint_proj_redraw(C, pop->stroke_handle, false); - paint_proj_redraw(C, pop->stroke_handle, true); - break; - } - } - } - - switch (pop->mode) { - case eTexPaintMode::_2D: - paint_2d_stroke_done(pop->stroke_handle); - break; - - case eTexPaintMode::_3D_PROJECT: - paint_proj_stroke_done(pop->stroke_handle); - break; - } - - if (pop->cursor) { - WM_paint_cursor_end(pop->cursor); - } - - ED_image_undo_push_end(); - - /* duplicate warning, see texpaint_init */ -#if 0 - if (pop->s.warnmultifile) { - BKE_reportf(op->reports, - RPT_WARNING, - "Image requires 4 color channels to paint: %s", - pop->s.warnmultifile); - } - if (pop->s.warnpackedfile) { - BKE_reportf(op->reports, - RPT_WARNING, - "Packed MultiLayer files cannot be painted: %s", - pop->s.warnpackedfile); - } -#endif - MEM_delete(pop); -} - -static bool paint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) -{ - PaintOperation *pop; - - /* TODO: Should avoid putting this here. Instead, last position should be requested - * from stroke system. */ - - if (!(pop = texture_paint_init(C, op, mouse))) { - return false; - } - - paint_stroke_set_mode_data(static_cast(op->customdata), pop); - - return true; -} - -static int paint_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - int retval; - - op->customdata = paint_stroke_new(C, - op, - nullptr, - paint_stroke_test_start, - paint_stroke_update_step, - paint_stroke_redraw, - paint_stroke_done, - event->type); - - if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op, static_cast(op->customdata)); - return OPERATOR_FINISHED; - } - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - OPERATOR_RETVAL_CHECK(retval); - BLI_assert(retval == OPERATOR_RUNNING_MODAL); - - return OPERATOR_RUNNING_MODAL; -} - -static int paint_exec(bContext *C, wmOperator *op) -{ - PropertyRNA *strokeprop; - PointerRNA firstpoint; - float mouse[2]; - - strokeprop = RNA_struct_find_property(op->ptr, "stroke"); - - if (!RNA_property_collection_lookup_int(op->ptr, strokeprop, 0, &firstpoint)) { - return OPERATOR_CANCELLED; - } - - RNA_float_get_array(&firstpoint, "mouse", mouse); - - op->customdata = paint_stroke_new(C, - op, - nullptr, - paint_stroke_test_start, - paint_stroke_update_step, - paint_stroke_redraw, - paint_stroke_done, - 0); - /* frees op->customdata */ - return paint_stroke_exec(C, op, static_cast(op->customdata)); -} - -static int paint_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - return paint_stroke_modal(C, op, event, static_cast(op->customdata)); -} - -static void paint_cancel(bContext *C, wmOperator *op) -{ - paint_stroke_cancel(C, op, static_cast(op->customdata)); -} - -void PAINT_OT_image_paint(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Image Paint"; - ot->idname = "PAINT_OT_image_paint"; - ot->description = "Paint a stroke into the image"; - - /* api callbacks */ - ot->invoke = paint_invoke; - ot->modal = paint_modal; - ot->exec = paint_exec; - ot->poll = image_paint_poll; - ot->cancel = paint_cancel; - - /* flags */ - ot->flag = OPTYPE_BLOCKING; - - paint_stroke_operator_properties(ot); -} - bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy) { ScrArea *area = CTX_wm_area(C); diff --git a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc new file mode 100644 index 00000000000..79701bba01c --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc @@ -0,0 +1,530 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * \brief Painting operator to paint in 2D and 3D. + */ + +#include "DNA_brush_types.h" +#include "DNA_color_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "DEG_depsgraph.h" + +#include "ED_paint.h" +#include "ED_view3d.h" + +#include "GPU_immediate.h" +#include "GPU_state.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "paint_intern.h" + +namespace blender::ed::sculpt_paint::image::ops::paint { + +/** + * Interface to use the same painting operator for 3D and 2D painting. Interface removes the + * differences between the actual calls that are being performed. + */ +class AbstractPaintMode { + public: + virtual void *paint_new_stroke( + bContext *C, wmOperator *op, Object *ob, const float mouse[2], int mode) = 0; + virtual void paint_stroke(bContext *C, + void *stroke_handle, + float prev_mouse[2], + float mouse[2], + int eraser, + float pressure, + float distance, + float size) = 0; + + virtual void paint_stroke_redraw(const bContext *C, void *stroke_handle, bool final) = 0; + virtual void paint_stroke_done(void *stroke_handle) = 0; + virtual void paint_gradient_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) = 0; + virtual void paint_bucket_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) = 0; +}; + +class ImagePaintMode : public AbstractPaintMode { + public: + void *paint_new_stroke(bContext *C, + wmOperator *op, + Object *UNUSED(ob), + const float UNUSED(mouse[2]), + int mode) override + { + return paint_2d_new_stroke(C, op, mode); + } + + void paint_stroke(bContext *UNUSED(C), + void *stroke_handle, + float prev_mouse[2], + float mouse[2], + int eraser, + float pressure, + float distance, + float size) override + { + paint_2d_stroke(stroke_handle, prev_mouse, mouse, eraser, pressure, distance, size); + } + + void paint_stroke_redraw(const bContext *C, void *stroke_handle, bool final) override + { + paint_2d_redraw(C, stroke_handle, final); + } + + void paint_stroke_done(void *stroke_handle) override + { + paint_2d_stroke_done(stroke_handle); + } + + void paint_gradient_fill(const bContext *C, + const Scene *UNUSED(scene), + Brush *brush, + struct PaintStroke *UNUSED(stroke), + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) override + { + paint_2d_gradient_fill(C, brush, mouse_start, mouse_end, stroke_handle); + } + + void paint_bucket_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) override + { + float color[3]; + if (paint_stroke_inverted(stroke)) { + srgb_to_linearrgb_v3_v3(color, BKE_brush_secondary_color_get(scene, brush)); + } + else { + srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush)); + } + paint_2d_bucket_fill(C, color, brush, mouse_start, mouse_end, stroke_handle); + } +}; + +class ProjectionPaintMode : public AbstractPaintMode { + public: + void *paint_new_stroke( + bContext *C, wmOperator *UNUSED(op), Object *ob, const float mouse[2], int mode) override + { + return paint_proj_new_stroke(C, ob, mouse, mode); + } + + void paint_stroke(bContext *C, + void *stroke_handle, + float prev_mouse[2], + float mouse[2], + int eraser, + float pressure, + float distance, + float size) override + { + paint_proj_stroke(C, stroke_handle, prev_mouse, mouse, eraser, pressure, distance, size); + }; + + void paint_stroke_redraw(const bContext *C, void *stroke_handle, bool final) override + { + paint_proj_redraw(C, stroke_handle, final); + } + + void paint_stroke_done(void *stroke_handle) override + { + paint_proj_stroke_done(stroke_handle); + } + + void paint_gradient_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) override + { + paint_fill(C, scene, brush, stroke, stroke_handle, mouse_start, mouse_end); + } + + void paint_bucket_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) override + { + paint_fill(C, scene, brush, stroke, stroke_handle, mouse_start, mouse_end); + } + + private: + void paint_fill(const bContext *C, + const Scene *scene, + Brush *brush, + struct PaintStroke *stroke, + void *stroke_handle, + float mouse_start[2], + float mouse_end[2]) + { + paint_proj_stroke(C, + stroke_handle, + mouse_start, + mouse_end, + paint_stroke_flipped(stroke), + 1.0, + 0.0, + BKE_brush_size_get(scene, brush)); + /* two redraws, one for GPU update, one for notification */ + paint_proj_redraw(C, stroke_handle, false); + paint_proj_redraw(C, stroke_handle, true); + } +}; + +struct PaintOperation { + AbstractPaintMode *mode = nullptr; + + void *stroke_handle = nullptr; + + float prevmouse[2] = {0.0f, 0.0f}; + float startmouse[2] = {0.0f, 0.0f}; + double starttime = 0.0; + + wmPaintCursor *cursor = nullptr; + ViewContext vc = {nullptr}; + + PaintOperation() = default; + ~PaintOperation() + { + MEM_delete(mode); + mode = nullptr; + + if (cursor) { + WM_paint_cursor_end(cursor); + cursor = nullptr; + } + } +}; + +static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customdata) +{ + PaintOperation *pop = (PaintOperation *)customdata; + + if (pop) { + GPU_line_smooth(true); + GPU_blend(GPU_BLEND_ALPHA); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + + ARegion *region = pop->vc.region; + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + GPU_line_width(4.0); + immUniformColor4ub(0, 0, 0, 255); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2i(pos, x, y); + immVertex2i( + pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); + immEnd(); + + GPU_line_width(2.0); + immUniformColor4ub(255, 255, 255, 255); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2i(pos, x, y); + immVertex2i( + pos, pop->startmouse[0] + region->winrct.xmin, pop->startmouse[1] + region->winrct.ymin); + immEnd(); + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); + } +} + +static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const float mouse[2]) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + PaintOperation *pop = MEM_new("PaintOperation"); /* caller frees */ + Brush *brush = BKE_paint_brush(&settings->imapaint.paint); + int mode = RNA_enum_get(op->ptr, "mode"); + ED_view3d_viewcontext_init(C, &pop->vc, depsgraph); + + copy_v2_v2(pop->prevmouse, mouse); + copy_v2_v2(pop->startmouse, mouse); + + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = OBACT(view_layer); + + /* initialize from context */ + if (CTX_wm_region_view3d(C)) { + bool uvs, mat, tex, stencil; + if (!ED_paint_proj_mesh_data_check(scene, ob, &uvs, &mat, &tex, &stencil)) { + ED_paint_data_warning(op->reports, uvs, mat, tex, stencil); + MEM_delete(pop); + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr); + return nullptr; + } + pop->mode = MEM_new("ProjectionPaintMode"); + } + else { + pop->mode = MEM_new("ImagePaintMode"); + } + + pop->stroke_handle = pop->mode->paint_new_stroke(C, op, ob, mouse, mode); + if (!pop->stroke_handle) { + MEM_delete(pop); + return nullptr; + } + + if ((brush->imagepaint_tool == PAINT_TOOL_FILL) && (brush->flag & BRUSH_USE_GRADIENT)) { + pop->cursor = WM_paint_cursor_activate( + SPACE_TYPE_ANY, RGN_TYPE_ANY, image_paint_poll, gradient_draw_line, pop); + } + + settings->imapaint.flag |= IMAGEPAINT_DRAWING; + ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); + + return pop; +} + +static void paint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) +{ + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + Scene *scene = CTX_data_scene(C); + ToolSettings *toolsettings = CTX_data_tool_settings(C); + UnifiedPaintSettings *ups = &toolsettings->unified_paint_settings; + Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); + + float alphafac = (brush->flag & BRUSH_ACCUMULATE) ? ups->overlap_factor : 1.0f; + + /* initial brush values. Maybe it should be considered moving these to stroke system */ + float startalpha = BKE_brush_alpha_get(scene, brush); + + float mouse[2]; + float pressure; + float size; + float distance = paint_stroke_distance_get(stroke); + int eraser; + + RNA_float_get_array(itemptr, "mouse", mouse); + pressure = RNA_float_get(itemptr, "pressure"); + eraser = RNA_boolean_get(itemptr, "pen_flip"); + size = RNA_float_get(itemptr, "size"); + + /* stroking with fill tool only acts on stroke end */ + if (brush->imagepaint_tool == PAINT_TOOL_FILL) { + copy_v2_v2(pop->prevmouse, mouse); + return; + } + + if (BKE_brush_use_alpha_pressure(brush)) { + BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * pressure * alphafac)); + } + else { + BKE_brush_alpha_set(scene, brush, max_ff(0.0f, startalpha * alphafac)); + } + + if ((brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED)) { + UndoStack *ustack = CTX_wm_manager(C)->undo_stack; + ED_image_undo_restore(ustack->step_init); + } + + pop->mode->paint_stroke( + C, pop->stroke_handle, pop->prevmouse, mouse, eraser, pressure, distance, size); + + copy_v2_v2(pop->prevmouse, mouse); + + /* restore brush values */ + BKE_brush_alpha_set(scene, brush, startalpha); +} + +static void paint_stroke_redraw(const bContext *C, struct PaintStroke *stroke, bool final) +{ + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + pop->mode->paint_stroke_redraw(C, pop->stroke_handle, final); +} + +static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *toolsettings = scene->toolsettings; + PaintOperation *pop = static_cast(paint_stroke_mode_data(stroke)); + Brush *brush = BKE_paint_brush(&toolsettings->imapaint.paint); + + toolsettings->imapaint.flag &= ~IMAGEPAINT_DRAWING; + + if (brush->imagepaint_tool == PAINT_TOOL_FILL) { + if (brush->flag & BRUSH_USE_GRADIENT) { + pop->mode->paint_gradient_fill( + C, scene, brush, stroke, pop->stroke_handle, pop->startmouse, pop->prevmouse); + } + else { + pop->mode->paint_bucket_fill( + C, scene, brush, stroke, pop->stroke_handle, pop->startmouse, pop->prevmouse); + } + } + pop->mode->paint_stroke_done(pop->stroke_handle); + pop->stroke_handle = nullptr; + + ED_image_undo_push_end(); + +/* duplicate warning, see texpaint_init */ +#if 0 + if (pop->s.warnmultifile) { + BKE_reportf(op->reports, + RPT_WARNING, + "Image requires 4 color channels to paint: %s", + pop->s.warnmultifile); + } + if (pop->s.warnpackedfile) { + BKE_reportf(op->reports, + RPT_WARNING, + "Packed MultiLayer files cannot be painted: %s", + pop->s.warnpackedfile); + } +#endif + MEM_delete(pop); +} + +static bool paint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) +{ + PaintOperation *pop; + + /* TODO: Should avoid putting this here. Instead, last position should be requested + * from stroke system. */ + + if (!(pop = texture_paint_init(C, op, mouse))) { + return false; + } + + paint_stroke_set_mode_data(static_cast(op->customdata), pop); + + return true; +} + +static int paint_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + int retval; + + op->customdata = paint_stroke_new(C, + op, + nullptr, + paint_stroke_test_start, + paint_stroke_update_step, + paint_stroke_redraw, + paint_stroke_done, + event->type); + + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return OPERATOR_FINISHED; + } + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); + + return OPERATOR_RUNNING_MODAL; +} + +static int paint_exec(bContext *C, wmOperator *op) +{ + PropertyRNA *strokeprop; + PointerRNA firstpoint; + float mouse[2]; + + strokeprop = RNA_struct_find_property(op->ptr, "stroke"); + + if (!RNA_property_collection_lookup_int(op->ptr, strokeprop, 0, &firstpoint)) { + return OPERATOR_CANCELLED; + } + + RNA_float_get_array(&firstpoint, "mouse", mouse); + + op->customdata = paint_stroke_new(C, + op, + nullptr, + paint_stroke_test_start, + paint_stroke_update_step, + paint_stroke_redraw, + paint_stroke_done, + 0); + /* frees op->customdata */ + return paint_stroke_exec(C, op, static_cast(op->customdata)); +} + +static int paint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, static_cast(op->customdata)); +} + +static void paint_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op, static_cast(op->customdata)); +} +} // namespace blender::ed::sculpt_paint::image::ops::paint + +extern "C" { +void PAINT_OT_image_paint(wmOperatorType *ot) +{ + using namespace blender::ed::sculpt_paint::image::ops::paint; + + /* identifiers */ + ot->name = "Image Paint"; + ot->idname = "PAINT_OT_image_paint"; + ot->description = "Paint a stroke into the image"; + + /* api callbacks */ + ot->invoke = paint_invoke; + ot->modal = paint_modal; + ot->exec = paint_exec; + ot->poll = image_paint_poll; + ot->cancel = paint_cancel; + + /* flags */ + ot->flag = OPTYPE_BLOCKING; + + paint_stroke_operator_properties(ot); +} +} diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index ba363f687dc..82fdf49c28e 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -269,6 +269,7 @@ void paint_brush_color_get(struct Scene *scene, bool paint_use_opacity_masking(struct Brush *brush); void paint_brush_init_tex(struct Brush *brush); void paint_brush_exit_tex(struct Brush *brush); +bool image_paint_poll(struct bContext *C); void PAINT_OT_grab_clone(struct wmOperatorType *ot); void PAINT_OT_sample_color(struct wmOperatorType *ot); -- cgit v1.2.3 From 4e78a7360e451b4e2c99e8fb88d6701bc8f13618 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 22 Feb 2022 13:52:15 +0100 Subject: Fix T93784: text and curve objects have no motion blur Previously, objects and geometries were mapped between frames using different hash tables in a way that is incompatible with geometry instances. That is because the geometry mapping happened without looking at the `persistent_id` of instances, which is not possible anymore. Now, there is just one mapping that identifies the same object at multiple points in time. There are also two new caches for duplicated vbos and textures used for motion blur. This data has to be duplicated, otherwise it would be freed when another time step is evaluated. This caching existed before, but is now a bit more explicit and works for geometry instances as well. Differential Revision: https://developer.blender.org/D13497 --- source/blender/draw/engines/eevee/eevee_data.c | 124 +++++----- .../blender/draw/engines/eevee/eevee_motion_blur.c | 254 +++++++++++---------- source/blender/draw/engines/eevee/eevee_private.h | 48 ++-- source/blender/gpu/GPU_batch.h | 1 + source/blender/gpu/intern/gpu_batch.cc | 10 + 5 files changed, 227 insertions(+), 210 deletions(-) diff --git a/source/blender/draw/engines/eevee/eevee_data.c b/source/blender/draw/engines/eevee/eevee_data.c index b453df284ed..58ea682c060 100644 --- a/source/blender/draw/engines/eevee/eevee_data.c +++ b/source/blender/draw/engines/eevee/eevee_data.c @@ -42,25 +42,12 @@ static void eevee_motion_blur_mesh_data_free(void *val) { - EEVEE_GeometryMotionData *geom_mb = (EEVEE_GeometryMotionData *)val; - EEVEE_HairMotionData *hair_mb = (EEVEE_HairMotionData *)val; - switch (geom_mb->type) { - case EEVEE_MOTION_DATA_HAIR: - for (int j = 0; j < hair_mb->psys_len; j++) { - for (int i = 0; i < ARRAY_SIZE(hair_mb->psys[0].hair_pos); i++) { - GPU_VERTBUF_DISCARD_SAFE(hair_mb->psys[j].hair_pos[i]); - } - for (int i = 0; i < ARRAY_SIZE(hair_mb->psys[0].hair_pos); i++) { - DRW_TEXTURE_FREE_SAFE(hair_mb->psys[j].hair_pos_tx[i]); - } - } - break; - - case EEVEE_MOTION_DATA_MESH: - for (int i = 0; i < ARRAY_SIZE(geom_mb->vbo); i++) { - GPU_VERTBUF_DISCARD_SAFE(geom_mb->vbo[i]); - } - break; + EEVEE_ObjectMotionData *mb_data = (EEVEE_ObjectMotionData *)val; + if (mb_data->hair_data != NULL) { + MEM_freeN(mb_data->hair_data); + } + if (mb_data->geometry_data != NULL) { + MEM_freeN(mb_data->geometry_data); } MEM_freeN(val); } @@ -99,39 +86,57 @@ static bool eevee_object_key_cmp(const void *a, const void *b) return false; } +void EEVEE_motion_hair_step_free(EEVEE_HairMotionStepData *step_data) +{ + GPU_vertbuf_discard(step_data->hair_pos); + DRW_texture_free(step_data->hair_pos_tx); + MEM_freeN(step_data); +} + void EEVEE_motion_blur_data_init(EEVEE_MotionBlurData *mb) { if (mb->object == NULL) { mb->object = BLI_ghash_new(eevee_object_key_hash, eevee_object_key_cmp, "EEVEE Object Motion"); } - if (mb->geom == NULL) { - mb->geom = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "EEVEE Mesh Motion"); + for (int i = 0; i < 2; i++) { + if (mb->position_vbo_cache[i] == NULL) { + mb->position_vbo_cache[i] = BLI_ghash_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "EEVEE duplicate vbo cache"); + } + if (mb->hair_motion_step_cache[i] == NULL) { + mb->hair_motion_step_cache[i] = BLI_ghash_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "EEVEE hair motion step cache"); + } } } void EEVEE_motion_blur_data_free(EEVEE_MotionBlurData *mb) { if (mb->object) { - BLI_ghash_free(mb->object, MEM_freeN, MEM_freeN); + BLI_ghash_free(mb->object, MEM_freeN, eevee_motion_blur_mesh_data_free); mb->object = NULL; } - if (mb->geom) { - BLI_ghash_free(mb->geom, NULL, eevee_motion_blur_mesh_data_free); - mb->geom = NULL; + for (int i = 0; i < 2; i++) { + if (mb->position_vbo_cache[i]) { + BLI_ghash_free(mb->position_vbo_cache[i], NULL, (GHashValFreeFP)GPU_vertbuf_discard); + } + if (mb->hair_motion_step_cache[i]) { + BLI_ghash_free( + mb->hair_motion_step_cache[i], NULL, (GHashValFreeFP)EEVEE_motion_hair_step_free); + } } } -EEVEE_ObjectMotionData *EEVEE_motion_blur_object_data_get(EEVEE_MotionBlurData *mb, - Object *ob, - bool hair) +EEVEE_ObjectMotionData *EEVEE_motion_blur_object_data_get(EEVEE_MotionBlurData *mb, Object *ob) { if (mb->object == NULL) { return NULL; } EEVEE_ObjectKey key, *key_p; - /* Small hack to avoid another comparison. */ - key.ob = (Object *)((char *)ob + hair); + /* Assumes that all instances have the same object pointer. This is currently the case because + * instance objects are temporary objects on the stack. */ + key.ob = ob; DupliObject *dup = DRW_object_get_dupli(ob); if (dup) { key.parent = DRW_object_get_dupli_parent(ob); @@ -154,53 +159,28 @@ EEVEE_ObjectMotionData *EEVEE_motion_blur_object_data_get(EEVEE_MotionBlurData * return ob_step; } -static void *motion_blur_deform_data_get(EEVEE_MotionBlurData *mb, Object *ob, bool hair) +EEVEE_GeometryMotionData *EEVEE_motion_blur_geometry_data_get(EEVEE_ObjectMotionData *mb_data) { - if (mb->geom == NULL) { - return NULL; + if (mb_data->geometry_data == NULL) { + EEVEE_GeometryMotionData *geom_step = MEM_callocN(sizeof(EEVEE_GeometryMotionData), __func__); + geom_step->type = EEVEE_MOTION_DATA_MESH; + mb_data->geometry_data = geom_step; } - DupliObject *dup = DRW_object_get_dupli(ob); - void *key; - if (dup) { - key = dup->ob; - } - else { - key = ob; - } - /* Only use data for object that have no modifiers. */ - if (!BKE_object_is_modified(DRW_context_state_get()->scene, ob)) { - key = ob->data; - } - key = (char *)key + (int)hair; - EEVEE_GeometryMotionData *geom_step = BLI_ghash_lookup(mb->geom, key); - if (geom_step == NULL) { - if (hair) { - EEVEE_HairMotionData *hair_step; - /* Ugly, we allocate for each modifiers and just fill based on modifier index in the list. */ - int psys_len = (ob->type != OB_HAIR) ? BLI_listbase_count(&ob->modifiers) : 1; - hair_step = MEM_callocN(sizeof(EEVEE_HairMotionData) + sizeof(hair_step->psys[0]) * psys_len, - __func__); - hair_step->psys_len = psys_len; - geom_step = (EEVEE_GeometryMotionData *)hair_step; - geom_step->type = EEVEE_MOTION_DATA_HAIR; - } - else { - geom_step = MEM_callocN(sizeof(EEVEE_GeometryMotionData), __func__); - geom_step->type = EEVEE_MOTION_DATA_MESH; - } - BLI_ghash_insert(mb->geom, key, geom_step); - } - return geom_step; + return mb_data->geometry_data; } -EEVEE_GeometryMotionData *EEVEE_motion_blur_geometry_data_get(EEVEE_MotionBlurData *mb, Object *ob) +EEVEE_HairMotionData *EEVEE_motion_blur_hair_data_get(EEVEE_ObjectMotionData *mb_data, Object *ob) { - return motion_blur_deform_data_get(mb, ob, false); -} - -EEVEE_HairMotionData *EEVEE_motion_blur_hair_data_get(EEVEE_MotionBlurData *mb, Object *ob) -{ - return motion_blur_deform_data_get(mb, ob, true); + if (mb_data->hair_data == NULL) { + /* Ugly, we allocate for each modifiers and just fill based on modifier index in the list. */ + int psys_len = (ob->type != OB_HAIR) ? BLI_listbase_count(&ob->modifiers) : 1; + EEVEE_HairMotionData *hair_step = MEM_callocN( + sizeof(EEVEE_HairMotionData) + sizeof(hair_step->psys[0]) * psys_len, __func__); + hair_step->psys_len = psys_len; + hair_step->type = EEVEE_MOTION_DATA_HAIR; + mb_data->hair_data = hair_step; + } + return mb_data->hair_data; } /* View Layer data. */ diff --git a/source/blender/draw/engines/eevee/eevee_motion_blur.c b/source/blender/draw/engines/eevee/eevee_motion_blur.c index 703518a32ec..dd6b4b2c17a 100644 --- a/source/blender/draw/engines/eevee/eevee_motion_blur.c +++ b/source/blender/draw/engines/eevee/eevee_motion_blur.c @@ -241,15 +241,14 @@ void EEVEE_motion_blur_hair_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), } /* For now we assume hair objects are always moving. */ - EEVEE_ObjectMotionData *mb_data = EEVEE_motion_blur_object_data_get( - &effects->motion_blur, ob, true); + EEVEE_ObjectMotionData *mb_data = EEVEE_motion_blur_object_data_get(&effects->motion_blur, ob); if (mb_data) { int mb_step = effects->motion_blur_step; /* Store transform. */ DRW_hair_duplimat_get(ob, psys, md, mb_data->obmat[mb_step]); - EEVEE_HairMotionData *mb_hair = EEVEE_motion_blur_hair_data_get(&effects->motion_blur, ob); + EEVEE_HairMotionData *mb_hair = EEVEE_motion_blur_hair_data_get(mb_data, ob); int psys_id = (md != NULL) ? BLI_findindex(&ob->modifiers, md) : 0; if (psys_id >= mb_hair->psys_len) { @@ -267,8 +266,8 @@ void EEVEE_motion_blur_hair_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), copy_m4_m4(mb_data->obmat[MB_NEXT], mb_data->obmat[MB_CURR]); } - GPUTexture *tex_prev = mb_hair->psys[psys_id].hair_pos_tx[MB_PREV]; - GPUTexture *tex_next = mb_hair->psys[psys_id].hair_pos_tx[MB_NEXT]; + GPUTexture *tex_prev = mb_hair->psys[psys_id].step_data[MB_PREV].hair_pos_tx; + GPUTexture *tex_next = mb_hair->psys[psys_id].step_data[MB_NEXT].hair_pos_tx; grp = DRW_shgroup_hair_create_sub(ob, psys, md, effects->motion_blur.hair_grp, NULL); DRW_shgroup_uniform_mat4(grp, "prevModelMatrix", mb_data->obmat[MB_PREV]); @@ -280,7 +279,7 @@ void EEVEE_motion_blur_hair_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), } else { /* Store vertex position buffer. */ - mb_hair->psys[psys_id].hair_pos[mb_step] = DRW_hair_pos_buffer_get(ob, psys, md); + mb_hair->psys[psys_id].step_data[mb_step].hair_pos = DRW_hair_pos_buffer_get(ob, psys, md); mb_hair->use_deform = true; } } @@ -319,24 +318,14 @@ void EEVEE_motion_blur_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), return; } - const DupliObject *dup = DRW_object_get_dupli(ob); - if (dup != NULL && dup->ob->data != dup->ob_data) { - /* Geometry instances do not support motion blur correctly yet. The #key used in - * #motion_blur_deform_data_get has to take ids of instances (#DupliObject.persistent_id) into - * account. Otherwise it can't find matching geometry instances at different points in time. */ - return; - } - - EEVEE_ObjectMotionData *mb_data = EEVEE_motion_blur_object_data_get( - &effects->motion_blur, ob, false); + EEVEE_ObjectMotionData *mb_data = EEVEE_motion_blur_object_data_get(&effects->motion_blur, ob); if (mb_data) { int mb_step = effects->motion_blur_step; /* Store transform. */ copy_m4_m4(mb_data->obmat[mb_step], ob->obmat); - EEVEE_GeometryMotionData *mb_geom = EEVEE_motion_blur_geometry_data_get(&effects->motion_blur, - ob); + EEVEE_GeometryMotionData *mb_geom = EEVEE_motion_blur_geometry_data_get(mb_data); if (mb_step == MB_CURR) { GPUBatch *batch = DRW_cache_object_surface_get(ob); @@ -422,86 +411,93 @@ void EEVEE_motion_blur_cache_finish(EEVEE_Data *vedata) DRW_cache_restart(); } - for (BLI_ghashIterator_init(&ghi, effects->motion_blur.geom); + for (BLI_ghashIterator_init(&ghi, effects->motion_blur.object); BLI_ghashIterator_done(&ghi) == false; BLI_ghashIterator_step(&ghi)) { - EEVEE_GeometryMotionData *mb_geom = BLI_ghashIterator_getValue(&ghi); - EEVEE_HairMotionData *mb_hair = (EEVEE_HairMotionData *)mb_geom; - - if (!mb_geom->use_deform) { - continue; - } - - switch (mb_geom->type) { - case EEVEE_MOTION_DATA_HAIR: - if (mb_step == MB_CURR) { - /* TODO(fclem): Check if vertex count mismatch. */ - mb_hair->use_deform = true; - } - else { - for (int i = 0; i < mb_hair->psys_len; i++) { - if (mb_hair->psys[i].hair_pos[mb_step] == NULL) { - continue; - } - mb_hair->psys[i].hair_pos[mb_step] = GPU_vertbuf_duplicate( - mb_hair->psys[i].hair_pos[mb_step]); - + EEVEE_ObjectMotionData *mb_data = BLI_ghashIterator_getValue(&ghi); + EEVEE_HairMotionData *mb_hair = mb_data->hair_data; + EEVEE_GeometryMotionData *mb_geom = mb_data->geometry_data; + if (mb_hair != NULL && mb_hair->use_deform) { + if (mb_step == MB_CURR) { + /* TODO(fclem): Check if vertex count mismatch. */ + mb_hair->use_deform = true; + } + else { + for (int i = 0; i < mb_hair->psys_len; i++) { + GPUVertBuf *vbo = mb_hair->psys[i].step_data[mb_step].hair_pos; + if (vbo == NULL) { + continue; + } + EEVEE_HairMotionStepData **step_data_cache_ptr; + if (!BLI_ghash_ensure_p(effects->motion_blur.hair_motion_step_cache[mb_step], + vbo, + (void ***)&step_data_cache_ptr)) { + EEVEE_HairMotionStepData *new_step_data = MEM_callocN(sizeof(EEVEE_HairMotionStepData), + __func__); + /* Duplicate the vbo, otherwise it would be lost when evaluating another frame. */ + new_step_data->hair_pos = GPU_vertbuf_duplicate(vbo); /* Create vbo immediately to bind to texture buffer. */ - GPU_vertbuf_use(mb_hair->psys[i].hair_pos[mb_step]); - - mb_hair->psys[i].hair_pos_tx[mb_step] = GPU_texture_create_from_vertbuf( - "hair_pos_motion_blur", mb_hair->psys[i].hair_pos[mb_step]); + GPU_vertbuf_use(new_step_data->hair_pos); + new_step_data->hair_pos_tx = GPU_texture_create_from_vertbuf("hair_pos_motion_blur", + new_step_data->hair_pos); + *step_data_cache_ptr = new_step_data; } + mb_hair->psys[i].step_data[mb_step] = **step_data_cache_ptr; } - break; - - case EEVEE_MOTION_DATA_MESH: - if (mb_step == MB_CURR) { - /* Modify batch to have data from adjacent frames. */ - GPUBatch *batch = mb_geom->batch; - for (int i = 0; i < MB_CURR; i++) { - GPUVertBuf *vbo = mb_geom->vbo[i]; - if (vbo && batch) { - if (GPU_vertbuf_get_vertex_len(vbo) != GPU_vertbuf_get_vertex_len(batch->verts[0])) { - /* Vertex count mismatch, disable deform motion blur. */ - mb_geom->use_deform = false; - } - - if (mb_geom->use_deform == false) { - motion_blur_remove_vbo_reference_from_batch( - batch, mb_geom->vbo[MB_PREV], mb_geom->vbo[MB_NEXT]); - - GPU_VERTBUF_DISCARD_SAFE(mb_geom->vbo[MB_PREV]); - GPU_VERTBUF_DISCARD_SAFE(mb_geom->vbo[MB_NEXT]); - break; - } + } + } + if (mb_geom != NULL && mb_geom->use_deform) { + if (mb_step == MB_CURR) { + /* Modify batch to have data from adjacent frames. */ + GPUBatch *batch = mb_geom->batch; + for (int i = 0; i < MB_CURR; i++) { + GPUVertBuf *vbo = mb_geom->vbo[i]; + if (vbo && batch) { + if (GPU_vertbuf_get_vertex_len(vbo) != GPU_vertbuf_get_vertex_len(batch->verts[0])) { + /* Vertex count mismatch, disable deform motion blur. */ + mb_geom->use_deform = false; + } + if (mb_geom->use_deform == false) { + motion_blur_remove_vbo_reference_from_batch( + batch, mb_geom->vbo[MB_PREV], mb_geom->vbo[MB_NEXT]); + break; + } + /* Avoid adding the same vbo more than once when the batch is used by multiple + * instances. */ + if (!GPU_batch_vertbuf_has(batch, vbo)) { + /* Currently, the code assumes that all objects that share the same mesh in the + * current frame also share the same mesh on other frames. */ GPU_batch_vertbuf_add_ex(batch, vbo, false); } } } - else { - GPUVertBuf *vbo = mb_geom->vbo[mb_step]; - if (vbo) { - /* Use the vbo to perform the copy on the GPU. */ - GPU_vertbuf_use(vbo); - /* Perform a copy to avoid losing it after RE_engine_frame_set(). */ - mb_geom->vbo[mb_step] = vbo = GPU_vertbuf_duplicate(vbo); + } + else { + GPUVertBuf *vbo = mb_geom->vbo[mb_step]; + if (vbo) { + /* Use the vbo to perform the copy on the GPU. */ + GPU_vertbuf_use(vbo); + /* Perform a copy to avoid losing it after RE_engine_frame_set(). */ + GPUVertBuf **vbo_cache_ptr; + if (!BLI_ghash_ensure_p(effects->motion_blur.position_vbo_cache[mb_step], + vbo, + (void ***)&vbo_cache_ptr)) { + /* Duplicate the vbo, otherwise it would be lost when evaluating another frame. */ + GPUVertBuf *duplicated_vbo = GPU_vertbuf_duplicate(vbo); + *vbo_cache_ptr = duplicated_vbo; /* Find and replace "pos" attrib name. */ - GPUVertFormat *format = (GPUVertFormat *)GPU_vertbuf_get_format(vbo); + GPUVertFormat *format = (GPUVertFormat *)GPU_vertbuf_get_format(duplicated_vbo); int attrib_id = GPU_vertformat_attr_id_get(format, "pos"); GPU_vertformat_attr_rename(format, attrib_id, (mb_step == MB_PREV) ? "prv" : "nxt"); } - else { - /* This might happen if the object visibility has been animated. */ - mb_geom->use_deform = false; - } + mb_geom->vbo[mb_step] = vbo = *vbo_cache_ptr; } - break; - - default: - BLI_assert(0); - break; + else { + /* This might happen if the object visibility has been animated. */ + mb_geom->use_deform = false; + } + } } } } @@ -518,54 +514,62 @@ void EEVEE_motion_blur_swap_data(EEVEE_Data *vedata) /* Camera Data. */ effects->motion_blur.camera[MB_PREV] = effects->motion_blur.camera[MB_NEXT]; - /* Object Data. */ - for (BLI_ghashIterator_init(&ghi, effects->motion_blur.object); - BLI_ghashIterator_done(&ghi) == false; - BLI_ghashIterator_step(&ghi)) { - EEVEE_ObjectMotionData *mb_data = BLI_ghashIterator_getValue(&ghi); - - copy_m4_m4(mb_data->obmat[MB_PREV], mb_data->obmat[MB_NEXT]); + /* Swap #position_vbo_cache pointers. */ + if (effects->motion_blur.position_vbo_cache[MB_PREV]) { + BLI_ghash_free(effects->motion_blur.position_vbo_cache[MB_PREV], + NULL, + (GHashValFreeFP)GPU_vertbuf_discard); + } + effects->motion_blur.position_vbo_cache[MB_PREV] = + effects->motion_blur.position_vbo_cache[MB_NEXT]; + effects->motion_blur.position_vbo_cache[MB_NEXT] = NULL; + + /* Swap #hair_motion_step_cache pointers. */ + if (effects->motion_blur.hair_motion_step_cache[MB_PREV]) { + BLI_ghash_free(effects->motion_blur.hair_motion_step_cache[MB_PREV], + NULL, + (GHashValFreeFP)EEVEE_motion_hair_step_free); } + effects->motion_blur.hair_motion_step_cache[MB_PREV] = + effects->motion_blur.hair_motion_step_cache[MB_NEXT]; + effects->motion_blur.hair_motion_step_cache[MB_NEXT] = NULL; - /* Deformation Data. */ - for (BLI_ghashIterator_init(&ghi, effects->motion_blur.geom); - BLI_ghashIterator_done(&ghi) == false; + /* Rename attributes in #position_vbo_cache. */ + for (BLI_ghashIterator_init(&ghi, effects->motion_blur.position_vbo_cache[MB_PREV]); + !BLI_ghashIterator_done(&ghi); BLI_ghashIterator_step(&ghi)) { - EEVEE_GeometryMotionData *mb_geom = BLI_ghashIterator_getValue(&ghi); - EEVEE_HairMotionData *mb_hair = (EEVEE_HairMotionData *)mb_geom; + GPUVertBuf *vbo = BLI_ghashIterator_getValue(&ghi); + GPUVertFormat *format = (GPUVertFormat *)GPU_vertbuf_get_format(vbo); + int attrib_id = GPU_vertformat_attr_id_get(format, "nxt"); + GPU_vertformat_attr_rename(format, attrib_id, "prv"); + } - switch (mb_geom->type) { - case EEVEE_MOTION_DATA_HAIR: - for (int i = 0; i < mb_hair->psys_len; i++) { - GPU_VERTBUF_DISCARD_SAFE(mb_hair->psys[i].hair_pos[MB_PREV]); - DRW_TEXTURE_FREE_SAFE(mb_hair->psys[i].hair_pos_tx[MB_PREV]); - mb_hair->psys[i].hair_pos[MB_PREV] = mb_hair->psys[i].hair_pos[MB_NEXT]; - mb_hair->psys[i].hair_pos_tx[MB_PREV] = mb_hair->psys[i].hair_pos_tx[MB_NEXT]; - mb_hair->psys[i].hair_pos[MB_NEXT] = NULL; - mb_hair->psys[i].hair_pos_tx[MB_NEXT] = NULL; - } - break; + /* Object Data. */ + for (BLI_ghashIterator_init(&ghi, effects->motion_blur.object); !BLI_ghashIterator_done(&ghi); + BLI_ghashIterator_step(&ghi)) { + EEVEE_ObjectMotionData *mb_data = BLI_ghashIterator_getValue(&ghi); + EEVEE_GeometryMotionData *mb_geom = mb_data->geometry_data; + EEVEE_HairMotionData *mb_hair = mb_data->hair_data; - case EEVEE_MOTION_DATA_MESH: - if (mb_geom->batch != NULL) { - motion_blur_remove_vbo_reference_from_batch( - mb_geom->batch, mb_geom->vbo[MB_PREV], mb_geom->vbo[MB_NEXT]); - } - GPU_VERTBUF_DISCARD_SAFE(mb_geom->vbo[MB_PREV]); - mb_geom->vbo[MB_PREV] = mb_geom->vbo[MB_NEXT]; - mb_geom->vbo[MB_NEXT] = NULL; - - if (mb_geom->vbo[MB_PREV]) { - GPUVertBuf *vbo = mb_geom->vbo[MB_PREV]; - GPUVertFormat *format = (GPUVertFormat *)GPU_vertbuf_get_format(vbo); - int attrib_id = GPU_vertformat_attr_id_get(format, "nxt"); - GPU_vertformat_attr_rename(format, attrib_id, "prv"); - } - break; + copy_m4_m4(mb_data->obmat[MB_PREV], mb_data->obmat[MB_NEXT]); - default: - BLI_assert(0); - break; + if (mb_hair != NULL) { + for (int i = 0; i < mb_hair->psys_len; i++) { + mb_hair->psys[i].step_data[MB_PREV].hair_pos = + mb_hair->psys[i].step_data[MB_NEXT].hair_pos; + mb_hair->psys[i].step_data[MB_PREV].hair_pos_tx = + mb_hair->psys[i].step_data[MB_NEXT].hair_pos_tx; + mb_hair->psys[i].step_data[MB_NEXT].hair_pos = NULL; + mb_hair->psys[i].step_data[MB_NEXT].hair_pos_tx = NULL; + } + } + if (mb_geom != NULL) { + if (mb_geom->batch != NULL) { + motion_blur_remove_vbo_reference_from_batch( + mb_geom->batch, mb_geom->vbo[MB_PREV], mb_geom->vbo[MB_NEXT]); + } + mb_geom->vbo[MB_PREV] = mb_geom->vbo[MB_NEXT]; + mb_geom->vbo[MB_NEXT] = NULL; } } } diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index 766e721b1b8..336b66af393 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -633,8 +633,23 @@ enum { #define MB_CURR 2 typedef struct EEVEE_MotionBlurData { + /** + * Maps #EEVEE_ObjectKey to #EEVEE_ObjectMotionData. + */ struct GHash *object; - struct GHash *geom; + /** + * Maps original #GPUVertBuf to duplicated #GPUVertBuf. + * There are two maps for #MB_PREV and #MB_NEXT. + * Only the values are owned. + */ + struct GHash *position_vbo_cache[2]; + /** + * Maps original #GPUVertBuf to #EEVEE_HairMotionStepData. + * There are two maps for #MB_PREV and #MB_NEXT. + * Only the values are owned. + */ + struct GHash *hair_motion_step_cache[2]; + struct { float viewmat[4][4]; float persmat[4][4]; @@ -652,15 +667,16 @@ typedef struct EEVEE_ObjectKey { int id[8]; /* MAX_DUPLI_RECUR */ } EEVEE_ObjectKey; -typedef struct EEVEE_ObjectMotionData { - float obmat[3][4][4]; -} EEVEE_ObjectMotionData; - typedef enum eEEVEEMotionData { EEVEE_MOTION_DATA_MESH = 0, EEVEE_MOTION_DATA_HAIR, } eEEVEEMotionData; +typedef struct EEVEE_HairMotionStepData { + struct GPUVertBuf *hair_pos; + struct GPUTexture *hair_pos_tx; +} EEVEE_HairMotionStepData; + typedef struct EEVEE_HairMotionData { /** Needs to be first to ensure casting. */ eEEVEEMotionData type; @@ -668,8 +684,8 @@ typedef struct EEVEE_HairMotionData { /** Allocator will alloc enough slot for all particle systems. Or 1 if it's a hair object. */ int psys_len; struct { - struct GPUVertBuf *hair_pos[2]; /* Position buffer for time = t +/- step. */ - struct GPUTexture *hair_pos_tx[2]; /* Buffer Texture of the corresponding VBO. */ + /* The vbos and textures are not owned. */ + EEVEE_HairMotionStepData step_data[2]; /* Data for time = t +/- step. */ } psys[0]; } EEVEE_HairMotionData; @@ -679,10 +695,18 @@ typedef struct EEVEE_GeometryMotionData { /** To disable deform mb if vertcount mismatch. */ int use_deform; + /* The batch and vbos are not owned. */ struct GPUBatch *batch; /* Batch for time = t. */ struct GPUVertBuf *vbo[2]; /* VBO for time = t +/- step. */ } EEVEE_GeometryMotionData; +typedef struct EEVEE_ObjectMotionData { + float obmat[3][4][4]; + + EEVEE_GeometryMotionData *geometry_data; + EEVEE_HairMotionData *hair_data; +} EEVEE_ObjectMotionData; + /* ************ EFFECTS DATA ************* */ typedef enum EEVEE_EffectsFlag { @@ -1077,17 +1101,15 @@ typedef struct EEVEE_PrivateData { void EEVEE_motion_blur_data_init(EEVEE_MotionBlurData *mb); void EEVEE_motion_blur_data_free(EEVEE_MotionBlurData *mb); void EEVEE_view_layer_data_free(void *storage); +void EEVEE_motion_hair_step_free(EEVEE_HairMotionStepData *step_data); EEVEE_ViewLayerData *EEVEE_view_layer_data_get(void); EEVEE_ViewLayerData *EEVEE_view_layer_data_ensure_ex(struct ViewLayer *view_layer); EEVEE_ViewLayerData *EEVEE_view_layer_data_ensure(void); EEVEE_ObjectEngineData *EEVEE_object_data_get(Object *ob); EEVEE_ObjectEngineData *EEVEE_object_data_ensure(Object *ob); -EEVEE_ObjectMotionData *EEVEE_motion_blur_object_data_get(EEVEE_MotionBlurData *mb, - Object *ob, - bool hair); -EEVEE_GeometryMotionData *EEVEE_motion_blur_geometry_data_get(EEVEE_MotionBlurData *mb, - Object *ob); -EEVEE_HairMotionData *EEVEE_motion_blur_hair_data_get(EEVEE_MotionBlurData *mb, Object *ob); +EEVEE_ObjectMotionData *EEVEE_motion_blur_object_data_get(EEVEE_MotionBlurData *mb, Object *ob); +EEVEE_GeometryMotionData *EEVEE_motion_blur_geometry_data_get(EEVEE_ObjectMotionData *mb_data); +EEVEE_HairMotionData *EEVEE_motion_blur_hair_data_get(EEVEE_ObjectMotionData *mb_data, Object *ob); EEVEE_LightProbeEngineData *EEVEE_lightprobe_data_get(Object *ob); EEVEE_LightProbeEngineData *EEVEE_lightprobe_data_ensure(Object *ob); EEVEE_LightEngineData *EEVEE_light_data_get(Object *ob); diff --git a/source/blender/gpu/GPU_batch.h b/source/blender/gpu/GPU_batch.h index b193b45d65c..4a0411fd9da 100644 --- a/source/blender/gpu/GPU_batch.h +++ b/source/blender/gpu/GPU_batch.h @@ -135,6 +135,7 @@ int GPU_batch_instbuf_add_ex(GPUBatch *, GPUVertBuf *, bool own_vbo); * Returns the index of verts in the batch. */ int GPU_batch_vertbuf_add_ex(GPUBatch *, GPUVertBuf *, bool own_vbo); +bool GPU_batch_vertbuf_has(GPUBatch *, GPUVertBuf *); #define GPU_batch_vertbuf_add(batch, verts) GPU_batch_vertbuf_add_ex(batch, verts, false) diff --git a/source/blender/gpu/intern/gpu_batch.cc b/source/blender/gpu/intern/gpu_batch.cc index 2874dea775f..7ad2786ab22 100644 --- a/source/blender/gpu/intern/gpu_batch.cc +++ b/source/blender/gpu/intern/gpu_batch.cc @@ -207,6 +207,16 @@ int GPU_batch_vertbuf_add_ex(GPUBatch *batch, GPUVertBuf *verts, bool own_vbo) return -1; } +bool GPU_batch_vertbuf_has(GPUBatch *batch, GPUVertBuf *verts) +{ + for (uint v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) { + if (batch->verts[v] == verts) { + return true; + } + } + return false; +} + /** \} */ /* -------------------------------------------------------------------- */ -- cgit v1.2.3 From 4a7559bbcb96a71ad045404d34ba99e0bb4c528c Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 22:38:29 +1100 Subject: Cleanup: quiet warnings for FFMPEG before 5.0 Even though 5.0 has been released newer distributions wont include it, so quiet warnings. --- source/blender/blenkernel/intern/writeffmpeg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index dee852ca33a..b9d013d4756 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -960,9 +960,14 @@ static int start_ffmpeg_impl(FFMpegContext *context, break; } - /* Returns after this must 'goto fail;' */ + /* Returns after this must 'goto fail;' */ +# if LIBAVFORMAT_VERSION_MAJOR >= 59 of->oformat = fmt; +# else + /* *DEPRECATED* 2022/08/01 For FFMPEG (<5.0) remove this else branch and the `ifdef` above. */ + of->oformat = (AVOutputFormat *)fmt; +# endif if (video_codec == AV_CODEC_ID_DVVIDEO) { if (rectx != 720) { -- cgit v1.2.3 From 6bb4c0e498014f6b82401dc2dc91c8031d6f97c8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 23:59:02 +1100 Subject: GNUmakefile: rename test_depracted to check_deprecated Also move this utility into the source/tools repo. --- GNUmakefile | 10 ++-- tests/check_deprecated.py | 131 ---------------------------------------------- 2 files changed, 6 insertions(+), 135 deletions(-) delete mode 100644 tests/check_deprecated.py diff --git a/GNUmakefile b/GNUmakefile index b100bf9290e..f3c03606be8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -65,6 +65,7 @@ Static Source Code Checking * check_cppcheck: Run blender source through cppcheck (C & C++). * check_clang_array: Run blender source through clang array checking script (C & C++). + * check_deprecated: Check if there is any deprecated code to remove. * check_splint: Run blenders source through splint (C only). * check_sparse: Run blenders source through sparse (C only). * check_smatch: Run blenders source through smatch (C only). @@ -410,10 +411,6 @@ test_cmake: .FORCE @$(PYTHON) build_files/cmake/cmake_consistency_check.py > test_cmake_consistency.log 2>&1 @echo "written: test_cmake_consistency.log" -# run deprecation tests, see if we have anything to remove. -test_deprecated: .FORCE - @$(PYTHON) tests/check_deprecated.py - # ----------------------------------------------------------------------------- # Project Files @@ -491,6 +488,11 @@ check_descriptions: .FORCE @$(BLENDER_BIN) --background -noaudio --factory-startup --python \ "$(BLENDER_DIR)/source/tools/check_source/check_descriptions.py" +# run deprecation tests, see if we have anything to remove. +check_deprecated: .FORCE + @PYTHONIOENCODING=utf_8 $(PYTHON) \ + source/tools/check_source/check_deprecated.py + check_licenses: .FORCE @PYTHONIOENCODING=utf_8 $(PYTHON) \ "$(BLENDER_DIR)/source/tools/check_source/check_licenses.py" \ diff --git a/tests/check_deprecated.py b/tests/check_deprecated.py deleted file mode 100644 index 089e0275229..00000000000 --- a/tests/check_deprecated.py +++ /dev/null @@ -1,131 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - -# - -import os -from os.path import splitext - -DEPRECATE_DAYS = 120 - -SKIP_DIRS = ("extern", - "tests", # not this dir - ) - - -def is_c_header(filename): - ext = splitext(filename)[1] - return (ext in {".h", ".hpp", ".hxx", ".hh"}) - - -def is_c(filename): - ext = splitext(filename)[1] - return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl"}) - - -def is_c_any(filename): - return is_c(filename) or is_c_header(filename) - - -def is_py(filename): - ext = splitext(filename)[1] - return (ext == ".py") - - -def is_source_any(filename): - return is_c_any(filename) or is_py(filename) - - -def source_list(path, filename_check=None): - for dirpath, dirnames, filenames in os.walk(path): - # skip '.git' - dirnames[:] = [d for d in dirnames if not d.startswith(".")] - - for filename in filenames: - if filename_check is None or filename_check(filename): - yield os.path.join(dirpath, filename) - - -def deprecations(): - """ - Searches out source code for lines like - - /* *DEPRECATED* 2011/7/17 bgl.Buffer.list info text */ - - Or... - - # *DEPRECATED* 2010/12/22 some.py.func more info */ - - """ - import datetime - SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..")))) - - SKIP_DIRS_ABS = [os.path.join(SOURCE_DIR, p) for p in SKIP_DIRS] - - deprecations_ls = [] - - scan_tot = 0 - - print("scanning in %r for '*DEPRECATED* YYYY/MM/DD info'" % SOURCE_DIR) - - for fn in source_list(SOURCE_DIR, is_source_any): - # print(fn) - skip = False - for p in SKIP_DIRS_ABS: - if fn.startswith(p): - skip = True - break - if skip: - continue - - file = open(fn, 'r', encoding="utf8") - for i, l in enumerate(file): - # logic for deprecation warnings - if '*DEPRECATED*' in l: - try: - l = l.strip() - data = l.split('*DEPRECATED*', 1)[-1].strip().strip() - data = [w.strip() for w in data.split('/', 2)] - data[-1], info = data[-1].split(' ', 1) - info = info.split("*/", 1)[0] - if len(data) != 3: - print(" poorly formatting line:\n" - " %r:%d\n" - " %s" % - (fn, i + 1, l) - ) - else: - data = datetime.datetime(*tuple([int(w) for w in data])) - - deprecations_ls.append((data, (fn, i + 1), info)) - except: - print("Error file - %r:%d" % (fn, i + 1)) - import traceback - traceback.print_exc() - - scan_tot += 1 - - print(" scanned %d files" % scan_tot) - - return deprecations_ls - - -def main(): - import datetime - now = datetime.datetime.now() - - deps = deprecations() - - print("\nAll deprecations...") - for data, fileinfo, info in deps: - days_old = (now - data).days - if days_old > DEPRECATE_DAYS: - info = "*** REMOVE! *** " + info - print(" %r, days-old(%.2d), %s:%d - %s" % (data, days_old, fileinfo[0], fileinfo[1], info)) - if deps: - print("\ndone!") - else: - print("\nnone found!") - - -if __name__ == '__main__': - main() -- cgit v1.2.3 From 85b39b6be03ff9d2d0ebd7b5405fd428b7ffa3c9 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 18 Feb 2022 21:34:41 +0100 Subject: Fix subdivision surface modifier doing unnnecessary work There was accidentally some displacement related code running even when not using displacement. Differential Revision: https://developer.blender.org/D14169 --- source/blender/blenkernel/intern/subdiv_mesh.c | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index 09af77d5f09..333755e0899 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -88,6 +88,9 @@ static void subdiv_mesh_ctx_cache_custom_data_layers(SubdivMeshContext *ctx) static void subdiv_mesh_prepare_accumulator(SubdivMeshContext *ctx, int num_vertices) { + if (!ctx->have_displacement) { + return; + } ctx->accumulated_counters = MEM_calloc_arrayN( num_vertices, sizeof(*ctx->accumulated_counters), "subdiv accumulated counters"); } @@ -440,18 +443,17 @@ static void subdiv_accumulate_vertex_displacement(SubdivMeshContext *ctx, const float v, MVert *subdiv_vert) { + /* Accumulate displacement. */ Subdiv *subdiv = ctx->subdiv; const int subdiv_vertex_index = subdiv_vert - ctx->subdiv_mesh->mvert; float dummy_P[3], dPdu[3], dPdv[3], D[3]; BKE_subdiv_eval_limit_point_and_derivatives(subdiv, ptex_face_index, u, v, dummy_P, dPdu, dPdv); - /* Accumulate displacement if needed. */ - if (ctx->have_displacement) { - /* NOTE: The subdivided mesh is allocated in this module, and its vertices are kept at zero - * locations as a default calloc(). */ - BKE_subdiv_eval_displacement(subdiv, ptex_face_index, u, v, dPdu, dPdv, D); - add_v3_v3(subdiv_vert->co, D); - } + /* NOTE: The subdivided mesh is allocated in this module, and its vertices are kept at zero + * locations as a default calloc(). */ + BKE_subdiv_eval_displacement(subdiv, ptex_face_index, u, v, dPdu, dPdv, D); + add_v3_v3(subdiv_vert->co, D); + if (ctx->accumulated_counters) { ++ctx->accumulated_counters[subdiv_vertex_index]; } @@ -570,12 +572,13 @@ static void evaluate_vertex_and_apply_displacement_interpolate( add_v3_v3(subdiv_vert->co, D); } -static void subdiv_mesh_vertex_every_corner_or_edge(const SubdivForeachContext *foreach_context, - void *UNUSED(tls), - const int ptex_face_index, - const float u, - const float v, - const int subdiv_vertex_index) +static void subdiv_mesh_vertex_displacement_every_corner_or_edge( + const SubdivForeachContext *foreach_context, + void *UNUSED(tls), + const int ptex_face_index, + const float u, + const float v, + const int subdiv_vertex_index) { SubdivMeshContext *ctx = foreach_context->user_data; Mesh *subdiv_mesh = ctx->subdiv_mesh; @@ -584,31 +587,32 @@ static void subdiv_mesh_vertex_every_corner_or_edge(const SubdivForeachContext * subdiv_accumulate_vertex_displacement(ctx, ptex_face_index, u, v, subdiv_vert); } -static void subdiv_mesh_vertex_every_corner(const SubdivForeachContext *foreach_context, - void *tls, - const int ptex_face_index, - const float u, - const float v, - const int UNUSED(coarse_vertex_index), - const int UNUSED(coarse_poly_index), - const int UNUSED(coarse_corner), - const int subdiv_vertex_index) +static void subdiv_mesh_vertex_displacement_every_corner( + const SubdivForeachContext *foreach_context, + void *tls, + const int ptex_face_index, + const float u, + const float v, + const int UNUSED(coarse_vertex_index), + const int UNUSED(coarse_poly_index), + const int UNUSED(coarse_corner), + const int subdiv_vertex_index) { - subdiv_mesh_vertex_every_corner_or_edge( + subdiv_mesh_vertex_displacement_every_corner_or_edge( foreach_context, tls, ptex_face_index, u, v, subdiv_vertex_index); } -static void subdiv_mesh_vertex_every_edge(const SubdivForeachContext *foreach_context, - void *tls, - const int ptex_face_index, - const float u, - const float v, - const int UNUSED(coarse_edge_index), - const int UNUSED(coarse_poly_index), - const int UNUSED(coarse_corner), - const int subdiv_vertex_index) +static void subdiv_mesh_vertex_displacement_every_edge(const SubdivForeachContext *foreach_context, + void *tls, + const int ptex_face_index, + const float u, + const float v, + const int UNUSED(coarse_edge_index), + const int UNUSED(coarse_poly_index), + const int UNUSED(coarse_corner), + const int subdiv_vertex_index) { - subdiv_mesh_vertex_every_corner_or_edge( + subdiv_mesh_vertex_displacement_every_corner_or_edge( foreach_context, tls, ptex_face_index, u, v, subdiv_vertex_index); } @@ -1094,12 +1098,8 @@ static void setup_foreach_callbacks(const SubdivMeshContext *subdiv_context, foreach_context->topology_info = subdiv_mesh_topology_info; /* Every boundary geometry. Used for displacement averaging. */ if (subdiv_context->have_displacement) { - foreach_context->vertex_every_corner = subdiv_mesh_vertex_every_corner; - foreach_context->vertex_every_edge = subdiv_mesh_vertex_every_edge; - } - else { - foreach_context->vertex_every_corner = subdiv_mesh_vertex_every_corner; - foreach_context->vertex_every_edge = subdiv_mesh_vertex_every_edge; + foreach_context->vertex_every_corner = subdiv_mesh_vertex_displacement_every_corner; + foreach_context->vertex_every_edge = subdiv_mesh_vertex_displacement_every_edge; } foreach_context->vertex_corner = subdiv_mesh_vertex_corner; foreach_context->vertex_edge = subdiv_mesh_vertex_edge; -- cgit v1.2.3 From 66f3545a0b4f76e9355e58f5b1881b3ba201d4dd Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 22 Feb 2022 13:43:28 +0100 Subject: Cleanup: compiler warning --- intern/cycles/device/cpu/device_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/device/cpu/device_impl.cpp b/intern/cycles/device/cpu/device_impl.cpp index 0ca24c241f0..ada1f8280a9 100644 --- a/intern/cycles/device/cpu/device_impl.cpp +++ b/intern/cycles/device/cpu/device_impl.cpp @@ -204,7 +204,7 @@ device_ptr CPUDevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_ void CPUDevice::const_copy_to(const char *name, void *host, size_t size) { -#if WITH_EMBREE +#ifdef WITH_EMBREE if (strcmp(name, "__data") == 0) { assert(size <= sizeof(KernelData)); -- cgit v1.2.3 From 9d8af55eb8daed365cc3360dc0e122ba91a9b9a6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 23 Feb 2022 00:05:34 +1100 Subject: GNUmakefile: missed removing docs for test_deprecated when renaming --- GNUmakefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index f3c03606be8..c08bde5c474 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -57,8 +57,6 @@ Testing Targets * test_pep8: Checks all python script are pep8 which are tagged to use the stricter formatting - * test_deprecated: - Checks for deprecation tags in our code which may need to be removed Static Source Code Checking Not associated with building Blender. -- cgit v1.2.3 From 1d4037645fb5467f71258883b12dc734532cde44 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 22 Feb 2022 22:38:29 +1100 Subject: Cleanup: quiet warnings for FFMPEG before 5.0 Even though 5.0 has been released newer distributions wont include it, so quiet warnings. --- source/blender/blenkernel/intern/writeffmpeg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 45bd977c109..36b3dedeeb0 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -974,9 +974,14 @@ static int start_ffmpeg_impl(FFMpegContext *context, break; } - /* Returns after this must 'goto fail;' */ + /* Returns after this must 'goto fail;' */ +# if LIBAVFORMAT_VERSION_MAJOR >= 59 of->oformat = fmt; +# else + /* *DEPRECATED* 2022/08/01 For FFMPEG (<5.0) remove this else branch and the `ifdef` above. */ + of->oformat = (AVOutputFormat *)fmt; +# endif if (video_codec == AV_CODEC_ID_DVVIDEO) { if (rectx != 720) { -- cgit v1.2.3 From f449b8968819da3669a5a8a87ac0f5822b785a15 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 22 Feb 2022 15:18:08 +0100 Subject: Cleanup: Make `RNA_property_editable` logic clearer. Split code in smaller bits with early returns, instead of a giant single set of checks. No behavioral change expected here. --- source/blender/makesrna/intern/rna_access.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 6c3b46c4408..5a43fc2b053 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1907,10 +1907,27 @@ bool RNA_property_editable(PointerRNA *ptr, PropertyRNA *prop_orig) PropertyRNA *prop = rna_ensure_property(prop_orig); flag = prop->editable ? prop->editable(ptr, &dummy_info) : prop->flag; - return ( - (flag & PROP_EDITABLE) && (flag & PROP_REGISTER) == 0 && - (!id || ((!ID_IS_LINKED(id) || (prop->flag & PROP_LIB_EXCEPTION)) && - (!ID_IS_OVERRIDE_LIBRARY(id) || RNA_property_overridable_get(ptr, prop_orig))))); + /* Early return if the property itself is not editable. */ + if ((flag & PROP_EDITABLE) == 0 || (flag & PROP_REGISTER) != 0) { + return false; + } + + /* If there is no owning ID, the property is editable at this point. */ + if (id == NULL) { + return true; + } + + /* Handle linked or liboverride ID cases. */ + const bool is_linked_prop_exception = (prop->flag & PROP_LIB_EXCEPTION) != 0; + if (ID_IS_LINKED(id)) { + return is_linked_prop_exception; + } + if (ID_IS_OVERRIDE_LIBRARY(id)) { + return RNA_property_overridable_get(ptr, prop_orig); + } + + /* At this point, property is owned by a local ID and therefore fully editable. */ + return true; } bool RNA_property_editable_info(PointerRNA *ptr, PropertyRNA *prop, const char **r_info) -- cgit v1.2.3 From 37fb69e02033b59895173aa2f47858ff3923afad Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 22 Feb 2022 15:33:52 +0100 Subject: Fix (unreported) `RNA_property_editable_index`. NOTE: This function is currently unused. However, it does use a callback defined by a few RNA properties through `RNA_def_property_editable_array_func`, so don't think it should be removed without further thinking. This function had two main issues: * It was doing bitwise AND on potentially three sources of property flag, when actually used `RNA_property_editable` just use one source ever. * It was completely ignoring liboverride cases. TODO: Deduplicate code between `RNA_property_editable`, `RNA_property_editable_info` and `RNA_property_editable_index`. --- source/blender/makesrna/intern/rna_access.c | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 5a43fc2b053..59acbc81afd 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1980,29 +1980,40 @@ bool RNA_property_editable_flag(PointerRNA *ptr, PropertyRNA *prop) return (flag & PROP_EDITABLE) != 0; } -bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop, int index) +bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop_orig, int index) { - ID *id; - int flag; - BLI_assert(index >= 0); - prop = rna_ensure_property(prop); + ID *id = ptr->owner_id; + const char *dummy_info; + + PropertyRNA *prop = rna_ensure_property(prop_orig); - flag = prop->flag; + const int flag = prop->itemeditable != NULL ? + prop->itemeditable(ptr, index) : + (prop->editable != NULL ? prop->editable(ptr, &dummy_info) : prop->flag); - if (prop->editable) { - const char *dummy_info; - flag &= prop->editable(ptr, &dummy_info); + /* Early return if the property itself is not editable. */ + if ((flag & PROP_EDITABLE) == 0 || (flag & PROP_REGISTER) != 0) { + return false; } - if (prop->itemeditable) { - flag &= prop->itemeditable(ptr, index); + /* If there is no owning ID, the property is editable at this point. */ + if (id == NULL) { + return true; } - id = ptr->owner_id; + /* Handle linked or liboverride ID cases. */ + const bool is_linked_prop_exception = (prop->flag & PROP_LIB_EXCEPTION) != 0; + if (ID_IS_LINKED(id)) { + return is_linked_prop_exception; + } + if (ID_IS_OVERRIDE_LIBRARY(id)) { + return RNA_property_overridable_get(ptr, prop_orig); + } - return (flag & PROP_EDITABLE) && (!id || !ID_IS_LINKED(id) || (prop->flag & PROP_LIB_EXCEPTION)); + /* At this point, property is owned by a local ID and therefore fully editable. */ + return true; } bool RNA_property_animateable(PointerRNA *ptr, PropertyRNA *prop) -- cgit v1.2.3 From ca991e3012854ce53d8c78c537910d3f858a9d28 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 11 Feb 2022 11:10:53 +0100 Subject: Fix compilation error on certain platforms Atomic operations performed by the C++ standard library might require libatomic on platforms which do not have hardware support for those operations. This change makes it that such configurations are automatically detected and -latomic is added when needed. Differential Revision: https://developer.blender.org/D14106 --- build_files/cmake/platform/platform_unix.cmake | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/build_files/cmake/platform/platform_unix.cmake b/build_files/cmake/platform/platform_unix.cmake index 95d0e4de380..10f636296b7 100644 --- a/build_files/cmake/platform/platform_unix.cmake +++ b/build_files/cmake/platform/platform_unix.cmake @@ -866,3 +866,45 @@ if(WITH_COMPILER_CCACHE) set(WITH_COMPILER_CCACHE OFF) endif() endif() + +# On some platforms certain atomic operations are not possible with assembly and/or intrinsics and +# they are emulated in software with locks. For example, on armel there is no intrinsics to grant +# 64 bit atomic operations and STL library uses libatomic to offload software emulation of atomics +# to. +# This function will check whether libatomic is required and if so will configure linker flags. +# If atomic operations are possible without libatomic then linker flags are left as-is. +function(CONFIGURE_ATOMIC_LIB_IF_NEEDED) + # Source which is used to enforce situation when software emulation of atomics is required. + # Assume that using 64bit integer gives a definitive asnwer (as in, if 64bit atomic operations + # are possible using assembly/intrinsics 8, 16, and 32 bit operations will also be possible. + set(_source + "#include + #include + int main(int argc, char **argv) { + std::atomic uint64; uint64++; + return 0; + }") + + include(CheckCXXSourceCompiles) + check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITHOUT_LIBATOMIC) + + if(NOT ATOMIC_OPS_WITHOUT_LIBATOMIC) + # Compilation of the test program has failed. + # Try it again with -latomic to see if this is what is needed, or whether something else is + # going on. + + set(CMAKE_REQUIRED_LIBRARIES atomic) + check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITH_LIBATOMIC) + + if(ATOMIC_OPS_WITH_LIBATOMIC) + set(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -latomic" PARENT_SCOPE) + else() + # Atomic operations are required part of Blender and it is not possible to process forward. + # We expect that either standard library or libatomic will make atomics to work. If both + # cases has failed something fishy o na bigger scope is going on. + message(FATAL_ERROR "Failed to detect required configuration for atomic operations") + endif() + endif() +endfunction() + +CONFIGURE_ATOMIC_LIB_IF_NEEDED() -- cgit v1.2.3 From 51c7193405873a0d90596f3683686bbf1603fc1f Mon Sep 17 00:00:00 2001 From: Stefan Werner Date: Tue, 22 Feb 2022 16:34:52 +0100 Subject: Cycles: Removed numaapi from standalone --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 890969f6724..bbddaff8480 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1886,7 +1886,6 @@ elseif(WITH_CYCLES_STANDALONE) add_subdirectory(intern/glew-mx) add_subdirectory(intern/guardedalloc) add_subdirectory(intern/libc_compat) - add_subdirectory(intern/numaapi) add_subdirectory(intern/sky) add_subdirectory(intern/cycles) -- cgit v1.2.3 From 8073c95fe9d47bc42ba390f61da35062ffd6398a Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 22 Feb 2022 16:36:31 +0100 Subject: Cleanup: Deduplicate logic in `RNA_property_editable` & co. Fairly straight-forward, now all the logic for all those 'is editable' complex checks is gathered into a single util function. --- source/blender/makesrna/RNA_access.h | 2 +- source/blender/makesrna/intern/rna_access.c | 108 +++++++++------------------- 2 files changed, 34 insertions(+), 76 deletions(-) diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index eb25733a88a..bc4e7314512 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -997,7 +997,7 @@ bool RNA_property_editable_info(PointerRNA *ptr, PropertyRNA *prop, const char * /** * Same as RNA_property_editable(), except this checks individual items in an array. */ -bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop, int index); +bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop, const int index); /** * Without lib check, only checks the flag. diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 59acbc81afd..c8f710d9931 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1898,17 +1898,28 @@ int RNA_property_ui_icon(const PropertyRNA *prop) return rna_ensure_property((PropertyRNA *)prop)->icon; } -bool RNA_property_editable(PointerRNA *ptr, PropertyRNA *prop_orig) +static bool rna_property_editable_do(PointerRNA *ptr, + PropertyRNA *prop_orig, + const int index, + const char **r_info) { ID *id = ptr->owner_id; - int flag; - const char *dummy_info; PropertyRNA *prop = rna_ensure_property(prop_orig); - flag = prop->editable ? prop->editable(ptr, &dummy_info) : prop->flag; + + const char *info = ""; + const int flag = (prop->itemeditable != NULL && index >= 0) ? + prop->itemeditable(ptr, index) : + (prop->editable != NULL ? prop->editable(ptr, &info) : prop->flag); + if (r_info != NULL) { + *r_info = info; + } /* Early return if the property itself is not editable. */ if ((flag & PROP_EDITABLE) == 0 || (flag & PROP_REGISTER) != 0) { + if (r_info != NULL && (*r_info)[0] == '\0') { + *r_info = N_("This property is for internal use only and can't be edited"); + } return false; } @@ -1919,55 +1930,31 @@ bool RNA_property_editable(PointerRNA *ptr, PropertyRNA *prop_orig) /* Handle linked or liboverride ID cases. */ const bool is_linked_prop_exception = (prop->flag & PROP_LIB_EXCEPTION) != 0; - if (ID_IS_LINKED(id)) { - return is_linked_prop_exception; + if (ID_IS_LINKED(id) && !is_linked_prop_exception) { + if (r_info != NULL && (*r_info)[0] == '\0') { + *r_info = N_("Can't edit this property from a linked data-block"); + } + return false; } - if (ID_IS_OVERRIDE_LIBRARY(id)) { - return RNA_property_overridable_get(ptr, prop_orig); + if (ID_IS_OVERRIDE_LIBRARY(id) && !RNA_property_overridable_get(ptr, prop_orig)) { + if (r_info != NULL && (*r_info)[0] == '\0') { + *r_info = N_("Can't edit this property from an override data-block"); + } + return false; } /* At this point, property is owned by a local ID and therefore fully editable. */ return true; } -bool RNA_property_editable_info(PointerRNA *ptr, PropertyRNA *prop, const char **r_info) +bool RNA_property_editable(PointerRNA *ptr, PropertyRNA *prop) { - ID *id = ptr->owner_id; - int flag; - - PropertyRNA *prop_type = rna_ensure_property(prop); - *r_info = ""; - - /* get flag */ - if (prop_type->editable) { - flag = prop_type->editable(ptr, r_info); - } - else { - flag = prop_type->flag; - if ((flag & PROP_EDITABLE) == 0 || (flag & PROP_REGISTER)) { - *r_info = N_("This property is for internal use only and can't be edited"); - } - } - - /* property from linked data-block */ - if (id) { - if (ID_IS_LINKED(id) && (prop_type->flag & PROP_LIB_EXCEPTION) == 0) { - if (!(*r_info)[0]) { - *r_info = N_("Can't edit this property from a linked data-block"); - } - return false; - } - if (ID_IS_OVERRIDE_LIBRARY(id)) { - if (!RNA_property_overridable_get(ptr, prop)) { - if (!(*r_info)[0]) { - *r_info = N_("Can't edit this property from an override data-block"); - } - return false; - } - } - } + return rna_property_editable_do(ptr, prop, -1, NULL); +} - return ((flag & PROP_EDITABLE) && (flag & PROP_REGISTER) == 0); +bool RNA_property_editable_info(PointerRNA *ptr, PropertyRNA *prop, const char **r_info) +{ + return rna_property_editable_do(ptr, prop, -1, r_info); } bool RNA_property_editable_flag(PointerRNA *ptr, PropertyRNA *prop) @@ -1980,40 +1967,11 @@ bool RNA_property_editable_flag(PointerRNA *ptr, PropertyRNA *prop) return (flag & PROP_EDITABLE) != 0; } -bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop_orig, int index) +bool RNA_property_editable_index(PointerRNA *ptr, PropertyRNA *prop, const int index) { BLI_assert(index >= 0); - ID *id = ptr->owner_id; - const char *dummy_info; - - PropertyRNA *prop = rna_ensure_property(prop_orig); - - const int flag = prop->itemeditable != NULL ? - prop->itemeditable(ptr, index) : - (prop->editable != NULL ? prop->editable(ptr, &dummy_info) : prop->flag); - - /* Early return if the property itself is not editable. */ - if ((flag & PROP_EDITABLE) == 0 || (flag & PROP_REGISTER) != 0) { - return false; - } - - /* If there is no owning ID, the property is editable at this point. */ - if (id == NULL) { - return true; - } - - /* Handle linked or liboverride ID cases. */ - const bool is_linked_prop_exception = (prop->flag & PROP_LIB_EXCEPTION) != 0; - if (ID_IS_LINKED(id)) { - return is_linked_prop_exception; - } - if (ID_IS_OVERRIDE_LIBRARY(id)) { - return RNA_property_overridable_get(ptr, prop_orig); - } - - /* At this point, property is owned by a local ID and therefore fully editable. */ - return true; + return rna_property_editable_do(ptr, prop, index, NULL); } bool RNA_property_animateable(PointerRNA *ptr, PropertyRNA *prop) -- cgit v1.2.3 From 353fe610ed2d31bda2da93f59a174fe1642d96ad Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 22 Feb 2022 11:14:50 -0500 Subject: Fix: Clear mesh runtime cache when adding elements Currently the RNA functions to add mesh elements like vertices don't clear the runtime cache of things like triangulation, BVH trees, etc. This is important, since they might be accessed with incorrect sizes. This is split from a fix for T95839. --- source/blender/editors/mesh/mesh_data.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/blender/editors/mesh/mesh_data.c b/source/blender/editors/mesh/mesh_data.c index 0f58752f323..7a089d7101e 100644 --- a/source/blender/editors/mesh/mesh_data.c +++ b/source/blender/editors/mesh/mesh_data.c @@ -37,6 +37,7 @@ #include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" #include "BKE_report.h" #include "DEG_depsgraph.h" @@ -1126,6 +1127,8 @@ static void mesh_add_verts(Mesh *mesh, int len) mesh->vdata = vdata; BKE_mesh_update_customdata_pointers(mesh, false); + BKE_mesh_runtime_clear_cache(mesh); + /* scan the input list and insert the new vertices */ /* set default flags */ @@ -1162,6 +1165,8 @@ static void mesh_add_edges(Mesh *mesh, int len) mesh->edata = edata; BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */ + BKE_mesh_runtime_clear_cache(mesh); + /* set default flags */ medge = &mesh->medge[mesh->totedge]; for (i = 0; i < len; i++, medge++) { @@ -1190,6 +1195,8 @@ static void mesh_add_loops(Mesh *mesh, int len) CustomData_add_layer(&ldata, CD_MLOOP, CD_CALLOC, NULL, totloop); } + BKE_mesh_runtime_clear_cache(mesh); + CustomData_free(&mesh->ldata, mesh->totloop); mesh->ldata = ldata; BKE_mesh_update_customdata_pointers(mesh, true); @@ -1221,6 +1228,8 @@ static void mesh_add_polys(Mesh *mesh, int len) mesh->pdata = pdata; BKE_mesh_update_customdata_pointers(mesh, true); + BKE_mesh_runtime_clear_cache(mesh); + /* set default flags */ mpoly = &mesh->mpoly[mesh->totpoly]; for (i = 0; i < len; i++, mpoly++) { -- cgit v1.2.3 From 2f951667c0b95f1c3d9c9ab1fe0f74becd80c78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Tue, 22 Feb 2022 17:29:05 +0100 Subject: OCIO: Fix bool1 vs. bool comparison warning. Fixes the following warning: `'!=': unsafe mix of type 'bool1' and type 'bool' in operation` --- intern/opencolorio/ocio_impl_glsl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index 47c965dbc8e..c5b0c597abd 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -506,11 +506,11 @@ static void updateGPUDisplayParameters(OCIO_GPUShader &shader, data.dither = dither; do_update = true; } - if (data.use_predivide != use_predivide) { + if (bool(data.use_predivide) != use_predivide) { data.use_predivide = use_predivide; do_update = true; } - if (data.use_overlay != use_overlay) { + if (bool(data.use_overlay) != use_overlay) { data.use_overlay = use_overlay; do_update = true; } -- cgit v1.2.3 From 283a4cd40e6d07a55027298d693d9fba9a8a6f30 Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Tue, 22 Feb 2022 17:30:52 +0100 Subject: Fix ffmpeg tests when using ffmpeg 5.0 --- intern/ffmpeg/tests/ffmpeg_codecs.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/intern/ffmpeg/tests/ffmpeg_codecs.cc b/intern/ffmpeg/tests/ffmpeg_codecs.cc index 9538bac84d2..157731bf1f2 100644 --- a/intern/ffmpeg/tests/ffmpeg_codecs.cc +++ b/intern/ffmpeg/tests/ffmpeg_codecs.cc @@ -2,12 +2,13 @@ extern "C" { #include +#include #include } namespace { -bool test_vcodec(AVCodec *codec, AVPixelFormat pixelformat) +bool test_vcodec(const AVCodec *codec, AVPixelFormat pixelformat) { av_log_set_level(AV_LOG_QUIET); bool result = false; @@ -28,7 +29,7 @@ bool test_vcodec(AVCodec *codec, AVPixelFormat pixelformat) } return result; } -bool test_acodec(AVCodec *codec, AVSampleFormat fmt) +bool test_acodec(const AVCodec *codec, AVSampleFormat fmt) { av_log_set_level(AV_LOG_QUIET); bool result = false; @@ -52,7 +53,7 @@ bool test_acodec(AVCodec *codec, AVSampleFormat fmt) bool test_codec_video_by_codecid(AVCodecID codec_id, AVPixelFormat pixelformat) { bool result = false; - AVCodec *codec = avcodec_find_encoder(codec_id); + const AVCodec *codec = avcodec_find_encoder(codec_id); if (codec) result = test_vcodec(codec, pixelformat); return result; @@ -61,7 +62,7 @@ bool test_codec_video_by_codecid(AVCodecID codec_id, AVPixelFormat pixelformat) bool test_codec_video_by_name(const char *codecname, AVPixelFormat pixelformat) { bool result = false; - AVCodec *codec = avcodec_find_encoder_by_name(codecname); + const AVCodec *codec = avcodec_find_encoder_by_name(codecname); if (codec) result = test_vcodec(codec, pixelformat); return result; @@ -70,7 +71,7 @@ bool test_codec_video_by_name(const char *codecname, AVPixelFormat pixelformat) bool test_codec_audio_by_codecid(AVCodecID codec_id, AVSampleFormat fmt) { bool result = false; - AVCodec *codec = avcodec_find_encoder(codec_id); + const AVCodec *codec = avcodec_find_encoder(codec_id); if (codec) result = test_acodec(codec, fmt); return result; @@ -79,7 +80,7 @@ bool test_codec_audio_by_codecid(AVCodecID codec_id, AVSampleFormat fmt) bool test_codec_audio_by_name(const char *codecname, AVSampleFormat fmt) { bool result = false; - AVCodec *codec = avcodec_find_encoder_by_name(codecname); + const AVCodec *codec = avcodec_find_encoder_by_name(codecname); if (codec) result = test_acodec(codec, fmt); return result; -- cgit v1.2.3 From 29696fb7252b88ecda0a5e724bb333b545e77c1e Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Tue, 22 Feb 2022 08:57:22 -0800 Subject: UI: ActionZone Swap Areas Between Windows Allow SCREEN_OT_area_swap to operate between different Blender windows, and other minor feedback improvements. See D14135 for more details and demonstrations. Differential Revision: https://developer.blender.org/D14135 Reviewed by Campbell Barton --- source/blender/editors/include/ED_screen.h | 6 ++++++ source/blender/editors/screen/area.c | 32 ++++++++++++++++++++++++++++++ source/blender/editors/screen/screen_ops.c | 21 ++++++++++++++------ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 5b439cb0978..c89df9c3789 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -250,6 +250,12 @@ void ED_area_offscreen_free(struct wmWindowManager *wm, struct wmWindow *win, struct ScrArea *area); +/** + * Search all screens, even non-active or overlapping (multiple windows), return the most-likely + * area of interest. xy is relative to active window, like all similar functions. + */ +ScrArea *ED_area_find_under_cursor(const struct bContext *C, int spacetype, const int xy[2]); + ScrArea *ED_screen_areas_iter_first(const struct wmWindow *win, const bScreen *screen); ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area); /** diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index 95534d2a036..a64948b5864 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -3438,6 +3438,38 @@ bool ED_area_is_global(const ScrArea *area) return area->global != NULL; } +ScrArea *ED_area_find_under_cursor(const bContext *C, int spacetype, const int xy[2]) +{ + bScreen *screen = CTX_wm_screen(C); + wmWindow *win = CTX_wm_window(C); + wmWindowManager *wm = CTX_wm_manager(C); + + ScrArea *area = NULL; + + if (win->parent) { + /* If active window is a child, check itself first. */ + area = BKE_screen_find_area_xy(screen, spacetype, xy); + } + + if (!area) { + /* Check all windows except the active one. */ + int scr_pos[2]; + wmWindow *r_win = WM_window_find_under_cursor(wm, win, win, xy, scr_pos); + if (r_win) { + win = r_win; + screen = WM_window_get_active_screen(win); + area = BKE_screen_find_area_xy(screen, spacetype, scr_pos); + } + } + + if (!area && !win->parent) { + /* If active window is a parent window, check itself last. */ + area = BKE_screen_find_area_xy(screen, spacetype, xy); + } + + return area; +} + ScrArea *ED_screen_areas_iter_first(const wmWindow *win, const bScreen *screen) { ScrArea *global_area = win->global_areas.areabase.first; diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 75c704b5f7b..b912b02852f 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -1008,9 +1008,6 @@ static void actionzone_exit(wmOperator *op) static void actionzone_apply(bContext *C, wmOperator *op, int type) { wmWindow *win = CTX_wm_window(C); - sActionzoneData *sad = op->customdata; - - sad->modifier = RNA_int_get(op->ptr, "modifier"); wmEvent event; wm_event_init_from_window(win, &event); @@ -1051,6 +1048,7 @@ static int actionzone_invoke(bContext *C, wmOperator *op, const wmEvent *event) sad->az = az; sad->x = event->xy[0]; sad->y = event->xy[1]; + sad->modifier = RNA_int_get(op->ptr, "modifier"); /* region azone directly reacts on mouse clicks */ if (ELEM(sad->az->type, AZONE_REGION, AZONE_FULLSCREEN)) { @@ -1114,7 +1112,17 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) /* What area are we now in? */ ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy); - if (area == sad->sa1) { + if (sad->modifier == 1) { + /* Duplicate area into new window. */ + WM_cursor_set(win, WM_CURSOR_EDIT); + is_gesture = (delta_max > area_threshold); + } + else if (sad->modifier == 2) { + /* Swap areas. */ + WM_cursor_set(win, WM_CURSOR_SWAP_AREA); + is_gesture = true; + } + else if (area == sad->sa1) { /* Same area, so possible split. */ WM_cursor_set(win, SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT : @@ -1320,8 +1328,9 @@ static int area_swap_modal(bContext *C, wmOperator *op, const wmEvent *event) switch (event->type) { case MOUSEMOVE: - /* second area, for join */ - sad->sa2 = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->xy); + /* Second area to swap with. */ + sad->sa2 = ED_area_find_under_cursor(C, SPACE_TYPE_ANY, event->xy); + WM_cursor_set(CTX_wm_window(C), (sad->sa2) ? WM_CURSOR_SWAP_AREA : WM_CURSOR_STOP); break; case LEFTMOUSE: /* release LMB */ if (event->val == KM_RELEASE) { -- cgit v1.2.3 From ee9949a85f77ec3bec9e40978770e6e7a36cd457 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 22 Feb 2022 18:01:52 +0100 Subject: Fix (unreported) LibOverride: missing copying `flag` member. --- source/blender/blenkernel/intern/lib_override.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 649e7dab5d1..92003f0bb25 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -161,6 +161,8 @@ void BKE_lib_override_library_copy(ID *dst_id, const ID *src_id, const bool do_f (ID *)src_id; id_us_plus(dst_id->override_library->reference); + dst_id->override_library->flag = src_id->override_library->flag; + if (do_full_copy) { BLI_duplicatelist(&dst_id->override_library->properties, &src_id->override_library->properties); -- cgit v1.2.3 From 59343ee1627f4c369e237cea201015b979da1540 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 22 Feb 2022 12:43:53 -0500 Subject: Fix T95839: Data race when lazily creating mesh normal layers Currently, when normals are calculated for a const mesh, a custom data layer might be added if it doesn't already exist. Adding a custom data layer to a mesh is not thread-safe, so this can be a problem in some situations. This commit moves derived mesh normals for polygons and vertices out of `CustomData` to `Mesh_Runtime`. Most of the hard work for this was already done by rBcfa53e0fbeed7178. Some changes to logic elsewhere are necessary/helpful: - No need to call both `BKE_mesh_runtime_clear_cache` and `BKE_mesh_normals_tag_dirty`, since the former also does the latter. - Cleanup/simplify mesh conversion and copying since normals are handled with other runtime data. Storing these normals like other runtime data clarifies their status as derived data, meaning custom data moves more towards storing original/editable data. This means normals won't automatically benefit from the planned copy-on-write refactor (T95845), so it will have to be added manually like for the other runtime data. Differential Revision: https://developer.blender.org/D14154 --- source/blender/blenkernel/BKE_mesh.h | 11 ++++ source/blender/blenkernel/intern/customdata.cc | 10 ++-- source/blender/blenkernel/intern/mesh.cc | 21 ++++--- source/blender/blenkernel/intern/mesh_convert.cc | 11 +--- source/blender/blenkernel/intern/mesh_normals.cc | 67 ++++++++++++++-------- source/blender/blenkernel/intern/mesh_runtime.c | 6 ++ source/blender/bmesh/intern/bmesh_mesh_convert.cc | 4 +- source/blender/makesdna/DNA_customdata_types.h | 4 ++ source/blender/makesdna/DNA_mesh_types.h | 13 ++++- .../nodes/geometry/nodes/node_geo_extrude_mesh.cc | 4 -- 10 files changed, 95 insertions(+), 56 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 2b32c6a5420..72a1303fc6b 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -427,6 +427,17 @@ float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3]; */ float (*BKE_mesh_poly_normals_for_write(struct Mesh *mesh))[3]; +/** + * Free any cached vertex or poly normals. Face corner (loop) normals are also derived data, + * but are not handled with the same method yet, so they are not included. It's important that this + * is called after the mesh changes size, since otherwise cached normal arrays might not be large + * enough (though it may be called indirectly by other functions). + * + * \note Normally it's preferred to call #BKE_mesh_normals_tag_dirty instead, + * but this can be used in specific situations to reset a mesh or reduce memory usage. + */ +void BKE_mesh_clear_derived_normals(struct Mesh *mesh); + /** * Mark the mesh's vertex normals non-dirty, for when they are calculated or assigned manually. */ diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index e4c18325d76..0935a902c3a 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -2000,10 +2000,10 @@ const CustomData_MeshMasks CD_MASK_FACECORNERS = { CD_MASK_NORMAL | CD_MASK_MLOOPTANGENT), }; const CustomData_MeshMasks CD_MASK_EVERYTHING = { - /* vmask */ (CD_MASK_MVERT | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | - CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_ORCO | - CD_MASK_CLOTH_ORCO | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | - CD_MASK_PAINT_MASK | CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* vmask */ (CD_MASK_MVERT | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | + CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | + CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_PAINT_MASK | + CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR | CD_MASK_CREASE), /* emask */ (CD_MASK_MEDGE | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_BWEIGHT | CD_MASK_CREASE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), @@ -2012,7 +2012,7 @@ const CustomData_MeshMasks CD_MASK_EVERYTHING = { CD_MASK_ORIGSPACE | CD_MASK_TANGENT | CD_MASK_TESSLOOPNORMAL | CD_MASK_PREVIEW_MCOL | CD_MASK_PROP_ALL), /* pmask */ - (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | CD_MASK_FACEMAP | + (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_PROP_ALL | CD_MASK_SCULPT_FACE_SETS), /* lmask */ (CD_MASK_MLOOP | CD_MASK_BM_ELEM_PYPTR | CD_MASK_MDISPS | CD_MASK_NORMAL | CD_MASK_MLOOPUV | diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 351535a6f78..09d9b19330a 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -75,6 +75,8 @@ #include "BLO_read_write.h" +using blender::float3; + static void mesh_clear_geometry(Mesh *mesh); static void mesh_tessface_clear_intern(Mesh *mesh, int free_customdata); @@ -1111,16 +1113,6 @@ Mesh *BKE_mesh_new_nomain_from_template_ex(const Mesh *me_src, mesh_tessface_clear_intern(me_dst, false); } - me_dst->runtime.cd_dirty_poly = me_src->runtime.cd_dirty_poly; - me_dst->runtime.cd_dirty_vert = me_src->runtime.cd_dirty_vert; - - /* Ensure that when no normal layers exist, they are marked dirty, because - * normals might not have been included in the mask of copied layers. */ - if (!CustomData_has_layer(&me_dst->vdata, CD_NORMAL) || - !CustomData_has_layer(&me_dst->pdata, CD_NORMAL)) { - BKE_mesh_normals_tag_dirty(me_dst); - } - /* The destination mesh should at least have valid primary CD layers, * even in cases where the source mesh does not. */ mesh_ensure_cdlayers_primary(me_dst, do_tessface); @@ -2150,6 +2142,10 @@ static void split_faces_split_new_verts(Mesh *mesh, MVert *mvert = mesh->mvert; float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(mesh); + /* Normals were already calculated at the beginning of this operation, we rely on that to update + * them partially here. */ + BLI_assert(!BKE_mesh_vertex_normals_are_dirty(mesh)); + /* Remember new_verts is a single linklist, so its items are in reversed order... */ MVert *new_mv = &mvert[mesh->totvert - 1]; for (int i = mesh->totvert - 1; i >= verts_len; i--, new_mv--, new_verts = new_verts->next) { @@ -2160,7 +2156,6 @@ static void split_faces_split_new_verts(Mesh *mesh, copy_v3_v3(vert_normals[i], new_verts->vnor); } } - BKE_mesh_vertex_normals_clear_dirty(mesh); } /* Perform actual split of edges. */ @@ -2231,6 +2226,10 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals) /* Update pointers to a newly allocated memory. */ BKE_mesh_update_customdata_pointers(mesh, false); + /* Update normals manually to avoid recalculation after this operation. */ + mesh->runtime.vert_normals = (float(*)[3])MEM_reallocN(mesh->runtime.vert_normals, + sizeof(float[3]) * mesh->totvert); + /* Perform actual split of vertices and edges. */ split_faces_split_new_verts(mesh, new_verts, num_new_verts); if (do_edges) { diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 3562f6c6b17..1db17c950b3 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -1495,15 +1495,8 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, tmp.cd_flag = mesh_src->cd_flag; tmp.runtime.deformed_only = mesh_src->runtime.deformed_only; - tmp.runtime.cd_dirty_poly = mesh_src->runtime.cd_dirty_poly; - tmp.runtime.cd_dirty_vert = mesh_src->runtime.cd_dirty_vert; - - /* Ensure that when no normal layers exist, they are marked dirty, because - * normals might not have been included in the mask of copied layers. */ - if (!CustomData_has_layer(&tmp.vdata, CD_NORMAL) || - !CustomData_has_layer(&tmp.pdata, CD_NORMAL)) { - BKE_mesh_normals_tag_dirty(&tmp); - } + /* Clear the normals completely, since the new vertex / polygon count might be different. */ + BKE_mesh_clear_derived_normals(&tmp); if (CustomData_has_layer(&mesh_src->vdata, CD_SHAPEKEY)) { KeyBlock *kb; diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 1b3c7e01be8..b690d7f1e32 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -110,53 +110,72 @@ static void add_v3_v3_atomic(float r[3], const float a[3]) void BKE_mesh_normals_tag_dirty(Mesh *mesh) { - mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + mesh->runtime.vert_normals_dirty = true; + mesh->runtime.poly_normals_dirty = true; } float (*BKE_mesh_vertex_normals_for_write(Mesh *mesh))[3] { - CustomData_duplicate_referenced_layer(&mesh->vdata, CD_NORMAL, mesh->totvert); - return (float(*)[3])CustomData_add_layer( - &mesh->vdata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totvert); + if (mesh->runtime.vert_normals == nullptr) { + mesh->runtime.vert_normals = (float(*)[3])MEM_malloc_arrayN( + mesh->totvert, sizeof(float[3]), __func__); + } + + BLI_assert(MEM_allocN_len(mesh->runtime.vert_normals) >= sizeof(float[3]) * mesh->totvert); + + return mesh->runtime.vert_normals; } float (*BKE_mesh_poly_normals_for_write(Mesh *mesh))[3] { - CustomData_duplicate_referenced_layer(&mesh->pdata, CD_NORMAL, mesh->totpoly); - return (float(*)[3])CustomData_add_layer( - &mesh->pdata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totpoly); + if (mesh->runtime.poly_normals == nullptr) { + mesh->runtime.poly_normals = (float(*)[3])MEM_malloc_arrayN( + mesh->totpoly, sizeof(float[3]), __func__); + } + + BLI_assert(MEM_allocN_len(mesh->runtime.poly_normals) >= sizeof(float[3]) * mesh->totpoly); + + return mesh->runtime.poly_normals; } void BKE_mesh_vertex_normals_clear_dirty(Mesh *mesh) { - mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL; + mesh->runtime.vert_normals_dirty = false; BKE_mesh_assert_normals_dirty_or_calculated(mesh); } void BKE_mesh_poly_normals_clear_dirty(Mesh *mesh) { - mesh->runtime.cd_dirty_poly &= ~CD_MASK_NORMAL; + mesh->runtime.poly_normals_dirty = false; BKE_mesh_assert_normals_dirty_or_calculated(mesh); } bool BKE_mesh_vertex_normals_are_dirty(const Mesh *mesh) { - return mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL; + return mesh->runtime.vert_normals_dirty; } bool BKE_mesh_poly_normals_are_dirty(const Mesh *mesh) { - return mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL; + return mesh->runtime.poly_normals_dirty; +} + +void BKE_mesh_clear_derived_normals(Mesh *mesh) +{ + MEM_SAFE_FREE(mesh->runtime.vert_normals); + MEM_SAFE_FREE(mesh->runtime.poly_normals); + + mesh->runtime.vert_normals_dirty = true; + mesh->runtime.poly_normals_dirty = true; } void BKE_mesh_assert_normals_dirty_or_calculated(const Mesh *mesh) { - if (!(mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL)) { - BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL) || mesh->totvert == 0); + if (!mesh->runtime.vert_normals_dirty) { + BLI_assert(mesh->runtime.vert_normals || mesh->totvert == 0); } - if (!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL)) { - BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL) || mesh->totpoly == 0); + if (!mesh->runtime.poly_normals_dirty) { + BLI_assert(mesh->runtime.poly_normals || mesh->totpoly == 0); } } @@ -358,8 +377,8 @@ static void mesh_calc_normals_poly_and_vertex(MVert *mvert, const float (*BKE_mesh_vertex_normals_ensure(const Mesh *mesh))[3] { if (!(BKE_mesh_vertex_normals_are_dirty(mesh) || BKE_mesh_poly_normals_are_dirty(mesh))) { - BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL) || mesh->totvert == 0); - return (const float(*)[3])CustomData_get_layer(&mesh->vdata, CD_NORMAL); + BLI_assert(mesh->runtime.vert_normals != nullptr || mesh->totvert == 0); + return mesh->runtime.vert_normals; } if (mesh->totvert == 0) { @@ -369,9 +388,9 @@ const float (*BKE_mesh_vertex_normals_ensure(const Mesh *mesh))[3] ThreadMutex *normals_mutex = (ThreadMutex *)mesh->runtime.normals_mutex; BLI_mutex_lock(normals_mutex); if (!(BKE_mesh_vertex_normals_are_dirty(mesh) || BKE_mesh_poly_normals_are_dirty(mesh))) { - BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL)); + BLI_assert(mesh->runtime.vert_normals != nullptr); BLI_mutex_unlock(normals_mutex); - return (const float(*)[3])CustomData_get_layer(&mesh->vdata, CD_NORMAL); + return mesh->runtime.vert_normals; } float(*vert_normals)[3]; @@ -404,8 +423,8 @@ const float (*BKE_mesh_vertex_normals_ensure(const Mesh *mesh))[3] const float (*BKE_mesh_poly_normals_ensure(const Mesh *mesh))[3] { if (!BKE_mesh_poly_normals_are_dirty(mesh)) { - BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL) || mesh->totpoly == 0); - return (const float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL); + BLI_assert(mesh->runtime.poly_normals != nullptr || mesh->totpoly == 0); + return mesh->runtime.poly_normals; } if (mesh->totpoly == 0) { @@ -415,9 +434,9 @@ const float (*BKE_mesh_poly_normals_ensure(const Mesh *mesh))[3] ThreadMutex *normals_mutex = (ThreadMutex *)mesh->runtime.normals_mutex; BLI_mutex_lock(normals_mutex); if (!BKE_mesh_poly_normals_are_dirty(mesh)) { - BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL)); + BLI_assert(mesh->runtime.poly_normals != nullptr); BLI_mutex_unlock(normals_mutex); - return (const float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL); + return mesh->runtime.poly_normals; } float(*poly_normals)[3]; diff --git a/source/blender/blenkernel/intern/mesh_runtime.c b/source/blender/blenkernel/intern/mesh_runtime.c index e7e5064df7c..92bc94c2c96 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.c +++ b/source/blender/blenkernel/intern/mesh_runtime.c @@ -104,6 +104,11 @@ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag)) runtime->bvh_cache = NULL; runtime->shrinkwrap_data = NULL; + runtime->vert_normals_dirty = true; + runtime->poly_normals_dirty = true; + runtime->vert_normals = NULL; + runtime->poly_normals = NULL; + mesh_runtime_init_mutexes(mesh); } @@ -117,6 +122,7 @@ void BKE_mesh_runtime_clear_cache(Mesh *mesh) BKE_mesh_runtime_clear_geometry(mesh); BKE_mesh_batch_cache_free(mesh); BKE_mesh_runtime_clear_edit_data(mesh); + BKE_mesh_clear_derived_normals(mesh); } /** diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index 41ca96b109e..4635da29d34 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -1205,7 +1205,9 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * const int cd_edge_bweight_offset = CustomData_get_offset(&bm->edata, CD_BWEIGHT); const int cd_edge_crease_offset = CustomData_get_offset(&bm->edata, CD_CREASE); - BKE_mesh_normals_tag_dirty(me); + /* Clear normals on the mesh completely, since the original vertex and polygon count might be + * different than the BMesh's. */ + BKE_mesh_clear_derived_normals(me); me->runtime.deformed_only = true; diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 629a5e88de7..3d65c62b09d 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -114,6 +114,10 @@ typedef enum CustomDataType { CD_MTFACE = 5, CD_MCOL = 6, CD_ORIGINDEX = 7, + /** + * Used for derived face corner normals on mesh `ldata`, since currently they are not computed + * lazily. Derived vertex and polygon normals are stored in #Mesh_Runtime. + */ CD_NORMAL = 8, CD_FACEMAP = 9, /* exclusive face group, each face can only be part of one */ CD_PROP_FLOAT = 10, diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index bf50100b302..24f3d1c95ed 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -132,14 +132,23 @@ typedef struct Mesh_Runtime { */ char wrapper_type_finalize; + int subsurf_resolution; /** * Settings for lazily evaluating the subdivision on the CPU if needed. These are * set in the modifier when GPU subdivision can be performed. */ char subsurf_apply_render; char subsurf_use_optimal_display; - char _pad[2]; - int subsurf_resolution; + + /** + * Caches for lazily computed vertex and polygon normals. These are stored here rather than in + * #CustomData because they can be calculated on a const mesh, and adding custom data layers on a + * const mesh is not thread-safe. + */ + char vert_normals_dirty; + char poly_normals_dirty; + float (*vert_normals)[3]; + float (*poly_normals)[3]; void *_pad2; diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 1d1c5bd2285..c39c092f30a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -316,7 +316,6 @@ static void extrude_mesh_vertices(MeshComponent &component, } BKE_mesh_runtime_clear_cache(&mesh); - BKE_mesh_normals_tag_dirty(&mesh); } static Array> mesh_calculate_polys_of_edge(const Mesh &mesh) @@ -640,7 +639,6 @@ static void extrude_mesh_edges(MeshComponent &component, } BKE_mesh_runtime_clear_cache(&mesh); - BKE_mesh_normals_tag_dirty(&mesh); } /** @@ -1009,7 +1007,6 @@ static void extrude_mesh_face_regions(MeshComponent &component, } BKE_mesh_runtime_clear_cache(&mesh); - BKE_mesh_normals_tag_dirty(&mesh); } /* Get the range into an array of extruded corners, edges, or vertices for a particular polygon. */ @@ -1277,7 +1274,6 @@ static void extrude_individual_mesh_faces(MeshComponent &component, } BKE_mesh_runtime_clear_cache(&mesh); - BKE_mesh_normals_tag_dirty(&mesh); } static void node_geo_exec(GeoNodeExecParams params) -- cgit v1.2.3 From ad3ee84f4e5ff3aa7c7b7b104a32b856e3a2cd9a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 22 Feb 2022 13:13:13 -0500 Subject: Cleanup: Remove unused mesh dirty flags These were only set in two places. One was related to "tessellated loop normal", and the other derived corner normals. The values were never checked though, after 59343ee1627f4c369. The handling of dirty face corner normals is clearly problematic, but in the future it should be handled like the normal layers on the other domains instead. Ref D14154, T95839 --- source/blender/blenkernel/intern/mesh.cc | 2 -- source/blender/makesdna/DNA_mesh_types.h | 11 ----------- source/blender/modifiers/intern/MOD_uvproject.c | 3 --- 3 files changed, 16 deletions(-) diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 83619cb50ef..37564f9334f 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1975,8 +1975,6 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac nullptr); BKE_mesh_assert_normals_dirty_or_calculated(mesh); - - mesh->runtime.cd_dirty_loop &= ~CD_MASK_NORMAL; } void BKE_mesh_calc_normals_split(Mesh *mesh) diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index f40e91c1d5e..d8a853681fd 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -135,17 +135,6 @@ typedef struct Mesh_Runtime { float (*poly_normals)[3]; void *_pad2; - - /** - * Used to mark when derived data needs to be recalculated for a certain layer. - * Currently only normals. - */ - - int64_t cd_dirty_vert; - int64_t cd_dirty_edge; - int64_t cd_dirty_loop; - int64_t cd_dirty_poly; - } Mesh_Runtime; typedef struct Mesh { diff --git a/source/blender/modifiers/intern/MOD_uvproject.c b/source/blender/modifiers/intern/MOD_uvproject.c index 51ec0a46d32..d6f493267f8 100644 --- a/source/blender/modifiers/intern/MOD_uvproject.c +++ b/source/blender/modifiers/intern/MOD_uvproject.c @@ -285,9 +285,6 @@ static Mesh *uvprojectModifier_do(UVProjectModifierData *umd, mesh->runtime.is_original = false; - /* Mark tessellated CD layers as dirty. */ - mesh->runtime.cd_dirty_vert |= CD_MASK_TESSLOOPNORMAL; - return mesh; } -- cgit v1.2.3 From 529f8918780dc5b998d9f031c51d405af100ca5c Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Tue, 22 Feb 2022 20:03:34 +0100 Subject: GPencil: Make Fill Dilate expand outside stroke To keep consistency with the new contract option, the dilate now expand the shape beyond the internal closed area. --- source/blender/editors/gpencil/gpencil_fill.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 8be34a35ca9..8e5161b3245 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1097,7 +1097,7 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf) /** * Naive dilate * - * Expand green areas into enclosing red areas. + * Expand green areas into enclosing red or transparent areas. * Using stack prevents creep when replacing colors directly. *
  * -----------
@@ -1110,8 +1110,8 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf)
  */
 static bool dilate_shape(ImBuf *ibuf)
 {
-#define IS_RED (color[0] == 1.0f)
 #define IS_GREEN (color[1] == 1.0f)
+#define IS_NOT_GREEN (color[1] != 1.0f)
 
   bool done = false;
 
@@ -1140,7 +1140,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v - 1 >= 0) {
           index = v - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             lt = index;
           }
@@ -1149,7 +1149,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v + 1 <= maxpixel) {
           index = v + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             rt = index;
           }
@@ -1158,7 +1158,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v + ibuf->x <= max_size) {
           index = v + ibuf->x;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             tp = index;
           }
@@ -1167,7 +1167,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v - ibuf->x >= 0) {
           index = v - ibuf->x;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             bm = index;
           }
@@ -1176,7 +1176,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && lt) {
           index = tp - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1184,7 +1184,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && rt) {
           index = tp + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1192,7 +1192,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && lt) {
           index = bm - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1200,7 +1200,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && rt) {
           index = bm + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1218,8 +1218,8 @@ static bool dilate_shape(ImBuf *ibuf)
 
   return done;
 
-#undef IS_RED
 #undef IS_GREEN
+#undef IS_NOT_GREEN
 }
 
 /**
-- 
cgit v1.2.3


From 2746238dde66817deb7dc9c9cca731f8ec70ef3b Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Tue, 22 Feb 2022 14:39:29 -0500
Subject: Python API Docs: Hide version swtich

This still needs some fixes to be ready for release.
---
 doc/python_api/static/css/version_switch.css | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css
index 360ff2eea0e..adb80b01c0a 100644
--- a/doc/python_api/static/css/version_switch.css
+++ b/doc/python_api/static/css/version_switch.css
@@ -1,5 +1,6 @@
 /* Override RTD theme */
 .rst-versions {
+	display: none;
 	border-top: 0px;
 	overflow: visible;
 }
-- 
cgit v1.2.3


From 0b9cc6725cca193ec20868caf266ea35c173b956 Mon Sep 17 00:00:00 2001
From: Peter Kim 
Date: Wed, 23 Feb 2022 06:56:08 +0900
Subject: XR: Use #ifdef for Vive Focus 3 extension

Helps with building against different OpenXR SDK versions (i.e. for
downstream builds that require specific versions), as the extension was
only defined since OpenXR 1.0.22.
---
 intern/ghost/intern/GHOST_XrContext.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp
index 716b60799f9..2ac3d9ec2a5 100644
--- a/intern/ghost/intern/GHOST_XrContext.cpp
+++ b/intern/ghost/intern/GHOST_XrContext.cpp
@@ -412,7 +412,9 @@ void GHOST_XrContext::getExtensionsToEnable(
   /* Interaction profile extensions. */
   try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME);
   try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME);
+#ifdef XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME
   try_ext.push_back(XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME);
+#endif
   try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME);
 
   /* Controller model extension. */
-- 
cgit v1.2.3


From 5b4732f81ca8a159e937336fba7c8eb3220216de Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 22 Feb 2022 17:37:58 -0500
Subject: Cleanup: Use new enum for CurveEval handle types

This will make the transition to the new curves data structure
a bit simple, since the handle types can be copied directly between
the two. The change to CurveEval is simple because it is runtime-only.
---
 source/blender/blenkernel/BKE_spline.hh            | 27 ++++---------
 source/blender/blenkernel/intern/curve_eval.cc     | 20 +++++-----
 source/blender/blenkernel/intern/spline_bezier.cc  | 46 +++++++++++-----------
 .../node_geo_legacy_curve_select_by_handle_type.cc | 20 +++++-----
 .../legacy/node_geo_legacy_curve_set_handles.cc    | 16 ++++----
 .../legacy/node_geo_legacy_curve_spline_type.cc    |  8 ++--
 .../legacy/node_geo_legacy_curve_subdivide.cc      | 20 +++++-----
 .../nodes/geometry/nodes/node_geo_curve_fillet.cc  | 12 +++---
 .../nodes/node_geo_curve_handle_type_selection.cc  | 20 +++++-----
 .../node_geo_curve_primitive_bezier_segment.cc     |  4 +-
 .../geometry/nodes/node_geo_curve_set_handles.cc   | 16 ++++----
 .../geometry/nodes/node_geo_curve_spline_type.cc   |  8 ++--
 .../geometry/nodes/node_geo_curve_subdivide.cc     | 20 +++++-----
 .../nodes/geometry/nodes/node_geo_curve_trim.cc    |  4 +-
 .../geometry/nodes/node_geo_set_curve_handles.cc   | 16 ++++----
 15 files changed, 123 insertions(+), 134 deletions(-)

diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 846dcd3ca8e..646af6f8f98 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -10,6 +10,8 @@
 
 #include "FN_generic_virtual_array.hh"
 
+#include "DNA_curves_types.h"
+
 #include "BLI_float4x4.hh"
 #include "BLI_math_vec_types.hh"
 #include "BLI_vector.hh"
@@ -252,26 +254,13 @@ class Spline {
  * factors and indices in a list of floats, which is then used to interpolate any other data.
  */
 class BezierSpline final : public Spline {
- public:
-  enum class HandleType {
-    /** The handle can be moved anywhere, and doesn't influence the point's other handle. */
-    Free,
-    /** The location is automatically calculated to be smooth. */
-    Auto,
-    /** The location is calculated to point to the next/previous control point. */
-    Vector,
-    /** The location is constrained to point in the opposite direction as the other handle. */
-    Align,
-  };
-
- private:
   blender::Vector positions_;
   blender::Vector radii_;
   blender::Vector tilts_;
   int resolution_;
 
-  blender::Vector handle_types_left_;
-  blender::Vector handle_types_right_;
+  blender::Vector handle_types_left_;
+  blender::Vector handle_types_right_;
 
   /* These are mutable to allow lazy recalculation of #Auto and #Vector handle positions. */
   mutable blender::Vector handle_positions_left_;
@@ -323,8 +312,8 @@ class BezierSpline final : public Spline {
   blender::Span radii() const final;
   blender::MutableSpan tilts() final;
   blender::Span tilts() const final;
-  blender::Span handle_types_left() const;
-  blender::MutableSpan handle_types_left();
+  blender::Span handle_types_left() const;
+  blender::MutableSpan handle_types_left();
   blender::Span handle_positions_left() const;
   /**
    * Get writable access to the handle position.
@@ -333,8 +322,8 @@ class BezierSpline final : public Spline {
    * uninitialized memory while auto-generating handles.
    */
   blender::MutableSpan handle_positions_left(bool write_only = false);
-  blender::Span handle_types_right() const;
-  blender::MutableSpan handle_types_right();
+  blender::Span handle_types_right() const;
+  blender::MutableSpan handle_types_right();
   blender::Span handle_positions_right() const;
   /**
    * Get writable access to the handle position.
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 7fb833e67f8..49010caef24 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -160,24 +160,24 @@ void CurveEval::mark_cache_invalid()
   }
 }
 
-static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
+static HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
 {
   switch (dna_handle_type) {
     case HD_FREE:
-      return BezierSpline::HandleType::Free;
+      return BEZIER_HANDLE_FREE;
     case HD_AUTO:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case HD_VECT:
-      return BezierSpline::HandleType::Vector;
+      return BEZIER_HANDLE_VECTOR;
     case HD_ALIGN:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
     case HD_AUTO_ANIM:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case HD_ALIGN_DOUBLESIDE:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
   }
   BLI_assert_unreachable();
-  return BezierSpline::HandleType::Auto;
+  return BEZIER_HANDLE_AUTO;
 }
 
 static Spline::NormalCalculationMode normal_mode_from_dna_curve(const int twist_mode)
@@ -220,8 +220,8 @@ static SplinePtr spline_from_dna_bezier(const Nurb &nurb)
   MutableSpan positions = spline->positions();
   MutableSpan handle_positions_left = spline->handle_positions_left(true);
   MutableSpan handle_positions_right = spline->handle_positions_right(true);
-  MutableSpan handle_types_left = spline->handle_types_left();
-  MutableSpan handle_types_right = spline->handle_types_right();
+  MutableSpan handle_types_left = spline->handle_types_left();
+  MutableSpan handle_types_right = spline->handle_types_right();
   MutableSpan radii = spline->radii();
   MutableSpan tilts = spline->tilts();
 
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index 57f1d73d55e..3c2ac1dae9c 100644
--- a/source/blender/blenkernel/intern/spline_bezier.cc
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -93,11 +93,11 @@ Span BezierSpline::tilts() const
 {
   return tilts_;
 }
-Span BezierSpline::handle_types_left() const
+Span BezierSpline::handle_types_left() const
 {
   return handle_types_left_;
 }
-MutableSpan BezierSpline::handle_types_left()
+MutableSpan BezierSpline::handle_types_left()
 {
   return handle_types_left_;
 }
@@ -114,11 +114,11 @@ MutableSpan BezierSpline::handle_positions_left(const bool write_only)
   return handle_positions_left_;
 }
 
-Span BezierSpline::handle_types_right() const
+Span BezierSpline::handle_types_right() const
 {
   return handle_types_right_;
 }
-MutableSpan BezierSpline::handle_types_right()
+MutableSpan BezierSpline::handle_types_right()
 {
   return handle_types_right_;
 }
@@ -187,7 +187,7 @@ void BezierSpline::ensure_auto_handles() const
   for (const int i : IndexRange(this->size())) {
     using namespace blender;
 
-    if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
+    if (ELEM(BEZIER_HANDLE_AUTO, handle_types_left_[i], handle_types_right_[i])) {
       const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
       const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i];
       float prev_len = math::length(prev_diff);
@@ -203,23 +203,23 @@ void BezierSpline::ensure_auto_handles() const
       /* This magic number is unfortunate, but comes from elsewhere in Blender. */
       const float len = math::length(dir) * 2.5614f;
       if (len != 0.0f) {
-        if (handle_types_left_[i] == HandleType::Auto) {
+        if (handle_types_left_[i] == BEZIER_HANDLE_AUTO) {
           const float prev_len_clamped = std::min(prev_len, next_len * 5.0f);
           handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len);
         }
-        if (handle_types_right_[i] == HandleType::Auto) {
+        if (handle_types_right_[i] == BEZIER_HANDLE_AUTO) {
           const float next_len_clamped = std::min(next_len, prev_len * 5.0f);
           handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len);
         }
       }
     }
 
-    if (handle_types_left_[i] == HandleType::Vector) {
+    if (handle_types_left_[i] == BEZIER_HANDLE_VECTOR) {
       const float3 prev = previous_position(positions_, is_cyclic_, i);
       handle_positions_left_[i] = math::interpolate(positions_[i], prev, 1.0f / 3.0f);
     }
 
-    if (handle_types_right_[i] == HandleType::Vector) {
+    if (handle_types_right_[i] == BEZIER_HANDLE_VECTOR) {
       const float3 next = next_position(positions_, is_cyclic_, i);
       handle_positions_right_[i] = math::interpolate(positions_[i], next, 1.0f / 3.0f);
     }
@@ -257,8 +257,8 @@ void BezierSpline::transform(const blender::float4x4 &matrix)
 }
 
 static void set_handle_position(const float3 &position,
-                                const BezierSpline::HandleType type,
-                                const BezierSpline::HandleType type_other,
+                                const HandleType type,
+                                const HandleType type_other,
                                 const float3 &new_value,
                                 float3 &handle,
                                 float3 &handle_other)
@@ -266,12 +266,12 @@ static void set_handle_position(const float3 &position,
   using namespace blender::math;
 
   /* Don't bother when the handle positions are calculated automatically anyway. */
-  if (ELEM(type, BezierSpline::HandleType::Auto, BezierSpline::HandleType::Vector)) {
+  if (ELEM(type, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_VECTOR)) {
     return;
   }
 
   handle = new_value;
-  if (type_other == BezierSpline::HandleType::Align) {
+  if (type_other == BEZIER_HANDLE_ALIGN) {
     /* Keep track of the old length of the opposite handle. */
     const float length = distance(handle_other, position);
     /* Set the other handle to directly opposite from the current handle. */
@@ -283,8 +283,8 @@ static void set_handle_position(const float3 &position,
 void BezierSpline::set_handle_position_right(const int index, const blender::float3 &value)
 {
   set_handle_position(positions_[index],
-                      handle_types_right_[index],
-                      handle_types_left_[index],
+                      static_cast(handle_types_right_[index]),
+                      static_cast(handle_types_left_[index]),
                       value,
                       handle_positions_right_[index],
                       handle_positions_left_[index]);
@@ -293,8 +293,8 @@ void BezierSpline::set_handle_position_right(const int index, const blender::flo
 void BezierSpline::set_handle_position_left(const int index, const blender::float3 &value)
 {
   set_handle_position(positions_[index],
-                      handle_types_left_[index],
-                      handle_types_right_[index],
+                      static_cast(handle_types_right_[index]),
+                      static_cast(handle_types_left_[index]),
                       value,
                       handle_positions_left_[index],
                       handle_positions_right_[index]);
@@ -302,8 +302,8 @@ void BezierSpline::set_handle_position_left(const int index, const blender::floa
 
 bool BezierSpline::point_is_sharp(const int index) const
 {
-  return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) ||
-         ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free);
+  return ELEM(handle_types_left_[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) ||
+         ELEM(handle_types_right_[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE);
 }
 
 bool BezierSpline::segment_is_vector(const int index) const
@@ -313,15 +313,15 @@ bool BezierSpline::segment_is_vector(const int index) const
 
   if (index == this->size() - 1) {
     if (is_cyclic_) {
-      return handle_types_right_.last() == HandleType::Vector &&
-             handle_types_left_.first() == HandleType::Vector;
+      return handle_types_right_.last() == BEZIER_HANDLE_VECTOR &&
+             handle_types_left_.first() == BEZIER_HANDLE_VECTOR;
     }
     /* There is actually no segment in this case, but it's nice to avoid
      * having a special case for the last segment in calling code. */
     return true;
   }
-  return handle_types_right_[index] == HandleType::Vector &&
-         handle_types_left_[index + 1] == HandleType::Vector;
+  return handle_types_right_[index] == BEZIER_HANDLE_VECTOR &&
+         handle_types_left_[index + 1] == BEZIER_HANDLE_VECTOR;
 }
 
 void BezierSpline::mark_cache_invalid()
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
index f0a201c5adf..091726bdf84 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
@@ -33,24 +33,24 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
   node->storage = data;
 }
 
-static BezierSpline::HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type)
+static HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type)
 {
   switch (type) {
     case GEO_NODE_CURVE_HANDLE_AUTO:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case GEO_NODE_CURVE_HANDLE_ALIGN:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
     case GEO_NODE_CURVE_HANDLE_FREE:
-      return BezierSpline::HandleType::Free;
+      return BEZIER_HANDLE_FREE;
     case GEO_NODE_CURVE_HANDLE_VECTOR:
-      return BezierSpline::HandleType::Vector;
+      return BEZIER_HANDLE_VECTOR;
   }
   BLI_assert_unreachable();
-  return BezierSpline::HandleType::Auto;
+  return BEZIER_HANDLE_AUTO;
 }
 
 static void select_curve_by_handle_type(const CurveEval &curve,
-                                        const BezierSpline::HandleType type,
+                                        const HandleType type,
                                         const GeometryNodeCurveHandleMode mode,
                                         const MutableSpan r_selection)
 {
@@ -61,8 +61,8 @@ static void select_curve_by_handle_type(const CurveEval &curve,
       const Spline &spline = *splines[i_spline];
       if (spline.type() == Spline::Type::Bezier) {
         const BezierSpline &bezier_spline = static_cast(spline);
-        Span types_left = bezier_spline.handle_types_left();
-        Span types_right = bezier_spline.handle_types_right();
+        Span types_left = bezier_spline.handle_types_left();
+        Span types_right = bezier_spline.handle_types_right();
         for (const int i_point : IndexRange(bezier_spline.size())) {
           r_selection[offsets[i_spline] + i_point] = (mode & GEO_NODE_CURVE_HANDLE_LEFT &&
                                                       types_left[i_point] == type) ||
@@ -81,7 +81,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 {
   const NodeGeometryCurveSelectHandles *storage =
       (const NodeGeometryCurveSelectHandles *)params.node().storage;
-  const BezierSpline::HandleType handle_type = handle_type_from_input_type(
+  const HandleType handle_type = handle_type_from_input_type(
       (GeometryNodeCurveHandleType)storage->handle_type);
   const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage->mode;
 
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
index b574b2e3cff..92dbd6afcaa 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
@@ -31,20 +31,20 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
   node->storage = data;
 }
 
-static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type)
+static HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type)
 {
   switch (type) {
     case GEO_NODE_CURVE_HANDLE_AUTO:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case GEO_NODE_CURVE_HANDLE_ALIGN:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
     case GEO_NODE_CURVE_HANDLE_FREE:
-      return BezierSpline::HandleType::Free;
+      return BEZIER_HANDLE_FREE;
     case GEO_NODE_CURVE_HANDLE_VECTOR:
-      return BezierSpline::HandleType::Vector;
+      return BEZIER_HANDLE_VECTOR;
   }
   BLI_assert_unreachable();
-  return BezierSpline::HandleType::Auto;
+  return BEZIER_HANDLE_AUTO;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -70,7 +70,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   VArray selection = curve_component.attribute_get_for_read(
       selection_name, ATTR_DOMAIN_POINT, true);
 
-  const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type);
+  const HandleType new_handle_type = handle_type_from_input_type(type);
   int point_index = 0;
   bool has_bezier_spline = false;
   for (SplinePtr &spline : splines) {
@@ -80,7 +80,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     }
 
     BezierSpline &bezier_spline = static_cast(*spline);
-    if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) {
+    if (ELEM(new_handle_type, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN)) {
       /* In this case the automatically calculated handle types need to be "baked", because
        * they're possibly changing from a type that is calculated automatically to a type that
        * is positioned manually. */
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
index b8696e1eb52..e0ce5ae1a97 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
@@ -148,8 +148,8 @@ static SplinePtr poly_to_bezier(const Spline &input)
   output->positions().copy_from(input.positions());
   output->radii().copy_from(input.radii());
   output->tilts().copy_from(input.tilts());
-  output->handle_types_left().fill(BezierSpline::HandleType::Vector);
-  output->handle_types_right().fill(BezierSpline::HandleType::Vector);
+  output->handle_types_left().fill(BEZIER_HANDLE_VECTOR);
+  output->handle_types_right().fill(BEZIER_HANDLE_VECTOR);
   output->set_resolution(12);
   Spline::copy_base_settings(input, *output);
   output->attributes = input.attributes;
@@ -166,8 +166,8 @@ static SplinePtr nurbs_to_bezier(const Spline &input)
   scale_input_assign(input.positions(), 3, 2, output->handle_positions_right());
   scale_input_assign(input.radii(), 3, 2, output->radii());
   scale_input_assign(input.tilts(), 3, 2, output->tilts());
-  output->handle_types_left().fill(BezierSpline::HandleType::Align);
-  output->handle_types_right().fill(BezierSpline::HandleType::Align);
+  output->handle_types_left().fill(BEZIER_HANDLE_ALIGN);
+  output->handle_types_right().fill(BEZIER_HANDLE_ALIGN);
   output->set_resolution(nurbs_spline.resolution());
   Spline::copy_base_settings(input, *output);
   output->attributes.reallocate(output->size());
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
index 8ae9df78936..38930d5db85 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
@@ -111,8 +111,8 @@ static void subdivide_bezier_segment(const BezierSpline &src,
                                      MutableSpan dst_positions,
                                      MutableSpan dst_handles_left,
                                      MutableSpan dst_handles_right,
-                                     MutableSpan dst_type_left,
-                                     MutableSpan dst_type_right)
+                                     MutableSpan dst_type_left,
+                                     MutableSpan dst_type_right)
 {
   const bool is_last_cyclic_segment = index == (src.size() - 1);
   const int next_index = is_last_cyclic_segment ? 0 : index + 1;
@@ -122,10 +122,10 @@ static void subdivide_bezier_segment(const BezierSpline &src,
 
   if (src.segment_is_vector(index)) {
     if (is_last_cyclic_segment) {
-      dst_type_left.first() = BezierSpline::HandleType::Vector;
+      dst_type_left.first() = BEZIER_HANDLE_VECTOR;
     }
-    dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector);
-    dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector);
+    dst_type_left.slice(offset + 1, result_size).fill(BEZIER_HANDLE_VECTOR);
+    dst_type_right.slice(offset, result_size).fill(BEZIER_HANDLE_VECTOR);
 
     const float factor_delta = 1.0f / result_size;
     for (const int cut : IndexRange(result_size)) {
@@ -136,10 +136,10 @@ static void subdivide_bezier_segment(const BezierSpline &src,
   }
   else {
     if (is_last_cyclic_segment) {
-      dst_type_left.first() = BezierSpline::HandleType::Free;
+      dst_type_left.first() = BEZIER_HANDLE_FREE;
     }
-    dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free);
-    dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free);
+    dst_type_left.slice(offset + 1, result_size).fill(BEZIER_HANDLE_FREE);
+    dst_type_right.slice(offset, result_size).fill(BEZIER_HANDLE_FREE);
 
     const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size;
 
@@ -187,8 +187,8 @@ static void subdivide_bezier_spline(const BezierSpline &src,
   MutableSpan dst_positions = dst.positions();
   MutableSpan dst_handles_left = dst.handle_positions_left();
   MutableSpan dst_handles_right = dst.handle_positions_right();
-  MutableSpan dst_type_left = dst.handle_types_left();
-  MutableSpan dst_type_right = dst.handle_types_right();
+  MutableSpan dst_type_left = dst.handle_types_left();
+  MutableSpan dst_type_right = dst.handle_types_right();
 
   threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) {
     for (const int i : range) {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
index 94425ab48f4..149b387b179 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -394,9 +394,9 @@ static void update_bezier_positions(const FilletData &fd,
     dst_spline.handle_positions_left()[end_i] = dst_spline.positions()[end_i] -
                                                 handle_length * next_dir;
     dst_spline.handle_types_right()[i_dst] = dst_spline.handle_types_left()[end_i] =
-        BezierSpline::HandleType::Align;
+        BEZIER_HANDLE_ALIGN;
     dst_spline.handle_types_left()[i_dst] = dst_spline.handle_types_right()[end_i] =
-        BezierSpline::HandleType::Vector;
+        BEZIER_HANDLE_VECTOR;
     dst_spline.mark_cache_invalid();
 
     /* Calculate the center of the radius to be formed. */
@@ -406,8 +406,8 @@ static void update_bezier_positions(const FilletData &fd,
     float radius;
     radius_vec = math::normalize_and_get_length(radius_vec, radius);
 
-    dst_spline.handle_types_right().slice(1, count - 2).fill(BezierSpline::HandleType::Align);
-    dst_spline.handle_types_left().slice(1, count - 2).fill(BezierSpline::HandleType::Align);
+    dst_spline.handle_types_right().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN);
+    dst_spline.handle_types_left().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN);
 
     /* For each of the vertices in between the end points. */
     for (const int j : IndexRange(1, count - 2)) {
@@ -516,8 +516,8 @@ static SplinePtr fillet_spline(const Spline &spline,
       const BezierSpline &src_spline = static_cast(spline);
       BezierSpline &dst_spline = static_cast(*dst_spline_ptr);
       if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) {
-        dst_spline.handle_types_left().fill(BezierSpline::HandleType::Vector);
-        dst_spline.handle_types_right().fill(BezierSpline::HandleType::Vector);
+        dst_spline.handle_types_left().fill(BEZIER_HANDLE_VECTOR);
+        dst_spline.handle_types_right().fill(BEZIER_HANDLE_VECTOR);
         update_poly_positions(fd, dst_spline, src_spline, point_counts);
       }
       else {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
index 26a8ad2d988..a7ef06a48b0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
@@ -31,24 +31,24 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
   node->storage = data;
 }
 
-static BezierSpline::HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type)
+static HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type)
 {
   switch (type) {
     case GEO_NODE_CURVE_HANDLE_AUTO:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case GEO_NODE_CURVE_HANDLE_ALIGN:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
     case GEO_NODE_CURVE_HANDLE_FREE:
-      return BezierSpline::HandleType::Free;
+      return BEZIER_HANDLE_FREE;
     case GEO_NODE_CURVE_HANDLE_VECTOR:
-      return BezierSpline::HandleType::Vector;
+      return BEZIER_HANDLE_VECTOR;
   }
   BLI_assert_unreachable();
-  return BezierSpline::HandleType::Auto;
+  return BEZIER_HANDLE_AUTO;
 }
 
 static void select_by_handle_type(const CurveEval &curve,
-                                  const BezierSpline::HandleType type,
+                                  const HandleType type,
                                   const GeometryNodeCurveHandleMode mode,
                                   const MutableSpan r_selection)
 {
@@ -71,11 +71,11 @@ static void select_by_handle_type(const CurveEval &curve,
 }
 
 class HandleTypeFieldInput final : public GeometryFieldInput {
-  BezierSpline::HandleType type_;
+  HandleType type_;
   GeometryNodeCurveHandleMode mode_;
 
  public:
-  HandleTypeFieldInput(BezierSpline::HandleType type, GeometryNodeCurveHandleMode mode)
+  HandleTypeFieldInput(HandleType type, GeometryNodeCurveHandleMode mode)
       : GeometryFieldInput(CPPType::get(), "Handle Type Selection node"),
         type_(type),
         mode_(mode)
@@ -124,7 +124,7 @@ class HandleTypeFieldInput final : public GeometryFieldInput {
 static void node_geo_exec(GeoNodeExecParams params)
 {
   const NodeGeometryCurveSelectHandles &storage = node_storage(params.node());
-  const BezierSpline::HandleType handle_type = handle_type_from_input_type(
+  const HandleType handle_type = handle_type_from_input_type(
       (GeometryNodeCurveHandleType)storage.handle_type);
   const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode;
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
index c6b9018f0db..69f43c81ea3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
@@ -69,8 +69,8 @@ static std::unique_ptr create_bezier_segment_curve(
 
   spline->resize(2);
   MutableSpan positions = spline->positions();
-  spline->handle_types_left().fill(BezierSpline::HandleType::Align);
-  spline->handle_types_right().fill(BezierSpline::HandleType::Align);
+  spline->handle_types_left().fill(BEZIER_HANDLE_ALIGN);
+  spline->handle_types_right().fill(BEZIER_HANDLE_ALIGN);
   spline->radii().fill(1.0f);
   spline->tilts().fill(0.0f);
 
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 3d7b2fddf72..fdc717bb7ee 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
@@ -33,20 +33,20 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
   node->storage = data;
 }
 
-static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type)
+static HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type)
 {
   switch (type) {
     case GEO_NODE_CURVE_HANDLE_AUTO:
-      return BezierSpline::HandleType::Auto;
+      return BEZIER_HANDLE_AUTO;
     case GEO_NODE_CURVE_HANDLE_ALIGN:
-      return BezierSpline::HandleType::Align;
+      return BEZIER_HANDLE_ALIGN;
     case GEO_NODE_CURVE_HANDLE_FREE:
-      return BezierSpline::HandleType::Free;
+      return BEZIER_HANDLE_FREE;
     case GEO_NODE_CURVE_HANDLE_VECTOR:
-      return BezierSpline::HandleType::Vector;
+      return BEZIER_HANDLE_VECTOR;
   }
   BLI_assert_unreachable();
-  return BezierSpline::HandleType::Auto;
+  return BEZIER_HANDLE_AUTO;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -77,7 +77,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     selection_evaluator.evaluate();
     const VArray &selection = selection_evaluator.get_evaluated(0);
 
-    const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type);
+    const HandleType new_handle_type = handle_type_from_input_type(type);
     int point_index = 0;
 
     for (SplinePtr &spline : splines) {
@@ -88,7 +88,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
       has_bezier_spline = true;
       BezierSpline &bezier_spline = static_cast(*spline);
-      if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) {
+      if (ELEM(new_handle_type, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN)) {
         /* In this case the automatically calculated handle types need to be "baked", because
          * they're possibly changing from a type that is calculated automatically to a type that
          * is positioned manually. */
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 4e4cabd3c33..aec2e106315 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
@@ -259,8 +259,8 @@ static SplinePtr poly_to_bezier(const Spline &input)
   output->positions().copy_from(input.positions());
   output->radii().copy_from(input.radii());
   output->tilts().copy_from(input.tilts());
-  output->handle_types_left().fill(BezierSpline::HandleType::Vector);
-  output->handle_types_right().fill(BezierSpline::HandleType::Vector);
+  output->handle_types_left().fill(BEZIER_HANDLE_VECTOR);
+  output->handle_types_right().fill(BEZIER_HANDLE_VECTOR);
   output->set_resolution(12);
   Spline::copy_base_settings(input, *output);
   output->attributes = input.attributes;
@@ -298,8 +298,8 @@ static SplinePtr nurbs_to_bezier(const Spline &input)
   nurbs_to_bezier_assign(nurbs_spline.tilts(), output->tilts(), knots_mode);
   scale_input_assign(handle_positions.as_span(), 2, 0, output->handle_positions_left());
   scale_input_assign(handle_positions.as_span(), 2, 1, output->handle_positions_right());
-  output->handle_types_left().fill(BezierSpline::HandleType::Align);
-  output->handle_types_right().fill(BezierSpline::HandleType::Align);
+  output->handle_types_left().fill(BEZIER_HANDLE_ALIGN);
+  output->handle_types_right().fill(BEZIER_HANDLE_ALIGN);
   output->set_resolution(nurbs_spline.resolution());
   Spline::copy_base_settings(nurbs_spline, *output);
   output->attributes.reallocate(output->size());
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 9daf3ff0594..79576854548 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -93,8 +93,8 @@ static void subdivide_bezier_segment(const BezierSpline &src,
                                      MutableSpan dst_positions,
                                      MutableSpan dst_handles_left,
                                      MutableSpan dst_handles_right,
-                                     MutableSpan dst_type_left,
-                                     MutableSpan dst_type_right)
+                                     MutableSpan dst_type_left,
+                                     MutableSpan dst_type_right)
 {
   const bool is_last_cyclic_segment = index == (src.size() - 1);
   const int next_index = is_last_cyclic_segment ? 0 : index + 1;
@@ -104,10 +104,10 @@ static void subdivide_bezier_segment(const BezierSpline &src,
 
   if (src.segment_is_vector(index)) {
     if (is_last_cyclic_segment) {
-      dst_type_left.first() = BezierSpline::HandleType::Vector;
+      dst_type_left.first() = BEZIER_HANDLE_VECTOR;
     }
-    dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector);
-    dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector);
+    dst_type_left.slice(offset + 1, result_size).fill(BEZIER_HANDLE_VECTOR);
+    dst_type_right.slice(offset, result_size).fill(BEZIER_HANDLE_VECTOR);
 
     const float factor_delta = 1.0f / result_size;
     for (const int cut : IndexRange(result_size)) {
@@ -118,10 +118,10 @@ static void subdivide_bezier_segment(const BezierSpline &src,
   }
   else {
     if (is_last_cyclic_segment) {
-      dst_type_left.first() = BezierSpline::HandleType::Free;
+      dst_type_left.first() = BEZIER_HANDLE_FREE;
     }
-    dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free);
-    dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free);
+    dst_type_left.slice(offset + 1, result_size).fill(BEZIER_HANDLE_FREE);
+    dst_type_right.slice(offset, result_size).fill(BEZIER_HANDLE_FREE);
 
     const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size;
 
@@ -169,8 +169,8 @@ static void subdivide_bezier_spline(const BezierSpline &src,
   MutableSpan dst_positions = dst.positions();
   MutableSpan dst_handles_left = dst.handle_positions_left();
   MutableSpan dst_handles_right = dst.handle_positions_right();
-  MutableSpan dst_type_left = dst.handle_types_left();
-  MutableSpan dst_type_right = dst.handle_types_right();
+  MutableSpan dst_type_left = dst.handle_types_left();
+  MutableSpan dst_type_right = dst.handle_types_right();
 
   threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) {
     for (const int i : range) {
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 abc5b1649c7..e6d40a8b6f7 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -400,8 +400,8 @@ static void to_single_point_bezier(Spline &spline, const Spline::LookupResult &l
   const BezierSpline::InsertResult new_point = bezier.calculate_segment_insertion(
       trim.left_index, trim.right_index, trim.factor);
   bezier.positions().first() = new_point.position;
-  bezier.handle_types_left().first() = BezierSpline::HandleType::Free;
-  bezier.handle_types_right().first() = BezierSpline::HandleType::Free;
+  bezier.handle_types_left().first() = BEZIER_HANDLE_FREE;
+  bezier.handle_types_right().first() = BEZIER_HANDLE_FREE;
   bezier.handle_positions_left().first() = new_point.left_handle;
   bezier.handle_positions_right().first() = new_point.right_handle;
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index fb648baad08..4223356397e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -67,23 +67,23 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
       for (int i : bezier.positions().index_range()) {
         if (current_mask < selection.size() && selection[current_mask] == current_point) {
           if (mode & GEO_NODE_CURVE_HANDLE_LEFT) {
-            if (bezier.handle_types_left()[i] == BezierSpline::HandleType::Vector) {
+            if (bezier.handle_types_left()[i] == BEZIER_HANDLE_VECTOR) {
               bezier.ensure_auto_handles();
-              bezier.handle_types_left()[i] = BezierSpline::HandleType::Free;
+              bezier.handle_types_left()[i] = BEZIER_HANDLE_FREE;
             }
-            else if (bezier.handle_types_left()[i] == BezierSpline::HandleType::Auto) {
+            else if (bezier.handle_types_left()[i] == BEZIER_HANDLE_AUTO) {
               bezier.ensure_auto_handles();
-              bezier.handle_types_left()[i] = BezierSpline::HandleType::Align;
+              bezier.handle_types_left()[i] = BEZIER_HANDLE_ALIGN;
             }
           }
           else {
-            if (bezier.handle_types_right()[i] == BezierSpline::HandleType::Vector) {
+            if (bezier.handle_types_right()[i] == BEZIER_HANDLE_VECTOR) {
               bezier.ensure_auto_handles();
-              bezier.handle_types_right()[i] = BezierSpline::HandleType::Free;
+              bezier.handle_types_right()[i] = BEZIER_HANDLE_FREE;
             }
-            else if (bezier.handle_types_right()[i] == BezierSpline::HandleType::Auto) {
+            else if (bezier.handle_types_right()[i] == BEZIER_HANDLE_AUTO) {
               bezier.ensure_auto_handles();
-              bezier.handle_types_right()[i] = BezierSpline::HandleType::Align;
+              bezier.handle_types_right()[i] = BEZIER_HANDLE_ALIGN;
             }
           }
           current_mask++;
-- 
cgit v1.2.3


From 4addc1f31e3db456a81dda89245bd09cd3faf80f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 12:30:54 +1100
Subject: Tests: temporarily add back tests/check_deprecated.py

The build-bot directly referenced this file and doesn't
have publically available configuration.

Add an empty file until this can be removed by the build scripts.
---
 tests/check_deprecated.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 tests/check_deprecated.py

diff --git a/tests/check_deprecated.py b/tests/check_deprecated.py
new file mode 100644
index 00000000000..e69de29bb2d
-- 
cgit v1.2.3


From 4c423ccbd6f6f24d8cfdbc563127409cddc5fb55 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 12:30:56 +1100
Subject: CMake: include missing files

Also use SRC_ prefix for source variables so cmake_consistency_check.py
detects these files as being known to CMake.
---
 intern/cycles/app/CMakeLists.txt         | 10 ++++++++--
 source/blender/blenkernel/CMakeLists.txt |  1 +
 source/blender/blenlib/CMakeLists.txt    |  4 ++++
 source/blender/draw/CMakeLists.txt       |  2 ++
 source/blender/gpu/CMakeLists.txt        | 17 +++++++++++------
 5 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt
index ab38e103311..3248ef0dcda 100644
--- a/intern/cycles/app/CMakeLists.txt
+++ b/intern/cycles/app/CMakeLists.txt
@@ -34,8 +34,14 @@ endif()
 
 if(WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI)
   add_definitions(${GL_DEFINITIONS})
-  list(APPEND INC_SYS ${GLEW_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS})
-  list(APPEND LIBRARIES ${CYCLES_GL_LIBRARIES} ${SDL2_LIBRARIES})
+  list(APPEND INC_SYS
+    ${GLEW_INCLUDE_DIR}
+    ${SDL2_INCLUDE_DIRS}
+  )
+  list(APPEND LIBRARIES
+    ${CYCLES_GL_LIBRARIES}
+    ${SDL2_LIBRARIES}
+  )
 endif()
 
 # Common configuration.
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 78a145335b4..ad8b5813492 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -377,6 +377,7 @@ set(SRC
   BKE_idprop.hh
   BKE_idtype.h
   BKE_image.h
+  BKE_image_partial_update.hh
   BKE_image_save.h
   BKE_ipo.h
   BKE_kelvinlet.h
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index e1b6e218ff5..ca22315b2ed 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -240,6 +240,7 @@ set(SRC
   BLI_math_vec_mpq_types.hh
   BLI_math_vec_types.hh
   BLI_math_vector.h
+  BLI_math_vector.hh
   BLI_memarena.h
   BLI_memblock.h
   BLI_memiter.h
@@ -308,6 +309,9 @@ set(SRC
   BLI_winstuff.h
   PIL_time.h
   PIL_time_utildefines.h
+
+  # Without these files listed, they aren't known to CMake.
+  ../../../extern/json/include/json.hpp
 )
 
 set(LIB
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 4103d9a7087..98f75ad6106 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -221,6 +221,8 @@ set(SRC
   engines/image/image_space_image.hh
   engines/image/image_space_node.hh
   engines/image/image_space.hh
+  engines/image/image_texture_info.hh
+  engines/image/image_usage.hh
   engines/image/image_wrappers.hh
   engines/workbench/workbench_engine.h
   engines/workbench/workbench_private.h
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt
index f2e189777f0..3ad43ef05af 100644
--- a/source/blender/gpu/CMakeLists.txt
+++ b/source/blender/gpu/CMakeLists.txt
@@ -147,6 +147,7 @@ set(SRC
   intern/gpu_select_private.h
   intern/gpu_shader_create_info.hh
   intern/gpu_shader_create_info_private.hh
+  intern/gpu_shader_dependency_private.h
   intern/gpu_shader_interface.hh
   intern/gpu_shader_private.hh
   intern/gpu_state_private.hh
@@ -384,7 +385,7 @@ file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}")
 list(APPEND SRC ${glsl_source_list_file})
 list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR})
 
-set(SHADER_CREATE_INFOS
+set(SRC_SHADER_CREATE_INFOS
   ../draw/engines/workbench/shaders/infos/workbench_composite_info.hh
   ../draw/engines/workbench/shaders/infos/workbench_effect_antialiasing_info.hh
   ../draw/engines/workbench/shaders/infos/workbench_effect_cavity_info.hh
@@ -435,7 +436,7 @@ set(SHADER_CREATE_INFOS
 )
 
 set(SHADER_CREATE_INFOS_CONTENT "")
-foreach(DESCRIPTOR_FILE ${SHADER_CREATE_INFOS})
+foreach(DESCRIPTOR_FILE ${SRC_SHADER_CREATE_INFOS})
   string(APPEND SHADER_CREATE_INFOS_CONTENT "#include \"${DESCRIPTOR_FILE}\"\n")
 endforeach()
 
@@ -486,18 +487,22 @@ if(WITH_GPU_SHADER_BUILDER)
   )
   target_include_directories(shader_builder PRIVATE ${INC} ${CMAKE_CURRENT_BINARY_DIR})
 
-  set(BAKED_CREATE_INFOS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shader_baked.hh)
+  set(SRC_BAKED_CREATE_INFOS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shader_baked.hh)
 
   add_custom_command(
     OUTPUT
-    ${BAKED_CREATE_INFOS_FILE}
+    ${SRC_BAKED_CREATE_INFOS_FILE}
     COMMAND
-      "$" ${BAKED_CREATE_INFOS_FILE}
+      "$" ${SRC_BAKED_CREATE_INFOS_FILE}
     DEPENDS shader_builder
   )
   set(GPU_SHADER_INFO_SRC
     intern/gpu_shader_info_baked.cc
-    ${BAKED_CREATE_INFOS_FILE}
+    ${SRC_BAKED_CREATE_INFOS_FILE}
+
+    # For project files to be aware of these headers.
+    ${SRC_SHADER_CREATE_INFOS}
+    shaders/infos/gpu_interface_info.hh
   )
 
   blender_add_lib(bf_gpu_shader_infos "${GPU_SHADER_INFO_SRC}" "" "" "")
-- 
cgit v1.2.3


From e1c33698e6919f569c30774a1c83d8c464d59d7e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 12:30:58 +1100
Subject: GNUmakefile: prefer "check_" prefix instead of "test_"

- Reserve "test" for tests & testing frameworks.
- Add "check_mypy" to "make help" text.
- Output to the standard output instead of redirecting to log-files,
  leave redirecting output log-files to the user running the command.
---
 GNUmakefile | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/GNUmakefile b/GNUmakefile
index c08bde5c474..a410662647e 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -51,12 +51,6 @@ Testing Targets
 
    * test:
      Run automated tests with ctest.
-   * test_cmake:
-     Runs our own cmake file checker
-     which detects errors in the cmake file list definitions
-   * test_pep8:
-     Checks all python script are pep8
-     which are tagged to use the stricter formatting
 
 Static Source Code Checking
    Not associated with building Blender.
@@ -72,6 +66,10 @@ Static Source Code Checking
                             using one of the accepted licenses in 'doc/license/SPDX-license-identifiers.txt'
                             Append with 'SHOW_HEADERS=1' to show all unique headers
                             which can be useful for spotting license irregularities.
+   * check_cmake:           Runs our own cmake file checker which detects errors in the cmake file list definitions.
+   * check_pep8:            Checks all Python script are pep8 which are tagged to use the stricter formatting.
+   * check_mypy:            Checks all Python scripts using mypy,
+                            see: source/tools/check_source/check_mypy_config.py scripts which are included.
 
 Spell Checkers
    This runs the spell checker from the developer tools repositor.
@@ -399,16 +397,6 @@ package_archive: .FORCE
 test: .FORCE
 	@$(PYTHON) ./build_files/utils/make_test.py "$(BUILD_DIR)"
 
-# run pep8 check check on scripts we distribute.
-test_pep8: .FORCE
-	@$(PYTHON) tests/python/pep8.py > test_pep8.log 2>&1
-	@echo "written: test_pep8.log"
-
-# run some checks on our CMAKE files.
-test_cmake: .FORCE
-	@$(PYTHON) build_files/cmake/cmake_consistency_check.py > test_cmake_consistency.log 2>&1
-	@echo "written: test_cmake_consistency.log"
-
 
 # -----------------------------------------------------------------------------
 # Project Files
@@ -486,7 +474,6 @@ check_descriptions: .FORCE
 	@$(BLENDER_BIN) --background -noaudio --factory-startup --python \
 	    "$(BLENDER_DIR)/source/tools/check_source/check_descriptions.py"
 
-# run deprecation tests, see if we have anything to remove.
 check_deprecated: .FORCE
 	@PYTHONIOENCODING=utf_8 $(PYTHON) \
 	    source/tools/check_source/check_deprecated.py
@@ -496,6 +483,14 @@ check_licenses: .FORCE
 	    "$(BLENDER_DIR)/source/tools/check_source/check_licenses.py" \
 	    "--show-headers=$(SHOW_HEADERS)"
 
+check_pep8: .FORCE
+	@PYTHONIOENCODING=utf_8 $(PYTHON) \
+	    tests/python/pep8.py
+
+check_cmake: .FORCE
+	@PYTHONIOENCODING=utf_8 $(PYTHON) \
+	    build_files/cmake/cmake_consistency_check.py
+
 
 # -----------------------------------------------------------------------------
 # Utilities
-- 
cgit v1.2.3


From c6df7266c718555cde5a729dae1c6023ef2b3530 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 12:30:59 +1100
Subject: CMake: move cmake_consistency_check.py into source/tools/ repo

This isn't needed for building Blender so move this along side
other source checking scripts.
---
 GNUmakefile                                        |   2 +-
 build_files/cmake/cmake_consistency_check.py       | 387 ---------------------
 .../cmake/cmake_consistency_check_config.py        |  64 ----
 3 files changed, 1 insertion(+), 452 deletions(-)
 delete mode 100755 build_files/cmake/cmake_consistency_check.py
 delete mode 100644 build_files/cmake/cmake_consistency_check_config.py

diff --git a/GNUmakefile b/GNUmakefile
index a410662647e..575f3e904df 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -489,7 +489,7 @@ check_pep8: .FORCE
 
 check_cmake: .FORCE
 	@PYTHONIOENCODING=utf_8 $(PYTHON) \
-	    build_files/cmake/cmake_consistency_check.py
+	    source/tools/check_source/check_cmake_consistency.py
 
 
 # -----------------------------------------------------------------------------
diff --git a/build_files/cmake/cmake_consistency_check.py b/build_files/cmake/cmake_consistency_check.py
deleted file mode 100755
index ec31b4cdfe3..00000000000
--- a/build_files/cmake/cmake_consistency_check.py
+++ /dev/null
@@ -1,387 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# 
-
-# Note: this code should be cleaned up / refactored.
-
-import sys
-if sys.version_info.major < 3:
-    print("\nPython3.x needed, found %s.\nAborting!\n" %
-          sys.version.partition(" ")[0])
-    sys.exit(1)
-
-import os
-from os.path import (
-    dirname,
-    join,
-    normpath,
-    splitext,
-)
-
-from cmake_consistency_check_config import (
-    IGNORE_SOURCE,
-    IGNORE_SOURCE_MISSING,
-    IGNORE_CMAKE,
-    UTF8_CHECK,
-    SOURCE_DIR,
-    BUILD_DIR,
-)
-
-from typing import (
-    Callable,
-    Dict,
-    Generator,
-    Iterator,
-    List,
-    Optional,
-    Tuple,
-)
-
-
-global_h = set()
-global_c = set()
-global_refs: Dict[str, List[Tuple[str, int]]] = {}
-
-# Flatten `IGNORE_SOURCE_MISSING` to avoid nested looping.
-IGNORE_SOURCE_MISSING_FLAT = [
-    (k, ignore_path) for k, ig_list in IGNORE_SOURCE_MISSING
-    for ignore_path in ig_list
-]
-
-# Ignore cmake file, path pairs.
-global_ignore_source_missing: Dict[str, List[str]] = {}
-for k, v in IGNORE_SOURCE_MISSING_FLAT:
-    global_ignore_source_missing.setdefault(k, []).append(v)
-del IGNORE_SOURCE_MISSING_FLAT
-
-
-def replace_line(f: str, i: int, text: str, keep_indent: bool = True) -> None:
-    file_handle = open(f, 'r')
-    data = file_handle.readlines()
-    file_handle.close()
-
-    l = data[i]
-    ws = l[:len(l) - len(l.lstrip())]
-
-    data[i] = "%s%s\n" % (ws, text)
-
-    file_handle = open(f, 'w')
-    file_handle.writelines(data)
-    file_handle.close()
-
-
-def source_list(
-        path: str,
-        filename_check: Optional[Callable[[str], bool]] = None,
-) -> Generator[str, None, None]:
-    for dirpath, dirnames, filenames in os.walk(path):
-        # skip '.git'
-        dirnames[:] = [d for d in dirnames if not d.startswith(".")]
-
-        for filename in filenames:
-            if filename_check is None or filename_check(filename):
-                yield os.path.join(dirpath, filename)
-
-
-# extension checking
-def is_cmake(filename: str) -> bool:
-    ext = splitext(filename)[1]
-    return (ext == ".cmake") or (filename == "CMakeLists.txt")
-
-
-def is_c_header(filename: str) -> bool:
-    ext = splitext(filename)[1]
-    return (ext in {".h", ".hpp", ".hxx", ".hh"})
-
-
-def is_c(filename: str) -> bool:
-    ext = splitext(filename)[1]
-    return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".metal"})
-
-
-def is_c_any(filename: str) -> bool:
-    return is_c(filename) or is_c_header(filename)
-
-
-def cmake_get_src(f: str) -> None:
-
-    sources_h = []
-    sources_c = []
-
-    filen = open(f, "r", encoding="utf8")
-    it: Optional[Iterator[str]] = iter(filen)
-    found = False
-    i = 0
-    # print(f)
-
-    def is_definition(l: str, f: str, i: int, name: str) -> bool:
-        if l.startswith("unset("):
-            return False
-
-        if ('set(%s' % name) in l or ('set(' in l and l.endswith(name)):
-            if len(l.split()) > 1:
-                raise Exception("strict formatting not kept 'set(%s*' %s:%d" % (name, f, i))
-            return True
-
-        if ("list(APPEND %s" % name) in l or ('list(APPEND ' in l and l.endswith(name)):
-            if l.endswith(")"):
-                raise Exception("strict formatting not kept 'list(APPEND %s...)' on 1 line %s:%d" % (name, f, i))
-            return True
-        return False
-
-    while it is not None:
-        context_name = ""
-        while it is not None:
-            i += 1
-            try:
-                l = next(it)
-            except StopIteration:
-                it = None
-                break
-            l = l.strip()
-            if not l.startswith("#"):
-                found = is_definition(l, f, i, "SRC")
-                if found:
-                    context_name = "SRC"
-                    break
-                found = is_definition(l, f, i, "INC")
-                if found:
-                    context_name = "INC"
-                    break
-
-        if found:
-            cmake_base = dirname(f)
-            cmake_base_bin = os.path.join(BUILD_DIR, os.path.relpath(cmake_base, SOURCE_DIR))
-
-            # Find known missing sources list (if we have one).
-            f_rel = os.path.relpath(f, SOURCE_DIR)
-            f_rel_key = f_rel
-            if os.sep != "/":
-                f_rel_key = f_rel_key.replace(os.sep, "/")
-            local_ignore_source_missing = global_ignore_source_missing.get(f_rel_key, [])
-
-            while it is not None:
-                i += 1
-                try:
-                    l = next(it)
-                except StopIteration:
-                    it = None
-                    break
-
-                l = l.strip()
-
-                if not l.startswith("#"):
-
-                    # Remove in-line comments.
-                    l = l.split(" # ")[0].rstrip()
-
-                    if ")" in l:
-                        if l.strip() != ")":
-                            raise Exception("strict formatting not kept '*)' %s:%d" % (f, i))
-                        break
-
-                    # replace dirs
-                    l = l.replace("${CMAKE_SOURCE_DIR}", SOURCE_DIR)
-                    l = l.replace("${CMAKE_CURRENT_SOURCE_DIR}", cmake_base)
-                    l = l.replace("${CMAKE_CURRENT_BINARY_DIR}", cmake_base_bin)
-                    l = l.strip('"')
-
-                    if not l:
-                        pass
-                    elif l in local_ignore_source_missing:
-                        local_ignore_source_missing.remove(l)
-                    elif l.startswith("$"):
-                        if context_name == "SRC":
-                            # assume if it ends with context_name we know about it
-                            if not l.split("}")[0].endswith(context_name):
-                                print("Can't use var '%s' %s:%d" % (l, f, i))
-                    elif len(l.split()) > 1:
-                        raise Exception("Multi-line define '%s' %s:%d" % (l, f, i))
-                    else:
-                        new_file = normpath(join(cmake_base, l))
-
-                        if context_name == "SRC":
-                            if is_c_header(new_file):
-                                sources_h.append(new_file)
-                                global_refs.setdefault(new_file, []).append((f, i))
-                            elif is_c(new_file):
-                                sources_c.append(new_file)
-                                global_refs.setdefault(new_file, []).append((f, i))
-                            elif l in {"PARENT_SCOPE", }:
-                                # cmake var, ignore
-                                pass
-                            elif new_file.endswith(".list"):
-                                pass
-                            elif new_file.endswith(".def"):
-                                pass
-                            elif new_file.endswith(".cl"):  # opencl
-                                pass
-                            elif new_file.endswith(".cu"):  # cuda
-                                pass
-                            elif new_file.endswith(".osl"):  # open shading language
-                                pass
-                            elif new_file.endswith(".glsl"):
-                                pass
-                            else:
-                                raise Exception("unknown file type - not c or h %s -> %s" % (f, new_file))
-
-                        elif context_name == "INC":
-                            if new_file.startswith(BUILD_DIR):
-                                # assume generated path
-                                pass
-                            elif os.path.isdir(new_file):
-                                new_path_rel = os.path.relpath(new_file, cmake_base)
-
-                                if new_path_rel != l:
-                                    print("overly relative path:\n  %s:%d\n  %s\n  %s" % (f, i, l, new_path_rel))
-
-                                    # # Save time. just replace the line
-                                    # replace_line(f, i - 1, new_path_rel)
-
-                            else:
-                                raise Exception("non existent include %s:%d -> %s" % (f, i, new_file))
-
-                        # print(new_file)
-
-            global_h.update(set(sources_h))
-            global_c.update(set(sources_c))
-            '''
-            if not sources_h and not sources_c:
-                raise Exception("No sources %s" % f)
-
-            sources_h_fs = list(source_list(cmake_base, is_c_header))
-            sources_c_fs = list(source_list(cmake_base, is_c))
-            '''
-            # find missing C files:
-            '''
-            for ff in sources_c_fs:
-                if ff not in sources_c:
-                    print("  missing: " + ff)
-            '''
-
-            # reset
-            del sources_h[:]
-            del sources_c[:]
-
-    filen.close()
-
-
-def is_ignore_source(f: str, ignore_used: List[bool]) -> bool:
-    for index, ignore_path in enumerate(IGNORE_SOURCE):
-        if ignore_path in f:
-            ignore_used[index] = True
-            return True
-    return False
-
-
-def is_ignore_cmake(f: str, ignore_used: List[bool]) -> bool:
-    for index, ignore_path in enumerate(IGNORE_CMAKE):
-        if ignore_path in f:
-            ignore_used[index] = True
-            return True
-    return False
-
-
-def main() -> None:
-
-    print("Scanning:", SOURCE_DIR)
-
-    ignore_used_source = [False] * len(IGNORE_SOURCE)
-    ignore_used_cmake = [False] * len(IGNORE_CMAKE)
-
-    for cmake in source_list(SOURCE_DIR, is_cmake):
-        if not is_ignore_cmake(cmake, ignore_used_cmake):
-            cmake_get_src(cmake)
-
-    # First do stupid check, do these files exist?
-    print("\nChecking for missing references:")
-    is_err = False
-    errs = []
-    for f in (global_h | global_c):
-        if f.startswith(BUILD_DIR):
-            continue
-
-        if not os.path.exists(f):
-            refs = global_refs[f]
-            if refs:
-                for cf, i in refs:
-                    errs.append((cf, i))
-            else:
-                raise Exception("CMake references missing, internal error, aborting!")
-            is_err = True
-
-    errs.sort()
-    errs.reverse()
-    for cf, i in errs:
-        print("%s:%d" % (cf, i))
-        # Write a 'sed' script, useful if we get a lot of these
-        # print("sed '%dd' '%s' > '%s.tmp' ; mv '%s.tmp' '%s'" % (i, cf, cf, cf, cf))
-
-    if is_err:
-        raise Exception("CMake references missing files, aborting!")
-    del is_err
-    del errs
-
-    # now check on files not accounted for.
-    print("\nC/C++ Files CMake does not know about...")
-    for cf in sorted(source_list(SOURCE_DIR, is_c)):
-        if not is_ignore_source(cf, ignore_used_source):
-            if cf not in global_c:
-                print("missing_c: ", cf)
-
-            # Check if automake builds a corresponding .o file.
-            '''
-            if cf in global_c:
-                out1 = os.path.splitext(cf)[0] + ".o"
-                out2 = os.path.splitext(cf)[0] + ".Po"
-                out2_dir, out2_file = out2 = os.path.split(out2)
-                out2 = os.path.join(out2_dir, ".deps", out2_file)
-                if not os.path.exists(out1) and not os.path.exists(out2):
-                    print("bad_c: ", cf)
-            '''
-
-    print("\nC/C++ Headers CMake does not know about...")
-    for hf in sorted(source_list(SOURCE_DIR, is_c_header)):
-        if not is_ignore_source(hf, ignore_used_source):
-            if hf not in global_h:
-                print("missing_h: ", hf)
-
-    if UTF8_CHECK:
-        # test encoding
-        import traceback
-        for files in (global_c, global_h):
-            for f in sorted(files):
-                if os.path.exists(f):
-                    # ignore outside of our source tree
-                    if "extern" not in f:
-                        i = 1
-                        try:
-                            for _ in open(f, "r", encoding="utf8"):
-                                i += 1
-                        except UnicodeDecodeError:
-                            print("Non utf8: %s:%d" % (f, i))
-                            if i > 1:
-                                traceback.print_exc()
-
-    # Check ignores aren't stale
-    print("\nCheck for unused 'IGNORE_SOURCE' paths...")
-    for index, ignore_path in enumerate(IGNORE_SOURCE):
-        if not ignore_used_source[index]:
-            print("unused ignore: %r" % ignore_path)
-
-    # Check ignores aren't stale
-    print("\nCheck for unused 'IGNORE_SOURCE_MISSING' paths...")
-    for k, v in sorted(global_ignore_source_missing.items()):
-        for ignore_path in v:
-            print("unused ignore: %r -> %r" % (ignore_path, k))
-
-    # Check ignores aren't stale
-    print("\nCheck for unused 'IGNORE_CMAKE' paths...")
-    for index, ignore_path in enumerate(IGNORE_CMAKE):
-        if not ignore_used_cmake[index]:
-            print("unused ignore: %r" % ignore_path)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/build_files/cmake/cmake_consistency_check_config.py b/build_files/cmake/cmake_consistency_check_config.py
deleted file mode 100644
index 94e5ddb4289..00000000000
--- a/build_files/cmake/cmake_consistency_check_config.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-import os
-
-IGNORE_SOURCE = (
-    "/test/",
-    "/tests/gtests/",
-    "/release/",
-
-    # specific source files
-    "extern/audaspace/",
-
-    # Use for `WIN32` only.
-    "source/creator/blender_launcher_win32.c",
-
-    # specific source files
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btBox2dBox2dCollisionAlgorithm.cpp",
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btConvex2dConvex2dAlgorithm.cpp",
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btInternalEdgeUtility.cpp",
-    "extern/bullet2/src/BulletCollision/CollisionShapes/btBox2dShape.cpp",
-    "extern/bullet2/src/BulletCollision/CollisionShapes/btConvex2dShape.cpp",
-    "extern/bullet2/src/BulletDynamics/Character/btKinematicCharacterController.cpp",
-    "extern/bullet2/src/BulletDynamics/ConstraintSolver/btHinge2Constraint.cpp",
-    "extern/bullet2/src/BulletDynamics/ConstraintSolver/btUniversalConstraint.cpp",
-
-    "doc/doxygen/doxygen.extern.h",
-    "doc/doxygen/doxygen.intern.h",
-    "doc/doxygen/doxygen.main.h",
-    "doc/doxygen/doxygen.source.h",
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btBox2dBox2dCollisionAlgorithm.h",
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btConvex2dConvex2dAlgorithm.h",
-    "extern/bullet2/src/BulletCollision/CollisionDispatch/btInternalEdgeUtility.h",
-    "extern/bullet2/src/BulletCollision/CollisionShapes/btBox2dShape.h",
-    "extern/bullet2/src/BulletCollision/CollisionShapes/btConvex2dShape.h",
-    "extern/bullet2/src/BulletDynamics/Character/btKinematicCharacterController.h",
-    "extern/bullet2/src/BulletDynamics/ConstraintSolver/btHinge2Constraint.h",
-    "extern/bullet2/src/BulletDynamics/ConstraintSolver/btUniversalConstraint.h",
-)
-
-# Ignore cmake file, path pairs.
-IGNORE_SOURCE_MISSING = (
-    (   # Use for cycles stand-alone.
-        "intern/cycles/util/CMakeLists.txt", (
-            "../../third_party/numaapi/include",
-        )),
-    (   # Use for `WITH_NANOVDB`.
-        "intern/cycles/kernel/CMakeLists.txt", (
-            "nanovdb/util/CSampleFromVoxels.h",
-            "nanovdb/util/SampleFromVoxels.h",
-            "nanovdb/NanoVDB.h",
-            "nanovdb/CNanoVDB.h",
-        ),
-    ),
-)
-
-IGNORE_CMAKE = (
-    "extern/audaspace/CMakeLists.txt",
-)
-
-UTF8_CHECK = True
-
-SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))))
-
-# doesn't have to exist, just use as reference
-BUILD_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(SOURCE_DIR, "..", "build"))))
-- 
cgit v1.2.3


From f3ef0763b41155e6234343de900b207bb5b2e20d Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 22 Feb 2022 21:52:48 -0500
Subject: Cleanup: Use new curves type enum for CurveEval

Though this is less aesthetically pleasing, it makes the transition to the
new curves type (T95941) a bit simpler, and it has to be done anyway.
---
 source/blender/blenkernel/BKE_spline.hh            |  20 +-
 source/blender/blenkernel/intern/curve_eval.cc     |   2 +-
 .../blenkernel/intern/curve_to_mesh_convert.cc     |   2 +-
 .../blenkernel/intern/geometry_component_curve.cc  |  27 +-
 .../blenkernel/intern/geometry_component_curves.cc | 517 +++++++++++++++++++++
 source/blender/blenkernel/intern/spline_base.cc    |  13 +-
 .../node_geo_legacy_curve_select_by_handle_type.cc |   2 +-
 .../legacy/node_geo_legacy_curve_set_handles.cc    |   2 +-
 .../legacy/node_geo_legacy_curve_spline_type.cc    |  12 +-
 .../legacy/node_geo_legacy_curve_subdivide.cc      |   6 +-
 .../legacy/node_geo_legacy_delete_geometry.cc      |   6 +-
 .../nodes/geometry/nodes/node_geo_curve_fillet.cc  |   6 +-
 .../nodes/node_geo_curve_handle_type_selection.cc  |   2 +-
 .../geometry/nodes/node_geo_curve_set_handles.cc   |   2 +-
 .../nodes/node_geo_curve_spline_parameter.cc       |   6 +-
 .../geometry/nodes/node_geo_curve_spline_type.cc   |  12 +-
 .../geometry/nodes/node_geo_curve_subdivide.cc     |   6 +-
 .../nodes/geometry/nodes/node_geo_curve_trim.cc    |  12 +-
 .../geometry/nodes/node_geo_delete_geometry.cc     |   6 +-
 .../nodes/geometry/nodes/node_geo_input_tangent.cc |   8 +-
 .../geometry/nodes/node_geo_set_curve_handles.cc   |   4 +-
 .../nodes/node_geo_set_spline_resolution.cc        |   2 +-
 22 files changed, 596 insertions(+), 79 deletions(-)
 create mode 100644 source/blender/blenkernel/intern/geometry_component_curves.cc

diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 646af6f8f98..439f20ee471 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -51,12 +51,6 @@ using SplinePtr = std::unique_ptr;
  */
 class Spline {
  public:
-  enum class Type {
-    Bezier,
-    NURBS,
-    Poly,
-  };
-
   enum NormalCalculationMode {
     ZUp,
     Minimum,
@@ -67,7 +61,7 @@ class Spline {
   blender::bke::CustomDataAttributes attributes;
 
  protected:
-  Type type_;
+  CurveType type_;
   bool is_cyclic_ = false;
 
   /** Direction of the spline at each evaluated point. */
@@ -87,7 +81,7 @@ class Spline {
 
  public:
   virtual ~Spline() = default;
-  Spline(const Type type) : type_(type)
+  Spline(const CurveType type) : type_(type)
   {
   }
   Spline(Spline &other) : attributes(other.attributes), type_(other.type_)
@@ -109,7 +103,7 @@ class Spline {
   SplinePtr copy_without_attributes() const;
   static void copy_base_settings(const Spline &src, Spline &dst);
 
-  Spline::Type type() const;
+  CurveType type() const;
 
   /** Return the number of control points. */
   virtual int size() const = 0;
@@ -285,7 +279,7 @@ class BezierSpline final : public Spline {
   mutable bool mapping_cache_dirty_ = true;
 
  public:
-  BezierSpline() : Spline(Type::Bezier)
+  BezierSpline() : Spline(CURVE_TYPE_BEZIER)
   {
   }
   BezierSpline(const BezierSpline &other)
@@ -508,7 +502,7 @@ class NURBSpline final : public Spline {
   mutable bool position_cache_dirty_ = true;
 
  public:
-  NURBSpline() : Spline(Type::NURBS)
+  NURBSpline() : Spline(CURVE_TYPE_NURBS)
   {
   }
   NURBSpline(const NURBSpline &other)
@@ -575,7 +569,7 @@ class PolySpline final : public Spline {
   blender::Vector tilts_;
 
  public:
-  PolySpline() : Spline(Type::Poly)
+  PolySpline() : Spline(CURVE_TYPE_POLY)
   {
   }
   PolySpline(const PolySpline &other)
@@ -647,7 +641,7 @@ struct CurveEval {
    * \note If you are looping over all of the splines in the same scope anyway,
    * it's better to avoid calling this function, in case there are many splines.
    */
-  bool has_spline_with_type(const Spline::Type type) const;
+  bool has_spline_with_type(const CurveType type) const;
 
   void resize(int size);
   /**
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 49010caef24..8529e7ad194 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -36,7 +36,7 @@ blender::MutableSpan CurveEval::splines()
   return splines_;
 }
 
-bool CurveEval::has_spline_with_type(const Spline::Type type) const
+bool CurveEval::has_spline_with_type(const CurveType type) const
 {
   for (const SplinePtr &spline : this->splines()) {
     if (spline->type() == type) {
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
index e62733e36ef..5d80ef47908 100644
--- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -226,7 +226,7 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info,
   }
 
   /* Mark edge loops from sharp vector control points sharp. */
-  if (profile.type() == Spline::Type::Bezier) {
+  if (profile.type() == CURVE_TYPE_BEZIER) {
     const BezierSpline &bezier_spline = static_cast(profile);
     Span control_point_offsets = bezier_spline.control_point_offsets();
     for (const int i : IndexRange(bezier_spline.size())) {
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index fff77dbd367..2004f1c0609 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -420,15 +420,18 @@ static Array curve_normal_point_domain(const CurveEval &curve)
       const Spline &spline = *splines[i];
       MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
       switch (splines[i]->type()) {
-        case Spline::Type::Bezier:
+        case CURVE_TYPE_BEZIER:
           calculate_bezier_normals(static_cast(spline), spline_normals);
           break;
-        case Spline::Type::Poly:
+        case CURVE_TYPE_POLY:
           calculate_poly_normals(static_cast(spline), spline_normals);
           break;
-        case Spline::Type::NURBS:
+        case CURVE_TYPE_NURBS:
           calculate_nurbs_normals(static_cast(spline), spline_normals);
           break;
+        case CURVE_TYPE_CATMULL_ROM:
+          BLI_assert_unreachable();
+          break;
       }
     }
   });
@@ -447,7 +450,7 @@ VArray curve_normals_varray(const CurveComponent &component, const Attri
 
     /* Use a reference to evaluated normals if possible to avoid an allocation and a copy.
      * This is only possible when there is only one poly spline. */
-    if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) {
+    if (splines.size() == 1 && splines.first()->type() == CURVE_TYPE_POLY) {
       const PolySpline &spline = static_cast(*splines.first());
       return VArray::ForSpan(spline.evaluated_normals());
     }
@@ -955,7 +958,7 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
   {
     const PointIndices indices = lookup_point_indices(offsets_, index);
     const Spline &spline = *splines_[indices.spline_index];
-    if (spline.type() == Spline::Type::Bezier) {
+    if (spline.type() == CURVE_TYPE_BEZIER) {
       const BezierSpline &bezier_spline = static_cast(spline);
       return is_right_ ? bezier_spline.handle_positions_right()[indices.point_index] :
                          bezier_spline.handle_positions_left()[indices.point_index];
@@ -967,7 +970,7 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
   {
     const PointIndices indices = lookup_point_indices(offsets_, index);
     Spline &spline = *splines_[indices.spline_index];
-    if (spline.type() == Spline::Type::Bezier) {
+    if (spline.type() == CURVE_TYPE_BEZIER) {
       BezierSpline &bezier_spline = static_cast(spline);
       if (is_right_) {
         bezier_spline.set_handle_position_right(indices.point_index, value);
@@ -983,7 +986,7 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
   {
     for (const int spline_index : splines_.index_range()) {
       Spline &spline = *splines_[spline_index];
-      if (spline.type() == Spline::Type::Bezier) {
+      if (spline.type() == CURVE_TYPE_BEZIER) {
         const int offset = offsets_[spline_index];
 
         BezierSpline &bezier_spline = static_cast(spline);
@@ -1024,7 +1027,7 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
   {
     Array> spans(splines.size());
     for (const int i : spans.index_range()) {
-      if (splines[i]->type() == Spline::Type::Bezier) {
+      if (splines[i]->type() == CURVE_TYPE_BEZIER) {
         BezierSpline &bezier_spline = static_cast(*splines[i]);
         spans[i] = is_right ? bezier_spline.handle_positions_right() :
                               bezier_spline.handle_positions_left();
@@ -1214,7 +1217,7 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProviderhas_spline_with_type(Spline::Type::Bezier)) {
+    if (!curve->has_spline_with_type(CURVE_TYPE_BEZIER)) {
       return BuiltinPointAttributeProvider::try_get_for_write(component);
     }
 
@@ -1255,7 +1258,7 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider {
       return {};
     }
 
-    if (!curve->has_spline_with_type(Spline::Type::Bezier)) {
+    if (!curve->has_spline_with_type(CURVE_TYPE_BEZIER)) {
       return {};
     }
 
@@ -1273,7 +1276,7 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider {
       return {};
     }
 
-    if (!curve->has_spline_with_type(Spline::Type::Bezier)) {
+    if (!curve->has_spline_with_type(CURVE_TYPE_BEZIER)) {
       return {};
     }
 
@@ -1304,7 +1307,7 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider {
       return false;
     }
 
-    return curve->has_spline_with_type(Spline::Type::Bezier) &&
+    return curve->has_spline_with_type(CURVE_TYPE_BEZIER) &&
            component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0;
   }
 };
diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc
new file mode 100644
index 00000000000..07b71ab5ae7
--- /dev/null
+++ b/source/blender/blenkernel/intern/geometry_component_curves.cc
@@ -0,0 +1,517 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_task.hh"
+
+#include "DNA_ID_enums.h"
+#include "DNA_curve_types.h"
+
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
+#include "BKE_curve.h"
+#include "BKE_curves.hh"
+#include "BKE_geometry_set.hh"
+#include "BKE_lib_id.h"
+#include "BKE_spline.hh"
+
+#include "attribute_access_intern.hh"
+
+using blender::fn::GVArray;
+
+/* -------------------------------------------------------------------- */
+/** \name Geometry Component Implementation
+ * \{ */
+
+CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
+{
+}
+
+CurveComponent::~CurveComponent()
+{
+  this->clear();
+}
+
+GeometryComponent *CurveComponent::copy() const
+{
+  CurveComponent *new_component = new CurveComponent();
+  if (curves_ != nullptr) {
+    new_component->curves_ = BKE_curves_copy_for_eval(curves_, false);
+    new_component->ownership_ = GeometryOwnershipType::Owned;
+  }
+  return new_component;
+}
+
+void CurveComponent::clear()
+{
+  BLI_assert(this->is_mutable());
+  if (curves_ != nullptr) {
+    if (ownership_ == GeometryOwnershipType::Owned) {
+      BKE_id_free(nullptr, curves_);
+    }
+    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;
+    }
+
+    curves_ = nullptr;
+  }
+}
+
+bool CurveComponent::has_curves() const
+{
+  return curves_ != nullptr;
+}
+
+void CurveComponent::replace(Curves *curves, GeometryOwnershipType ownership)
+{
+  BLI_assert(this->is_mutable());
+  this->clear();
+  curves_ = curves;
+  ownership_ = ownership;
+}
+
+Curves *CurveComponent::release()
+{
+  BLI_assert(this->is_mutable());
+  Curves *curves = curves_;
+  curves_ = nullptr;
+  return curves;
+}
+
+const Curves *CurveComponent::get_for_read() const
+{
+  return curves_;
+}
+
+Curves *CurveComponent::get_for_write()
+{
+  BLI_assert(this->is_mutable());
+  if (ownership_ == GeometryOwnershipType::ReadOnly) {
+    curves_ = BKE_curves_copy_for_eval(curves_, false);
+    ownership_ = GeometryOwnershipType::Owned;
+  }
+  return curves_;
+}
+
+bool CurveComponent::is_empty() const
+{
+  return curves_ == nullptr;
+}
+
+bool CurveComponent::owns_direct_data() const
+{
+  return ownership_ == GeometryOwnershipType::Owned;
+}
+
+void CurveComponent::ensure_owns_direct_data()
+{
+  BLI_assert(this->is_mutable());
+  if (ownership_ != GeometryOwnershipType::Owned) {
+    curves_ = BKE_curves_copy_for_eval(curves_, false);
+    ownership_ = GeometryOwnershipType::Owned;
+  }
+}
+
+const Curve *CurveComponent::get_curve_for_render() const
+{
+  if (curves_ == nullptr) {
+    return nullptr;
+  }
+  if (curve_for_render_ != nullptr) {
+    return curve_for_render_;
+  }
+  std::lock_guard lock{curve_for_render_mutex_};
+  if (curve_for_render_ != nullptr) {
+    return curve_for_render_;
+  }
+
+  curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU_LEGACY, nullptr);
+  curve_for_render_->curve_eval = curves_to_curve_eval(*curves_).release();
+
+  return curve_for_render_;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Curve Normals Access
+ * \{ */
+
+namespace blender::bke {
+
+static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan normals)
+{
+  Span offsets = spline.control_point_offsets();
+  Span evaluated_normals = spline.evaluated_normals();
+  for (const int i : IndexRange(spline.size())) {
+    normals[i] = evaluated_normals[offsets[i]];
+  }
+}
+
+static void calculate_poly_normals(const PolySpline &spline, MutableSpan normals)
+{
+  normals.copy_from(spline.evaluated_normals());
+}
+
+/**
+ * Because NURBS control points are not necessarily on the path, the normal at the control points
+ * is not well defined, so create a temporary poly spline to find the normals. This requires extra
+ * copying currently, but may be more efficient in the future if attributes have some form of CoW.
+ */
+static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan normals)
+{
+  PolySpline poly_spline;
+  poly_spline.resize(spline.size());
+  poly_spline.positions().copy_from(spline.positions());
+  poly_spline.tilts().copy_from(spline.tilts());
+  normals.copy_from(poly_spline.evaluated_normals());
+}
+
+static Array curve_normal_point_domain(const CurveEval &curve)
+{
+  Span splines = curve.splines();
+  Array offsets = curve.control_point_offsets();
+  const int total_size = offsets.last();
+  Array normals(total_size);
+
+  threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
+    for (const int i : range) {
+      const Spline &spline = *splines[i];
+      MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
+      switch (splines[i]->type()) {
+        case CURVE_TYPE_BEZIER:
+          calculate_bezier_normals(static_cast(spline), spline_normals);
+          break;
+        case CURVE_TYPE_POLY:
+          calculate_poly_normals(static_cast(spline), spline_normals);
+          break;
+        case CURVE_TYPE_NURBS:
+          calculate_nurbs_normals(static_cast(spline), spline_normals);
+          break;
+      }
+    }
+  });
+  return normals;
+}
+
+VArray curve_normals_varray(const CurveComponent &component, const AttributeDomain domain)
+{
+  if (component.is_empty()) {
+    return nullptr;
+  }
+  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
+
+  if (domain == ATTR_DOMAIN_POINT) {
+    Array normals = curve_normal_point_domain(*curve);
+    return VArray::ForContainer(std::move(normals));
+  }
+
+  if (domain == ATTR_DOMAIN_CURVE) {
+    Array point_normals = curve_normal_point_domain(*curve);
+    VArray varray = VArray::ForContainer(std::move(point_normals));
+    return component.attribute_try_adapt_domain(
+        std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+  }
+
+  return nullptr;
+}
+
+}  // namespace blender::bke
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Attribute Access Helper Functions
+ * \{ */
+
+int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
+{
+  if (curves_ == nullptr) {
+    return 0;
+  }
+  const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(
+      curves_->geometry);
+  if (domain == ATTR_DOMAIN_POINT) {
+    return geometry.points_size();
+  }
+  if (domain == ATTR_DOMAIN_CURVE) {
+    return geometry.curves_size();
+  }
+  return 0;
+}
+
+GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray,
+                                                        const AttributeDomain from_domain,
+                                                        const AttributeDomain to_domain) const
+{
+  return blender::bke::CurvesGeometry::wrap(curves_->geometry)
+      .adapt_domain(varray, from_domain, to_domain);
+}
+
+static Curves *get_curves_from_component_for_write(GeometryComponent &component)
+{
+  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
+  CurveComponent &curve_component = static_cast(component);
+  return curve_component.get_for_write();
+}
+
+static const Curves *get_curves_from_component_for_read(const GeometryComponent &component)
+{
+  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
+  const CurveComponent &curve_component = static_cast(component);
+  return curve_component.get_for_read();
+}
+
+static void tag_component_topology_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_topology_changed();
+  }
+}
+
+static void tag_component_positions_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_positions_changed();
+  }
+}
+
+static void tag_component_normals_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_normals_changed();
+  }
+}
+
+/** \} */
+
+namespace blender::bke {
+
+/* -------------------------------------------------------------------- */
+/** \name Attribute Provider Declaration
+ * \{ */
+
+/**
+ * In this function all the attribute providers for a curves component are created.
+ * Most data in this function is statically allocated, because it does not change over time.
+ */
+static ComponentAttributeProviders create_attribute_providers_for_curve()
+{
+  static CustomDataAccessInfo curve_access = {
+      [](GeometryComponent &component) -> CustomData * {
+        Curves *curves = get_curves_from_component_for_write(component);
+        return curves ? &curves->geometry.curve_data : nullptr;
+      },
+      [](const GeometryComponent &component) -> const CustomData * {
+        const Curves *curves = get_curves_from_component_for_read(component);
+        return curves ? &curves->geometry.curve_data : nullptr;
+      },
+      [](GeometryComponent &component) {
+        Curves *curves = get_curves_from_component_for_write(component);
+        if (curves) {
+          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
+        }
+      }};
+  static CustomDataAccessInfo point_access = {
+      [](GeometryComponent &component) -> CustomData * {
+        Curves *curves = get_curves_from_component_for_write(component);
+        return curves ? &curves->geometry.point_data : nullptr;
+      },
+      [](const GeometryComponent &component) -> const CustomData * {
+        const Curves *curves = get_curves_from_component_for_read(component);
+        return curves ? &curves->geometry.point_data : nullptr;
+      },
+      [](GeometryComponent &component) {
+        Curves *curves = get_curves_from_component_for_write(component);
+        if (curves) {
+          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
+        }
+      }};
+
+  static BuiltinCustomDataLayerProvider position("position",
+                                                 ATTR_DOMAIN_POINT,
+                                                 CD_PROP_FLOAT3,
+                                                 CD_PROP_FLOAT3,
+                                                 BuiltinAttributeProvider::NonCreatable,
+                                                 BuiltinAttributeProvider::Writable,
+                                                 BuiltinAttributeProvider::NonDeletable,
+                                                 point_access,
+                                                 make_array_read_attribute,
+                                                 make_array_write_attribute,
+                                                 tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider radius("radius",
+                                               ATTR_DOMAIN_POINT,
+                                               CD_PROP_FLOAT,
+                                               CD_PROP_FLOAT,
+                                               BuiltinAttributeProvider::Creatable,
+                                               BuiltinAttributeProvider::Writable,
+                                               BuiltinAttributeProvider::Deletable,
+                                               point_access,
+                                               make_array_read_attribute,
+                                               make_array_write_attribute,
+                                               tag_component_normals_changed);
+
+  static BuiltinCustomDataLayerProvider id("id",
+                                           ATTR_DOMAIN_POINT,
+                                           CD_PROP_INT32,
+                                           CD_PROP_INT32,
+                                           BuiltinAttributeProvider::Creatable,
+                                           BuiltinAttributeProvider::Writable,
+                                           BuiltinAttributeProvider::Deletable,
+                                           point_access,
+                                           make_array_read_attribute,
+                                           make_array_write_attribute,
+                                           nullptr);
+
+  static BuiltinCustomDataLayerProvider tilt("tilt",
+                                             ATTR_DOMAIN_POINT,
+                                             CD_PROP_FLOAT,
+                                             CD_PROP_FLOAT,
+                                             BuiltinAttributeProvider::Creatable,
+                                             BuiltinAttributeProvider::Writable,
+                                             BuiltinAttributeProvider::Deletable,
+                                             point_access,
+                                             make_array_read_attribute,
+                                             make_array_write_attribute,
+                                             tag_component_normals_changed);
+
+  static BuiltinCustomDataLayerProvider handle_right("handle_right",
+                                                     ATTR_DOMAIN_POINT,
+                                                     CD_PROP_FLOAT3,
+                                                     CD_PROP_FLOAT3,
+                                                     BuiltinAttributeProvider::Creatable,
+                                                     BuiltinAttributeProvider::Writable,
+                                                     BuiltinAttributeProvider::Deletable,
+                                                     point_access,
+                                                     make_array_read_attribute,
+                                                     make_array_write_attribute,
+                                                     tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider handle_left("handle_left",
+                                                    ATTR_DOMAIN_POINT,
+                                                    CD_PROP_FLOAT3,
+                                                    CD_PROP_FLOAT3,
+                                                    BuiltinAttributeProvider::Creatable,
+                                                    BuiltinAttributeProvider::Writable,
+                                                    BuiltinAttributeProvider::Deletable,
+                                                    point_access,
+                                                    make_array_read_attribute,
+                                                    make_array_write_attribute,
+                                                    tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
+                                                          ATTR_DOMAIN_POINT,
+                                                          CD_PROP_INT8,
+                                                          CD_PROP_INT8,
+                                                          BuiltinAttributeProvider::Creatable,
+                                                          BuiltinAttributeProvider::Writable,
+                                                          BuiltinAttributeProvider::Deletable,
+                                                          point_access,
+                                                          make_array_read_attribute,
+                                                          make_array_write_attribute,
+                                                          tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
+                                                         ATTR_DOMAIN_POINT,
+                                                         CD_PROP_INT8,
+                                                         CD_PROP_INT8,
+                                                         BuiltinAttributeProvider::Creatable,
+                                                         BuiltinAttributeProvider::Writable,
+                                                         BuiltinAttributeProvider::Deletable,
+                                                         point_access,
+                                                         make_array_read_attribute,
+                                                         make_array_write_attribute,
+                                                         tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
+                                                     ATTR_DOMAIN_POINT,
+                                                     CD_PROP_FLOAT,
+                                                     CD_PROP_FLOAT,
+                                                     BuiltinAttributeProvider::Creatable,
+                                                     BuiltinAttributeProvider::Writable,
+                                                     BuiltinAttributeProvider::Deletable,
+                                                     point_access,
+                                                     make_array_read_attribute,
+                                                     make_array_write_attribute,
+                                                     tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
+                                                    ATTR_DOMAIN_CURVE,
+                                                    CD_PROP_INT32,
+                                                    CD_PROP_INT32,
+                                                    BuiltinAttributeProvider::Creatable,
+                                                    BuiltinAttributeProvider::Writable,
+                                                    BuiltinAttributeProvider::Deletable,
+                                                    curve_access,
+                                                    make_array_read_attribute,
+                                                    make_array_write_attribute,
+                                                    tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
+                                                         ATTR_DOMAIN_CURVE,
+                                                         CD_PROP_INT8,
+                                                         CD_PROP_INT8,
+                                                         BuiltinAttributeProvider::Creatable,
+                                                         BuiltinAttributeProvider::Writable,
+                                                         BuiltinAttributeProvider::Deletable,
+                                                         curve_access,
+                                                         make_array_read_attribute,
+                                                         make_array_write_attribute,
+                                                         tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider resolution("resolution",
+                                                   ATTR_DOMAIN_CURVE,
+                                                   CD_PROP_INT32,
+                                                   CD_PROP_INT32,
+                                                   BuiltinAttributeProvider::Creatable,
+                                                   BuiltinAttributeProvider::Writable,
+                                                   BuiltinAttributeProvider::Deletable,
+                                                   curve_access,
+                                                   make_array_read_attribute,
+                                                   make_array_write_attribute,
+                                                   tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider cyclic("cyclic",
+                                               ATTR_DOMAIN_CURVE,
+                                               CD_PROP_BOOL,
+                                               CD_PROP_BOOL,
+                                               BuiltinAttributeProvider::Creatable,
+                                               BuiltinAttributeProvider::Writable,
+                                               BuiltinAttributeProvider::Deletable,
+                                               curve_access,
+                                               make_array_read_attribute,
+                                               make_array_write_attribute,
+                                               tag_component_topology_changed);
+
+  static CustomDataAttributeProvider curve_custom_data(ATTR_DOMAIN_CURVE, curve_access);
+  static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access);
+
+  return ComponentAttributeProviders({&position,
+                                      &radius,
+                                      &id,
+                                      &tilt,
+                                      &handle_right,
+                                      &handle_left,
+                                      &handle_type_right,
+                                      &handle_type_left,
+                                      &nurbs_order,
+                                      &nurbs_weight & resolution,
+                                      &cyclic},
+                                     {&curve_custom_data, &point_custom_data});
+}
+
+/** \} */
+
+}  // namespace blender::bke
+
+const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const
+{
+  static blender::bke::ComponentAttributeProviders providers =
+      blender::bke::create_attribute_providers_for_curve();
+  return &providers;
+}
diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc
index d1c4756a3b9..dc5b1d28539 100644
--- a/source/blender/blenkernel/intern/spline_base.cc
+++ b/source/blender/blenkernel/intern/spline_base.cc
@@ -23,7 +23,7 @@ using blender::fn::GMutableSpan;
 using blender::fn::GSpan;
 using blender::fn::GVArray;
 
-Spline::Type Spline::type() const
+CurveType Spline::type() const
 {
   return type_;
 }
@@ -34,15 +34,18 @@ void Spline::copy_base_settings(const Spline &src, Spline &dst)
   dst.is_cyclic_ = src.is_cyclic_;
 }
 
-static SplinePtr create_spline(const Spline::Type type)
+static SplinePtr create_spline(const CurveType type)
 {
   switch (type) {
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       return std::make_unique();
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       return std::make_unique();
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       return std::make_unique();
+    case CURVE_TYPE_CATMULL_ROM:
+      BLI_assert_unreachable();
+      return {};
   }
   BLI_assert_unreachable();
   return {};
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
index 091726bdf84..a09a751b550 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
@@ -59,7 +59,7 @@ static void select_curve_by_handle_type(const CurveEval &curve,
   threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
     for (const int i_spline : range) {
       const Spline &spline = *splines[i_spline];
-      if (spline.type() == Spline::Type::Bezier) {
+      if (spline.type() == CURVE_TYPE_BEZIER) {
         const BezierSpline &bezier_spline = static_cast(spline);
         Span types_left = bezier_spline.handle_types_left();
         Span types_right = bezier_spline.handle_types_right();
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
index 92dbd6afcaa..e5d4d6c1d0f 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
@@ -74,7 +74,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   int point_index = 0;
   bool has_bezier_spline = false;
   for (SplinePtr &spline : splines) {
-    if (spline->type() != Spline::Type::Bezier) {
+    if (spline->type() != CURVE_TYPE_BEZIER) {
       point_index += spline->positions().size();
       continue;
     }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
index e0ce5ae1a97..0826a78b2b9 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
@@ -183,11 +183,11 @@ static SplinePtr nurbs_to_bezier(const Spline &input)
 static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params)
 {
   switch (input.type()) {
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       return input.copy();
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       return poly_to_bezier(input);
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       if (input.size() < 6) {
         params.error_message_add(
             NodeWarningType::Info,
@@ -210,11 +210,11 @@ static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params
 static SplinePtr convert_to_nurbs(const Spline &input)
 {
   switch (input.type()) {
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       return input.copy();
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       return bezier_to_nurbs(input);
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       return poly_to_nurbs(input);
   }
   BLI_assert_unreachable();
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
index 38930d5db85..06f0e633e40 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
@@ -235,20 +235,20 @@ static void subdivide_builtin_attributes(const Spline &src_spline,
   subdivide_attribute(src_spline.radii(), offsets, is_cyclic, dst_spline.radii());
   subdivide_attribute(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts());
   switch (src_spline.type()) {
-    case Spline::Type::Poly: {
+    case CURVE_TYPE_POLY: {
       const PolySpline &src = static_cast(src_spline);
       PolySpline &dst = static_cast(dst_spline);
       subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions());
       break;
     }
-    case Spline::Type::Bezier: {
+    case CURVE_TYPE_BEZIER: {
       const BezierSpline &src = static_cast(src_spline);
       BezierSpline &dst = static_cast(dst_spline);
       subdivide_bezier_spline(src, offsets, dst);
       dst.mark_cache_invalid();
       break;
     }
-    case Spline::Type::NURBS: {
+    case CURVE_TYPE_NURBS: {
       const NURBSpline &src = static_cast(src_spline);
       NURBSpline &dst = static_cast(dst_spline);
       subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions());
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
index a0b862546bc..3dc40639627 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
@@ -111,9 +111,9 @@ static void spline_copy_builtin_attributes(const Spline &spline,
   copy_data(spline.radii(), r_spline.radii(), mask);
   copy_data(spline.tilts(), r_spline.tilts(), mask);
   switch (spline.type()) {
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       break;
-    case Spline::Type::Bezier: {
+    case CURVE_TYPE_BEZIER: {
       const BezierSpline &src = static_cast(spline);
       BezierSpline &dst = static_cast(r_spline);
       copy_data(src.handle_positions_left(), dst.handle_positions_left(), mask);
@@ -122,7 +122,7 @@ static void spline_copy_builtin_attributes(const Spline &spline,
       copy_data(src.handle_types_right(), dst.handle_types_right(), mask);
       break;
     }
-    case Spline::Type::NURBS: {
+    case CURVE_TYPE_NURBS: {
       const NURBSpline &src = static_cast(spline);
       NURBSpline &dst = static_cast(r_spline);
       copy_data(src.weights(), dst.weights(), mask);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
index 149b387b179..ef402d41268 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -512,7 +512,7 @@ static SplinePtr fillet_spline(const Spline &spline,
   copy_common_attributes_by_mapping(spline, *dst_spline_ptr, dst_to_src);
 
   switch (spline.type()) {
-    case Spline::Type::Bezier: {
+    case CURVE_TYPE_BEZIER: {
       const BezierSpline &src_spline = static_cast(spline);
       BezierSpline &dst_spline = static_cast(*dst_spline_ptr);
       if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) {
@@ -525,11 +525,11 @@ static SplinePtr fillet_spline(const Spline &spline,
       }
       break;
     }
-    case Spline::Type::Poly: {
+    case CURVE_TYPE_POLY: {
       update_poly_positions(fd, *dst_spline_ptr, spline, point_counts);
       break;
     }
-    case Spline::Type::NURBS: {
+    case CURVE_TYPE_NURBS: {
       const NURBSpline &src_spline = static_cast(spline);
       NURBSpline &dst_spline = static_cast(*dst_spline_ptr);
       copy_attribute_by_mapping(src_spline.weights(), dst_spline.weights(), dst_to_src);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
index a7ef06a48b0..8dc74aa3dea 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
@@ -54,7 +54,7 @@ static void select_by_handle_type(const CurveEval &curve,
 {
   int offset = 0;
   for (const SplinePtr &spline : curve.splines()) {
-    if (spline->type() != Spline::Type::Bezier) {
+    if (spline->type() != CURVE_TYPE_BEZIER) {
       r_selection.slice(offset, spline->size()).fill(false);
       offset += spline->size();
     }
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 fdc717bb7ee..9f50b29d995 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
@@ -81,7 +81,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     int point_index = 0;
 
     for (SplinePtr &spline : splines) {
-      if (spline->type() != Spline::Type::Bezier) {
+      if (spline->type() != CURVE_TYPE_BEZIER) {
         point_index += spline->positions().size();
         continue;
       }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
index a303be99242..f4343965d66 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
@@ -100,15 +100,15 @@ static Array curve_length_point_domain(const CurveEval &curve)
       MutableSpan spline_factors{lengths.as_mutable_span().slice(offsets[i], spline.size())};
       spline_factors.first() = 0.0f;
       switch (splines[i]->type()) {
-        case Spline::Type::Bezier: {
+        case CURVE_TYPE_BEZIER: {
           calculate_bezier_lengths(static_cast(spline), spline_factors);
           break;
         }
-        case Spline::Type::Poly: {
+        case CURVE_TYPE_POLY: {
           calculate_poly_length(static_cast(spline), spline_factors);
           break;
         }
-        case Spline::Type::NURBS: {
+        case CURVE_TYPE_NURBS: {
           calculate_nurbs_lengths(static_cast(spline), spline_factors);
           break;
         }
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 aec2e106315..c79d1da8b6c 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
@@ -315,11 +315,11 @@ static SplinePtr nurbs_to_bezier(const Spline &input)
 static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params)
 {
   switch (input.type()) {
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       return input.copy();
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       return poly_to_bezier(input);
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       if (input.size() < 4) {
         params.error_message_add(
             NodeWarningType::Info,
@@ -335,11 +335,11 @@ static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params
 static SplinePtr convert_to_nurbs(const Spline &input)
 {
   switch (input.type()) {
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       return input.copy();
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       return bezier_to_nurbs(input);
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       return poly_to_nurbs(input);
   }
   BLI_assert_unreachable();
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 79576854548..6e8a505f9c4 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -217,20 +217,20 @@ static void subdivide_builtin_attributes(const Spline &src_spline,
   subdivide_attribute(src_spline.radii(), offsets, is_cyclic, dst_spline.radii());
   subdivide_attribute(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts());
   switch (src_spline.type()) {
-    case Spline::Type::Poly: {
+    case CURVE_TYPE_POLY: {
       const PolySpline &src = static_cast(src_spline);
       PolySpline &dst = static_cast(dst_spline);
       subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions());
       break;
     }
-    case Spline::Type::Bezier: {
+    case CURVE_TYPE_BEZIER: {
       const BezierSpline &src = static_cast(src_spline);
       BezierSpline &dst = static_cast(dst_spline);
       subdivide_bezier_spline(src, offsets, dst);
       dst.mark_cache_invalid();
       break;
     }
-    case Spline::Type::NURBS: {
+    case CURVE_TYPE_NURBS: {
       const NURBSpline &src = static_cast(src_spline);
       NURBSpline &dst = static_cast(dst_spline);
       subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions());
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 e6d40a8b6f7..652377065b0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -367,13 +367,13 @@ static void trim_spline(SplinePtr &spline,
                         const Spline::LookupResult end)
 {
   switch (spline->type()) {
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       trim_bezier_spline(*spline, start, end);
       break;
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       trim_poly_spline(*spline, start, end);
       break;
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       spline = std::make_unique(trim_nurbs_spline(*spline, start, end));
       break;
   }
@@ -477,13 +477,13 @@ static PolySpline to_single_point_nurbs(const Spline &spline, const Spline::Look
 static void to_single_point_spline(SplinePtr &spline, const Spline::LookupResult &lookup)
 {
   switch (spline->type()) {
-    case Spline::Type::Bezier:
+    case CURVE_TYPE_BEZIER:
       to_single_point_bezier(*spline, lookup);
       break;
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       to_single_point_poly(*spline, lookup);
       break;
-    case Spline::Type::NURBS:
+    case CURVE_TYPE_NURBS:
       spline = std::make_unique(to_single_point_nurbs(*spline, lookup));
       break;
   }
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 2eeb957c123..60aabc7ce37 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -332,9 +332,9 @@ static void spline_copy_builtin_attributes(const Spline &spline,
   copy_data_based_on_mask(spline.radii(), r_spline.radii(), mask);
   copy_data_based_on_mask(spline.tilts(), r_spline.tilts(), mask);
   switch (spline.type()) {
-    case Spline::Type::Poly:
+    case CURVE_TYPE_POLY:
       break;
-    case Spline::Type::Bezier: {
+    case CURVE_TYPE_BEZIER: {
       const BezierSpline &src = static_cast(spline);
       BezierSpline &dst = static_cast(r_spline);
       copy_data_based_on_mask(src.handle_positions_left(), dst.handle_positions_left(), mask);
@@ -343,7 +343,7 @@ static void spline_copy_builtin_attributes(const Spline &spline,
       copy_data_based_on_mask(src.handle_types_right(), dst.handle_types_right(), mask);
       break;
     }
-    case Spline::Type::NURBS: {
+    case CURVE_TYPE_NURBS: {
       const NURBSpline &src = static_cast(spline);
       NURBSpline &dst = static_cast(r_spline);
       copy_data_based_on_mask(src.weights(), dst.weights(), mask);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
index 9ae99b4d83e..ca10b640653 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
@@ -52,15 +52,15 @@ static Array curve_tangent_point_domain(const CurveEval &curve)
       const Spline &spline = *splines[i];
       MutableSpan spline_tangents{tangents.as_mutable_span().slice(offsets[i], spline.size())};
       switch (splines[i]->type()) {
-        case Spline::Type::Bezier: {
+        case CURVE_TYPE_BEZIER: {
           calculate_bezier_tangents(static_cast(spline), spline_tangents);
           break;
         }
-        case Spline::Type::Poly: {
+        case CURVE_TYPE_POLY: {
           calculate_poly_tangents(static_cast(spline), spline_tangents);
           break;
         }
-        case Spline::Type::NURBS: {
+        case CURVE_TYPE_NURBS: {
           calculate_nurbs_tangents(static_cast(spline), spline_tangents);
           break;
         }
@@ -83,7 +83,7 @@ static VArray construct_curve_tangent_gvarray(const CurveComponent &comp
 
     /* Use a reference to evaluated tangents if possible to avoid an allocation and a copy.
      * This is only possible when there is only one poly spline. */
-    if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) {
+    if (splines.size() == 1 && splines.first()->type() == CURVE_TYPE_POLY) {
       const PolySpline &spline = static_cast(*splines.first());
       return VArray::ForSpan(spline.evaluated_tangents());
     }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index 4223356397e..3ccb9b79d1f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -62,7 +62,7 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
   int current_mask = 0;
 
   for (const SplinePtr &spline : curve->splines()) {
-    if (spline->type() == Spline::Type::Bezier) {
+    if (spline->type() == CURVE_TYPE_BEZIER) {
       BezierSpline &bezier = static_cast(*spline);
       for (int i : bezier.positions().index_range()) {
         if (current_mask < selection.size() && selection[current_mask] == current_point) {
@@ -128,7 +128,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   bool has_bezier = false;
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
     if (geometry_set.has_curve() &&
-        geometry_set.get_curve_for_read()->has_spline_with_type(Spline::Type::Bezier)) {
+        geometry_set.get_curve_for_read()->has_spline_with_type(CURVE_TYPE_BEZIER)) {
       has_bezier = true;
       set_position_in_component(mode,
                                 geometry_set.get_component_for_write(),
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
index e4702035eec..f1353bda9ce 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
@@ -46,7 +46,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     if (geometry_set.has_curve()) {
       if (only_poly) {
         for (const SplinePtr &spline : geometry_set.get_curve_for_read()->splines()) {
-          if (ELEM(spline->type(), Spline::Type::Bezier, Spline::Type::NURBS)) {
+          if (ELEM(spline->type(), CURVE_TYPE_BEZIER, CURVE_TYPE_NURBS)) {
             only_poly = false;
             break;
           }
-- 
cgit v1.2.3


From 06bc20f61a8289d612ea985e45a372721d66ccb1 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 22 Feb 2022 22:15:03 -0500
Subject: Fix: Errors in previous cleanup commit

f3ef0763b41155e623 introduced a file by mistake, and didn't add
a new enum type to many switch cases. Sorry for the noise.
---
 .../blenkernel/intern/geometry_component_curves.cc | 517 ---------------------
 .../legacy/node_geo_legacy_curve_spline_type.cc    |   7 +
 .../legacy/node_geo_legacy_curve_subdivide.cc      |   4 +
 .../legacy/node_geo_legacy_delete_geometry.cc      |   4 +
 .../nodes/geometry/nodes/node_geo_curve_fillet.cc  |   4 +
 .../nodes/node_geo_curve_spline_parameter.cc       |   4 +
 .../geometry/nodes/node_geo_curve_spline_type.cc   |   7 +
 .../geometry/nodes/node_geo_curve_subdivide.cc     |   4 +
 .../nodes/geometry/nodes/node_geo_curve_trim.cc    |   6 +
 .../geometry/nodes/node_geo_delete_geometry.cc     |   4 +
 .../nodes/geometry/nodes/node_geo_input_tangent.cc |   4 +
 11 files changed, 48 insertions(+), 517 deletions(-)
 delete mode 100644 source/blender/blenkernel/intern/geometry_component_curves.cc

diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc
deleted file mode 100644
index 07b71ab5ae7..00000000000
--- a/source/blender/blenkernel/intern/geometry_component_curves.cc
+++ /dev/null
@@ -1,517 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#include "BLI_task.hh"
-
-#include "DNA_ID_enums.h"
-#include "DNA_curve_types.h"
-
-#include "BKE_attribute_access.hh"
-#include "BKE_attribute_math.hh"
-#include "BKE_curve.h"
-#include "BKE_curves.hh"
-#include "BKE_geometry_set.hh"
-#include "BKE_lib_id.h"
-#include "BKE_spline.hh"
-
-#include "attribute_access_intern.hh"
-
-using blender::fn::GVArray;
-
-/* -------------------------------------------------------------------- */
-/** \name Geometry Component Implementation
- * \{ */
-
-CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
-{
-}
-
-CurveComponent::~CurveComponent()
-{
-  this->clear();
-}
-
-GeometryComponent *CurveComponent::copy() const
-{
-  CurveComponent *new_component = new CurveComponent();
-  if (curves_ != nullptr) {
-    new_component->curves_ = BKE_curves_copy_for_eval(curves_, false);
-    new_component->ownership_ = GeometryOwnershipType::Owned;
-  }
-  return new_component;
-}
-
-void CurveComponent::clear()
-{
-  BLI_assert(this->is_mutable());
-  if (curves_ != nullptr) {
-    if (ownership_ == GeometryOwnershipType::Owned) {
-      BKE_id_free(nullptr, curves_);
-    }
-    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;
-    }
-
-    curves_ = nullptr;
-  }
-}
-
-bool CurveComponent::has_curves() const
-{
-  return curves_ != nullptr;
-}
-
-void CurveComponent::replace(Curves *curves, GeometryOwnershipType ownership)
-{
-  BLI_assert(this->is_mutable());
-  this->clear();
-  curves_ = curves;
-  ownership_ = ownership;
-}
-
-Curves *CurveComponent::release()
-{
-  BLI_assert(this->is_mutable());
-  Curves *curves = curves_;
-  curves_ = nullptr;
-  return curves;
-}
-
-const Curves *CurveComponent::get_for_read() const
-{
-  return curves_;
-}
-
-Curves *CurveComponent::get_for_write()
-{
-  BLI_assert(this->is_mutable());
-  if (ownership_ == GeometryOwnershipType::ReadOnly) {
-    curves_ = BKE_curves_copy_for_eval(curves_, false);
-    ownership_ = GeometryOwnershipType::Owned;
-  }
-  return curves_;
-}
-
-bool CurveComponent::is_empty() const
-{
-  return curves_ == nullptr;
-}
-
-bool CurveComponent::owns_direct_data() const
-{
-  return ownership_ == GeometryOwnershipType::Owned;
-}
-
-void CurveComponent::ensure_owns_direct_data()
-{
-  BLI_assert(this->is_mutable());
-  if (ownership_ != GeometryOwnershipType::Owned) {
-    curves_ = BKE_curves_copy_for_eval(curves_, false);
-    ownership_ = GeometryOwnershipType::Owned;
-  }
-}
-
-const Curve *CurveComponent::get_curve_for_render() const
-{
-  if (curves_ == nullptr) {
-    return nullptr;
-  }
-  if (curve_for_render_ != nullptr) {
-    return curve_for_render_;
-  }
-  std::lock_guard lock{curve_for_render_mutex_};
-  if (curve_for_render_ != nullptr) {
-    return curve_for_render_;
-  }
-
-  curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU_LEGACY, nullptr);
-  curve_for_render_->curve_eval = curves_to_curve_eval(*curves_).release();
-
-  return curve_for_render_;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Curve Normals Access
- * \{ */
-
-namespace blender::bke {
-
-static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan normals)
-{
-  Span offsets = spline.control_point_offsets();
-  Span evaluated_normals = spline.evaluated_normals();
-  for (const int i : IndexRange(spline.size())) {
-    normals[i] = evaluated_normals[offsets[i]];
-  }
-}
-
-static void calculate_poly_normals(const PolySpline &spline, MutableSpan normals)
-{
-  normals.copy_from(spline.evaluated_normals());
-}
-
-/**
- * Because NURBS control points are not necessarily on the path, the normal at the control points
- * is not well defined, so create a temporary poly spline to find the normals. This requires extra
- * copying currently, but may be more efficient in the future if attributes have some form of CoW.
- */
-static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan normals)
-{
-  PolySpline poly_spline;
-  poly_spline.resize(spline.size());
-  poly_spline.positions().copy_from(spline.positions());
-  poly_spline.tilts().copy_from(spline.tilts());
-  normals.copy_from(poly_spline.evaluated_normals());
-}
-
-static Array curve_normal_point_domain(const CurveEval &curve)
-{
-  Span splines = curve.splines();
-  Array offsets = curve.control_point_offsets();
-  const int total_size = offsets.last();
-  Array normals(total_size);
-
-  threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
-    for (const int i : range) {
-      const Spline &spline = *splines[i];
-      MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
-      switch (splines[i]->type()) {
-        case CURVE_TYPE_BEZIER:
-          calculate_bezier_normals(static_cast(spline), spline_normals);
-          break;
-        case CURVE_TYPE_POLY:
-          calculate_poly_normals(static_cast(spline), spline_normals);
-          break;
-        case CURVE_TYPE_NURBS:
-          calculate_nurbs_normals(static_cast(spline), spline_normals);
-          break;
-      }
-    }
-  });
-  return normals;
-}
-
-VArray curve_normals_varray(const CurveComponent &component, const AttributeDomain domain)
-{
-  if (component.is_empty()) {
-    return nullptr;
-  }
-  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
-
-  if (domain == ATTR_DOMAIN_POINT) {
-    Array normals = curve_normal_point_domain(*curve);
-    return VArray::ForContainer(std::move(normals));
-  }
-
-  if (domain == ATTR_DOMAIN_CURVE) {
-    Array point_normals = curve_normal_point_domain(*curve);
-    VArray varray = VArray::ForContainer(std::move(point_normals));
-    return component.attribute_try_adapt_domain(
-        std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
-  }
-
-  return nullptr;
-}
-
-}  // namespace blender::bke
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Attribute Access Helper Functions
- * \{ */
-
-int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
-{
-  if (curves_ == nullptr) {
-    return 0;
-  }
-  const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(
-      curves_->geometry);
-  if (domain == ATTR_DOMAIN_POINT) {
-    return geometry.points_size();
-  }
-  if (domain == ATTR_DOMAIN_CURVE) {
-    return geometry.curves_size();
-  }
-  return 0;
-}
-
-GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray,
-                                                        const AttributeDomain from_domain,
-                                                        const AttributeDomain to_domain) const
-{
-  return blender::bke::CurvesGeometry::wrap(curves_->geometry)
-      .adapt_domain(varray, from_domain, to_domain);
-}
-
-static Curves *get_curves_from_component_for_write(GeometryComponent &component)
-{
-  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
-  CurveComponent &curve_component = static_cast(component);
-  return curve_component.get_for_write();
-}
-
-static const Curves *get_curves_from_component_for_read(const GeometryComponent &component)
-{
-  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
-  const CurveComponent &curve_component = static_cast(component);
-  return curve_component.get_for_read();
-}
-
-static void tag_component_topology_changed(GeometryComponent &component)
-{
-  Curves *curves = get_curves_from_component_for_write(component);
-  if (curves) {
-    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_topology_changed();
-  }
-}
-
-static void tag_component_positions_changed(GeometryComponent &component)
-{
-  Curves *curves = get_curves_from_component_for_write(component);
-  if (curves) {
-    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_positions_changed();
-  }
-}
-
-static void tag_component_normals_changed(GeometryComponent &component)
-{
-  Curves *curves = get_curves_from_component_for_write(component);
-  if (curves) {
-    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_normals_changed();
-  }
-}
-
-/** \} */
-
-namespace blender::bke {
-
-/* -------------------------------------------------------------------- */
-/** \name Attribute Provider Declaration
- * \{ */
-
-/**
- * In this function all the attribute providers for a curves component are created.
- * Most data in this function is statically allocated, because it does not change over time.
- */
-static ComponentAttributeProviders create_attribute_providers_for_curve()
-{
-  static CustomDataAccessInfo curve_access = {
-      [](GeometryComponent &component) -> CustomData * {
-        Curves *curves = get_curves_from_component_for_write(component);
-        return curves ? &curves->geometry.curve_data : nullptr;
-      },
-      [](const GeometryComponent &component) -> const CustomData * {
-        const Curves *curves = get_curves_from_component_for_read(component);
-        return curves ? &curves->geometry.curve_data : nullptr;
-      },
-      [](GeometryComponent &component) {
-        Curves *curves = get_curves_from_component_for_write(component);
-        if (curves) {
-          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
-        }
-      }};
-  static CustomDataAccessInfo point_access = {
-      [](GeometryComponent &component) -> CustomData * {
-        Curves *curves = get_curves_from_component_for_write(component);
-        return curves ? &curves->geometry.point_data : nullptr;
-      },
-      [](const GeometryComponent &component) -> const CustomData * {
-        const Curves *curves = get_curves_from_component_for_read(component);
-        return curves ? &curves->geometry.point_data : nullptr;
-      },
-      [](GeometryComponent &component) {
-        Curves *curves = get_curves_from_component_for_write(component);
-        if (curves) {
-          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
-        }
-      }};
-
-  static BuiltinCustomDataLayerProvider position("position",
-                                                 ATTR_DOMAIN_POINT,
-                                                 CD_PROP_FLOAT3,
-                                                 CD_PROP_FLOAT3,
-                                                 BuiltinAttributeProvider::NonCreatable,
-                                                 BuiltinAttributeProvider::Writable,
-                                                 BuiltinAttributeProvider::NonDeletable,
-                                                 point_access,
-                                                 make_array_read_attribute,
-                                                 make_array_write_attribute,
-                                                 tag_component_positions_changed);
-
-  static BuiltinCustomDataLayerProvider radius("radius",
-                                               ATTR_DOMAIN_POINT,
-                                               CD_PROP_FLOAT,
-                                               CD_PROP_FLOAT,
-                                               BuiltinAttributeProvider::Creatable,
-                                               BuiltinAttributeProvider::Writable,
-                                               BuiltinAttributeProvider::Deletable,
-                                               point_access,
-                                               make_array_read_attribute,
-                                               make_array_write_attribute,
-                                               tag_component_normals_changed);
-
-  static BuiltinCustomDataLayerProvider id("id",
-                                           ATTR_DOMAIN_POINT,
-                                           CD_PROP_INT32,
-                                           CD_PROP_INT32,
-                                           BuiltinAttributeProvider::Creatable,
-                                           BuiltinAttributeProvider::Writable,
-                                           BuiltinAttributeProvider::Deletable,
-                                           point_access,
-                                           make_array_read_attribute,
-                                           make_array_write_attribute,
-                                           nullptr);
-
-  static BuiltinCustomDataLayerProvider tilt("tilt",
-                                             ATTR_DOMAIN_POINT,
-                                             CD_PROP_FLOAT,
-                                             CD_PROP_FLOAT,
-                                             BuiltinAttributeProvider::Creatable,
-                                             BuiltinAttributeProvider::Writable,
-                                             BuiltinAttributeProvider::Deletable,
-                                             point_access,
-                                             make_array_read_attribute,
-                                             make_array_write_attribute,
-                                             tag_component_normals_changed);
-
-  static BuiltinCustomDataLayerProvider handle_right("handle_right",
-                                                     ATTR_DOMAIN_POINT,
-                                                     CD_PROP_FLOAT3,
-                                                     CD_PROP_FLOAT3,
-                                                     BuiltinAttributeProvider::Creatable,
-                                                     BuiltinAttributeProvider::Writable,
-                                                     BuiltinAttributeProvider::Deletable,
-                                                     point_access,
-                                                     make_array_read_attribute,
-                                                     make_array_write_attribute,
-                                                     tag_component_positions_changed);
-
-  static BuiltinCustomDataLayerProvider handle_left("handle_left",
-                                                    ATTR_DOMAIN_POINT,
-                                                    CD_PROP_FLOAT3,
-                                                    CD_PROP_FLOAT3,
-                                                    BuiltinAttributeProvider::Creatable,
-                                                    BuiltinAttributeProvider::Writable,
-                                                    BuiltinAttributeProvider::Deletable,
-                                                    point_access,
-                                                    make_array_read_attribute,
-                                                    make_array_write_attribute,
-                                                    tag_component_positions_changed);
-
-  static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
-                                                          ATTR_DOMAIN_POINT,
-                                                          CD_PROP_INT8,
-                                                          CD_PROP_INT8,
-                                                          BuiltinAttributeProvider::Creatable,
-                                                          BuiltinAttributeProvider::Writable,
-                                                          BuiltinAttributeProvider::Deletable,
-                                                          point_access,
-                                                          make_array_read_attribute,
-                                                          make_array_write_attribute,
-                                                          tag_component_topology_changed);
-
-  static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
-                                                         ATTR_DOMAIN_POINT,
-                                                         CD_PROP_INT8,
-                                                         CD_PROP_INT8,
-                                                         BuiltinAttributeProvider::Creatable,
-                                                         BuiltinAttributeProvider::Writable,
-                                                         BuiltinAttributeProvider::Deletable,
-                                                         point_access,
-                                                         make_array_read_attribute,
-                                                         make_array_write_attribute,
-                                                         tag_component_topology_changed);
-
-  static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
-                                                     ATTR_DOMAIN_POINT,
-                                                     CD_PROP_FLOAT,
-                                                     CD_PROP_FLOAT,
-                                                     BuiltinAttributeProvider::Creatable,
-                                                     BuiltinAttributeProvider::Writable,
-                                                     BuiltinAttributeProvider::Deletable,
-                                                     point_access,
-                                                     make_array_read_attribute,
-                                                     make_array_write_attribute,
-                                                     tag_component_positions_changed);
-
-  static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
-                                                    ATTR_DOMAIN_CURVE,
-                                                    CD_PROP_INT32,
-                                                    CD_PROP_INT32,
-                                                    BuiltinAttributeProvider::Creatable,
-                                                    BuiltinAttributeProvider::Writable,
-                                                    BuiltinAttributeProvider::Deletable,
-                                                    curve_access,
-                                                    make_array_read_attribute,
-                                                    make_array_write_attribute,
-                                                    tag_component_topology_changed);
-
-  static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
-                                                         ATTR_DOMAIN_CURVE,
-                                                         CD_PROP_INT8,
-                                                         CD_PROP_INT8,
-                                                         BuiltinAttributeProvider::Creatable,
-                                                         BuiltinAttributeProvider::Writable,
-                                                         BuiltinAttributeProvider::Deletable,
-                                                         curve_access,
-                                                         make_array_read_attribute,
-                                                         make_array_write_attribute,
-                                                         tag_component_topology_changed);
-
-  static BuiltinCustomDataLayerProvider resolution("resolution",
-                                                   ATTR_DOMAIN_CURVE,
-                                                   CD_PROP_INT32,
-                                                   CD_PROP_INT32,
-                                                   BuiltinAttributeProvider::Creatable,
-                                                   BuiltinAttributeProvider::Writable,
-                                                   BuiltinAttributeProvider::Deletable,
-                                                   curve_access,
-                                                   make_array_read_attribute,
-                                                   make_array_write_attribute,
-                                                   tag_component_positions_changed);
-
-  static BuiltinCustomDataLayerProvider cyclic("cyclic",
-                                               ATTR_DOMAIN_CURVE,
-                                               CD_PROP_BOOL,
-                                               CD_PROP_BOOL,
-                                               BuiltinAttributeProvider::Creatable,
-                                               BuiltinAttributeProvider::Writable,
-                                               BuiltinAttributeProvider::Deletable,
-                                               curve_access,
-                                               make_array_read_attribute,
-                                               make_array_write_attribute,
-                                               tag_component_topology_changed);
-
-  static CustomDataAttributeProvider curve_custom_data(ATTR_DOMAIN_CURVE, curve_access);
-  static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access);
-
-  return ComponentAttributeProviders({&position,
-                                      &radius,
-                                      &id,
-                                      &tilt,
-                                      &handle_right,
-                                      &handle_left,
-                                      &handle_type_right,
-                                      &handle_type_left,
-                                      &nurbs_order,
-                                      &nurbs_weight & resolution,
-                                      &cyclic},
-                                     {&curve_custom_data, &point_custom_data});
-}
-
-/** \} */
-
-}  // namespace blender::bke
-
-const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const
-{
-  static blender::bke::ComponentAttributeProviders providers =
-      blender::bke::create_attribute_providers_for_curve();
-  return &providers;
-}
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
index 0826a78b2b9..87b8bbf8786 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
@@ -202,6 +202,10 @@ static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params
         }
         return nurbs_to_bezier(input);
       }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      return {};
+    }
   }
   BLI_assert_unreachable();
   return {};
@@ -216,6 +220,9 @@ static SplinePtr convert_to_nurbs(const Spline &input)
       return bezier_to_nurbs(input);
     case CURVE_TYPE_POLY:
       return poly_to_nurbs(input);
+    case CURVE_TYPE_CATMULL_ROM:
+      BLI_assert_unreachable();
+      return {};
   }
   BLI_assert_unreachable();
   return {};
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
index 06f0e633e40..bce320496a1 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
@@ -255,6 +255,10 @@ static void subdivide_builtin_attributes(const Spline &src_spline,
       subdivide_attribute(src.weights(), offsets, is_cyclic, dst.weights());
       break;
     }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      break;
+    }
   }
 }
 
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
index 3dc40639627..897a1c1cd2d 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
@@ -128,6 +128,10 @@ static void spline_copy_builtin_attributes(const Spline &spline,
       copy_data(src.weights(), dst.weights(), mask);
       break;
     }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      break;
+    }
   }
 }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
index ef402d41268..b8f317a9679 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -536,6 +536,10 @@ static SplinePtr fillet_spline(const Spline &spline,
       update_poly_positions(fd, dst_spline, src_spline, point_counts);
       break;
     }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      break;
+    }
   }
 
   return dst_spline_ptr;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
index f4343965d66..3fcbd1b88c3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
@@ -112,6 +112,10 @@ static Array curve_length_point_domain(const CurveEval &curve)
           calculate_nurbs_lengths(static_cast(spline), spline_factors);
           break;
         }
+        case CURVE_TYPE_CATMULL_ROM: {
+          BLI_assert_unreachable();
+          break;
+        }
       }
     }
   });
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 c79d1da8b6c..053df030f15 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
@@ -327,6 +327,10 @@ static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params
         return input.copy();
       }
       return nurbs_to_bezier(input);
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      return {};
+    }
   }
   BLI_assert_unreachable();
   return {};
@@ -341,6 +345,9 @@ static SplinePtr convert_to_nurbs(const Spline &input)
       return bezier_to_nurbs(input);
     case CURVE_TYPE_POLY:
       return poly_to_nurbs(input);
+    case CURVE_TYPE_CATMULL_ROM:
+      BLI_assert_unreachable();
+      return {};
   }
   BLI_assert_unreachable();
   return {};
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 6e8a505f9c4..81c3f14d8b1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -237,6 +237,10 @@ static void subdivide_builtin_attributes(const Spline &src_spline,
       subdivide_attribute(src.weights(), offsets, is_cyclic, dst.weights());
       break;
     }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      break;
+    }
   }
 }
 
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 652377065b0..6f2eb9f23c4 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -376,6 +376,9 @@ static void trim_spline(SplinePtr &spline,
     case CURVE_TYPE_NURBS:
       spline = std::make_unique(trim_nurbs_spline(*spline, start, end));
       break;
+    case CURVE_TYPE_CATMULL_ROM:
+      BLI_assert_unreachable();
+      spline = {};
   }
   spline->mark_cache_invalid();
 }
@@ -486,6 +489,9 @@ static void to_single_point_spline(SplinePtr &spline, const Spline::LookupResult
     case CURVE_TYPE_NURBS:
       spline = std::make_unique(to_single_point_nurbs(*spline, lookup));
       break;
+    case CURVE_TYPE_CATMULL_ROM:
+      BLI_assert_unreachable();
+      spline = {};
   }
 }
 
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 60aabc7ce37..c11b82a7d99 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -349,6 +349,10 @@ static void spline_copy_builtin_attributes(const Spline &spline,
       copy_data_based_on_mask(src.weights(), dst.weights(), mask);
       break;
     }
+    case CURVE_TYPE_CATMULL_ROM: {
+      BLI_assert_unreachable();
+      break;
+    }
   }
 }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
index ca10b640653..4ee7c52a872 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
@@ -64,6 +64,10 @@ static Array curve_tangent_point_domain(const CurveEval &curve)
           calculate_nurbs_tangents(static_cast(spline), spline_tangents);
           break;
         }
+        case CURVE_TYPE_CATMULL_ROM: {
+          BLI_assert_unreachable();
+          break;
+        }
       }
     }
   });
-- 
cgit v1.2.3


From 74611e3555684a22e9a07bd0992a444b571b8083 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 16:13:21 +1100
Subject: Fix T93331: Gizmo tool options reset when switching tools

Drag Action was constantly resetting itself to "move".

Solve this by storing the tool settings per tool and no longer clear
gizmo properties when activating a new tool.
---
 .../startup/bl_ui/space_toolsystem_common.py       |  3 ---
 .../blender/windowmanager/intern/wm_toolsystem.c   | 28 ++++++++++++----------
 2 files changed, 15 insertions(+), 16 deletions(-)

diff --git a/release/scripts/startup/bl_ui/space_toolsystem_common.py b/release/scripts/startup/bl_ui/space_toolsystem_common.py
index c4dabb5b5bc..62d735c9e60 100644
--- a/release/scripts/startup/bl_ui/space_toolsystem_common.py
+++ b/release/scripts/startup/bl_ui/space_toolsystem_common.py
@@ -1054,9 +1054,6 @@ def _activate_by_item(context, space_type, item, index, *, as_fallback=False):
         if props is None:
             print("Error:", gizmo_group, "could not access properties!")
         else:
-            for key in props.bl_rna.properties.keys():
-                props.property_unset(key)
-
             gizmo_properties = item.widget_properties
             if gizmo_properties is not None:
                 if not isinstance(gizmo_properties, list):
diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c
index 7b8feac45b4..fce11853030 100644
--- a/source/blender/windowmanager/intern/wm_toolsystem.c
+++ b/source/blender/windowmanager/intern/wm_toolsystem.c
@@ -818,13 +818,25 @@ void WM_toolsystem_do_msg_notify_tag_refresh(bContext *C,
   WM_toolsystem_refresh_screen_area(workspace, view_layer, area);
 }
 
+static IDProperty *idprops_ensure_named_group(IDProperty *group, const char *idname)
+{
+  IDProperty *prop = IDP_GetPropertyFromGroup(group, idname);
+  if ((prop == NULL) || (prop->type != IDP_GROUP)) {
+    IDPropertyTemplate val = {0};
+    prop = IDP_New(IDP_GROUP, &val, __func__);
+    STRNCPY(prop->name, idname);
+    IDP_ReplaceInGroup_ex(group, prop, NULL);
+  }
+  return prop;
+}
+
 IDProperty *WM_toolsystem_ref_properties_ensure_idprops(bToolRef *tref)
 {
   if (tref->properties == NULL) {
     IDPropertyTemplate val = {0};
-    tref->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties");
+    tref->properties = IDP_New(IDP_GROUP, &val, __func__);
   }
-  return tref->properties;
+  return idprops_ensure_named_group(tref->properties, tref->idname);
 }
 
 bool WM_toolsystem_ref_properties_get_ex(bToolRef *tref,
@@ -844,17 +856,7 @@ void WM_toolsystem_ref_properties_ensure_ex(bToolRef *tref,
                                             PointerRNA *r_ptr)
 {
   IDProperty *group = WM_toolsystem_ref_properties_ensure_idprops(tref);
-  IDProperty *prop = IDP_GetPropertyFromGroup(group, idname);
-  if (prop == NULL) {
-    IDPropertyTemplate val = {0};
-    prop = IDP_New(IDP_GROUP, &val, "wmGenericProperties");
-    STRNCPY(prop->name, idname);
-    IDP_ReplaceInGroup_ex(group, prop, NULL);
-  }
-  else {
-    BLI_assert(prop->type == IDP_GROUP);
-  }
-
+  IDProperty *prop = idprops_ensure_named_group(group, idname);
   RNA_pointer_create(NULL, type, prop, r_ptr);
 }
 
-- 
cgit v1.2.3


From 165b030b19718694eaf33d6602034ed88fdd31a1 Mon Sep 17 00:00:00 2001
From: Alaska 
Date: Wed, 23 Feb 2022 11:05:48 +0530
Subject: Cleanup: Remove repeated word in comment

Differential Revision: https://developer.blender.org/D14178
---
 CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bbddaff8480..9f8800fe303 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -866,7 +866,7 @@ if(WITH_CYCLES_DEVICE_HIP)
 endif()
 
 #-----------------------------------------------------------------------------
-# Check check if submodules are cloned
+# Check if submodules are cloned.
 
 if(WITH_INTERNATIONAL)
   file(GLOB RESULT "${CMAKE_SOURCE_DIR}/release/datafiles/locale")
-- 
cgit v1.2.3


From 7393cc1db7abc8fd2c1c37dc13dfc1c85ccffbf6 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 18:24:08 +1100
Subject: Cleanup: Remove repeated word in comments

---
 intern/cycles/app/opengl/display_driver.cpp                 | 2 +-
 intern/cycles/integrator/render_scheduler.cpp               | 2 +-
 intern/cycles/integrator/render_scheduler.h                 | 4 ++--
 source/blender/blenkernel/BKE_attribute_access.hh           | 2 +-
 source/blender/blenkernel/BKE_geometry_set.hh               | 2 +-
 source/blender/blenkernel/BKE_idprop.h                      | 2 +-
 source/blender/blenkernel/intern/asset_library_test.cc      | 2 +-
 source/blender/blenkernel/intern/brush.c                    | 2 +-
 source/blender/blenkernel/intern/fluid.c                    | 2 +-
 source/blender/blenkernel/intern/mesh_boolean_convert.cc    | 2 +-
 source/blender/blenlib/BLI_path_util.h                      | 2 +-
 source/blender/blenlib/intern/mesh_boolean.cc               | 2 +-
 source/blender/bmesh/tools/bmesh_bevel.c                    | 2 +-
 source/blender/editors/animation/anim_channels_defines.c    | 4 ++--
 source/blender/editors/interface/interface.c                | 2 +-
 source/blender/editors/interface/interface_template_list.cc | 2 +-
 source/blender/editors/interface/interface_widgets.c        | 2 +-
 source/blender/editors/object/object_edit.c                 | 2 +-
 source/blender/editors/space_graph/space_graph.c            | 2 +-
 source/blender/editors/space_node/drawnode.cc               | 2 +-
 source/blender/editors/space_node/node_draw.cc              | 2 +-
 source/blender/editors/space_sequencer/sequencer_draw.c     | 4 +++-
 source/blender/functions/intern/field.cc                    | 4 ++--
 source/blender/gpu/intern/gpu_shader_create_info.hh         | 6 +++---
 source/blender/gpu/intern/gpu_shader_dependency_private.h   | 2 +-
 source/blender/gpu/opengl/gl_immediate.cc                   | 2 +-
 source/blender/ikplugin/intern/itasc_plugin.cpp             | 4 ++--
 source/blender/imbuf/intern/indexer.c                       | 2 +-
 source/blender/makesdna/DNA_fluid_types.h                   | 2 +-
 source/blender/makesrna/intern/rna_access.c                 | 2 +-
 source/blender/makesrna/intern/rna_internal.h               | 2 +-
 source/blender/nodes/NOD_node_declaration.hh                | 2 +-
 source/blender/nodes/NOD_socket_search_link.hh              | 2 +-
 source/blender/nodes/intern/node_common.cc                  | 2 +-
 source/blender/sequencer/intern/effects.c                   | 2 +-
 source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c    | 2 +-
 36 files changed, 44 insertions(+), 42 deletions(-)

diff --git a/intern/cycles/app/opengl/display_driver.cpp b/intern/cycles/app/opengl/display_driver.cpp
index 6e610b8b02c..8b99f3b6feb 100644
--- a/intern/cycles/app/opengl/display_driver.cpp
+++ b/intern/cycles/app/opengl/display_driver.cpp
@@ -75,7 +75,7 @@ bool OpenGLDisplayDriver::update_begin(const Params ¶ms, int texture_width,
 
   /* Update PBO dimensions if needed.
    *
-   * NOTE: Allocate the PBO for the the size which will fit the final render resolution (as in,
+   * NOTE: Allocate the PBO for the size which will fit the final render resolution (as in,
    * at a resolution divider 1. This was we don't need to recreate graphics interoperability
    * objects which are costly and which are tied to the specific underlying buffer size.
    * The downside of this approach is that when graphics interoperability is not used we are
diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp
index fe4697e082b..0d8aef106e4 100644
--- a/intern/cycles/integrator/render_scheduler.cpp
+++ b/intern/cycles/integrator/render_scheduler.cpp
@@ -244,7 +244,7 @@ void RenderScheduler::render_work_reschedule_on_cancel(RenderWork &render_work)
   render_work.tile.write = tile_write;
   render_work.full.write = full_write;
 
-  /* Do not write tile if it has zero samples it it, treat it similarly to all other tiles which
+  /* Do not write tile if it has zero samples it, treat it similarly to all other tiles which
    * got canceled. */
   if (!state_.tile_result_was_written && has_rendered_samples) {
     render_work.tile.write = true;
diff --git a/intern/cycles/integrator/render_scheduler.h b/intern/cycles/integrator/render_scheduler.h
index 404f65e98a1..dce876d44bd 100644
--- a/intern/cycles/integrator/render_scheduler.h
+++ b/intern/cycles/integrator/render_scheduler.h
@@ -124,7 +124,7 @@ class RenderScheduler {
   /* Get sample up to which rendering has been done.
    * This is an absolute 0-based value.
    *
-   * For example, if start sample is 10 and and 5 samples were rendered, then this call will
+   * For example, if start sample is 10 and 5 samples were rendered, then this call will
    * return 14.
    *
    * If there were no samples rendered, then the behavior is undefined. */
@@ -132,7 +132,7 @@ class RenderScheduler {
 
   /* Get number of samples rendered within the current scheduling session.
    *
-   * For example, if start sample is 10 and and 5 samples were rendered, then this call will
+   * For example, if start sample is 10 and 5 samples were rendered, then this call will
    * return 5.
    *
    * Note that this is based on the scheduling information. In practice this means that if someone
diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index 0d63bb88de1..500f386dcc0 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -227,7 +227,7 @@ struct WriteAttributeLookup {
  * - An output attribute can live side by side with an existing attribute with a different domain
  *   or data type. The old attribute will only be overwritten when the #save function is called.
  *
- * \note The lifetime of an output attribute should not be longer than the the lifetime of the
+ * \note The lifetime of an output attribute should not be longer than the lifetime of the
  * geometry component it comes from, since it can keep a reference to the component for use in
  * the #save method.
  */
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index 7c55c53ed66..d93b3ca95e7 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -693,7 +693,7 @@ class CurveComponent : public GeometryComponent {
 
 /**
  * Holds a reference to conceptually unique geometry or a pointer to object/collection data
- * that is is instanced with a transform in #InstancesComponent.
+ * that is instanced with a transform in #InstancesComponent.
  */
 class InstanceReference {
  public:
diff --git a/source/blender/blenkernel/BKE_idprop.h b/source/blender/blenkernel/BKE_idprop.h
index b4dda3689e6..3f7d9498e39 100644
--- a/source/blender/blenkernel/BKE_idprop.h
+++ b/source/blender/blenkernel/BKE_idprop.h
@@ -169,7 +169,7 @@ struct IDProperty *IDP_GetPropertyTypeFromGroup(const struct IDProperty *prop,
 
 /*-------- Main Functions --------*/
 /**
- * Get the Group property that contains the id properties for ID id.
+ * Get the Group property that contains the id properties for ID `id`.
  *
  * \param create_if_needed: Set to create the group property and attach it to id if it doesn't
  * exist; otherwise the function will return NULL if there's no Group property attached to the ID.
diff --git a/source/blender/blenkernel/intern/asset_library_test.cc b/source/blender/blenkernel/intern/asset_library_test.cc
index 45889099567..1d862e5e4d4 100644
--- a/source/blender/blenkernel/intern/asset_library_test.cc
+++ b/source/blender/blenkernel/intern/asset_library_test.cc
@@ -52,7 +52,7 @@ TEST_F(AssetLibraryTest, bke_asset_library_load)
   ASSERT_NE(nullptr, service);
 
   /* Check that the catalogs defined in the library are actually loaded. This just tests one single
-   * catalog, as that indicates the file has been loaded. Testing that that loading went OK is for
+   * catalog, as that indicates the file has been loaded. Testing that loading went OK is for
    * the asset catalog service tests. */
   const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
   AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie);
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index 4531783c72a..6ee6ff7f41d 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -144,7 +144,7 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
     /* 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
+    /* NOTE: assert below ensures that the comment above is valid, and that exception is
      * acceptable for the time being. */
     BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0);
     BLI_assert(!ID_IS_LINKED(brush->clone.image) && brush->clone.image->id.newid == NULL);
diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c
index ef2fa2266c4..6f2760e91a6 100644
--- a/source/blender/blenkernel/intern/fluid.c
+++ b/source/blender/blenkernel/intern/fluid.c
@@ -3292,7 +3292,7 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds,
   mpolys = me->mpoly;
   mloops = me->mloop;
 
-  /* Get size (dimension) but considering scaling scaling. */
+  /* Get size (dimension) but considering scaling. */
   copy_v3_v3(cell_size_scaled, fds->cell_size);
   mul_v3_v3(cell_size_scaled, ob->scale);
   madd_v3fl_v3fl_v3fl_v3i(min, fds->p0, cell_size_scaled, fds->res_min);
diff --git a/source/blender/blenkernel/intern/mesh_boolean_convert.cc b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
index ec66cd0d84d..eee1d3b9eec 100644
--- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
@@ -292,7 +292,7 @@ static IMesh meshes_to_imesh(Span meshes,
     r_info->mesh_edge_offset[mi] = e;
     r_info->mesh_poly_offset[mi] = f;
     /* Get matrix that transforms a coordinate in objects[mi]'s local space
-     * to the target space space. */
+     * to the target space. */
     const float4x4 objn_mat = (obmats[mi] == nullptr) ? float4x4::identity() :
                                                         clean_obmat(*obmats[mi]);
     r_info->to_target_transform[mi] = inv_target_mat * objn_mat;
diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h
index b4427b1dc2a..7b3e3e983f0 100644
--- a/source/blender/blenlib/BLI_path_util.h
+++ b/source/blender/blenlib/BLI_path_util.h
@@ -298,7 +298,7 @@ bool BLI_path_parent_dir_until_exists(char *path) ATTR_NONNULL();
 bool BLI_path_abs(char *path, const char *basepath) ATTR_NONNULL();
 /**
  * Replaces "#" character sequence in last slash-separated component of `path`
- * with frame as decimal integer, with leading zeroes as necessary, to make digits digits.
+ * with frame as decimal integer, with leading zeroes as necessary, to make digits.
  */
 bool BLI_path_frame(char *path, int frame, int digits) ATTR_NONNULL();
 /**
diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc
index 6e2e9787ebe..70030fc2bdf 100644
--- a/source/blender/blenlib/intern/mesh_boolean.cc
+++ b/source/blender/blenlib/intern/mesh_boolean.cc
@@ -2185,7 +2185,7 @@ static void finish_patch_cell_graph(const IMesh &tm,
  * There will be a vector of \a nshapes winding numbers in each cell, one per
  * input shape.
  * As one crosses a patch into a new cell, the original shape (mesh part)
- * that that patch was part of dictates which winding number changes.
+ * that patch was part of dictates which winding number changes.
  * The shape_fn(triangle_number) function should return the shape that the
  * triangle is part of.
  * Also, as soon as the winding numbers for a cell are set, use bool_optype
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c
index e2e26c5c52b..10bdc2c7294 100644
--- a/source/blender/bmesh/tools/bmesh_bevel.c
+++ b/source/blender/bmesh/tools/bmesh_bevel.c
@@ -532,7 +532,7 @@ static BevVert *find_bevvert(BevelParams *bp, BMVert *bmv)
 
 /**
  * Find the EdgeHalf representing the other end of e->e.
- * \return Return other end's BevVert in *r_bvother, if r_bvother is provided. That may not have
+ * \return other end's BevVert in *r_bvother, if r_bvother is provided. That may not have
  * been constructed yet, in which case return NULL.
  */
 static EdgeHalf *find_other_end_edge_half(BevelParams *bp, EdgeHalf *e, BevVert **r_bvother)
diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c
index 3d768b84846..edb6d188ab8 100644
--- a/source/blender/editors/animation/anim_channels_defines.c
+++ b/source/blender/editors/animation/anim_channels_defines.c
@@ -4601,7 +4601,7 @@ void ANIM_channel_draw(
     /* Draw slider:
      * - Even if we can draw sliders for this view,
      *   we must also check that the channel-type supports them
-     *   (only only F-Curves really can support them for now).
+     *   (only F-Curves really can support them for now).
      * - Slider should start before the toggles (if they're visible)
      *   to keep a clean line down the side.
      */
@@ -5336,7 +5336,7 @@ void ANIM_channel_draw_widgets(const bContext *C,
 
     /* Draw slider:
      * - Even if we can draw sliders for this view, we must also check that the channel-type
-     *   supports them (only only F-Curves really can support them for now).
+     *   supports them (only F-Curves really can support them for now).
      * - To make things easier, we use RNA-autobuts for this so that changes are
      *   reflected immediately, wherever they occurred.
      *   BUT, we don't use the layout engine, otherwise we'd get wrong alignment,
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index 4819e8ed82c..a9a68be2d48 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -1418,7 +1418,7 @@ static bool ui_but_event_property_operator_string(const bContext *C,
   }
 
   /* This version is only for finding hotkeys for properties.
-   * These are set set via a data-path which is appended to the context,
+   * These are set via a data-path which is appended to the context,
    * manipulated using operators (see #ctx_toggle_opnames). */
 
   if (ptr->owner_id) {
diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc
index 40675da71a9..6139ac8e702 100644
--- a/source/blender/editors/interface/interface_template_list.cc
+++ b/source/blender/editors/interface/interface_template_list.cc
@@ -598,7 +598,7 @@ static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const cha
 }
 
 /**
- * \note Note that \a layout_type may be null.
+ * \note that \a layout_type may be null.
  */
 static uiList *ui_list_ensure(bContext *C,
                               uiListType *ui_list_type,
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index fbbf3c6fdf1..5638a98b33b 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -2579,7 +2579,7 @@ static void widget_state(uiWidgetType *wt, int state, int drawflag, eUIEmbossTyp
  *
  * A lot of places of the UI like the Node Editor or panels are zoomable. In most cases we can
  * get the zoom factor from the aspect, but in some cases like popups we need to fall back to
- * using the the size of the element. The latter method relies on the element always being the same
+ * using the size of the element. The latter method relies on the element always being the same
  * size.
  * \{ */
 
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 58594affc05..203e18f9621 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -89,7 +89,7 @@
 
 #include "CLG_log.h"
 
-/* For menu/popup icons etc etc. */
+/* For menu/popup icons etc. */
 
 #include "UI_interface.h"
 #include "UI_resources.h"
diff --git a/source/blender/editors/space_graph/space_graph.c b/source/blender/editors/space_graph/space_graph.c
index 27ac34ba346..22427675ff3 100644
--- a/source/blender/editors/space_graph/space_graph.c
+++ b/source/blender/editors/space_graph/space_graph.c
@@ -242,7 +242,7 @@ static void graph_main_region_draw(const bContext *C, ARegion *region)
 
     GPU_blend(GPU_BLEND_NONE);
 
-    /* Vertical component of of the cursor. */
+    /* Vertical component of the cursor. */
     if (sipo->mode == SIPO_MODE_DRIVERS) {
       /* cursor x-value */
       float x = sipo->cursorTime;
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 28ac7a34fa8..b2c361ecaba 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -147,7 +147,7 @@ static void node_buts_curvefloat(uiLayout *layout, bContext *UNUSED(C), PointerR
 }  // namespace blender::ed::space_node
 
 #define SAMPLE_FLT_ISNONE FLT_MAX
-/* Bad bad, 2.5 will do better? ... no it won't! */
+/* Bad! 2.5 will do better? ... no it won't! */
 static float _sample_col[4] = {SAMPLE_FLT_ISNONE};
 void ED_node_sample_set(const float col[4])
 {
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 455eceed293..7f3bc8ccb13 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -1049,7 +1049,7 @@ static void node_socket_draw_nested(const bContext &C,
       },
       data,
       MEM_freeN);
-  /* Disable the button so that clicks on it are ignored the the link operator still works. */
+  /* Disable the button so that clicks on it are ignored the link operator still works. */
   UI_but_flag_enable(but, UI_BUT_DISABLED);
   UI_block_emboss_set(&block, old_emboss);
 }
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index 1e810a9c59e..5ac4363e63d 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -1636,7 +1636,9 @@ static void sequencer_draw_gpencil_overlay(const bContext *C)
   ED_annotation_draw_view2d(C, 0);
 }
 
-/* Draw content and safety borders borders. */
+/**
+ * Draw content and safety borders.
+ */
 static void sequencer_draw_borders_overlay(const SpaceSeq *sseq,
                                            const View2D *v2d,
                                            const Scene *scene)
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
index 054a6b51adf..7b514b6a49b 100644
--- a/source/blender/functions/intern/field.cc
+++ b/source/blender/functions/intern/field.cc
@@ -26,7 +26,7 @@ struct FieldTreeInfo {
    */
   MultiValueMap field_users;
   /**
-   * The same field input may exist in the field tree as as separate nodes due to the way
+   * The same field input may exist in the field tree as separate nodes due to the way
    * the tree is constructed. This set contains every different input only once.
    */
   VectorSet> deduplicated_field_inputs;
@@ -137,7 +137,7 @@ static Set find_varying_fields(const FieldTreeInfo &field_tree_info,
 }
 
 /**
- * Builds the #procedure so that it computes the the fields.
+ * Builds the #procedure so that it computes the fields.
  */
 static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
                                                       ResourceScope &scope,
diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh
index bd0187e2dc5..e05dce57674 100644
--- a/source/blender/gpu/intern/gpu_shader_create_info.hh
+++ b/source/blender/gpu/intern/gpu_shader_create_info.hh
@@ -497,9 +497,9 @@ struct ShaderCreateInfo {
 
   /**
    * IMPORTANT: invocations count is only used if GL_ARB_gpu_shader5 is supported. On
-   * implementations that do not supports it, the max_vertices will be be multiplied by
-   * invocations. Your shader needs to account for this fact. Use `#ifdef GPU_ARB_gpu_shader5`
-   * and make a code path that does not rely on gl_InvocationID.
+   * implementations that do not supports it, the max_vertices will be multiplied by invocations.
+   * Your shader needs to account for this fact. Use `#ifdef GPU_ARB_gpu_shader5` and make a code
+   * path that does not rely on #gl_InvocationID.
    */
   Self &geometry_layout(PrimitiveIn prim_in,
                         PrimitiveOut prim_out,
diff --git a/source/blender/gpu/intern/gpu_shader_dependency_private.h b/source/blender/gpu/intern/gpu_shader_dependency_private.h
index 29ee70962d1..7b9ecef5835 100644
--- a/source/blender/gpu/intern/gpu_shader_dependency_private.h
+++ b/source/blender/gpu/intern/gpu_shader_dependency_private.h
@@ -38,7 +38,7 @@ StringRefNull gpu_shader_dependency_get_source(const StringRefNull source_name);
 
 /**
  * \brief Find the name of the file from which the given string was generated.
- * \return Return filename or empty string.
+ * \return filename or empty string.
  * \note source_string needs to be identical to the one given by gpu_shader_dependency_get_source()
  */
 StringRefNull gpu_shader_dependency_get_filename_from_source_string(
diff --git a/source/blender/gpu/opengl/gl_immediate.cc b/source/blender/gpu/opengl/gl_immediate.cc
index cfedfe348f1..c32a6afd8cf 100644
--- a/source/blender/gpu/opengl/gl_immediate.cc
+++ b/source/blender/gpu/opengl/gl_immediate.cc
@@ -139,7 +139,7 @@ void GLImmediate::end()
     GLContext::get()->state_manager->apply_state();
 
     /* We convert the offset in vertex offset from the buffer's start.
-     * This works because we added some padding to align the first vertex vertex. */
+     * This works because we added some padding to align the first vertex. */
     uint v_first = buffer_offset() / vertex_format.stride;
     GLVertArray::update_bindings(
         vao_id_, v_first, &vertex_format, reinterpret_cast(shader)->interface);
diff --git a/source/blender/ikplugin/intern/itasc_plugin.cpp b/source/blender/ikplugin/intern/itasc_plugin.cpp
index 6dcfd3d014e..470dfb01bdd 100644
--- a/source/blender/ikplugin/intern/itasc_plugin.cpp
+++ b/source/blender/ikplugin/intern/itasc_plugin.cpp
@@ -244,9 +244,9 @@ static int initialize_chain(Object *ob, bPoseChannel *pchan_tip, bConstraint *co
     }
 
     if (BLI_listbase_is_empty(&curchan->iktree) == false) {
-      /* Oh oh, there is already a chain starting from this channel and our chain is longer...
+      /* Oh, there is already a chain starting from this channel and our chain is longer.
        * Should handle this by moving the previous chain up to the beginning of our chain
-       * For now we just stop here */
+       * For now we just stop here. */
       break;
     }
   }
diff --git a/source/blender/imbuf/intern/indexer.c b/source/blender/imbuf/intern/indexer.c
index 7e24ca44244..c1e00642a6d 100644
--- a/source/blender/imbuf/intern/indexer.c
+++ b/source/blender/imbuf/intern/indexer.c
@@ -1162,7 +1162,7 @@ static int indexer_performance_get_max_gop_size(FFmpegIndexBuilderContext *conte
 }
 
 /* Assess scrubbing performance of provided file. This function is not meant to be very exact.
- * It compares number number of frames decoded in reasonable time with largest detected GOP size.
+ * It compares number of frames decoded in reasonable time with largest detected GOP size.
  * Because seeking happens in single GOP, it means, that maximum seek time can be detected this
  * way.
  * Since proxies use GOP size of 10 frames, skip building if detected GOP size is less or
diff --git a/source/blender/makesdna/DNA_fluid_types.h b/source/blender/makesdna/DNA_fluid_types.h
index 953a15f7502..11780d99af8 100644
--- a/source/blender/makesdna/DNA_fluid_types.h
+++ b/source/blender/makesdna/DNA_fluid_types.h
@@ -611,7 +611,7 @@ typedef struct FluidDomainSettings {
 
   /* Fluid guiding options. */
   float guide_alpha;      /* Guiding weight scalar (determines strength). */
-  int guide_beta;         /* Guiding blur radius (affects size of vortices vortices). */
+  int guide_beta;         /* Guiding blur radius (affects size of vortices). */
   float guide_vel_factor; /* Multiply guiding velocity by this factor. */
   int guide_res[3];       /* Res for velocity guide grids - independent from base res. */
   short guide_source;
diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c
index c8f710d9931..7ccb8181b51 100644
--- a/source/blender/makesrna/intern/rna_access.c
+++ b/source/blender/makesrna/intern/rna_access.c
@@ -4904,7 +4904,7 @@ static char *rna_path_token_in_brackets(const char **path,
 }
 
 /**
- * \return true when when the key in the path is correctly parsed and found in the collection
+ * \return true when the key in the path is correctly parsed and found in the collection
  * or when the path is empty.
  */
 static bool rna_path_parse_collection_key(const char **path,
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index 2c5f264f7e9..44bf51d9770 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -649,7 +649,7 @@ const char *rna_translate_ui_text(const char *text,
                                   struct PropertyRNA *prop,
                                   bool translate);
 
-/* Internal functions that cycles uses so we need to declare (tsk tsk) */
+/* Internal functions that cycles uses so we need to declare (tsk!). */
 void rna_RenderPass_rect_set(PointerRNA *ptr, const float *values);
 
 #ifdef RNA_RUNTIME
diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh
index 3bcd4b87b15..4e78f6c1142 100644
--- a/source/blender/nodes/NOD_node_declaration.hh
+++ b/source/blender/nodes/NOD_node_declaration.hh
@@ -110,7 +110,7 @@ class SocketDeclaration {
   /**
    * Change the node such that the socket will become visible. The node type's update method
    * should be called afterwards.
-   * \note Note that this is not necessarily implemented for all node types.
+   * \note this is not necessarily implemented for all node types.
    */
   void make_available(bNode &node) const;
 
diff --git a/source/blender/nodes/NOD_socket_search_link.hh b/source/blender/nodes/NOD_socket_search_link.hh
index e3927488612..7a1aff13020 100644
--- a/source/blender/nodes/NOD_socket_search_link.hh
+++ b/source/blender/nodes/NOD_socket_search_link.hh
@@ -16,7 +16,7 @@ struct bContext;
 namespace blender::nodes {
 
 /**
- * Parameters for the operation operation of adding a node after the link drag search menu closes.
+ * Parameters for the operation of adding a node after the link drag search menu closes.
  */
 class LinkSearchOpParams {
  private:
diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc
index d6a4af7ef39..c2d440efdb5 100644
--- a/source/blender/nodes/intern/node_common.cc
+++ b/source/blender/nodes/intern/node_common.cc
@@ -326,7 +326,7 @@ void ntree_update_reroute_nodes(bNodeTree *ntree)
   }
 
   /* Propagate socket types from right to left. This affects reroute nodes that haven't been
-   * changed in the the loop above. */
+   * changed in the loop above. */
   for (bNode *start_node : nodes_linked_with_reroutes) {
     LISTBASE_FOREACH (bNodeSocket *, input_socket, &start_node->inputs) {
       propagate_reroute_type_from_start_socket(input_socket, links_map, reroute_types);
diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c
index 3196ca08155..aa433eeed09 100644
--- a/source/blender/sequencer/intern/effects.c
+++ b/source/blender/sequencer/intern/effects.c
@@ -1918,7 +1918,7 @@ static void RVBlurBitmap2_float(float *map, int width, int height, float blur, i
    * I changed the math around to implement an actual Gaussian distribution.
    *
    * Watch out though, it tends to misbehave with large blur values on
-   * a small bitmap. Avoid avoid! */
+   * a small bitmap. Avoid! */
 
   float *temp = NULL, *swap;
   float *filter = NULL;
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
index 4aba287aefb..ad902f0963e 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
@@ -726,7 +726,7 @@ static wmGizmo *gizmo_find_intersected_3d(bContext *C,
      * - First, don't use the depth buffer at all, use occlusion queries to detect any gizmos.
      *   If there are no gizmos or only one - early exit, otherwise.
      *
-     * - Bind the depth buffer and and use selection picking logic.
+     * - Bind the depth buffer and use selection picking logic.
      *   This is much slower than occlusion queries (since it's reading depths while drawing).
      *   When there is a single gizmo under the cursor (quite common), early exit, otherwise.
      *
-- 
cgit v1.2.3


From 66c0fe5b234fad377c21c25dc406309abcd57656 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 20:46:48 +1100
Subject: Cleanup: correction to repeated word removal & correct spelling

---
 intern/cycles/integrator/render_scheduler.cpp      | 2 +-
 source/blender/editors/sculpt_paint/paint_image.cc | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp
index 0d8aef106e4..90a5a01320b 100644
--- a/intern/cycles/integrator/render_scheduler.cpp
+++ b/intern/cycles/integrator/render_scheduler.cpp
@@ -244,7 +244,7 @@ void RenderScheduler::render_work_reschedule_on_cancel(RenderWork &render_work)
   render_work.tile.write = tile_write;
   render_work.full.write = full_write;
 
-  /* Do not write tile if it has zero samples it, treat it similarly to all other tiles which
+  /* Do not write tile if it has zero samples in it, treat it similarly to all other tiles which
    * got canceled. */
   if (!state_.tile_result_was_written && has_rendered_samples) {
     render_work.tile.write = true;
diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc
index 9413f872c3a..0c73c2e1f43 100644
--- a/source/blender/editors/sculpt_paint/paint_image.cc
+++ b/source/blender/editors/sculpt_paint/paint_image.cc
@@ -86,7 +86,7 @@ void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr)
   imapaintpartial = *ippr;
 }
 
-/* Imagepaint Partial Redraw & Dirty Region */
+/* Image paint Partial Redraw & Dirty Region. */
 
 void ED_imapaint_clear_partial_redraw(void)
 {
@@ -356,7 +356,7 @@ void paint_brush_color_get(struct Scene *scene,
           break;
         }
       }
-      /* Gradient / Colorband colors are not considered PROP_COLOR_GAMMA.
+      /* Gradient / Color-band colors are not considered #PROP_COLOR_GAMMA.
        * Brush colors are expected to be in sRGB though. */
       IMB_colormanagement_scene_linear_to_srgb_v3(color_gr);
 
-- 
cgit v1.2.3


From 34294449059744ba4b3d4b16eb5fb14a48c16265 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Wed, 23 Feb 2022 11:13:54 +0100
Subject: BLI: add index mask utilities

Sometimes it is useful to get the index ranges that are in an index mask.
That is because some algorithms can process index ranges more efficiently
than generic index masks.

Extracting ranges from an index mask is relatively efficient, because it is
cheap to check if a span of indices contains a contiguous range.
---
 source/blender/blenlib/BLI_index_mask.hh           |  36 +++++
 source/blender/blenlib/intern/index_mask.cc        |  95 +++++++++++++
 .../blender/blenlib/tests/BLI_index_mask_test.cc   | 149 +++++++++++++++++++++
 3 files changed, 280 insertions(+)

diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh
index 7f4e7be543b..3decd8b9441 100644
--- a/source/blender/blenlib/BLI_index_mask.hh
+++ b/source/blender/blenlib/BLI_index_mask.hh
@@ -209,6 +209,18 @@ class IndexMask {
     return indices_.is_empty();
   }
 
+  bool contained_in(const IndexRange range) const
+  {
+    if (indices_.is_empty()) {
+      return true;
+    }
+    if (range.size() < indices_.size()) {
+      return false;
+    }
+    return indices_.first() >= range.first() && indices_.last() <= range.last();
+  }
+
+  IndexMask slice(int64_t start, int64_t size) const;
   IndexMask slice(IndexRange slice) const;
   /**
    * Create a sub-mask that is also shifted to the beginning.
@@ -229,6 +241,30 @@ class IndexMask {
    * so that the first index in the output is zero.
    */
   IndexMask slice_and_offset(IndexRange slice, Vector &r_new_indices) const;
+
+  /**
+   * Get a new mask that contains all the indices that are not in the current mask.
+   * If necessary, the indices referenced by the new mask are inserted in #r_new_indices.
+   */
+  IndexMask invert(const IndexRange full_range, Vector &r_new_indices) const;
+
+  /**
+   * Get all contiguous index ranges within the mask.
+   */
+  Vector extract_ranges() const;
+
+  /**
+   * Similar to #extract ranges, but works on the inverted mask. So the returned ranges are
+   * in-between the indices in the mask.
+   *
+   * Using this method is generally more efficient than first inverting the index mask and then
+   * extracting the ranges.
+   *
+   * If #r_skip_amounts is passed in, it will contain the number of indices that have been skipped
+   * before each range in the return value starts.
+   */
+  Vector extract_ranges_invert(const IndexRange full_range,
+                                           Vector *r_skip_amounts) const;
 };
 
 }  // namespace blender
diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc
index b55de6a9264..8c03df2d4c3 100644
--- a/source/blender/blenlib/intern/index_mask.cc
+++ b/source/blender/blenlib/intern/index_mask.cc
@@ -4,6 +4,11 @@
 
 namespace blender {
 
+IndexMask IndexMask::slice(int64_t start, int64_t size) const
+{
+  return this->slice(IndexRange(start, size));
+}
+
 IndexMask IndexMask::slice(IndexRange slice) const
 {
   return IndexMask(indices_.slice(slice));
@@ -30,4 +35,94 @@ IndexMask IndexMask::slice_and_offset(const IndexRange slice, Vector &r
   return IndexMask(r_new_indices.as_span());
 }
 
+IndexMask IndexMask::invert(const IndexRange full_range, Vector &r_new_indices) const
+{
+  BLI_assert(this->contained_in(full_range));
+  if (full_range.size() == indices_.size()) {
+    return {};
+  }
+  if (indices_.is_empty()) {
+    return full_range;
+  }
+  r_new_indices.clear();
+
+  const Vector ranges = this->extract_ranges_invert(full_range, nullptr);
+  for (const IndexRange &range : ranges) {
+    for (const int64_t index : range) {
+      r_new_indices.append(index);
+    }
+  }
+  return r_new_indices.as_span();
+}
+
+Vector IndexMask::extract_ranges() const
+{
+  Vector ranges;
+  int64_t range_start = 0;
+  while (range_start < indices_.size()) {
+    int64_t current_range_end = range_start + 1;
+    int64_t step_size = 1;
+
+    while (true) {
+      const int64_t possible_range_end = current_range_end + step_size;
+      if (possible_range_end > indices_.size()) {
+        break;
+      }
+      if (!this->slice(range_start, possible_range_end - range_start).is_range()) {
+        break;
+      }
+      current_range_end = possible_range_end;
+      step_size *= 2;
+    }
+
+    /* This step size was tried already, no need to try it again. */
+    step_size /= 2;
+
+    while (step_size > 0) {
+      const int64_t possible_range_end = current_range_end + step_size;
+      step_size /= 2;
+      if (possible_range_end > indices_.size()) {
+        continue;
+      }
+      if (!this->slice(range_start, possible_range_end - range_start).is_range()) {
+        continue;
+      }
+      current_range_end = possible_range_end;
+    }
+
+    ranges.append(IndexRange{indices_[range_start], current_range_end - range_start});
+    range_start = current_range_end;
+  }
+  return ranges;
+}
+
+Vector IndexMask::extract_ranges_invert(const IndexRange full_range,
+                                                    Vector *r_skip_amounts) const
+{
+  BLI_assert(this->contained_in(full_range));
+  const Vector ranges = this->extract_ranges();
+  Vector inverted_ranges;
+
+  int64_t skip_amount = 0;
+  int64_t next_start = full_range.start();
+  for (const int64_t i : ranges.index_range()) {
+    const IndexRange range = ranges[i];
+    if (range.start() > next_start) {
+      inverted_ranges.append({next_start, range.start() - next_start});
+      if (r_skip_amounts != nullptr) {
+        r_skip_amounts->append(skip_amount);
+      }
+    }
+    next_start = range.one_after_last();
+    skip_amount += range.size();
+  }
+  if (next_start < full_range.one_after_last()) {
+    inverted_ranges.append({next_start, full_range.one_after_last() - next_start});
+    if (r_skip_amounts != nullptr) {
+      r_skip_amounts->append(skip_amount);
+    }
+  }
+  return inverted_ranges;
+}
+
 }  // namespace blender
diff --git a/source/blender/blenlib/tests/BLI_index_mask_test.cc b/source/blender/blenlib/tests/BLI_index_mask_test.cc
index 179c1c58cc4..86ae31cedcc 100644
--- a/source/blender/blenlib/tests/BLI_index_mask_test.cc
+++ b/source/blender/blenlib/tests/BLI_index_mask_test.cc
@@ -64,4 +64,153 @@ TEST(index_mask, SliceAndOffset)
   }
 }
 
+TEST(index_mask, ExtractRanges)
+{
+  {
+    Vector indices = {1, 2, 3, 5, 7, 8};
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), 3);
+    EXPECT_EQ(ranges[0], IndexRange(1, 3));
+    EXPECT_EQ(ranges[1], IndexRange(5, 1));
+    EXPECT_EQ(ranges[2], IndexRange(7, 2));
+  }
+  {
+    Vector indices;
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), 0);
+  }
+  {
+    Vector indices = {5, 6, 7, 8, 9, 10};
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), 1);
+    EXPECT_EQ(ranges[0], IndexRange(5, 6));
+  }
+  {
+    Vector indices = {1, 3, 6, 8};
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), 4);
+    EXPECT_EQ(ranges[0], IndexRange(1, 1));
+    EXPECT_EQ(ranges[1], IndexRange(3, 1));
+    EXPECT_EQ(ranges[2], IndexRange(6, 1));
+    EXPECT_EQ(ranges[3], IndexRange(8, 1));
+  }
+  {
+    Vector indices;
+    IndexRange range1{4, 10};
+    IndexRange range2{20, 30};
+    IndexRange range3{100, 1};
+    IndexRange range4{150, 100};
+    for (const IndexRange &range : {range1, range2, range3, range4}) {
+      for (const int64_t i : range) {
+        indices.append(i);
+      }
+    }
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), 4);
+    EXPECT_EQ(ranges[0], range1);
+    EXPECT_EQ(ranges[1], range2);
+    EXPECT_EQ(ranges[2], range3);
+    EXPECT_EQ(ranges[3], range4);
+  }
+  {
+    const int64_t max_test_range_size = 50;
+    Vector indices;
+    int64_t offset = 0;
+    for (const int64_t range_size : IndexRange(1, max_test_range_size)) {
+      for (const int i : IndexRange(range_size)) {
+        indices.append(offset + i);
+      }
+      offset += range_size + 1;
+    }
+    Vector ranges = IndexMask(indices).extract_ranges();
+    EXPECT_EQ(ranges.size(), max_test_range_size);
+    for (const int64_t range_size : IndexRange(1, max_test_range_size)) {
+      const IndexRange range = ranges[range_size - 1];
+      EXPECT_EQ(range.size(), range_size);
+    }
+  }
+}
+
+TEST(index_mask, Invert)
+{
+  {
+    Vector indices;
+    Vector new_indices;
+    IndexMask inverted_mask = IndexMask(indices).invert(IndexRange(10), new_indices);
+    EXPECT_EQ(inverted_mask.size(), 10);
+    EXPECT_TRUE(new_indices.is_empty());
+  }
+  {
+    Vector indices = {3, 4, 5, 6};
+    Vector new_indices;
+    IndexMask inverted_mask = IndexMask(indices).invert(IndexRange(3, 4), new_indices);
+    EXPECT_TRUE(inverted_mask.is_empty());
+  }
+  {
+    Vector indices = {5};
+    Vector new_indices;
+    IndexMask inverted_mask = IndexMask(indices).invert(IndexRange(10), new_indices);
+    EXPECT_EQ(inverted_mask.size(), 9);
+    EXPECT_EQ(inverted_mask.indices(), Span({0, 1, 2, 3, 4, 6, 7, 8, 9}));
+  }
+  {
+    Vector indices = {0, 1, 2, 6, 7, 9};
+    Vector new_indices;
+    IndexMask inverted_mask = IndexMask(indices).invert(IndexRange(10), new_indices);
+    EXPECT_EQ(inverted_mask.size(), 4);
+    EXPECT_EQ(inverted_mask.indices(), Span({3, 4, 5, 8}));
+  }
+}
+
+TEST(index_mask, ExtractRangesInvert)
+{
+  {
+    Vector indices;
+    Vector ranges = IndexMask(indices).extract_ranges_invert(IndexRange(10), nullptr);
+    EXPECT_EQ(ranges.size(), 1);
+    EXPECT_EQ(ranges[0], IndexRange(10));
+  }
+  {
+    Vector indices = {1, 2, 3, 6, 7};
+    Vector skip_amounts;
+    Vector ranges = IndexMask(indices).extract_ranges_invert(IndexRange(10),
+                                                                         &skip_amounts);
+    EXPECT_EQ(ranges.size(), 3);
+    EXPECT_EQ(ranges[0], IndexRange(0, 1));
+    EXPECT_EQ(ranges[1], IndexRange(4, 2));
+    EXPECT_EQ(ranges[2], IndexRange(8, 2));
+    EXPECT_EQ(skip_amounts[0], 0);
+    EXPECT_EQ(skip_amounts[1], 3);
+    EXPECT_EQ(skip_amounts[2], 5);
+  }
+  {
+    Vector indices = {0, 1, 2, 3, 4};
+    Vector skip_amounts;
+    Vector ranges = IndexMask(indices).extract_ranges_invert(IndexRange(5),
+                                                                         &skip_amounts);
+    EXPECT_TRUE(ranges.is_empty());
+    EXPECT_TRUE(skip_amounts.is_empty());
+  }
+  {
+    Vector indices = {5, 6, 7, 10, 11};
+    Vector skip_amounts;
+    Vector ranges = IndexMask(indices).extract_ranges_invert(IndexRange(5, 20),
+                                                                         &skip_amounts);
+    EXPECT_EQ(ranges.size(), 2);
+    EXPECT_EQ(ranges[0], IndexRange(8, 2));
+    EXPECT_EQ(ranges[1], IndexRange(12, 13));
+    EXPECT_EQ(skip_amounts[0], 3);
+    EXPECT_EQ(skip_amounts[1], 5);
+  }
+}
+
+TEST(index_mask, ContainedIn)
+{
+  EXPECT_TRUE(IndexMask({3, 4, 5}).contained_in(IndexRange(10)));
+  EXPECT_TRUE(IndexMask().contained_in(IndexRange(5, 0)));
+  EXPECT_FALSE(IndexMask({3}).contained_in(IndexRange(3)));
+  EXPECT_FALSE(IndexMask({4, 5, 6}).contained_in(IndexRange(5, 10)));
+  EXPECT_FALSE(IndexMask({5, 6}).contained_in(IndexRange()));
+}
+
 }  // namespace blender::tests
-- 
cgit v1.2.3


From 391c3848b1326db1c29fc5c5f791d732d7d282a3 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 23 Feb 2022 21:25:46 +1100
Subject: NDOF: make camera view/pan behavior optional

User request since adding this option in:
51975b89edfcc02131f1f8248e1b3442ea2778fa

When disabled, use the previous behavior when orbiting a camera view.
---
 release/datafiles/userdef/userdef_default.c              |  2 +-
 release/scripts/startup/bl_ui/space_userpref.py          |  1 +
 source/blender/blenkernel/BKE_blender_version.h          |  2 +-
 source/blender/blenloader/intern/versioning_userdef.c    |  4 ++++
 .../blender/editors/space_view3d/view3d_navigate_ndof.c  | 16 ++++++++++------
 source/blender/makesdna/DNA_userdef_types.h              |  1 +
 source/blender/makesrna/intern/rna_userdef.c             |  7 +++++++
 7 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c
index 34168cc0126..7cf8158c42d 100644
--- a/release/datafiles/userdef/userdef_default.c
+++ b/release/datafiles/userdef/userdef_default.c
@@ -144,7 +144,7 @@ const UserDef U_default = {
                    * so invert this by default, see: T67579. */
                   NDOF_ROTX_INVERT_AXIS | NDOF_ROTY_INVERT_AXIS | NDOF_ROTZ_INVERT_AXIS |
                   NDOF_PANX_INVERT_AXIS | NDOF_PANY_INVERT_AXIS | NDOF_PANZ_INVERT_AXIS |
-                  NDOF_ZOOM_INVERT),
+                  NDOF_ZOOM_INVERT | NDOF_CAMERA_PAN_ZOOM),
     .image_draw_method = IMAGE_DRAW_METHOD_AUTO,
     .glalphaclip = 0.004,
     .autokey_mode = (AUTOKEY_MODE_NORMAL & ~AUTOKEY_ON),
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index fc1911910c4..f80ad378b3c 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -1718,6 +1718,7 @@ class USERPREF_PT_ndof_settings(Panel):
         if show_3dview_settings:
             col.prop(props, "ndof_show_guide")
         col.prop(props, "ndof_zoom_invert")
+        col.prop(props, "ndof_lock_camera_pan_zoom")
         row = col.row(heading="Pan")
         row.prop(props, "ndof_pan_yz_swap_axis", text="Swap Y and Z Axes")
 
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index d09b0a02ad8..6f22e45d8d5 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -31,7 +31,7 @@ extern "C" {
  * version. Older Blender versions will test this and show a warning if the file
  * was written with too new a version. */
 #define BLENDER_FILE_MIN_VERSION 300
-#define BLENDER_FILE_MIN_SUBVERSION 42
+#define BLENDER_FILE_MIN_SUBVERSION 43
 
 /** User readable version string. */
 const char *BKE_blender_version_string(void);
diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index 8685a0fa62d..10160e9aadc 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -948,6 +948,10 @@ void blo_do_versions_userdef(UserDef *userdef)
     }
   }
 
+  if (!USER_VERSION_ATLEAST(300, 43)) {
+    userdef->ndof_flag |= NDOF_CAMERA_PAN_ZOOM;
+  }
+
   /**
    * Versioning code until next subversion bump goes here.
    *
diff --git a/source/blender/editors/space_view3d/view3d_navigate_ndof.c b/source/blender/editors/space_view3d/view3d_navigate_ndof.c
index 67b48154e8c..6b77f464773 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_ndof.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_ndof.c
@@ -500,9 +500,11 @@ static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *ev
     return OPERATOR_CANCELLED;
   }
 
-  const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event);
-  if (camera_retval != OPERATOR_PASS_THROUGH) {
-    return camera_retval;
+  if (U.ndof_flag & NDOF_CAMERA_PAN_ZOOM) {
+    const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event);
+    if (camera_retval != OPERATOR_PASS_THROUGH) {
+      return camera_retval;
+    }
   }
 
   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
@@ -619,9 +621,11 @@ static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *e
     return OPERATOR_CANCELLED;
   }
 
-  const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event);
-  if (camera_retval != OPERATOR_PASS_THROUGH) {
-    return camera_retval;
+  if (U.ndof_flag & NDOF_CAMERA_PAN_ZOOM) {
+    const int camera_retval = view3d_ndof_cameraview_pan_zoom(C, event);
+    if (camera_retval != OPERATOR_PASS_THROUGH) {
+      return camera_retval;
+    }
   }
 
   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index e081be73a1c..80a107e4bae 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -1318,6 +1318,7 @@ typedef enum eNdof_Flag {
   NDOF_PANY_INVERT_AXIS = (1 << 13),
   NDOF_PANZ_INVERT_AXIS = (1 << 14),
   NDOF_TURNTABLE = (1 << 15),
+  NDOF_CAMERA_PAN_ZOOM = (1 << 16),
 } eNdof_Flag;
 
 #define NDOF_PIXELS_PER_SECOND 600.0f
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index e32bedf3ed0..2a759dde39a 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -6040,6 +6040,13 @@ static void rna_def_userdef_input(BlenderRNA *brna)
                            "Helicopter Mode",
                            "Device up/down directly controls the Z position of the 3D viewport");
 
+  prop = RNA_def_property(srna, "ndof_lock_camera_pan_zoom", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "ndof_flag", NDOF_CAMERA_PAN_ZOOM);
+  RNA_def_property_ui_text(
+      prop,
+      "Lock Camera Pan/Zoom",
+      "Pan/zoom the camera view instead of leaving the camera view when orbiting");
+
   /* let Python know whether NDOF is enabled */
   prop = RNA_def_boolean(srna, "use_ndof", true, "", "");
 #  else
-- 
cgit v1.2.3


From 867e50b8862c0c14ef37479a5e0ed8057fd3f637 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Wed, 23 Feb 2022 14:25:00 +0100
Subject: Fix T95183: Cage gizmo axis unreliable

`orient_matrix` was not initialized.

Reviewed By: lichtwerk

Differential Revision: https://developer.blender.org/D14167
---
 source/blender/editors/transform/transform_gizmo_2d.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c
index c3a0e4b1163..d843195b803 100644
--- a/source/blender/editors/transform/transform_gizmo_2d.c
+++ b/source/blender/editors/transform/transform_gizmo_2d.c
@@ -683,6 +683,7 @@ static void gizmo2d_xform_invoke_prepare(const bContext *C,
   float c[3] = {mid[0], mid[1], 0.0f};
 
   float orient_matrix[3][3];
+  unit_m3(orient_matrix);
 
   ScrArea *area = CTX_wm_area(C);
 
-- 
cgit v1.2.3


From 1361c6e60460b0db1d0b6878a7043940afd25ad7 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Wed, 23 Feb 2022 08:54:35 -0500
Subject: Curves: Add methods to retrieve range for points or curves

---
 source/blender/blenkernel/BKE_curves.hh             | 2 ++
 source/blender/blenkernel/intern/curves_geometry.cc | 9 +++++++++
 2 files changed, 11 insertions(+)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 0f0b77902cc..209f892c651 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -82,6 +82,8 @@ class CurvesGeometry : public ::CurvesGeometry {
 
   int points_size() const;
   int curves_size() const;
+  IndexRange points_range() const;
+  IndexRange curves_range() const;
 
   /**
    * The total number of points in the evaluated poly curve.
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index 3e45fce3776..eb2a0b37af2 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -106,6 +106,15 @@ int CurvesGeometry::curves_size() const
 {
   return this->curve_size;
 }
+IndexRange CurvesGeometry::points_range() const
+{
+  return IndexRange(this->points_size());
+}
+IndexRange CurvesGeometry::curves_range() const
+{
+  return IndexRange(this->curves_size());
+}
+
 int CurvesGeometry::evaluated_points_size() const
 {
   /* TODO: Implement when there are evaluated points. */
-- 
cgit v1.2.3


From 398538b914a3c7ff3cf845f4fe6b2bdbb47204cb Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Wed, 23 Feb 2022 09:49:25 -0500
Subject: Fix T95987: Data transfer modifier custom normals crash

59343ee1627f4c369e missed one case of normals being
retrieved from polygon custom data instead of the normals API.
The fix is simple.
---
 source/blender/blenkernel/intern/data_transfer.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c
index 79aecb4598a..5aa364f5f16 100644
--- a/source/blender/blenkernel/intern/data_transfer.c
+++ b/source/blender/blenkernel/intern/data_transfer.c
@@ -280,7 +280,6 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src,
 
     /* This should be ensured by cddata_masks we pass to code generating/giving us me_src now. */
     BLI_assert(CustomData_get_layer(&me_src->ldata, CD_NORMAL) != NULL);
-    BLI_assert(CustomData_get_layer(&me_src->pdata, CD_NORMAL) != NULL);
     (void)me_src;
 
     float(*loop_nors_dst)[3];
@@ -335,15 +334,12 @@ static void data_transfer_dtdata_type_postprocess(Object *UNUSED(ob_src),
     const int num_polys_dst = me_dst->totpoly;
     MLoop *loops_dst = me_dst->mloop;
     const int num_loops_dst = me_dst->totloop;
-    CustomData *pdata_dst = &me_dst->pdata;
     CustomData *ldata_dst = &me_dst->ldata;
 
-    const float(*poly_nors_dst)[3] = CustomData_get_layer(pdata_dst, CD_NORMAL);
+    const float(*poly_nors_dst)[3] = BKE_mesh_poly_normals_ensure(me_dst);
     float(*loop_nors_dst)[3] = CustomData_get_layer(ldata_dst, CD_NORMAL);
     short(*custom_nors_dst)[2] = CustomData_get_layer(ldata_dst, CD_CUSTOMLOOPNORMAL);
 
-    BLI_assert(poly_nors_dst);
-
     if (!custom_nors_dst) {
       custom_nors_dst = CustomData_add_layer(
           ldata_dst, CD_CUSTOMLOOPNORMAL, CD_CALLOC, NULL, num_loops_dst);
-- 
cgit v1.2.3


From 120f16fa1f1efd3cbb4da191d2912e0a6ce3ea59 Mon Sep 17 00:00:00 2001
From: Johnny Matthews 
Date: Wed, 23 Feb 2022 09:08:16 -0600
Subject: Geometry Nodes: Duplicate Elements Node This adds a node which copies
 part of a geometry a dynamic number of times.

Different parts of the geometry can be copied differing amounts
of times, controlled by the amount input field. Geometry can also
be ignored by use of the selection input.

The output geometry contains only the copies created by the node.
if the amount input is set to zero, the output geometry will be
empty. The duplicate index output is an integer index with the copy
number of each duplicate.

Differential Revision: https://developer.blender.org/D13701
---
 release/scripts/startup/nodeitems_builtins.py      |    1 +
 source/blender/blenkernel/BKE_node.h               |    2 +
 source/blender/blenkernel/intern/node.cc           |    1 +
 source/blender/makesdna/DNA_node_types.h           |    5 +
 source/blender/makesrna/intern/rna_nodetree.c      |   21 +
 source/blender/nodes/NOD_geometry.h                |    1 +
 source/blender/nodes/NOD_static_types.h            |    1 +
 source/blender/nodes/geometry/CMakeLists.txt       |    1 +
 .../geometry/nodes/node_geo_duplicate_elements.cc  | 1098 ++++++++++++++++++++
 9 files changed, 1131 insertions(+)
 create mode 100644 source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 21e20c3b734..cea938bf1a4 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -164,6 +164,7 @@ def geometry_node_items(context):
     yield NodeItem("GeometryNodeBoundBox")
     yield NodeItem("GeometryNodeConvexHull")
     yield NodeItem("GeometryNodeDeleteGeometry")
+    yield NodeItem("GeometryNodeDuplicateElements")
     yield NodeItem("GeometryNodeGeometryToInstance")
     yield NodeItem("GeometryNodeMergeByDistance")
     yield NodeItem("GeometryNodeProximity")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 933585b1f8f..024e98daea5 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1515,6 +1515,8 @@ struct TexResult;
 #define GEO_NODE_EXTRUDE_MESH 1152
 #define GEO_NODE_MERGE_BY_DISTANCE 1153
 
+#define GEO_NODE_DUPLICATE_ELEMENTS 1160
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index ad24b3c0eff..474859128dc 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4751,6 +4751,7 @@ static void registerGeometryNodes()
   register_node_type_geo_curve_to_points();
   register_node_type_geo_curve_trim();
   register_node_type_geo_delete_geometry();
+  register_node_type_geo_duplicate_elements();
   register_node_type_geo_distribute_points_on_faces();
   register_node_type_geo_dual_mesh();
   register_node_type_geo_edge_split();
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 963a34aa645..18b79a6fc25 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1610,6 +1610,11 @@ typedef struct NodeGeometryDeleteGeometry {
   int8_t mode;
 } NodeGeometryDeleteGeometry;
 
+typedef struct NodeGeometryDuplicateElements {
+  /* AttributeDomain. */
+  int8_t domain;
+} NodeGeometryDuplicateElements;
+
 typedef struct NodeGeometrySeparateGeometry {
   /* AttributeDomain. */
   int8_t domain;
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 85bf98914eb..387166e77b4 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -11287,6 +11287,27 @@ static void def_geo_delete_geometry(StructRNA *srna)
   RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
 }
 
+static void def_geo_duplicate_elements(StructRNA *srna)
+{
+  PropertyRNA *prop;
+
+  static const EnumPropertyItem domain_items[] = {
+      {ATTR_DOMAIN_POINT, "POINT", 0, "Point", ""},
+      {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", ""},
+      {ATTR_DOMAIN_FACE, "FACE", 0, "Face", ""},
+      {ATTR_DOMAIN_CURVE, "SPLINE", 0, "Spline", ""},
+      {ATTR_DOMAIN_INSTANCE, "INSTANCE", 0, "Instance", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+  RNA_def_struct_sdna_from(srna, "NodeGeometryDuplicateElements", "storage");
+
+  prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_items(prop, domain_items);
+  RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
+  RNA_def_property_ui_text(prop, "Domain", "Which domain to duplicate");
+  RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
 static void def_geo_string_to_curves(StructRNA *srna)
 {
   static const EnumPropertyItem rna_node_geometry_string_to_curves_overflow_items[] = {
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 86dde999ffc..0a53d9cc019 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -82,6 +82,7 @@ void register_node_type_geo_curve_to_mesh(void);
 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_duplicate_elements(void);
 void register_node_type_geo_distribute_points_on_faces(void);
 void register_node_type_geo_dual_mesh(void);
 void register_node_type_geo_edge_split(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 1f1d7e4dc3a..a3033651a3a 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -338,6 +338,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CU
 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_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
+DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "")
 DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")
 DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "")
 DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index f38562a8926..fbf584e0f4b 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -98,6 +98,7 @@ set(SRC
   nodes/node_geo_curve_to_points.cc
   nodes/node_geo_curve_trim.cc
   nodes/node_geo_delete_geometry.cc
+  nodes/node_geo_duplicate_elements.cc
   nodes/node_geo_distribute_points_on_faces.cc
   nodes/node_geo_dual_mesh.cc
   nodes/node_geo_edge_split.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
new file mode 100644
index 00000000000..4c1d26e1012
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -0,0 +1,1098 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_map.hh"
+#include "BLI_noise.hh"
+#include "BLI_span.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_pointcloud_types.h"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_mesh.h"
+#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
+
+#include "node_geometry_util.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+namespace blender::nodes::node_geo_duplicate_elements_cc {
+
+NODE_STORAGE_FUNCS(NodeGeometryDuplicateElements);
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+  b.add_input(N_("Geometry"));
+  b.add_input(N_("Selection")).hide_value().default_value(true).supports_field();
+  b.add_input(N_("Amount"))
+      .min(0)
+      .default_value(1)
+      .supports_field()
+      .description(N_("The number of duplicates to create for each element"));
+
+  b.add_output(N_("Geometry"))
+      .description(
+          N_("The duplicated geometry only. The output does not contain the original geometry"));
+  b.add_output(N_("Duplicate Index"))
+      .field_source()
+      .description(N_("The indices of the duplicates for each element"));
+}
+
+static void node_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+  NodeGeometryDuplicateElements *data = MEM_cnew(__func__);
+  data->domain = ATTR_DOMAIN_POINT;
+  node->storage = data;
+}
+
+static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+  uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+}
+
+struct IndexAttributes {
+  StrongAnonymousAttributeID duplicate_index;
+};
+
+/* --------------------------------------------------------------------
+ * Attribute Copy/Creation Functions.
+ */
+
+static void gather_attributes_without_id(const GeometrySet &geometry_set,
+                                         const GeometryComponentType component_type,
+                                         const Span skip_attributes,
+                                         const bool include_instances,
+                                         Map &r_gathered_attributes)
+{
+  geometry_set.gather_attributes_for_propagation(
+      {component_type}, component_type, include_instances, r_gathered_attributes);
+  for (const std::string &attribute : skip_attributes) {
+    r_gathered_attributes.remove(attribute);
+  }
+  r_gathered_attributes.remove("id");
+};
+
+static IndexRange range_for_offsets_index(const Span offsets, const int index)
+{
+  return {offsets[index], offsets[index + 1] - offsets[index]};
+}
+
+static Array accumulate_counts_to_offsets(const IndexMask selection,
+                                               const VArray &counts)
+{
+  Array offsets(selection.size() + 1);
+  int dst_points_size = 0;
+  for (const int i_point : selection.index_range()) {
+    offsets[i_point] = dst_points_size;
+    dst_points_size += std::max(counts[selection[i_point]], 0);
+  }
+  offsets.last() = dst_points_size;
+  return offsets;
+}
+
+/* Utility functions for threaded copying of attribute data where possible. */
+template
+static void threaded_slice_fill(Span offsets, Span src, MutableSpan dst)
+{
+  BLI_assert(offsets.last() == dst.size());
+  threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+    for (const int i : range) {
+      dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
+    }
+  });
+}
+
+template
+static void threaded_mapped_copy(const Span mapping, const Span src, MutableSpan dst)
+{
+  threading::parallel_for(mapping.index_range(), 512, [&](IndexRange range) {
+    for (const int i : range) {
+      dst[i] = src[mapping[i]];
+    }
+  });
+}
+
+static void threaded_id_offset_copy(const Span offsets,
+                                    const Span src,
+                                    MutableSpan dst)
+{
+  BLI_assert(offsets.last() == dst.size());
+  threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+    for (const int i : range) {
+      dst[offsets[i]] = src[i];
+      const int count = offsets[i + 1] - offsets[i];
+      for (const int i_duplicate : IndexRange(1, count - 1)) {
+        dst[offsets[i] + i_duplicate] = noise::hash(src[i], i_duplicate);
+      }
+    }
+  });
+}
+
+/* Create the copy indices for the duplication domain. */
+static void create_duplicate_index_attribute(GeometryComponent &component,
+                                             const AttributeDomain output_domain,
+                                             const IndexMask selection,
+                                             const IndexAttributes &attributes,
+                                             const Span offsets)
+{
+  OutputAttribute_Typed copy_attribute = component.attribute_try_get_for_output_only(
+      attributes.duplicate_index.get(), output_domain);
+  MutableSpan duplicate_indices = copy_attribute.as_span();
+  for (const int i : IndexRange(selection.size())) {
+    const IndexRange range = range_for_offsets_index(offsets, i);
+    MutableSpan indices = duplicate_indices.slice(range);
+    for (const int i : indices.index_range()) {
+      indices[i] = i;
+    }
+  }
+  copy_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for the point domain elements. */
+static void copy_stable_id_point(const Span offsets,
+                                 const GeometryComponent &src_component,
+                                 GeometryComponent &dst_component)
+{
+  ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+  if (!src_attribute) {
+    return;
+  }
+  OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+      "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+  if (!dst_attribute) {
+    return;
+  }
+
+  VArray_Span src{src_attribute.varray.typed()};
+  MutableSpan dst = dst_attribute.as_span();
+  threaded_id_offset_copy(offsets, src, dst);
+  dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the edge domain.
+ */
+static void copy_stable_id_edges(const Mesh &mesh,
+                                 const IndexMask selection,
+                                 const Span edge_offsets,
+                                 const GeometryComponent &src_component,
+                                 GeometryComponent &dst_component)
+{
+  ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+  if (!src_attribute) {
+    return;
+  }
+  OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+      "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+  if (!dst_attribute) {
+    return;
+  }
+
+  Span edges(mesh.medge, mesh.totedge);
+
+  VArray_Span src{src_attribute.varray.typed()};
+  MutableSpan dst = dst_attribute.as_span();
+  threading::parallel_for(IndexRange(selection.size()), 1024, [&](IndexRange range) {
+    for (const int i_edge : range) {
+      const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+      if (edge_range.size() == 0) {
+        continue;
+      }
+      const MEdge &edge = edges[i_edge];
+      const IndexRange vert_range = {edge_range.start() * 2, edge_range.size() * 2};
+
+      dst[vert_range[0]] = src[edge.v1];
+      dst[vert_range[1]] = src[edge.v2];
+      for (const int i_duplicate : IndexRange(1, edge_range.size() - 1)) {
+        dst[vert_range[i_duplicate * 2]] = noise::hash(src[edge.v1], i_duplicate);
+        dst[vert_range[i_duplicate * 2 + 1]] = noise::hash(src[edge.v2], i_duplicate);
+      }
+    }
+  });
+  dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the face domain.
+ *
+ * This function could be threaded in the future, but since it is only 1 attribute and the
+ * face->edge->vert mapping would mean creating a 1/1 mapping to allow for it, is it worth it?
+ */
+static void copy_stable_id_faces(const Mesh &mesh,
+                                 const IndexMask selection,
+                                 const Span poly_offsets,
+                                 const Span vert_mapping,
+                                 const GeometryComponent &src_component,
+                                 GeometryComponent &dst_component)
+{
+  ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+  if (!src_attribute) {
+    return;
+  }
+  OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+      "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+  if (!dst_attribute) {
+    return;
+  }
+
+  VArray_Span src{src_attribute.varray.typed()};
+  MutableSpan dst = dst_attribute.as_span();
+
+  Span polys(mesh.mpoly, mesh.totpoly);
+  int loop_index = 0;
+  for (const int i_poly : selection.index_range()) {
+    const IndexRange range = range_for_offsets_index(poly_offsets, i_poly);
+    if (range.size() == 0) {
+      continue;
+    }
+    const MPoly &source = polys[i_poly];
+    for ([[maybe_unused]] const int i_duplicate : IndexRange(range.size())) {
+      for ([[maybe_unused]] const int i_loops : IndexRange(source.totloop)) {
+        if (i_duplicate == 0) {
+          dst[loop_index] = src[vert_mapping[loop_index]];
+        }
+        else {
+          dst[loop_index] = noise::hash(src[vert_mapping[loop_index]], i_duplicate);
+        }
+        loop_index++;
+      }
+    }
+  }
+
+  dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. In the spline case, copy the entire spline's points to the
+ * destination,
+ * then loop over the remaining ones point by point, hashing their ids to the new ids. */
+static void copy_stable_id_splines(const CurveEval &curve,
+                                   const IndexMask selection,
+                                   const Span curve_offsets,
+                                   const GeometryComponent &src_component,
+                                   GeometryComponent &dst_component)
+{
+  ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+  if (!src_attribute) {
+    return;
+  }
+  OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+      "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+  if (!dst_attribute) {
+    return;
+  }
+
+  Array control_point_offsets = curve.control_point_offsets();
+  VArray_Span src{src_attribute.varray.typed()};
+  MutableSpan dst = dst_attribute.as_span();
+
+  Array curve_point_offsets(selection.size() + 1);
+  int dst_point_size = 0;
+  for (const int i_curve : selection.index_range()) {
+    const int spline_size = curve.splines()[i_curve]->size();
+    const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+    curve_point_offsets[i_curve] = dst_point_size;
+    dst_point_size += curve_range.size() * spline_size;
+  }
+  curve_point_offsets.last() = dst_point_size;
+
+  threading::parallel_for(IndexRange(curve_point_offsets.size() - 1), 512, [&](IndexRange range) {
+    for (const int i_curve : range) {
+      const int spline_size = curve.splines()[i_curve]->size();
+      const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+      dst.slice(curve_point_offsets[i_curve], spline_size)
+          .copy_from(src.slice(control_point_offsets[i_curve], spline_size));
+      for (const int i_duplicate : IndexRange(1, curve_range.size() - 1)) {
+        for (const int i_point : IndexRange(spline_size)) {
+          dst[curve_point_offsets[i_curve] + i_duplicate * spline_size + i_point] = noise::hash(
+              src[control_point_offsets[i_curve] + i_point], i_duplicate);
+        }
+      }
+    }
+  });
+  dst_attribute.save();
+}
+
+/* The attributes for the point (also instance) duplicated elements are stored sequentially
+ * (1,1,1,2,2,2,3,3,3,etc) They can be copied by using a simple offset array. For each domain, if
+ * elements are ordered differently a custom function is called to copy the attributes.
+ */
+
+static void copy_point_attributes_without_id(GeometrySet &geometry_set,
+                                             const GeometryComponentType component_type,
+                                             const bool include_instances,
+                                             const Span offsets,
+                                             const GeometryComponent &src_component,
+                                             GeometryComponent &dst_component)
+{
+  Map gathered_attributes;
+  gather_attributes_without_id(
+      geometry_set, component_type, {}, include_instances, gathered_attributes);
+
+  for (const Map::Item entry : gathered_attributes.items()) {
+    const AttributeIDRef attribute_id = entry.key;
+    ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+    if (!src_attribute || src_attribute.domain != ATTR_DOMAIN_POINT) {
+      continue;
+    }
+    AttributeDomain out_domain = src_attribute.domain;
+    const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+        src_attribute.varray.type());
+    OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+        attribute_id, out_domain, data_type);
+    if (!dst_attribute) {
+      continue;
+    }
+    attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+      using T = decltype(dummy);
+      VArray_Span src = src_attribute.varray.typed();
+      MutableSpan dst = dst_attribute.as_span();
+      threaded_slice_fill(offsets, src, dst);
+    });
+    dst_attribute.save();
+  }
+}
+
+/* Copies the attributes for spline duplciates. If copying the spline domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_spline_attributes_without_id(const GeometrySet &geometry_set,
+                                              const Span point_mapping,
+                                              const Span offsets,
+                                              const Span attributes_to_ignore,
+                                              const GeometryComponent &src_component,
+                                              GeometryComponent &dst_component)
+{
+  Map gathered_attributes;
+  gather_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_CURVE, attributes_to_ignore, false, gathered_attributes);
+
+  for (const Map::Item entry : gathered_attributes.items()) {
+
+    const AttributeIDRef attribute_id = entry.key;
+    ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+    if (!src_attribute) {
+      continue;
+    }
+
+    AttributeDomain out_domain = src_attribute.domain;
+    const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+        src_attribute.varray.type());
+    OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+        attribute_id, out_domain, data_type);
+    if (!dst_attribute) {
+      continue;
+    }
+
+    attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+      using T = decltype(dummy);
+      VArray_Span src{src_attribute.varray.typed()};
+      MutableSpan dst = dst_attribute.as_span();
+
+      switch (out_domain) {
+        case ATTR_DOMAIN_CURVE:
+          threaded_slice_fill(offsets, src, dst);
+          break;
+        case ATTR_DOMAIN_POINT:
+          threaded_mapped_copy(point_mapping, src, dst);
+          break;
+        default:
+          break;
+      }
+    });
+    dst_attribute.save();
+  }
+}
+
+/* Copies the attributes for edge duplciates. If copying the edge domain, the attributes are
+ * copied with an offset fill, for point domain a mapping is used. */
+static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
+                                            const Span point_mapping,
+                                            const Span offsets,
+                                            const GeometryComponent &src_component,
+                                            GeometryComponent &dst_component)
+{
+  Map gathered_attributes;
+  gather_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+  for (const Map::Item entry : gathered_attributes.items()) {
+    const AttributeIDRef attribute_id = entry.key;
+    ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+    if (!src_attribute) {
+      continue;
+    }
+
+    const AttributeDomain out_domain = src_attribute.domain;
+    const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+        src_attribute.varray.type());
+    OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+        attribute_id, out_domain, data_type);
+    if (!dst_attribute) {
+      continue;
+    }
+    attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+      using T = decltype(dummy);
+      VArray_Span src{src_attribute.varray.typed()};
+      MutableSpan dst = dst_attribute.as_span();
+
+      switch (out_domain) {
+        case ATTR_DOMAIN_EDGE:
+          threaded_slice_fill(offsets, src, dst);
+          break;
+        case ATTR_DOMAIN_POINT:
+          threaded_mapped_copy(point_mapping, src, dst);
+          break;
+        default:
+          break;
+      }
+    });
+    dst_attribute.save();
+  }
+}
+
+/* Copies the attributes for face duplciates. If copying the face domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_face_attributes_without_id(GeometrySet &geometry_set,
+                                            const Span edge_mapping,
+                                            const Span vert_mapping,
+                                            const Span loop_mapping,
+                                            const Span offsets,
+                                            const GeometryComponent &src_component,
+                                            GeometryComponent &dst_component)
+{
+  Map gathered_attributes;
+  gather_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+  for (const Map::Item entry : gathered_attributes.items()) {
+    const AttributeIDRef attribute_id = entry.key;
+    ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+    if (!src_attribute) {
+      continue;
+    }
+
+    AttributeDomain out_domain = src_attribute.domain;
+    const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+        src_attribute.varray.type());
+    OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+        attribute_id, out_domain, data_type);
+    if (!dst_attribute) {
+      continue;
+    }
+
+    attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+      using T = decltype(dummy);
+      VArray_Span src{src_attribute.varray.typed()};
+      MutableSpan dst = dst_attribute.as_span();
+
+      switch (out_domain) {
+        case ATTR_DOMAIN_FACE:
+          threaded_slice_fill(offsets, src, dst);
+          break;
+        case ATTR_DOMAIN_EDGE:
+          threaded_mapped_copy(edge_mapping, src, dst);
+          break;
+        case ATTR_DOMAIN_POINT:
+          threaded_mapped_copy(vert_mapping, src, dst);
+          break;
+        case ATTR_DOMAIN_CORNER:
+          threaded_mapped_copy(loop_mapping, src, dst);
+          break;
+        default:
+          break;
+      }
+    });
+    dst_attribute.save();
+  }
+}
+
+/* --------------------------------------------------------------------
+ * Duplication Functions.
+ */
+
+static void duplicate_splines(GeometrySet &geometry_set,
+                              const Field &count_field,
+                              const Field &selection_field,
+                              IndexAttributes &attributes)
+{
+  if (!geometry_set.has_curve()) {
+    geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+    return;
+  }
+  geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
+
+  const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+      GEO_COMPONENT_TYPE_CURVE);
+  const CurveEval &curve = *geometry_set.get_curve_for_read();
+  const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
+  GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const VArray counts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  Array curve_offsets(selection.size() + 1);
+
+  int dst_splines_size = 0;
+  int dst_points_size = 0;
+  for (const int i_spline : selection.index_range()) {
+    int count = std::max(counts[selection[i_spline]], 0);
+    curve_offsets[i_spline] = dst_splines_size;
+    dst_splines_size += count;
+    dst_points_size += count * curve.splines()[selection[i_spline]]->size();
+  }
+  curve_offsets.last() = dst_splines_size;
+
+  Array control_point_offsets = curve.control_point_offsets();
+  Array point_mapping(dst_points_size);
+
+  std::unique_ptr new_curve = std::make_unique();
+  int point_index = 0;
+  for (const int i_spline : selection.index_range()) {
+    const IndexRange spline_range = range_for_offsets_index(curve_offsets, i_spline);
+    for ([[maybe_unused]] const int i_duplicate : IndexRange(spline_range.size())) {
+      SplinePtr spline = curve.splines()[selection[i_spline]]->copy();
+      for (const int i_point : IndexRange(curve.splines()[selection[i_spline]]->size())) {
+        point_mapping[point_index++] = control_point_offsets[selection[i_spline]] + i_point;
+      }
+      new_curve->add_spline(std::move(spline));
+    }
+  }
+  new_curve->attributes.reallocate(new_curve->splines().size());
+
+  CurveComponent dst_component;
+  dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+  Vector skip(
+      {"position", "radius", "resolution", "cyclic", "tilt", "handle_left", "handle_right"});
+
+  copy_spline_attributes_without_id(
+      geometry_set, point_mapping, curve_offsets, skip, src_component, dst_component);
+
+  copy_stable_id_splines(curve, selection, curve_offsets, src_component, dst_component);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_CURVE, selection, attributes, curve_offsets);
+  }
+
+  geometry_set.replace_curve(dst_component.get_for_write());
+}
+
+static void duplicate_faces(GeometrySet &geometry_set,
+                            const Field &count_field,
+                            const Field &selection_field,
+                            IndexAttributes &attributes)
+{
+  if (!geometry_set.has_mesh()) {
+    geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+    return;
+  }
+  geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
+
+  GeometryComponent &component = geometry_set.get_component_for_write(GEO_COMPONENT_TYPE_MESH);
+  const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE);
+
+  GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE};
+  FieldEvaluator evaluator(field_context, domain_size);
+
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+  const VArray counts = evaluator.get_evaluated(0);
+
+  MeshComponent &mesh_component = static_cast(component);
+  const Mesh &mesh = *mesh_component.get_for_read();
+  Span verts(mesh.mvert, mesh.totvert);
+  Span edges(mesh.medge, mesh.totedge);
+  Span polys(mesh.mpoly, mesh.totpoly);
+  Span loops(mesh.mloop, mesh.totloop);
+
+  int total_polys = 0;
+  int total_loops = 0;
+  Array offsets(selection.size() + 1);
+  for (const int i_selection : selection.index_range()) {
+    const int count = std::max(counts[selection[i_selection]], 0);
+    offsets[i_selection] = total_polys;
+    total_polys += count;
+    total_loops += count * polys[selection[i_selection]].totloop;
+  }
+  offsets[selection.size()] = total_polys;
+
+  Array vert_mapping(total_loops);
+  Array edge_mapping(total_loops);
+  Array loop_mapping(total_loops);
+
+  Mesh *new_mesh = BKE_mesh_new_nomain(total_loops, total_loops, 0, total_loops, total_polys);
+
+  MutableSpan new_verts(new_mesh->mvert, new_mesh->totvert);
+  MutableSpan new_edges(new_mesh->medge, new_mesh->totedge);
+  MutableSpan new_loops(new_mesh->mloop, new_mesh->totloop);
+  MutableSpan new_poly(new_mesh->mpoly, new_mesh->totpoly);
+
+  int poly_index = 0;
+  int loop_index = 0;
+  for (const int i_selection : selection.index_range()) {
+    const IndexRange poly_range = range_for_offsets_index(offsets, i_selection);
+
+    const MPoly &source = polys[selection[i_selection]];
+    for ([[maybe_unused]] const int i_duplicate : IndexRange(poly_range.size())) {
+      new_poly[poly_index] = source;
+      new_poly[poly_index].loopstart = loop_index;
+      for (const int i_loops : IndexRange(source.totloop)) {
+        const MLoop ¤t_loop = loops[source.loopstart + i_loops];
+        loop_mapping[loop_index] = source.loopstart + i_loops;
+        new_verts[loop_index] = verts[current_loop.v];
+        vert_mapping[loop_index] = current_loop.v;
+        new_edges[loop_index] = edges[current_loop.e];
+        edge_mapping[loop_index] = current_loop.e;
+        new_edges[loop_index].v1 = loop_index;
+        if (i_loops + 1 != source.totloop) {
+          new_edges[loop_index].v2 = loop_index + 1;
+        }
+        else {
+          new_edges[loop_index].v2 = new_poly[poly_index].loopstart;
+        }
+        new_loops[loop_index].v = loop_index;
+        new_loops[loop_index].e = loop_index;
+        loop_index++;
+      }
+      poly_index++;
+    }
+  }
+  MeshComponent dst_component;
+  dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+  copy_face_attributes_without_id(geometry_set,
+                                  edge_mapping,
+                                  vert_mapping,
+                                  loop_mapping,
+                                  offsets,
+                                  mesh_component,
+                                  dst_component);
+
+  copy_stable_id_faces(mesh, selection, offsets, vert_mapping, mesh_component, dst_component);
+  mesh_component.replace(dst_component.get_for_write());
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_FACE, selection, attributes, offsets);
+  }
+}
+
+static void duplicate_edges(GeometrySet &geometry_set,
+                            const Field &count_field,
+                            const Field &selection_field,
+                            IndexAttributes &attributes)
+{
+  if (!geometry_set.has_mesh()) {
+    geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+    return;
+  };
+  const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+      GEO_COMPONENT_TYPE_MESH);
+  const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
+
+  GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_EDGE};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const VArray counts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  Array edge_offsets = accumulate_counts_to_offsets(selection, counts);
+
+  const Mesh *mesh = geometry_set.get_mesh_for_read();
+  Span verts(mesh->mvert, mesh->totvert);
+  Span edges(mesh->medge, mesh->totedge);
+
+  Mesh *new_mesh = BKE_mesh_new_nomain(edge_offsets.last() * 2, edge_offsets.last(), 0, 0, 0);
+  MutableSpan new_verts(new_mesh->mvert, new_mesh->totvert);
+  MutableSpan new_edges(new_mesh->medge, new_mesh->totedge);
+
+  Array vert_orig_indices(edge_offsets.last() * 2);
+  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+    for (const int i_edge : range) {
+      const MEdge &edge = edges[i_edge];
+      const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+      const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+
+      for (const int i_duplicate : IndexRange(edge_range.size())) {
+        vert_orig_indices[vert_range[i_duplicate * 2]] = edge.v1;
+        vert_orig_indices[vert_range[i_duplicate * 2 + 1]] = edge.v2;
+      }
+    }
+  });
+
+  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+    for (const int i_edge : range) {
+      const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+      const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+      for (const int i_duplicate : IndexRange(edge_range.size())) {
+        MEdge &new_edge = new_edges[edge_range[i_duplicate]];
+        new_edge.v1 = vert_range[i_duplicate * 2];
+        new_edge.v2 = vert_range[i_duplicate * 2] + 1;
+      }
+    }
+  });
+
+  MeshComponent dst_component;
+  dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+  copy_edge_attributes_without_id(
+      geometry_set, vert_orig_indices, edge_offsets, src_component, dst_component);
+
+  copy_stable_id_edges(*mesh, selection, edge_offsets, src_component, dst_component);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_EDGE, selection, attributes, edge_offsets);
+  }
+
+  MeshComponent &mesh_component = geometry_set.get_component_for_write();
+  mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_curve(const GeometryComponentType component_type,
+                                   const Field &count_field,
+                                   const Field &selection_field,
+                                   GeometrySet &geometry_set,
+                                   IndexAttributes &attributes)
+{
+  const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+  const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+  GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const VArray counts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  Array offsets = accumulate_counts_to_offsets(selection, counts);
+
+  CurveComponent &curve_component = geometry_set.get_component_for_write();
+  const CurveEval &curve = *geometry_set.get_curve_for_read();
+  Array control_point_offsets = curve.control_point_offsets();
+  std::unique_ptr new_curve = std::make_unique();
+
+  Array parent(domain_size);
+  int spline = 0;
+  for (const int i_spline : IndexRange(domain_size)) {
+    if (i_spline == control_point_offsets[spline + 1]) {
+      spline++;
+    }
+    parent[i_spline] = spline;
+  }
+
+  for (const int i_point : selection) {
+    const IndexRange point_range = range_for_offsets_index(offsets, i_point);
+    for ([[maybe_unused]] const int i_duplicate : IndexRange(point_range.size())) {
+      const SplinePtr &parent_spline = curve.splines()[parent[i_point]];
+      switch (parent_spline->type()) {
+        case CurveType::CURVE_TYPE_BEZIER: {
+          std::unique_ptr spline = std::make_unique();
+          spline->resize(1);
+          spline->set_resolution(2);
+          new_curve->add_spline(std::move(spline));
+          break;
+        }
+        case CurveType::CURVE_TYPE_NURBS: {
+          std::unique_ptr spline = std::make_unique();
+          spline->resize(1);
+          spline->set_resolution(2);
+          new_curve->add_spline(std::move(spline));
+          break;
+        }
+        case CurveType::CURVE_TYPE_POLY: {
+          std::unique_ptr spline = std::make_unique();
+          spline->resize(1);
+          new_curve->add_spline(std::move(spline));
+          break;
+        }
+        case CurveType::CURVE_TYPE_CATMULL_ROM: {
+          /* Catmull Rom curves are not supported yet. */
+          break;
+        }
+      }
+    }
+  }
+  new_curve->attributes.reallocate(new_curve->splines().size());
+  CurveComponent dst_component;
+  dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+  copy_point_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_CURVE, false, offsets, src_component, dst_component);
+
+  copy_stable_id_point(offsets, src_component, dst_component);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+  }
+
+  curve_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_mesh(const GeometryComponentType component_type,
+                                  const Field &count_field,
+                                  const Field &selection_field,
+                                  GeometrySet &geometry_set,
+                                  IndexAttributes &attributes)
+{
+  const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+  const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+  GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const VArray counts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  Array offsets = accumulate_counts_to_offsets(selection, counts);
+
+  const Mesh *mesh = geometry_set.get_mesh_for_read();
+  Span src_verts(mesh->mvert, mesh->totvert);
+
+  Mesh *new_mesh = BKE_mesh_new_nomain(offsets.last(), 0, 0, 0, 0);
+  MutableSpan dst_verts(new_mesh->mvert, new_mesh->totvert);
+
+  threaded_slice_fill(offsets.as_span(), src_verts, dst_verts);
+
+  MeshComponent dst_component;
+  dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+  copy_point_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_MESH, false, offsets, src_component, dst_component);
+
+  copy_stable_id_point(offsets, src_component, dst_component);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+  }
+
+  MeshComponent &mesh_component = geometry_set.get_component_for_write();
+  mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_pointcloud(const GeometryComponentType component_type,
+                                        const Field &count_field,
+                                        const Field &selection_field,
+                                        GeometrySet &geometry_set,
+                                        IndexAttributes &attributes)
+{
+  const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+  const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+  GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  const VArray counts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  Array offsets = accumulate_counts_to_offsets(selection, counts);
+
+  PointCloud *pointcloud = BKE_pointcloud_new_nomain(offsets.last());
+  PointCloudComponent dst_component;
+  dst_component.replace(pointcloud, GeometryOwnershipType::Editable);
+
+  copy_point_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_POINT_CLOUD, false, offsets, src_component, dst_component);
+
+  copy_stable_id_point(offsets, src_component, dst_component);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets);
+  }
+  geometry_set.replace_pointcloud(pointcloud);
+}
+
+static void duplicate_points(GeometrySet &geometry_set,
+                             const Field &count_field,
+                             const Field &selection_field,
+                             IndexAttributes &attributes)
+{
+  if (!geometry_set.has_mesh() && !geometry_set.has_curve() && !geometry_set.has_pointcloud()) {
+    geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+    return;
+  }
+
+  Vector component_types = geometry_set.gather_component_types(true, true);
+  Vector types_to_keep;
+  for (const GeometryComponentType component_type : component_types) {
+    switch (component_type) {
+      case GEO_COMPONENT_TYPE_POINT_CLOUD:
+        types_to_keep.append(component_type);
+        duplicate_points_pointcloud(
+            component_type, count_field, selection_field, geometry_set, attributes);
+        break;
+      case GEO_COMPONENT_TYPE_MESH:
+        types_to_keep.append(component_type);
+        duplicate_points_mesh(
+            component_type, count_field, selection_field, geometry_set, attributes);
+        break;
+      case GEO_COMPONENT_TYPE_CURVE:
+        types_to_keep.append(component_type);
+        duplicate_points_curve(
+            component_type, count_field, selection_field, geometry_set, attributes);
+        break;
+      default:
+        break;
+    }
+  }
+  types_to_keep.append(GEO_COMPONENT_TYPE_INSTANCES);
+  geometry_set.keep_only(types_to_keep);
+}
+
+static void duplicate_instances(GeometrySet &geometry_set,
+                                const Field &count_field,
+                                const Field &selection_field,
+                                IndexAttributes &attributes)
+{
+  if (!geometry_set.has_instances()) {
+    geometry_set.clear();
+    return;
+  }
+
+  const InstancesComponent &src_instances =
+      *geometry_set.get_component_for_read();
+
+  const int domain_size = src_instances.attribute_domain_size(ATTR_DOMAIN_INSTANCE);
+  GeometryComponentFieldContext field_context{src_instances, ATTR_DOMAIN_INSTANCE};
+  FieldEvaluator evaluator{field_context, domain_size};
+  evaluator.add(count_field);
+  evaluator.set_selection(selection_field);
+  evaluator.evaluate();
+  IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+  const VArray counts = evaluator.get_evaluated(0);
+
+  Array offsets = accumulate_counts_to_offsets(selection, counts);
+
+  if (offsets.last() == 0) {
+    geometry_set.clear();
+    return;
+  }
+
+  GeometrySet instances_geometry;
+  InstancesComponent &dst_instances =
+      instances_geometry.get_component_for_write();
+  dst_instances.resize(offsets.last());
+  for (const int i_selection : selection.index_range()) {
+    const int count = offsets[i_selection + 1] - offsets[i_selection];
+    if (count == 0) {
+      continue;
+    }
+    const int old_handle = src_instances.instance_reference_handles()[i_selection];
+    const InstanceReference reference = src_instances.references()[old_handle];
+    const int new_handle = dst_instances.add_reference(reference);
+    const float4x4 transform = src_instances.instance_transforms()[i_selection];
+    dst_instances.instance_transforms().slice(offsets[i_selection], count).fill(transform);
+    dst_instances.instance_reference_handles().slice(offsets[i_selection], count).fill(new_handle);
+  }
+
+  copy_point_attributes_without_id(
+      geometry_set, GEO_COMPONENT_TYPE_INSTANCES, true, offsets, src_instances, dst_instances);
+
+  if (attributes.duplicate_index) {
+    create_duplicate_index_attribute(
+        dst_instances, ATTR_DOMAIN_INSTANCE, selection, attributes, offsets);
+  }
+
+  geometry_set.remove(GEO_COMPONENT_TYPE_INSTANCES);
+  geometry_set.add(dst_instances);
+}
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+  GeometrySet geometry_set = params.extract_input("Geometry");
+
+  const NodeGeometryDuplicateElements &storage = node_storage(params.node());
+  const AttributeDomain duplicate_domain = AttributeDomain(storage.domain);
+
+  Field count_field = params.extract_input>("Amount");
+  Field selection_field = params.extract_input>("Selection");
+  IndexAttributes attributes;
+  if (params.output_is_required("Duplicate Index")) {
+    attributes.duplicate_index = StrongAnonymousAttributeID("duplicate_index");
+  }
+
+  if (duplicate_domain == ATTR_DOMAIN_INSTANCE) {
+    geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+    duplicate_instances(geometry_set, count_field, selection_field, attributes);
+  }
+  else {
+    if (geometry_set.is_empty()) {
+      params.set_default_remaining_outputs();
+      return;
+    }
+    geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
+      switch (duplicate_domain) {
+        case ATTR_DOMAIN_CURVE:
+          duplicate_splines(geometry_set, count_field, selection_field, attributes);
+          break;
+        case ATTR_DOMAIN_FACE:
+          duplicate_faces(geometry_set, count_field, selection_field, attributes);
+          break;
+        case ATTR_DOMAIN_EDGE:
+          duplicate_edges(geometry_set, count_field, selection_field, attributes);
+          break;
+        case ATTR_DOMAIN_POINT:
+          duplicate_points(geometry_set, count_field, selection_field, attributes);
+          break;
+        default:
+          BLI_assert_unreachable();
+          break;
+      }
+    });
+  }
+
+  if (geometry_set.is_empty()) {
+    params.set_default_remaining_outputs();
+    return;
+  }
+
+  if (attributes.duplicate_index) {
+    params.set_output(
+        "Duplicate Index",
+        AnonymousAttributeFieldInput::Create(std::move(attributes.duplicate_index),
+                                                  params.attribute_producer_name()));
+  }
+  params.set_output("Geometry", geometry_set);
+}
+
+}  // namespace blender::nodes::node_geo_duplicate_elements_cc
+
+void register_node_type_geo_duplicate_elements()
+{
+  namespace file_ns = blender::nodes::node_geo_duplicate_elements_cc;
+  static bNodeType ntype;
+  geo_node_type_base(
+      &ntype, GEO_NODE_DUPLICATE_ELEMENTS, "Duplicate Elements", NODE_CLASS_GEOMETRY);
+
+  node_type_storage(&ntype,
+                    "NodeGeometryDuplicateElements",
+                    node_free_standard_storage,
+                    node_copy_standard_storage);
+
+  node_type_init(&ntype, file_ns::node_init);
+  ntype.draw_buttons = file_ns::node_layout;
+  ntype.geometry_node_execute = file_ns::node_geo_exec;
+  ntype.declare = file_ns::node_declare;
+  nodeRegisterType(&ntype);
+}
-- 
cgit v1.2.3


From 226f0c4fef7e7792c16458cd3e456b169ddce918 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Wed, 23 Feb 2022 16:56:27 +0100
Subject: Curves: initial brush implementations for curves sculpt mode

The main goal here is to add the boilerplate code to make it possible
to add the actual sculpt tools more easily. Both brush implementations
added by this patch are meant to be prototypes which will be removed
or refined in the coming weeks.

Ref T95773.

Differential Revision: https://developer.blender.org/D14180
---
 source/blender/blenkernel/BKE_brush.h              |   2 +-
 source/blender/blenlib/BLI_index_mask_ops.hh       |  60 ++++++
 source/blender/blenlib/BLI_math_geom.h             |   3 +
 source/blender/blenlib/CMakeLists.txt              |   1 +
 source/blender/blenlib/intern/index_mask.cc        |  68 ++++++
 source/blender/blenlib/intern/math_geom.c          |  12 ++
 source/blender/editors/sculpt_paint/CMakeLists.txt |  12 ++
 .../editors/sculpt_paint/curves_sculpt_ops.cc      | 240 +++++++++++++++++++--
 8 files changed, 382 insertions(+), 16 deletions(-)
 create mode 100644 source/blender/blenlib/BLI_index_mask_ops.hh

diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h
index c3a2aba18b5..4b84c0cfe23 100644
--- a/source/blender/blenkernel/BKE_brush.h
+++ b/source/blender/blenkernel/BKE_brush.h
@@ -8,13 +8,13 @@
  * General operations for brushes.
  */
 
+#include "DNA_color_types.h"
 #include "DNA_object_enums.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-enum eCurveMappingPreset;
 struct Brush;
 struct ImBuf;
 struct ImagePool;
diff --git a/source/blender/blenlib/BLI_index_mask_ops.hh b/source/blender/blenlib/BLI_index_mask_ops.hh
new file mode 100644
index 00000000000..48a1f27a2fa
--- /dev/null
+++ b/source/blender/blenlib/BLI_index_mask_ops.hh
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ *
+ * This is separate from `BLI_index_mask.hh` because it includes headers just `IndexMask` shouldn't
+ * depend on.
+ */
+
+#include "BLI_enumerable_thread_specific.hh"
+#include "BLI_index_mask.hh"
+#include "BLI_task.hh"
+#include "BLI_vector.hh"
+
+namespace blender::index_mask_ops {
+
+namespace detail {
+IndexMask find_indices_based_on_predicate__merge(
+    IndexMask indices_to_check,
+    threading::EnumerableThreadSpecific>> &sub_masks,
+    Vector &r_indices);
+}  // namespace detail
+
+/**
+ * Evaluate the #predicate for all indices in #indices_to_check and return a mask that contains all
+ * indices where the predicate was true.
+ *
+ * #r_indices indices is only used if necessary.
+ */
+template
+inline IndexMask find_indices_based_on_predicate(const IndexMask indices_to_check,
+                                                 const int64_t parallel_grain_size,
+                                                 Vector &r_indices,
+                                                 const Predicate &predicate)
+{
+  /* Evaluate predicate in parallel. Since the size of the final mask is not known yet, many
+   * smaller vectors have to be filled with all indices where the predicate is true. Those smaller
+   * vectors are joined afterwards. */
+  threading::EnumerableThreadSpecific>> sub_masks;
+  threading::parallel_for(
+      indices_to_check.index_range(), parallel_grain_size, [&](const IndexRange range) {
+        const IndexMask sub_mask = indices_to_check.slice(range);
+        Vector masked_indices;
+        for (const int64_t i : sub_mask) {
+          if (predicate(i)) {
+            masked_indices.append(i);
+          }
+        }
+        if (!masked_indices.is_empty()) {
+          sub_masks.local().append(std::move(masked_indices));
+        }
+      });
+
+  /* This part doesn't have to be in the header. */
+  return detail::find_indices_based_on_predicate__merge(indices_to_check, sub_masks, r_indices);
+}
+
+}  // namespace blender::index_mask_ops
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index 3d2ac5688ff..4bba84f2e29 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -303,6 +303,9 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4],
                                             const float bbmin[3],
                                             const float bbmax[3]);
 
+/** Returns the distance between two 2D line segments. */
+float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3]);
+
 float closest_to_ray_v3(float r_close[3],
                         const float p[3],
                         const float ray_orig[3],
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index ca22315b2ed..6e3e84f6495 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -203,6 +203,7 @@ set(SRC
   BLI_heap.h
   BLI_heap_simple.h
   BLI_index_mask.hh
+  BLI_index_mask_ops.hh
   BLI_index_range.hh
   BLI_inplace_priority_queue.hh
   BLI_iterator.h
diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc
index 8c03df2d4c3..1e301bc5fb9 100644
--- a/source/blender/blenlib/intern/index_mask.cc
+++ b/source/blender/blenlib/intern/index_mask.cc
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "BLI_index_mask.hh"
+#include "BLI_index_mask_ops.hh"
 
 namespace blender {
 
@@ -126,3 +127,70 @@ Vector IndexMask::extract_ranges_invert(const IndexRange full_range,
 }
 
 }  // namespace blender
+
+namespace blender::index_mask_ops::detail {
+
+IndexMask find_indices_based_on_predicate__merge(
+    IndexMask indices_to_check,
+    threading::EnumerableThreadSpecific>> &sub_masks,
+    Vector &r_indices)
+{
+  /* Gather vectors that have been generated by possibly multiple threads. */
+  Vector *> all_vectors;
+  int64_t result_mask_size = 0;
+  for (Vector> &local_sub_masks : sub_masks) {
+    for (Vector &sub_mask : local_sub_masks) {
+      all_vectors.append(&sub_mask);
+      result_mask_size += sub_mask.size();
+    }
+  }
+
+  if (all_vectors.is_empty()) {
+    /* Special case when the predicate was false for all elements. */
+    return {};
+  }
+  if (result_mask_size == indices_to_check.size()) {
+    /* Special case when the predicate was true for all elements. */
+    return indices_to_check;
+  }
+  if (all_vectors.size() == 1) {
+    /* Special case when all indices for which the predicate is true happen to be in a single
+     * vector. */
+    r_indices = std::move(*all_vectors[0]);
+    return r_indices.as_span();
+  }
+
+  /* Indices in separate vectors don't overlap. So it is ok to sort the vectors just by looking at
+   * the first element. */
+  std::sort(all_vectors.begin(),
+            all_vectors.end(),
+            [](const Vector *a, const Vector *b) { return (*a)[0] < (*b)[0]; });
+
+  /* Precompute the offsets for the individual vectors, so that the indices can be copied into the
+   * final vector in parallel. */
+  Vector offsets;
+  offsets.reserve(all_vectors.size() + 1);
+  offsets.append(0);
+  for (Vector *vector : all_vectors) {
+    offsets.append(offsets.last() + vector->size());
+  }
+
+  r_indices.resize(result_mask_size);
+
+  /* Fill the final index mask in parallel again. */
+  threading::parallel_for(all_vectors.index_range(), 100, [&](const IndexRange all_vectors_range) {
+    for (const int64_t vector_index : all_vectors_range) {
+      Vector &vector = *all_vectors[vector_index];
+      const int64_t offset = offsets[vector_index];
+      threading::parallel_for(vector.index_range(), 1024, [&](const IndexRange range) {
+        initialized_copy_n(vector.data() + range.start(),
+                           range.size(),
+                           r_indices.data() + offset + range.start());
+      });
+    }
+  });
+
+  return r_indices.as_span();
+}
+
+}  // namespace blender::index_mask_ops::detail
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index f96c80185b1..bc3ed099fd5 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -903,6 +903,18 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4],
 
 /** \} */
 
+float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3])
+{
+  if (isect_seg_seg_v2_simple(a1, a2, b1, b2)) {
+    return 0.0f;
+  }
+  const float d1 = dist_squared_to_line_segment_v2(a1, b1, b2);
+  const float d2 = dist_squared_to_line_segment_v2(a2, b1, b2);
+  const float d3 = dist_squared_to_line_segment_v2(b1, a1, a2);
+  const float d4 = dist_squared_to_line_segment_v2(b2, a1, a2);
+  return sqrtf(min_ffff(d1, d2, d3, d4));
+}
+
 void closest_on_tri_to_point_v3(
     float r[3], const float p[3], const float v1[3], const float v2[3], const float v3[3])
 {
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index de7888aa1e8..59fbc3a64fb 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -9,6 +9,7 @@ set(INC
   ../../bmesh
   ../../depsgraph
   ../../draw
+  ../../functions
   ../../gpu
   ../../imbuf
   ../../makesdna
@@ -78,5 +79,16 @@ set(LIB
   bf_blenlib
 )
 
+if(WITH_TBB)
+  list(APPEND INC_SYS
+    ${TBB_INCLUDE_DIRS}
+  )
+  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()
+endif()
 
 blender_add_lib(bf_editor_sculpt_paint "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 04a14712d03..936226a03ed 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -2,7 +2,9 @@
 
 #include "BLI_utildefines.h"
 
+#include "BKE_brush.h"
 #include "BKE_context.h"
+#include "BKE_curves.hh"
 #include "BKE_paint.h"
 
 #include "WM_api.h"
@@ -10,6 +12,19 @@
 
 #include "ED_curves_sculpt.h"
 #include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_curves_types.h"
+#include "DNA_screen_types.h"
+
+#include "RNA_access.h"
+
+#include "BLI_index_mask_ops.hh"
+#include "BLI_math_vector.hh"
 
 #include "curves_sculpt_intern.h"
 #include "paint_intern.h"
@@ -35,14 +50,178 @@ bool CURVES_SCULPT_mode_poll_view3d(bContext *C)
   return true;
 }
 
+/** \} */
+
 namespace blender::ed::sculpt_paint {
 
-/** \} */
+using blender::bke::CurvesGeometry;
 
 /* -------------------------------------------------------------------- */
 /** \name * SCULPT_CURVES_OT_brush_stroke
  * \{ */
 
+struct StrokeExtension {
+  bool is_first;
+  float2 mouse_position;
+};
+
+/**
+ * Base class for stroke based operations in curves sculpt mode.
+ */
+class CurvesSculptStrokeOperation {
+ public:
+  virtual ~CurvesSculptStrokeOperation() = default;
+  virtual void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) = 0;
+};
+
+class DeleteOperation : public CurvesSculptStrokeOperation {
+ private:
+  float2 last_mouse_position_;
+
+ public:
+  void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension)
+  {
+    Scene &scene = *CTX_data_scene(C);
+    Object &object = *CTX_data_active_object(C);
+    ARegion *region = CTX_wm_region(C);
+    RegionView3D *rv3d = CTX_wm_region_view3d(C);
+
+    CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+    Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+    const float brush_radius = BKE_brush_size_get(&scene, &brush);
+
+    float4x4 projection;
+    ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
+
+    Curves &curves_id = *static_cast(object.data);
+    CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
+    MutableSpan positions = curves.positions();
+
+    const float2 mouse_start = stroke_extension.is_first ? stroke_extension.mouse_position :
+                                                           last_mouse_position_;
+    const float2 mouse_end = stroke_extension.mouse_position;
+
+    /* Find indices of curves that have to be removed. */
+    Vector indices;
+    const IndexMask curves_to_remove = index_mask_ops::find_indices_based_on_predicate(
+        curves.curves_range(), 512, indices, [&](const int curve_i) {
+          const IndexRange point_range = curves.range_for_curve(curve_i);
+          for (const int segment_i : IndexRange(point_range.size() - 1)) {
+            const float3 pos1 = positions[point_range[segment_i]];
+            const float3 pos2 = positions[point_range[segment_i + 1]];
+
+            float2 pos1_proj, pos2_proj;
+            ED_view3d_project_float_v2_m4(region, pos1, pos1_proj, projection.values);
+            ED_view3d_project_float_v2_m4(region, pos2, pos2_proj, projection.values);
+
+            const float dist = dist_seg_seg_v2(pos1_proj, pos2_proj, mouse_start, mouse_end);
+            if (dist <= brush_radius) {
+              return true;
+            }
+          }
+          return false;
+        });
+
+    /* Just reset positions instead of actually removing the curves. This is just a prototype. */
+    threading::parallel_for(curves_to_remove.index_range(), 512, [&](const IndexRange range) {
+      for (const int curve_i : curves_to_remove.slice(range)) {
+        for (const int point_i : curves.range_for_curve(curve_i)) {
+          positions[point_i] = {0.0f, 0.0f, 0.0f};
+        }
+      }
+    });
+
+    curves.tag_positions_changed();
+    DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+    ED_region_tag_redraw(region);
+
+    last_mouse_position_ = stroke_extension.mouse_position;
+  }
+};
+
+class MoveOperation : public CurvesSculptStrokeOperation {
+ private:
+  Vector points_to_move_indices_;
+  IndexMask points_to_move_;
+  float2 last_mouse_position_;
+
+ public:
+  void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension)
+  {
+    Scene &scene = *CTX_data_scene(C);
+    Object &object = *CTX_data_active_object(C);
+    ARegion *region = CTX_wm_region(C);
+    View3D *v3d = CTX_wm_view3d(C);
+    RegionView3D *rv3d = CTX_wm_region_view3d(C);
+
+    CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+    Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+    const float brush_radius = BKE_brush_size_get(&scene, &brush);
+
+    float4x4 projection;
+    ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
+
+    Curves &curves_id = *static_cast(object.data);
+    CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
+    MutableSpan positions = curves.positions();
+
+    if (stroke_extension.is_first) {
+      /* Find point indices to move. */
+      points_to_move_ = index_mask_ops::find_indices_based_on_predicate(
+          curves.points_range(), 512, points_to_move_indices_, [&](const int64_t point_i) {
+            const float3 position = positions[point_i];
+            float2 screen_position;
+            ED_view3d_project_float_v2_m4(region, position, screen_position, projection.values);
+            const float distance = len_v2v2(screen_position, stroke_extension.mouse_position);
+            return distance <= brush_radius;
+          });
+    }
+    else {
+      /* Move points based on mouse movement. */
+      const float2 mouse_diff = stroke_extension.mouse_position - last_mouse_position_;
+      threading::parallel_for(points_to_move_.index_range(), 512, [&](const IndexRange range) {
+        for (const int point_i : points_to_move_.slice(range)) {
+          const float3 old_position = positions[point_i];
+          float2 old_position_screen;
+          ED_view3d_project_float_v2_m4(
+              region, old_position, old_position_screen, projection.values);
+          const float2 new_position_screen = old_position_screen + mouse_diff;
+          float3 new_position;
+          ED_view3d_win_to_3d(v3d, region, old_position, new_position_screen, new_position);
+          positions[point_i] = new_position;
+        }
+      });
+
+      curves.tag_positions_changed();
+      DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+      ED_region_tag_redraw(region);
+    }
+
+    last_mouse_position_ = stroke_extension.mouse_position;
+  }
+};
+
+static std::unique_ptr start_brush_operation(bContext *C,
+                                                                          wmOperator *UNUSED(op))
+{
+  Scene &scene = *CTX_data_scene(C);
+  CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+  Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+  switch (brush.curves_sculpt_tool) {
+    case CURVES_SCULPT_TOOL_TEST1:
+      return std::make_unique();
+    case CURVES_SCULPT_TOOL_TEST2:
+      return std::make_unique();
+  }
+  BLI_assert_unreachable();
+  return {};
+}
+
+struct SculptCurvesBrushStrokeData {
+  std::unique_ptr operation;
+  PaintStroke *stroke;
+};
+
 static bool stroke_get_location(bContext *C, float out[3], const float mouse[2])
 {
   out[0] = mouse[0];
@@ -60,10 +239,24 @@ static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mo
 
 static void stroke_update_step(bContext *C,
                                wmOperator *op,
-                               PaintStroke *stroke,
-                               PointerRNA *itemptr)
+                               PaintStroke *UNUSED(stroke),
+                               PointerRNA *stroke_element)
 {
-  UNUSED_VARS(C, op, stroke, itemptr);
+  SculptCurvesBrushStrokeData *op_data = static_cast(
+      op->customdata);
+
+  StrokeExtension stroke_extension;
+  RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position);
+
+  if (!op_data->operation) {
+    stroke_extension.is_first = true;
+    op_data->operation = start_brush_operation(C, op);
+  }
+  else {
+    stroke_extension.is_first = false;
+  }
+
+  op_data->operation->on_stroke_extended(C, stroke_extension);
 }
 
 static void stroke_done(const bContext *C, PaintStroke *stroke)
@@ -73,15 +266,23 @@ static void stroke_done(const bContext *C, PaintStroke *stroke)
 
 static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
-  PaintStroke *stroke = paint_stroke_new(C,
-                                         op,
-                                         stroke_get_location,
-                                         stroke_test_start,
-                                         stroke_update_step,
-                                         nullptr,
-                                         stroke_done,
-                                         event->type);
-  op->customdata = stroke;
+  SculptCurvesBrushStrokeData *op_data = MEM_new(__func__);
+  op_data->stroke = paint_stroke_new(C,
+                                     op,
+                                     stroke_get_location,
+                                     stroke_test_start,
+                                     stroke_update_step,
+                                     nullptr,
+                                     stroke_done,
+                                     event->type);
+  op->customdata = op_data;
+
+  int return_value = op->type->modal(C, op, event);
+  if (return_value == OPERATOR_FINISHED) {
+    paint_stroke_free(C, op, op_data->stroke);
+    MEM_delete(op_data);
+    return OPERATOR_FINISHED;
+  }
 
   WM_event_add_modal_handler(C, op);
   return OPERATOR_RUNNING_MODAL;
@@ -89,12 +290,21 @@ static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEven
 
 static int sculpt_curves_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
 {
-  return paint_stroke_modal(C, op, event, static_cast(op->customdata));
+  SculptCurvesBrushStrokeData *op_data = static_cast(
+      op->customdata);
+  int return_value = paint_stroke_modal(C, op, event, op_data->stroke);
+  if (ELEM(return_value, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
+    MEM_delete(op_data);
+  }
+  return return_value;
 }
 
 static void sculpt_curves_stroke_cancel(bContext *C, wmOperator *op)
 {
-  paint_stroke_cancel(C, op, static_cast(op->customdata));
+  SculptCurvesBrushStrokeData *op_data = static_cast(
+      op->customdata);
+  paint_stroke_cancel(C, op, op_data->stroke);
+  MEM_delete(op_data);
 }
 
 static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot)
-- 
cgit v1.2.3


From 7518adc5bbe25ab8fbac9d42ccf865e6538e044a Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Wed, 23 Feb 2022 13:02:06 -0500
Subject: Fix: Attempt to fix build error on windows

`index_mask.cc` ends up including this header, but not much else,
the `` include is necessary on Windows for `std::max`.
---
 source/blender/blenlib/BLI_hash_tables.hh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/blenlib/BLI_hash_tables.hh b/source/blender/blenlib/BLI_hash_tables.hh
index 40b20dbd84f..334634613a2 100644
--- a/source/blender/blenlib/BLI_hash_tables.hh
+++ b/source/blender/blenlib/BLI_hash_tables.hh
@@ -8,6 +8,7 @@
  * This file contains code that can be shared between different hash table implementations.
  */
 
+#include 
 #include 
 
 #include "BLI_allocator.hh"
-- 
cgit v1.2.3


From dbef66c32f728546c6b0a1759a38e7edd9fa4dfc Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Wed, 23 Feb 2022 13:57:04 -0500
Subject: Fix T95952: Uninitialized value used in Bezier Segment node

0fd72a98ac1377a385b6 called functions to set bezier handle positions
that used uninitialized memory. The fix is to define the handle positions
explicitly, like before.
---
 .../nodes/node_geo_curve_primitive_bezier_segment.cc    | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
index 7d84ddf9917..6e1c8a80a00 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
@@ -91,13 +91,22 @@ static std::unique_ptr create_bezier_segment_curve(
   positions.first() = start;
   positions.last() = end;
 
+  MutableSpan handles_right = spline->handle_positions_right();
+  MutableSpan handles_left = spline->handle_positions_left();
+
   if (mode == GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION) {
-    spline->set_handle_position_right(0, start_handle_right);
-    spline->set_handle_position_left(1, end_handle_left);
+    handles_left.first() = 2.0f * start - start_handle_right;
+    handles_right.first() = start_handle_right;
+
+    handles_left.last() = end_handle_left;
+    handles_right.last() = 2.0f * end - end_handle_left;
   }
   else {
-    spline->set_handle_position_right(0, start + start_handle_right);
-    spline->set_handle_position_left(1, end + end_handle_left);
+    handles_left.first() = start - start_handle_right;
+    handles_right.first() = start + start_handle_right;
+
+    handles_left.last() = end + end_handle_left;
+    handles_right.last() = end - end_handle_left;
   }
 
   curve->add_spline(std::move(spline));
-- 
cgit v1.2.3


From 99a6392fb5aed1831616bfd1ab4a00c345b32d51 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Wed, 23 Feb 2022 18:38:51 +0100
Subject: Fix OSL trace() not being fully updated for ray offsetting removal

This was the last place still using the ray_offset() function.
---
 intern/cycles/kernel/osl/services.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp
index d79e7dfa8a5..67e8a40d4c3 100644
--- a/intern/cycles/kernel/osl/services.cpp
+++ b/intern/cycles/kernel/osl/services.cpp
@@ -1651,12 +1651,16 @@ bool OSLRenderServices::trace(TraceOpt &options,
   ray.D = TO_FLOAT3(R);
   ray.t = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist - options.mindist;
   ray.time = sd->time;
+  ray.self.object = OBJECT_NONE;
+  ray.self.prim = PRIM_NONE;
+  ray.self.light_object = OBJECT_NONE;
+  ray.self.light_prim = PRIM_NONE;
 
   if (options.mindist == 0.0f) {
     /* avoid self-intersections */
     if (ray.P == sd->P) {
-      bool transmit = (dot(sd->Ng, ray.D) < 0.0f);
-      ray.P = ray_offset(sd->P, (transmit) ? -sd->Ng : sd->Ng);
+      ray.self.object = sd->object;
+      ray.self.prim = sd->prim;
     }
   }
   else {
-- 
cgit v1.2.3


From b93d4faba8d4dec42354b45e0d26c38ab601adde Mon Sep 17 00:00:00 2001
From: Johnny Matthews 
Date: Wed, 23 Feb 2022 13:04:12 -0600
Subject: Fix for last commit:  Define for Node numbering

---
 source/blender/blenkernel/BKE_node.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 024e98daea5..90f933d7cbb 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1514,8 +1514,7 @@ struct TexResult;
 #define GEO_NODE_SCALE_ELEMENTS 1151
 #define GEO_NODE_EXTRUDE_MESH 1152
 #define GEO_NODE_MERGE_BY_DISTANCE 1153
-
-#define GEO_NODE_DUPLICATE_ELEMENTS 1160
+#define GEO_NODE_DUPLICATE_ELEMENTS 1154
 
 /** \} */
 
-- 
cgit v1.2.3


From 80be63e2a59d5fe009182cec2d167f3636a76470 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Wed, 23 Feb 2022 14:10:40 -0500
Subject: Update RNA to user manual mapping file

---
 release/scripts/modules/rna_manual_reference.py | 64 ++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 11 deletions(-)

diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py
index 0467e7b5788..c5f64212083 100644
--- a/release/scripts/modules/rna_manual_reference.py
+++ b/release/scripts/modules/rna_manual_reference.py
@@ -1,3 +1,4 @@
+SPDX-License-Identifier: GPL-2.0-or-later
 # Do not edit this file. This file is auto generated from rna_manual_reference_updater.py
 
 import bpy
@@ -49,7 +50,6 @@ url_manual_mapping = (
 	("bpy.types.lineartgpencilmodifier.use_offset_towards_custom_camera*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-offset-towards-custom-camera"),
 	("bpy.types.movietrackingsettings.refine_intrinsics_principal_point*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-principal-point"),
 	("bpy.types.cyclesobjectsettings.shadow_terminator_geometry_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-geometry-offset"),
-	("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"),
@@ -113,6 +113,7 @@ url_manual_mapping = (
 	("bpy.types.gpencilsculptsettings.intersection_threshold*", "grease_pencil/modes/draw/tools/cutter.html#bpy-types-gpencilsculptsettings-intersection-threshold"),
 	("bpy.types.gpencilsculptsettings.use_multiframe_falloff*", "grease_pencil/multiframe.html#bpy-types-gpencilsculptsettings-use-multiframe-falloff"),
 	("bpy.types.lineartgpencilmodifier.use_intersection_mask*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-intersection-mask"),
+	("bpy.types.lineartgpencilmodifier.use_invert_collection*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-invert-collection"),
 	("bpy.types.movietrackingsettings.use_keyframe_selection*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-keyframe-selection"),
 	("bpy.types.rendersettings.simplify_gpencil_antialiasing*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-antialiasing"),
 	("bpy.types.sequencertimelineoverlay.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-duration"),
@@ -122,7 +123,6 @@ url_manual_mapping = (
 	("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"),
 	("bpy.types.brushgpencilsettings.eraser_strength_factor*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-strength-factor"),
 	("bpy.types.cyclesmaterialsettings.volume_interpolation*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-interpolation"),
-	("bpy.types.cyclesrendersettings.debug_optix_curves_api*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-optix-curves-api"),
 	("bpy.types.cyclesrendersettings.denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-input-passes"),
 	("bpy.types.cyclesrendersettings.film_transparent_glass*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-glass"),
 	("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"),
@@ -357,6 +357,7 @@ url_manual_mapping = (
 	("bpy.types.toolsettings.use_keyframe_insert_auto*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-insert-auto"),
 	("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.ops.ed.lib_id_generate_preview_from_object*", "editors/asset_browser.html#bpy-ops-ed-lib-id-generate-preview-from-object"),
 	("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"),
 	("bpy.types.brushgpencilsettings.random_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-strength"),
 	("bpy.types.brushgpencilsettings.simplify_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-simplify-factor"),
@@ -378,6 +379,7 @@ url_manual_mapping = (
 	("bpy.types.freestylelinestyle.use_split_pattern*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-split-pattern"),
 	("bpy.types.freestylesettings.use_view_map_cache*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-view-map-cache"),
 	("bpy.types.geometrynodecurvehandletypeselection*", "modeling/geometry_nodes/curve/handle_type_selection.html#bpy-types-geometrynodecurvehandletypeselection"),
+	("bpy.types.geometrynodeinputmeshvertexneighbors*", "modeling/geometry_nodes/mesh/vertex_neighbors.html#bpy-types-geometrynodeinputmeshvertexneighbors"),
 	("bpy.types.greasepencil.curve_edit_corner_angle*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-corner-angle"),
 	("bpy.types.lineartgpencilmodifier.source_camera*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-source-camera"),
 	("bpy.types.lineartgpencilmodifier.use_face_mark*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark"),
@@ -404,7 +406,6 @@ url_manual_mapping = (
 	("bpy.types.brushgpencilsettings.use_fill_limit*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-use-fill-limit"),
 	("bpy.types.clothsettings.vertex_group_pressure*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-pressure"),
 	("bpy.types.cyclesmaterialsettings.displacement*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-displacement"),
-	("bpy.types.cyclesrendersettings.debug_bvh_type*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-bvh-type"),
 	("bpy.types.cyclesrendersettings.fast_gi_method*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-fast-gi-method"),
 	("bpy.types.cyclesrendersettings.glossy_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-glossy-bounces"),
 	("bpy.types.cyclesrendersettings.volume_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-volume-bounces"),
@@ -457,6 +458,7 @@ url_manual_mapping = (
 	("bpy.types.cyclescamerasettings.panorama_type*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-panorama-type"),
 	("bpy.types.cyclesrendersettings.dicing_camera*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-camera"),
 	("bpy.types.cyclesrendersettings.film_exposure*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-exposure"),
+	("bpy.types.cyclesrendersettings.sample_offset*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-offset"),
 	("bpy.types.cyclesrendersettings.texture_limit*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-texture-limit"),
 	("bpy.types.cyclesrendersettings.use_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-denoising"),
 	("bpy.types.editbone.bbone_custom_handle_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-start"),
@@ -482,6 +484,8 @@ url_manual_mapping = (
 	("bpy.types.freestylelinestyle.use_dashed_line*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-dashed-line"),
 	("bpy.types.freestylelinestyle.use_same_object*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-same-object"),
 	("bpy.types.functionnodeinputspecialcharacters*", "modeling/geometry_nodes/text/special_characters.html#bpy-types-functionnodeinputspecialcharacters"),
+	("bpy.types.geometrynodeinputmeshedgeneighbors*", "modeling/geometry_nodes/mesh/edge_neighbors.html#bpy-types-geometrynodeinputmeshedgeneighbors"),
+	("bpy.types.geometrynodeinputmeshfaceneighbors*", "modeling/geometry_nodes/mesh/face_neighbors.html#bpy-types-geometrynodeinputmeshfaceneighbors"),
 	("bpy.types.gpencilsculptguide.reference_point*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-reference-point"),
 	("bpy.types.greasepencil.edit_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-edit-curve-resolution"),
 	("bpy.types.linestylegeometrymodifier_2doffset*", "render/freestyle/view_layer/line_style/modifiers/geometry/2d_offset.html#bpy-types-linestylegeometrymodifier-2doffset"),
@@ -494,6 +498,7 @@ url_manual_mapping = (
 	("bpy.types.sequencertimelineoverlay.show_grid*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-grid"),
 	("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.spacenodeoverlay.show_context_path*", "interface/controls/nodes/introduction.html#bpy-types-spacenodeoverlay-show-context-path"),
 	("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"),
 	("bpy.types.spaceuveditor.display_stretch_type*", "editors/uv/overlays.html#bpy-types-spaceuveditor-display-stretch-type"),
@@ -521,6 +526,7 @@ url_manual_mapping = (
 	("bpy.types.freestylelineset.select_edge_mark*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-edge-mark"),
 	("bpy.types.freestylelinestyle.use_length_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-max"),
 	("bpy.types.freestylelinestyle.use_length_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-min"),
+	("bpy.types.geometrynodeinputmeshedgevertices*", "modeling/geometry_nodes/mesh/edge_vertices.html#bpy-types-geometrynodeinputmeshedgevertices"),
 	("bpy.types.geometrynodeinputsplineresolution*", "modeling/geometry_nodes/curve/spline_resolution.html#bpy-types-geometrynodeinputsplineresolution"),
 	("bpy.types.greasepencil.curve_edit_threshold*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-threshold"),
 	("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"),
@@ -616,6 +622,7 @@ url_manual_mapping = (
 	("bpy.types.brush.surface_smooth_iterations*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-iterations"),
 	("bpy.types.brushgpencilsettings.pen_jitter*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-jitter"),
 	("bpy.types.brushgpencilsettings.show_lasso*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-show-lasso"),
+	("bpy.types.compositornodeconvertcolorspace*", "compositing/types/converter/color_space.html#bpy-types-compositornodeconvertcolorspace"),
 	("bpy.types.cyclescurverendersettings.shape*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-shape"),
 	("bpy.types.cyclesrendersettings.ao_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces"),
 	("bpy.types.cyclesrendersettings.time_limit*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-time-limit"),
@@ -713,7 +720,9 @@ url_manual_mapping = (
 	("bpy.types.geometrynodealigneulertovector*", "modeling/geometry_nodes/utilities/align_euler_to_vector.html#bpy-types-geometrynodealigneulertovector"),
 	("bpy.types.geometrynodeattributestatistic*", "modeling/geometry_nodes/attribute/attribute_statistic.html#bpy-types-geometrynodeattributestatistic"),
 	("bpy.types.geometrynodecurvequadrilateral*", "modeling/geometry_nodes/curve_primitives/quadrilateral.html#bpy-types-geometrynodecurvequadrilateral"),
+	("bpy.types.geometrynodegeometrytoinstance*", "modeling/geometry_nodes/geometry/geometry_to_instance.html#bpy-types-geometrynodegeometrytoinstance"),
 	("bpy.types.geometrynodeinputmaterialindex*", "modeling/geometry_nodes/material/material_index.html#bpy-types-geometrynodeinputmaterialindex"),
+	("bpy.types.geometrynodeinputmeshedgeangle*", "modeling/geometry_nodes/mesh/edge_angle.html#bpy-types-geometrynodeinputmeshedgeangle"),
 	("bpy.types.geometrynodeseparatecomponents*", "modeling/geometry_nodes/geometry/separate_components.html#bpy-types-geometrynodeseparatecomponents"),
 	("bpy.types.geometrynodesubdivisionsurface*", "modeling/geometry_nodes/mesh/subdivision_surface.html#bpy-types-geometrynodesubdivisionsurface"),
 	("bpy.types.geometrynodetranslateinstances*", "modeling/geometry_nodes/instances/translate_instances.html#bpy-types-geometrynodetranslateinstances"),
@@ -793,6 +802,7 @@ url_manual_mapping = (
 	("bpy.types.freestylesettings.use_culling*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-culling"),
 	("bpy.types.geometrynodeendpointselection*", "modeling/geometry_nodes/curve/endpoint_selection.html#bpy-types-geometrynodeendpointselection"),
 	("bpy.types.geometrynodegeometryproximity*", "modeling/geometry_nodes/geometry/geometry_proximity.html#bpy-types-geometrynodegeometryproximity"),
+	("bpy.types.geometrynodeinputmeshfacearea*", "modeling/geometry_nodes/mesh/face_area.html#bpy-types-geometrynodeinputmeshfacearea"),
 	("bpy.types.geometrynodeinputsplinecyclic*", "modeling/geometry_nodes/curve/is_spline_cyclic.html#bpy-types-geometrynodeinputsplinecyclic"),
 	("bpy.types.geometrynodeinstancestopoints*", "modeling/geometry_nodes/instances/instances_to_points.html#bpy-types-geometrynodeinstancestopoints"),
 	("bpy.types.geometrynodetransferattribute*", "modeling/geometry_nodes/attribute/transfer_attribute.html#bpy-types-geometrynodetransferattribute"),
@@ -822,6 +832,7 @@ url_manual_mapping = (
 	("bpy.types.toolsettings.mesh_select_mode*", "modeling/meshes/selecting/introduction.html#bpy-types-toolsettings-mesh-select-mode"),
 	("bpy.types.toolsettings.use_snap_project*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-project"),
 	("bpy.types.transformorientationslot.type*", "editors/3dview/controls/orientation.html#bpy-types-transformorientationslot-type"),
+	("bpy.types.unitsettings.temperature_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-temperature-unit"),
 	("bpy.types.vertexweightproximitymodifier*", "modeling/modifiers/modify/weight_proximity.html#bpy-types-vertexweightproximitymodifier"),
 	("bpy.types.view3doverlay.show_wireframes*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-wireframes"),
 	("bpy.types.view3dshading.background_type*", "editors/3dview/display/shading.html#bpy-types-view3dshading-background-type"),
@@ -879,6 +890,7 @@ url_manual_mapping = (
 	("bpy.types.spacetexteditor.use_find_all*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-all"),
 	("bpy.types.toolsettings.snap_uv_element*", "editors/uv/controls/snapping.html#bpy-types-toolsettings-snap-uv-element"),
 	("bpy.types.toolsettings.use_snap_rotate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-rotate"),
+	("bpy.types.unitsettings.system_rotation*", "scene_layout/scene/properties.html#bpy-types-unitsettings-system-rotation"),
 	("bpy.types.view3doverlay.display_handle*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-display-handle"),
 	("bpy.types.volumedisplay.wireframe_type*", "modeling/volumes/properties.html#bpy-types-volumedisplay-wireframe-type"),
 	("bpy.ops.anim.channels_editable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-editable-toggle"),
@@ -923,11 +935,15 @@ url_manual_mapping = (
 	("bpy.types.freestylelineset.visibility*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-visibility"),
 	("bpy.types.freestylelinestyle.chaining*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-chaining"),
 	("bpy.types.freestylelinestyle.sort_key*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-sort-key"),
+	("bpy.types.geometrynodeaccumulatefield*", "modeling/geometry_nodes/utilities/accumulate_field.html#bpy-types-geometrynodeaccumulatefield"),
 	("bpy.types.geometrynodecurvesethandles*", "modeling/geometry_nodes/curve/set_handle_type.html#bpy-types-geometrynodecurvesethandles"),
 	("bpy.types.geometrynodecurvesplinetype*", "modeling/geometry_nodes/curve/set_spline_type.html#bpy-types-geometrynodecurvesplinetype"),
+	("bpy.types.geometrynodeinputmeshisland*", "modeling/geometry_nodes/mesh/mesh_island.html#bpy-types-geometrynodeinputmeshisland"),
+	("bpy.types.geometrynodemergebydistance*", "modeling/geometry_nodes/geometry/merge_by_distance.html#bpy-types-geometrynodemergebydistance"),
 	("bpy.types.geometrynodereplacematerial*", "modeling/geometry_nodes/material/replace_material.html#bpy-types-geometrynodereplacematerial"),
 	("bpy.types.geometrynoderotateinstances*", "modeling/geometry_nodes/instances/rotate_instances.html#bpy-types-geometrynoderotateinstances"),
 	("bpy.types.geometrynodesetsplinecyclic*", "modeling/geometry_nodes/curve/set_spline_cyclic.html#bpy-types-geometrynodesetsplinecyclic"),
+	("bpy.types.geometrynodesplineparameter*", "modeling/geometry_nodes/curve/spline_parameter.html#bpy-types-geometrynodesplineparameter"),
 	("bpy.types.gpencillayer.use_mask_layer*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-mask-layer"),
 	("bpy.types.greasepencil.use_curve_edit*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-curve-edit"),
 	("bpy.types.imagepaint.screen_grab_size*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-screen-grab-size"),
@@ -997,9 +1013,9 @@ url_manual_mapping = (
 	("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"),
 	("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"),
 	("bpy.types.geometrynodecollectioninfo*", "modeling/geometry_nodes/input/collection_info.html#bpy-types-geometrynodecollectioninfo"),
-	("bpy.types.geometrynodecurveparameter*", "modeling/geometry_nodes/curve/curve_parameter.html#bpy-types-geometrynodecurveparameter"),
 	("bpy.types.geometrynodedeletegeometry*", "modeling/geometry_nodes/geometry/delete_geometry.html#bpy-types-geometrynodedeletegeometry"),
 	("bpy.types.geometrynodeinputcurvetilt*", "modeling/geometry_nodes/curve/curve_tilt.html#bpy-types-geometrynodeinputcurvetilt"),
+	("bpy.types.geometrynodeinputscenetime*", "modeling/geometry_nodes/input/scene_time.html#bpy-types-geometrynodeinputscenetime"),
 	("bpy.types.geometrynodepointstovolume*", "modeling/geometry_nodes/point/points_to_volume.html#bpy-types-geometrynodepointstovolume"),
 	("bpy.types.geometrynodescaleinstances*", "modeling/geometry_nodes/instances/scale_instances.html#bpy-types-geometrynodescaleinstances"),
 	("bpy.types.geometrynodesetcurveradius*", "modeling/geometry_nodes/curve/set_curve_radius.html#bpy-types-geometrynodesetcurveradius"),
@@ -1079,7 +1095,6 @@ url_manual_mapping = (
 	("bpy.types.fluidflowsettings.density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density"),
 	("bpy.types.freestylelineset.qi_start*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-start"),
 	("bpy.types.freestylelinestyle.rounds*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-rounds"),
-	("bpy.types.functionnodecomparefloats*", "modeling/geometry_nodes/utilities/compare_floats.html#bpy-types-functionnodecomparefloats"),
 	("bpy.types.geometrynodecurvetopoints*", "modeling/geometry_nodes/curve/curve_to_points.html#bpy-types-geometrynodecurvetopoints"),
 	("bpy.types.geometrynodeinputmaterial*", "modeling/geometry_nodes/input/material.html#bpy-types-geometrynodeinputmaterial"),
 	("bpy.types.geometrynodeinputposition*", "modeling/geometry_nodes/input/position.html#bpy-types-geometrynodeinputposition"),
@@ -1087,6 +1102,7 @@ url_manual_mapping = (
 	("bpy.types.geometrynodemeshicosphere*", "modeling/geometry_nodes/mesh_primitives/icosphere.html#bpy-types-geometrynodemeshicosphere"),
 	("bpy.types.geometrynodereplacestring*", "modeling/geometry_nodes/text/replace_string.html#bpy-types-geometrynodereplacestring"),
 	("bpy.types.geometrynoderesamplecurve*", "modeling/geometry_nodes/curve/resample_curve.html#bpy-types-geometrynoderesamplecurve"),
+	("bpy.types.geometrynodescaleelements*", "modeling/geometry_nodes/mesh/scale_elements.html#bpy-types-geometrynodescaleelements"),
 	("bpy.types.geometrynodesubdividemesh*", "modeling/geometry_nodes/mesh/subdivide_mesh.html#bpy-types-geometrynodesubdividemesh"),
 	("bpy.types.geometrynodevaluetostring*", "modeling/geometry_nodes/text/value_to_string.html#bpy-types-geometrynodevaluetostring"),
 	("bpy.types.keyframe.handle_left_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left-type"),
@@ -1110,6 +1126,7 @@ url_manual_mapping = (
 	("bpy.types.shadernodebsdftranslucent*", "render/shader_nodes/shader/translucent.html#bpy-types-shadernodebsdftranslucent"),
 	("bpy.types.shadernodebsdftransparent*", "render/shader_nodes/shader/transparent.html#bpy-types-shadernodebsdftransparent"),
 	("bpy.types.shadernodevectortransform*", "render/shader_nodes/vector/transform.html#bpy-types-shadernodevectortransform"),
+	("bpy.types.shrinkwrapgpencilmodifier*", "grease_pencil/modifiers/deform/shrinkwrap.html#bpy-types-shrinkwrapgpencilmodifier"),
 	("bpy.types.spaceclipeditor.show_grid*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-grid"),
 	("bpy.types.spaceoutliner.filter_text*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-text"),
 	("bpy.types.spacetexteditor.find_text*", "editors/text_editor.html#bpy-types-spacetexteditor-find-text"),
@@ -1118,6 +1135,8 @@ url_manual_mapping = (
 	("bpy.types.spaceuveditor.lock_bounds*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-lock-bounds"),
 	("bpy.types.spline.tilt_interpolation*", "modeling/curves/properties/active_spline.html#bpy-types-spline-tilt-interpolation"),
 	("bpy.types.transformorientation.name*", "editors/3dview/controls/orientation.html#bpy-types-transformorientation-name"),
+	("bpy.types.unitsettings.scale_length*", "scene_layout/scene/properties.html#bpy-types-unitsettings-scale-length"),
+	("bpy.types.unitsettings.use_separate*", "scene_layout/scene/properties.html#bpy-types-unitsettings-use-separate"),
 	("bpy.types.viewlayer.use_motion_blur*", "render/layers/introduction.html#bpy-types-viewlayer-use-motion-blur"),
 	("bpy.types.volumedisplay.slice_depth*", "modeling/volumes/properties.html#bpy-types-volumedisplay-slice-depth"),
 	("bpy.types.worldmistsettings.falloff*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-falloff"),
@@ -1150,13 +1169,14 @@ url_manual_mapping = (
 	("bpy.ops.wm.previews_batch_generate*", "files/blend/previews.html#bpy-ops-wm-previews-batch-generate"),
 	("bpy.types.assetmetadata.active_tag*", "editors/asset_browser.html#bpy-types-assetmetadata-active-tag"),
 	("bpy.types.bakesettings.cage_object*", "render/cycles/baking.html#bpy-types-bakesettings-cage-object"),
+	("bpy.types.bakesettings.margin_type*", "render/cycles/baking.html#bpy-types-bakesettings-margin-type"),
 	("bpy.types.bone.use_relative_parent*", "animation/armatures/bones/properties/relations.html#bpy-types-bone-use-relative-parent"),
 	("bpy.types.brush.auto_smooth_factor*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-auto-smooth-factor"),
 	("bpy.types.brush.smooth_deform_type*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-smooth-deform-type"),
 	("bpy.types.brush.use_connected_only*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-use-connected-only"),
 	("bpy.types.brush.use_cursor_overlay*", "sculpt_paint/brush/cursor.html#bpy-types-brush-use-cursor-overlay"),
 	("bpy.types.camera.show_passepartout*", "render/cameras.html#bpy-types-camera-show-passepartout"),
-	("bpy.types.collection.lineart_usage*", "scene_layout/collections/properties.html#bpy-types-collection-lineart-usage"),
+	("bpy.types.collection.lineart_usage*", "scene_layout/collections/collections.html#bpy-types-collection-lineart-usage"),
 	("bpy.types.colormanagedviewsettings*", "render/color_management.html#bpy-types-colormanagedviewsettings"),
 	("bpy.types.compositornodebokehimage*", "compositing/types/input/bokeh_image.html#bpy-types-compositornodebokehimage"),
 	("bpy.types.compositornodecolormatte*", "compositing/types/matte/color_key.html#bpy-types-compositornodecolormatte"),
@@ -1172,6 +1192,7 @@ url_manual_mapping = (
 	("bpy.types.freestylelineset.exclude*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-exclude"),
 	("bpy.types.freestylelinestyle.alpha*", "render/freestyle/view_layer/line_style/alpha.html#bpy-types-freestylelinestyle-alpha"),
 	("bpy.types.freestylelinestyle.color*", "render/freestyle/view_layer/line_style/color.html#bpy-types-freestylelinestyle-color"),
+	("bpy.types.geometrynodefieldatindex*", "modeling/geometry_nodes/utilities/field_at_index.html#bpy-types-geometrynodefieldatindex"),
 	("bpy.types.geometrynodeinputtangent*", "modeling/geometry_nodes/curve/curve_tangent.html#bpy-types-geometrynodeinputtangent"),
 	("bpy.types.geometrynodejoingeometry*", "modeling/geometry_nodes/geometry/join_geometry.html#bpy-types-geometrynodejoingeometry"),
 	("bpy.types.geometrynodemeshcylinder*", "modeling/geometry_nodes/mesh_primitives/cylinder.html#bpy-types-geometrynodemeshcylinder"),
@@ -1207,6 +1228,7 @@ url_manual_mapping = (
 	("bpy.types.thicknessgpencilmodifier*", "grease_pencil/modifiers/deform/thickness.html#bpy-types-thicknessgpencilmodifier"),
 	("bpy.types.toolsettings.snap_target*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-snap-target"),
 	("bpy.types.transformcacheconstraint*", "animation/constraints/transform/transform_cache.html#bpy-types-transformcacheconstraint"),
+	("bpy.types.unitsettings.length_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-length-unit"),
 	("bpy.types.vertexweighteditmodifier*", "modeling/modifiers/modify/weight_edit.html#bpy-types-vertexweighteditmodifier"),
 	("bpy.types.volumedisplay.slice_axis*", "modeling/volumes/properties.html#bpy-types-volumedisplay-slice-axis"),
 	("bpy.ops.anim.channels_clean_empty*", "editors/nla/editing.html#bpy-ops-anim-channels-clean-empty"),
@@ -1229,7 +1251,7 @@ url_manual_mapping = (
 	("bpy.ops.object.vertex_group_clean*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-clean"),
 	("bpy.ops.poselib.create_pose_asset*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-create-pose-asset"),
 	("bpy.ops.preferences.theme_install*", "editors/preferences/themes.html#bpy-ops-preferences-theme-install"),
-	("bpy.ops.render.play-rendered-anim*", "render/output/animation_player.html#bpy-ops-render-play-rendered-anim"),
+	("bpy.ops.render.play_rendered_anim*", "render/output/animation_player.html#bpy-ops-render-play-rendered-anim"),
 	("bpy.ops.sculpt.set_pivot_position*", "sculpt_paint/sculpting/editing/sculpt.html#bpy-ops-sculpt-set-pivot-position"),
 	("bpy.ops.sequencer.image_strip_add*", "video_editing/sequencer/strips/image.html#bpy-ops-sequencer-image-strip-add"),
 	("bpy.ops.sequencer.movie_strip_add*", "video_editing/sequencer/strips/movie.html#bpy-ops-sequencer-movie-strip-add"),
@@ -1253,6 +1275,7 @@ url_manual_mapping = (
 	("bpy.types.compositornodemovieclip*", "compositing/types/input/movie_clip.html#bpy-types-compositornodemovieclip"),
 	("bpy.types.compositornodenormalize*", "compositing/types/vector/normalize.html#bpy-types-compositornodenormalize"),
 	("bpy.types.compositornodepremulkey*", "compositing/types/converter/alpha_convert.html#bpy-types-compositornodepremulkey"),
+	("bpy.types.compositornodescenetime*", "compositing/types/input/scene_time.html#bpy-types-compositornodescenetime"),
 	("bpy.types.compositornodestabilize*", "compositing/types/distort/stabilize_2d.html#bpy-types-compositornodestabilize"),
 	("bpy.types.compositornodetransform*", "compositing/types/distort/transform.html#bpy-types-compositornodetransform"),
 	("bpy.types.compositornodetranslate*", "compositing/types/distort/translate.html#bpy-types-compositornodetranslate"),
@@ -1273,6 +1296,7 @@ url_manual_mapping = (
 	("bpy.types.geometrynodecurvelength*", "modeling/geometry_nodes/curve/curve_length.html#bpy-types-geometrynodecurvelength"),
 	("bpy.types.geometrynodecurvespiral*", "modeling/geometry_nodes/curve_primitives/curve_spiral.html#bpy-types-geometrynodecurvespiral"),
 	("bpy.types.geometrynodecurvetomesh*", "modeling/geometry_nodes/curve/curve_to_mesh.html#bpy-types-geometrynodecurvetomesh"),
+	("bpy.types.geometrynodeextrudemesh*", "modeling/geometry_nodes/mesh/extrude_mesh.html#bpy-types-geometrynodeextrudemesh"),
 	("bpy.types.geometrynodefilletcurve*", "modeling/geometry_nodes/curve/fillet_curve.html#bpy-types-geometrynodefilletcurve"),
 	("bpy.types.geometrynodeinputnormal*", "modeling/geometry_nodes/input/normal.html#bpy-types-geometrynodeinputnormal"),
 	("bpy.types.geometrynodeinputradius*", "modeling/geometry_nodes/input/radius.html#bpy-types-geometrynodeinputradius"),
@@ -1332,6 +1356,7 @@ url_manual_mapping = (
 	("bpy.ops.mesh.primitive_plane_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-plane-add"),
 	("bpy.ops.mesh.primitive_torus_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-torus-add"),
 	("bpy.ops.mesh.select_non_manifold*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-non-manifold"),
+	("bpy.ops.object.attribute_convert*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-convert"),
 	("bpy.ops.object.constraints_clear*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraints-clear"),
 	("bpy.ops.object.quadriflow_remesh*", "modeling/meshes/retopology.html#bpy-ops-object-quadriflow-remesh"),
 	("bpy.ops.object.vertex_group_copy*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-copy"),
@@ -1388,6 +1413,7 @@ url_manual_mapping = (
 	("bpy.types.freestylelinestyle.gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-gap"),
 	("bpy.types.freestylesettings.mode*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-mode"),
 	("bpy.types.geometrynodeconvexhull*", "modeling/geometry_nodes/geometry/convex_hull.html#bpy-types-geometrynodeconvexhull"),
+	("bpy.types.geometrynodedomainsize*", "modeling/geometry_nodes/attribute/domain_size.html#bpy-types-geometrynodedomainsize"),
 	("bpy.types.geometrynodefloattoint*", "modeling/geometry_nodes/utilities/float_to_integer.html#bpy-types-geometrynodefloattoint"),
 	("bpy.types.geometrynodeinputcolor*", "modeling/geometry_nodes/input/color.html#bpy-types-geometrynodeinputcolor"),
 	("bpy.types.geometrynodeinputindex*", "modeling/geometry_nodes/input/input_index.html#bpy-types-geometrynodeinputindex"),
@@ -1418,6 +1444,8 @@ url_manual_mapping = (
 	("bpy.types.sound.use_memory_cache*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sound-use-memory-cache"),
 	("bpy.types.spaceview3d.show_gizmo*", "editors/3dview/display/gizmo.html#bpy-types-spaceview3d-show-gizmo"),
 	("bpy.types.texturegpencilmodifier*", "grease_pencil/modifiers/modify/texture_mapping.html#bpy-types-texturegpencilmodifier"),
+	("bpy.types.unitsettings.mass_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-mass-unit"),
+	("bpy.types.unitsettings.time_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-time-unit"),
 	("bpy.types.volumedisplacemodifier*", "modeling/modifiers/deform/volume_displace.html#bpy-types-volumedisplacemodifier"),
 	("bpy.types.volumerender.step_size*", "modeling/volumes/properties.html#bpy-types-volumerender-step-size"),
 	("bpy.types.weightednormalmodifier*", "modeling/modifiers/modify/weighted_normal.html#bpy-types-weightednormalmodifier"),
@@ -1435,6 +1463,7 @@ url_manual_mapping = (
 	("bpy.ops.gpencil.stroke_caps_set*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-caps-set"),
 	("bpy.ops.gpencil.stroke_separate*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-separate"),
 	("bpy.ops.gpencil.stroke_simplify*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-simplify"),
+	("bpy.ops.graph.blend_to_neighbor*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-blend-to-neighbor"),
 	("bpy.ops.graph.snap_cursor_value*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-snap-cursor-value"),
 	("bpy.ops.image.save_all_modified*", "editors/image/editing.html#bpy-ops-image-save-all-modified"),
 	("bpy.ops.mesh.extrude_edges_move*", "modeling/meshes/editing/edge/extrude_edges.html#bpy-ops-mesh-extrude-edges-move"),
@@ -1447,6 +1476,7 @@ url_manual_mapping = (
 	("bpy.ops.mesh.subdivide_edgering*", "modeling/meshes/editing/edge/subdivide_edge_ring.html#bpy-ops-mesh-subdivide-edgering"),
 	("bpy.ops.node.hide_socket_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-hide-socket-toggle"),
 	("bpy.ops.node.tree_socket_remove*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-remove"),
+	("bpy.ops.object.attribute_remove*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-remove"),
 	("bpy.ops.object.constraints_copy*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraints-copy"),
 	("bpy.ops.object.gpencil_modifier*", "grease_pencil/modifiers/index.html#bpy-ops-object-gpencil-modifier"),
 	("bpy.ops.object.make_links_scene*", "scene_layout/object/editing/link_transfer/link_scene.html#bpy-ops-object-make-links-scene"),
@@ -1509,6 +1539,7 @@ url_manual_mapping = (
 	("bpy.types.geometrynodecurvestar*", "modeling/geometry_nodes/curve_primitives/star.html#bpy-types-geometrynodecurvestar"),
 	("bpy.types.geometrynodeedgesplit*", "modeling/geometry_nodes/mesh/split_edges.html#bpy-types-geometrynodeedgesplit"),
 	("bpy.types.geometrynodefillcurve*", "modeling/geometry_nodes/curve/fill_curve.html#bpy-types-geometrynodefillcurve"),
+	("bpy.types.geometrynodeflipfaces*", "modeling/geometry_nodes/mesh/flip_faces.html#bpy-types-geometrynodeflipfaces"),
 	("bpy.types.geometrynodetransform*", "modeling/geometry_nodes/geometry/transform.html#bpy-types-geometrynodetransform"),
 	("bpy.types.geometrynodetrimcurve*", "modeling/geometry_nodes/curve/trim_curve.html#bpy-types-geometrynodetrimcurve"),
 	("bpy.types.gpencilsculptsettings*", "grease_pencil/properties/index.html#bpy-types-gpencilsculptsettings"),
@@ -1552,6 +1583,7 @@ url_manual_mapping = (
 	("bpy.ops.curve.switch_direction*", "modeling/curves/editing/segments.html#bpy-ops-curve-switch-direction"),
 	("bpy.ops.gpencil.duplicate_move*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-duplicate-move"),
 	("bpy.ops.gpencil.stroke_arrange*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-arrange"),
+	("bpy.ops.graph.equalize_handles*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-equalize-handles"),
 	("bpy.ops.mesh.bridge-edge-loops*", "modeling/meshes/editing/edge/bridge_edge_loops.html#bpy-ops-mesh-bridge-edge-loops"),
 	("bpy.ops.mesh.intersect_boolean*", "modeling/meshes/editing/face/intersect_boolean.html#bpy-ops-mesh-intersect-boolean"),
 	("bpy.ops.mesh.loop_multi_select*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-loop-multi-select"),
@@ -1616,6 +1648,7 @@ url_manual_mapping = (
 	("bpy.types.motionpath.frame_end*", "animation/motion_paths.html#bpy-types-motionpath-frame-end"),
 	("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.object.rotation_mode*", "scene_layout/object/properties/transforms.html#bpy-types-object-rotation-mode"),
 	("bpy.types.object.show_in_front*", "scene_layout/object/properties/display.html#bpy-types-object-show-in-front"),
 	("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"),
 	("bpy.types.preferencesfilepaths*", "editors/preferences/file_paths.html#bpy-types-preferencesfilepaths"),
@@ -1718,6 +1751,7 @@ url_manual_mapping = (
 	("bpy.types.cyclesworldsettings*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings"),
 	("bpy.types.dashgpencilmodifier*", "grease_pencil/modifiers/generate/dash.html#bpy-types-dashgpencilmodifier"),
 	("bpy.types.fluiddomainsettings*", "physics/fluid/type/domain/index.html#bpy-types-fluiddomainsettings"),
+	("bpy.types.functionnodecompare*", "modeling/geometry_nodes/utilities/compare.html#bpy-types-functionnodecompare"),
 	("bpy.types.geometrynodeboolean*", "modeling/geometry_nodes/input/boolean.html#bpy-types-geometrynodeboolean"),
 	("bpy.types.geometrynodeinputid*", "modeling/geometry_nodes/input/id.html#bpy-types-geometrynodeinputid"),
 	("bpy.types.geometrynodeinteger*", "modeling/geometry_nodes/input/integer.html#bpy-types-geometrynodeinteger"),
@@ -1744,6 +1778,7 @@ url_manual_mapping = (
 	("bpy.types.shadernodelightpath*", "render/shader_nodes/input/light_path.html#bpy-types-shadernodelightpath"),
 	("bpy.types.shadernodemixshader*", "render/shader_nodes/shader/mix.html#bpy-types-shadernodemixshader"),
 	("bpy.types.shadernodenormalmap*", "render/shader_nodes/vector/normal_map.html#bpy-types-shadernodenormalmap"),
+	("bpy.types.shadernodepointinfo*", "render/shader_nodes/input/point_info.html#bpy-types-shadernodepointinfo"),
 	("bpy.types.shadernodewireframe*", "render/shader_nodes/input/wireframe.html#bpy-types-shadernodewireframe"),
 	("bpy.types.spacesequenceeditor*", "video_editing/index.html#bpy-types-spacesequenceeditor"),
 	("bpy.types.spline.resolution_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-resolution-u"),
@@ -1756,6 +1791,7 @@ url_manual_mapping = (
 	("bpy.types.tintgpencilmodifier*", "grease_pencil/modifiers/color/tint.html#bpy-types-tintgpencilmodifier"),
 	("bpy.types.transformconstraint*", "animation/constraints/transform/transformation.html#bpy-types-transformconstraint"),
 	("bpy.types.triangulatemodifier*", "modeling/modifiers/generate/triangulate.html#bpy-types-triangulatemodifier"),
+	("bpy.types.unitsettings.system*", "scene_layout/scene/properties.html#bpy-types-unitsettings-system"),
 	("bpy.types.viewlayer.use_solid*", "render/layers/introduction.html#bpy-types-viewlayer-use-solid"),
 	("bpy.types.volume.frame_offset*", "modeling/volumes/properties.html#bpy-types-volume-frame-offset"),
 	("bpy.types.windowmanager.addon*", "editors/preferences/addons.html#bpy-types-windowmanager-addon"),
@@ -1788,6 +1824,7 @@ url_manual_mapping = (
 	("bpy.ops.node.node_copy_color*", "interface/controls/nodes/sidebar.html#bpy-ops-node-node-copy-color"),
 	("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.attribute_add*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-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"),
@@ -1819,12 +1856,12 @@ url_manual_mapping = (
 	("bpy.types.armature.show_axes*", "animation/armatures/properties/display.html#bpy-types-armature-show-axes"),
 	("bpy.types.armatureconstraint*", "animation/constraints/relationship/armature.html#bpy-types-armatureconstraint"),
 	("bpy.types.compositornodeblur*", "compositing/types/filter/blur_node.html#bpy-types-compositornodeblur"),
-	("bpy.types.compositornodecomb*", "editors/texture_node/types/color/combine_separate.html#bpy-types-compositornodecomb"),
+	("bpy.types.compositornodecomb*", "compositing/types/converter/combine_separate.html#bpy-types-compositornodecomb"),
 	("bpy.types.compositornodecrop*", "compositing/types/distort/crop.html#bpy-types-compositornodecrop"),
 	("bpy.types.compositornodeflip*", "compositing/types/distort/flip.html#bpy-types-compositornodeflip"),
 	("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.compositornodetime*", "compositing/types/input/time_curve.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"),
@@ -1860,7 +1897,6 @@ url_manual_mapping = (
 	("bpy.types.shadernodeemission*", "render/shader_nodes/shader/emission.html#bpy-types-shadernodeemission"),
 	("bpy.types.shadernodegeometry*", "render/shader_nodes/input/geometry.html#bpy-types-shadernodegeometry"),
 	("bpy.types.shadernodehairinfo*", "render/shader_nodes/input/hair_info.html#bpy-types-shadernodehairinfo"),
-	("bpy.types.shadernodepointinfo*", "render/shader_nodes/input/point_info.html#bpy-types-shadernodepointinfo"),
 	("bpy.types.shadernodemaprange*", "render/shader_nodes/converter/map_range.html#bpy-types-shadernodemaprange"),
 	("bpy.types.shadernodergbcurve*", "modeling/geometry_nodes/color/rgb_curves.html#bpy-types-shadernodergbcurve"),
 	("bpy.types.shadernodeseparate*", "render/shader_nodes/converter/combine_separate.html#bpy-types-shadernodeseparate"),
@@ -1892,6 +1928,7 @@ url_manual_mapping = (
 	("bpy.ops.curve.smooth_weight*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-weight"),
 	("bpy.ops.file.pack_libraries*", "files/blend/packed_data.html#bpy-ops-file-pack-libraries"),
 	("bpy.ops.font.change_spacing*", "modeling/texts/editing.html#bpy-ops-font-change-spacing"),
+	("bpy.ops.gpencil.layer_merge*", "grease_pencil/properties/layers.html#bpy-ops-gpencil-layer-merge"),
 	("bpy.ops.gpencil.stroke_flip*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-flip"),
 	("bpy.ops.gpencil.stroke_join*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-join"),
 	("bpy.ops.gpencil.stroke_trim*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-trim"),
@@ -1949,7 +1986,7 @@ url_manual_mapping = (
 	("bpy.types.collisionmodifier*", "physics/collision.html#bpy-types-collisionmodifier"),
 	("bpy.types.collisionsettings*", "physics/collision.html#bpy-types-collisionsettings"),
 	("bpy.types.compositornodergb*", "compositing/types/input/rgb.html#bpy-types-compositornodergb"),
-	("bpy.types.compositornodesep*", "editors/texture_node/types/color/combine_separate.html#bpy-types-compositornodesep"),
+	("bpy.types.compositornodesep*", "compositing/types/converter/combine_separate.html#bpy-types-compositornodesep"),
 	("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"),
@@ -2173,6 +2210,7 @@ url_manual_mapping = (
 	("bpy.types.floorconstraint*", "animation/constraints/relationship/floor.html#bpy-types-floorconstraint"),
 	("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.geometrynodearc*", "modeling/geometry_nodes/curve_primitives/arc.html#bpy-types-geometrynodearc"),
 	("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.materiallineart*", "render/materials/line_art.html#bpy-types-materiallineart"),
@@ -2290,6 +2328,7 @@ url_manual_mapping = (
 	("bpy.ops.fluid.free_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-ops-fluid-free-mesh"),
 	("bpy.ops.font.select_all*", "modeling/texts/selecting.html#bpy-ops-font-select-all"),
 	("bpy.ops.gpencil.convert*", "grease_pencil/modes/object/convert_to_geometry.html#bpy-ops-gpencil-convert"),
+	("bpy.ops.graph.breakdown*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-breakdown"),
 	("bpy.ops.mask.parent_set*", "movie_clip/masking/editing.html#bpy-ops-mask-parent-set"),
 	("bpy.ops.mask.select_all*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-all"),
 	("bpy.ops.mask.select_box*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-box"),
@@ -2476,6 +2515,7 @@ url_manual_mapping = (
 	("bpy.ops.sculpt.expand*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-expand"),
 	("bpy.ops.view3d.select*", "editors/3dview/selecting.html#bpy-ops-view3d-select"),
 	("bpy.ops.wm.debug_menu*", "advanced/operators.html#bpy-ops-wm-debug-menu"),
+	("bpy.ops.wm.obj_export*", "files/import_export/obj.html#bpy-ops-wm-obj-export"),
 	("bpy.ops.wm.properties*", "files/data_blocks.html#bpy-ops-wm-properties"),
 	("bpy.ops.wm.usd_export*", "files/import_export/usd.html#bpy-ops-wm-usd-export"),
 	("bpy.types.addsequence*", "video_editing/sequencer/strips/effects/add.html#bpy-types-addsequence"),
@@ -2579,6 +2619,7 @@ url_manual_mapping = (
 	("bpy.types.node.name*", "interface/controls/nodes/sidebar.html#bpy-types-node-name"),
 	("bpy.types.nodeframe*", "interface/controls/nodes/frame.html#bpy-types-nodeframe"),
 	("bpy.types.nodegroup*", "interface/controls/nodes/groups.html#bpy-types-nodegroup"),
+	("bpy.types.scene.muv*", "addons/uv/magic_uv.html#bpy-types-scene-muv"),
 	("bpy.types.spotlight*", "render/lights/light_object.html#bpy-types-spotlight"),
 	("bpy.types.textcurve*", "modeling/texts/index.html#bpy-types-textcurve"),
 	("bpy.types.udimtiles*", "modeling/meshes/uv/workflows/udims.html#bpy-types-udimtiles"),
@@ -2714,6 +2755,7 @@ url_manual_mapping = (
 	("bpy.ops.render*", "render/index.html#bpy-ops-render"),
 	("bpy.ops.script*", "advanced/scripting/index.html#bpy-ops-script"),
 	("bpy.ops.sculpt*", "sculpt_paint/sculpting/index.html#bpy-ops-sculpt"),
+	("bpy.ops.uv.muv*", "addons/uv/magic_uv.html#bpy-ops-uv-muv"),
 	("bpy.ops.uv.pin*", "modeling/meshes/uv/editing.html#bpy-ops-uv-pin"),
 	("bpy.ops.uv.rip*", "modeling/meshes/uv/tools/rip.html#bpy-ops-uv-rip"),
 	("bpy.ops.view3d*", "editors/3dview/index.html#bpy-ops-view3d"),
-- 
cgit v1.2.3


From 88712453f6c1a337ad0bf47c039d7c2cb58a5591 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Wed, 23 Feb 2022 14:16:46 -0500
Subject: Fix error with SPDX-License-Identifier

---
 release/scripts/modules/rna_manual_reference.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py
index c5f64212083..81e6fa10f62 100644
--- a/release/scripts/modules/rna_manual_reference.py
+++ b/release/scripts/modules/rna_manual_reference.py
@@ -1,4 +1,4 @@
-SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-License-Identifier: GPL-2.0-or-later
 # Do not edit this file. This file is auto generated from rna_manual_reference_updater.py
 
 import bpy
-- 
cgit v1.2.3


From 6ca85a8cf1f30ea36eaebe28484ae183d836811d Mon Sep 17 00:00:00 2001
From: Ray Molenkamp 
Date: Wed, 23 Feb 2022 12:27:02 -0700
Subject: Fix: Build issue on windows

tbb/enumerable_thread_specific.h drags in windows.h
which will define min/max macro's unless you politely
ask it not to.

it's bit of an eyesore, but it is what it is
---
 .../blenlib/BLI_enumerable_thread_specific.hh        | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/source/blender/blenlib/BLI_enumerable_thread_specific.hh b/source/blender/blenlib/BLI_enumerable_thread_specific.hh
index 339f02dce0f..51bf8d06cf1 100644
--- a/source/blender/blenlib/BLI_enumerable_thread_specific.hh
+++ b/source/blender/blenlib/BLI_enumerable_thread_specific.hh
@@ -3,7 +3,25 @@
 #pragma once
 
 #ifdef WITH_TBB
-#  include 
+
+#  ifdef WITH_TBB
+/* Quiet top level deprecation message, unrelated to API usage here. */
+#    if defined(WIN32) && !defined(NOMINMAX)
+/* TBB includes Windows.h which will define min/max macros causing issues
+ * when we try to use std::min and std::max later on. */
+#      define NOMINMAX
+#      define TBB_MIN_MAX_CLEANUP
+#    endif
+#    include 
+#    ifdef WIN32
+/* We cannot keep this defined, since other parts of the code deal with this on their own, leading
+ * to multiple define warnings unless we un-define this, however we can only undefine this if we
+ * were the ones that made the definition earlier. */
+#      ifdef TBB_MIN_MAX_CLEANUP
+#        undef NOMINMAX
+#      endif
+#    endif
+#  endif
 #endif
 
 #include 
-- 
cgit v1.2.3


From 86fc63c4ca9d36eae7512c5c3e640285d84415d1 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Wed, 23 Feb 2022 14:59:51 -0500
Subject: Cleanup: Simplify manual url version handling

Recently we changed the build pipeline to always create a version
number in the url and point 'dev' to the latest version rather than creating the version number url once we release.

This makes the check to `bpy.app.version_cycle` unnecessary.
---
 release/scripts/modules/addon_utils.py          |  7 +------
 release/scripts/modules/rna_manual_reference.py |  5 +----
 release/scripts/startup/bl_operators/wm.py      | 20 +++++++++-----------
 release/scripts/startup/bl_ui/space_topbar.py   |  2 +-
 4 files changed, 12 insertions(+), 22 deletions(-)

diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py
index 554150de87d..3e823f2b6b7 100644
--- a/release/scripts/modules/addon_utils.py
+++ b/release/scripts/modules/addon_utils.py
@@ -489,12 +489,7 @@ def disable_all():
 
 
 def _blender_manual_url_prefix():
-    if _bpy.app.version_cycle in {"rc", "release"}:
-        manual_version = "%d.%d" % _bpy.app.version[:2]
-    else:
-        manual_version = "dev"
-
-    return "https://docs.blender.org/manual/en/" + manual_version
+    return "https://docs.blender.org/manual/en/%d.%d" % _bpy.app.version[:2]
 
 
 def module_bl_info(mod, *, info_basis=None):
diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py
index 85c46ebbcd8..4b10c29346e 100644
--- a/release/scripts/modules/rna_manual_reference.py
+++ b/release/scripts/modules/rna_manual_reference.py
@@ -3,10 +3,7 @@
 
 import bpy
 
-if bpy.app.version_cycle in {'rc', 'release'}:
-    manual_version = '%d.%d' % bpy.app.version[:2]
-else:
-    manual_version = 'dev'
+manual_version = '%d.%d' % bpy.app.version[:2]
 
 url_manual_prefix = "https://docs.blender.org/manual/en/" + manual_version + "/"
 
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index fd0671a2369..a7f401afad1 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -995,11 +995,10 @@ class WM_OT_url_open_preset(Operator):
         return "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
 
     def _url_from_manual(self, _context):
-        if bpy.app.version_cycle in {"rc", "release"}:
-            manual_version = "%d.%d" % bpy.app.version[:2]
-        else:
-            manual_version = "dev"
-        return "https://docs.blender.org/manual/en/" + manual_version + "/"
+        return "https://docs.blender.org/manual/en/%d.%d/" % bpy.app.version[:2]
+
+    def _url_from_api(self, _context):
+        return "https://docs.blender.org/api/%d.%d/" % bpy.app.version[:2]
 
     # This list is: (enum_item, url) pairs.
     # Allow dynamically extending.
@@ -1014,9 +1013,12 @@ class WM_OT_url_open_preset(Operator):
         (('RELEASE_NOTES', "Release Notes",
           "Read about what's new in this version of Blender"),
          _url_from_release_notes),
-        (('MANUAL', "Manual",
+        (('MANUAL', "User Manual",
           "The reference manual for this version of Blender"),
          _url_from_manual),
+        (('API', "Python API Reference",
+          "The API reference manual for this version of Blender"),
+         _url_from_api),
 
         # Static URL's.
         (('FUND', "Development Fund",
@@ -1231,11 +1233,7 @@ class WM_OT_doc_view(Operator):
     bl_label = "View Documentation"
 
     doc_id: doc_id
-    if bpy.app.version_cycle in {"release", "rc", "beta"}:
-        _prefix = ("https://docs.blender.org/api/%d.%d" %
-                   (bpy.app.version[0], bpy.app.version[1]))
-    else:
-        _prefix = ("https://docs.blender.org/api/master")
+    _prefix = "https://docs.blender.org/api/%d.%d" % bpy.app.version[:2]
 
     def execute(self, _context):
         url = _wm_doc_get_id(self.doc_id, do_url=True, url_prefix=self._prefix, report=self.report)
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index e6d2d1403b5..e488e23c777 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -689,7 +689,7 @@ class TOPBAR_MT_help(Menu):
 
         layout.operator(
             "wm.url_open", text="Python API Reference", icon='URL',
-        ).url = bpy.types.WM_OT_doc_view._prefix
+        ).type = 'API'
 
         if show_developer:
             layout.operator(
-- 
cgit v1.2.3


From 756f7fb23e2b3ed1ec877a3bf6e0d9812271e491 Mon Sep 17 00:00:00 2001
From: Johnny Matthews 
Date: Wed, 23 Feb 2022 14:19:08 -0600
Subject: Geometry Nodes: Face is Planar Node This adds a node with a boolean
 field output which returns true if all of the points of the evaluated face
 are on the same plane. A float field input allows for the threshold of the
 face/point comparison to be adjusted on a per face basis.

One clear use case is to only triangulate faces that are not planar.

Differential Revision: https://developer.blender.org/D13906
---
 release/scripts/startup/nodeitems_builtins.py      |   1 +
 source/blender/blenkernel/BKE_node.h               |   1 +
 source/blender/blenkernel/intern/node.cc           |   1 +
 source/blender/nodes/NOD_geometry.h                |   1 +
 source/blender/nodes/NOD_static_types.h            |   1 +
 source/blender/nodes/geometry/CMakeLists.txt       |   1 +
 .../nodes/node_geo_input_mesh_face_is_planar.cc    | 116 +++++++++++++++++++++
 7 files changed, 122 insertions(+)
 create mode 100644 source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index cea938bf1a4..a0205a2bcb1 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -142,6 +142,7 @@ def mesh_node_items(context):
     yield NodeItem("GeometryNodeInputMeshEdgeVertices")
     yield NodeItem("GeometryNodeInputMeshFaceArea")
     yield NodeItem("GeometryNodeInputMeshFaceNeighbors")
+    yield NodeItem("GeometryNodeInputMeshFaceIsPlanar")
     yield NodeItem("GeometryNodeInputMeshIsland")
     yield NodeItem("GeometryNodeInputShadeSmooth")
     yield NodeItem("GeometryNodeInputMeshVertexNeighbors")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 90f933d7cbb..8a65279ae74 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1515,6 +1515,7 @@ struct TexResult;
 #define GEO_NODE_EXTRUDE_MESH 1152
 #define GEO_NODE_MERGE_BY_DISTANCE 1153
 #define GEO_NODE_DUPLICATE_ELEMENTS 1154
+#define GEO_NODE_INPUT_MESH_FACE_IS_PLANAR 1155
 
 /** \} */
 
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 474859128dc..9fd9f9a1492 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4770,6 +4770,7 @@ static void registerGeometryNodes()
   register_node_type_geo_input_mesh_edge_neighbors();
   register_node_type_geo_input_mesh_edge_vertices();
   register_node_type_geo_input_mesh_face_area();
+  register_node_type_geo_input_mesh_face_is_planar();
   register_node_type_geo_input_mesh_face_neighbors();
   register_node_type_geo_input_mesh_island();
   register_node_type_geo_input_mesh_vertex_neighbors();
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 0a53d9cc019..cfcc30655b5 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -101,6 +101,7 @@ void register_node_type_geo_input_mesh_edge_angle(void);
 void register_node_type_geo_input_mesh_edge_neighbors(void);
 void register_node_type_geo_input_mesh_edge_vertices(void);
 void register_node_type_geo_input_mesh_face_area(void);
+void register_node_type_geo_input_mesh_face_is_planar(void);
 void register_node_type_geo_input_mesh_face_neighbors(void);
 void register_node_type_geo_input_mesh_island(void);
 void register_node_type_geo_input_mesh_vertex_neighbors(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index a3033651a3a..efd3c420330 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -359,6 +359,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", Inpu
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS", InputMeshEdgeNeighbors, "Edge Neighbors", "")
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "")
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, 0, "MESH_FACE_IS_PLANAR", InputMeshFaceIsPlanar, "Face is Planar", "")
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, 0, "MESH_FACE_NEIGHBORS", InputMeshFaceNeighbors, "Face Neighbors", "")
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_ISLAND, 0, "MESH_ISLAND", InputMeshIsland, "Mesh Island", "")
 DefNode(GeometryNode, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, 0, "MESH_VERTEX_NEIGHBORS", InputMeshVertexNeighbors, "Vertex Neighbors", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index fbf584e0f4b..48a83dc825b 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -117,6 +117,7 @@ set(SRC
   nodes/node_geo_input_mesh_edge_neighbors.cc
   nodes/node_geo_input_mesh_edge_vertices.cc
   nodes/node_geo_input_mesh_face_area.cc
+  nodes/node_geo_input_mesh_face_is_planar.cc
   nodes/node_geo_input_mesh_face_neighbors.cc
   nodes/node_geo_input_mesh_island.cc
   nodes/node_geo_input_mesh_vertex_neighbors.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
new file mode 100644
index 00000000000..1113087b264
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+  b.add_input("Threshold")
+      .field_source()
+      .default_value(0.01f)
+      .subtype(PROP_DISTANCE)
+      .supports_field()
+      .description(N_("The distance a point can be from the surface before the face is no longer "
+                      "considered planar"))
+      .min(0.0f);
+  b.add_output("Planar").field_source();
+}
+
+class PlanarFieldInput final : public GeometryFieldInput {
+ private:
+  Field threshold_;
+
+ public:
+  PlanarFieldInput(Field threshold)
+      : GeometryFieldInput(CPPType::get(), "Planar"), threshold_(threshold)
+  {
+    category_ = Category::Generated;
+  }
+
+  GVArray get_varray_for_context(const GeometryComponent &component,
+                                 const AttributeDomain domain,
+                                 [[maybe_unused]] IndexMask mask) const final
+  {
+    if (component.type() != GEO_COMPONENT_TYPE_MESH) {
+      return {};
+    }
+
+    const MeshComponent &mesh_component = static_cast(component);
+    const Mesh *mesh = mesh_component.get_for_read();
+    if (mesh == nullptr) {
+      return {};
+    }
+
+    GeometryComponentFieldContext context{mesh_component, ATTR_DOMAIN_FACE};
+    fn::FieldEvaluator evaluator{context, mesh->totpoly};
+    evaluator.add(threshold_);
+    evaluator.evaluate();
+    const VArray &thresholds = evaluator.get_evaluated(0);
+
+    Span poly_normals{(float3 *)BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly};
+
+    auto planar_fn = [mesh, thresholds, poly_normals](const int i_poly) -> bool {
+      if (mesh->mpoly[i_poly].totloop <= 3) {
+        return true;
+      }
+      const int loopstart = mesh->mpoly[i_poly].loopstart;
+      const int loops = mesh->mpoly[i_poly].totloop;
+      Span poly_loops(&mesh->mloop[loopstart], loops);
+      float3 reference_normal = poly_normals[i_poly];
+
+      float min = FLT_MAX;
+      float max = FLT_MIN;
+
+      for (const int i_loop : poly_loops.index_range()) {
+        const float3 vert = mesh->mvert[poly_loops[i_loop].v].co;
+        float dot = math::dot(reference_normal, vert);
+        if (dot > max) {
+          max = dot;
+        }
+        if (dot < min) {
+          min = dot;
+        }
+      }
+      return max - min < thresholds[i_poly] / 2.0f;
+    };
+
+    return component.attribute_try_adapt_domain(
+        VArray::ForFunc(mesh->totpoly, planar_fn), ATTR_DOMAIN_FACE, domain);
+  }
+
+  uint64_t hash() const override
+  {
+    /* Some random constant hash. */
+    return 2356235652;
+  }
+
+  bool is_equal_to(const fn::FieldNode &other) const override
+  {
+    return dynamic_cast(&other) != nullptr;
+  }
+};
+
+static void geo_node_exec(GeoNodeExecParams params)
+{
+  Field threshold = params.extract_input>("Threshold");
+  Field planar_field{std::make_shared(threshold)};
+  params.set_output("Planar", std::move(planar_field));
+}
+
+}  // namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc
+
+void register_node_type_geo_input_mesh_face_is_planar()
+{
+  namespace file_ns = blender::nodes::node_geo_input_mesh_face_is_planar_cc;
+
+  static bNodeType ntype;
+
+  geo_node_type_base(
+      &ntype, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, "Face is Planar", NODE_CLASS_INPUT);
+  ntype.geometry_node_execute = file_ns::geo_node_exec;
+  ntype.declare = file_ns::node_declare;
+  nodeRegisterType(&ntype);
+}
-- 
cgit v1.2.3


From f8fe0e831ec14cc521e03df6b586918a6a5add03 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Wed, 23 Feb 2022 15:44:55 -0500
Subject: Curves: Use simpler "set" behavior for handle position attributes

The handle position attributes `handle_left` and `handle_right` had
rather complex behavior to get expected behavior when aligned or auto/
vector handles were used. In order to simplify the attribtue API and
make the transition to the new curves data structure simpler, this
commit moves that behavior from the attribute to the "Set Handle
Positions" node. When that node is used, the behavior should be the
same as before. However, if the modifier's output attributes were used
to set handle positions, the behavior may be different. That situation
is expected to be very rare though.
---
 .../blenkernel/intern/geometry_component_curve.cc  |  8 ++--
 .../geometry/nodes/node_geo_set_curve_handles.cc   | 49 ++++++++++++++--------
 2 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 2004f1c0609..9116ffc63a8 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -973,10 +973,10 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
     if (spline.type() == CURVE_TYPE_BEZIER) {
       BezierSpline &bezier_spline = static_cast(spline);
       if (is_right_) {
-        bezier_spline.set_handle_position_right(indices.point_index, value);
+        bezier_spline.handle_positions_right()[indices.point_index] = value;
       }
       else {
-        bezier_spline.set_handle_position_left(indices.point_index, value);
+        bezier_spline.handle_positions_left()[indices.point_index] = value;
       }
       bezier_spline.mark_cache_invalid();
     }
@@ -992,12 +992,12 @@ class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl {
         BezierSpline &bezier_spline = static_cast(spline);
         if (is_right_) {
           for (const int i : IndexRange(bezier_spline.size())) {
-            bezier_spline.set_handle_position_right(i, src[offset + i]);
+            bezier_spline.handle_positions_right()[i] = src[offset + i];
           }
         }
         else {
           for (const int i : IndexRange(bezier_spline.size())) {
-            bezier_spline.set_handle_position_left(i, src[offset + i]);
+            bezier_spline.handle_positions_left()[i] = src[offset + i];
           }
         }
         bezier_spline.mark_cache_invalid();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index 3ccb9b79d1f..808d679fb73 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -53,36 +53,31 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
   evaluator.evaluate();
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
 
-  CurveComponent *curve_component = static_cast(&component);
-  CurveEval *curve = curve_component->get_for_write();
-
-  StringRef side = mode & GEO_NODE_CURVE_HANDLE_LEFT ? "handle_left" : "handle_right";
+  CurveComponent &curve_component = *static_cast(&component);
+  CurveEval *curve = curve_component.get_for_write();
 
   int current_point = 0;
   int current_mask = 0;
-
   for (const SplinePtr &spline : curve->splines()) {
     if (spline->type() == CURVE_TYPE_BEZIER) {
       BezierSpline &bezier = static_cast(*spline);
-      for (int i : bezier.positions().index_range()) {
+
+      bezier.ensure_auto_handles();
+      for (const int i : bezier.positions().index_range()) {
         if (current_mask < selection.size() && selection[current_mask] == current_point) {
           if (mode & GEO_NODE_CURVE_HANDLE_LEFT) {
             if (bezier.handle_types_left()[i] == BEZIER_HANDLE_VECTOR) {
-              bezier.ensure_auto_handles();
               bezier.handle_types_left()[i] = BEZIER_HANDLE_FREE;
             }
             else if (bezier.handle_types_left()[i] == BEZIER_HANDLE_AUTO) {
-              bezier.ensure_auto_handles();
               bezier.handle_types_left()[i] = BEZIER_HANDLE_ALIGN;
             }
           }
           else {
             if (bezier.handle_types_right()[i] == BEZIER_HANDLE_VECTOR) {
-              bezier.ensure_auto_handles();
               bezier.handle_types_right()[i] = BEZIER_HANDLE_FREE;
             }
             else if (bezier.handle_types_right()[i] == BEZIER_HANDLE_AUTO) {
-              bezier.ensure_auto_handles();
               bezier.handle_types_right()[i] = BEZIER_HANDLE_ALIGN;
             }
           }
@@ -104,15 +99,33 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
   const VArray &positions_input = evaluator.get_evaluated(0);
   const VArray &offsets_input = evaluator.get_evaluated(1);
 
-  OutputAttribute_Typed positions = component.attribute_try_get_for_output(
-      side, ATTR_DOMAIN_POINT, {0, 0, 0});
-  MutableSpan position_mutable = positions.as_span();
-
-  for (int i : selection) {
-    position_mutable[i] = positions_input[i] + offsets_input[i];
+  current_point = 0;
+  current_mask = 0;
+  for (const SplinePtr &spline : curve->splines()) {
+    if (spline->type() == CURVE_TYPE_BEZIER) {
+      BezierSpline &bezier = static_cast(*spline);
+      for (const int i : bezier.positions().index_range()) {
+        if (current_mask < selection.size() && selection[current_mask] == current_point) {
+          if (mode & GEO_NODE_CURVE_HANDLE_LEFT) {
+            bezier.set_handle_position_left(i, positions_input[i] + offsets_input[i]);
+          }
+          else {
+            bezier.set_handle_position_right(i, positions_input[i] + offsets_input[i]);
+          }
+          current_mask++;
+        }
+        current_point++;
+      }
+    }
+    else {
+      for ([[maybe_unused]] int i : spline->positions().index_range()) {
+        if (current_mask < selection.size() && selection[current_mask] == current_point) {
+          current_mask++;
+        }
+        current_point++;
+      }
+    }
   }
-
-  positions.save();
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
-- 
cgit v1.2.3


From c9582b2752c8e016f128f71a530cab283ba26f64 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 11:03:31 +1100
Subject: Fix T95116: Scale to fit fails with a single word & non-zero Y-size

The scale-to-fit option did nothing for single words when
the text box had a height. This happened because it was expected that
text would be wrapped however single words never wrap.

Now the same behavior for zero-height text boxes is used when text
can't be wrapped onto multiple lines.
---
 source/blender/blenkernel/intern/vfont.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c
index 4a598288ee6..537d49d6f8a 100644
--- a/source/blender/blenkernel/intern/vfont.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -1001,7 +1001,22 @@ static bool vfont_to_curve(Object *ob,
       }
       else if (x_used > x_available) {
         // CLOG_WARN(&LOG, "linewidth exceeded: %c%c%c...", mem[i], mem[i+1], mem[i+2]);
-        for (j = i; j && (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
+        for (j = i; (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
+
+          /* Special case when there are no breaks possible. */
+          if (UNLIKELY(j == 0)) {
+            if (i == slen) {
+              /* Use the behavior of zero a height text-box when a break cannot be inserted.
+               *
+               * Typically when a text-box has any height and overflow is set to scale
+               * the text will wrap to fit the width as necessary. When wrapping isn't
+               * possible it's important to use the same code-path as zero-height lines.
+               * Without this exception a single word will not scale-to-fit (see: T95116). */
+              tb_scale.h = 0.0f;
+            }
+            break;
+          }
+
           bool dobreak = false;
           if (ELEM(mem[j], ' ', '-')) {
             ct -= (i - (j - 1));
-- 
cgit v1.2.3


From ba274d20b4f19833e6413fdc7f1bc3a663cc2f8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Thu, 24 Feb 2022 02:46:33 +0100
Subject: Fix T95959: crash when exporting subdivision to Alembic

When exporting generated coordinates, the subdivision export code was
using the schema for the non-subdivision case, which is invalid as
non-initialized. This typo existed since the initial commit for the
feature (rBf9567f6c63e75feaf701fa7b78669b9a436f13dd).
---
 source/blender/io/alembic/exporter/abc_writer_mesh.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
index 6ab4b06fba6..ba028f756e1 100644
--- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc
+++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc
@@ -323,7 +323,7 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me
   }
 
   if (args_.export_params->orcos) {
-    write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config);
+    write_generated_coordinates(abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config);
   }
 
   if (!edge_crease_indices.empty()) {
-- 
cgit v1.2.3


From e83e7d49b6637177c1ba16531e4927533f7cb0f4 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 12:53:09 +1100
Subject: Cleanup: use continue instead of goto for 3D text wrapping logic

---
 source/blender/blenkernel/intern/vfont.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c
index 7e025c59b6c..5e75088a008 100644
--- a/source/blender/blenkernel/intern/vfont.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -905,8 +905,8 @@ static bool vfont_to_curve(Object *ob,
     custrinfo[i].flag &= ~(CU_CHINFO_WRAP | CU_CHINFO_SMALLCAPS_CHECK | CU_CHINFO_OVERFLOW);
   }
 
-  for (i = 0; i <= slen; i++) {
-  makebreak:
+  i = 0;
+  while (i <= slen) {
     /* Characters in the list */
     info = &custrinfo[i];
     ascii = mem[i];
@@ -985,6 +985,7 @@ static bool vfont_to_curve(Object *ob,
       }
       else if (x_used > x_available) {
         // CLOG_WARN(&LOG, "linewidth exceeded: %c%c%c...", mem[i], mem[i+1], mem[i+2]);
+        bool dobreak = false;
         for (j = i; (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
 
           /* Special case when there are no breaks possible. */
@@ -1001,7 +1002,6 @@ static bool vfont_to_curve(Object *ob,
             break;
           }
 
-          bool dobreak = false;
           if (ELEM(mem[j], ' ', '-')) {
             ct -= (i - (j - 1));
             cnr -= (i - (j - 1));
@@ -1016,8 +1016,9 @@ static bool vfont_to_curve(Object *ob,
             ct[1].dobreak = 1;
             custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
             dobreak = true;
+            break;
           }
-          else if (chartransdata[j].dobreak) {
+          if (chartransdata[j].dobreak) {
             // CLOG_WARN(&LOG, "word too long: %c%c%c...", mem[j], mem[j+1], mem[j+2]);
             ct->dobreak = 1;
             custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
@@ -1026,14 +1027,17 @@ static bool vfont_to_curve(Object *ob,
             i--;
             xof = ct->xof;
             dobreak = true;
+            break;
           }
-          if (dobreak) {
-            if (tb_scale.h == 0.0f) {
-              /* NOTE: If underlined text is truncated away, the extra space is also truncated. */
-              custrinfo[i + 1].flag |= CU_CHINFO_OVERFLOW;
-            }
-            goto makebreak;
+        }
+
+        if (dobreak) {
+          if (tb_scale.h == 0.0f) {
+            /* NOTE: If underlined text is truncated away, the extra space is also truncated. */
+            custrinfo[i + 1].flag |= CU_CHINFO_OVERFLOW;
           }
+          /* Since a break was added, re-run this loop with `i` at it's new value. */
+          continue;
         }
       }
     }
@@ -1139,7 +1143,9 @@ static bool vfont_to_curve(Object *ob,
       }
     }
     ct++;
+    i++;
   }
+
   current_line_length += xof + twidth - MARGIN_X_MIN;
   longest_line_length = MAX2(current_line_length, longest_line_length);
 
-- 
cgit v1.2.3


From d59f45fb15c9aec836bc2e3dbdd87679e2f485ec Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 12:53:11 +1100
Subject: Cleanup: remove dead code from text wrapping logic

Error from original commit in 2005,
97df61a7e5391e302d1a5f9069cf0b388f85e0c8.
---
 source/blender/blenkernel/intern/vfont.c | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c
index 5e75088a008..3af09f22fa9 100644
--- a/source/blender/blenkernel/intern/vfont.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -1018,17 +1018,7 @@ static bool vfont_to_curve(Object *ob,
             dobreak = true;
             break;
           }
-          if (chartransdata[j].dobreak) {
-            // CLOG_WARN(&LOG, "word too long: %c%c%c...", mem[j], mem[j+1], mem[j+2]);
-            ct->dobreak = 1;
-            custrinfo[i + 1].flag |= CU_CHINFO_WRAP;
-            ct -= 1;
-            cnr -= 1;
-            i--;
-            xof = ct->xof;
-            dobreak = true;
-            break;
-          }
+          BLI_assert(chartransdata[j].dobreak == 0);
         }
 
         if (dobreak) {
-- 
cgit v1.2.3


From b45a3e8747018a173afba8ca0d25c898fb6e62bc Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 13:13:06 +1100
Subject: Cleanup: remove dead code in vfont_to_curve

Also replaces numbers with character literals for tab/space.
---
 source/blender/blenkernel/intern/vfont.c | 14 ++------------
 1 file changed, 2 insertions(+), 12 deletions(-)

diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c
index 3af09f22fa9..40d3b5cf062 100644
--- a/source/blender/blenkernel/intern/vfont.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -1072,22 +1072,12 @@ static bool vfont_to_curve(Object *ob,
         current_line_length = 0.0f;
       }
 
-      /* XXX(campbell): has been unused for years, need to check if this is useful, r4613 r5282. */
-#if 0
-      if (ascii == '\n') {
-        xof = xof_scale;
-      }
-      else {
-        xof = MARGIN_X_MIN;
-      }
-#else
       xof = MARGIN_X_MIN;
-#endif
       lnr++;
       cnr = 0;
       wsnr = 0;
     }
-    else if (ascii == 9) { /* TAB */
+    else if (ascii == '\t') { /* Tab character. */
       float tabfac;
 
       ct->xof = xof;
@@ -1115,7 +1105,7 @@ static bool vfont_to_curve(Object *ob,
         sb->w = xof * font_size;
       }
 
-      if (ascii == 32) {
+      if (ascii == ' ') { /* Space character. */
         wsfac = cu->wordspace;
         wsnr++;
       }
-- 
cgit v1.2.3


From 1a6b35157cdfde2cbde5d96210eb08745626a291 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 13:19:29 +1100
Subject: Cleanup: code comments in vfont_to_curve

---
 source/blender/blenkernel/intern/vfont.c | 42 +++++++++++++++-----------------
 1 file changed, 19 insertions(+), 23 deletions(-)

diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c
index 40d3b5cf062..5f751da1ee1 100644
--- a/source/blender/blenkernel/intern/vfont.c
+++ b/source/blender/blenkernel/intern/vfont.c
@@ -811,8 +811,8 @@ static bool vfont_to_curve(Object *ob,
 #define MARGIN_X_MIN (xof_scale + tb_scale.x)
 #define MARGIN_Y_MIN (yof_scale + tb_scale.y)
 
-  /* remark: do calculations including the trailing '\0' of a string
-   * because the cursor can be at that location */
+  /* NOTE: do calculations including the trailing '\0' of a string
+   * because the cursor can be at that location. */
 
   BLI_assert(ob == NULL || ob->type == OB_FONT);
 
@@ -941,19 +941,16 @@ static bool vfont_to_curve(Object *ob,
       che = find_vfont_char(vfd, ascii);
       BLI_rw_mutex_unlock(&vfont_rwlock);
 
-      /*
-       * The character wasn't in the current curve base so load it
+      /* The character wasn't in the current curve base so load it.
        * But if the font is built-in then do not try loading since
-       * whole font is in the memory already
-       */
+       * whole font is in the memory already. */
       if (che == NULL && BKE_vfont_is_builtin(vfont) == false) {
         BLI_rw_mutex_lock(&vfont_rwlock, THREAD_LOCK_WRITE);
         /* Check it once again, char might have been already load
-         * between previous BLI_rw_mutex_unlock() and this BLI_rw_mutex_lock().
+         * between previous #BLI_rw_mutex_unlock() and this #BLI_rw_mutex_lock().
          *
          * Such a check should not be a bottleneck since it wouldn't
-         * happen often once all the chars are load.
-         */
+         * happen often once all the chars are load. */
         if ((che = find_vfont_char(vfd, ascii)) == NULL) {
           che = BKE_vfontdata_char_from_freetypefont(vfont, ascii);
         }
@@ -1113,7 +1110,7 @@ static bool vfont_to_curve(Object *ob,
         wsfac = 1.0f;
       }
 
-      /* Set the width of the character */
+      /* Set the width of the character. */
       twidth = char_width(cu, che, info);
 
       xof += (twidth * wsfac * (1.0f + (info->kern / 40.0f))) + xtrax;
@@ -1214,7 +1211,7 @@ static bool vfont_to_curve(Object *ob,
     }
   }
 
-  /* top-baseline is default, in this case, do nothing */
+  /* Top-baseline is default, in this case, do nothing. */
   if (cu->align_y != CU_ALIGN_Y_TOP_BASELINE) {
     if (tb_scale.h != 0.0f) {
       /* We need to loop all the text-boxes even the "full" ones.
@@ -1239,7 +1236,7 @@ static bool vfont_to_curve(Object *ob,
         }
 
         textbox_scale(&tb_scale, &cu->tb[tb_index], 1.0f / font_size);
-        /* The initial Y origin of the textbox is hardcoded to 1.0f * text scale. */
+        /* The initial Y origin of the text-box is hard-coded to 1.0f * text scale. */
         const float textbox_y_origin = 1.0f;
         float yoff = 0.0f;
 
@@ -1303,7 +1300,7 @@ static bool vfont_to_curve(Object *ob,
   MEM_freeN(i_textbox_array);
 
   /* TEXT ON CURVE */
-  /* NOTE: Only OB_CURVES_LEGACY objects could have a path. */
+  /* NOTE: Only #OB_CURVES_LEGACY objects could have a path. */
   if (cu->textoncurve && cu->textoncurve->type == OB_CURVES_LEGACY) {
     BLI_assert(cu->textoncurve->runtime.curve_cache != NULL);
     if (cu->textoncurve->runtime.curve_cache != NULL &&
@@ -1394,8 +1391,8 @@ static bool vfont_to_curve(Object *ob,
         ctime = timeofs + distfac * (ct->xof - minx);
         CLAMP(ctime, 0.0f, 1.0f);
 
-        /* calc the right loc AND the right rot separately */
-        /* vec, tvec need 4 items */
+        /* Calculate the right loc AND the right rot separately. */
+        /* `vec`, `tvec` need 4 items. */
         BKE_where_on_path(cu->textoncurve, ctime, vec, tvec, NULL, NULL, NULL);
         BKE_where_on_path(cu->textoncurve, ctime + dtime, tvec, rotvec, NULL, NULL, NULL);
 
@@ -1456,7 +1453,7 @@ static bool vfont_to_curve(Object *ob,
           break;
       }
       cnr = ct->charnr;
-      /* seek for char with lnr en cnr */
+      /* Seek for char with `lnr` & `cnr`. */
       ef->pos = 0;
       ct = chartransdata;
       for (i = 0; i < slen; i++) {
@@ -1474,7 +1471,7 @@ static bool vfont_to_curve(Object *ob,
     }
   }
 
-  /* cursor first */
+  /* Cursor first. */
   if (ef) {
     float si, co;
 
@@ -1520,13 +1517,13 @@ static bool vfont_to_curve(Object *ob,
       }
 
       /* Only do that check in case we do have an object, otherwise all materials get erased every
-       * time that code is called without an object... */
+       * time that code is called without an object. */
       if (ob != NULL && (info->mat_nr > (ob->totcol))) {
         // CLOG_ERROR(
         //     &LOG, "Illegal material index (%d) in text object, setting to 0", info->mat_nr);
         info->mat_nr = 0;
       }
-      /* We do not want to see any character for \n or \r */
+      /* We don't want to see any character for '\n'. */
       if (cha != '\n') {
         BKE_vfont_build_char(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size);
       }
@@ -1602,8 +1599,7 @@ static bool vfont_to_curve(Object *ob,
        *
        * Keep in mind that there is no single number that will make all fit to the end.
        * In a way, our ultimate goal is to get the highest scale that still leads to the
-       * number of extra lines to zero.
-       */
+       * number of extra lines to zero. */
       if (iter_data->status == VFONT_TO_CURVE_INIT) {
         bool valid = true;
 
@@ -1636,7 +1632,7 @@ static bool vfont_to_curve(Object *ob,
           iter_data->bisect.max = iter_data->scale_to_fit;
         }
         else {
-          /* It fits inside the textbox, scale it up. */
+          /* It fits inside the text-box, scale it up. */
           iter_data->bisect.min = iter_data->scale_to_fit;
           valid = true;
         }
@@ -1698,7 +1694,7 @@ finally:
     }
   }
 
-  /* Store the effective scale, to use for the textbox lines. */
+  /* Store the effective scale, to use for the text-box lines. */
   cu->fsize_realtime = font_size;
 
   return ok;
-- 
cgit v1.2.3


From f80f7afbf03af08a0255b353e1e775d51174763e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 15:19:15 +1100
Subject: Fix 2D view NDOF panning requiring both axes to be non-zero

Possible fix for T86592.
---
 source/blender/editors/interface/view2d_ops.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c
index 0d3c427bf39..62103e2cd63 100644
--- a/source/blender/editors/interface/view2d_ops.c
+++ b/source/blender/editors/interface/view2d_ops.c
@@ -1465,7 +1465,7 @@ static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   /* tune these until it feels right */
   const float zoom_sensitivity = 0.5f;
   const float speed = 10.0f; /* match view3d ortho */
-  const bool has_translate = (ndof->tvec[0] && ndof->tvec[1]) && view_pan_poll(C);
+  const bool has_translate = !is_zero_v2(ndof->tvec) && view_pan_poll(C);
   const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C);
 
   if (has_translate) {
-- 
cgit v1.2.3


From 81df323df4d51d1c64133946aaa009021381888d Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 17:43:36 +1100
Subject: Tests: disable file output for bl_keymap_validate.py

Left on by accident but should only be used when debugging output.
---
 tests/python/bl_keymap_validate.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index 9408f641718..7bd40ce97e7 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -47,7 +47,7 @@ import bpy  # type: ignore
 
 # Useful for diffing the output to see what changed in context.
 # this writes keymaps into the current directory with `.orig.py` & `.rewrite.py` extensions.
-WRITE_OUTPUT_DIR = "/tmp/test"  # "/tmp", defaults to the systems temp directory.
+WRITE_OUTPUT_DIR = ""  # "/tmp", defaults to the systems temp directory.
 
 # For each preset, test all of these options.
 # The key is the preset name, containing a sequence of (attribute, value) pairs to test.
-- 
cgit v1.2.3


From e767a2d98bd0ec1e62b639d0307f49536a288c31 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 17:51:19 +1100
Subject: Keymap: correct NDOF button roll assignment

NDOF ROLL CCW was used for both left & right roll.
---
 release/scripts/presets/keyconfig/keymap_data/blender_default.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index b4f684fa5cc..eab97b30fff 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -1467,7 +1467,7 @@ def km_view3d(params):
         ("view3d.ndof_all", {"type": 'NDOF_MOTION', "value": 'ANY', "shift": True, "ctrl": True}, None),
         ("view3d.view_selected", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'},
          {"properties": [("use_all_regions", False)]}),
-        ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
+        ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CW', "value": 'PRESS'},
          {"properties": [("type", 'LEFT')]}),
         ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
          {"properties": [("type", 'RIGHT')]}),
-- 
cgit v1.2.3


From 87c2b1988d7c040a76f741a2e7eb6bda5dcea973 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 17:51:19 +1100
Subject: Keymap: correct NDOF button roll assignment

NDOF ROLL CCW was used for both left & right roll.
---
 release/scripts/presets/keyconfig/keymap_data/blender_default.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index ba0521d1317..0f131b0d33f 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -1453,7 +1453,7 @@ def km_view3d(params):
         ("view3d.ndof_all", {"type": 'NDOF_MOTION', "value": 'ANY', "shift": True, "ctrl": True}, None),
         ("view3d.view_selected", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'},
          {"properties": [("use_all_regions", False)]}),
-        ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
+        ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CW', "value": 'PRESS'},
          {"properties": [("type", 'LEFT')]}),
         ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
          {"properties": [("type", 'RIGHT')]}),
-- 
cgit v1.2.3


From 13efaa8a09ab805c81164bc04a7ac4cc2c40bd1c Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 17:56:01 +1100
Subject: 3D View: keep the orthographic view when rolling 90 degrees

Support for maintaining orthographic view for view3d.view_roll for an
angle of 90/-90 degrees.
---
 .../editors/space_view3d/view3d_navigate_roll.c    | 24 +++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/source/blender/editors/space_view3d/view3d_navigate_roll.c b/source/blender/editors/space_view3d/view3d_navigate_roll.c
index 56bd9c93216..8d01f6b591b 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_roll.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_roll.c
@@ -24,8 +24,12 @@
 /** \name View Roll Operator
  * \{ */
 
-static void view_roll_angle(
-    ARegion *region, float quat[4], const float orig_quat[4], const float dvec[3], float angle)
+static void view_roll_angle(ARegion *region,
+                            float quat[4],
+                            const float orig_quat[4],
+                            const float dvec[3],
+                            float angle,
+                            bool use_axis_view)
 {
   RegionView3D *rv3d = region->regiondata;
   float quat_mul[4];
@@ -38,7 +42,16 @@ static void view_roll_angle(
   /* avoid precision loss over time */
   normalize_qt(quat);
 
-  rv3d->view = RV3D_VIEW_USER;
+  if (use_axis_view && RV3D_VIEW_IS_AXIS(rv3d->view) && (fabsf(angle) == (float)M_PI_2)) {
+    if (ED_view3d_quat_to_axis_view(quat, 0.01f, &rv3d->view, &rv3d->view_axis_roll)) {
+      if (rv3d->view != RV3D_VIEW_USER) {
+        ED_view3d_quat_from_axis_view(rv3d->view, rv3d->view_axis_roll, quat_mul);
+      }
+    }
+  }
+  else {
+    rv3d->view = RV3D_VIEW_USER;
+  }
 }
 
 static void viewroll_apply(ViewOpsData *vod, int x, int y)
@@ -46,7 +59,8 @@ static void viewroll_apply(ViewOpsData *vod, int x, int y)
   float angle = BLI_dial_angle(vod->init.dial, (const float[2]){x, y});
 
   if (angle != 0.0f) {
-    view_roll_angle(vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle);
+    view_roll_angle(
+        vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle, false);
   }
 
   if (vod->use_dyn_ofs) {
@@ -169,7 +183,7 @@ static int viewroll_exec(bContext *C, wmOperator *op)
 
     normalize_v3_v3(mousevec, rv3d->viewinv[2]);
     negate_v3(mousevec);
-    view_roll_angle(region, quat_new, rv3d->viewquat, mousevec, angle);
+    view_roll_angle(region, quat_new, rv3d->viewquat, mousevec, angle, true);
 
     const float *dyn_ofs_pt = NULL;
     float dyn_ofs[3];
-- 
cgit v1.2.3


From 41d607976c2b831ebe340c5dcdefbab97a5145bf Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 17:58:11 +1100
Subject: Keymap: use 90 degree roll for NDOF roll shortcuts

3Dconnexion documentation states these buttons should roll 90 degrees.
---
 release/scripts/presets/keyconfig/keymap_data/blender_default.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 0f131b0d33f..5fb0f052154 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -208,6 +208,9 @@ class Params:
 # ------------------------------------------------------------------------------
 # Constants
 
+from math import pi
+pi_2 = pi / 2.0
+
 # Physical layout.
 NUMBERS_1 = ('ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO')
 # Numeric order.
@@ -1409,7 +1412,7 @@ def km_view3d(params):
         ("view3d.view_roll", {"type": 'NUMPAD_6', "value": 'PRESS', "shift": True, "repeat": True},
          {"properties": [("type", 'RIGHT')]}),
         ("view3d.view_orbit", {"type": 'NUMPAD_9', "value": 'PRESS'},
-         {"properties": [("angle", 3.1415927), ("type", 'ORBITRIGHT')]}),
+         {"properties": [("angle", pi), ("type", 'ORBITRIGHT')]}),
         ("view3d.view_axis", {"type": 'NUMPAD_1', "value": 'PRESS', "shift": True},
          {"properties": [("type", 'FRONT'), ("align_active", True)]}),
         ("view3d.view_axis", {"type": 'NUMPAD_3', "value": 'PRESS', "shift": True},
@@ -1454,9 +1457,9 @@ def km_view3d(params):
         ("view3d.view_selected", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'},
          {"properties": [("use_all_regions", False)]}),
         ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CW', "value": 'PRESS'},
-         {"properties": [("type", 'LEFT')]}),
+         {"properties": [("angle", pi_2)]}),
         ("view3d.view_roll", {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
-         {"properties": [("type", 'RIGHT')]}),
+         {"properties": [("angle", -pi_2)]}),
         ("view3d.view_axis", {"type": 'NDOF_BUTTON_FRONT', "value": 'PRESS'},
          {"properties": [("type", 'FRONT')]}),
         ("view3d.view_axis", {"type": 'NDOF_BUTTON_BACK', "value": 'PRESS'},
-- 
cgit v1.2.3


From d48e595c7873469cef54902af15c26544994d36d Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Thu, 24 Feb 2022 12:40:40 +0100
Subject: Fix wrong asset library displayed/loaded in asset view templates

Sometimes when switching asset libraries in the asset view templates
(like the pose library sidebar UI), the wrong library would end up being
shown.
---
 source/blender/editors/asset/intern/asset_library_reference.cc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/asset/intern/asset_library_reference.cc b/source/blender/editors/asset/intern/asset_library_reference.cc
index 04f77821114..5096b9d653d 100644
--- a/source/blender/editors/asset/intern/asset_library_reference.cc
+++ b/source/blender/editors/asset/intern/asset_library_reference.cc
@@ -17,9 +17,9 @@ AssetLibraryReferenceWrapper::AssetLibraryReferenceWrapper(const AssetLibraryRef
 
 bool operator==(const AssetLibraryReferenceWrapper &a, const AssetLibraryReferenceWrapper &b)
 {
-  return (a.type == b.type) && (a.type == ASSET_LIBRARY_CUSTOM) ?
-             (a.custom_library_index == b.custom_library_index) :
-             true;
+  return (a.type == b.type) &&
+         ((a.type == ASSET_LIBRARY_CUSTOM) ? (a.custom_library_index == b.custom_library_index) :
+                                             true);
 }
 
 uint64_t AssetLibraryReferenceWrapper::hash() const
-- 
cgit v1.2.3


From 17e0634902b7fc8987918e02bc2d4e2090c32e02 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Thu, 24 Feb 2022 12:44:02 +0100
Subject: Fix T95985: crash when assigning a name for an output attribute

This was a double free error which happened because `BM_mesh_bm_from_me`
was taking ownership of arrays that were still owned by the Mesh. Note that
this only happens when the mesh is empty but some custom data layers still
have a non-null data pointer. While usually the data pointer should be null in
this case for performance reasons, other functions should still be able to
handle this situation.

Differential Revision: https://developer.blender.org/D14181
---
 source/blender/bmesh/intern/bmesh_mesh_convert.cc | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc
index 4635da29d34..ee0e5789169 100644
--- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc
+++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc
@@ -226,10 +226,10 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *me, const struct BMeshFromMeshPar
 
   if (!me || !me->totvert) {
     if (me && is_new) { /* No verts? still copy custom-data layout. */
-      CustomData_copy(&me->vdata, &bm->vdata, mask.vmask, CD_ASSIGN, 0);
-      CustomData_copy(&me->edata, &bm->edata, mask.emask, CD_ASSIGN, 0);
-      CustomData_copy(&me->ldata, &bm->ldata, mask.lmask, CD_ASSIGN, 0);
-      CustomData_copy(&me->pdata, &bm->pdata, mask.pmask, CD_ASSIGN, 0);
+      CustomData_copy(&me->vdata, &bm->vdata, mask.vmask, CD_DEFAULT, 0);
+      CustomData_copy(&me->edata, &bm->edata, mask.emask, CD_DEFAULT, 0);
+      CustomData_copy(&me->ldata, &bm->ldata, mask.lmask, CD_DEFAULT, 0);
+      CustomData_copy(&me->pdata, &bm->pdata, mask.pmask, CD_DEFAULT, 0);
 
       CustomData_bmesh_init_pool(&bm->vdata, me->totvert, BM_VERT);
       CustomData_bmesh_init_pool(&bm->edata, me->totedge, BM_EDGE);
-- 
cgit v1.2.3


From b7a954218b0d1b90b67f228ae3ab8498710a1a65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Wed, 23 Feb 2022 15:27:29 +0100
Subject: Fix T95976: on cage GPU subdivision breaks X-ray selection

When X-ray mode is active the selection is done using the mesh data to
select what is closest to the cursor. When GPU subdivision is active with
the "show on cage" modifier option, this fails as the mesh used for selection
is the unsubdivided one.

This creates a subdivision wrapper before running the selection routines to
ensure that subdivision is available on the CPU side as well.

Differential Revision: https://developer.blender.org/D14188
---
 source/blender/editors/space_view3d/view3d_iterators.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c
index 990b4fc85b1..d711a3795c8 100644
--- a/source/blender/editors/space_view3d/view3d_iterators.c
+++ b/source/blender/editors/space_view3d/view3d_iterators.c
@@ -39,6 +39,7 @@
 #include "BKE_editmesh.h"
 #include "BKE_mesh_iterators.h"
 #include "BKE_mesh_runtime.h"
+#include "BKE_mesh_wrapper.h"
 #include "BKE_modifier.h"
 
 #include "DEG_depsgraph.h"
@@ -348,6 +349,7 @@ void mesh_foreachScreenVert(
 
   Mesh *me = editbmesh_get_eval_cage_from_orig(
       vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH);
+  me = BKE_mesh_wrapper_ensure_subdivision(vc->obedit, me);
 
   ED_view3d_check_mats_rv3d(vc->rv3d);
 
@@ -410,6 +412,7 @@ void mesh_foreachScreenEdge(ViewContext *vc,
 
   Mesh *me = editbmesh_get_eval_cage_from_orig(
       vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH);
+  me = BKE_mesh_wrapper_ensure_subdivision(vc->obedit, me);
 
   ED_view3d_check_mats_rv3d(vc->rv3d);
 
@@ -497,6 +500,7 @@ void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc,
 
   Mesh *me = editbmesh_get_eval_cage_from_orig(
       vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH);
+  me = BKE_mesh_wrapper_ensure_subdivision(vc->obedit, me);
 
   ED_view3d_check_mats_rv3d(vc->rv3d);
 
@@ -568,6 +572,7 @@ void mesh_foreachScreenFace(
 
   Mesh *me = editbmesh_get_eval_cage_from_orig(
       vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH);
+  me = BKE_mesh_wrapper_ensure_subdivision(vc->obedit, me);
   ED_view3d_check_mats_rv3d(vc->rv3d);
 
   data.vc = *vc;
-- 
cgit v1.2.3


From 9be720d85efb307ad37b73f0f5fffd95629c7e20 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Thu, 24 Feb 2022 14:45:41 +0100
Subject: BLI: support accessing nth last element in Array/Span/Vector

This often helps to make the intend of code more clear compared
to computing the index manually in the caller.
---
 source/blender/blenlib/BLI_array.hh             | 18 ++++++++++--------
 source/blender/blenlib/BLI_span.hh              | 22 ++++++++++++----------
 source/blender/blenlib/BLI_vector.hh            | 18 ++++++++++--------
 source/blender/blenlib/tests/BLI_array_test.cc  |  2 ++
 source/blender/blenlib/tests/BLI_span_test.cc   |  3 +++
 source/blender/blenlib/tests/BLI_vector_test.cc |  3 +++
 6 files changed, 40 insertions(+), 26 deletions(-)

diff --git a/source/blender/blenlib/BLI_array.hh b/source/blender/blenlib/BLI_array.hh
index a580f12851e..91dfc81ae27 100644
--- a/source/blender/blenlib/BLI_array.hh
+++ b/source/blender/blenlib/BLI_array.hh
@@ -278,18 +278,20 @@ class Array {
   }
 
   /**
-   * Return a reference to the last element in the array.
-   * This invokes undefined behavior when the array is empty.
+   * Return a reference to the nth last element.
+   * This invokes undefined behavior when the array is too short.
    */
-  const T &last() const
+  const T &last(const int64_t n = 0) const
   {
-    BLI_assert(size_ > 0);
-    return *(data_ + size_ - 1);
+    BLI_assert(n >= 0);
+    BLI_assert(n < size_);
+    return *(data_ + size_ - 1 - n);
   }
-  T &last()
+  T &last(const int64_t n = 0)
   {
-    BLI_assert(size_ > 0);
-    return *(data_ + size_ - 1);
+    BLI_assert(n >= 0);
+    BLI_assert(n < size_);
+    return *(data_ + size_ - 1 - n);
   }
 
   /**
diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh
index d82f21a57ff..9ab096094de 100644
--- a/source/blender/blenlib/BLI_span.hh
+++ b/source/blender/blenlib/BLI_span.hh
@@ -307,13 +307,14 @@ template class Span {
   }
 
   /**
-   * Returns a reference to the last element in the array. This invokes undefined behavior when the
-   * array is empty.
+   * Returns a reference to the nth last element. This invokes undefined behavior when the span is
+   * too short.
    */
-  constexpr const T &last() const
+  constexpr const T &last(const int64_t n = 0) const
   {
-    BLI_assert(size_ > 0);
-    return data_[size_ - 1];
+    BLI_assert(n >= 0);
+    BLI_assert(n < size_);
+    return data_[size_ - 1 - n];
   }
 
   /**
@@ -673,13 +674,14 @@ template class MutableSpan {
   }
 
   /**
-   * Returns a reference to the last element. This invokes undefined behavior when the array is
-   * empty.
+   * Returns a reference to the nth last element. This invokes undefined behavior when the span is
+   * too short.
    */
-  constexpr T &last() const
+  constexpr T &last(const int64_t n = 0) const
   {
-    BLI_assert(size_ > 0);
-    return data_[size_ - 1];
+    BLI_assert(n >= 0);
+    BLI_assert(n < size_);
+    return data_[size_ - 1 - n];
   }
 
   /**
diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh
index d5d33b8a000..da9ab9c313e 100644
--- a/source/blender/blenlib/BLI_vector.hh
+++ b/source/blender/blenlib/BLI_vector.hh
@@ -639,18 +639,20 @@ class Vector {
   }
 
   /**
-   * Return a reference to the last element in the vector.
-   * This invokes undefined behavior when the vector is empty.
+   * Return a reference to the nth last element.
+   * This invokes undefined behavior when the vector is too short.
    */
-  const T &last() const
+  const T &last(const int64_t n = 0) const
   {
-    BLI_assert(this->size() > 0);
-    return *(end_ - 1);
+    BLI_assert(n >= 0);
+    BLI_assert(n < this->size());
+    return *(end_ - 1 - n);
   }
-  T &last()
+  T &last(const int64_t n = 0)
   {
-    BLI_assert(this->size() > 0);
-    return *(end_ - 1);
+    BLI_assert(n >= 0);
+    BLI_assert(n < this->size());
+    return *(end_ - 1 - n);
   }
 
   /**
diff --git a/source/blender/blenlib/tests/BLI_array_test.cc b/source/blender/blenlib/tests/BLI_array_test.cc
index 6d12b54099a..74eeb5e4e5e 100644
--- a/source/blender/blenlib/tests/BLI_array_test.cc
+++ b/source/blender/blenlib/tests/BLI_array_test.cc
@@ -231,9 +231,11 @@ TEST(array, Last)
 {
   Array array = {5, 7, 8, 9};
   EXPECT_EQ(array.last(), 9);
+  EXPECT_EQ(array.last(1), 8);
   array.last() = 1;
   EXPECT_EQ(array[3], 1);
   EXPECT_EQ(const_cast &>(array).last(), 1);
+  EXPECT_EQ(const_cast &>(array).last(2), 7);
 }
 
 TEST(array, Reinitialize)
diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc
index 35fb22b3257..0bd34250deb 100644
--- a/source/blender/blenlib/tests/BLI_span_test.cc
+++ b/source/blender/blenlib/tests/BLI_span_test.cc
@@ -247,6 +247,8 @@ TEST(span, FirstLast)
   Span a_span(a);
   EXPECT_EQ(a_span.first(), 6);
   EXPECT_EQ(a_span.last(), 9);
+  EXPECT_EQ(a_span.last(1), 8);
+  EXPECT_EQ(a_span.last(2), 7);
 }
 
 TEST(span, FirstLast_OneElement)
@@ -255,6 +257,7 @@ TEST(span, FirstLast_OneElement)
   Span a_span(&a, 1);
   EXPECT_EQ(a_span.first(), 3);
   EXPECT_EQ(a_span.last(), 3);
+  EXPECT_EQ(a_span.last(0), 3);
 }
 
 TEST(span, Get)
diff --git a/source/blender/blenlib/tests/BLI_vector_test.cc b/source/blender/blenlib/tests/BLI_vector_test.cc
index 40cda20c395..29b6d2b41fe 100644
--- a/source/blender/blenlib/tests/BLI_vector_test.cc
+++ b/source/blender/blenlib/tests/BLI_vector_test.cc
@@ -447,6 +447,9 @@ TEST(vector, Last)
 {
   Vector a{3, 5, 7};
   EXPECT_EQ(a.last(), 7);
+  EXPECT_EQ(a.last(0), 7);
+  EXPECT_EQ(a.last(1), 5);
+  EXPECT_EQ(a.last(2), 3);
 }
 
 TEST(vector, AppendNTimes)
-- 
cgit v1.2.3


From 1685b1dba44e02ce1faace4a5de6d6cd0d98ef90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Thu, 24 Feb 2022 14:56:02 +0100
Subject: OCIO: Fix mip length in texture creation

Also add an assert to avoid reproducing the error in the future.
---
 intern/opencolorio/ocio_impl_glsl.cc     | 4 ++--
 source/blender/gpu/intern/gpu_texture.cc | 1 +
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc
index c5b0c597abd..e3d44ae9d55 100644
--- a/intern/opencolorio/ocio_impl_glsl.cc
+++ b/intern/opencolorio/ocio_impl_glsl.cc
@@ -324,7 +324,7 @@ static bool addGPULut2D(OCIO_GPUTextures &textures,
                                                                                   GPU_R16F;
 
   OCIO_GPULutTexture lut;
-  lut.texture = GPU_texture_create_2d(texture_name, width, height, 0, format, values);
+  lut.texture = GPU_texture_create_2d(texture_name, width, height, 1, format, values);
   if (lut.texture == nullptr) {
     return false;
   }
@@ -356,7 +356,7 @@ static bool addGPULut3D(OCIO_GPUTextures &textures,
 
   OCIO_GPULutTexture lut;
   lut.texture = GPU_texture_create_3d(
-      texture_name, edgelen, edgelen, edgelen, 0, GPU_RGB16F, GPU_DATA_FLOAT, values);
+      texture_name, edgelen, edgelen, edgelen, 1, GPU_RGB16F, GPU_DATA_FLOAT, values);
   if (lut.texture == nullptr) {
     return false;
   }
diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc
index da5f08f003e..762b819036c 100644
--- a/source/blender/gpu/intern/gpu_texture.cc
+++ b/source/blender/gpu/intern/gpu_texture.cc
@@ -192,6 +192,7 @@ static inline GPUTexture *gpu_texture_create(const char *name,
                                              eGPUDataFormat data_format,
                                              const void *pixels)
 {
+  BLI_assert(mips > 0);
   Texture *tex = GPUBackend::get()->texture_alloc(name);
   bool success = false;
   switch (type) {
-- 
cgit v1.2.3


From 8d83fc552a319223089087e0c60265adaef48724 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Thu, 24 Feb 2022 15:55:22 +0100
Subject: GPU: Fix compilation in debug mode

Fixes issue introduced in rB1685b1dba44e02ce1faace4a5de6d6cd0d98ef90
---
 source/blender/gpu/intern/gpu_texture.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc
index 762b819036c..d84f420f35b 100644
--- a/source/blender/gpu/intern/gpu_texture.cc
+++ b/source/blender/gpu/intern/gpu_texture.cc
@@ -187,7 +187,7 @@ static inline GPUTexture *gpu_texture_create(const char *name,
                                              const int h,
                                              const int d,
                                              const eGPUTextureType type,
-                                             int UNUSED(mips),
+                                             int mips,
                                              eGPUTextureFormat tex_format,
                                              eGPUDataFormat data_format,
                                              const void *pixels)
-- 
cgit v1.2.3


From fc8aab755ad626198b279adc2f56158c4812537f Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Thu, 24 Feb 2022 11:10:00 -0500
Subject: Fix T96015: Calling wrong operator resulting in python error

Own mistake in rB86fc63c4ca9d36eae7512c5c3e640285d84415d1.
Forgot to change the call from `wm.url_open` to `wm.url_open_preset`.
---
 release/scripts/startup/bl_ui/space_topbar.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index e488e23c777..3b6bdd01efa 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -688,7 +688,7 @@ class TOPBAR_MT_help(Menu):
         layout.separator()
 
         layout.operator(
-            "wm.url_open", text="Python API Reference", icon='URL',
+            "wm.url_open_preset", text="Python API Reference", icon='URL',
         ).type = 'API'
 
         if show_developer:
-- 
cgit v1.2.3


From 17301a3163c9b44c42f8032a575681a0c9fa1b8c Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Thu, 24 Feb 2022 17:02:14 +0100
Subject: Fix incorrect Outliner tree-display type returned

Reverts 6d97fdc37eef. A function like this should not return a different
tree-display object than of the requested type. This may hide errors,
and leaves the Outliner in an undefined state (where the stored display
mode doesn't match the tree-display object). I rather don't hide the
fact that all display-modes should be handled here, and emit a clear
error if one isn't.
---
 source/blender/editors/space_outliner/tree/tree_display.cc | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/space_outliner/tree/tree_display.cc b/source/blender/editors/space_outliner/tree/tree_display.cc
index 6b68f1ee4a4..f9141dffd6a 100644
--- a/source/blender/editors/space_outliner/tree/tree_display.cc
+++ b/source/blender/editors/space_outliner/tree/tree_display.cc
@@ -7,6 +7,8 @@
 #include "DNA_listBase.h"
 #include "DNA_space_types.h"
 
+#include "BLI_utildefines.h"
+
 #include "tree_display.hh"
 
 using namespace blender::ed::outliner;
@@ -30,11 +32,11 @@ std::unique_ptr AbstractTreeDisplay::createFromDisplayMode(
     case SO_OVERRIDES_LIBRARY:
       return std::make_unique(space_outliner);
     case SO_VIEW_LAYER:
-      /* FIXME(Julian): this should not be the default! Return nullptr and handle that as valid
-       * case. */
-    default:
       return std::make_unique(space_outliner);
   }
+
+  BLI_assert_unreachable();
+  return nullptr;
 }
 
 bool AbstractTreeDisplay::hasWarnings() const
-- 
cgit v1.2.3


From f8507ca3720e6bcec934d01ef3af129e9bf1d3a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Thu, 24 Feb 2022 17:22:41 +0100
Subject: EEVEE: Fix crash when shadow caster count is reduced to 0.

The crash was catched by ASAN. It was caused by the buffer shrinking logic
being faulty.
---
 source/blender/draw/engines/eevee/eevee_shadows.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/source/blender/draw/engines/eevee/eevee_shadows.c b/source/blender/draw/engines/eevee/eevee_shadows.c
index d8a40fd13cc..7d8dc85bea6 100644
--- a/source/blender/draw/engines/eevee/eevee_shadows.c
+++ b/source/blender/draw/engines/eevee/eevee_shadows.c
@@ -288,11 +288,9 @@ void EEVEE_shadows_update(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
 
   /* Resize shcasters buffers if too big. */
   if (frontbuffer->alloc_count - frontbuffer->count > SH_CASTER_ALLOC_CHUNK) {
-    frontbuffer->alloc_count = (frontbuffer->count / SH_CASTER_ALLOC_CHUNK) *
+    frontbuffer->alloc_count = divide_ceil_u(max_ii(1, frontbuffer->count),
+                                             SH_CASTER_ALLOC_CHUNK) *
                                SH_CASTER_ALLOC_CHUNK;
-    frontbuffer->alloc_count += (frontbuffer->count % SH_CASTER_ALLOC_CHUNK != 0) ?
-                                    SH_CASTER_ALLOC_CHUNK :
-                                    0;
     frontbuffer->bbox = MEM_reallocN(frontbuffer->bbox,
                                      sizeof(EEVEE_BoundBox) * frontbuffer->alloc_count);
     BLI_BITMAP_RESIZE(frontbuffer->update, frontbuffer->alloc_count);
-- 
cgit v1.2.3


From 7b37d980b97fd6f2fde7561c5f2a881f46db2c1f Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 24 Feb 2022 11:56:11 -0500
Subject: Fix: Crash switching between sculpt and edit mode

Also fix a couple other places where normals layers weren't properly
tagged dirty or reallocated when the mesh changes.

Caused by cfa53e0fbeed7178. When the size of a mesh changes,
the normal layers need to be reallocated. There were a couple of places
that cleared other runtime data with `BKE_mesh_runtime_clear_geometry`
but didn't deal with normals properly. Clearing the runtime "geometry"
is different from clearing the normals, because sometimes the size of
the normal layers doesn't have to change, in which case simply tagging
them dirty is fine.
---
 source/blender/bmesh/intern/bmesh_mesh_convert.cc | 6 +++---
 source/blender/editors/mesh/meshtools.c           | 1 +
 source/blender/makesrna/intern/rna_mesh.c         | 1 +
 source/blender/makesrna/intern/rna_mesh_api.c     | 2 +-
 4 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc
index ee0e5789169..c7b414d2c0c 100644
--- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc
+++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc
@@ -948,9 +948,9 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh
   CustomData_add_layer(&me->ldata, CD_MLOOP, CD_ASSIGN, mloop, me->totloop);
   CustomData_add_layer(&me->pdata, CD_MPOLY, CD_ASSIGN, mpoly, me->totpoly);
 
-  /* There is no way to tell if BMesh normals are dirty or not. Instead of calculating the normals
-   * on the BMesh possibly unnecessarily, just tag them dirty on the resulting mesh. */
-  BKE_mesh_normals_tag_dirty(me);
+  /* Clear normals on the mesh completely, since the original vertex and polygon count might be
+   * different than the BMesh's. */
+  BKE_mesh_clear_derived_normals(me);
 
   me->cd_flag = BM_mesh_cd_flag_from_bmesh(bm);
 
diff --git a/source/blender/editors/mesh/meshtools.c b/source/blender/editors/mesh/meshtools.c
index c9615698c46..b8e3d5737a3 100644
--- a/source/blender/editors/mesh/meshtools.c
+++ b/source/blender/editors/mesh/meshtools.c
@@ -428,6 +428,7 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op)
    * Even though this mesh wont typically have run-time data, the Python API can for e.g.
    * create loop-triangle cache here, which is confusing when left in the mesh, see: T90798. */
   BKE_mesh_runtime_clear_geometry(me);
+  BKE_mesh_clear_derived_normals(me);
 
   /* new material indices and material array */
   if (totmat) {
diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c
index 55b70fd1b41..07f98aa3d9f 100644
--- a/source/blender/makesrna/intern/rna_mesh.c
+++ b/source/blender/makesrna/intern/rna_mesh.c
@@ -470,6 +470,7 @@ static void rna_MeshPolygon_flip(ID *id, MPoly *mp)
   BKE_mesh_polygon_flip(mp, me->mloop, &me->ldata);
   BKE_mesh_tessface_clear(me);
   BKE_mesh_runtime_clear_geometry(me);
+  BKE_mesh_normals_tag_dirty(me);
 }
 
 static void rna_MeshLoopTriangle_verts_get(PointerRNA *ptr, int *values)
diff --git a/source/blender/makesrna/intern/rna_mesh_api.c b/source/blender/makesrna/intern/rna_mesh_api.c
index 9835d664a55..01837ad7035 100644
--- a/source/blender/makesrna/intern/rna_mesh_api.c
+++ b/source/blender/makesrna/intern/rna_mesh_api.c
@@ -183,7 +183,7 @@ static void rna_Mesh_flip_normals(Mesh *mesh)
 {
   BKE_mesh_polygons_flip(mesh->mpoly, mesh->mloop, &mesh->ldata, mesh->totpoly);
   BKE_mesh_tessface_clear(mesh);
-  BKE_mesh_calc_normals(mesh);
+  BKE_mesh_normals_tag_dirty(mesh);
   BKE_mesh_runtime_clear_geometry(mesh);
 
   DEG_id_tag_update(&mesh->id, 0);
-- 
cgit v1.2.3


From f4d80ecdfd3137693f7c00c4d3db6f9f8743a6bd Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 24 Feb 2022 12:01:22 -0500
Subject: Cleanup: Typo in comment

---
 source/blender/blenlib/BLI_virtual_array.hh          | 2 +-
 source/blender/functions/FN_generic_virtual_array.hh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh
index d697590b946..16fd706c99d 100644
--- a/source/blender/blenlib/BLI_virtual_array.hh
+++ b/source/blender/blenlib/BLI_virtual_array.hh
@@ -667,7 +667,7 @@ template class VArrayCommon {
   }
 
   /**
-   * Returns the internally used span of the virtual array. This invokes undefined behavior is the
+   * Returns the internally used span of the virtual array. This invokes undefined behavior if the
    * virtual array is not stored as a span internally.
    */
   Span get_internal_span() const
diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh
index 2655cd26bfe..ced0c2b9546 100644
--- a/source/blender/functions/FN_generic_virtual_array.hh
+++ b/source/blender/functions/FN_generic_virtual_array.hh
@@ -139,7 +139,7 @@ class GVArrayCommon {
    */
   bool is_span() const;
   /**
-   * Returns the internally used span of the virtual array. This invokes undefined behavior is the
+   * Returns the internally used span of the virtual array. This invokes undefined behavior if the
    * virtual array is not stored as a span internally.
    */
   GSpan get_internal_span() const;
-- 
cgit v1.2.3


From 4c66bd5da256779ef5a308d86abfe9b88f4a039b Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 24 Feb 2022 12:17:22 -0500
Subject: Curves: Use simpler "set" behavior for postion attribute

This is similar to f8fe0e831ec14cc521e03df, which made the change to the
handle position attributes. This commit removes the way that setting the
`position` attribute also changes the handle position attributes. Now,
the "Set Position" node still has this behavior, but changing the
attribute directly (with the modifier's output attributes) does not.

The previous behavior was a relic of the geometry nodes design
from before fields and the set position node existed.

This makes the transition to the new curves data structure simpler.
There is more room for optimizing the Bezier case of the set position
node in the future.
---
 .../blenkernel/intern/geometry_component_curve.cc  | 25 ++--------------
 .../nodes/geometry/nodes/node_geo_set_position.cc  | 35 ++++++++++++++++++++++
 2 files changed, 37 insertions(+), 23 deletions(-)

diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 9116ffc63a8..c22b6ff07ec 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -880,15 +880,7 @@ class VArrayImpl_For_SplinePosition final : public VMutableArrayImpl {
   {
     const PointIndices indices = lookup_point_indices(offsets_, index);
     Spline &spline = *splines_[indices.spline_index];
-    if (BezierSpline *bezier_spline = dynamic_cast(&spline)) {
-      const float3 delta = value - bezier_spline->positions()[indices.point_index];
-      bezier_spline->handle_positions_left()[indices.point_index] += delta;
-      bezier_spline->handle_positions_right()[indices.point_index] += delta;
-      bezier_spline->positions()[indices.point_index] = value;
-    }
-    else {
-      spline.positions()[indices.point_index] = value;
-    }
+    spline.positions()[indices.point_index] = value;
   }
 
   void set_all(Span src) final
@@ -897,20 +889,7 @@ class VArrayImpl_For_SplinePosition final : public VMutableArrayImpl {
       Spline &spline = *splines_[spline_index];
       const int offset = offsets_[spline_index];
       const int next_offset = offsets_[spline_index + 1];
-      if (BezierSpline *bezier_spline = dynamic_cast(&spline)) {
-        MutableSpan positions = bezier_spline->positions();
-        MutableSpan handle_positions_left = bezier_spline->handle_positions_left();
-        MutableSpan handle_positions_right = bezier_spline->handle_positions_right();
-        for (const int i : IndexRange(next_offset - offset)) {
-          const float3 delta = src[offset + i] - positions[i];
-          handle_positions_left[i] += delta;
-          handle_positions_right[i] += delta;
-          positions[i] = src[offset + i];
-        }
-      }
-      else {
-        spline.positions().copy_from(src.slice(offset, next_offset - offset));
-      }
+      spline.positions().copy_from(src.slice(offset, next_offset - offset));
     }
   }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
index 361a75ee0a8..eb035aa9b6b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
@@ -61,6 +61,41 @@ static void set_computed_position_and_offset(GeometryComponent &component,
       }
       break;
     }
+    case GEO_COMPONENT_TYPE_CURVE: {
+      if (component.attribute_exists("handle_right") &&
+          component.attribute_exists("handle_left")) {
+        OutputAttribute_Typed handle_right_attribute =
+            component.attribute_try_get_for_output(
+                "handle_right", ATTR_DOMAIN_POINT, {0, 0, 0});
+        OutputAttribute_Typed handle_left_attribute =
+            component.attribute_try_get_for_output(
+                "handle_left", ATTR_DOMAIN_POINT, {0, 0, 0});
+        MutableSpan handle_right = handle_right_attribute.as_span();
+        MutableSpan handle_left = handle_left_attribute.as_span();
+
+        MutableSpan out_positions_span = positions.as_span();
+        devirtualize_varray2(
+            in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) {
+              threading::parallel_for(
+                  selection.index_range(), grain_size, [&](const IndexRange range) {
+                    for (const int i : selection.slice(range)) {
+                      const float3 new_position = in_positions[i] + in_offsets[i];
+                      const float3 delta = new_position - out_positions_span[i];
+                      handle_right[i] += delta;
+                      handle_left[i] += delta;
+                      out_positions_span[i] = new_position;
+                    }
+                  });
+            });
+
+        handle_right_attribute.save();
+        handle_left_attribute.save();
+        break;
+      }
+      else {
+        ATTR_FALLTHROUGH;
+      }
+    }
     default: {
       MutableSpan out_positions_span = positions.as_span();
       if (in_positions.is_same(positions.varray())) {
-- 
cgit v1.2.3


From 6e396e21e666d4c614ded626455011b5a5011b1d Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Thu, 24 Feb 2022 19:20:03 +0100
Subject: Cleanup: remove unused ray offset function

---
 intern/cycles/kernel/bvh/util.h | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h
index 39c3ecd78c0..ec23f3a46fe 100644
--- a/intern/cycles/kernel/bvh/util.h
+++ b/intern/cycles/kernel/bvh/util.h
@@ -18,27 +18,6 @@
 
 CCL_NAMESPACE_BEGIN
 
-/* Ray offset to avoid self intersection.
- *
- * This function should be used to compute a modified ray start position for
- * rays leaving from a surface. This is from "A Fast and Robust Method for Avoiding
- * Self-Intersection" see https://research.nvidia.com/publication/2019-03_A-Fast-and
- */
-ccl_device_inline float3 ray_offset(float3 P, float3 Ng)
-{
-  const float int_scale = 256.0f;
-  int3 of_i = make_int3((int)(int_scale * Ng.x), (int)(int_scale * Ng.y), (int)(int_scale * Ng.z));
-
-  float3 p_i = make_float3(__int_as_float(__float_as_int(P.x) + ((P.x < 0) ? -of_i.x : of_i.x)),
-                           __int_as_float(__float_as_int(P.y) + ((P.y < 0) ? -of_i.y : of_i.y)),
-                           __int_as_float(__float_as_int(P.z) + ((P.z < 0) ? -of_i.z : of_i.z)));
-  const float origin = 1.0f / 32.0f;
-  const float float_scale = 1.0f / 65536.0f;
-  return make_float3(fabsf(P.x) < origin ? P.x + float_scale * Ng.x : p_i.x,
-                     fabsf(P.y) < origin ? P.y + float_scale * Ng.y : p_i.y,
-                     fabsf(P.z) < origin ? P.z + float_scale * Ng.z : p_i.z);
-}
-
 #if defined(__KERNEL_CPU__)
 ccl_device int intersections_compare(const void *a, const void *b)
 {
-- 
cgit v1.2.3


From 0781c22ceedc6700a073c620723270d32b2f2852 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Wed, 23 Feb 2022 18:44:25 +0100
Subject: Fix T95969, T91856: bake AO to vertex color artifacts after ray
 offset removal

Without ray offsets intersections at neigbhoring triangles are found, as
the ray start is exactly at the vertex. There was a small offset towards
the center of the triangle, but not enough.

Now this offset computation is moved into Cycles and modified for better
results. It's still not perfect though like any offset approach, especially
with long thin triangles.

Additionaly, this uses the shadow terminate offset for AO rays now, which
helps remove some pre-existing artifacts.
---
 intern/cycles/kernel/integrator/init_from_bake.h | 54 +++++++++++++++++++++++-
 intern/cycles/kernel/integrator/shade_surface.h  |  6 +--
 source/blender/editors/object/object_bake_api.c  | 15 +++----
 3 files changed, 61 insertions(+), 14 deletions(-)

diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h
index f4a2fbea405..717a4a0f3e2 100644
--- a/intern/cycles/kernel/integrator/init_from_bake.h
+++ b/intern/cycles/kernel/integrator/init_from_bake.h
@@ -43,6 +43,50 @@ ccl_device_inline float bake_clamp_mirror_repeat(float u, float max)
   return ((((int)fu) & 1) ? 1.0f - u : u) * max;
 }
 
+/* Offset towards center of triangle to avoid ray-tracing precision issues. */
+ccl_device const float2 bake_offset_towards_center(KernelGlobals kg,
+                                                   const int prim,
+                                                   const float u,
+                                                   const float v)
+{
+  float3 tri_verts[3];
+  triangle_vertices(kg, prim, tri_verts);
+
+  /* Empirically determined values, by no means perfect. */
+  const float position_offset = 1e-4f;
+  const float uv_offset = 1e-5f;
+
+  /* Offset position towards center, amount relative to absolute size of position coordinates. */
+  const float3 P = u * tri_verts[0] + v * tri_verts[1] + (1.0f - u - v) * tri_verts[2];
+  const float3 center = (tri_verts[0] + tri_verts[1] + tri_verts[2]) / 3.0f;
+  const float3 to_center = center - P;
+
+  const float3 offset_P = P + normalize(to_center) *
+                                  min(len(to_center), max(max3(fabs(P)), 1.0f) * position_offset);
+
+  /* Compute barycentric coordinates at new position. */
+  const float3 v1 = tri_verts[1] - tri_verts[0];
+  const float3 v2 = tri_verts[2] - tri_verts[0];
+  const float3 vP = offset_P - tri_verts[0];
+
+  const float d11 = dot(v1, v1);
+  const float d12 = dot(v1, v2);
+  const float d22 = dot(v2, v2);
+  const float dP1 = dot(vP, v1);
+  const float dP2 = dot(vP, v2);
+
+  const float denom = d11 * d22 - d12 * d12;
+  if (denom == 0.0f) {
+    return make_float2(0.0f, 0.0f);
+  }
+
+  const float offset_v = clamp((d22 * dP1 - d12 * dP2) / denom, uv_offset, 1.0f - uv_offset);
+  const float offset_w = clamp((d11 * dP2 - d12 * dP1) / denom, uv_offset, 1.0f - uv_offset);
+  const float offset_u = clamp(1.0f - offset_v - offset_w, uv_offset, 1.0f - uv_offset);
+
+  return make_float2(offset_u, offset_v);
+}
+
 /* Return false to indicate that this pixel is finished.
  * Used by CPU implementation to not attempt to sample pixel for multiple samples once its known
  * that the pixel did converge. */
@@ -100,7 +144,7 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg,
   /* Initialize path state for path integration. */
   path_state_init_integrator(kg, state, sample, rng_hash);
 
-  /* Barycentric UV with sub-pixel offset. */
+  /* Barycentric UV. */
   float u = primitive[2];
   float v = primitive[3];
 
@@ -109,6 +153,14 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg,
   float dvdx = differential[2];
   float dvdy = differential[3];
 
+  /* Exactly at vertex? Nudge inwards to avoid self-intersection. */
+  if ((u == 0.0f || u == 1.0f) && (v == 0.0f || v == 1.0f)) {
+    const float2 uv = bake_offset_towards_center(kg, prim, u, v);
+    u = uv.x;
+    v = uv.y;
+  }
+
+  /* Sub-pixel offset. */
   if (sample > 0) {
     u = bake_clamp_mirror_repeat(u + dudx * (filter_x - 0.5f) + dudy * (filter_y - 0.5f), 1.0f);
     v = bake_clamp_mirror_repeat(v + dvdx * (filter_x - 0.5f) + dvdy * (filter_y - 0.5f),
diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h
index 10d3cbf7f57..d3edfb0e05e 100644
--- a/intern/cycles/kernel/integrator/shade_surface.h
+++ b/intern/cycles/kernel/integrator/shade_surface.h
@@ -365,12 +365,8 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg,
   float ao_pdf;
   sample_cos_hemisphere(ao_N, bsdf_u, bsdf_v, &ao_D, &ao_pdf);
 
-  if (!(dot(sd->Ng, ao_D) > 0.0f && ao_pdf != 0.0f)) {
-    return;
-  }
-
   Ray ray ccl_optional_struct_init;
-  ray.P = sd->P;
+  ray.P = shadow_ray_offset(kg, sd, ao_D);
   ray.D = ao_D;
   ray.t = kernel_data.integrator.ao_bounces_distance;
   ray.time = sd->time;
diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c
index f52d2103fff..cef99017b9c 100644
--- a/source/blender/editors/object/object_bake_api.c
+++ b/source/blender/editors/object/object_bake_api.c
@@ -1054,19 +1054,18 @@ static void bake_targets_populate_pixels_vertex_colors(BakeTargets *targets,
        * materials and UVs. */
       pixel->seed = v;
 
-      /* Barycentric coordinates, nudged a bit to avoid precision issues that
-       * may happen when exactly at the vertex coordinate. */
+      /* Barycentric coordinates. */
       if (j == 0) {
-        pixel->uv[0] = 1.0f - FLT_EPSILON;
-        pixel->uv[1] = FLT_EPSILON / 2.0f;
+        pixel->uv[0] = 1.0f;
+        pixel->uv[1] = 0.0f;
       }
       else if (j == 1) {
-        pixel->uv[0] = FLT_EPSILON / 2.0f;
-        pixel->uv[1] = 1.0f - FLT_EPSILON;
+        pixel->uv[0] = 0.0f;
+        pixel->uv[1] = 1.0f;
       }
       else if (j == 2) {
-        pixel->uv[0] = FLT_EPSILON / 2.0f;
-        pixel->uv[1] = FLT_EPSILON / 2.0f;
+        pixel->uv[0] = 0.0f;
+        pixel->uv[1] = 0.0f;
       }
     }
   }
-- 
cgit v1.2.3


From 8fb7c50aabdf79d7c0f72a43c624cbbabbe146a7 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Thu, 24 Feb 2022 19:38:16 +0100
Subject: Fix compiler error in Windows

In some compilers this file fails because the function `BKE_mesh_poly_normals_ensure`is not defined.
---
 .../blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
index 1113087b264..59b8ceb1caa 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
@@ -4,6 +4,8 @@
 #include "DNA_meshdata_types.h"
 #include "node_geometry_util.hh"
 
+#include "BKE_mesh.h"
+
 namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc {
 
 static void node_declare(NodeDeclarationBuilder &b)
-- 
cgit v1.2.3


From 3b3e519edd98db52451dbdbbef21709c682901ce Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Thu, 24 Feb 2022 19:43:03 +0100
Subject: Nodes: Add include to avoid Windows compiler error

This line was added as request of @HooglyBoogly
---
 .../blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
index 59b8ceb1caa..e7509cf83ce 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
@@ -6,6 +6,8 @@
 
 #include "BKE_mesh.h"
 
+#include "node_geometry_util.hh"
+
 namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc {
 
 static void node_declare(NodeDeclarationBuilder &b)
-- 
cgit v1.2.3


From 5ccbbaed08dbbf9ac07017e8f8f6053ac0e1ed38 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Thu, 24 Feb 2022 19:44:07 +0100
Subject: Cleanup: Sort include lines

---
 .../blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc   | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
index e7509cf83ce..62af0476057 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
@@ -2,7 +2,6 @@
 
 #include "DNA_mesh_types.h"
 #include "DNA_meshdata_types.h"
-#include "node_geometry_util.hh"
 
 #include "BKE_mesh.h"
 
-- 
cgit v1.2.3


From c23ee6d7b4d6665aed4d0440153f0b1e80b77c8b Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 24 Feb 2022 14:02:32 -0500
Subject: Cleanup: Simplify operating on multiple geometry components

---
 .../geometry/nodes/node_geo_attribute_remove.cc    | 23 +++++---------
 .../geometry/nodes/node_geo_instance_on_points.cc  | 37 ++++++++--------------
 .../geometry/nodes/node_geo_points_to_volume.cc    | 17 ++++------
 3 files changed, 27 insertions(+), 50 deletions(-)

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 6424fccbe04..cb7132d5ea2 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
@@ -33,25 +33,18 @@ static void node_geo_exec(GeoNodeExecParams params)
   GeometrySet geometry_set = params.extract_input("Geometry");
   Vector attribute_names = params.extract_multi_input("Attribute");
 
-  if (geometry_set.has()) {
-    remove_attribute(
-        geometry_set.get_component_for_write(), params, attribute_names);
-  }
-  if (geometry_set.has()) {
-    remove_attribute(
-        geometry_set.get_component_for_write(), params, attribute_names);
-  }
-  if (geometry_set.has()) {
-    remove_attribute(
-        geometry_set.get_component_for_write(), params, attribute_names);
-  }
-  if (geometry_set.has()) {
-    remove_attribute(
-        geometry_set.get_component_for_write(), params, attribute_names);
+  for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_MESH,
+                                           GEO_COMPONENT_TYPE_POINT_CLOUD,
+                                           GEO_COMPONENT_TYPE_CURVE,
+                                           GEO_COMPONENT_TYPE_INSTANCES}) {
+    if (geometry_set.has(type)) {
+      remove_attribute(geometry_set.get_component_for_write(type), params, attribute_names);
+    }
   }
 
   params.set_output("Geometry", geometry_set);
 }
+
 }  // namespace blender::nodes::node_geo_attribute_remove_cc
 
 void register_node_type_geo_attribute_remove()
diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc
index df6d10991fb..61f719ade4e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc
@@ -195,35 +195,24 @@ static void node_geo_exec(GeoNodeExecParams params)
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
     InstancesComponent &instances = geometry_set.get_component_for_write();
 
+    const Array types{
+        GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
+
     Map attributes_to_propagate;
     geometry_set.gather_attributes_for_propagation(
-        {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE},
-        GEO_COMPONENT_TYPE_INSTANCES,
-        false,
-        attributes_to_propagate);
+        types, GEO_COMPONENT_TYPE_INSTANCES, false, attributes_to_propagate);
     attributes_to_propagate.remove("position");
 
-    if (geometry_set.has()) {
-      add_instances_from_component(instances,
-                                   *geometry_set.get_component_for_read(),
-                                   instance,
-                                   params,
-                                   attributes_to_propagate);
-    }
-    if (geometry_set.has()) {
-      add_instances_from_component(instances,
-                                   *geometry_set.get_component_for_read(),
-                                   instance,
-                                   params,
-                                   attributes_to_propagate);
-    }
-    if (geometry_set.has()) {
-      add_instances_from_component(instances,
-                                   *geometry_set.get_component_for_read(),
-                                   instance,
-                                   params,
-                                   attributes_to_propagate);
+    for (const GeometryComponentType type : types) {
+      if (geometry_set.has(type)) {
+        add_instances_from_component(instances,
+                                     *geometry_set.get_component_for_read(type),
+                                     instance,
+                                     params,
+                                     attributes_to_propagate);
+      }
     }
+
     geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
   });
 
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 1731ba64b97..c99b51ffd4c 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
@@ -198,17 +198,12 @@ static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms,
   Vector positions;
   Vector radii;
 
-  if (r_geometry_set.has()) {
-    gather_point_data_from_component(
-        params, *r_geometry_set.get_component_for_read(), positions, radii);
-  }
-  if (r_geometry_set.has()) {
-    gather_point_data_from_component(
-        params, *r_geometry_set.get_component_for_read(), positions, radii);
-  }
-  if (r_geometry_set.has()) {
-    gather_point_data_from_component(
-        params, *r_geometry_set.get_component_for_read(), positions, radii);
+  for (const GeometryComponentType type :
+       {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) {
+    if (r_geometry_set.has(type)) {
+      gather_point_data_from_component(
+          params, *r_geometry_set.get_component_for_read(type), positions, radii);
+    }
   }
 
   const float max_radius = *std::max_element(radii.begin(), radii.end());
-- 
cgit v1.2.3


From e59f754c169d855110296a365d93c33e82333385 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Thu, 24 Feb 2022 21:40:16 +0100
Subject: GPUTexture: Use immutable storage

This means textures need to have the number of mipmap levels specified
upfront. It does not mean the data is immutable.

There is fallback code for OpenGL < 4.2.

Immutable storage will enables texture views in the future.
---
 .../draw/engines/eevee/eevee_depth_of_field.c      |   5 -
 source/blender/draw/intern/DRW_gpu_wrapper.hh      |   5 -
 source/blender/editors/screen/glutil.c             |   3 +-
 source/blender/gpu/intern/gpu_texture.cc           |  26 ++--
 source/blender/gpu/intern/gpu_texture_private.hh   |   8 +-
 source/blender/gpu/opengl/gl_backend.cc            |   3 +
 source/blender/gpu/opengl/gl_context.hh            |   1 +
 source/blender/gpu/opengl/gl_texture.cc            | 140 +++++++++++----------
 source/blender/gpu/opengl/gl_texture.hh            |   2 -
 9 files changed, 100 insertions(+), 93 deletions(-)

diff --git a/source/blender/draw/engines/eevee/eevee_depth_of_field.c b/source/blender/draw/engines/eevee/eevee_depth_of_field.c
index 39cfbb40318..ef4d88bd521 100644
--- a/source/blender/draw/engines/eevee/eevee_depth_of_field.c
+++ b/source/blender/draw/engines/eevee/eevee_depth_of_field.c
@@ -626,11 +626,6 @@ static void dof_reduce_pass_init(EEVEE_FramebufferList *fbl,
         "dof_reduced_color", UNPACK2(res), mip_count, GPU_RGBA16F, NULL);
     txl->dof_reduced_coc = GPU_texture_create_2d(
         "dof_reduced_coc", UNPACK2(res), mip_count, GPU_R16F, NULL);
-
-    /* TODO(@fclem): Remove once we have immutable storage or when mips are generated on creation.
-     */
-    GPU_texture_generate_mipmap(txl->dof_reduced_color);
-    GPU_texture_generate_mipmap(txl->dof_reduced_coc);
   }
 
   GPU_framebuffer_ensure_config(&fbl->dof_reduce_fb,
diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh
index f387d5371b5..bce001659b2 100644
--- a/source/blender/draw/intern/DRW_gpu_wrapper.hh
+++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh
@@ -641,11 +641,6 @@ class Texture : NonCopyable {
     }
     if (tx_ == nullptr) {
       tx_ = create(w, h, d, mips, format, data, layered, cubemap);
-      if (mips > 1) {
-        /* TODO(@fclem): Remove once we have immutable storage or when mips are
-         * generated on creation. */
-        GPU_texture_generate_mipmap(tx_);
-      }
       return true;
     }
     return false;
diff --git a/source/blender/editors/screen/glutil.c b/source/blender/editors/screen/glutil.c
index f43bc08151a..8a84f4cf079 100644
--- a/source/blender/editors/screen/glutil.c
+++ b/source/blender/editors/screen/glutil.c
@@ -77,8 +77,9 @@ void immDrawPixelsTexScaledFullSize(const IMMDrawPixelsTexState *state,
    * filtering results. Mipmaps can be used to get better results (i.e. #GL_LINEAR_MIPMAP_LINEAR),
    * so always use mipmaps when filtering. */
   const bool use_mipmap = use_filter && ((draw_width < img_w) || (draw_height < img_h));
+  const int mips = use_mipmap ? 9999 : 1;
 
-  GPUTexture *tex = GPU_texture_create_2d("immDrawPixels", img_w, img_h, 1, gpu_format, NULL);
+  GPUTexture *tex = GPU_texture_create_2d("immDrawPixels", img_w, img_h, mips, gpu_format, NULL);
 
   const bool use_float_data = ELEM(gpu_format, GPU_RGBA16F, GPU_RGB16F, GPU_R16F);
   eGPUDataFormat gpu_data_format = (use_float_data) ? GPU_DATA_FLOAT : GPU_DATA_UBYTE;
diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc
index d84f420f35b..507ad47e36a 100644
--- a/source/blender/gpu/intern/gpu_texture.cc
+++ b/source/blender/gpu/intern/gpu_texture.cc
@@ -52,11 +52,13 @@ Texture::~Texture()
 #endif
 }
 
-bool Texture::init_1D(int w, int layers, eGPUTextureFormat format)
+bool Texture::init_1D(int w, int layers, int mips, eGPUTextureFormat format)
 {
   w_ = w;
   h_ = layers;
   d_ = 0;
+  int mips_max = 1 + floorf(log2f(w));
+  mipmaps_ = min_ii(mips, mips_max);
   format_ = format;
   format_flag_ = to_format_flag(format);
   type_ = (layers > 0) ? GPU_TEXTURE_1D_ARRAY : GPU_TEXTURE_1D;
@@ -66,11 +68,13 @@ bool Texture::init_1D(int w, int layers, eGPUTextureFormat format)
   return this->init_internal();
 }
 
-bool Texture::init_2D(int w, int h, int layers, eGPUTextureFormat format)
+bool Texture::init_2D(int w, int h, int layers, int mips, eGPUTextureFormat format)
 {
   w_ = w;
   h_ = h;
   d_ = layers;
+  int mips_max = 1 + floorf(log2f(max_ii(w, h)));
+  mipmaps_ = min_ii(mips, mips_max);
   format_ = format;
   format_flag_ = to_format_flag(format);
   type_ = (layers > 0) ? GPU_TEXTURE_2D_ARRAY : GPU_TEXTURE_2D;
@@ -80,11 +84,13 @@ bool Texture::init_2D(int w, int h, int layers, eGPUTextureFormat format)
   return this->init_internal();
 }
 
-bool Texture::init_3D(int w, int h, int d, eGPUTextureFormat format)
+bool Texture::init_3D(int w, int h, int d, int mips, eGPUTextureFormat format)
 {
   w_ = w;
   h_ = h;
   d_ = d;
+  int mips_max = 1 + floorf(log2f(max_iii(w, h, d)));
+  mipmaps_ = min_ii(mips, mips_max);
   format_ = format;
   format_flag_ = to_format_flag(format);
   type_ = GPU_TEXTURE_3D;
@@ -94,11 +100,13 @@ bool Texture::init_3D(int w, int h, int d, eGPUTextureFormat format)
   return this->init_internal();
 }
 
-bool Texture::init_cubemap(int w, int layers, eGPUTextureFormat format)
+bool Texture::init_cubemap(int w, int layers, int mips, eGPUTextureFormat format)
 {
   w_ = w;
   h_ = w;
   d_ = max_ii(1, layers) * 6;
+  int mips_max = 1 + floorf(log2f(w));
+  mipmaps_ = min_ii(mips, mips_max);
   format_ = format;
   format_flag_ = to_format_flag(format);
   type_ = (layers > 0) ? GPU_TEXTURE_CUBE_ARRAY : GPU_TEXTURE_CUBE;
@@ -198,18 +206,18 @@ static inline GPUTexture *gpu_texture_create(const char *name,
   switch (type) {
     case GPU_TEXTURE_1D:
     case GPU_TEXTURE_1D_ARRAY:
-      success = tex->init_1D(w, h, tex_format);
+      success = tex->init_1D(w, h, mips, tex_format);
       break;
     case GPU_TEXTURE_2D:
     case GPU_TEXTURE_2D_ARRAY:
-      success = tex->init_2D(w, h, d, tex_format);
+      success = tex->init_2D(w, h, d, mips, tex_format);
       break;
     case GPU_TEXTURE_3D:
-      success = tex->init_3D(w, h, d, tex_format);
+      success = tex->init_3D(w, h, d, mips, tex_format);
       break;
     case GPU_TEXTURE_CUBE:
     case GPU_TEXTURE_CUBE_ARRAY:
-      success = tex->init_cubemap(w, d, tex_format);
+      success = tex->init_cubemap(w, d, mips, tex_format);
       break;
     default:
       break;
@@ -287,7 +295,7 @@ GPUTexture *GPU_texture_create_compressed_2d(
     const char *name, int w, int h, int miplen, eGPUTextureFormat tex_format, const void *data)
 {
   Texture *tex = GPUBackend::get()->texture_alloc(name);
-  bool success = tex->init_2D(w, h, 0, tex_format);
+  bool success = tex->init_2D(w, h, 0, miplen, tex_format);
 
   if (!success) {
     delete tex;
diff --git a/source/blender/gpu/intern/gpu_texture_private.hh b/source/blender/gpu/intern/gpu_texture_private.hh
index 2c57c467bcf..b019235e463 100644
--- a/source/blender/gpu/intern/gpu_texture_private.hh
+++ b/source/blender/gpu/intern/gpu_texture_private.hh
@@ -101,10 +101,10 @@ class Texture {
   virtual ~Texture();
 
   /* Return true on success. */
-  bool init_1D(int w, int layers, eGPUTextureFormat format);
-  bool init_2D(int w, int h, int layers, eGPUTextureFormat format);
-  bool init_3D(int w, int h, int d, eGPUTextureFormat format);
-  bool init_cubemap(int w, int layers, eGPUTextureFormat format);
+  bool init_1D(int w, int layers, int mips, eGPUTextureFormat format);
+  bool init_2D(int w, int h, int layers, int mips, eGPUTextureFormat format);
+  bool init_3D(int w, int h, int d, int mips, eGPUTextureFormat format);
+  bool init_cubemap(int w, int layers, int mips, eGPUTextureFormat format);
   bool init_buffer(GPUVertBuf *vbo, eGPUTextureFormat format);
 
   virtual void generate_mipmap() = 0;
diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc
index 7a1674a5f01..302d8249914 100644
--- a/source/blender/gpu/opengl/gl_backend.cc
+++ b/source/blender/gpu/opengl/gl_backend.cc
@@ -240,6 +240,7 @@ static void detect_workarounds()
     GLContext::texture_cube_map_array_support = false;
     GLContext::texture_filter_anisotropic_support = false;
     GLContext::texture_gather_support = false;
+    GLContext::texture_storage_support = false;
     GLContext::vertex_attrib_binding_support = false;
     return;
   }
@@ -439,6 +440,7 @@ bool GLContext::shader_draw_parameters_support = false;
 bool GLContext::texture_cube_map_array_support = false;
 bool GLContext::texture_filter_anisotropic_support = false;
 bool GLContext::texture_gather_support = false;
+bool GLContext::texture_storage_support = false;
 bool GLContext::vertex_attrib_binding_support = false;
 
 /** Workarounds. */
@@ -501,6 +503,7 @@ void GLBackend::capabilities_init()
   GLContext::texture_cube_map_array_support = GLEW_ARB_texture_cube_map_array;
   GLContext::texture_filter_anisotropic_support = GLEW_EXT_texture_filter_anisotropic;
   GLContext::texture_gather_support = GLEW_ARB_texture_gather;
+  GLContext::texture_storage_support = GLEW_VERSION_4_3;
   GLContext::vertex_attrib_binding_support = GLEW_ARB_vertex_attrib_binding;
 
   detect_workarounds();
diff --git a/source/blender/gpu/opengl/gl_context.hh b/source/blender/gpu/opengl/gl_context.hh
index fe2ad0a4747..369b667cbcc 100644
--- a/source/blender/gpu/opengl/gl_context.hh
+++ b/source/blender/gpu/opengl/gl_context.hh
@@ -64,6 +64,7 @@ class GLContext : public Context {
   static bool texture_cube_map_array_support;
   static bool texture_filter_anisotropic_support;
   static bool texture_gather_support;
+  static bool texture_storage_support;
   static bool vertex_attrib_binding_support;
 
   /** Workarounds. */
diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc
index 22af2dd463f..37bf2f6b8b3 100644
--- a/source/blender/gpu/opengl/gl_texture.cc
+++ b/source/blender/gpu/opengl/gl_texture.cc
@@ -69,9 +69,79 @@ bool GLTexture::init_internal()
     return false;
   }
 
-  this->ensure_mipmaps(0);
+  GLenum internal_format = to_gl_internal_format(format_);
+  const bool is_cubemap = bool(type_ == GPU_TEXTURE_CUBE);
+  const bool is_layered = bool(type_ & GPU_TEXTURE_ARRAY);
+  const bool is_compressed = bool(format_flag_ & GPU_TEXTURE_ARRAY);
+  const int dimensions = (is_cubemap) ? 2 : this->dimensions_count();
+  GLenum gl_format = to_gl_data_format(format_);
+  GLenum gl_type = to_gl(to_data_format(format_));
 
-  /* Avoid issue with incomplete textures. */
+  auto mip_size = [&](int h, int w = 1, int d = 1) -> size_t {
+    return divide_ceil_u(w, 4) * divide_ceil_u(h, 4) * divide_ceil_u(d, 4) *
+           to_block_size(format_);
+  };
+  switch (dimensions) {
+    default:
+    case 1:
+      if (GLContext::texture_storage_support) {
+        glTexStorage1D(target_, mipmaps_, internal_format, w_);
+      }
+      else {
+        for (int i = 0, w = w_; i < mipmaps_; i++) {
+          if (is_compressed) {
+            glCompressedTexImage1D(target_, i, internal_format, w, 0, mip_size(w), nullptr);
+          }
+          else {
+            glTexImage1D(target_, i, internal_format, w, 0, gl_format, gl_type, nullptr);
+          }
+          w = max_ii(1, (w / 2));
+        }
+      }
+      break;
+    case 2:
+      if (GLContext::texture_storage_support) {
+        glTexStorage2D(target_, mipmaps_, internal_format, w_, h_);
+      }
+      else {
+        for (int i = 0, w = w_, h = h_; i < mipmaps_; i++) {
+          for (int f = 0; f < (is_cubemap ? 6 : 1); f++) {
+            GLenum target = (is_cubemap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + f : target_;
+            if (is_compressed) {
+              glCompressedTexImage2D(target, i, internal_format, w, h, 0, mip_size(w, h), nullptr);
+            }
+            else {
+              glTexImage2D(target, i, internal_format, w, h, 0, gl_format, gl_type, nullptr);
+            }
+          }
+          w = max_ii(1, (w / 2));
+          h = is_layered ? h_ : max_ii(1, (h / 2));
+        }
+      }
+      break;
+    case 3:
+      if (GLContext::texture_storage_support) {
+        glTexStorage3D(target_, mipmaps_, internal_format, w_, h_, d_);
+      }
+      else {
+        for (int i = 0, w = w_, h = h_, d = d_; i < mipmaps_; i++) {
+          if (is_compressed) {
+            glCompressedTexImage3D(
+                target_, i, internal_format, w, h, d, 0, mip_size(w, h, d), nullptr);
+          }
+          else {
+            glTexImage3D(target_, i, internal_format, w, h, d, 0, gl_format, gl_type, nullptr);
+          }
+          w = max_ii(1, (w / 2));
+          h = max_ii(1, (h / 2));
+          d = is_layered ? d_ : max_ii(1, (d / 2));
+        }
+      }
+      break;
+  }
+  this->mip_range_set(0, mipmaps_ - 1);
+
+  /* Avoid issue with formats not supporting filtering. Nearest by default. */
   if (GLContext::direct_state_access_support) {
     glTextureParameteri(tex_id_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   }
@@ -105,67 +175,6 @@ bool GLTexture::init_internal(GPUVertBuf *vbo)
   return true;
 }
 
-void GLTexture::ensure_mipmaps(int miplvl)
-{
-  int effective_h = (type_ == GPU_TEXTURE_1D_ARRAY) ? 0 : h_;
-  int effective_d = (type_ != GPU_TEXTURE_3D) ? 0 : d_;
-  int max_dimension = max_iii(w_, effective_h, effective_d);
-  int max_miplvl = floor(log2(max_dimension));
-  miplvl = min_ii(miplvl, max_miplvl);
-
-  while (mipmaps_ < miplvl) {
-    int mip = ++mipmaps_;
-    const int dimensions = this->dimensions_count();
-
-    int w = mip_width_get(mip);
-    int h = mip_height_get(mip);
-    int d = mip_depth_get(mip);
-    GLenum internal_format = to_gl_internal_format(format_);
-    GLenum gl_format = to_gl_data_format(format_);
-    GLenum gl_type = to_gl(to_data_format(format_));
-
-    GLContext::state_manager_active_get()->texture_bind_temp(this);
-
-    if (type_ == GPU_TEXTURE_CUBE) {
-      for (int i = 0; i < d; i++) {
-        GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
-        glTexImage2D(target, mip, internal_format, w, h, 0, gl_format, gl_type, nullptr);
-      }
-    }
-    else if (format_flag_ & GPU_FORMAT_COMPRESSED) {
-      size_t size = ((w + 3) / 4) * ((h + 3) / 4) * to_block_size(format_);
-      switch (dimensions) {
-        default:
-        case 1:
-          glCompressedTexImage1D(target_, mip, internal_format, w, 0, size, nullptr);
-          break;
-        case 2:
-          glCompressedTexImage2D(target_, mip, internal_format, w, h, 0, size, nullptr);
-          break;
-        case 3:
-          glCompressedTexImage3D(target_, mip, internal_format, w, h, d, 0, size, nullptr);
-          break;
-      }
-    }
-    else {
-      switch (dimensions) {
-        default:
-        case 1:
-          glTexImage1D(target_, mip, internal_format, w, 0, gl_format, gl_type, nullptr);
-          break;
-        case 2:
-          glTexImage2D(target_, mip, internal_format, w, h, 0, gl_format, gl_type, nullptr);
-          break;
-        case 3:
-          glTexImage3D(target_, mip, internal_format, w, h, d, 0, gl_format, gl_type, nullptr);
-          break;
-      }
-    }
-  }
-
-  this->mip_range_set(0, mipmaps_);
-}
-
 /** \} */
 
 /* -------------------------------------------------------------------- */
@@ -216,9 +225,7 @@ void GLTexture::update_sub(
   BLI_assert(validate_data_format(format_, type));
   BLI_assert(data != nullptr);
 
-  this->ensure_mipmaps(mip);
-
-  if (mip > mipmaps_) {
+  if (mip >= mipmaps_) {
     debug::raise_gl_error("Updating a miplvl on a texture too small to have this many levels.");
     return;
   }
@@ -283,7 +290,6 @@ void GLTexture::update_sub(
  */
 void GLTexture::generate_mipmap()
 {
-  this->ensure_mipmaps(9999);
   /* Some drivers have bugs when using #glGenerateMipmap with depth textures (see T56789).
    * In this case we just create a complete texture with mipmaps manually without
    * down-sampling. You must initialize the texture levels using other methods like
diff --git a/source/blender/gpu/opengl/gl_texture.hh b/source/blender/gpu/opengl/gl_texture.hh
index 07d3bb8c946..9e128da90e8 100644
--- a/source/blender/gpu/opengl/gl_texture.hh
+++ b/source/blender/gpu/opengl/gl_texture.hh
@@ -77,8 +77,6 @@ class GLTexture : public Texture {
 
  private:
   bool proxy_check(int mip);
-  /** Will create enough mipmaps up to get to the given level. */
-  void ensure_mipmaps(int mip);
   void update_sub_direct_state_access(
       int mip, int offset[3], int extent[3], GLenum gl_format, GLenum gl_type, const void *data);
   GPUFrameBuffer *framebuffer_get();
-- 
cgit v1.2.3


From 7d7dd66ba78fc97795edeb7c1a0221e4bb676c85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Thu, 24 Feb 2022 22:45:00 +0100
Subject: GPUTexture: Add support for texture view

This is an OpenGL 4.3 feature that enables creating a texture using a range
of the same data as another texture.
---
 source/blender/gpu/GPU_texture.h                 | 13 ++++++
 source/blender/gpu/intern/gpu_texture.cc         | 51 ++++++++++++++++++++++++
 source/blender/gpu/intern/gpu_texture_private.hh |  7 ++++
 source/blender/gpu/opengl/gl_texture.cc          | 22 ++++++++++
 source/blender/gpu/opengl/gl_texture.hh          |  2 +
 5 files changed, 95 insertions(+)

diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index d689fbe14b5..734d407d011 100644
--- a/source/blender/gpu/GPU_texture.h
+++ b/source/blender/gpu/GPU_texture.h
@@ -226,6 +226,19 @@ GPUTexture *GPU_texture_create_compressed_2d(
  * Create an error texture that will bind an invalid texture (pink) at draw time.
  */
 GPUTexture *GPU_texture_create_error(int dimension, bool array);
+/**
+ * Create an alias of the source texture data.
+ * If \a src is freed, the texture view will continue to be valid.
+ * If \a mip_start or \a mip_len is bigger than available mips they will be clamped.
+ * TODO(@fclem): Target conversion is not implemented yet.
+ */
+GPUTexture *GPU_texture_create_view(const char *name,
+                                    const GPUTexture *src,
+                                    eGPUTextureFormat format,
+                                    int mip_start,
+                                    int mip_len,
+                                    int layer_start,
+                                    int layer_len);
 
 void GPU_texture_update_mipmap(GPUTexture *tex,
                                int miplvl,
diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc
index 507ad47e36a..9e6a6f75391 100644
--- a/source/blender/gpu/intern/gpu_texture.cc
+++ b/source/blender/gpu/intern/gpu_texture.cc
@@ -131,6 +131,42 @@ bool Texture::init_buffer(GPUVertBuf *vbo, eGPUTextureFormat format)
   return this->init_internal(vbo);
 }
 
+bool Texture::init_view(const GPUTexture *src_,
+                        eGPUTextureFormat format,
+                        int mip_start,
+                        int mip_len,
+                        int layer_start,
+                        int layer_len)
+{
+  const Texture *src = unwrap(src_);
+  w_ = src->w_;
+  h_ = src->h_;
+  d_ = src->d_;
+  switch (type_) {
+    case GPU_TEXTURE_1D_ARRAY:
+      h_ = layer_len;
+      break;
+    case GPU_TEXTURE_CUBE_ARRAY:
+      BLI_assert(layer_len % 6 == 0);
+      ATTR_FALLTHROUGH;
+    case GPU_TEXTURE_2D_ARRAY:
+      d_ = layer_len;
+      break;
+    default:
+      BLI_assert(layer_len == 1 && layer_start == 0);
+      break;
+  }
+  mip_start = min_ii(mip_start, src->mipmaps_ - 1);
+  mip_len = min_ii(mip_len, (src->mipmaps_ - mip_start));
+  mipmaps_ = mip_len;
+  format_ = format;
+  format_flag_ = to_format_flag(format);
+  /* For now always copy the target. Target aliasing could be exposed later. */
+  type_ = src->type_;
+  sampler_state = src->sampler_state;
+  return this->init_internal(src_, mip_start, layer_start);
+}
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
@@ -343,6 +379,21 @@ GPUTexture *GPU_texture_create_error(int dimension, bool is_array)
   return gpu_texture_create("invalid_tex", w, h, d, type, 1, GPU_RGBA8, GPU_DATA_FLOAT, pixel);
 }
 
+GPUTexture *GPU_texture_create_view(const char *name,
+                                    const GPUTexture *src,
+                                    eGPUTextureFormat format,
+                                    int mip_start,
+                                    int mip_len,
+                                    int layer_start,
+                                    int layer_len)
+{
+  BLI_assert(mip_len > 0);
+  BLI_assert(layer_len > 0);
+  Texture *view = GPUBackend::get()->texture_alloc(name);
+  view->init_view(src, format, mip_start, mip_len, layer_start, layer_len);
+  return wrap(view);
+}
+
 /* ------ Update ------ */
 
 void GPU_texture_update_mipmap(GPUTexture *tex_,
diff --git a/source/blender/gpu/intern/gpu_texture_private.hh b/source/blender/gpu/intern/gpu_texture_private.hh
index b019235e463..ec11fab421c 100644
--- a/source/blender/gpu/intern/gpu_texture_private.hh
+++ b/source/blender/gpu/intern/gpu_texture_private.hh
@@ -106,6 +106,12 @@ class Texture {
   bool init_3D(int w, int h, int d, int mips, eGPUTextureFormat format);
   bool init_cubemap(int w, int layers, int mips, eGPUTextureFormat format);
   bool init_buffer(GPUVertBuf *vbo, eGPUTextureFormat format);
+  bool init_view(const GPUTexture *src,
+                 eGPUTextureFormat format,
+                 int mip_start,
+                 int mip_len,
+                 int layer_start,
+                 int layer_len);
 
   virtual void generate_mipmap() = 0;
   virtual void copy_to(Texture *tex) = 0;
@@ -234,6 +240,7 @@ class Texture {
  protected:
   virtual bool init_internal() = 0;
   virtual bool init_internal(GPUVertBuf *vbo) = 0;
+  virtual bool init_internal(const GPUTexture *src, int mip_offset, int layer_offset) = 0;
 };
 
 /* Syntactic sugar. */
diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc
index 37bf2f6b8b3..0a5c7f8e79e 100644
--- a/source/blender/gpu/opengl/gl_texture.cc
+++ b/source/blender/gpu/opengl/gl_texture.cc
@@ -175,6 +175,28 @@ bool GLTexture::init_internal(GPUVertBuf *vbo)
   return true;
 }
 
+bool GLTexture::init_internal(const GPUTexture *src, int mip_offset, int layer_offset)
+{
+  BLI_assert(GLContext::texture_storage_support);
+
+  const GLTexture *gl_src = static_cast(unwrap(src));
+  GLenum internal_format = to_gl_internal_format(format_);
+  target_ = to_gl_target(type_);
+
+  glTextureView(tex_id_,
+                target_,
+                gl_src->tex_id_,
+                internal_format,
+                mip_offset,
+                mipmaps_,
+                layer_offset,
+                this->layer_count());
+
+  debug::object_label(GL_TEXTURE, tex_id_, name_);
+
+  return true;
+}
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
diff --git a/source/blender/gpu/opengl/gl_texture.hh b/source/blender/gpu/opengl/gl_texture.hh
index 9e128da90e8..d4d024f5e3e 100644
--- a/source/blender/gpu/opengl/gl_texture.hh
+++ b/source/blender/gpu/opengl/gl_texture.hh
@@ -74,6 +74,8 @@ class GLTexture : public Texture {
   bool init_internal() override;
   /** Return true on success. */
   bool init_internal(GPUVertBuf *vbo) override;
+  /** Return true on success. */
+  bool init_internal(const GPUTexture *src, int mip_offset, int layer_offset) override;
 
  private:
   bool proxy_check(int mip);
-- 
cgit v1.2.3


From e7cae5187773f41e62830be597c6f598bff0653f Mon Sep 17 00:00:00 2001
From: YimingWu 
Date: Fri, 25 Feb 2022 13:50:23 +0800
Subject: Fix T95984: Use consistent shifting for persp and ortho cam.

Now always properly shifting camera for ortho and perspective.
---
 .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 53 ++++++++++------------
 1 file changed, 25 insertions(+), 28 deletions(-)

diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
index 31dd37db1a7..2f648d7ed4b 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -1388,21 +1388,20 @@ static void lineart_main_perspective_division(LineartRenderBuffer *rb)
   LineartVert *vt;
   int i;
 
-  if (!rb->cam_is_persp) {
-    return;
-  }
-
   LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->vertex_buffer_pointers) {
     vt = eln->pointer;
     for (i = 0; i < eln->element_count; i++) {
-      /* Do not divide Z, we use Z to back transform cut points in later chaining process. */
-      vt[i].fbcoord[0] /= vt[i].fbcoord[3];
-      vt[i].fbcoord[1] /= vt[i].fbcoord[3];
-      /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates)
-       * at the moment.
-       * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed in
-       * the future, the line below correctly transforms it to view space coordinates. */
-      // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near);
+      if (rb->cam_is_persp) {
+        /* Do not divide Z, we use Z to back transform cut points in later chaining process. */
+        vt[i].fbcoord[0] /= vt[i].fbcoord[3];
+        vt[i].fbcoord[1] /= vt[i].fbcoord[3];
+        /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates)
+         * at the moment.
+         * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed
+         * in the future, the line below correctly transforms it to view space coordinates. */
+        // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near);
+      }
+      /* Shifting is always needed. */
       vt[i].fbcoord[0] -= rb->shift_x * 2;
       vt[i].fbcoord[1] -= rb->shift_y * 2;
     }
@@ -2521,19 +2520,16 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl),
     interp_v3_v3v3_db(gloc, e->v1->gloc, e->v2->gloc, cut);
     mul_v4_m4v3_db(trans, vp, gloc);
     mul_v3db_db(trans, (1 / trans[3]));
-  }
-  else {
-    interp_v3_v3v3_db(trans, e->v1->fbcoord, e->v2->fbcoord, cut);
-  }
-  trans[0] -= cam_shift_x * 2;
-  trans[1] -= cam_shift_y * 2;
-
-  /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */
-  if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) > fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) {
-    cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]);
-  }
-  else {
-    cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]);
+    trans[0] -= cam_shift_x * 2;
+    trans[1] -= cam_shift_y * 2;
+    /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */
+    if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) >
+        fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) {
+      cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]);
+    }
+    else {
+      cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]);
+    }
   }
 
 #define LRT_GUARD_NOT_FOUND \
@@ -2959,9 +2955,10 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb,
    * them as well. */
   mul_v4_m4v3_db(v1->fbcoord, rb->view_projection, v1->gloc);
   mul_v4_m4v3_db(v2->fbcoord, rb->view_projection, v2->gloc);
-  mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3]));
-  mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3]));
-
+  if (rb->cam_is_persp) {
+    mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3]));
+    mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3]));
+  }
   v1->fbcoord[0] -= rb->shift_x * 2;
   v1->fbcoord[1] -= rb->shift_y * 2;
   v2->fbcoord[0] -= rb->shift_x * 2;
-- 
cgit v1.2.3


From 60af7a349614f9ebe88ebb71f4c8fad14a0e274f Mon Sep 17 00:00:00 2001
From: YimingWu 
Date: Fri, 25 Feb 2022 13:50:23 +0800
Subject: Fix T95984: Use consistent shifting for persp and ortho cam.

Now always properly shifting camera for ortho and perspective.
---
 .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 53 ++++++++++------------
 1 file changed, 25 insertions(+), 28 deletions(-)

diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
index 19548f6ffa3..431cbb79238 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -1404,21 +1404,20 @@ static void lineart_main_perspective_division(LineartRenderBuffer *rb)
   LineartVert *vt;
   int i;
 
-  if (!rb->cam_is_persp) {
-    return;
-  }
-
   LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->vertex_buffer_pointers) {
     vt = eln->pointer;
     for (i = 0; i < eln->element_count; i++) {
-      /* Do not divide Z, we use Z to back transform cut points in later chaining process. */
-      vt[i].fbcoord[0] /= vt[i].fbcoord[3];
-      vt[i].fbcoord[1] /= vt[i].fbcoord[3];
-      /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates)
-       * at the moment.
-       * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed in
-       * the future, the line below correctly transforms it to view space coordinates. */
-      // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near);
+      if (rb->cam_is_persp) {
+        /* Do not divide Z, we use Z to back transform cut points in later chaining process. */
+        vt[i].fbcoord[0] /= vt[i].fbcoord[3];
+        vt[i].fbcoord[1] /= vt[i].fbcoord[3];
+        /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates)
+         * at the moment.
+         * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed
+         * in the future, the line below correctly transforms it to view space coordinates. */
+        // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near);
+      }
+      /* Shifting is always needed. */
       vt[i].fbcoord[0] -= rb->shift_x * 2;
       vt[i].fbcoord[1] -= rb->shift_y * 2;
     }
@@ -2518,19 +2517,16 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl),
     interp_v3_v3v3_db(gloc, e->v1->gloc, e->v2->gloc, cut);
     mul_v4_m4v3_db(trans, vp, gloc);
     mul_v3db_db(trans, (1 / trans[3]));
-  }
-  else {
-    interp_v3_v3v3_db(trans, e->v1->fbcoord, e->v2->fbcoord, cut);
-  }
-  trans[0] -= cam_shift_x * 2;
-  trans[1] -= cam_shift_y * 2;
-
-  /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */
-  if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) > fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) {
-    cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]);
-  }
-  else {
-    cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]);
+    trans[0] -= cam_shift_x * 2;
+    trans[1] -= cam_shift_y * 2;
+    /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */
+    if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) >
+        fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) {
+      cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]);
+    }
+    else {
+      cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]);
+    }
   }
 
 #define LRT_GUARD_NOT_FOUND \
@@ -2956,9 +2952,10 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb,
    * them as well. */
   mul_v4_m4v3_db(v1->fbcoord, rb->view_projection, v1->gloc);
   mul_v4_m4v3_db(v2->fbcoord, rb->view_projection, v2->gloc);
-  mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3]));
-  mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3]));
-
+  if (rb->cam_is_persp) {
+    mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3]));
+    mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3]));
+  }
   v1->fbcoord[0] -= rb->shift_x * 2;
   v1->fbcoord[1] -= rb->shift_y * 2;
   v2->fbcoord[0] -= rb->shift_x * 2;
-- 
cgit v1.2.3


From ad0b3abf539bbb358f799d3f36649b5d46f222c8 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 24 Feb 2022 22:48:34 +1100
Subject: Cleanup: use flags for wmEvent modifier keys

Using flags makes checking multiple modifiers at once more convenient
and avoids macros/functions such as IS_EVENT_MOD & WM_event_modifier_flag
which have been removed. It also simplifies checking if modifier keys
have changed.
---
 source/blender/editors/animation/anim_markers.c    |   8 +-
 source/blender/editors/armature/pose_edit.c        |   2 +-
 source/blender/editors/curve/editfont.c            |   4 +-
 source/blender/editors/gpencil/annotate_paint.c    |   6 +-
 source/blender/editors/gpencil/gpencil_fill.c      |   7 +-
 source/blender/editors/gpencil/gpencil_paint.c     |  20 +--
 source/blender/editors/gpencil/gpencil_primitive.c |  14 +-
 .../blender/editors/gpencil/gpencil_sculpt_paint.c |   4 +-
 source/blender/editors/gpencil/gpencil_select.c    |   2 +-
 .../blender/editors/gpencil/gpencil_vertex_paint.c |   2 +-
 .../blender/editors/gpencil/gpencil_weight_paint.c |   2 +-
 source/blender/editors/include/ED_util.h           |   2 +-
 .../interface/interface_eyedropper_gpencil_color.c |   6 +-
 .../blender/editors/interface/interface_handlers.c | 175 ++++++++++-----------
 .../blender/editors/interface/interface_layout.c   |   4 +-
 source/blender/editors/interface/interface_panel.c |  19 ++-
 source/blender/editors/interface/interface_query.c |   2 +-
 .../editors/interface/interface_region_tooltip.c   |   2 +-
 .../editors/interface/interface_templates.c        |   6 +-
 source/blender/editors/mesh/editmesh_bevel.c       |   2 +-
 source/blender/editors/mesh/editmesh_loopcut.c     |   6 +-
 source/blender/editors/mesh/editmesh_select.c      |   4 +-
 source/blender/editors/object/object_edit.c        |   6 +-
 source/blender/editors/object/object_remesh.cc     |   2 +-
 source/blender/editors/object/object_transform.c   |   2 +-
 source/blender/editors/physics/particle_edit.c     |   2 +-
 source/blender/editors/sculpt_paint/paint_stroke.c |   2 +-
 .../editors/sculpt_paint/sculpt_mask_expand.c      |   2 +-
 source/blender/editors/space_action/action_data.c  |   2 +-
 source/blender/editors/space_buttons/buttons_ops.c |   4 +-
 source/blender/editors/space_console/console_ops.c |   2 +-
 source/blender/editors/space_nla/nla_channels.c    |   2 +-
 .../editors/space_outliner/outliner_collections.cc |   2 +-
 .../editors/space_outliner/outliner_dragdrop.cc    |  33 ++--
 .../editors/space_outliner/outliner_draw.cc        |  16 +-
 .../blender/editors/space_text/text_autocomplete.c |   4 +-
 source/blender/editors/space_text/text_ops.c       |   2 +-
 .../editors/space_view3d/view3d_cursor_snap.c      |  25 +--
 .../space_view3d/view3d_gizmo_preselect_type.c     |   4 +-
 source/blender/editors/transform/transform.c       |  18 ++-
 .../blender/editors/transform/transform_gizmo_3d.c |   2 +-
 source/blender/editors/transform/transform_snap.c  |   3 +-
 source/blender/editors/util/ed_util.c              |   2 +-
 source/blender/editors/util/numinput.c             |  24 +--
 .../blender/editors/uvedit/uvedit_smart_stitch.c   |   6 +-
 source/blender/makesrna/intern/rna_wm.c            |   8 +-
 source/blender/makesrna/intern/rna_wm_api.c        |  17 +-
 source/blender/windowmanager/WM_api.h              |   2 -
 source/blender/windowmanager/WM_types.h            |   9 +-
 .../windowmanager/gizmo/intern/wm_gizmo_map.c      |   6 +-
 .../blender/windowmanager/intern/wm_event_query.c  |  30 +---
 .../blender/windowmanager/intern/wm_event_system.c |  84 ++++++----
 .../windowmanager/intern/wm_operator_utils.c       |   8 +-
 source/blender/windowmanager/intern/wm_operators.c |   2 +-
 source/blender/windowmanager/intern/wm_window.c    |  21 ++-
 source/blender/windowmanager/wm_event_types.h      |   9 --
 56 files changed, 340 insertions(+), 322 deletions(-)

diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c
index 9566402ad85..ab51702d628 100644
--- a/source/blender/editors/animation/anim_markers.c
+++ b/source/blender/editors/animation/anim_markers.c
@@ -960,7 +960,13 @@ static int ed_marker_move_modal(bContext *C, wmOperator *op, const wmEvent *even
             mm->evtx = event->xy[0];
             fac = ((float)(event->xy[0] - mm->firstx) * dx);
 
-            apply_keyb_grid(event->shift, event->ctrl, &fac, 0.0, FPS, 0.1 * FPS, 0);
+            apply_keyb_grid((event->modifier & KM_SHIFT) != 0,
+                            (event->modifier & KM_CTRL) != 0,
+                            &fac,
+                            0.0,
+                            FPS,
+                            0.1 * FPS,
+                            0);
 
             RNA_int_set(op->ptr, "frames", (int)fac);
             ed_marker_move_apply(C, op);
diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c
index e227e69f9e1..13abcefa632 100644
--- a/source/blender/editors/armature/pose_edit.c
+++ b/source/blender/editors/armature/pose_edit.c
@@ -427,7 +427,7 @@ static int pose_clear_paths_exec(bContext *C, wmOperator *op)
 /* operator callback/wrapper */
 static int pose_clear_paths_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
-  if ((event->shift) && !RNA_struct_property_is_set(op->ptr, "only_selected")) {
+  if ((event->modifier & KM_SHIFT) && !RNA_struct_property_is_set(op->ptr, "only_selected")) {
     RNA_boolean_set(op->ptr, "only_selected", true);
   }
   return pose_clear_paths_exec(C, op);
diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c
index b2af0643e2c..02c7f3856e8 100644
--- a/source/blender/editors/curve/editfont.c
+++ b/source/blender/editors/curve/editfont.c
@@ -1640,7 +1640,9 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   EditFont *ef = cu->editfont;
   static int accentcode = 0;
   uintptr_t ascii = event->ascii;
-  int alt = event->alt, shift = event->shift, ctrl = event->ctrl;
+  const bool alt = event->modifier & KM_ALT;
+  const bool shift = event->modifier & KM_SHIFT;
+  const bool ctrl = event->modifier & KM_CTRL;
   int event_type = event->type, event_val = event->val;
   char32_t inserted_text[2] = {0};
 
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index 163b5657326..b40dda146dc 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -2060,7 +2060,7 @@ static void annotation_draw_apply_event(
   p->mval[1] = (float)event->mval[1] - y;
 
   /* Key to toggle stabilization. */
-  if (event->shift && p->paintmode == GP_PAINTMODE_DRAW) {
+  if ((event->modifier & KM_SHIFT) && (p->paintmode == GP_PAINTMODE_DRAW)) {
     /* Using permanent stabilization, shift will deactivate the flag. */
     if (p->flags & GP_PAINTFLAG_USE_STABILIZER) {
       if (p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) {
@@ -2075,7 +2075,7 @@ static void annotation_draw_apply_event(
     }
   }
   /* verify key status for straight lines */
-  else if (event->ctrl || event->alt) {
+  else if (event->modifier & (KM_CTRL | KM_ALT)) {
     if (p->straight[0] == 0) {
       int dx = abs((int)(p->mval[0] - p->mvalo[0]));
       int dy = abs((int)(p->mval[1] - p->mvalo[1]));
@@ -2299,7 +2299,7 @@ static int annotation_draw_invoke(bContext *C, wmOperator *op, const wmEvent *ev
       p->flags |= GP_PAINTFLAG_USE_STABILIZER | GP_PAINTFLAG_USE_STABILIZER_TEMP;
       annotation_draw_toggle_stabilizer_cursor(p, true);
     }
-    else if (event->shift) {
+    else if (event->modifier & KM_SHIFT) {
       p->flags |= GP_PAINTFLAG_USE_STABILIZER_TEMP;
       annotation_draw_toggle_stabilizer_cursor(p, true);
     }
diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 8e5161b3245..32dc086099c 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -2172,7 +2172,8 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
   tgpf->on_back = RNA_boolean_get(op->ptr, "on_back");
 
   const bool is_brush_inv = brush_settings->fill_direction == BRUSH_DIR_IN;
-  const bool is_inverted = (is_brush_inv && !event->ctrl) || (!is_brush_inv && event->ctrl);
+  const bool is_inverted = (is_brush_inv && (event->modifier & KM_CTRL) == 0) ||
+                           (!is_brush_inv && (event->modifier & KM_CTRL) != 0);
   const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(tgpf->gpd);
   const bool do_extend = (tgpf->fill_extend_fac > 0.0f);
   const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) ||
@@ -2313,7 +2314,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
     case EVT_PAGEUPKEY:
     case WHEELUPMOUSE:
       if (tgpf->oldkey == 1) {
-        tgpf->fill_extend_fac -= (event->shift) ? 0.01f : 0.1f;
+        tgpf->fill_extend_fac -= (event->modifier & KM_SHIFT) ? 0.01f : 0.1f;
         CLAMP_MIN(tgpf->fill_extend_fac, 0.0f);
         gpencil_update_extend(tgpf);
       }
@@ -2321,7 +2322,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
     case EVT_PAGEDOWNKEY:
     case WHEELDOWNMOUSE:
       if (tgpf->oldkey == 1) {
-        tgpf->fill_extend_fac += (event->shift) ? 0.01f : 0.1f;
+        tgpf->fill_extend_fac += (event->modifier & KM_SHIFT) ? 0.01f : 0.1f;
         CLAMP_MAX(tgpf->fill_extend_fac, 100.0f);
         gpencil_update_extend(tgpf);
       }
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index a6691a12505..9442b2edc13 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -235,7 +235,7 @@ typedef struct tGPsdata {
   /** key used for invoking the operator */
   short keymodifier;
   /** shift modifier flag */
-  short shift;
+  bool shift;
   /** size in pixels for uv calculation */
   float totpixlen;
   /** Special mode for fill brush. */
@@ -2841,11 +2841,11 @@ static void gpencil_draw_apply_event(bContext *C,
    * add any x,y override position
    */
   copy_v2fl_v2i(p->mval, event->mval);
-  p->shift = event->shift;
+  p->shift = (event->modifier & KM_SHIFT) != 0;
 
   /* verify direction for straight lines and guides */
   if ((is_speed_guide) ||
-      (event->alt && (RNA_boolean_get(op->ptr, "disable_straight") == false))) {
+      ((event->modifier & KM_ALT) && (RNA_boolean_get(op->ptr, "disable_straight") == false))) {
     if (p->straight == 0) {
       int dx = (int)fabsf(p->mval[0] - p->mvali[0]);
       int dy = (int)fabsf(p->mval[1] - p->mvali[1]);
@@ -2886,13 +2886,13 @@ static void gpencil_draw_apply_event(bContext *C,
 
   /* special eraser modes */
   if (p->paintmode == GP_PAINTMODE_ERASER) {
-    if (event->shift) {
+    if (event->modifier & KM_SHIFT) {
       p->flags |= GP_PAINTFLAG_HARD_ERASER;
     }
     else {
       p->flags &= ~GP_PAINTFLAG_HARD_ERASER;
     }
-    if (event->alt) {
+    if (event->modifier & KM_ALT) {
       p->flags |= GP_PAINTFLAG_STROKE_ERASER;
     }
     else {
@@ -3116,11 +3116,11 @@ static void gpencil_guide_event_handling(bContext *C,
   else if ((event->type == EVT_LKEY) && (event->val == KM_RELEASE)) {
     add_notifier = true;
     guide->use_guide = true;
-    if (event->ctrl) {
+    if (event->modifier & KM_CTRL) {
       guide->angle = 0.0f;
       guide->type = GP_GUIDE_PARALLEL;
     }
-    else if (event->alt) {
+    else if (event->modifier & KM_ALT) {
       guide->type = GP_GUIDE_PARALLEL;
       guide->angle = RNA_float_get(op->ptr, "guide_last_angle");
     }
@@ -3150,10 +3150,10 @@ static void gpencil_guide_event_handling(bContext *C,
     add_notifier = true;
     float angle = guide->angle;
     float adjust = (float)M_PI / 180.0f;
-    if (event->alt) {
+    if (event->modifier & KM_ALT) {
       adjust *= 45.0f;
     }
-    else if (!event->shift) {
+    else if ((event->modifier & KM_SHIFT) == 0) {
       adjust *= 15.0f;
     }
     angle += (event->type == EVT_JKEY) ? adjust : -adjust;
@@ -3633,7 +3633,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
        */
     }
     else if (event->type == EVT_ZKEY) {
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         p->status = GP_STATUS_DONE;
         estate = OPERATOR_FINISHED;
       }
diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c
index 2d761dd6c91..57a184b0e8d 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -1494,7 +1494,7 @@ static void gpencil_primitive_edit_event_handling(
           float dy = (tgpi->mval[1] - tgpi->mvalo[1]);
           tgpi->cp1[0] += dx;
           tgpi->cp1[1] += dy;
-          if (event->shift) {
+          if (event->modifier & KM_SHIFT) {
             copy_v2_v2(tgpi->cp2, tgpi->cp1);
           }
         }
@@ -1503,7 +1503,7 @@ static void gpencil_primitive_edit_event_handling(
           float dy = (tgpi->mval[1] - tgpi->mvalo[1]);
           tgpi->cp2[0] += dx;
           tgpi->cp2[1] += dy;
-          if (event->shift) {
+          if (event->modifier & KM_SHIFT) {
             copy_v2_v2(tgpi->cp1, tgpi->cp2);
           }
         }
@@ -1692,7 +1692,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
         WM_cursor_modal_set(win, WM_CURSOR_NSEW_SCROLL);
         copy_v2_v2(tgpi->end, tgpi->mval);
 
-        if (event->shift) {
+        if (event->modifier & KM_SHIFT) {
           gpencil_primitive_constrain(tgpi, true);
         }
 
@@ -1722,7 +1722,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
       case EVT_FKEY: /* brush thickness/ brush strength */
       {
         if ((event->val == KM_PRESS)) {
-          if (event->shift) {
+          if (event->modifier & KM_SHIFT) {
             tgpi->prev_flag = tgpi->flag;
             tgpi->flag = IN_BRUSH_STRENGTH;
           }
@@ -1900,7 +1900,7 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
     case EVT_FKEY: /* brush thickness/ brush strength */
     {
       if ((event->val == KM_PRESS)) {
-        if (event->shift) {
+        if (event->modifier & KM_SHIFT) {
           tgpi->prev_flag = tgpi->flag;
           tgpi->flag = IN_BRUSH_STRENGTH;
         }
@@ -1954,12 +1954,12 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
           copy_v2_v2(tgpi->origin, tgpi->mval);
         }
         /* Keep square if shift key */
-        if (event->shift) {
+        if (event->modifier & KM_SHIFT) {
           gpencil_primitive_constrain(
               tgpi, (ELEM(tgpi->type, GP_STROKE_LINE, GP_STROKE_POLYLINE) || tgpi->curve));
         }
         /* Center primitive if alt key */
-        if (event->alt && !ELEM(tgpi->type, GP_STROKE_POLYLINE)) {
+        if ((event->modifier & KM_ALT) && !ELEM(tgpi->type, GP_STROKE_POLYLINE)) {
           tgpi->start[0] = tgpi->origin[0] - (tgpi->end[0] - tgpi->origin[0]);
           tgpi->start[1] = tgpi->origin[1] - (tgpi->end[1] - tgpi->origin[1]);
         }
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index e8f097d0018..ebe879959c9 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -1883,7 +1883,7 @@ static void gpencil_sculpt_brush_apply_event(bContext *C, wmOperator *op, const
   RNA_collection_add(op->ptr, "stroke", &itemptr);
 
   RNA_float_set_array(&itemptr, "mouse", mouse);
-  RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
+  RNA_boolean_set(&itemptr, "pen_flip", (event->modifier & KM_CTRL) != 0);
   RNA_boolean_set(&itemptr, "is_start", gso->first);
 
   /* handle pressure sensitivity (which is supplied by tablets and otherwise 1.0) */
@@ -1895,7 +1895,7 @@ static void gpencil_sculpt_brush_apply_event(bContext *C, wmOperator *op, const
   }
   RNA_float_set(&itemptr, "pressure", pressure);
 
-  if (event->shift) {
+  if (event->modifier & KM_SHIFT) {
     gso->brush_prev = gso->brush;
 
     gso->brush = gpencil_sculpt_get_smooth_brush(gso);
diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c
index da263d44fc6..fca4ff84dc5 100644
--- a/source/blender/editors/gpencil/gpencil_select.c
+++ b/source/blender/editors/gpencil/gpencil_select.c
@@ -2659,7 +2659,7 @@ static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *eve
   RNA_int_set_array(op->ptr, "location", event->mval);
 
   if (!RNA_struct_property_is_set(op->ptr, "use_shift_extend")) {
-    RNA_boolean_set(op->ptr, "use_shift_extend", event->shift);
+    RNA_boolean_set(op->ptr, "use_shift_extend", event->modifier & KM_SHIFT);
   }
 
   return gpencil_select_exec(C, op);
diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c
index 1d1b00e3f08..244942a87ba 100644
--- a/source/blender/editors/gpencil/gpencil_vertex_paint.c
+++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c
@@ -1241,7 +1241,7 @@ static void gpencil_vertexpaint_brush_apply_event(bContext *C,
   RNA_collection_add(op->ptr, "stroke", &itemptr);
 
   RNA_float_set_array(&itemptr, "mouse", mouse);
-  RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
+  RNA_boolean_set(&itemptr, "pen_flip", event->modifier & KM_CTRL);
   RNA_boolean_set(&itemptr, "is_start", gso->first);
 
   /* Handle pressure sensitivity (which is supplied by tablets). */
diff --git a/source/blender/editors/gpencil/gpencil_weight_paint.c b/source/blender/editors/gpencil/gpencil_weight_paint.c
index 8fe4cba7021..01e73cd2abd 100644
--- a/source/blender/editors/gpencil/gpencil_weight_paint.c
+++ b/source/blender/editors/gpencil/gpencil_weight_paint.c
@@ -698,7 +698,7 @@ static void gpencil_weightpaint_brush_apply_event(bContext *C,
   RNA_collection_add(op->ptr, "stroke", &itemptr);
 
   RNA_float_set_array(&itemptr, "mouse", mouse);
-  RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
+  RNA_boolean_set(&itemptr, "pen_flip", event->modifier & KM_CTRL);
   RNA_boolean_set(&itemptr, "is_start", gso->first);
 
   /* Handle pressure sensitivity (which is supplied by tablets). */
diff --git a/source/blender/editors/include/ED_util.h b/source/blender/editors/include/ED_util.h
index bd3a6bce8e8..f235f696ccc 100644
--- a/source/blender/editors/include/ED_util.h
+++ b/source/blender/editors/include/ED_util.h
@@ -106,7 +106,7 @@ void ED_slider_allow_overshoot_set(struct tSlider *slider, bool value);
  * \note Shift/Control are not configurable key-bindings.
  */
 void apply_keyb_grid(
-    int shift, int ctrl, float *val, float fac1, float fac2, float fac3, int invert);
+    bool shift, bool ctrl, float *val, float fac1, float fac2, float fac3, int invert);
 
 /* where else to go ? */
 void unpack_menu(struct bContext *C,
diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
index d6f529f9f94..f3c70e6a96a 100644
--- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
+++ b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
@@ -222,9 +222,9 @@ static void eyedropper_add_palette_color(bContext *C, const float col_conv[4])
 static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye)
 {
 
-  const bool only_stroke = ((!event->ctrl) && (!event->shift));
-  const bool only_fill = ((!event->ctrl) && (event->shift));
-  const bool both = ((event->ctrl) && (event->shift));
+  const bool only_stroke = (event->modifier & (KM_CTRL | KM_SHIFT)) == 0;
+  const bool only_fill = ((event->modifier & KM_CTRL) == 0 && (event->modifier & KM_SHIFT));
+  const bool both = ((event->modifier & KM_CTRL) && (event->modifier & KM_SHIFT));
 
   float col_conv[4];
 
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 9dcee73f6bb..3d92cd6c397 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -275,7 +275,7 @@ static void ui_selectcontext_apply(bContext *C,
                                    const double value,
                                    const double value_orig);
 
-#  define IS_ALLSELECT_EVENT(event) ((event)->alt != 0)
+#  define IS_ALLSELECT_EVENT(event) (((event)->modifier & KM_ALT) != 0)
 
 /** just show a tinted color so users know its activated */
 #  define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
@@ -708,7 +708,8 @@ enum eSnapType {
 
 static enum eSnapType ui_event_to_snap(const wmEvent *event)
 {
-  return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
+  return (event->modifier & KM_CTRL) ? (event->modifier & KM_SHIFT) ? SNAP_ON_SMALL : SNAP_ON :
+                                       SNAP_OFF;
 }
 
 static bool ui_event_is_snap(const wmEvent *event)
@@ -1937,7 +1938,7 @@ static void ui_selectcontext_apply(bContext *C,
           /* could check for 'handle_layer_buttons' */
           but->func) {
         wmWindow *win = CTX_wm_window(C);
-        if (!win->eventstate->shift) {
+        if ((win->eventstate->modifier & KM_SHIFT) == 0) {
           const int len = RNA_property_array_length(&but->rnapoin, prop);
           bool *tmparray = MEM_callocN(sizeof(bool) * len, __func__);
 
@@ -3747,11 +3748,11 @@ static void ui_do_but_textedit(
       case EVT_XKEY:
       case EVT_CKEY:
 #if defined(__APPLE__)
-        if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) ||
-            (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))) {
+        if (ELEM(event->modifier, KM_OSKEY, KM_CTRL))
 #else
-        if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)) {
+        if (event->modifier == KM_CTRL)
 #endif
+        {
           if (event->type == EVT_VKEY) {
             changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE);
           }
@@ -3769,16 +3770,16 @@ static void ui_do_but_textedit(
         ui_textedit_move(but,
                          data,
                          STRCUR_DIR_NEXT,
-                         event->shift != 0,
-                         event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
+                         event->modifier & KM_SHIFT,
+                         (event->modifier & KM_CTRL) ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
         retval = WM_UI_HANDLER_BREAK;
         break;
       case EVT_LEFTARROWKEY:
         ui_textedit_move(but,
                          data,
                          STRCUR_DIR_PREV,
-                         event->shift != 0,
-                         event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
+                         event->modifier & KM_SHIFT,
+                         (event->modifier & KM_CTRL) ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
         retval = WM_UI_HANDLER_BREAK;
         break;
       case WHEELDOWNMOUSE:
@@ -3795,7 +3796,7 @@ static void ui_do_but_textedit(
         }
         ATTR_FALLTHROUGH;
       case EVT_ENDKEY:
-        ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->shift != 0, STRCUR_JUMP_ALL);
+        ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->modifier & KM_SHIFT, STRCUR_JUMP_ALL);
         retval = WM_UI_HANDLER_BREAK;
         break;
       case WHEELUPMOUSE:
@@ -3812,7 +3813,7 @@ static void ui_do_but_textedit(
         }
         ATTR_FALLTHROUGH;
       case EVT_HOMEKEY:
-        ui_textedit_move(but, data, STRCUR_DIR_PREV, event->shift != 0, STRCUR_JUMP_ALL);
+        ui_textedit_move(but, data, STRCUR_DIR_PREV, event->modifier & KM_SHIFT, STRCUR_JUMP_ALL);
         retval = WM_UI_HANDLER_BREAK;
         break;
       case EVT_PADENTER:
@@ -3822,13 +3823,13 @@ static void ui_do_but_textedit(
         break;
       case EVT_DELKEY:
         changed = ui_textedit_delete(
-            but, data, 1, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
+            but, data, 1, (event->modifier & KM_CTRL) ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
         retval = WM_UI_HANDLER_BREAK;
         break;
 
       case EVT_BACKSPACEKEY:
         changed = ui_textedit_delete(
-            but, data, 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
+            but, data, 0, (event->modifier & KM_CTRL) ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
         retval = WM_UI_HANDLER_BREAK;
         break;
 
@@ -3837,10 +3838,9 @@ static void ui_do_but_textedit(
         /* Ctrl-A: Select all. */
 #if defined(__APPLE__)
         /* OSX uses Command-A system-wide, so add it. */
-        if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) ||
-            (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)))
+        if (ELEM(event->modifier, KM_OSKEY, KM_CTRL))
 #else
-        if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))
+        if (event->modifier == KM_CTRL)
 #endif
         {
           ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL);
@@ -3859,9 +3859,9 @@ static void ui_do_but_textedit(
             button_activate_state(C, but, BUTTON_STATE_EXIT);
           }
         }
-        else if (!IS_EVENT_MOD(event, ctrl, alt, oskey)) {
+        else if ((event->modifier & (KM_CTRL | KM_ALT | KM_OSKEY)) == 0) {
           /* Use standard keys for cycling through buttons Tab, Shift-Tab to reverse. */
-          if (event->shift) {
+          if (event->modifier & KM_SHIFT) {
             ui_textedit_prev_but(block, but, data);
           }
           else {
@@ -3874,12 +3874,12 @@ static void ui_do_but_textedit(
       case EVT_ZKEY: {
         /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */
 
-        const bool is_redo = (event->shift != 0);
+        const bool is_redo = (event->modifier & KM_SHIFT);
         if (
 #if defined(__APPLE__)
-            (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) ||
+            ((event->modifier & KM_OSKEY) && ((event->modifier & (KM_ALT | KM_CTRL)) == 0)) ||
 #endif
-            (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) {
+            ((event->modifier & KM_CTRL) && ((event->modifier & (KM_ALT | KM_OSKEY)) == 0))) {
           int undo_pos;
           const char *undo_str = ui_textedit_undo(
               data->undo_stack_text, is_redo ? 1 : -1, &undo_pos);
@@ -4542,19 +4542,7 @@ static int ui_do_but_HOTKEYEVT(bContext *C,
     }
 
     /* always set */
-    but->modifier_key = 0;
-    if (event->shift) {
-      but->modifier_key |= KM_SHIFT;
-    }
-    if (event->alt) {
-      but->modifier_key |= KM_ALT;
-    }
-    if (event->ctrl) {
-      but->modifier_key |= KM_CTRL;
-    }
-    if (event->oskey) {
-      but->modifier_key |= KM_OSKEY;
-    }
+    but->modifier_key = event->modifier;
 
     ui_but_update(but);
     ED_region_tag_redraw(data->region);
@@ -4633,7 +4621,8 @@ static int ui_do_but_TAB(
     const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0;
 
     if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) && (but->custom_data != NULL) &&
-        (event->type == LEFTMOUSE) && ((event->val == KM_DBL_CLICK) || event->ctrl)) {
+        (event->type == LEFTMOUSE) &&
+        ((event->val == KM_DBL_CLICK) || (event->modifier & KM_CTRL))) {
       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
       return WM_UI_HANDLER_BREAK;
     }
@@ -4666,7 +4655,8 @@ static int ui_do_but_TEX(
       if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && (!UI_but_is_utf8(but))) {
         /* pass - allow filesel, enter to execute */
       }
-      else if (ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS) && !event->ctrl) {
+      else if (ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS) &&
+               ((event->modifier & KM_CTRL) == 0)) {
         /* pass */
       }
       else {
@@ -4735,7 +4725,7 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons
       button_activate_state(C, but, BUTTON_STATE_EXIT);
       return WM_UI_HANDLER_BREAK;
     }
-    if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
+    if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
       /* Support Ctrl-Wheel to cycle values on expanded enum rows. */
       if (but->type == UI_BTYPE_ROW) {
         int type = event->type;
@@ -5325,24 +5315,24 @@ static int ui_do_but_NUM(
     }
 
     /* XXX hardcoded keymap check.... */
-    if (type == MOUSEPAN && event->ctrl) {
+    if (type == MOUSEPAN && (event->modifier & KM_CTRL)) {
       /* allow accumulating values, otherwise scrolling gets preference */
       retval = WM_UI_HANDLER_BREAK;
     }
-    else if (type == WHEELDOWNMOUSE && event->ctrl) {
+    else if (type == WHEELDOWNMOUSE && (event->modifier & KM_CTRL)) {
       mx = but->rect.xmin;
       but->drawflag &= ~UI_BUT_ACTIVE_RIGHT;
       but->drawflag |= UI_BUT_ACTIVE_LEFT;
       click = 1;
     }
-    else if (type == WHEELUPMOUSE && event->ctrl) {
+    else if ((type == WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
       mx = but->rect.xmax;
       but->drawflag &= ~UI_BUT_ACTIVE_LEFT;
       but->drawflag |= UI_BUT_ACTIVE_RIGHT;
       click = 1;
     }
     else if (event->val == KM_PRESS) {
-      if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
+      if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->modifier & KM_CTRL)) {
         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
         retval = WM_UI_HANDLER_BREAK;
       }
@@ -5402,7 +5392,7 @@ static int ui_do_but_NUM(
 #endif
 
       fac = 1.0f;
-      if (event->shift) {
+      if (event->modifier & KM_SHIFT) {
         fac /= 10.0f;
       }
 
@@ -5668,27 +5658,27 @@ static int ui_do_but_SLI(
     }
 
     /* XXX hardcoded keymap check.... */
-    if (type == MOUSEPAN && event->ctrl) {
+    if ((type == MOUSEPAN) && (event->modifier & KM_CTRL)) {
       /* allow accumulating values, otherwise scrolling gets preference */
       retval = WM_UI_HANDLER_BREAK;
     }
-    else if (type == WHEELDOWNMOUSE && event->ctrl) {
+    else if ((type == WHEELDOWNMOUSE) && (event->modifier & KM_CTRL)) {
       mx = but->rect.xmin;
       click = 2;
     }
-    else if (type == WHEELUPMOUSE && event->ctrl) {
+    else if ((type == WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
       mx = but->rect.xmax;
       click = 2;
     }
     else if (event->val == KM_PRESS) {
-      if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
+      if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->modifier & KM_CTRL)) {
         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
         retval = WM_UI_HANDLER_BREAK;
       }
 #ifndef USE_ALLSELECT
       /* alt-click on sides to get "arrows" like in UI_BTYPE_NUM buttons,
        * and match wheel usage above */
-      else if (event->type == LEFTMOUSE && event->alt) {
+      else if ((event->type == LEFTMOUSE) && (event->modifier & KM_ALT)) {
         int halfpos = BLI_rctf_cent_x(&but->rect);
         click = 2;
         if (mx < halfpos) {
@@ -5754,8 +5744,13 @@ static int ui_do_but_SLI(
       data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
       data->multi_data.drag_dir[1] += abs(data->draglasty - my);
 #endif
-      if (ui_numedit_but_SLI(
-              but, data, mx, true, is_motion, event->ctrl != 0, event->shift != 0)) {
+      if (ui_numedit_but_SLI(but,
+                             data,
+                             mx,
+                             true,
+                             is_motion,
+                             event->modifier & KM_CTRL,
+                             event->modifier & KM_SHIFT)) {
         ui_numedit_apply(C, block, but, data);
       }
 
@@ -5981,8 +5976,8 @@ static int ui_do_but_LISTROW(bContext *C,
     /* hack to pass on ctrl+click and double click to overlapping text
      * editing field for editing list item names
      */
-    if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS &&
-         event->ctrl) ||
+    if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->val == KM_PRESS) &&
+         (event->modifier & KM_CTRL)) ||
         (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK)) {
       uiBut *labelbut = ui_but_list_row_text_activate(
           C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING);
@@ -6023,7 +6018,8 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co
       return WM_UI_HANDLER_BREAK;
     }
     if (ui_but_supports_cycling(but)) {
-      if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
+      if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) &&
+          (event->modifier & KM_CTRL)) {
         int type = event->type;
         int val = event->val;
 
@@ -6210,7 +6206,7 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
       button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
       return WM_UI_HANDLER_BREAK;
     }
-    if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
+    if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
       ColorPicker *cpicker = but->custom_data;
       float hsv_static[3] = {0.0f};
       float *hsv = cpicker ? cpicker->hsv_perceptual : hsv_static;
@@ -6269,7 +6265,7 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
 
     if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
       if (color_but->is_pallete_color) {
-        if (!event->ctrl) {
+        if ((event->modifier & KM_CTRL) == 0) {
           float color[3];
           Paint *paint = BKE_paint_get_active_from_context(C);
           Brush *brush = BKE_paint_brush(paint);
@@ -6639,7 +6635,7 @@ static int ui_do_but_HSVCUBE(
       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
       /* also do drag the first time */
-      if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
+      if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
         ui_numedit_apply(C, block, but, data);
       }
 
@@ -6650,7 +6646,7 @@ static int ui_do_but_HSVCUBE(
       const wmNDOFMotionData *ndof = event->customdata;
       const enum eSnapType snap = ui_event_to_snap(event);
 
-      ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->shift != 0);
+      ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->modifier & KM_SHIFT);
 
       button_activate_state(C, but, BUTTON_STATE_EXIT);
       ui_apply_but(C, but->block, but, data, true);
@@ -6702,7 +6698,7 @@ static int ui_do_but_HSVCUBE(
       if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
         const enum eSnapType snap = ui_event_to_snap(event);
 
-        if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
+        if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
           ui_numedit_apply(C, block, but, data);
         }
       }
@@ -6914,7 +6910,7 @@ static int ui_do_but_HSVCIRCLE(
       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
       /* also do drag the first time */
-      if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
+      if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
         ui_numedit_apply(C, block, but, data);
       }
 
@@ -6925,7 +6921,7 @@ static int ui_do_but_HSVCIRCLE(
       const enum eSnapType snap = ui_event_to_snap(event);
       const wmNDOFMotionData *ndof = event->customdata;
 
-      ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->shift != 0);
+      ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->modifier & KM_SHIFT);
 
       button_activate_state(C, but, BUTTON_STATE_EXIT);
       ui_apply_but(C, but->block, but, data, true);
@@ -6987,7 +6983,7 @@ static int ui_do_but_HSVCIRCLE(
       if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
         const enum eSnapType snap = ui_event_to_snap(event);
 
-        if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
+        if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
           ui_numedit_apply(C, block, but, data);
         }
       }
@@ -7037,7 +7033,7 @@ static int ui_do_but_COLORBAND(
     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
       ColorBand *coba = (ColorBand *)but->poin;
 
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         /* insert new key on mouse location */
         const float pos = ((float)(mx - but->rect.xmin)) / BLI_rctf_size_x(&but->rect);
         BKE_colorband_element_add(coba, pos);
@@ -7237,7 +7233,7 @@ static int ui_do_but_CURVE(
       float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius */
       int sel = -1;
 
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         float f_xy[2];
         BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
 
@@ -7301,7 +7297,7 @@ static int ui_do_but_CURVE(
       if (sel != -1) {
         /* ok, we move a point */
         /* deselect all if this one is deselect. except if we hold shift */
-        if (!event->shift) {
+        if ((event->modifier & KM_SHIFT) == 0) {
           for (int a = 0; a < cuma->totpoint; a++) {
             cmp[a].flag &= ~CUMA_SELECT;
           }
@@ -7336,8 +7332,8 @@ static int ui_do_but_CURVE(
                                  data,
                                  event->xy[0],
                                  event->xy[1],
-                                 event->ctrl != 0,
-                                 event->shift != 0)) {
+                                 event->modifier & KM_CTRL,
+                                 event->modifier & KM_SHIFT)) {
           ui_numedit_apply(C, block, but, data);
         }
       }
@@ -7350,7 +7346,7 @@ static int ui_do_but_CURVE(
 
         if (data->dragchange == false) {
           /* deselect all, select one */
-          if (!event->shift) {
+          if ((event->modifier & KM_SHIFT) == 0) {
             for (int a = 0; a < cuma->totpoint; a++) {
               cmp[a].flag &= ~CUMA_SELECT;
             }
@@ -7539,7 +7535,7 @@ static int ui_do_but_CURVEPROFILE(
     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
       const float m_xy[2] = {mx, my};
 
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         float f_xy[2];
         BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
 
@@ -7616,7 +7612,7 @@ static int ui_do_but_CURVEPROFILE(
       /* Change the flag for the point(s) if one was selected or added. */
       if (i_selected != -1) {
         /* Deselect all if this one is deselected, except if we hold shift. */
-        if (event->shift) {
+        if (event->modifier & KM_SHIFT) {
           pts[i_selected].flag ^= selection_type;
         }
         else {
@@ -7647,7 +7643,7 @@ static int ui_do_but_CURVEPROFILE(
     if (event->type == MOUSEMOVE) {
       if (mx != data->draglastx || my != data->draglasty) {
         if (ui_numedit_but_CURVEPROFILE(
-                block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) {
+                block, but, data, mx, my, event->modifier & KM_CTRL, event->modifier & KM_SHIFT)) {
           ui_numedit_apply(C, block, but, data);
         }
       }
@@ -7871,7 +7867,7 @@ static int ui_do_but_TRACKPREVIEW(
       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
       /* also do drag the first time */
-      if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
+      if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->modifier & KM_SHIFT)) {
         ui_numedit_apply(C, block, but, data);
       }
 
@@ -7888,7 +7884,7 @@ static int ui_do_but_TRACKPREVIEW(
     }
     else if (event->type == MOUSEMOVE) {
       if (mx != data->draglastx || my != data->draglasty) {
-        if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
+        if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->modifier & KM_SHIFT)) {
           ui_numedit_apply(C, block, but, data);
         }
       }
@@ -7918,8 +7914,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
   if (data->state == BUTTON_STATE_HIGHLIGHT) {
 
     /* handle copy and paste */
-    bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) &&
-                                      !event->shift;
+    bool is_press_ctrl_but_no_shift = (event->val == KM_PRESS) &&
+                                      (event->modifier & (KM_CTRL | KM_OSKEY)) &&
+                                      (event->modifier & KM_SHIFT) == 0;
     const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift;
     const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift;
 
@@ -7934,12 +7931,14 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
 
     /* do copy first, because it is the only allowed operator when disabled */
     if (do_copy) {
-      ui_but_copy(C, but, event->alt);
+      ui_but_copy(C, but, event->modifier & KM_ALT);
       return WM_UI_HANDLER_BREAK;
     }
 
     /* handle menu */
-    if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
+
+    if ((event->type == RIGHTMOUSE) &&
+        (event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0 &&
         (event->val == KM_PRESS)) {
       /* For some button types that are typically representing entire sets of data, right-clicking
        * to spawn the context menu should also activate the item. This makes it clear which item
@@ -7960,7 +7959,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
     }
 
     if (do_paste) {
-      ui_but_paste(C, but, data, event->alt);
+      ui_but_paste(C, but, data, event->modifier & KM_ALT);
       return WM_UI_HANDLER_BREAK;
     }
 
@@ -8947,7 +8946,7 @@ static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *reg
     if (but) {
       button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
 
-      if (event->alt && but->active) {
+      if ((event->modifier & KM_ALT) && but->active) {
         /* Display tool-tips if holding Alt on mouse-over when tool-tips are disabled in the
          * preferences. */
         but->active->tooltip_force = true;
@@ -9555,9 +9554,9 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
   }
   else if (val == KM_PRESS) {
     if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY, EVT_LEFTARROWKEY, EVT_RIGHTARROWKEY) &&
-         !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) ||
-        ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl &&
-          !IS_EVENT_MOD(event, shift, alt, oskey)))) {
+         (event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0) ||
+        ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && (event->modifier & KM_CTRL) &&
+          (event->modifier & (KM_SHIFT | KM_ALT | KM_OSKEY)) == 0))) {
       const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop);
       int value, min, max;
 
@@ -9614,7 +9613,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
       }
       retval = WM_UI_HANDLER_BREAK;
     }
-    else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->shift) {
+    else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && (event->modifier & KM_SHIFT)) {
       /* We now have proper grip, but keep this anyway! */
       if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) {
         ui_list->list_grip = dyn_data->visual_height;
@@ -10268,7 +10267,7 @@ static int ui_handle_menu_event(bContext *C,
 
         /* Smooth scrolling for popovers. */
         case MOUSEPAN: {
-          if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
+          if (event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) {
             /* pass */
           }
           else if (!ui_block_is_menu(block)) {
@@ -10290,7 +10289,7 @@ static int ui_handle_menu_event(bContext *C,
         }
         case WHEELUPMOUSE:
         case WHEELDOWNMOUSE: {
-          if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
+          if (event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) {
             /* pass */
           }
           else if (!ui_block_is_menu(block)) {
@@ -10313,7 +10312,7 @@ static int ui_handle_menu_event(bContext *C,
         case EVT_HOMEKEY:
         case EVT_ENDKEY:
           /* Arrow-keys: only handle for block_loop blocks. */
-          if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
+          if (event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) {
             /* pass */
           }
           else if (inside || (block->flag & UI_BLOCK_LOOP)) {
@@ -10464,7 +10463,7 @@ static int ui_handle_menu_event(bContext *C,
               break;
             }
 
-            if (event->alt) {
+            if (event->modifier & KM_ALT) {
               act += 10;
             }
 
@@ -10544,7 +10543,7 @@ static int ui_handle_menu_event(bContext *C,
         case EVT_YKEY:
         case EVT_ZKEY: {
           if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) &&
-              !IS_EVENT_MOD(event, shift, ctrl, oskey) &&
+              ((event->modifier & (KM_SHIFT | KM_CTRL | KM_OSKEY)) == 0) &&
               /* Only respond to explicit press to avoid the event that opened the menu
                * activating an item when the key is held. */
               !event->is_repeat) {
@@ -11071,7 +11070,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
         case EVT_YKEY:
         case EVT_ZKEY: {
           if ((ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) &&
-              !IS_EVENT_MOD(event, shift, ctrl, oskey)) {
+              ((event->modifier & (KM_SHIFT | KM_CTRL | KM_OSKEY)) == 0)) {
             LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
               if (but->menu_key == event->type) {
                 ui_but_pie_button_activate(C, but, menu);
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index 54a5b496048..bd55d2d9d81 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -492,7 +492,7 @@ static void ui_layer_but_cb(bContext *C, void *arg_but, void *arg_index)
   PointerRNA *ptr = &but->rnapoin;
   PropertyRNA *prop = but->rnaprop;
   const int index = POINTER_AS_INT(arg_index);
-  const int shift = win->eventstate->shift;
+  const bool shift = win->eventstate->modifier & KM_SHIFT;
   const int len = RNA_property_array_length(ptr, prop);
 
   if (!shift) {
@@ -752,7 +752,7 @@ static void ui_item_enum_expand_handle(bContext *C, void *arg1, void *arg2)
 {
   wmWindow *win = CTX_wm_window(C);
 
-  if (!win->eventstate->shift) {
+  if ((win->eventstate->modifier & KM_SHIFT) == 0) {
     uiBut *but = (uiBut *)arg1;
     const int enum_value = POINTER_AS_INT(arg2);
 
diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c
index 2cb0f256b71..c7f2eb230cb 100644
--- a/source/blender/editors/interface/interface_panel.c
+++ b/source/blender/editors/interface/interface_panel.c
@@ -2061,8 +2061,8 @@ static void ui_handle_panel_header(const bContext *C,
                                    const uiBlock *block,
                                    const int mx,
                                    const int event_type,
-                                   const short ctrl,
-                                   const short shift)
+                                   const bool ctrl,
+                                   const bool shift)
 {
   Panel *panel = block->panel;
   ARegion *region = CTX_wm_region(C);
@@ -2274,7 +2274,7 @@ static int ui_handle_panel_category_cycling(const wmEvent *event,
            (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin));
 
   /* If mouse is inside non-tab region, ctrl key is required. */
-  if (is_mousewheel && !event->ctrl && !inside_tabregion) {
+  if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) {
     return WM_UI_HANDLER_CONTINUE;
   }
 
@@ -2291,7 +2291,7 @@ static int ui_handle_panel_category_cycling(const wmEvent *event,
           pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev;
         }
         else {
-          const bool backwards = event->shift;
+          const bool backwards = event->modifier & KM_SHIFT;
           pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next;
           if (!pc_dyn) {
             /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */
@@ -2349,7 +2349,7 @@ int ui_handler_panel_region(bContext *C,
         retval = WM_UI_HANDLER_BREAK;
       }
     }
-    else if ((event->type == EVT_TABKEY && event->ctrl) ||
+    else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) ||
              ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
       /* Cycle tabs. */
       retval = ui_handle_panel_category_cycling(event, region, active_but);
@@ -2386,9 +2386,11 @@ int ui_handler_panel_region(bContext *C,
 
       /* The panel collapse / expand key "A" is special as it takes priority over
        * active button handling. */
-      if (event->type == EVT_AKEY && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
+      if (event->type == EVT_AKEY &&
+          ((event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0)) {
         retval = WM_UI_HANDLER_BREAK;
-        ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift);
+        ui_handle_panel_header(
+            C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT);
         break;
       }
     }
@@ -2402,7 +2404,8 @@ int ui_handler_panel_region(bContext *C,
       /* All mouse clicks inside panel headers should return in break. */
       if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) {
         retval = WM_UI_HANDLER_BREAK;
-        ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift);
+        ui_handle_panel_header(
+            C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT);
       }
       else if (event->type == RIGHTMOUSE) {
         retval = WM_UI_HANDLER_BREAK;
diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c
index 8a945c8c913..4703367671d 100644
--- a/source/blender/editors/interface/interface_query.c
+++ b/source/blender/editors/interface/interface_query.c
@@ -310,7 +310,7 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
 
 uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event)
 {
-  return ui_but_find_mouse_over_ex(region, event->xy, event->ctrl != 0, NULL, NULL);
+  return ui_but_find_mouse_over_ex(region, event->xy, event->modifier & KM_CTRL, NULL, NULL);
 }
 
 uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c
index 09faf493ce7..29553ff65d1 100644
--- a/source/blender/editors/interface/interface_region_tooltip.c
+++ b/source/blender/editors/interface/interface_region_tooltip.c
@@ -701,7 +701,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
   /* Keymap */
 
   /* This is too handy not to expose somehow, let's be sneaky for now. */
-  if ((is_label == false) && CTX_wm_window(C)->eventstate->shift) {
+  if ((is_label == false) && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
     const char *expr_imports[] = {"bpy", "bl_ui", NULL};
     char expr[256];
     SNPRINTF(expr,
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index f8fbc2d01a6..32b3bb5e926 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -609,7 +609,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
       RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL);
       RNA_property_update(C, &template_ui->ptr, template_ui->prop);
 
-      if (id && CTX_wm_window(C)->eventstate->shift) {
+      if (id && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
         /* only way to force-remove data (on save) */
         id_us_clear_real(id);
         id_fake_user_clear(id);
@@ -635,7 +635,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
     case UI_ID_LOCAL:
       if (id) {
         Main *bmain = CTX_data_main(C);
-        if (CTX_wm_window(C)->eventstate->shift) {
+        if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
           if (ID_IS_OVERRIDABLE_LIBRARY(id)) {
             /* Only remap that specific ID usage to overriding local data-block. */
             ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false);
@@ -5539,7 +5539,7 @@ static void handle_layer_buttons(bContext *C, void *arg1, void *arg2)
   uiBut *but = arg1;
   const int cur = POINTER_AS_INT(arg2);
   wmWindow *win = CTX_wm_window(C);
-  const int shift = win->eventstate->shift;
+  const bool shift = win->eventstate->modifier & KM_SHIFT;
 
   if (!shift) {
     const int tot = RNA_property_array_length(&but->rnapoin, but->rnaprop);
diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c
index 429db50f321..e53dda1760e 100644
--- a/source/blender/editors/mesh/editmesh_bevel.c
+++ b/source/blender/editors/mesh/editmesh_bevel.c
@@ -546,7 +546,7 @@ static void edbm_bevel_mouse_set_value(wmOperator *op, const wmEvent *event)
   value = value_start[vmode] + value * opdata->scale[vmode];
 
   /* Fake shift-transform... */
-  if (event->shift) {
+  if (event->modifier & KM_SHIFT) {
     if (opdata->shift_value[vmode] < 0.0f) {
       opdata->shift_value[vmode] = (vmode == SEGMENTS_VALUE) ?
                                        opdata->segments :
diff --git a/source/blender/editors/mesh/editmesh_loopcut.c b/source/blender/editors/mesh/editmesh_loopcut.c
index b2ed6780443..c9fc48c3568 100644
--- a/source/blender/editors/mesh/editmesh_loopcut.c
+++ b/source/blender/editors/mesh/editmesh_loopcut.c
@@ -581,7 +581,7 @@ static int loopcut_modal(bContext *C, wmOperator *op, const wmEvent *event)
         handled = true;
         break;
       case MOUSEPAN:
-        if (event->alt == 0) {
+        if ((event->modifier & KM_ALT) == 0) {
           cuts += 0.02f * (event->xy[1] - event->prev_xy[1]);
           if (cuts < 1 && lcd->cuts >= 1) {
             cuts = 1;
@@ -598,7 +598,7 @@ static int loopcut_modal(bContext *C, wmOperator *op, const wmEvent *event)
         if (event->val == KM_RELEASE) {
           break;
         }
-        if (event->alt == 0) {
+        if ((event->modifier & KM_ALT) == 0) {
           cuts += 1;
         }
         else {
@@ -612,7 +612,7 @@ static int loopcut_modal(bContext *C, wmOperator *op, const wmEvent *event)
         if (event->val == KM_RELEASE) {
           break;
         }
-        if (event->alt == 0) {
+        if ((event->modifier & KM_ALT) == 0) {
           cuts = max_ff(cuts - 1, 1);
         }
         else {
diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c
index fc1d60fc768..d8fc7a4f9d4 100644
--- a/source/blender/editors/mesh/editmesh_select.c
+++ b/source/blender/editors/mesh/editmesh_select.c
@@ -1367,10 +1367,10 @@ static int edbm_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *e
   /* detecting these options based on shift/ctrl here is weak, but it's done
    * to make this work when clicking buttons or menus */
   if (!RNA_struct_property_is_set(op->ptr, "use_extend")) {
-    RNA_boolean_set(op->ptr, "use_extend", event->shift);
+    RNA_boolean_set(op->ptr, "use_extend", event->modifier & KM_SHIFT);
   }
   if (!RNA_struct_property_is_set(op->ptr, "use_expand")) {
-    RNA_boolean_set(op->ptr, "use_expand", event->ctrl);
+    RNA_boolean_set(op->ptr, "use_expand", event->modifier & KM_CTRL);
   }
 
   return edbm_select_mode_exec(C, op);
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 203e18f9621..4da278b7a31 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -340,10 +340,10 @@ static int object_hide_collection_exec(bContext *C, wmOperator *op)
   View3D *v3d = CTX_wm_view3d(C);
 
   int index = RNA_int_get(op->ptr, "collection_index");
-  const bool extend = (win->eventstate->shift != 0);
+  const bool extend = (win->eventstate->modifier & KM_SHIFT) != 0;
   const bool toggle = RNA_boolean_get(op->ptr, "toggle");
 
-  if (win->eventstate->alt != 0) {
+  if (win->eventstate->modifier & KM_ALT) {
     index += 10;
   }
 
@@ -1427,7 +1427,7 @@ static int object_clear_paths_exec(bContext *C, wmOperator *op)
 /* operator callback/wrapper */
 static int object_clear_paths_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
-  if ((event->shift) && !RNA_struct_property_is_set(op->ptr, "only_selected")) {
+  if ((event->modifier & KM_SHIFT) && !RNA_struct_property_is_set(op->ptr, "only_selected")) {
     RNA_boolean_set(op->ptr, "only_selected", true);
   }
   return object_clear_paths_exec(C, op);
diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc
index 44e6fb10528..2e495ac6147 100644
--- a/source/blender/editors/object/object_remesh.cc
+++ b/source/blender/editors/object/object_remesh.cc
@@ -406,7 +406,7 @@ static int voxel_size_edit_modal(bContext *C, wmOperator *op, const wmEvent *eve
     d = cd->slow_mval[0] - mval[0];
   }
 
-  if (event->ctrl) {
+  if (event->modifier & KM_CTRL) {
     /* Linear mode, enables jumping to any voxel size. */
     d = d * 0.0005f;
   }
diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c
index 94aa7d59f9d..9e82abf4d07 100644
--- a/source/blender/editors/object/object_transform.c
+++ b/source/blender/editors/object/object_transform.c
@@ -1861,7 +1861,7 @@ static int object_transform_axis_target_modal(bContext *C, wmOperator *op, const
 
   view3d_operator_needs_opengl(C);
 
-  const bool is_translate = (event->ctrl != 0);
+  const bool is_translate = event->modifier & KM_CTRL;
   const bool is_translate_init = is_translate && (xfd->is_translate != is_translate);
 
   if (event->type == MOUSEMOVE || is_translate_init) {
diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c
index af9496263e7..6307f6dc37f 100644
--- a/source/blender/editors/physics/particle_edit.c
+++ b/source/blender/editors/physics/particle_edit.c
@@ -4963,7 +4963,7 @@ static void brush_edit_apply_event(bContext *C, wmOperator *op, const wmEvent *e
   RNA_collection_add(op->ptr, "stroke", &itemptr);
 
   RNA_float_set_array(&itemptr, "mouse", mouse);
-  RNA_boolean_set(&itemptr, "pen_flip", event->shift != false); /* XXX hardcoded */
+  RNA_boolean_set(&itemptr, "pen_flip", event->modifier & KM_SHIFT); /* XXX hardcoded */
 
   /* apply */
   brush_edit_apply(C, op, &itemptr);
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index 7c4623dd4b0..ae7570d21a1 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -1496,7 +1496,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS
     return OPERATOR_FINISHED;
   }
   else if (br->flag & BRUSH_LINE) {
-    if (event->alt) {
+    if (event->modifier & KM_ALT) {
       stroke->constrain_line = true;
     }
     else {
diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
index 72640893dea..8fc10061f83 100644
--- a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
+++ b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
@@ -245,7 +245,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
 
   /* When pressing Ctrl, expand directly to the max number of iterations. This allows to flood fill
    * mask and face sets by connectivity directly. */
-  if (event->ctrl) {
+  if (event->modifier & KM_CTRL) {
     mask_expand_update_it = ss->filter_cache->mask_update_last_it - 1;
   }
 
diff --git a/source/blender/editors/space_action/action_data.c b/source/blender/editors/space_action/action_data.c
index e383ada1331..8e8902c2ea7 100644
--- a/source/blender/editors/space_action/action_data.c
+++ b/source/blender/editors/space_action/action_data.c
@@ -659,7 +659,7 @@ static int action_unlink_invoke(bContext *C, wmOperator *op, const wmEvent *even
 {
   /* NOTE: this is hardcoded to match the behavior for the unlink button
    * (in interface_templates.c). */
-  RNA_boolean_set(op->ptr, "force_delete", event->shift != 0);
+  RNA_boolean_set(op->ptr, "force_delete", event->modifier & KM_SHIFT);
   return action_unlink_exec(C, op);
 }
 
diff --git a/source/blender/editors/space_buttons/buttons_ops.c b/source/blender/editors/space_buttons/buttons_ops.c
index cad6d34af24..e215b7c7992 100644
--- a/source/blender/editors/space_buttons/buttons_ops.c
+++ b/source/blender/editors/space_buttons/buttons_ops.c
@@ -282,11 +282,11 @@ static int file_browse_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 
   /* Useful yet irritating feature, Shift+Click to open the file
    * Alt+Click to browse a folder in the OS's browser. */
-  if (event->shift || event->alt) {
+  if (event->modifier & (KM_SHIFT | KM_ALT)) {
     wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true);
     PointerRNA props_ptr;
 
-    if (event->alt) {
+    if (event->modifier & KM_ALT) {
       char *lslash = (char *)BLI_path_slash_rfind(str);
       if (lslash) {
         *lslash = '\0';
diff --git a/source/blender/editors/space_console/console_ops.c b/source/blender/editors/space_console/console_ops.c
index 3fba069879b..a24eae6a0ce 100644
--- a/source/blender/editors/space_console/console_ops.c
+++ b/source/blender/editors/space_console/console_ops.c
@@ -408,7 +408,7 @@ static int console_insert_invoke(bContext *C, wmOperator *op, const wmEvent *eve
      * (when input method are used for utf8 inputs, the user may assign key event
      * including alt/ctrl/super like ctrl+m to commit utf8 string.  in such case,
      * the modifiers in the utf8 character event make no sense.) */
-    if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
+    if ((event->modifier & (KM_CTRL | KM_OSKEY)) && !event->utf8_buf[0]) {
       return OPERATOR_PASS_THROUGH;
     }
 
diff --git a/source/blender/editors/space_nla/nla_channels.c b/source/blender/editors/space_nla/nla_channels.c
index 4a507aa3bf2..8b059b33a9a 100644
--- a/source/blender/editors/space_nla/nla_channels.c
+++ b/source/blender/editors/space_nla/nla_channels.c
@@ -583,7 +583,7 @@ static int nla_action_unlink_invoke(bContext *C, wmOperator *op, const wmEvent *
 {
   /* NOTE: this is hardcoded to match the behavior for the unlink button
    * (in interface_templates.c) */
-  RNA_boolean_set(op->ptr, "force_delete", event->shift != 0);
+  RNA_boolean_set(op->ptr, "force_delete", event->modifier & KM_SHIFT);
   return nla_action_unlink_exec(C, op);
 }
 
diff --git a/source/blender/editors/space_outliner/outliner_collections.cc b/source/blender/editors/space_outliner/outliner_collections.cc
index 765661aa9d5..f38f6c2855d 100644
--- a/source/blender/editors/space_outliner/outliner_collections.cc
+++ b/source/blender/editors/space_outliner/outliner_collections.cc
@@ -1071,7 +1071,7 @@ static int collection_isolate_exec(bContext *C, wmOperator *op)
 static int collection_isolate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
   PropertyRNA *prop = RNA_struct_find_property(op->ptr, "extend");
-  if (!RNA_property_is_set(op->ptr, prop) && (event->shift)) {
+  if (!RNA_property_is_set(op->ptr, prop) && (event->modifier & KM_SHIFT)) {
     RNA_property_boolean_set(op->ptr, prop, true);
   }
   return collection_isolate_exec(C, op);
diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc
index 0d8ee76d2f0..edd2e5f304f 100644
--- a/source/blender/editors/space_outliner/outliner_dragdrop.cc
+++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc
@@ -319,7 +319,7 @@ static bool parent_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
   }
 
   if (!allow_parenting_without_modifier_key(space_outliner)) {
-    if (!event->shift) {
+    if ((event->modifier & KM_SHIFT) == 0) {
       return false;
     }
   }
@@ -417,8 +417,12 @@ static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   ListBase *lb = reinterpret_cast(event->customdata);
   wmDrag *drag = reinterpret_cast(lb->first);
 
-  parent_drop_set_parents(
-      C, op->reports, reinterpret_cast(drag->ids.first), par, PAR_OBJECT, event->alt);
+  parent_drop_set_parents(C,
+                          op->reports,
+                          reinterpret_cast(drag->ids.first),
+                          par,
+                          PAR_OBJECT,
+                          event->modifier & KM_ALT);
 
   return OPERATOR_FINISHED;
 }
@@ -446,7 +450,7 @@ static bool parent_clear_poll(bContext *C, wmDrag *drag, const wmEvent *event)
   SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
 
   if (!allow_parenting_without_modifier_key(space_outliner)) {
-    if (!event->shift) {
+    if ((event->modifier & KM_SHIFT) == 0) {
       return false;
     }
   }
@@ -471,7 +475,7 @@ static bool parent_clear_poll(bContext *C, wmDrag *drag, const wmEvent *event)
       case ID_OB:
         return ELEM(tselem->type, TSE_MODIFIER_BASE, TSE_CONSTRAINT_BASE);
       case ID_GR:
-        return event->shift || ELEM(tselem->type, TSE_LIBRARY_OVERRIDE_BASE);
+        return (event->modifier & KM_SHIFT) || ELEM(tselem->type, TSE_LIBRARY_OVERRIDE_BASE);
       default:
         return true;
     }
@@ -496,7 +500,8 @@ static int parent_clear_invoke(bContext *C, wmOperator *UNUSED(op), const wmEven
     if (GS(drag_id->id->name) == ID_OB) {
       Object *object = (Object *)drag_id->id;
 
-      ED_object_parent_clear(object, event->alt ? CLEAR_PARENT_KEEP_TRANSFORM : CLEAR_PARENT_ALL);
+      ED_object_parent_clear(
+          object, (event->modifier & KM_ALT) ? CLEAR_PARENT_KEEP_TRANSFORM : CLEAR_PARENT_ALL);
     }
   }
 
@@ -1166,10 +1171,11 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event
       &space_outliner->tree, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false);
 
   CollectionDrop data;
-  if (!event->shift && collection_drop_init(C, drag, event->xy, event->ctrl, &data)) {
+  if (((event->modifier & KM_SHIFT) == 0) &&
+      collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) {
     TreeElement *te = data.te;
     TreeStoreElem *tselem = TREESTORE(te);
-    if (!data.from || event->ctrl) {
+    if (!data.from || event->modifier & KM_CTRL) {
       tselem->flag |= TSE_DRAG_INTO;
       changed = true;
     }
@@ -1210,9 +1216,10 @@ static char *collection_drop_tooltip(bContext *C,
   const wmEvent *event = win ? win->eventstate : nullptr;
 
   CollectionDrop data;
-  if (event && !event->shift && collection_drop_init(C, drag, xy, event->ctrl, &data)) {
+  if (event && ((event->modifier & KM_SHIFT) == 0) &&
+      collection_drop_init(C, drag, xy, event->modifier & KM_CTRL, &data)) {
     TreeElement *te = data.te;
-    if (!data.from || event->ctrl) {
+    if (!data.from || event->modifier & KM_CTRL) {
       return BLI_strdup(TIP_("Link inside Collection"));
     }
     switch (data.insert_type) {
@@ -1263,7 +1270,7 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE
   wmDrag *drag = reinterpret_cast(lb->first);
 
   CollectionDrop data;
-  if (!collection_drop_init(C, drag, event->xy, event->ctrl, &data)) {
+  if (!collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) {
     return OPERATOR_CANCELLED;
   }
 
@@ -1291,7 +1298,9 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE
 
   LISTBASE_FOREACH (wmDragID *, drag_id, &drag->ids) {
     /* Ctrl enables linking, so we don't need a from collection then. */
-    Collection *from = (event->ctrl) ? nullptr : collection_parent_from_ID(drag_id->from_parent);
+    Collection *from = (event->modifier & KM_CTRL) ?
+                           nullptr :
+                           collection_parent_from_ID(drag_id->from_parent);
 
     if (GS(drag_id->id->name) == ID_OB) {
       /* Move/link object into collection. */
diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc
index bc58ad226ee..2da416c8671 100644
--- a/source/blender/editors/space_outliner/outliner_draw.cc
+++ b/source/blender/editors/space_outliner/outliner_draw.cc
@@ -166,7 +166,7 @@ static void restrictbutton_bone_visibility_fn(bContext *C, void *poin, void *UNU
 {
   Bone *bone = (Bone *)poin;
 
-  if (CTX_wm_window(C)->eventstate->shift) {
+  if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
     restrictbutton_recursive_bone(bone, BONE_HIDDEN_P, (bone->flag & BONE_HIDDEN_P) != 0);
   }
 }
@@ -178,7 +178,7 @@ static void restrictbutton_bone_select_fn(bContext *C, void *UNUSED(poin), void
     bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
   }
 
-  if (CTX_wm_window(C)->eventstate->shift) {
+  if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
     restrictbutton_recursive_bone(bone, BONE_UNSELECTABLE, (bone->flag & BONE_UNSELECTABLE) != 0);
   }
 
@@ -194,7 +194,7 @@ static void restrictbutton_ebone_select_fn(bContext *C, void *poin, void *poin2)
     ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
   }
 
-  if (CTX_wm_window(C)->eventstate->shift) {
+  if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
     restrictbutton_recursive_ebone(
         arm, ebone, BONE_UNSELECTABLE, (ebone->flag & BONE_UNSELECTABLE) != 0);
   }
@@ -210,7 +210,7 @@ static void restrictbutton_ebone_visibility_fn(bContext *C, void *poin, void *po
     ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
   }
 
-  if (CTX_wm_window(C)->eventstate->shift) {
+  if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
     restrictbutton_recursive_ebone(arm, ebone, BONE_HIDDEN_A, (ebone->flag & BONE_HIDDEN_A) != 0);
   }
 
@@ -250,7 +250,7 @@ static void outliner_object_set_flag_recursive_fn(bContext *C,
   ViewLayer *view_layer = CTX_data_view_layer(C);
   PointerRNA ptr;
 
-  bool extend = (win->eventstate->shift != 0);
+  bool extend = (win->eventstate->modifier & KM_SHIFT);
 
   if (!extend) {
     return;
@@ -571,8 +571,8 @@ static void outliner_collection_set_flag_recursive_fn(bContext *C,
   ViewLayer *view_layer = CTX_data_view_layer(C);
   PointerRNA ptr;
 
-  bool do_isolate = (win->eventstate->ctrl != 0);
-  bool extend = (win->eventstate->shift != 0);
+  bool do_isolate = (win->eventstate->modifier & KM_CTRL);
+  bool extend = (win->eventstate->modifier & KM_SHIFT);
 
   if (!ELEM(true, do_isolate, extend)) {
     return;
@@ -2043,7 +2043,7 @@ static void outliner_mode_toggle_fn(bContext *C, void *tselem_poin, void *UNUSED
   const bool object_data_shared = (ob->data == tvc.obact->data);
 
   wmWindow *win = CTX_wm_window(C);
-  const bool do_extend = win->eventstate->ctrl != 0 && !object_data_shared;
+  const bool do_extend = (win->eventstate->modifier & KM_CTRL) && !object_data_shared;
   outliner_item_mode_toggle(C, &tvc, te, do_extend);
 }
 
diff --git a/source/blender/editors/space_text/text_autocomplete.c b/source/blender/editors/space_text/text_autocomplete.c
index 496f500ef04..55873740491 100644
--- a/source/blender/editors/space_text/text_autocomplete.c
+++ b/source/blender/editors/space_text/text_autocomplete.c
@@ -408,7 +408,7 @@ static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *e
     case EVT_BACKSPACEKEY:
       if (event->val == KM_PRESS) {
         if (tools & TOOL_SUGG_LIST) {
-          if (event->ctrl) {
+          if (event->modifier & KM_CTRL) {
             texttool_suggest_clear();
             retval = OPERATOR_CANCELLED;
             draw = 1;
@@ -445,7 +445,7 @@ static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *e
     case EVT_RIGHTARROWKEY:
       if (event->val == KM_PRESS) {
         if (tools & TOOL_SUGG_LIST) {
-          if (event->ctrl) {
+          if (event->modifier & KM_CTRL) {
             texttool_suggest_clear();
             retval = OPERATOR_CANCELLED;
             draw = 1;
diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c
index ddba6803f61..3c29b32c2fa 100644
--- a/source/blender/editors/space_text/text_ops.c
+++ b/source/blender/editors/space_text/text_ops.c
@@ -3495,7 +3495,7 @@ static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event)
      * (when input method are used for utf8 inputs, the user may assign key event
      * including alt/ctrl/super like ctrl+m to commit utf8 string.  in such case,
      * the modifiers in the utf8 character event make no sense.) */
-    if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
+    if ((event->modifier & (KM_CTRL | KM_OSKEY)) && !event->utf8_buf[0]) {
       return OPERATOR_PASS_THROUGH;
     }
 
diff --git a/source/blender/editors/space_view3d/view3d_cursor_snap.c b/source/blender/editors/space_view3d/view3d_cursor_snap.c
index 785c5ab28c8..53f7b3d5871 100644
--- a/source/blender/editors/space_view3d/view3d_cursor_snap.c
+++ b/source/blender/editors/space_view3d/view3d_cursor_snap.c
@@ -63,7 +63,7 @@ typedef struct SnapCursorDataIntern {
     int x;
     int y;
 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
-    short shift, ctrl, alt, oskey;
+    uint8_t modifier;
 #endif
   } last_eventstate;
 
@@ -478,10 +478,7 @@ static bool v3d_cursor_eventstate_has_changed(SnapCursorDataIntern *data_intern,
     }
 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
     if (!(state && (state->flag & V3D_SNAPCURSOR_TOGGLE_ALWAYS_TRUE))) {
-      if ((event->ctrl != data_intern->last_eventstate.ctrl) ||
-          (event->shift != data_intern->last_eventstate.shift) ||
-          (event->alt != data_intern->last_eventstate.alt) ||
-          (event->oskey != data_intern->last_eventstate.oskey)) {
+      if (event->modifier != data_intern->last_eventstate.modifier) {
         return true;
       }
     }
@@ -507,19 +504,13 @@ static bool v3d_cursor_is_snap_invert(SnapCursorDataIntern *data_intern, const w
   }
 
   const wmEvent *event = wm->winactive->eventstate;
-  if ((event->ctrl == data_intern->last_eventstate.ctrl) &&
-      (event->shift == data_intern->last_eventstate.shift) &&
-      (event->alt == data_intern->last_eventstate.alt) &&
-      (event->oskey == data_intern->last_eventstate.oskey)) {
+  if (event->modifier == data_intern->last_eventstate.modifier) {
     /* Nothing has changed. */
     return data_intern->snap_data.is_snap_invert;
   }
 
   /* Save new eventstate. */
-  data_intern->last_eventstate.ctrl = event->ctrl;
-  data_intern->last_eventstate.shift = event->shift;
-  data_intern->last_eventstate.alt = event->alt;
-  data_intern->last_eventstate.oskey = event->oskey;
+  data_intern->last_eventstate.modifier = event->modifier;
 
   const int snap_on = data_intern->snap_on;
 
@@ -530,10 +521,10 @@ static bool v3d_cursor_is_snap_invert(SnapCursorDataIntern *data_intern, const w
     }
 
     if (kmi->propvalue == snap_on) {
-      if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) && event->ctrl) ||
-          (ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) && event->shift) ||
-          (ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && event->alt) ||
-          ((kmi->type == EVT_OSKEY) && event->oskey)) {
+      if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) && (event->modifier & KM_CTRL)) ||
+          (ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) && (event->modifier & KM_SHIFT)) ||
+          (ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && (event->modifier & KM_ALT)) ||
+          ((kmi->type == EVT_OSKEY) && (event->modifier & KM_OSKEY))) {
         return true;
       }
     }
diff --git a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c
index 6cc197c8a43..a0c010a6813 100644
--- a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c
+++ b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.c
@@ -105,8 +105,8 @@ static int gizmo_preselect_elem_test_select(bContext *C, wmGizmo *gz, const int
   MeshElemGizmo3D *gz_ele = (MeshElemGizmo3D *)gz;
 
   /* Hack: Switch action mode based on key input */
-  const bool is_ctrl_pressed = WM_event_modifier_flag(event) & KM_CTRL;
-  const bool is_shift_pressed = WM_event_modifier_flag(event) & KM_SHIFT;
+  const bool is_ctrl_pressed = (event->modifier & KM_CTRL) != 0;
+  const bool is_shift_pressed = (event->modifier & KM_SHIFT) != 0;
   EDBM_preselect_action_set(gz_ele->psel, PRESELECT_ACTION_TRANSFORM);
   if (is_ctrl_pressed && !is_shift_pressed) {
     EDBM_preselect_action_set(gz_ele->psel, PRESELECT_ACTION_CREATE);
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index 923084854e1..b006b13d217 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -1153,7 +1153,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         if (event->is_repeat) {
           break;
         }
-        if (event->alt) {
+        if (event->modifier & KM_ALT) {
           if (!(t->options & CTX_NO_PET)) {
             t->flag ^= T_PROP_CONNECTED;
             sort_trans_data_dist(t);
@@ -1167,7 +1167,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         if (event->is_repeat) {
           break;
         }
-        if (t->flag & T_PROP_EDIT && event->shift) {
+        if ((t->flag & T_PROP_EDIT) && (event->modifier & KM_SHIFT)) {
           t->prop_mode = (t->prop_mode + 1) % PROP_MODE_MAX;
           calculatePropRatio(t);
           t->redraw |= TREDRAW_HARD;
@@ -1175,7 +1175,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         }
         break;
       case EVT_PADPLUSKEY:
-        if (event->alt && t->flag & T_PROP_EDIT) {
+        if ((event->modifier & KM_ALT) && (t->flag & T_PROP_EDIT)) {
           t->prop_size *= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
           if (t->spacetype == SPACE_VIEW3D && t->persp != RV3D_ORTHO) {
             t->prop_size = min_ff(t->prop_size, ((View3D *)t->view)->clip_end);
@@ -1186,7 +1186,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         }
         break;
       case EVT_PADMINUS:
-        if (event->alt && t->flag & T_PROP_EDIT) {
+        if ((event->modifier & KM_ALT) && (t->flag & T_PROP_EDIT)) {
           t->prop_size /= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
           calculatePropRatio(t);
           t->redraw = TREDRAW_HARD;
@@ -1780,10 +1780,12 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
         }
 
         if (kmi->propvalue == TFM_MODAL_SNAP_INV_ON && kmi->val == KM_PRESS) {
-          if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) && event->ctrl) ||
-              (ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) && event->shift) ||
-              (ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && event->alt) ||
-              ((kmi->type == EVT_OSKEY) && event->oskey)) {
+          if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) &&
+               (event->modifier & KM_CTRL)) ||
+              (ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) &&
+               (event->modifier & KM_SHIFT)) ||
+              (ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && (event->modifier & KM_ALT)) ||
+              ((kmi->type == EVT_OSKEY) && (event->modifier & KM_OSKEY))) {
             t->modifiers |= MOD_SNAP_INVERT;
           }
           break;
diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c
index 329840e064b..f07fadb7fc1 100644
--- a/source/blender/editors/transform/transform_gizmo_3d.c
+++ b/source/blender/editors/transform/transform_gizmo_3d.c
@@ -1869,7 +1869,7 @@ static void WIDGETGROUP_gizmo_invoke_prepare(const bContext *C,
   if (axis != -1) {
     wmWindow *win = CTX_wm_window(C);
     /* Swap single axis for two-axis constraint. */
-    bool flip = win->eventstate->shift;
+    bool flip = (win->eventstate->modifier & KM_SHIFT) != 0;
     BLI_assert(axis_idx != -1);
     const short axis_type = gizmo_get_axis_type(axis_idx);
     if (axis_type != MAN_AXES_ROTATE) {
diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c
index 37bc753517f..2f3c021ba38 100644
--- a/source/blender/editors/transform/transform_snap.c
+++ b/source/blender/editors/transform/transform_snap.c
@@ -308,7 +308,8 @@ eRedrawFlag handleSnapping(TransInfo *t, const wmEvent *event)
   eRedrawFlag status = TREDRAW_NOTHING;
 
 #if 0 /* XXX need a proper selector for all snap mode */
-  if (BIF_snappingSupported(t->obedit) && event->type == TABKEY && event->shift) {
+  if (BIF_snappingSupported(t->obedit) && (event->type == EVT_TABKEY) &&
+      (event->modifier & KM_SHIFT)) {
     /* toggle snap and reinit */
     t->settings->snap_flag ^= SCE_SNAP;
     initSnapping(t, NULL);
diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c
index 15fe944be49..32d405df841 100644
--- a/source/blender/editors/util/ed_util.c
+++ b/source/blender/editors/util/ed_util.c
@@ -306,7 +306,7 @@ bool ED_editors_flush_edits(Main *bmain)
 /* ***** XXX: functions are using old blender names, cleanup later ***** */
 
 void apply_keyb_grid(
-    int shift, int ctrl, float *val, float fac1, float fac2, float fac3, int invert)
+    bool shift, bool ctrl, float *val, float fac1, float fac2, float fac3, int invert)
 {
   /* fac1 is for 'nothing', fac2 for CTRL, fac3 for SHIFT */
   if (invert) {
diff --git a/source/blender/editors/util/numinput.c b/source/blender/editors/util/numinput.c
index 3bf226f6ba1..be6ac6e13e6 100644
--- a/source/blender/editors/util/numinput.c
+++ b/source/blender/editors/util/numinput.c
@@ -321,7 +321,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
   if (U.flag & USER_FLAG_NUMINPUT_ADVANCED)
 #endif
   {
-    if ((event->ctrl == 0) && (event->alt == 0) && (event->ascii != '\0') &&
+    if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event->ascii != '\0') &&
         strchr("01234567890@%^&*-+/{}()[]<>.|", event->ascii)) {
       if (!(n->flag & NUM_EDIT_FULL)) {
         n->flag |= NUM_EDITED;
@@ -339,7 +339,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
       n->val_flag[idx] |= NUM_EDITED;
       return true;
     }
-    if (event->ctrl) {
+    if (event->modifier & KM_CTRL) {
       n->flag &= ~NUM_EDIT_FULL;
       return true;
     }
@@ -375,7 +375,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
         updated = true;
         break;
       }
-      else if (event->shift || !n->str[0]) {
+      else if ((event->modifier & KM_SHIFT) || !n->str[0]) {
         n->val[idx] = n->val_org[idx];
         n->val_flag[idx] &= ~NUM_EDITED;
         n->str[0] = '\0';
@@ -390,7 +390,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
     case EVT_DELKEY:
       if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
         int t_cur = cur = n->str_cur;
-        if (event->ctrl) {
+        if (event->modifier & KM_CTRL) {
           mode = STRCUR_JUMP_DELIM;
         }
         BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true);
@@ -416,7 +416,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
       ATTR_FALLTHROUGH;
     case EVT_RIGHTARROWKEY:
       cur = n->str_cur;
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         mode = STRCUR_JUMP_DELIM;
       }
       BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true);
@@ -442,7 +442,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
       n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE);
 #endif
 
-      idx = (idx + idx_max + (event->ctrl ? 0 : 2)) % (idx_max + 1);
+      idx = (idx + idx_max + ((event->modifier & KM_CTRL) ? 0 : 2)) % (idx_max + 1);
       n->idx = idx;
       if (n->val_flag[idx] & NUM_EDITED) {
         value_to_editstr(n, idx);
@@ -470,7 +470,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
         n->val_flag[idx] |= NUM_EDITED;
         return true;
       }
-      else if (event->ctrl) {
+      else if (event->modifier & KM_CTRL) {
         n->flag &= ~NUM_EDIT_FULL;
         return true;
       }
@@ -480,28 +480,28 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
 #ifdef USE_FAKE_EDIT
     case EVT_PADMINUS:
     case EVT_MINUSKEY:
-      if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
+      if ((event->modifier & KM_CTRL) || !(n->flag & NUM_EDIT_FULL)) {
         n->val_flag[idx] ^= NUM_NEGATE;
         updated = true;
       }
       break;
     case EVT_PADSLASHKEY:
     case EVT_SLASHKEY:
-      if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
+      if ((event->modifier & KM_CTRL) || !(n->flag & NUM_EDIT_FULL)) {
         n->val_flag[idx] ^= NUM_INVERSE;
         updated = true;
       }
       break;
 #endif
     case EVT_CKEY:
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         /* Copy current `str` to the copy/paste buffer. */
         WM_clipboard_text_set(n->str, 0);
         updated = true;
       }
       break;
     case EVT_VKEY:
-      if (event->ctrl) {
+      if (event->modifier & KM_CTRL) {
         /* extract the first line from the clipboard */
         int pbuf_len;
         char *pbuf = WM_clipboard_text_get_firstline(false, &pbuf_len);
@@ -531,7 +531,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
   /* Up to this point, if we have a ctrl modifier, skip.
    * This allows to still access most of modals' shortcuts even in numinput mode.
    */
-  if (!updated && event->ctrl) {
+  if (!updated && (event->modifier & KM_CTRL)) {
     return false;
   }
 
diff --git a/source/blender/editors/uvedit/uvedit_smart_stitch.c b/source/blender/editors/uvedit/uvedit_smart_stitch.c
index 91b29049ecf..c0d0fe95c8c 100644
--- a/source/blender/editors/uvedit/uvedit_smart_stitch.c
+++ b/source/blender/editors/uvedit/uvedit_smart_stitch.c
@@ -2598,7 +2598,7 @@ static int stitch_modal(bContext *C, wmOperator *op, const wmEvent *event)
       /* Increase limit */
     case EVT_PADPLUSKEY:
     case WHEELUPMOUSE:
-      if (event->val == KM_PRESS && event->alt) {
+      if ((event->val == KM_PRESS) && (event->modifier & KM_ALT)) {
         ssc->limit_dist += 0.01f;
         if (!stitch_process_data(ssc, active_state, scene, false)) {
           stitch_cancel(C, op);
@@ -2612,7 +2612,7 @@ static int stitch_modal(bContext *C, wmOperator *op, const wmEvent *event)
       /* Decrease limit */
     case EVT_PADMINUS:
     case WHEELDOWNMOUSE:
-      if (event->val == KM_PRESS && event->alt) {
+      if ((event->val == KM_PRESS) && (event->modifier & KM_ALT)) {
         ssc->limit_dist -= 0.01f;
         ssc->limit_dist = MAX2(0.01f, ssc->limit_dist);
         if (!stitch_process_data(ssc, active_state, scene, false)) {
@@ -2673,7 +2673,7 @@ static int stitch_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
       /* Select geometry */
     case RIGHTMOUSE:
-      if (!event->shift) {
+      if ((event->modifier & KM_SHIFT) == 0) {
         stitch_cancel(C, op);
         return OPERATOR_CANCELLED;
       }
diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c
index b0021bd0f3d..16d1dc0e93f 100644
--- a/source/blender/makesrna/intern/rna_wm.c
+++ b/source/blender/makesrna/intern/rna_wm.c
@@ -2194,23 +2194,23 @@ static void rna_def_event(BlenderRNA *brna)
 
   /* modifiers */
   prop = RNA_def_property(srna, "shift", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "shift", 1);
+  RNA_def_property_boolean_sdna(prop, NULL, "modifier", KM_SHIFT);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "Shift", "True when the Shift key is held");
   RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_WINDOWMANAGER);
 
   prop = RNA_def_property(srna, "ctrl", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "ctrl", 1);
+  RNA_def_property_boolean_sdna(prop, NULL, "modifier", KM_CTRL);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "Ctrl", "True when the Ctrl key is held");
 
   prop = RNA_def_property(srna, "alt", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "alt", 1);
+  RNA_def_property_boolean_sdna(prop, NULL, "modifier", KM_ALT);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "Alt", "True when the Alt/Option key is held");
 
   prop = RNA_def_property(srna, "oskey", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "oskey", 1);
+  RNA_def_property_boolean_sdna(prop, NULL, "modifier", KM_OSKEY);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "OS Key", "True when the Cmd key is held");
 
diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c
index 81cc72088a6..dc53b7a5e12 100644
--- a/source/blender/makesrna/intern/rna_wm_api.c
+++ b/source/blender/makesrna/intern/rna_wm_api.c
@@ -639,10 +639,19 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win,
   e.xy[0] = x;
   e.xy[1] = y;
 
-  e.shift = shift;
-  e.ctrl = ctrl;
-  e.alt = alt;
-  e.oskey = oskey;
+  e.modifier = 0;
+  if (shift) {
+    e.modifier |= KM_SHIFT;
+  }
+  if (ctrl) {
+    e.modifier |= KM_CTRL;
+  }
+  if (alt) {
+    e.modifier |= KM_ALT;
+  }
+  if (oskey) {
+    e.modifier |= KM_OSKEY;
+  }
 
   e.ascii = '\0';
   e.utf8_buf[0] = '\0';
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index b00d441dd5d..ef8aed4c664 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -1427,8 +1427,6 @@ bool WM_window_modal_keymap_status_draw(struct bContext *C,
  */
 void WM_event_print(const struct wmEvent *event);
 
-int WM_event_modifier_flag(const struct wmEvent *event);
-
 /**
  * For modal callbacks, check configuration for how to interpret exit with tweaks.
  */
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index a95c2ee2bb2..2f431bcd208 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -635,9 +635,12 @@ typedef struct wmEvent {
    */
   int prev_xy[2];
 
-  /** Modifier states. */
-  /** 'oskey' is apple or windows-key, value denotes order of pressed. */
-  short shift, ctrl, alt, oskey;
+  /**
+   * Modifier states.
+   * #KM_SHIFT, #KM_CTRL, #KM_ALT & #KM_OSKEY is apple or windows-key.
+   */
+  uint8_t modifier;
+
   /** Raw-key modifier (allow using any key as a modifier). */
   short keymodifier;
 
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
index ad902f0963e..f1ac19f4651 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
@@ -819,8 +819,6 @@ wmGizmo *wm_gizmomap_highlight_find(wmGizmoMap *gzmap,
     do_step[i] = WM_gizmo_context_check_drawstep(C, i);
   }
 
-  const int event_modifier = WM_event_modifier_flag(event);
-
   LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, &gzmap->groups) {
 
     /* If it were important we could initialize here,
@@ -839,11 +837,11 @@ wmGizmo *wm_gizmomap_highlight_find(wmGizmoMap *gzmap,
         }
         if (step == WM_GIZMOMAP_DRAWSTEP_3D) {
           wm_gizmogroup_intersectable_gizmos_to_list(
-              wm, gzgroup, event_modifier, &visible_3d_gizmos);
+              wm, gzgroup, event->modifier, &visible_3d_gizmos);
         }
         else if (step == WM_GIZMOMAP_DRAWSTEP_2D) {
           if ((gz = wm_gizmogroup_find_intersected_gizmo(
-                   wm, gzgroup, C, event_modifier, event->mval, r_part))) {
+                   wm, gzgroup, C, event->modifier, event->mval, r_part))) {
             break;
           }
         }
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index 304d7f73eb1..751dcc61fa9 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -80,10 +80,10 @@ void WM_event_print(const wmEvent *event)
         prev_type_id,
         event->prev_val,
         prev_val_id,
-        event->shift,
-        event->ctrl,
-        event->alt,
-        event->oskey,
+        (event->modifier & KM_SHIFT) != 0,
+        (event->modifier & KM_CTRL) != 0,
+        (event->modifier & KM_ALT) != 0,
+        (event->modifier & KM_OSKEY) != 0,
         event->keymodifier,
         event->is_repeat,
         event->xy[0],
@@ -129,24 +129,6 @@ void WM_event_print(const wmEvent *event)
 /** \name Event Modifier/Type Queries
  * \{ */
 
-int WM_event_modifier_flag(const wmEvent *event)
-{
-  int flag = 0;
-  if (event->ctrl) {
-    flag |= KM_CTRL;
-  }
-  if (event->alt) {
-    flag |= KM_ALT;
-  }
-  if (event->shift) {
-    flag |= KM_SHIFT;
-  }
-  if (event->oskey) {
-    flag |= KM_OSKEY;
-  }
-  return flag;
-}
-
 bool WM_event_type_mask_test(const int event_type, const enum eEventType_Mask mask)
 {
   /* Keyboard. */
@@ -491,8 +473,8 @@ int WM_event_absolute_delta_y(const struct wmEvent *event)
  */
 bool WM_event_is_ime_switch(const struct wmEvent *event)
 {
-  return event->val == KM_PRESS && event->type == EVT_SPACEKEY &&
-         (event->ctrl || event->oskey || event->alt);
+  return (event->val == KM_PRESS) && (event->type == EVT_SPACEKEY) &&
+         (event->modifier & (KM_CTRL | KM_OSKEY | KM_ALT));
 }
 #endif
 
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 8a19c99f59b..326b8c167b8 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -2029,29 +2029,33 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi)
     }
   }
 
+  const bool shift = (winevent->modifier & KM_SHIFT) != 0;
+  const bool ctrl = (winevent->modifier & KM_CTRL) != 0;
+  const bool alt = (winevent->modifier & KM_ALT) != 0;
+  const bool oskey = (winevent->modifier & KM_OSKEY) != 0;
+
   /* Modifiers also check bits, so it allows modifier order.
    * Account for rare case of when these keys are used as the 'type' not as modifiers. */
   if (kmi->shift != KM_ANY) {
-    if ((winevent->shift != kmi->shift) && !(winevent->shift & kmi->shift) &&
+    if ((shift != kmi->shift) && !(shift & kmi->shift) &&
         !ELEM(winevent->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY)) {
       return false;
     }
   }
   if (kmi->ctrl != KM_ANY) {
-    if (winevent->ctrl != kmi->ctrl && !(winevent->ctrl & kmi->ctrl) &&
+    if (ctrl != kmi->ctrl && !(ctrl & kmi->ctrl) &&
         !ELEM(winevent->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY)) {
       return false;
     }
   }
   if (kmi->alt != KM_ANY) {
-    if (winevent->alt != kmi->alt && !(winevent->alt & kmi->alt) &&
+    if (alt != kmi->alt && !(alt & kmi->alt) &&
         !ELEM(winevent->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY)) {
       return false;
     }
   }
   if (kmi->oskey != KM_ANY) {
-    if (winevent->oskey != kmi->oskey && !(winevent->oskey & kmi->oskey) &&
-        (winevent->type != EVT_OSKEY)) {
+    if (oskey != kmi->oskey && !(oskey & kmi->oskey) && (winevent->type != EVT_OSKEY)) {
       return false;
     }
   }
@@ -4487,19 +4491,18 @@ static void wm_eventemulation(wmEvent *event, bool test_only)
   if (U.flag & USER_TWOBUTTONMOUSE) {
 
     if (event->type == LEFTMOUSE) {
-      short *mod = (
+      const uint8_t mod_test = (
 #if !defined(WIN32)
-          (U.mouse_emulate_3_button_modifier == USER_EMU_MMB_MOD_OSKEY) ? &event->oskey :
-                                                                          &event->alt
+          (U.mouse_emulate_3_button_modifier == USER_EMU_MMB_MOD_OSKEY) ? KM_OSKEY : KM_ALT
 #else
           /* Disable for WIN32 for now because it accesses the start menu. */
-          &event->alt
+          KM_ALT
 #endif
       );
 
       if (event->val == KM_PRESS) {
-        if (*mod) {
-          *mod = 0;
+        if (event->modifier & mod_test) {
+          event->modifier &= ~mod_test;
           event->type = MIDDLEMOUSE;
 
           if (!test_only) {
@@ -4511,7 +4514,7 @@ static void wm_eventemulation(wmEvent *event, bool test_only)
         /* Only send middle-mouse release if emulated. */
         if (emulating_event == MIDDLEMOUSE) {
           event->type = MIDDLEMOUSE;
-          *mod = 0;
+          event->modifier &= ~mod_test;
         }
 
         if (!test_only) {
@@ -4931,7 +4934,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
     case GHOST_kEventKeyDown:
     case GHOST_kEventKeyUp: {
       GHOST_TEventKeyData *kd = customdata;
-      short keymodifier = KM_NOTHING;
+      bool keymodifier = 0;
       event.type = convert_key(kd->key);
       event.ascii = kd->ascii;
       /* Might be not NULL terminated. */
@@ -4981,29 +4984,57 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
         case EVT_LEFTSHIFTKEY:
         case EVT_RIGHTSHIFTKEY:
           if (event.val == KM_PRESS) {
-            keymodifier = KM_MOD_HELD;
+            keymodifier = true;
+          }
+          if (keymodifier) {
+            event.modifier |= KM_SHIFT;
+            event_state->modifier |= KM_SHIFT;
+          }
+          else {
+            event.modifier &= ~KM_SHIFT;
+            event_state->modifier &= ~KM_SHIFT;
           }
-          event.shift = event_state->shift = keymodifier;
           break;
         case EVT_LEFTCTRLKEY:
         case EVT_RIGHTCTRLKEY:
           if (event.val == KM_PRESS) {
-            keymodifier = KM_MOD_HELD;
+            keymodifier = true;
+          }
+          if (keymodifier) {
+            event.modifier |= KM_CTRL;
+            event_state->modifier |= KM_CTRL;
+          }
+          else {
+            event.modifier &= ~KM_CTRL;
+            event_state->modifier &= ~KM_CTRL;
           }
-          event.ctrl = event_state->ctrl = keymodifier;
           break;
         case EVT_LEFTALTKEY:
         case EVT_RIGHTALTKEY:
           if (event.val == KM_PRESS) {
-            keymodifier = KM_MOD_HELD;
+            keymodifier = true;
+          }
+          if (keymodifier) {
+            event.modifier |= KM_ALT;
+            event_state->modifier |= KM_ALT;
+          }
+          else {
+            event.modifier &= ~KM_ALT;
+            event_state->modifier &= ~KM_ALT;
           }
-          event.alt = event_state->alt = keymodifier;
           break;
         case EVT_OSKEY:
           if (event.val == KM_PRESS) {
-            keymodifier = KM_MOD_HELD;
+            keymodifier = true;
+          }
+          if (keymodifier) {
+            event.modifier |= KM_OSKEY;
+            event_state->modifier |= KM_OSKEY;
+          }
+          else {
+            event.modifier &= ~KM_OSKEY;
+            event_state->modifier &= ~KM_OSKEY;
           }
-          event.oskey = event_state->oskey = keymodifier;
           break;
         default:
           if (event.val == KM_PRESS && event.keymodifier == 0) {
@@ -5041,7 +5072,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
        * XXX Keep global for now? */
       if ((event.type == EVT_ESCKEY && event.val == KM_PRESS) &&
           /* Check other modifiers because ms-windows uses these to bring up the task manager. */
-          (event.shift == 0 && event.ctrl == 0 && event.alt == 0)) {
+          ((event.modifier & (KM_SHIFT | KM_CTRL | KM_ALT)) == 0)) {
         G.is_break = true;
       }
 
@@ -5301,9 +5332,7 @@ wmKeyMapItem *WM_event_match_keymap_item_from_handlers(
 
 /** State storage to detect changes between calls to refresh the information. */
 struct CursorKeymapInfo_State {
-  struct {
-    short shift, ctrl, alt, oskey;
-  } modifiers;
+  uint8_t modifier;
   short space_type;
   short region_type;
   /* Never use, just compare memory for changes. */
@@ -5326,10 +5355,7 @@ static void wm_event_cursor_store(struct CursorKeymapInfo_State *state,
                                   short region_type,
                                   const bToolRef *tref)
 {
-  state->modifiers.shift = event->shift;
-  state->modifiers.ctrl = event->ctrl;
-  state->modifiers.alt = event->alt;
-  state->modifiers.oskey = event->oskey;
+  state->modifier = event->modifier;
   state->space_type = space_type;
   state->region_type = region_type;
   state->tref = tref ? *tref : (bToolRef){0};
diff --git a/source/blender/windowmanager/intern/wm_operator_utils.c b/source/blender/windowmanager/intern/wm_operator_utils.c
index 6f3f42bee53..5a817075cd5 100644
--- a/source/blender/windowmanager/intern/wm_operator_utils.c
+++ b/source/blender/windowmanager/intern/wm_operator_utils.c
@@ -115,11 +115,11 @@ static bool interactive_value_update(ValueInteraction *inter,
                        (((float)(mval_curr - mval_init) / inter->context_vars.region->winx) *
                         value_range)) *
                       value_scale;
-  if (event->ctrl) {
+  if (event->modifier & KM_CTRL) {
     const double snap = 0.1;
     value_delta = (float)roundf((double)value_delta / snap) * snap;
   }
-  if (event->shift) {
+  if (event->modifier & KM_SHIFT) {
     value_delta *= 0.1f;
   }
   const float value_final = inter->init.prop_value + value_delta;
@@ -133,8 +133,8 @@ static bool interactive_value_update(ValueInteraction *inter,
   }
 
   inter->prev.prop_value = value_final;
-  inter->prev.is_snap = event->ctrl;
-  inter->prev.is_precise = event->shift;
+  inter->prev.is_snap = (event->modifier & KM_CTRL) != 0;
+  inter->prev.is_precise = (event->modifier & KM_SHIFT) != 0;
 
   *r_value_final = value_final;
   return changed;
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index a476fb4fa13..6b8f7309d80 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -2822,7 +2822,7 @@ static int radial_control_modal(bContext *C, wmOperator *op, const wmEvent *even
   float numValue;
   /* TODO: fix hardcoded events */
 
-  bool snap = event->ctrl != 0;
+  bool snap = (event->modifier & KM_CTRL) != 0;
 
   /* Modal numinput active, try to handle numeric inputs first... */
   if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &rc->num_input, event)) {
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index a983150b504..cdfb6a81596 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -1104,10 +1104,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
         win->active = 0; /* XXX */
 
         /* clear modifiers for inactive windows */
-        win->eventstate->alt = 0;
-        win->eventstate->ctrl = 0;
-        win->eventstate->shift = 0;
-        win->eventstate->oskey = 0;
+        win->eventstate->modifier = 0;
         win->eventstate->keymodifier = 0;
 
         break;
@@ -1138,7 +1135,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
         kdata.ascii = '\0';
         kdata.utf8_buf[0] = '\0';
 
-        if (win->eventstate->shift) {
+        if (win->eventstate->modifier & KM_SHIFT) {
           if ((keymodifier & KM_SHIFT) == 0) {
             kdata.key = GHOST_kKeyLeftShift;
             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
@@ -1147,11 +1144,11 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
 #ifdef USE_WIN_ACTIVATE
         else {
           if (keymodifier & KM_SHIFT) {
-            win->eventstate->shift = KM_MOD_HELD;
+            win->eventstate->modifier |= KM_SHIFT;
           }
         }
 #endif
-        if (win->eventstate->ctrl) {
+        if (win->eventstate->modifier & KM_CTRL) {
           if ((keymodifier & KM_CTRL) == 0) {
             kdata.key = GHOST_kKeyLeftControl;
             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
@@ -1160,11 +1157,11 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
 #ifdef USE_WIN_ACTIVATE
         else {
           if (keymodifier & KM_CTRL) {
-            win->eventstate->ctrl = KM_MOD_HELD;
+            win->eventstate->modifier |= KM_CTRL;
           }
         }
 #endif
-        if (win->eventstate->alt) {
+        if (win->eventstate->modifier & KM_ALT) {
           if ((keymodifier & KM_ALT) == 0) {
             kdata.key = GHOST_kKeyLeftAlt;
             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
@@ -1173,11 +1170,11 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
 #ifdef USE_WIN_ACTIVATE
         else {
           if (keymodifier & KM_ALT) {
-            win->eventstate->alt = KM_MOD_HELD;
+            win->eventstate->modifier |= KM_ALT;
           }
         }
 #endif
-        if (win->eventstate->oskey) {
+        if (win->eventstate->modifier & KM_OSKEY) {
           if ((keymodifier & KM_OSKEY) == 0) {
             kdata.key = GHOST_kKeyOS;
             wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata);
@@ -1186,7 +1183,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
 #ifdef USE_WIN_ACTIVATE
         else {
           if (keymodifier & KM_OSKEY) {
-            win->eventstate->oskey = KM_MOD_HELD;
+            win->eventstate->modifier |= KM_OSKEY;
           }
         }
 #endif
diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h
index 0ff181db9b1..c5764ad7a5e 100644
--- a/source/blender/windowmanager/wm_event_types.h
+++ b/source/blender/windowmanager/wm_event_types.h
@@ -408,15 +408,6 @@ enum {
   ((ISKEYBOARD(event_type) || ISMOUSE(event_type) || ISNDOF(event_type)) && \
    (ISKEYMODIFIER(event_type) == false))
 
-/* Internal helpers. */
-#define _VA_IS_EVENT_MOD2(v, a) (CHECK_TYPE_INLINE(v, wmEvent *), ((v)->a))
-#define _VA_IS_EVENT_MOD3(v, a, b) (_VA_IS_EVENT_MOD2(v, a) || ((v)->b))
-#define _VA_IS_EVENT_MOD4(v, a, b, c) (_VA_IS_EVENT_MOD3(v, a, b) || ((v)->c))
-#define _VA_IS_EVENT_MOD5(v, a, b, c, d) (_VA_IS_EVENT_MOD4(v, a, b, c) || ((v)->d))
-
-/** Reusable `IS_EVENT_MOD(event, shift, ctrl, alt, oskey)` macro. */
-#define IS_EVENT_MOD(...) VA_NARGS_CALL_OVERLOAD(_VA_IS_EVENT_MOD, __VA_ARGS__)
-
 enum eEventType_Mask {
   /** #ISKEYMODIFIER */
   EVT_TYPE_MASK_KEYBOARD_MODIFIER = (1 << 0),
-- 
cgit v1.2.3


From 5fd792c1f6d348411469d5b9744c55106d317375 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Mon, 14 Feb 2022 16:30:09 +0100
Subject: GPencil: Fill Dilate using negative values contract the fill area

This is requested by artist for some animation styles where is necessary to fill the area, but create a gap between fill and stroke.

Also some code cleanup and fix a bug in dilate for top area.

Reviewed By: pepeland, mendio

Differential Revision: https://developer.blender.org/D14082

Note: This was committed only in master (3.2) by error.
---
 source/blender/editors/gpencil/gpencil_fill.c | 155 +++++++++++++++++++++-----
 source/blender/makesrna/intern/rna_brush.c    |   7 +-
 2 files changed, 132 insertions(+), 30 deletions(-)

diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 541b6673cb6..9b4f4470356 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -1020,7 +1020,6 @@ static void gpencil_invert_image(tGPDfill *tgpf)
   ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
 
   const int maxpixel = (ibuf->x * ibuf->y) - 1;
-  const int center = ibuf->x / 2;
 
   for (int v = maxpixel; v != 0; v--) {
     float color[4];
@@ -1032,15 +1031,6 @@ static void gpencil_invert_image(tGPDfill *tgpf)
     /* Red->Green */
     else if (color[0] == 1.0f) {
       set_pixel(ibuf, v, fill_col[1]);
-      /* Add thickness of 2 pixels to avoid too thin lines, but avoid extremes of the pixel line.
-       */
-      int row = v / ibuf->x;
-      int lowpix = row * ibuf->x;
-      int highpix = lowpix + ibuf->x - 1;
-      if ((v > lowpix) && (v < highpix)) {
-        int offset = (v % ibuf->x < center) ? 1 : -1;
-        set_pixel(ibuf, v + offset, fill_col[1]);
-      }
     }
     else {
       /* Set to Transparent. */
@@ -1136,11 +1126,14 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf)
  */
 static bool dilate_shape(ImBuf *ibuf)
 {
+#define IS_RED (color[0] == 1.0f)
+#define IS_GREEN (color[1] == 1.0f)
+
   bool done = false;
 
   BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__);
   const float green[4] = {0.0f, 1.0f, 0.0f, 1.0f};
-  // const int maxpixel = (ibuf->x * ibuf->y) - 1;
+  const int max_size = (ibuf->x * ibuf->y) - 1;
   /* detect pixels and expand into red areas */
   for (int row = 0; row < ibuf->y; row++) {
     if (!is_row_filled(ibuf, row)) {
@@ -1153,7 +1146,7 @@ static bool dilate_shape(ImBuf *ibuf)
       float color[4];
       int index;
       get_pixel(ibuf, v, color);
-      if (color[1] == 1.0f) {
+      if (IS_GREEN) {
         int tp = 0;
         int bm = 0;
         int lt = 0;
@@ -1163,7 +1156,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v - 1 >= 0) {
           index = v - 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
             lt = index;
           }
@@ -1172,25 +1165,25 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v + 1 <= maxpixel) {
           index = v + 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
             rt = index;
           }
         }
         /* pixel top */
-        if (v + (ibuf->x * 1) <= maxpixel) {
-          index = v + (ibuf->x * 1);
+        if (v + ibuf->x <= max_size) {
+          index = v + ibuf->x;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
             tp = index;
           }
         }
         /* pixel bottom */
-        if (v - (ibuf->x * 1) >= 0) {
-          index = v - (ibuf->x * 1);
+        if (v - ibuf->x >= 0) {
+          index = v - ibuf->x;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
             bm = index;
           }
@@ -1199,7 +1192,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && lt) {
           index = tp - 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1207,7 +1200,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && rt) {
           index = tp + 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1215,7 +1208,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && lt) {
           index = bm - 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1223,7 +1216,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && rt) {
           index = bm + 1;
           get_pixel(ibuf, index, color);
-          if (color[0] == 1.0f) {
+          if (IS_RED) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1240,6 +1233,88 @@ static bool dilate_shape(ImBuf *ibuf)
   BLI_stack_free(stack);
 
   return done;
+
+#undef IS_RED
+#undef IS_GREEN
+}
+
+/**
+ * Contract
+ *
+ * Contract green areas to scale down the size.
+ * Using stack prevents creep when replacing colors directly.
+ */
+static bool contract_shape(ImBuf *ibuf)
+{
+#define IS_GREEN (color[1] == 1.0f)
+#define IS_NOT_GREEN (color[1] != 1.0f)
+
+  bool done = false;
+
+  BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__);
+  const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+  const int max_size = (ibuf->x * ibuf->y) - 1;
+
+  /* detect pixels and expand into red areas */
+  for (int row = 0; row < ibuf->y; row++) {
+    if (!is_row_filled(ibuf, row)) {
+      continue;
+    }
+    int maxpixel = (ibuf->x * (row + 1)) - 1;
+    int minpixel = ibuf->x * row;
+
+    for (int v = maxpixel; v != minpixel; v--) {
+      float color[4];
+      get_pixel(ibuf, v, color);
+      if (IS_GREEN) {
+        /* pixel left */
+        if (v - 1 >= 0) {
+          get_pixel(ibuf, v - 1, color);
+          if (IS_NOT_GREEN) {
+            BLI_stack_push(stack, &v);
+            continue;
+          }
+        }
+        /* pixel right */
+        if (v + 1 <= maxpixel) {
+          get_pixel(ibuf, v + 1, color);
+          if (IS_NOT_GREEN) {
+            BLI_stack_push(stack, &v);
+            continue;
+          }
+        }
+        /* pixel top */
+        if (v + ibuf->x <= max_size) {
+          get_pixel(ibuf, v + ibuf->x, color);
+          if (IS_NOT_GREEN) {
+            BLI_stack_push(stack, &v);
+            continue;
+          }
+        }
+        /* pixel bottom */
+        if (v - ibuf->x >= 0) {
+          get_pixel(ibuf, v - ibuf->x, color);
+          if (IS_NOT_GREEN) {
+            BLI_stack_push(stack, &v);
+            continue;
+          }
+        }
+      }
+    }
+  }
+  /* Clear pixels. */
+  while (!BLI_stack_is_empty(stack)) {
+    int v;
+    BLI_stack_pop(stack, &v);
+    set_pixel(ibuf, v, clear);
+    done = true;
+  }
+  BLI_stack_free(stack);
+
+  return done;
+
+#undef IS_GREEN
+#undef IS_NOT_GREEN
 }
 
 /* Get the outline points of a shape using Moore Neighborhood algorithm
@@ -1281,10 +1356,15 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate)
   ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
   int imagesize = ibuf->x * ibuf->y;
 
-  /* Dilate. */
+  /* Dilate or contract. */
   if (dilate) {
-    for (int i = 0; i < brush->gpencil_settings->dilate_pixels; i++) {
-      dilate_shape(ibuf);
+    for (int i = 0; i < abs(brush->gpencil_settings->dilate_pixels); i++) {
+      if (brush->gpencil_settings->dilate_pixels > 0) {
+        dilate_shape(ibuf);
+      }
+      else {
+        contract_shape(ibuf);
+      }
     }
   }
 
@@ -1991,6 +2071,24 @@ static void gpencil_zoom_level_set(tGPDfill *tgpf)
   }
 }
 
+static bool gpencil_find_and_mark_empty_areas(tGPDfill *tgpf)
+{
+  ImBuf *ibuf;
+  void *lock;
+  const float blue_col[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+  ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
+  const int maxpixel = (ibuf->x * ibuf->y) - 1;
+  float rgba[4];
+  for (int i = 0; i < maxpixel; i++) {
+    get_pixel(ibuf, i, rgba);
+    if (rgba[3] == 0.0f) {
+      set_pixel(ibuf, i, blue_col);
+      return true;
+    }
+  }
+  return false;
+}
+
 static bool gpencil_do_frame_fill(tGPDfill *tgpf, const bool is_inverted)
 {
   wmWindow *win = CTX_wm_window(tgpf->C);
@@ -2011,6 +2109,9 @@ static bool gpencil_do_frame_fill(tGPDfill *tgpf, const bool is_inverted)
       /* Invert direction if press Ctrl. */
       if (is_inverted) {
         gpencil_invert_image(tgpf);
+        while (gpencil_find_and_mark_empty_areas(tgpf)) {
+          gpencil_boundaryfill_area(tgpf);
+        }
       }
 
       /* Clean borders to avoid infinite loops. */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index b4cf15ebfc6..7a5877ec4ce 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -1629,12 +1629,13 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
       prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable");
   RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
 
-  /* Number of pixels to dilate fill area. */
+  /* Number of pixels to dilate fill area. Negative values contract the filled area. */
   prop = RNA_def_property(srna, "dilate", PROP_INT, PROP_PIXEL);
   RNA_def_property_int_sdna(prop, NULL, "dilate_pixels");
-  RNA_def_property_range(prop, 0, 20);
+  RNA_def_property_range(prop, -40, 40);
   RNA_def_property_int_default(prop, 1);
-  RNA_def_property_ui_text(prop, "Dilate", "Number of pixels to dilate fill area");
+  RNA_def_property_ui_text(
+      prop, "Dilate/Contract", "Number of pixels to expand or contract fill area");
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
 
-- 
cgit v1.2.3


From 5186a28dec79a3722a516314cede5fcdd1c07376 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Tue, 22 Feb 2022 20:03:34 +0100
Subject: GPencil: Make Fill Dilate expand outside stroke

To keep consistency with the new contract option, the dilate now expand the shape beyond the internal closed area.

Note: This was committed only in master (3.2) by error.
---
 source/blender/editors/gpencil/gpencil_fill.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 9b4f4470356..fcb97e8faf4 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -1113,7 +1113,7 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf)
 /**
  * Naive dilate
  *
- * Expand green areas into enclosing red areas.
+ * Expand green areas into enclosing red or transparent areas.
  * Using stack prevents creep when replacing colors directly.
  * 
  * -----------
@@ -1126,8 +1126,8 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf)
  */
 static bool dilate_shape(ImBuf *ibuf)
 {
-#define IS_RED (color[0] == 1.0f)
 #define IS_GREEN (color[1] == 1.0f)
+#define IS_NOT_GREEN (color[1] != 1.0f)
 
   bool done = false;
 
@@ -1156,7 +1156,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v - 1 >= 0) {
           index = v - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             lt = index;
           }
@@ -1165,7 +1165,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v + 1 <= maxpixel) {
           index = v + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             rt = index;
           }
@@ -1174,7 +1174,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v + ibuf->x <= max_size) {
           index = v + ibuf->x;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             tp = index;
           }
@@ -1183,7 +1183,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (v - ibuf->x >= 0) {
           index = v - ibuf->x;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
             bm = index;
           }
@@ -1192,7 +1192,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && lt) {
           index = tp - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1200,7 +1200,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (tp && rt) {
           index = tp + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1208,7 +1208,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && lt) {
           index = bm - 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1216,7 +1216,7 @@ static bool dilate_shape(ImBuf *ibuf)
         if (bm && rt) {
           index = bm + 1;
           get_pixel(ibuf, index, color);
-          if (IS_RED) {
+          if (IS_NOT_GREEN) {
             BLI_stack_push(stack, &index);
           }
         }
@@ -1234,8 +1234,8 @@ static bool dilate_shape(ImBuf *ibuf)
 
   return done;
 
-#undef IS_RED
 #undef IS_GREEN
+#undef IS_NOT_GREEN
 }
 
 /**
-- 
cgit v1.2.3


From 4c9846eeae03c8224d9d5dd9d26cdcf9a43da252 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Fri, 25 Feb 2022 11:24:45 +0100
Subject: Cleanup: Modify comment

---
 source/blender/editors/gpencil/gpencil_fill.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 2ff12d2246c..069493025dc 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -1239,7 +1239,7 @@ static bool contract_shape(ImBuf *ibuf)
   const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f};
   const int max_size = (ibuf->x * ibuf->y) - 1;
 
-  /* detect pixels and expand into red areas */
+  /* Detect if pixel is near of no green pixels and mark green pixel to be cleared. */
   for (int row = 0; row < ibuf->y; row++) {
     if (!is_row_filled(ibuf, row)) {
       continue;
-- 
cgit v1.2.3


From 66328db703bba8196f159d15d9632c34d845892d Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Fri, 25 Feb 2022 11:37:55 +0100
Subject: Fix T95636: Dragging Material from Asset Browser (Link mode) to
 Viewport empty space removes this material from all objects

Trust user count to actually delete or not the dragged ID when current
dragging is cancelled, since it may be already used by others.

NOTE: This is more a band-aid fix than anything else, cancelling drag
has a lot of other issues here (like never deleting any indirectly
linked/appended data, etc.). It needs a proper rethink in general.
---
 source/blender/windowmanager/intern/wm_dragdrop.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c
index 96cb66b44ea..9610960d178 100644
--- a/source/blender/windowmanager/intern/wm_dragdrop.c
+++ b/source/blender/windowmanager/intern/wm_dragdrop.c
@@ -620,8 +620,12 @@ void WM_drag_free_imported_drag_ID(struct Main *bmain, wmDrag *drag, wmDropBox *
   }
 
   ID *id = BKE_libblock_find_name(bmain, asset_drag->id_type, name);
-  if (id) {
-    BKE_id_delete(bmain, id);
+  if (id != NULL) {
+    /* Do not delete the dragged ID if it has any user, otherwise if it is a 're-used' ID it will
+     * cause T95636. Note that we need first to add the user that we want to remove in
+     * #BKE_id_free_us. */
+    id_us_plus(id);
+    BKE_id_free_us(bmain, id);
   }
 }
 
-- 
cgit v1.2.3


From 0bac962fe51be9ea98c24f27ca56700ddc84e646 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 25 Feb 2022 21:47:07 +1100
Subject: CMake: exclude add-ons that don't confirm to key requirements

These add-ons don't have documentation on blender.org, see: T95442

These can be re-enabled once documentation has been relocated.
---
 source/creator/CMakeLists.txt | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index bc4d912405c..8d7728d0577 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -389,6 +389,12 @@ if(WITH_PYTHON)
     PATTERN "__pycache__" EXCLUDE
     PATTERN "${ADDON_EXCLUDE_CONDITIONAL}" EXCLUDE
     PATTERN "${FREESTYLE_EXCLUDE_CONDITIONAL}" EXCLUDE
+
+	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
+	  PATTERN "addons/amaranth" EXCLUDE
+	  PATTERN "addons/mesh_tiny_cad" EXCLUDE
+	  PATTERN "addons/mesh_tissue" EXCLUDE
+	  PATTERN "addons/real_snow.py" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From 1a853a9e90e9f1a8a5028bd1ce37785e1b2ce6d5 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Fri, 25 Feb 2022 12:15:56 +0100
Subject: LibOverrides: fix handling of hierarchy root in complex cases.

This affects essentially the Outliner 'create hierarchy' tool currenlty.
Previously code did not handle properly hierarchy root in case overrides
where created from a non-root ID (e.g. an object inside of a linked
collection), and in case additional partial overrides were added to an
existing partially overrided hierarchy.

Also did some renaming on the go to avoid using 'reference' in override
context for anything else but the reference linked IDs.
---
 source/blender/blenkernel/BKE_lib_override.h       | 29 +++++--
 source/blender/blenkernel/intern/lib_override.c    | 85 ++++++++++++++-----
 .../intern/lib_override_proxy_conversion.c         |  4 +-
 source/blender/editors/object/object_relations.c   |  2 +-
 .../editors/space_outliner/outliner_tools.cc       | 99 ++++++++++++++++++----
 source/blender/makesrna/intern/rna_ID.c            |  6 +-
 6 files changed, 172 insertions(+), 53 deletions(-)

diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h
index 858bc2f5625..98301ca7a70 100644
--- a/source/blender/blenkernel/BKE_lib_override.h
+++ b/source/blender/blenkernel/BKE_lib_override.h
@@ -92,7 +92,13 @@ struct ID *BKE_lib_override_library_create_from_id(struct Main *bmain,
  *
  * \param id_hierarchy_root: the override ID that is the root of the hierarchy. May be NULL, in
  * which case it is assumed that the given `id_root_reference` is tagged for override, and its
- * newly created override will be used as hierarchy root.
+ * newly created override will be used as hierarchy root. Must be NULL if
+ * `id_hierarchy_root_reference` is not NULL.
+ *
+ * \param id_hierarchy_root_reference: the linked ID that is the root of the hierarchy. Must be
+ * tagged for override. May be NULL, in which case it is assumed that the given `id_root_reference`
+ * is tagged for override, and its newly created override will be used as hierarchy root. Must be
+ * NULL if `id_hierarchy_root` is not NULL.
  *
  * \param do_no_main: Create the new override data outside of Main database.
  * Used for resyncing of linked overrides.
@@ -103,6 +109,7 @@ bool BKE_lib_override_library_create_from_tag(struct Main *bmain,
                                               struct Library *owner_library,
                                               const struct ID *id_root_reference,
                                               struct ID *id_hierarchy_root,
+                                              const struct ID *id_hierarchy_root_reference,
                                               bool do_no_main);
 /**
  * Advanced 'smart' function to create fully functional overrides.
@@ -119,11 +126,18 @@ bool BKE_lib_override_library_create_from_tag(struct Main *bmain,
  * \param owner_library: the library in which the overrides should be created. Besides versioning
  * and resync code path, this should always be NULL (i.e. the local .blend file).
  *
- * \param id_root: The root ID to create an override from.
+ * \param id_root_reference: The linked root ID to create an override from. May be a sub-root of
+ * the overall hierarchy, in which case calling code is expected to have already tagged required
+ * 'path' of IDs leading from the given `id_hierarchy_root` to the given `id_root`.
+ *
+ * \param id_hierarchy_root_reference: The ID to be used a hierarchy root of the overrides to be
+ * created. Can be either the linked root ID of the whole override hierarchy, (typically the same
+ * as `id_root`, unless a sub-part only of the hierarchy is overridden), or the already existing
+ * override hierarchy root if part of the hierarchy is already overridden.
  *
- * \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 id_instance_hint: Some ID used as hint/reference 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.
  *
@@ -133,8 +147,9 @@ bool BKE_lib_override_library_create(struct Main *bmain,
                                      struct Scene *scene,
                                      struct ViewLayer *view_layer,
                                      struct Library *owner_library,
-                                     struct ID *id_root,
-                                     struct ID *id_reference,
+                                     struct ID *id_root_reference,
+                                     struct ID *id_hierarchy_root_reference,
+                                     struct ID *id_instance_hint,
                                      struct ID **r_id_root_override);
 /**
  * Create a library override template.
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c
index d9418d7ec03..922c1beda38 100644
--- a/source/blender/blenkernel/intern/lib_override.c
+++ b/source/blender/blenkernel/intern/lib_override.c
@@ -321,17 +321,36 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
   return local_id;
 }
 
+/* TODO: Make this static local function instead? API is becoming complex, and it's not used
+ * outside of this file anyway. */
 bool BKE_lib_override_library_create_from_tag(Main *bmain,
                                               Library *owner_library,
                                               const ID *id_root_reference,
                                               ID *id_hierarchy_root,
+                                              const ID *id_hierarchy_root_reference,
                                               const bool do_no_main)
 {
-  BLI_assert(id_root_reference != NULL);
-  BLI_assert(id_hierarchy_root != NULL || (id_root_reference->tag & LIB_TAG_DOIT) != 0);
-  BLI_assert(id_hierarchy_root == NULL ||
-             (ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root) &&
-              id_hierarchy_root->override_library->reference == id_root_reference));
+  BLI_assert(id_root_reference != NULL && ID_IS_LINKED(id_root_reference));
+  /* If we do not have any hierarchy root given, then the root reference must be tagged for
+   * override. */
+  BLI_assert(id_hierarchy_root != NULL || id_hierarchy_root_reference != NULL ||
+             (id_root_reference->tag & LIB_TAG_DOIT) != 0);
+  /* At least one of the hierarchy root pointers must be NULL, passing both is useless and can
+   * create confusion. */
+  BLI_assert(ELEM(NULL, id_hierarchy_root, id_hierarchy_root_reference));
+
+  if (id_hierarchy_root != NULL) {
+    /* If the hierarchy root is given, it must be a valid existing override (used during partial
+     * resync process mainly). */
+    BLI_assert((ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root) &&
+                id_hierarchy_root->override_library->reference->lib == id_root_reference->lib));
+  }
+  if (!ELEM(id_hierarchy_root_reference, NULL, id_root_reference)) {
+    /* If the reference hierarchy root is given, it must be from the same library as the reference
+     * root, and also tagged for override. */
+    BLI_assert((id_hierarchy_root_reference->lib == id_root_reference->lib &&
+                (id_hierarchy_root_reference->tag & LIB_TAG_DOIT) != 0));
+  }
 
   const Library *reference_library = id_root_reference->lib;
 
@@ -387,7 +406,12 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain,
   /* Only remap new local ID's pointers, we don't want to force our new overrides onto our whole
    * existing linked IDs usages. */
   if (success) {
-    if (id_root_reference->newid != NULL) {
+    if (id_hierarchy_root_reference != NULL) {
+      id_hierarchy_root = id_hierarchy_root_reference->newid;
+    }
+    else if (id_root_reference->newid != NULL &&
+             (id_hierarchy_root == NULL ||
+              id_hierarchy_root->override_library->reference == id_root_reference)) {
       id_hierarchy_root = id_root_reference->newid;
     }
     BLI_assert(id_hierarchy_root != NULL);
@@ -887,12 +911,13 @@ static void lib_override_overrides_group_tag(LibOverrideGroupTagData *data)
 static bool lib_override_library_create_do(Main *bmain,
                                            Scene *scene,
                                            Library *owner_library,
-                                           ID *id_root)
+                                           ID *id_root_reference,
+                                           ID *id_hierarchy_root_reference)
 {
   BKE_main_relations_create(bmain, 0);
   LibOverrideGroupTagData data = {.bmain = bmain,
                                   .scene = scene,
-                                  .id_root = id_root,
+                                  .id_root = id_root_reference,
                                   .tag = LIB_TAG_DOIT,
                                   .missing_tag = LIB_TAG_MISSING,
                                   .is_override = false,
@@ -906,8 +931,18 @@ static bool lib_override_library_create_do(Main *bmain,
   BKE_main_relations_free(bmain);
   lib_override_group_tag_data_clear(&data);
 
-  const bool success = BKE_lib_override_library_create_from_tag(
-      bmain, owner_library, id_root, NULL, false);
+  bool success = false;
+  if (id_hierarchy_root_reference->lib != id_root_reference->lib) {
+    BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference));
+    BLI_assert(id_hierarchy_root_reference->override_library->reference->lib ==
+               id_root_reference->lib);
+    success = BKE_lib_override_library_create_from_tag(
+        bmain, owner_library, id_root_reference, id_hierarchy_root_reference, NULL, false);
+  }
+  else {
+    success = BKE_lib_override_library_create_from_tag(
+        bmain, owner_library, id_root_reference, NULL, id_hierarchy_root_reference, false);
+  }
 
   return success;
 }
@@ -917,7 +952,7 @@ static void lib_override_library_create_post_process(Main *bmain,
                                                      ViewLayer *view_layer,
                                                      const Library *owner_library,
                                                      ID *id_root,
-                                                     ID *id_reference,
+                                                     ID *id_instance_hint,
                                                      Collection *residual_storage,
                                                      const bool is_resync)
 {
@@ -941,8 +976,8 @@ static void lib_override_library_create_post_process(Main *bmain,
       (!ID_IS_LINKED(id_root->newid) || id_root->newid->lib == owner_library)) {
     switch (GS(id_root->name)) {
       case ID_GR: {
-        Object *ob_reference = id_reference != NULL && GS(id_reference->name) == ID_OB ?
-                                   (Object *)id_reference :
+        Object *ob_reference = id_instance_hint != NULL && GS(id_instance_hint->name) == ID_OB ?
+                                   (Object *)id_instance_hint :
                                    NULL;
         Collection *collection_new = ((Collection *)id_root->newid);
         if (is_resync && BKE_collection_is_in_scene(collection_new)) {
@@ -951,10 +986,10 @@ static void lib_override_library_create_post_process(Main *bmain,
         if (ob_reference != NULL) {
           BKE_collection_add_from_object(bmain, scene, ob_reference, collection_new);
         }
-        else if (id_reference != NULL) {
-          BLI_assert(GS(id_reference->name) == ID_GR);
+        else if (id_instance_hint != NULL) {
+          BLI_assert(GS(id_instance_hint->name) == ID_GR);
           BKE_collection_add_from_collection(
-              bmain, scene, ((Collection *)id_reference), collection_new);
+              bmain, scene, ((Collection *)id_instance_hint), collection_new);
         }
         else {
           BKE_collection_add_from_collection(
@@ -1047,26 +1082,32 @@ bool BKE_lib_override_library_create(Main *bmain,
                                      Scene *scene,
                                      ViewLayer *view_layer,
                                      Library *owner_library,
-                                     ID *id_root,
-                                     ID *id_reference,
+                                     ID *id_root_reference,
+                                     ID *id_hierarchy_root_reference,
+                                     ID *id_instance_hint,
                                      ID **r_id_root_override)
 {
   if (r_id_root_override != NULL) {
     *r_id_root_override = NULL;
   }
 
-  const bool success = lib_override_library_create_do(bmain, scene, owner_library, id_root);
+  if (id_hierarchy_root_reference == NULL) {
+    id_hierarchy_root_reference = id_root_reference;
+  }
+
+  const bool success = lib_override_library_create_do(
+      bmain, scene, owner_library, id_root_reference, id_hierarchy_root_reference);
 
   if (!success) {
     return success;
   }
 
   if (r_id_root_override != NULL) {
-    *r_id_root_override = id_root->newid;
+    *r_id_root_override = id_root_reference->newid;
   }
 
   lib_override_library_create_post_process(
-      bmain, scene, view_layer, owner_library, id_root, id_reference, NULL, false);
+      bmain, scene, view_layer, owner_library, id_root_reference, id_instance_hint, NULL, false);
 
   /* Cleanup. */
   BKE_main_id_newptr_and_tag_clear(bmain);
@@ -1527,7 +1568,7 @@ static bool lib_override_library_resync(Main *bmain,
    * override IDs (including within the old overrides themselves, since those are tagged too
    * above). */
   const bool success = BKE_lib_override_library_create_from_tag(
-      bmain, NULL, id_root_reference, id_root->override_library->hierarchy_root, true);
+      bmain, NULL, id_root_reference, id_root->override_library->hierarchy_root, NULL, true);
 
   if (!success) {
     BLI_ghash_free(linkedref_to_old_override, NULL, NULL);
diff --git a/source/blender/blenkernel/intern/lib_override_proxy_conversion.c b/source/blender/blenkernel/intern/lib_override_proxy_conversion.c
index 809235ad24c..dc164313788 100644
--- a/source/blender/blenkernel/intern/lib_override_proxy_conversion.c
+++ b/source/blender/blenkernel/intern/lib_override_proxy_conversion.c
@@ -42,7 +42,7 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain,
   const bool is_override_instancing_object = ob_proxy_group != NULL;
   ID *id_root = is_override_instancing_object ? &ob_proxy_group->instance_collection->id :
                                                 &ob_proxy->proxy->id;
-  ID *id_reference = is_override_instancing_object ? &ob_proxy_group->id : &ob_proxy->id;
+  ID *id_instance_hint = is_override_instancing_object ? &ob_proxy_group->id : &ob_proxy->id;
 
   /* In some cases the instance collection of a proxy object may be local (see e.g. T83875). Not
    * sure this is a valid state, but for now just abort the overriding process. */
@@ -81,7 +81,7 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain,
   FOREACH_MAIN_ID_END;
 
   return BKE_lib_override_library_create(
-      bmain, scene, view_layer, ob_proxy->id.lib, id_root, id_reference, NULL);
+      bmain, scene, view_layer, ob_proxy->id.lib, id_root, id_root, id_instance_hint, NULL);
 }
 
 static void lib_override_library_proxy_convert_do(Main *bmain,
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 1204a57d59f..3ecf86d14ed 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -2318,7 +2318,7 @@ static int make_override_library_exec(bContext *C, wmOperator *op)
   BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
 
   const bool success = BKE_lib_override_library_create(
-      bmain, scene, view_layer, NULL, id_root, &obact->id, NULL);
+      bmain, scene, view_layer, NULL, id_root, id_root, &obact->id, NULL);
 
   /* Remove the instance empty from this scene, the items now have an overridden collection
    * instead. */
diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc
index 53a81cf161e..8de7322f1f1 100644
--- a/source/blender/editors/space_outliner/outliner_tools.cc
+++ b/source/blender/editors/space_outliner/outliner_tools.cc
@@ -766,29 +766,30 @@ static void id_override_library_create_fn(bContext *C,
                                           void *user_data)
 {
   BLI_assert(TSE_IS_REAL_ID(tselem));
-  ID *id_root = tselem->id;
+  ID *id_root_reference = tselem->id;
   OutlinerLibOverrideData *data = reinterpret_cast(user_data);
   const bool do_hierarchy = data->do_hierarchy;
   bool success = false;
 
-  ID *id_reference = nullptr;
+  ID *id_instance_hint = nullptr;
   bool is_override_instancing_object = false;
   if (tsep != nullptr && tsep->type == TSE_SOME_ID && tsep->id != nullptr &&
       GS(tsep->id->name) == ID_OB && !ID_IS_OVERRIDE_LIBRARY(tsep->id)) {
     Object *ob = (Object *)tsep->id;
-    if (ob->type == OB_EMPTY && &ob->instance_collection->id == id_root) {
-      BLI_assert(GS(id_root->name) == ID_GR);
+    if (ob->type == OB_EMPTY && &ob->instance_collection->id == id_root_reference) {
+      BLI_assert(GS(id_root_reference->name) == ID_GR);
       /* Empty instantiating the collection we override, we need to pass it to BKE overriding code
        * for proper handling. */
-      id_reference = tsep->id;
+      id_instance_hint = tsep->id;
       is_override_instancing_object = true;
     }
   }
 
-  if (ID_IS_OVERRIDABLE_LIBRARY(id_root) || (ID_IS_LINKED(id_root) && do_hierarchy)) {
+  if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) ||
+      (ID_IS_LINKED(id_root_reference) && do_hierarchy)) {
     Main *bmain = CTX_data_main(C);
 
-    id_root->tag |= LIB_TAG_DOIT;
+    id_root_reference->tag |= LIB_TAG_DOIT;
 
     /* For now, remap all local usages of linked ID to local override one here. */
     ID *id_iter;
@@ -804,38 +805,100 @@ static void id_override_library_create_fn(bContext *C,
 
     if (do_hierarchy) {
       /* Tag all linked parents in tree hierarchy to be also overridden. */
+      ID *id_hierarchy_root_reference = id_root_reference;
       while ((te = te->parent) != nullptr) {
         if (!TSE_IS_REAL_ID(te->store_elem)) {
           continue;
         }
-        if (!ID_IS_LINKED(te->store_elem->id)) {
+
+        /* Tentative hierarchy root. */
+        ID *id_current_hierarchy_root = te->store_elem->id;
+
+        /* If the parent ID is from a different library than the reference root one, we are done
+         * with upwards tree processing in any case. */
+        if (id_current_hierarchy_root->lib != id_root_reference->lib) {
+          if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) {
+            /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to
+             * get an actual real override. */
+            continue;
+          }
+
+          /* If the parent ID is already an override, and is valid (i.e. local override), we can
+           * access its hierarchy root directly. */
+          if (!ID_IS_LINKED(id_current_hierarchy_root) &&
+              ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) &&
+              id_current_hierarchy_root->override_library->reference->lib ==
+                  id_root_reference->lib) {
+            id_hierarchy_root_reference =
+                id_current_hierarchy_root->override_library->hierarchy_root;
+            BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference));
+            break;
+          }
+
+          if (ID_IS_LINKED(id_current_hierarchy_root)) {
+            /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this
+             * would most likely generate invisible/confusing/hard to use and manage overrides. */
+            BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
+            BKE_reportf(reports,
+                        RPT_WARNING,
+                        "Invalid anchor ('%s') found, needed to create library override from "
+                        "data-block '%s'",
+                        id_current_hierarchy_root->name,
+                        id_root_reference->name);
+            return;
+          }
+
+          /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so
+           * current `id_hierarchy_root_reference` is our best candidate. */
+
           break;
         }
+
         /* If some element in the tree needs to be overridden, but its ID is not overridable,
          * abort. */
-        if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(te->store_elem->id)) {
+        if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) {
           BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
           BKE_reportf(reports,
                       RPT_WARNING,
                       "Could not create library override from data-block '%s', one of its parents "
                       "is not overridable ('%s')",
-                      id_root->name,
-                      te->store_elem->id->name);
+                      id_root_reference->name,
+                      id_current_hierarchy_root->name);
           return;
         }
-        te->store_elem->id->tag |= LIB_TAG_DOIT;
+        id_current_hierarchy_root->tag |= LIB_TAG_DOIT;
+        id_hierarchy_root_reference = id_current_hierarchy_root;
+      }
+
+      /* That case can happen when linked data is a complex mix involving several libraries and/or
+       * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data
+       * from another library. Do not try to support such cases for now. */
+      if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) ||
+            (!ID_IS_LINKED(id_hierarchy_root_reference) &&
+             ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) &&
+             id_hierarchy_root_reference->override_library->reference->lib ==
+                 id_root_reference->lib))) {
+        BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
+        BKE_reportf(reports,
+                    RPT_WARNING,
+                    "Invalid hierarchy root ('%s') found, needed to create library override from "
+                    "data-block '%s'",
+                    id_hierarchy_root_reference->name,
+                    id_root_reference->name);
+        return;
       }
 
       success = BKE_lib_override_library_create(bmain,
                                                 CTX_data_scene(C),
                                                 CTX_data_view_layer(C),
                                                 nullptr,
-                                                id_root,
-                                                id_reference,
+                                                id_root_reference,
+                                                id_hierarchy_root_reference,
+                                                id_instance_hint,
                                                 nullptr);
     }
-    else if (ID_IS_OVERRIDABLE_LIBRARY(id_root)) {
-      success = BKE_lib_override_library_create_from_id(bmain, id_root, true) != nullptr;
+    else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) {
+      success = BKE_lib_override_library_create_from_id(bmain, id_root_reference, true) != nullptr;
 
       /* Cleanup. */
       BKE_main_id_newptr_and_tag_clear(bmain);
@@ -845,14 +908,14 @@ static void id_override_library_create_fn(bContext *C,
     /* Remove the instance empty from this scene, the items now have an overridden collection
      * instead. */
     if (success && is_override_instancing_object) {
-      ED_object_base_free_and_unlink(bmain, scene, (Object *)id_reference);
+      ED_object_base_free_and_unlink(bmain, scene, (Object *)id_instance_hint);
     }
   }
   if (!success) {
     BKE_reportf(reports,
                 RPT_WARNING,
                 "Could not create library override from data-block '%s'",
-                id_root->name);
+                id_root_reference->name);
   }
 }
 
diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c
index 78960803182..94ffa330064 100644
--- a/source/blender/makesrna/intern/rna_ID.c
+++ b/source/blender/makesrna/intern/rna_ID.c
@@ -696,7 +696,7 @@ static ID *rna_ID_override_create(ID *id, Main *bmain, bool remap_local_usages)
 }
 
 static ID *rna_ID_override_hierarchy_create(
-    ID *id, Main *bmain, Scene *scene, ViewLayer *view_layer, ID *id_reference)
+    ID *id, Main *bmain, Scene *scene, ViewLayer *view_layer, ID *id_instance_hint)
 {
   if (!ID_IS_OVERRIDABLE_LIBRARY(id)) {
     return NULL;
@@ -706,7 +706,7 @@ static ID *rna_ID_override_hierarchy_create(
 
   ID *id_root_override = NULL;
   BKE_lib_override_library_create(
-      bmain, scene, view_layer, NULL, id, id_reference, &id_root_override);
+      bmain, scene, view_layer, NULL, id, id, id_instance_hint, &id_root_override);
 
   WM_main_add_notifier(NC_ID | NA_ADDED, NULL);
   WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
@@ -2057,7 +2057,7 @@ static void rna_def_ID(BlenderRNA *brna)
                   "reference",
                   "ID",
                   "",
-                  "Another ID (usually an Object or Collection) used to decide where to "
+                  "Another ID (usually an Object or Collection) used as a hint to decide where to "
                   "instantiate the new overrides");
 
   func = RNA_def_function(srna, "override_template_create", "rna_ID_override_template_create");
-- 
cgit v1.2.3


From 6e11cfc56af4e1594972d134e4e0c5d256d1fcce Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Fri, 25 Feb 2022 13:22:42 +0100
Subject: Curves: add surface object pointer

Ref T95776.

Differential Revision: https://developer.blender.org/D14182
---
 release/scripts/startup/bl_ui/properties_data_curves.py | 12 ++++++++++++
 source/blender/blenkernel/intern/curves.cc              |  3 +++
 source/blender/blenkernel/intern/lib_query.c            |  2 +-
 source/blender/makesdna/DNA_curves_types.h              |  9 +++++++++
 source/blender/makesrna/intern/rna_curves.c             |  9 +++++++++
 5 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/release/scripts/startup/bl_ui/properties_data_curves.py b/release/scripts/startup/bl_ui/properties_data_curves.py
index 3e350575bc8..1bb5fc9afbe 100644
--- a/release/scripts/startup/bl_ui/properties_data_curves.py
+++ b/release/scripts/startup/bl_ui/properties_data_curves.py
@@ -35,6 +35,17 @@ class DATA_PT_context_curves(DataButtonsPanel, Panel):
             layout.template_ID(space, "pin_id")
 
 
+class DATA_PT_curves_surface(DataButtonsPanel, Panel):
+    bl_label = "Surface"
+    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+    def draw(self, context):
+        layout = self.layout
+        ob = context.object
+
+        layout.prop(ob.data, "surface")
+
+
 class CURVES_MT_add_attribute(Menu):
     bl_label = "Add Attribute"
 
@@ -115,6 +126,7 @@ class DATA_PT_custom_props_curves(DataButtonsPanel, PropertyPanel, Panel):
 classes = (
     DATA_PT_context_curves,
     DATA_PT_CURVES_attributes,
+    DATA_PT_curves_surface,
     DATA_PT_custom_props_curves,
     CURVES_MT_add_attribute,
     CURVES_UL_attributes,
diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc
index 9935166f874..d7783c76f65 100644
--- a/source/blender/blenkernel/intern/curves.cc
+++ b/source/blender/blenkernel/intern/curves.cc
@@ -114,6 +114,7 @@ static void curves_foreach_id(ID *id, LibraryForeachIDData *data)
   for (int i = 0; i < curves->totcol; i++) {
     BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curves->mat[i], IDWALK_CB_USER);
   }
+  BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curves->surface, IDWALK_CB_NOP);
 }
 
 static void curves_blend_write(BlendWriter *writer, ID *id, const void *id_address)
@@ -186,6 +187,7 @@ static void curves_blend_read_lib(BlendLibReader *reader, ID *id)
   for (int a = 0; a < curves->totcol; a++) {
     BLO_read_id_address(reader, curves->id.lib, &curves->mat[a]);
   }
+  BLO_read_id_address(reader, curves->id.lib, &curves->surface);
 }
 
 static void curves_blend_read_expand(BlendExpander *expander, ID *id)
@@ -194,6 +196,7 @@ static void curves_blend_read_expand(BlendExpander *expander, ID *id)
   for (int a = 0; a < curves->totcol; a++) {
     BLO_expand(expander, curves->mat[a]);
   }
+  BLO_expand(expander, curves->surface);
 }
 
 IDTypeInfo IDType_ID_CV = {
diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c
index 0103e40e90d..5de8704e13b 100644
--- a/source/blender/blenkernel/intern/lib_query.c
+++ b/source/blender/blenkernel/intern/lib_query.c
@@ -448,7 +448,7 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner)
     case ID_WS:
       return FILTER_ID_SCE;
     case ID_CV:
-      return FILTER_ID_MA;
+      return FILTER_ID_MA | FILTER_ID_OB;
     case ID_PT:
       return FILTER_ID_MA;
     case ID_VO:
diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h
index c45de832e3c..98d2aa4b295 100644
--- a/source/blender/makesdna/DNA_curves_types.h
+++ b/source/blender/makesdna/DNA_curves_types.h
@@ -115,6 +115,15 @@ typedef struct Curves {
   short totcol;
   short _pad2[3];
 
+  /**
+   * Used as base mesh when curves represent e.g. hair or fur. This surface is used in edit modes.
+   * When set, the curves will have attributes that indicate a position on this surface. This is
+   * used for deforming the curves when the surface is deformed dynamically.
+   *
+   * This is expected to be a mesh object.
+   */
+  struct Object *surface;
+
   /* Draw Cache. */
   void *batch_cache;
 } Curves;
diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c
index 1552a7ddbfb..7a1a368551f 100644
--- a/source/blender/makesrna/intern/rna_curves.c
+++ b/source/blender/makesrna/intern/rna_curves.c
@@ -16,6 +16,8 @@
 #include "BLI_math_base.h"
 #include "BLI_string.h"
 
+#include "WM_types.h"
+
 #ifdef RNA_RUNTIME
 
 #  include "BLI_math_vector.h"
@@ -265,6 +267,13 @@ static void rna_def_curves(BlenderRNA *brna)
   RNA_def_property_collection_funcs(
       prop, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "rna_IDMaterials_assign_int");
 
+  prop = RNA_def_property(srna, "surface", PROP_POINTER, PROP_NONE);
+  RNA_def_property_struct_type(prop, "Object");
+  RNA_def_property_flag(prop, PROP_EDITABLE);
+  RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Mesh_object_poll");
+  RNA_def_property_ui_text(prop, "Surface", "Mesh object that the curves can be attached to");
+  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
+
   /* attributes */
   rna_def_attributes_common(srna);
 
-- 
cgit v1.2.3


From e84b42bfcf105be555c6cfef2692d20b223cdad4 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Fri, 25 Feb 2022 15:06:43 +0100
Subject: Fix (unreported) Outliner 'liboverride create hierarchy' applied on
 several IDs.

This operation can only be applied on one ID at a time, so only apply it
to the active Outliner item, and not all the selected ones.

Also renamed `Make Library Override` menu entry to `Make Library Override
Single` to emphasis this is not the 'default expected' option for the
user.
---
 source/blender/editors/space_outliner/outliner_tools.cc | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc
index 03fc4c20fe5..2a3c0a9392b 100644
--- a/source/blender/editors/space_outliner/outliner_tools.cc
+++ b/source/blender/editors/space_outliner/outliner_tools.cc
@@ -814,6 +814,13 @@ static void id_override_library_create_fn(bContext *C,
                                           void *user_data)
 {
   BLI_assert(TSE_IS_REAL_ID(tselem));
+
+  /* We can only safely apply this operation on one item at a time, so only do it on the active
+   * one. */
+  if ((tselem->flag & TSE_ACTIVE) == 0) {
+    return;
+  }
+
   ID *id_root = tselem->id;
   OutlinerLibOverrideData *data = reinterpret_cast(user_data);
   const bool do_hierarchy = data->do_hierarchy;
@@ -1829,13 +1836,15 @@ static const EnumPropertyItem prop_id_op_types[] = {
     {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE,
      "OVERRIDE_LIBRARY_CREATE",
      0,
-     "Make Library Override",
-     "Make a local override of this linked data-block"},
+     "Make Library Override Single",
+     "Make a single, out-of-hierarchy local override of this linked data-block - only applies to "
+     "active Outliner item"},
     {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY,
      "OVERRIDE_LIBRARY_CREATE_HIERARCHY",
      0,
      "Make Library Override Hierarchy",
-     "Make a local override of this linked data-block, and its hierarchy of dependencies"},
+     "Make a local override of this linked data-block, and its hierarchy of dependencies - only "
+     "applies to active Outliner item"},
     {OUTLINER_IDOP_OVERRIDE_LIBRARY_PROXY_CONVERT,
      "OVERRIDE_LIBRARY_PROXY_CONVERT",
      0,
-- 
cgit v1.2.3


From 0a6a74bac41558af0ee6d3031d923470d32e767e Mon Sep 17 00:00:00 2001
From: Michael Kowalski 
Date: Fri, 25 Feb 2022 09:27:10 -0500
Subject: Fix T94396: USD errors opening saved scenes.

Added call to ensure that the USD plugins are registered
when opening a USD cache archive. This is to avoid USD
load errors due to missing USD file format plugins when
opening blender files that contain USD transform cache
constraints and mesh sequence cache modifilers.

Fixes T94396
---
 source/blender/io/usd/intern/usd_capi_import.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc
index 1f384bdf127..6186e2bb61f 100644
--- a/source/blender/io/usd/intern/usd_capi_import.cc
+++ b/source/blender/io/usd/intern/usd_capi_import.cc
@@ -502,6 +502,9 @@ CacheArchiveHandle *USD_create_handle(struct Main * /*bmain*/,
                                       const char *filename,
                                       ListBase *object_paths)
 {
+  /* Must call this so that USD file format plugins are loaded. */
+  ensure_usd_plugin_path_registered();
+
   pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(filename);
 
   if (!stage) {
-- 
cgit v1.2.3


From 1a8db5b717299eee1a6e9726c9de0abe5c169253 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Fri, 25 Feb 2022 10:35:14 -0500
Subject: Fix: Only possible to create one 8 bit integer attribute

The custom data code checks for `LayerTypeInfo.defaultname` before
adding a second layer with a certain type. This was missed in
e7912dfa1959be671f77e4e67eab. In practice, this default name
is not actually used.
---
 source/blender/blenkernel/intern/customdata.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc
index 587c0b73dff..4492f8bbc64 100644
--- a/source/blender/blenkernel/intern/customdata.cc
+++ b/source/blender/blenkernel/intern/customdata.cc
@@ -1777,7 +1777,7 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
     /* 44: CD_RADIUS */
     {sizeof(float), "MFloatProperty", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
     /* 45: CD_PROP_INT8 */
-    {sizeof(int8_t), "MInt8Property", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
+    {sizeof(int8_t), "MInt8Property", 1, N_("Int8"), nullptr, nullptr, nullptr, nullptr, nullptr},
     /* 46: CD_HAIRMAPPING */ /* UNUSED */
     {-1, "", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
     /* 47: CD_PROP_COLOR */
-- 
cgit v1.2.3


From 4896e72a4d9d4fc65aae15a5120ce9f1b53482f7 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Fri, 25 Feb 2022 20:03:25 +0100
Subject: Fix T95977: Point Info node radius wrong under rotation

---
 intern/cycles/kernel/geom/point.h | 5 +++--
 intern/cycles/util/math.h         | 3 +++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/intern/cycles/kernel/geom/point.h b/intern/cycles/kernel/geom/point.h
index 545b5c7fa43..29e2bcbe9e3 100644
--- a/intern/cycles/kernel/geom/point.h
+++ b/intern/cycles/kernel/geom/point.h
@@ -141,9 +141,10 @@ ccl_device float point_radius(KernelGlobals kg, ccl_private const ShaderData *sd
       return r;
     }
     else {
-      float3 dir = make_float3(r, r, r);
+      const float normalized_r = r * (1.0f / M_SQRT3_F);
+      float3 dir = make_float3(normalized_r, normalized_r, normalized_r);
       object_dir_transform(kg, sd, &dir);
-      return average(dir);
+      return len(dir);
     }
   }
 
diff --git a/intern/cycles/util/math.h b/intern/cycles/util/math.h
index 5f047f6f3f4..5c94baccf0c 100644
--- a/intern/cycles/util/math.h
+++ b/intern/cycles/util/math.h
@@ -80,6 +80,9 @@ CCL_NAMESPACE_BEGIN
 #ifndef M_SQRT2_F
 #  define M_SQRT2_F (1.4142135623730950f) /* sqrt(2) */
 #endif
+#ifndef M_SQRT3_F
+#  define M_SQRT3_F (1.7320508075688772f) /* sqrt(3) */
+#endif
 #ifndef M_LN2_F
 #  define M_LN2_F (0.6931471805599453f) /* ln(2) */
 #endif
-- 
cgit v1.2.3


From 118a219e9dcec433902b228dc8d029e7458e6976 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Fri, 25 Feb 2022 17:55:02 +0100
Subject: Fix different shading between CPU and GPU subdivision

Reuse the same vertex normals calculation as for the GPU code, by
weighing each vertex normals by the angle of the edges incident to the
vertex on the face.

Additionally, remove limit normals, as the CPU code does not use them
either, and would also cause different shading issues when limit surface
is used.

Fixes T95242: shade smooth artifacts with edge crease and limit surface
Fixes T94919: subdivision, different shading between CPU and GPU
---
 .../draw/intern/draw_cache_impl_subdivision.cc     | 25 ++-------
 source/blender/draw/intern/draw_subdivision.h      |  6 +--
 .../mesh_extractors/extract_mesh_vbo_pos_nor.cc    |  7 ++-
 .../draw/intern/shaders/common_subdiv_lib.glsl     |  7 +++
 .../common_subdiv_normals_accumulate_comp.glsl     | 59 ++++++++++++++++++----
 .../common_subdiv_patch_evaluation_comp.glsl       |  4 --
 .../shaders/common_subdiv_vbo_lnor_comp.glsl       | 19 ++++---
 7 files changed, 79 insertions(+), 48 deletions(-)

diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
index e8ff9a508a2..8c42eea1689 100644
--- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc
+++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
@@ -82,7 +82,6 @@ enum {
   SHADER_BUFFER_NORMALS_ACCUMULATE,
   SHADER_BUFFER_NORMALS_FINALIZE,
   SHADER_PATCH_EVALUATION,
-  SHADER_PATCH_EVALUATION_LIMIT_NORMALS,
   SHADER_PATCH_EVALUATION_FVAR,
   SHADER_PATCH_EVALUATION_FACE_DOTS,
   SHADER_COMP_CUSTOM_DATA_INTERP_1D,
@@ -122,7 +121,6 @@ static const char *get_shader_code(int shader_type)
       return datatoc_common_subdiv_normals_finalize_comp_glsl;
     }
     case SHADER_PATCH_EVALUATION:
-    case SHADER_PATCH_EVALUATION_LIMIT_NORMALS:
     case SHADER_PATCH_EVALUATION_FVAR:
     case SHADER_PATCH_EVALUATION_FACE_DOTS: {
       return datatoc_common_subdiv_patch_evaluation_comp_glsl;
@@ -174,9 +172,6 @@ static const char *get_shader_name(int shader_type)
     case SHADER_PATCH_EVALUATION: {
       return "subdiv patch evaluation";
     }
-    case SHADER_PATCH_EVALUATION_LIMIT_NORMALS: {
-      return "subdiv patch evaluation limit normals";
-    }
     case SHADER_PATCH_EVALUATION_FVAR: {
       return "subdiv patch evaluation face-varying";
     }
@@ -214,13 +209,7 @@ static GPUShader *get_patch_evaluation_shader(int shader_type)
     const char *compute_code = get_shader_code(shader_type);
 
     const char *defines = nullptr;
-    if (shader_type == SHADER_PATCH_EVALUATION_LIMIT_NORMALS) {
-      defines =
-          "#define OSD_PATCH_BASIS_GLSL\n"
-          "#define OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES\n"
-          "#define LIMIT_NORMALS\n";
-    }
-    else if (shader_type == SHADER_PATCH_EVALUATION_FVAR) {
+    if (shader_type == SHADER_PATCH_EVALUATION_FVAR) {
       defines =
           "#define OSD_PATCH_BASIS_GLSL\n"
           "#define OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES\n"
@@ -261,7 +250,6 @@ static GPUShader *get_subdiv_shader(int shader_type, const char *defines)
 {
   if (ELEM(shader_type,
            SHADER_PATCH_EVALUATION,
-           SHADER_PATCH_EVALUATION_LIMIT_NORMALS,
            SHADER_PATCH_EVALUATION_FVAR,
            SHADER_PATCH_EVALUATION_FACE_DOTS)) {
     return get_patch_evaluation_shader(shader_type);
@@ -1209,9 +1197,7 @@ static void drw_subdiv_compute_dispatch(const DRWSubdivCache *cache,
   GPU_compute_dispatch(shader, dispatch_rx, dispatch_ry, 1);
 }
 
-void draw_subdiv_extract_pos_nor(const DRWSubdivCache *cache,
-                                 GPUVertBuf *pos_nor,
-                                 const bool do_limit_normals)
+void draw_subdiv_extract_pos_nor(const DRWSubdivCache *cache, GPUVertBuf *pos_nor)
 {
   Subdiv *subdiv = cache->subdiv;
   OpenSubdiv_Evaluator *evaluator = subdiv->evaluator;
@@ -1236,8 +1222,7 @@ void draw_subdiv_extract_pos_nor(const DRWSubdivCache *cache,
                                                                get_patch_param_format());
   evaluator->wrapPatchParamBuffer(evaluator, &patch_param_buffer_interface);
 
-  GPUShader *shader = get_patch_evaluation_shader(
-      do_limit_normals ? SHADER_PATCH_EVALUATION_LIMIT_NORMALS : SHADER_PATCH_EVALUATION);
+  GPUShader *shader = get_patch_evaluation_shader(SHADER_PATCH_EVALUATION);
   GPU_shader_bind(shader);
 
   GPU_vertbuf_bind_as_ssbo(src_buffer, 0);
@@ -1409,6 +1394,7 @@ void draw_subdiv_accumulate_normals(const DRWSubdivCache *cache,
                                     GPUVertBuf *pos_nor,
                                     GPUVertBuf *face_adjacency_offsets,
                                     GPUVertBuf *face_adjacency_lists,
+                                    GPUVertBuf *vertex_loop_map,
                                     GPUVertBuf *vertex_normals)
 {
   GPUShader *shader = get_subdiv_shader(SHADER_BUFFER_NORMALS_ACCUMULATE, nullptr);
@@ -1419,6 +1405,7 @@ void draw_subdiv_accumulate_normals(const DRWSubdivCache *cache,
   GPU_vertbuf_bind_as_ssbo(pos_nor, binding_point++);
   GPU_vertbuf_bind_as_ssbo(face_adjacency_offsets, binding_point++);
   GPU_vertbuf_bind_as_ssbo(face_adjacency_lists, binding_point++);
+  GPU_vertbuf_bind_as_ssbo(vertex_loop_map, binding_point++);
   GPU_vertbuf_bind_as_ssbo(vertex_normals, binding_point++);
 
   drw_subdiv_compute_dispatch(cache, shader, 0, 0, cache->num_subdiv_verts);
@@ -1866,8 +1853,6 @@ static bool draw_subdiv_create_requested_buffers(const Scene *scene,
   draw_cache->subdiv = subdiv;
   draw_cache->optimal_display = optimal_display;
   draw_cache->num_subdiv_triangles = tris_count_from_number_of_loops(draw_cache->num_subdiv_loops);
-  /* We can only evaluate limit normals if the patches are adaptive. */
-  draw_cache->do_limit_normals = settings.is_adaptive;
 
   draw_cache->use_custom_loop_normals = (smd->flags & eSubsurfModifierFlag_UseCustomNormals) &&
                                         (mesh_eval->flag & ME_AUTOSMOOTH) &&
diff --git a/source/blender/draw/intern/draw_subdivision.h b/source/blender/draw/intern/draw_subdivision.h
index 78313441a70..2f339cf18f8 100644
--- a/source/blender/draw/intern/draw_subdivision.h
+++ b/source/blender/draw/intern/draw_subdivision.h
@@ -66,7 +66,6 @@ typedef struct DRWSubdivCache {
   struct BMesh *bm;
   struct Subdiv *subdiv;
   bool optimal_display;
-  bool do_limit_normals;
   bool use_custom_loop_normals;
 
   /* Coordinates used to evaluate patches for UVs, positions, and normals. */
@@ -180,6 +179,7 @@ void draw_subdiv_accumulate_normals(const DRWSubdivCache *cache,
                                     struct GPUVertBuf *pos_nor,
                                     struct GPUVertBuf *face_adjacency_offsets,
                                     struct GPUVertBuf *face_adjacency_lists,
+                                    struct GPUVertBuf *vertex_loop_map,
                                     struct GPUVertBuf *vertex_normals);
 
 void draw_subdiv_finalize_normals(const DRWSubdivCache *cache,
@@ -191,9 +191,7 @@ void draw_subdiv_finalize_custom_normals(const DRWSubdivCache *cache,
                                          GPUVertBuf *src_custom_normals,
                                          GPUVertBuf *pos_nor);
 
-void draw_subdiv_extract_pos_nor(const DRWSubdivCache *cache,
-                                 struct GPUVertBuf *pos_nor,
-                                 bool do_limit_normals);
+void draw_subdiv_extract_pos_nor(const DRWSubdivCache *cache, struct GPUVertBuf *pos_nor);
 
 void draw_subdiv_interp_custom_data(const DRWSubdivCache *cache,
                                     struct GPUVertBuf *src_data,
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
index ef88a34021e..0645683d58b 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
@@ -233,14 +233,12 @@ static void extract_pos_nor_init_subdiv(const DRWSubdivCache *subdiv_cache,
                                         void *UNUSED(data))
 {
   GPUVertBuf *vbo = static_cast(buffer);
-  const bool do_limit_normals = subdiv_cache->do_limit_normals &&
-                                !subdiv_cache->use_custom_loop_normals;
 
   /* Initialize the vertex buffer, it was already allocated. */
   GPU_vertbuf_init_build_on_device(
       vbo, get_pos_nor_format(), subdiv_cache->num_subdiv_loops + mr->loop_loose_len);
 
-  draw_subdiv_extract_pos_nor(subdiv_cache, vbo, do_limit_normals);
+  draw_subdiv_extract_pos_nor(subdiv_cache, vbo);
 
   if (subdiv_cache->use_custom_loop_normals) {
     Mesh *coarse_mesh = subdiv_cache->mesh;
@@ -266,7 +264,7 @@ static void extract_pos_nor_init_subdiv(const DRWSubdivCache *subdiv_cache,
     GPU_vertbuf_discard(src_custom_normals);
     GPU_vertbuf_discard(dst_custom_normals);
   }
-  else if (!do_limit_normals) {
+  else {
     /* We cannot evaluate vertex normals using the limit surface, so compute them manually. */
     GPUVertBuf *subdiv_loop_subdiv_vert_index = draw_subdiv_build_origindex_buffer(
         subdiv_cache->subdiv_loop_subdiv_vert_index, subdiv_cache->num_subdiv_loops);
@@ -279,6 +277,7 @@ static void extract_pos_nor_init_subdiv(const DRWSubdivCache *subdiv_cache,
                                    vbo,
                                    subdiv_cache->subdiv_vertex_face_adjacency_offsets,
                                    subdiv_cache->subdiv_vertex_face_adjacency,
+                                   subdiv_loop_subdiv_vert_index,
                                    vertex_normals);
 
     draw_subdiv_finalize_normals(subdiv_cache, vertex_normals, subdiv_loop_subdiv_vert_index, vbo);
diff --git a/source/blender/draw/intern/shaders/common_subdiv_lib.glsl b/source/blender/draw/intern/shaders/common_subdiv_lib.glsl
index 9dd86c35ee4..a620905622a 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_lib.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_lib.glsl
@@ -140,6 +140,13 @@ void set_vertex_nor(inout PosNorLoop vertex_data, vec3 nor)
   set_vertex_nor(vertex_data, nor, 0);
 }
 
+void add_newell_cross_v3_v3v3(inout vec3 n, vec3 v_prev, vec3 v_curr)
+{
+  n[0] += (v_prev[1] - v_curr[1]) * (v_prev[2] + v_curr[2]);
+  n[1] += (v_prev[2] - v_curr[2]) * (v_prev[0] + v_curr[0]);
+  n[2] += (v_prev[0] - v_curr[0]) * (v_prev[1] + v_curr[1]);
+}
+
 #define ORIGINDEX_NONE -1
 
 #ifdef SUBDIV_POLYGON_OFFSET
diff --git a/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
index 575090472b1..a6d6c205615 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
@@ -16,11 +16,33 @@ layout(std430, binding = 2) readonly buffer faceAdjacencyLists
   uint face_adjacency_lists[];
 };
 
-layout(std430, binding = 3) writeonly buffer vertexNormals
+layout(std430, binding = 3) readonly buffer vertexLoopMap
+{
+  uint vert_loop_map[];
+};
+
+layout(std430, binding = 4) writeonly buffer vertexNormals
 {
   vec3 normals[];
 };
 
+void find_prev_and_next_vertex_on_face(
+    uint face_index, uint vertex_index, out uint curr, out uint next, out uint prev)
+{
+  uint start_loop_index = face_index * 4;
+
+  for (uint i = 0; i < 4; i++) {
+    uint subdiv_vert_index = vert_loop_map[start_loop_index + i];
+
+    if (subdiv_vert_index == vertex_index) {
+      curr = i;
+      next = (i + 1) % 4;
+      prev = (i + 4 - 1) % 4;
+      break;
+    }
+  }
+}
+
 void main()
 {
   uint vertex_index = get_global_invocation_index();
@@ -39,18 +61,37 @@ void main()
     uint adjacent_face = face_adjacency_lists[first_adjacent_face_offset + i];
     uint start_loop_index = adjacent_face * 4;
 
-    /* Compute face normal. */
-    vec3 adjacent_verts[3];
-    for (uint j = 0; j < 3; j++) {
-      adjacent_verts[j] = get_vertex_pos(pos_nor[start_loop_index + j]);
+    /* Compute the face normal using Newell's method. */
+    vec3 verts[4];
+    for (uint j = 0; j < 4; j++) {
+      verts[j] = get_vertex_pos(pos_nor[start_loop_index + j]);
     }
 
-    vec3 face_normal = normalize(
-        cross(adjacent_verts[1] - adjacent_verts[0], adjacent_verts[2] - adjacent_verts[0]));
-    accumulated_normal += face_normal;
+    vec3 face_normal = vec3(0.0);
+    add_newell_cross_v3_v3v3(face_normal, verts[0], verts[1]);
+    add_newell_cross_v3_v3v3(face_normal, verts[1], verts[2]);
+    add_newell_cross_v3_v3v3(face_normal, verts[2], verts[3]);
+    add_newell_cross_v3_v3v3(face_normal, verts[3], verts[0]);
+
+    /* Accumulate angle weighted normal. */
+    uint curr_vert = 0;
+    uint next_vert = 0;
+    uint prev_vert = 0;
+    find_prev_and_next_vertex_on_face(
+        adjacent_face, vertex_index, curr_vert, next_vert, prev_vert);
+
+    vec3 curr_co = verts[curr_vert];
+    vec3 prev_co = verts[next_vert];
+    vec3 next_co = verts[prev_vert];
+
+    vec3 edvec_prev = normalize(prev_co - curr_co);
+    vec3 edvec_next = normalize(curr_co - next_co);
+
+    float fac = acos(-dot(edvec_prev, edvec_next));
+
+    accumulated_normal += face_normal * fac;
   }
 
-  float weight = 1.0 / float(number_of_adjacent_faces);
   vec3 normal = normalize(accumulated_normal);
   normals[vertex_index] = normal;
 }
diff --git a/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
index 5dd7decf663..26410eb2d7f 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
@@ -394,12 +394,8 @@ void main()
 
     evaluate_patches_limits(patch_co.patch_index, uv.x, uv.y, pos, du, dv);
 
-#  if defined(LIMIT_NORMALS)
-    vec3 nor = normalize(cross(du, dv));
-#  else
     /* This will be computed later. */
     vec3 nor = vec3(0.0);
-#  endif
 
     int origindex = input_vert_origindex[loop_index];
     uint flag = 0;
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
index 41a8df3cf82..8b4a5dd14f3 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
@@ -38,13 +38,18 @@ void main()
     }
   }
   else {
-    /* Face is flat shaded, compute flat face normal from an inscribed triangle. */
-    vec3 verts[3];
-    for (int i = 0; i < 3; i++) {
-      verts[i] = get_vertex_pos(pos_nor[start_loop_index + i]);
-    }
-
-    vec3 face_normal = normalize(cross(verts[1] - verts[0], verts[2] - verts[0]));
+    vec3 v0 = get_vertex_pos(pos_nor[start_loop_index + 0]);
+    vec3 v1 = get_vertex_pos(pos_nor[start_loop_index + 1]);
+    vec3 v2 = get_vertex_pos(pos_nor[start_loop_index + 2]);
+    vec3 v3 = get_vertex_pos(pos_nor[start_loop_index + 3]);
+
+    vec3 face_normal = vec3(0.0);
+    add_newell_cross_v3_v3v3(face_normal, v0, v1);
+    add_newell_cross_v3_v3v3(face_normal, v1, v2);
+    add_newell_cross_v3_v3v3(face_normal, v2, v3);
+    add_newell_cross_v3_v3v3(face_normal, v3, v0);
+
+    face_normal = normalize(face_normal);
     for (int i = 0; i < 4; i++) {
       output_lnor[start_loop_index + i] = face_normal;
     }
-- 
cgit v1.2.3


From c8b4e0c0b5f874906d746637c5a006d990b72e49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Fri, 25 Feb 2022 03:53:49 +0100
Subject: Disable GPU subdivision if autosmooth or split normals are used

These features are complicated to support on GPU and hardly compatible
with subdivision in the first place. In the future, with T68891 and
T68893, subdivision and custom smooth shading will be separate workflows.
For now, and to better prepare for this future (although long term
plan), we should discourage workflows mixing subdivision and custom
smooth normals, and as such, this disables GPU subdivision when
autosmoothing or custom split normals are used.

This also adds a message in the modifier's UI to indicate that GPU
subdivision will be disabled if autosmooth or custom split normals are
used on the mesh.

Differential Revision: https://developer.blender.org/D14194
---
 source/blender/blenkernel/BKE_subdiv_modifier.h    | 12 ++++
 source/blender/blenkernel/intern/subdiv_modifier.c | 71 +++++++++++++++++-----
 source/blender/draw/intern/draw_cache_impl_mesh.c  |  2 +-
 source/blender/modifiers/intern/MOD_subsurf.c      | 14 +++--
 4 files changed, 79 insertions(+), 20 deletions(-)

diff --git a/source/blender/blenkernel/BKE_subdiv_modifier.h b/source/blender/blenkernel/BKE_subdiv_modifier.h
index 8a179206dd7..5447f9fda7c 100644
--- a/source/blender/blenkernel/BKE_subdiv_modifier.h
+++ b/source/blender/blenkernel/BKE_subdiv_modifier.h
@@ -40,18 +40,30 @@ void BKE_subsurf_modifier_subdiv_settings_init(struct SubdivSettings *settings,
                                                const struct SubsurfModifierData *smd,
                                                bool use_render_params);
 
+bool BKE_subsurf_modifier_use_custom_loop_normals(const struct SubsurfModifierData *smd,
+                                                  const struct Mesh *mesh);
+
+/**
+ * Return true if GPU subdivision evaluation is disabled by force due to incompatible mesh or
+ * modifier settings. This will only return true if GPU subdivision is enabled in the preferences
+ * and supported by the GPU. It is mainly useful for showing UI messages.
+ */
+bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(
+    const struct SubsurfModifierData *smd, const struct Mesh *mesh);
 /**
  * \param skip_check_is_last: When true, we assume that the modifier passed is the last enabled
  * modifier in the stack.
  */
 bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const struct Scene *scene,
                                                const struct Object *ob,
+                                               const struct Mesh *mesh,
                                                const struct SubsurfModifierData *smd,
                                                int required_mode,
                                                bool skip_check_is_last);
 
 bool BKE_subsurf_modifier_can_do_gpu_subdiv(const struct Scene *scene,
                                             const struct Object *ob,
+                                            const struct Mesh *mesh,
                                             int required_mode);
 
 extern void (*BKE_subsurf_modifier_free_gpu_cache_cb)(struct Subdiv *subdiv);
diff --git a/source/blender/blenkernel/intern/subdiv_modifier.c b/source/blender/blenkernel/intern/subdiv_modifier.c
index 525c4837bc4..df798ccd490 100644
--- a/source/blender/blenkernel/intern/subdiv_modifier.c
+++ b/source/blender/blenkernel/intern/subdiv_modifier.c
@@ -70,23 +70,20 @@ static ModifierData *modifier_get_last_enabled_for_mode(const Scene *scene,
   return md;
 }
 
-bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const Scene *scene,
-                                               const Object *ob,
-                                               const SubsurfModifierData *smd,
-                                               int required_mode,
-                                               bool skip_check_is_last)
+bool BKE_subsurf_modifier_use_custom_loop_normals(const SubsurfModifierData *smd, const Mesh *mesh)
 {
-  if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) {
-    return false;
-  }
+  return (smd->flags & eSubsurfModifierFlag_UseCustomNormals) && (mesh->flag & ME_AUTOSMOOTH) &&
+         CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
+}
 
-  if (!skip_check_is_last) {
-    ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode);
-    if (md != (const ModifierData *)smd) {
-      return false;
-    }
-  }
+bool subsurf_modifier_use_autosmooth_or_split_normals(const SubsurfModifierData *smd,
+                                                      const Mesh *mesh)
+{
+  return (mesh->flag & ME_AUTOSMOOTH) || BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh);
+}
 
+static bool is_subdivision_evaluation_possible_on_gpu(void)
+{
   /* Only OpenGL is supported for OpenSubdiv evaluation for now. */
   if (GPU_backend_get_type() != GPU_BACKEND_OPENGL) {
     return false;
@@ -104,8 +101,52 @@ bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const Scene *scene,
   return true;
 }
 
+bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const SubsurfModifierData *smd,
+                                                                const Mesh *mesh)
+{
+  if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) {
+    /* GPU subdivision is explicitely disabled, so we don't force it. */
+    return false;
+  }
+
+  if (!is_subdivision_evaluation_possible_on_gpu()) {
+    /* The GPU type is not compatible with the subdivision. */
+    return false;
+  }
+
+  return subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh);
+}
+
+bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const Scene *scene,
+                                               const Object *ob,
+                                               const Mesh *mesh,
+                                               const SubsurfModifierData *smd,
+                                               int required_mode,
+                                               bool skip_check_is_last)
+{
+  if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) {
+    return false;
+  }
+
+  /* Deactivate GPU subdivision if autosmooth or custom split normals are used as those are
+   * complicated to support on GPU, and should really be separate workflows. */
+  if (subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh)) {
+    return false;
+  }
+
+  if (!skip_check_is_last) {
+    ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode);
+    if (md != (const ModifierData *)smd) {
+      return false;
+    }
+  }
+
+  return is_subdivision_evaluation_possible_on_gpu();
+}
+
 bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene,
                                             const Object *ob,
+                                            const Mesh *mesh,
                                             int required_mode)
 {
   ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode);
@@ -119,7 +160,7 @@ bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene,
   }
 
   return BKE_subsurf_modifier_can_do_gpu_subdiv_ex(
-      scene, ob, (SubsurfModifierData *)md, required_mode, true);
+      scene, ob, mesh, (SubsurfModifierData *)md, required_mode, true);
 }
 
 void (*BKE_subsurf_modifier_free_gpu_cache_cb)(Subdiv *subdiv) = NULL;
diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index db4c95baedb..71f13fe9423 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -1726,7 +1726,7 @@ void DRW_mesh_batch_cache_create_requested(struct TaskGraph *task_graph,
 
   const int required_mode = BKE_subsurf_modifier_eval_required_mode(DRW_state_is_scene_render(),
                                                                     is_editmode);
-  const bool do_subdivision = BKE_subsurf_modifier_can_do_gpu_subdiv(scene, ob, required_mode);
+  const bool do_subdivision = BKE_subsurf_modifier_can_do_gpu_subdiv(scene, ob, me, required_mode);
 
   MeshBufferList *mbuflist = &cache->final.buff;
 
diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c
index a8c6687193b..2006ca1b1b7 100644
--- a/source/blender/modifiers/intern/MOD_subsurf.c
+++ b/source/blender/modifiers/intern/MOD_subsurf.c
@@ -250,7 +250,8 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
      * assigned at this stage of modifier stack evaluation. */
     const bool is_editmode = (mesh->edit_mesh != NULL);
     const int required_mode = BKE_subsurf_modifier_eval_required_mode(is_render_mode, is_editmode);
-    if (BKE_subsurf_modifier_can_do_gpu_subdiv_ex(scene, ctx->object, smd, required_mode, false)) {
+    if (BKE_subsurf_modifier_can_do_gpu_subdiv_ex(
+            scene, ctx->object, mesh, smd, required_mode, false)) {
       subdiv_cache_cpu_evaluation_settings(ctx, mesh, smd);
       return result;
     }
@@ -262,9 +263,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
     /* Happens on bad topology, but also on empty input mesh. */
     return result;
   }
-  const bool use_clnors = (smd->flags & eSubsurfModifierFlag_UseCustomNormals) &&
-                          (mesh->flag & ME_AUTOSMOOTH) &&
-                          CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
+  const bool use_clnors = BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh);
   if (use_clnors) {
     /* If custom normals are present and the option is turned on calculate the split
      * normals and clear flag so the normals get interpolated to the result mesh. */
@@ -428,6 +427,13 @@ static void panel_draw(const bContext *C, Panel *panel)
 
   uiItemR(layout, ptr, "show_only_control_edges", 0, NULL, ICON_NONE);
 
+  SubsurfModifierData *smd = ptr->data;
+  Object *ob = ob_ptr.data;
+  Mesh *mesh = ob->data;
+  if (BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(smd, mesh)) {
+    uiItemL(layout, "Autosmooth or custom normals detected, disabling GPU subdivision", ICON_INFO);
+  }
+
   modifier_panel_end(layout, ptr);
 }
 
-- 
cgit v1.2.3


From 72a286760f3f2e759839c63a2a1b588aeca17e9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Fri, 25 Feb 2022 22:01:17 +0100
Subject: Fix compile warning from earlier commit.

---
 source/blender/blenkernel/intern/subdiv_modifier.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/intern/subdiv_modifier.c b/source/blender/blenkernel/intern/subdiv_modifier.c
index df798ccd490..0d910212aa1 100644
--- a/source/blender/blenkernel/intern/subdiv_modifier.c
+++ b/source/blender/blenkernel/intern/subdiv_modifier.c
@@ -76,8 +76,8 @@ bool BKE_subsurf_modifier_use_custom_loop_normals(const SubsurfModifierData *smd
          CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
 }
 
-bool subsurf_modifier_use_autosmooth_or_split_normals(const SubsurfModifierData *smd,
-                                                      const Mesh *mesh)
+static bool subsurf_modifier_use_autosmooth_or_split_normals(const SubsurfModifierData *smd,
+                                                             const Mesh *mesh)
 {
   return (mesh->flag & ME_AUTOSMOOTH) || BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh);
 }
-- 
cgit v1.2.3


From a911f075d7aa823b215d4e7baf91ea4f5bd6099c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Fri, 25 Feb 2022 22:04:10 +0100
Subject: Fix T93123: viewport lags with custom attributes

The check to see if newly requested attributes are not already in the
cache was not taking into account the possibility that we do not have
new requested attributes (`num_requests == 0`). In this case, if
`attr_used` already had attributes, but `attr_requested` is empty, we
would consider the cache as dirty, and needlessly rebuild the attribute
VBOs.
---
 source/blender/draw/intern/draw_cache_impl_mesh.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index 71f13fe9423..a47fd0c9067 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -355,11 +355,7 @@ static void drw_mesh_attributes_merge(DRW_MeshAttributes *dst,
 /* Return true if all requests in b are in a. */
 static bool drw_mesh_attributes_overlap(DRW_MeshAttributes *a, DRW_MeshAttributes *b)
 {
-  if (a->num_requests != b->num_requests) {
-    return false;
-  }
-
-  for (int i = 0; i < a->num_requests; i++) {
+  for (int i = 0; i < b->num_requests; i++) {
     if (!has_request(a, b->requests[i])) {
       return false;
     }
-- 
cgit v1.2.3


From 7aa0be4b32bdbee61ea732f43175c8bc6585fc98 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Fri, 25 Feb 2022 17:18:07 -0500
Subject: Fix: Failing OBJ export tests due to mesh normals commit

In some cases, the normal edit modifier calculated the normals on one
mesh with the "ensure" functions, then copied the mesh and retrieved
the layers "for write" on the copy. Since 59343ee1627f4c369e23, normal
layers are never copied, and normals are allocated with malloc instead
of calloc, so the mutable memory was uninitialized.

Fix by calculating normals on the correct mesh, and also add a warning
to the "for write" functions in the header.
---
 source/blender/blenkernel/BKE_mesh.h              | 6 ++++++
 source/blender/modifiers/intern/MOD_normal_edit.c | 4 ++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h
index 72a1303fc6b..26c48816b39 100644
--- a/source/blender/blenkernel/BKE_mesh.h
+++ b/source/blender/blenkernel/BKE_mesh.h
@@ -414,6 +414,9 @@ void BKE_mesh_assert_normals_dirty_or_calculated(const struct Mesh *mesh);
  * \note In order to clear the dirty flag, this function should be followed by a call to
  * #BKE_mesh_vertex_normals_clear_dirty. This is separate so that normals are still tagged dirty
  * while they are being assigned.
+ *
+ * \warning The memory returned by this function is not initialized if it was not previously
+ * allocated.
  */
 float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3];
 
@@ -424,6 +427,9 @@ float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3];
  * \note In order to clear the dirty flag, this function should be followed by a call to
  * #BKE_mesh_poly_normals_clear_dirty. This is separate so that normals are still tagged dirty
  * while they are being assigned.
+ *
+ * \warning The memory returned by this function is not initialized if it was not previously
+ * allocated.
  */
 float (*BKE_mesh_poly_normals_for_write(struct Mesh *mesh))[3];
 
diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c
index 61099fedf46..642c0f0180f 100644
--- a/source/blender/modifiers/intern/MOD_normal_edit.c
+++ b/source/blender/modifiers/intern/MOD_normal_edit.c
@@ -551,8 +551,8 @@ static Mesh *normalEditModifier_do(NormalEditModifierData *enmd,
 
   CustomData *ldata = &result->ldata;
 
-  const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh);
-  const float(*poly_normals)[3] = BKE_mesh_poly_normals_ensure(mesh);
+  const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(result);
+  const float(*poly_normals)[3] = BKE_mesh_poly_normals_ensure(result);
 
   clnors = CustomData_get_layer(ldata, CD_CUSTOMLOOPNORMAL);
   if (use_current_clnors) {
-- 
cgit v1.2.3


From ac3f4db719451acc9ee846b37c77912e2735e4be Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Fri, 25 Feb 2022 17:47:42 -0500
Subject: Cleanup: Mesh normal calculation comments and logic

Some logic and comments in the vertex normal calculation were
left over from when normals were stored in MVert, before
cfa53e0fbeed7178c7. Normals are never allocated and freed
locally anymore.
---
 source/blender/blenkernel/intern/mesh_normals.cc | 31 ++++++------------------
 1 file changed, 8 insertions(+), 23 deletions(-)

diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc
index 89aa1dbfa94..1c2a903d8c3 100644
--- a/source/blender/blenkernel/intern/mesh_normals.cc
+++ b/source/blender/blenkernel/intern/mesh_normals.cc
@@ -220,14 +220,13 @@ void BKE_mesh_calc_normals_poly(const MVert *mvert,
  * \{ */
 
 struct MeshCalcNormalsData_PolyAndVertex {
-  /** Write into vertex normals #MVert.no. */
-  MVert *mvert;
+  const MVert *mvert;
   const MLoop *mloop;
   const MPoly *mpoly;
 
   /** Polygon normal output. */
   float (*pnors)[3];
-  /** Vertex normal output (may be freed, copied into #MVert.no). */
+  /** Vertex normal output. */
   float (*vnors)[3];
 };
 
@@ -298,7 +297,7 @@ static void mesh_calc_normals_poly_and_vertex_finalize_fn(
 {
   MeshCalcNormalsData_PolyAndVertex *data = (MeshCalcNormalsData_PolyAndVertex *)userdata;
 
-  MVert *mv = &data->mvert[vidx];
+  const MVert *mv = &data->mvert[vidx];
   float *no = data->vnors[vidx];
 
   if (UNLIKELY(normalize_v3(no) == 0.0f)) {
@@ -307,7 +306,7 @@ static void mesh_calc_normals_poly_and_vertex_finalize_fn(
   }
 }
 
-static void mesh_calc_normals_poly_and_vertex(MVert *mvert,
+static void mesh_calc_normals_poly_and_vertex(const MVert *mvert,
                                               const int mvert_len,
                                               const MLoop *mloop,
                                               const int UNUSED(mloop_len),
@@ -320,36 +319,22 @@ static void mesh_calc_normals_poly_and_vertex(MVert *mvert,
   BLI_parallel_range_settings_defaults(&settings);
   settings.min_iter_per_thread = 1024;
 
-  float(*vnors)[3] = r_vert_normals;
-  bool free_vnors = false;
-
-  /* First go through and calculate normals for all the polys. */
-  if (vnors == nullptr) {
-    vnors = (float(*)[3])MEM_calloc_arrayN((size_t)mvert_len, sizeof(*vnors), __func__);
-    free_vnors = true;
-  }
-  else {
-    memset(vnors, 0, sizeof(*vnors) * (size_t)mvert_len);
-  }
+  memset(r_vert_normals, 0, sizeof(*r_vert_normals) * (size_t)mvert_len);
 
   MeshCalcNormalsData_PolyAndVertex data = {};
   data.mpoly = mpoly;
   data.mloop = mloop;
   data.mvert = mvert;
   data.pnors = r_poly_normals;
-  data.vnors = vnors;
+  data.vnors = r_vert_normals;
 
-  /* Compute poly normals (`pnors`), accumulating them into vertex normals (`vnors`). */
+  /* Compute poly normals, accumulating them into vertex normals. */
   BLI_task_parallel_range(
       0, mpoly_len, &data, mesh_calc_normals_poly_and_vertex_accum_fn, &settings);
 
-  /* Normalize and validate computed vertex normals (`vnors`). */
+  /* Normalize and validate computed vertex normals. */
   BLI_task_parallel_range(
       0, mvert_len, &data, mesh_calc_normals_poly_and_vertex_finalize_fn, &settings);
-
-  if (free_vnors) {
-    MEM_freeN(vnors);
-  }
 }
 
 /** \} */
-- 
cgit v1.2.3


From d6b3723b088d2ae2652f2c821b4fdeafe5be7827 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Sat, 26 Feb 2022 02:15:22 +0100
Subject: Cleanup: typos in comments.

---
 intern/cycles/kernel/svm/light_path.h                                 | 4 ++--
 .../draw/intern/shaders/common_subdiv_custom_data_interp_comp.glsl    | 2 +-
 source/blender/draw/intern/shaders/common_subdiv_ibo_lines_comp.glsl  | 2 +-
 source/blender/draw/intern/shaders/common_subdiv_ibo_tris_comp.glsl   | 2 +-
 .../draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl    | 2 +-
 .../draw/intern/shaders/common_subdiv_normals_finalize_comp.glsl      | 2 +-
 .../draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl      | 2 +-
 .../blender/draw/intern/shaders/common_subdiv_vbo_edge_fac_comp.glsl  | 2 +-
 .../intern/shaders/common_subdiv_vbo_edituv_strech_angle_comp.glsl    | 2 +-
 .../intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl     | 2 +-
 source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl   | 2 +-
 .../draw/intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl       | 2 +-
 source/blender/gpu/intern/gpu_node_graph.c                            | 2 +-
 source/blender/nodes/intern/node_common.cc                            | 2 +-
 14 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/intern/cycles/kernel/svm/light_path.h b/intern/cycles/kernel/svm/light_path.h
index dce0f83da68..7c2189d3608 100644
--- a/intern/cycles/kernel/svm/light_path.h
+++ b/intern/cycles/kernel/svm/light_path.h
@@ -58,8 +58,8 @@ ccl_device_noinline void svm_node_light_path(KernelGlobals kg,
         info = (float)integrator_state_bounce(state, path_flag);
       }
 
-      /* For background, light emission and shadow evaluation we from a
-       * surface or volume we are effective one bounce further. */
+      /* For background, light emission and shadow evaluation from a
+       * surface or volume we are effectively one bounce further. */
       if (path_flag & (PATH_RAY_SHADOW | PATH_RAY_EMISSION)) {
         info += 1.0f;
       }
diff --git a/source/blender/draw/intern/shaders/common_subdiv_custom_data_interp_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_custom_data_interp_comp.glsl
index df0016761e2..097ae0b3913 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_custom_data_interp_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_custom_data_interp_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 1) readonly restrict buffer sourceBuffer
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_ibo_lines_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_ibo_lines_comp.glsl
index f11c0f6427e..3cbb9f980f3 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_ibo_lines_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_ibo_lines_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 0) readonly buffer inputEdgeOrigIndex
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_ibo_tris_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_ibo_tris_comp.glsl
index 3257ebdae17..3dccc82541e 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_ibo_tris_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_ibo_tris_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 /* Generate triangles from subdivision quads indices. */
 
diff --git a/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
index a6d6c205615..0665cadfd2d 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_normals_accumulate_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 0) readonly buffer inputVertexData
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_normals_finalize_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_normals_finalize_comp.glsl
index c2e0e752783..e6a56ff02c7 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_normals_finalize_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_normals_finalize_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 #ifdef CUSTOM_NORMALS
 struct CustomNormal {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
index 26410eb2d7f..65cf4ebb90f 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_patch_evaluation_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 /* Source buffer. */
 layout(std430, binding = 0) buffer src_buffer
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_edge_fac_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_edge_fac_comp.glsl
index 6c76cd41ca4..2161f0b28a9 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_edge_fac_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_edge_fac_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 0) readonly buffer inputVertexData
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_angle_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_angle_comp.glsl
index ea73b9482d3..a8c9b7183eb 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_angle_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_angle_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 0) readonly buffer inputVerts
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl
index e897fb3f3c0..230484048b1 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 1) readonly buffer inputCoarseData
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
index 8b4a5dd14f3..b7e04e240fb 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_lnor_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 layout(std430, binding = 1) readonly buffer inputVertexData
 {
diff --git a/source/blender/draw/intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl b/source/blender/draw/intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl
index 7182ce57ad3..77b599f6252 100644
--- a/source/blender/draw/intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl
+++ b/source/blender/draw/intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl
@@ -1,5 +1,5 @@
 
-/* To be compile with common_subdiv_lib.glsl */
+/* To be compiled with common_subdiv_lib.glsl */
 
 struct SculptData {
   uint face_set_color;
diff --git a/source/blender/gpu/intern/gpu_node_graph.c b/source/blender/gpu/intern/gpu_node_graph.c
index da4e49f0257..57e415a8183 100644
--- a/source/blender/gpu/intern/gpu_node_graph.c
+++ b/source/blender/gpu/intern/gpu_node_graph.c
@@ -165,7 +165,7 @@ static const char *gpu_uniform_set_function_from_type(eNodeSocketDatatype type)
 
 /**
  * Link stack uniform buffer.
- * This is called for the input/output sockets that are note connected.
+ * This is called for the input/output sockets that are not connected.
  */
 static GPUNodeLink *gpu_uniformbuffer_link(GPUMaterial *mat,
                                            bNode *node,
diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc
index c2d440efdb5..c4befd5828c 100644
--- a/source/blender/nodes/intern/node_common.cc
+++ b/source/blender/nodes/intern/node_common.cc
@@ -171,7 +171,7 @@ static void group_verify_socket_list(bNodeTree &node_tree,
       BLI_addtail(&verify_lb, matching_socket);
     }
     else {
-      /* If there was no socket withe the same identifier already, simply create a new socket
+      /* If there was no socket with the same identifier already, simply create a new socket
        * based on the interface socket, which will already add it to the new list. */
       add_new_socket_from_interface(node_tree, node, *interface_socket, in_out);
     }
-- 
cgit v1.2.3


From 576853661c4231ebe7f075966fdbe9340c8475f1 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Fri, 25 Feb 2022 23:34:08 -0500
Subject: UI: Use title case for labels

---
 source/blender/makesrna/intern/rna_space.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 88378542a9f..2344aa42838 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -5469,7 +5469,7 @@ static void rna_def_space_sequencer_preview_overlay(BlenderRNA *brna)
 
   prop = RNA_def_property(srna, "show_cursor", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_2D_CURSOR);
-  RNA_def_property_ui_text(prop, "2D cursor", "");
+  RNA_def_property_ui_text(prop, "2D Cursor", "");
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
 }
 
-- 
cgit v1.2.3


From 4ee4b61dd8d372358606441f450df02a6e27d563 Mon Sep 17 00:00:00 2001
From: Germano Cavalcante 
Date: Sat, 26 Feb 2022 17:42:19 -0300
Subject: Ghost/Event System: Support mapping more keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This fixes T93051, T76405 and maybe others.

Characters like '²', '<' are not recognized in Blender's shortcut keys.
And sometimes simple buttons like {key .} and {key /} on the Windows
keyboard, although the symbol is "known", Blender also doesn't
detect for shortcuts.

For Windows, some of the symbols represented by `VK_OEM_[1-8]` values,
depending on the language, are not mapped by Blender.

On Mac there is a fallback reading the "actual character value of the
'remappable' keys". But sometimes the character is not mapped either.

On Windows, the solution now mimics the Mac and tries to read the button's
character as a fallback.

For unmapped characters ('²', '<', '\''), now another value is chosen as a
substitute.

More "substitutes" may be added over time.

Differential Revision: https://developer.blender.org/D14149
---
 intern/ghost/intern/GHOST_SystemCocoa.mm  |  1 +
 intern/ghost/intern/GHOST_SystemWin32.cpp | 45 +++++++++++++++++++------------
 2 files changed, 29 insertions(+), 17 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm
index b92c3e73a88..a53c3d8f2ab 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemCocoa.mm
@@ -323,6 +323,7 @@ static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
           case ']':
             return GHOST_kKeyRightBracket;
           case '`':
+          case '<': /* The position of '`' is equivalent to this symbol in the French layout. */
             return GHOST_kKeyAccentGrave;
           default:
             return GHOST_kKeyUnknown;
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index 5251dd01b29..9b5994ae5e7 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -69,9 +69,6 @@
 #ifndef VK_COMMA
 #  define VK_COMMA 0xBC
 #endif  // VK_COMMA
-#ifndef VK_QUOTE
-#  define VK_QUOTE 0xDE
-#endif  // VK_QUOTE
 #ifndef VK_BACK_QUOTE
 #  define VK_BACK_QUOTE 0xC0
 #endif  // VK_BACK_QUOTE
@@ -646,14 +643,32 @@ GHOST_TKey GHOST_SystemWin32::hardKey(RAWINPUT const &raw,
 GHOST_TKey GHOST_SystemWin32::processSpecialKey(short vKey, short scanCode) const
 {
   GHOST_TKey key = GHOST_kKeyUnknown;
-  switch (PRIMARYLANGID(m_langId)) {
-    case LANG_FRENCH:
-      if (vKey == VK_OEM_8)
-        key = GHOST_kKeyF13;  // oem key; used purely for shortcuts .
+  char ch = (char)MapVirtualKeyA(vKey, MAPVK_VK_TO_CHAR);
+  switch (ch) {
+    case u'\"':
+    case u'\'':
+      key = GHOST_kKeyQuote;
       break;
-    case LANG_ENGLISH:
-      if (SUBLANGID(m_langId) == SUBLANG_ENGLISH_UK && vKey == VK_OEM_8)  // "`¬"
-        key = GHOST_kKeyAccentGrave;
+    case u'.':
+      key = GHOST_kKeyNumpadPeriod;
+      break;
+    case u'/':
+      key = GHOST_kKeySlash;
+      break;
+    case u'`':
+    case u'²':
+      key = GHOST_kKeyAccentGrave;
+      break;
+    default:
+      if (vKey == VK_OEM_7) {
+        key = GHOST_kKeyQuote;
+      }
+      else if (vKey == VK_OEM_8) {
+        if (PRIMARYLANGID(m_langId) == LANG_FRENCH) {
+          /* oem key; used purely for shortcuts. */
+          key = GHOST_kKeyF13;
+        }
+      }
       break;
   }
 
@@ -788,9 +803,6 @@ GHOST_TKey GHOST_SystemWin32::convertKey(short vKey, short scanCode, short exten
       case VK_CLOSE_BRACKET:
         key = GHOST_kKeyRightBracket;
         break;
-      case VK_QUOTE:
-        key = GHOST_kKeyQuote;
-        break;
       case VK_GR_LESS:
         key = GHOST_kKeyGrLess;
         break;
@@ -832,9 +844,6 @@ GHOST_TKey GHOST_SystemWin32::convertKey(short vKey, short scanCode, short exten
       case VK_CAPITAL:
         key = GHOST_kKeyCapsLock;
         break;
-      case VK_OEM_8:
-        key = ((GHOST_SystemWin32 *)getSystem())->processSpecialKey(vKey, scanCode);
-        break;
       case VK_MEDIA_PLAY_PAUSE:
         key = GHOST_kKeyMediaPlay;
         break;
@@ -847,8 +856,10 @@ GHOST_TKey GHOST_SystemWin32::convertKey(short vKey, short scanCode, short exten
       case VK_MEDIA_NEXT_TRACK:
         key = GHOST_kKeyMediaLast;
         break;
+      case VK_OEM_7:
+      case VK_OEM_8:
       default:
-        key = GHOST_kKeyUnknown;
+        key = ((GHOST_SystemWin32 *)getSystem())->processSpecialKey(vKey, scanCode);
         break;
     }
   }
-- 
cgit v1.2.3


From 4f2f3114b7be2760d74ef1024626dd9f960a7bf3 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Sat, 26 Feb 2022 17:33:45 -0500
Subject: Fix: Incorrect assert in curves code

The attribute data might be null if the number of curves is zero.
While that is not common, an empty curves data-block is valid.
---
 source/blender/blenkernel/intern/curves_geometry.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index eb2a0b37af2..68797942b56 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -145,7 +145,6 @@ MutableSpan CurvesGeometry::curve_types()
                                                       nullptr,
                                                       this->curve_size,
                                                       ATTR_CURVE_TYPE.c_str());
-  BLI_assert(data != nullptr);
   return {data, this->curve_size};
 }
 
-- 
cgit v1.2.3


From 63891f9dad930f3d766d8586b909ca59ba089f3e Mon Sep 17 00:00:00 2001
From: Johnny Matthews 
Date: Sun, 27 Feb 2022 15:16:45 -0600
Subject: Fix: Crash in Duplicate Element Return early if a curve has a domain
 size of 0. T96060

---
 source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index 4c1d26e1012..c247a255e5b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -771,6 +771,9 @@ static void duplicate_points_curve(const GeometryComponentType component_type,
 {
   const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
   const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+  if (domain_size == 0) {
+    return;
+  }
 
   GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
   FieldEvaluator evaluator{field_context, domain_size};
-- 
cgit v1.2.3


From db4313610cab18933c1b1b1348720ea241b9d91e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Mon, 28 Feb 2022 15:16:16 +1100
Subject: Event System: drag events now use modifier state on drag start

Now drag & tweak can have modifier keys to be released while dragging.
without this, modifier keys needs to be held which is more noticeable
for tablet input or whenever the drag threshold is set to a large value.

Resolves T89989.
---
 source/blender/windowmanager/WM_types.h            |  9 +++++++
 .../blender/windowmanager/intern/wm_event_system.c | 31 +++++++++++++++++-----
 source/blender/windowmanager/intern/wm_gesture.c   |  2 ++
 .../blender/windowmanager/intern/wm_gesture_ops.c  | 15 ++++++++++-
 4 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 2f431bcd208..e9f12287d29 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -510,6 +510,10 @@ typedef struct wmGesture {
   struct wmGesture *next, *prev;
   /** #wmEvent.type */
   int event_type;
+  /** #wmEvent.modifier */
+  uint8_t event_modifier;
+  /** #wmEvent.keymodifier */
+  short event_keymodifier;
   /** Gesture type define. */
   int type;
   /** bounds of region to draw gesture within. */
@@ -628,6 +632,11 @@ typedef struct wmEvent {
   double prev_click_time;
   /** The location when the key is pressed (used to enforce drag thresholds). */
   int prev_click_xy[2];
+  /** The `modifier` at the point of the click action. */
+  uint8_t prev_click_modifier;
+  /** The `keymodifier` at the point of the click action. */
+  short prev_click_keymodifier;
+
   /**
    * The previous value of #wmEvent.xy,
    * Unlike other previous state variables, this is set on any mouse motion.
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 326b8c167b8..6753978eece 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3161,21 +3161,27 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
         if (WM_event_drag_test(event, event->prev_click_xy)) {
           win->event_queue_check_drag_handled = true;
 
-          int xy[2] = {UNPACK2(event->xy)};
-          short val = event->val;
-          short type = event->type;
+          const int prev_xy[2] = {UNPACK2(event->xy)};
+          const short prev_val = event->val;
+          const short prev_type = event->type;
+          const uint8_t prev_modifier = event->modifier;
+          const short prev_keymodifier = event->keymodifier;
 
           copy_v2_v2_int(event->xy, event->prev_click_xy);
           event->val = KM_CLICK_DRAG;
           event->type = event->prev_type;
+          event->modifier = event->prev_click_modifier;
+          event->keymodifier = event->prev_click_keymodifier;
 
           CLOG_INFO(WM_LOG_HANDLERS, 1, "handling PRESS_DRAG");
 
           action |= wm_handlers_do_intern(C, win, event, handlers);
 
-          event->val = val;
-          event->type = type;
-          copy_v2_v2_int(event->xy, xy);
+          event->keymodifier = prev_keymodifier;
+          event->modifier = prev_modifier;
+          event->val = prev_val;
+          event->type = prev_type;
+          copy_v2_v2_int(event->xy, prev_xy);
 
           win->event_queue_check_click = false;
           if (!wm_action_not_handled(action)) {
@@ -3205,7 +3211,16 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
         }
       }
       else if (event->val == KM_RELEASE) {
-        win->event_queue_check_drag = false;
+        if (win->event_queue_check_drag) {
+          if ((event->prev_type != event->type) &&
+              (ISKEYMODIFIER(event->type) || (event->type == event->prev_click_keymodifier))) {
+            /* Support releasing modifier keys without canceling the drag event, see T89989.
+             * NOTE: this logic is replicated for tweak gestures. */
+          }
+          else {
+            win->event_queue_check_drag = false;
+          }
+        }
       }
 
       if (event->prev_type == event->type) {
@@ -4692,6 +4707,8 @@ static void wm_event_prev_click_set(wmEvent *event, wmEvent *event_state)
   event->prev_click_time = event_state->prev_click_time = PIL_check_seconds_timer();
   event->prev_click_xy[0] = event_state->prev_click_xy[0] = event_state->xy[0];
   event->prev_click_xy[1] = event_state->prev_click_xy[1] = event_state->xy[1];
+  event->prev_click_modifier = event_state->prev_click_modifier = event_state->modifier;
+  event->prev_click_keymodifier = event_state->prev_click_keymodifier = event_state->keymodifier;
 }
 
 static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event)
diff --git a/source/blender/windowmanager/intern/wm_gesture.c b/source/blender/windowmanager/intern/wm_gesture.c
index 581c5f8a198..86ada4aaf2a 100644
--- a/source/blender/windowmanager/intern/wm_gesture.c
+++ b/source/blender/windowmanager/intern/wm_gesture.c
@@ -42,6 +42,8 @@ wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent
 
   gesture->type = type;
   gesture->event_type = event->type;
+  gesture->event_modifier = event->modifier;
+  gesture->event_keymodifier = event->keymodifier;
   gesture->winrct = region->winrct;
   gesture->user_data.use_free = true; /* Free if userdata is set. */
   gesture->modal_state = GESTURE_MODAL_NOP;
diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c
index 2a27a8df411..d7e62d549d0 100644
--- a/source/blender/windowmanager/intern/wm_gesture_ops.c
+++ b/source/blender/windowmanager/intern/wm_gesture_ops.c
@@ -507,6 +507,8 @@ static void gesture_tweak_modal(bContext *C, const wmEvent *event)
           tevent.type = EVT_TWEAK_M;
         }
         tevent.val = val;
+        tevent.modifier = gesture->event_modifier;
+        tevent.keymodifier = gesture->event_keymodifier;
         tevent.is_repeat = false;
         /* mouse coords! */
 
@@ -533,7 +535,18 @@ static void gesture_tweak_modal(bContext *C, const wmEvent *event)
       }
       break;
     default:
-      if (!ISTIMER(event->type) && event->type != EVENT_NONE) {
+      if (ISTIMER(event->type)) {
+        /* Ignore timers. */
+      }
+      else if (event->type == EVENT_NONE) {
+        /* Ignore none events. */
+      }
+      else if ((event->val == KM_RELEASE) &&
+               (ISKEYMODIFIER(event->type) || (event->type == event->prev_click_keymodifier))) {
+        /* Support releasing modifier keys without canceling the drag event, see T89989.
+         * NOTE: this logic is replicated for drag events. */
+      }
+      else {
         gesture_end = true;
       }
       break;
-- 
cgit v1.2.3


From 2a644deaa7b2a94c3c6b73c5fdac59b524337c80 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Mon, 28 Feb 2022 08:40:59 +0100
Subject: Fix T95981: Remove implicit conversion in texture paint shader.

This fixes a crash on selected platforms.
---
 source/blender/draw/engines/overlay/shaders/paint_texture_frag.glsl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/draw/engines/overlay/shaders/paint_texture_frag.glsl b/source/blender/draw/engines/overlay/shaders/paint_texture_frag.glsl
index 4d0692039a4..ebaa898429d 100644
--- a/source/blender/draw/engines/overlay/shaders/paint_texture_frag.glsl
+++ b/source/blender/draw/engines/overlay/shaders/paint_texture_frag.glsl
@@ -15,7 +15,7 @@ void main()
   if (maskInvertStencil) {
     mask.rgb = 1.0 - mask.rgb;
   }
-  float mask_step = smoothstep(0, 3.0, mask.r + mask.g + mask.b);
+  float mask_step = smoothstep(0.0, 3.0, mask.r + mask.g + mask.b);
   mask.rgb *= maskColor;
   mask.a = mask_step * opacity;
 
-- 
cgit v1.2.3


From 540fd10b4fda80eb9c806aae29363daefd2f990e Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Mon, 28 Feb 2022 09:03:47 +0100
Subject: Fix T95992: Crash Ancored strokes 2d texture painting.

When using ancored stroked the diameter of the stroke can be 0 what
leads to a division by zero that on certain platforms wrap to a large
negative number that cannot be looked up. This fix will clamp the size
of the brush to 1.
---
 source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
index 8d57a3d9152..5133b5fc982 100644
--- a/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
+++ b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
@@ -69,6 +69,7 @@ static void update_curve_mask(CurveMaskCache *curve_mask_cache,
 {
   BLI_assert(curve_mask_cache->curve_mask != nullptr);
   int offset = (int)floorf(diameter / 2.0f);
+  int clamped_radius = max_ff(radius, 1.0);
 
   unsigned short *m = curve_mask_cache->curve_mask;
 
@@ -92,7 +93,7 @@ static void update_curve_mask(CurveMaskCache *curve_mask_cache,
         pixel_xy[1] = static_cast(y) + aa_offset;
         for (int j = 0; j < aa_samples; j++) {
           const float len = len_v2v2(pixel_xy, bpos);
-          const int sample_index = min_ii((len / radius) * CurveSamplesBaseLen,
+          const int sample_index = min_ii((len / clamped_radius) * CurveSamplesBaseLen,
                                           CurveSamplesLen - 1);
           const float sample_weight = curve_mask_cache->sampled_curve[sample_index];
 
-- 
cgit v1.2.3


From cb736d2f030e3a66a6f65438b257452124c12523 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Mon, 28 Feb 2022 09:23:45 +0100
Subject: Fix T95298: Multiview images not displaying correctly.

Image users have a multi_index that wasn't updated in case it is a normal
image.
---
 source/blender/draw/engines/image/image_engine.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/draw/engines/image/image_engine.cc b/source/blender/draw/engines/image/image_engine.cc
index 201733f41a7..48afc9c7f7a 100644
--- a/source/blender/draw/engines/image/image_engine.cc
+++ b/source/blender/draw/engines/image/image_engine.cc
@@ -122,6 +122,7 @@ class ImageEngine {
     space->release_buffer(instance_data->image, image_buffer, lock);
 
     ImageUser *iuser = space->get_image_user();
+    BKE_image_multiview_index(instance_data->image, iuser);
     drawing_mode.cache_image(vedata, instance_data->image, iuser);
   }
 
-- 
cgit v1.2.3


From 307d612404caa1ed3252d568b506353e07242296 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Mon, 28 Feb 2022 09:45:52 +0100
Subject: Fix compilation warning.

---
 source/blender/editors/sculpt_paint/paint_image_ops_paint.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc
index 79701bba01c..786fcc47526 100644
--- a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc
+++ b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc
@@ -44,6 +44,7 @@ namespace blender::ed::sculpt_paint::image::ops::paint {
  */
 class AbstractPaintMode {
  public:
+  virtual ~AbstractPaintMode() = default;
   virtual void *paint_new_stroke(
       bContext *C, wmOperator *op, Object *ob, const float mouse[2], int mode) = 0;
   virtual void paint_stroke(bContext *C,
-- 
cgit v1.2.3


From 6bbf63f25195d97c2770470a973bafb35dda5e28 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Mon, 28 Feb 2022 10:54:07 +0100
Subject: Fix T96054: Switching vertex group does not update display

Caused by 0f89bcdbebf5 and was not fully addressed by 6f9828289f39:
tagging an ID with flag 0 is to be seen as an explicit tag for copy
on write.

Would be nice to either consolidate code paths of flag 0 and explicit
component tag, or get rid of tagging with 0 flag, but that is above of
what we can do for the upcoming release.
---
 source/blender/depsgraph/intern/depsgraph_tag.cc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc
index 11074b5bf72..f8de6b4580c 100644
--- a/source/blender/depsgraph/intern/depsgraph_tag.cc
+++ b/source/blender/depsgraph/intern/depsgraph_tag.cc
@@ -499,6 +499,10 @@ void deg_graph_node_tag_zero(Main *bmain,
     if (comp_node->type == NodeType::ANIMATION) {
       continue;
     }
+    else if (comp_node->type == NodeType::COPY_ON_WRITE) {
+      id_node->is_cow_explicitly_tagged = true;
+    }
+
     comp_node->tag_update(graph, update_source);
   }
   deg_graph_id_tag_legacy_compat(bmain, graph, id, (IDRecalcFlag)0, update_source);
-- 
cgit v1.2.3


From 1558b270e9fcbc2e23fa248b0e7e770dddae155c Mon Sep 17 00:00:00 2001
From: Colin Marmont 
Date: Mon, 28 Feb 2022 12:23:56 +0100
Subject: Animation: Sensible frame range for motion paths

Motion paths can now be initialised to more sensible frame ranges,
rather than simply 1-250:

- Scene Frame Range
- Selected Keyframes
- All Keyframes

The Motion Paths operators are now also added to the Object context menu
and the Dopesheet context menu.

The scene range operator was removed, because the operators now
automatically find the range when baking the motion paths.

The clear operator now appears separated in "Selected Only" and "All",
because it was not clear for the user what the button was doing.

Reviewed By: sybren, looch

Maniphest Tasks: T93047

Differential Revision: https://developer.blender.org/D13687
---
 .../scripts/startup/bl_ui/properties_animviz.py    |  68 ++++++-------
 release/scripts/startup/bl_ui/space_dopesheet.py   |   4 +
 release/scripts/startup/bl_ui/space_graph.py       |   3 +
 release/scripts/startup/bl_ui/space_view3d.py      |  29 +++++-
 .../blender/blenkernel/intern/anim_visualization.c |   5 +
 .../draw/engines/overlay/overlay_motion_path.c     |   4 +-
 .../blender/editors/animation/anim_motion_paths.c  |  49 ++++++++++
 .../blender/editors/animation/keyframes_keylist.cc |  48 +++++++--
 source/blender/editors/armature/pose_edit.c        |  66 ++++++++-----
 source/blender/editors/include/ED_anim_api.h       |   9 ++
 .../blender/editors/include/ED_keyframes_keylist.h |   6 +-
 source/blender/editors/object/object_edit.c        | 108 +++++++--------------
 source/blender/editors/object/object_intern.h      |   1 -
 source/blender/editors/object/object_ops.c         |   1 -
 source/blender/editors/space_nla/nla_draw.c        |   2 +-
 source/blender/makesdna/DNA_action_types.h         |  12 ++-
 source/blender/makesrna/RNA_enum_items.h           |   2 +
 source/blender/makesrna/intern/rna_animviz.c       |  43 +++++---
 18 files changed, 296 insertions(+), 164 deletions(-)

diff --git a/release/scripts/startup/bl_ui/properties_animviz.py b/release/scripts/startup/bl_ui/properties_animviz.py
index 548ce72c429..629399084ba 100644
--- a/release/scripts/startup/bl_ui/properties_animviz.py
+++ b/release/scripts/startup/bl_ui/properties_animviz.py
@@ -24,55 +24,55 @@ class MotionPathButtonsPanel:
         layout.use_property_split = True
         layout.use_property_decorate = False
 
-        row = layout.row(align=True)
-        row.prop(mps, "type")
-        if mps.type == 'RANGE':
-            if bones:
-                row.operator("pose.paths_range_update", text="", icon='TIME')
-            else:
-                row.operator("object.paths_range_update", text="", icon='TIME')
-
+        # Display Range
+        col = layout.column(align=True)
+        col.prop(mps, "type")
+        col = layout.column(align=True)
         if mps.type == 'CURRENT_FRAME':
-            col = layout.column(align=True)
             col.prop(mps, "frame_before", text="Frame Range Before")
             col.prop(mps, "frame_after", text="After")
-            col.prop(mps, "frame_step", text="Step")
-        elif mps.type == 'RANGE':
-            col = layout.column(align=True)
-            col.prop(mps, "frame_start", text="Frame Range Start")
-            col.prop(mps, "frame_end", text="End")
-            col.prop(mps, "frame_step", text="Step")
+        col.prop(mps, "frame_step", text="Step")
+
+        # Calculation Range
+        col = layout.column(align=True)
+        row = col.row(align=True)
+        row.prop(mps, "range", text="Calculation Range")
 
         if mpath:
             col = layout.column(align=True)
-            col.enabled = False
-            if bones:
-                col.prop(mpath, "frame_start", text="Bone Cache From")
-            else:
-                col.prop(mpath, "frame_start", text="Cache From")
-            col.prop(mpath, "frame_end", text="To")
+            row = col.row(align=True)
+            row.enabled = False
+            row.prop(mpath, "frame_start", text="Cached Range")
+            row.prop(mpath, "frame_end", text="")
 
             col = layout.column(align=True)
-
+            row = col.row(align=True)
             if bones:
-                col.operator("pose.paths_update", text="Update Paths", icon='BONE_DATA')
+                row.operator("pose.paths_update", text="Update Paths", icon='BONE_DATA')
+                row.operator("pose.paths_clear", text="", icon='X').only_selected = True
+                row = col.row(align=True)
+                row.operator("object.paths_update_visible", text="Update All Paths", icon='WORLD')
+                row.operator("pose.paths_clear", text="", icon='X').only_selected = False
             else:
-                col.operator("object.paths_update", text="Update Paths", icon='OBJECT_DATA')
+                row.operator("object.paths_update", text="Update Paths", icon='OBJECT_DATA')
+                row.operator("object.paths_clear", text="", icon='X').only_selected = True
+                row = col.row(align=True)
+                row.operator("object.paths_update_visible", text="Update All Paths", icon='WORLD')
+                row.operator("object.paths_clear", text="", icon='X').only_selected = False
         else:
             col = layout.column(align=True)
-            col.label(text="Nothing to show yet...", icon='ERROR')
+            col.label(text="No Motion Path generated yet", icon='ERROR')
 
+            # Don't invoke settings popup because settings are right above
+            col.operator_context = 'EXEC_REGION_WIN'
             if bones:
-                col.operator("pose.paths_calculate", text="Calculate...", icon='BONE_DATA')
+                col.operator(
+                    "pose.paths_calculate", text="Generate for selected bones", icon='BONE_DATA')
             else:
-                col.operator("object.paths_calculate", text="Calculate...", icon='OBJECT_DATA')
-
-        row = col.row(align=True)
-        row.operator("object.paths_update_visible", text="Update All Paths", icon='WORLD')
-        if bones:
-            row.operator("pose.paths_clear", text="", icon='X')
-        else:
-            row.operator("object.paths_clear", text="", icon='X')
+                col.operator("object.paths_calculate", text="Generate", icon='OBJECT_DATA')
+            row = col.row(align=True)
+            row.operator("object.paths_update_visible", text="Update All Paths", icon='WORLD')
+            row.operator("object.paths_clear", text="", icon='X').only_selected = False
 
 
 class MotionPathButtonsPanel_display:
diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py
index 8e328e7cf2b..7b7fc9dcf77 100644
--- a/release/scripts/startup/bl_ui/space_dopesheet.py
+++ b/release/scripts/startup/bl_ui/space_dopesheet.py
@@ -663,6 +663,10 @@ class DOPESHEET_MT_context_menu(Menu):
         layout.operator_menu_enum("action.mirror", "type", text="Mirror")
         layout.operator_menu_enum("action.snap", "type", text="Snap")
 
+        if st.mode == 'DOPESHEET':
+            layout.separator()
+            layout.menu("VIEW3D_MT_motion_path")
+
 
 class DOPESHEET_MT_channel_context_menu(Menu):
     bl_label = "Dope Sheet Channel Context Menu"
diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py
index 6f9ef12c3b7..6bc11d51ca0 100644
--- a/release/scripts/startup/bl_ui/space_graph.py
+++ b/release/scripts/startup/bl_ui/space_graph.py
@@ -390,6 +390,9 @@ class GRAPH_MT_context_menu(Menu):
         layout.operator_menu_enum("graph.mirror", "type", text="Mirror")
         layout.operator_menu_enum("graph.snap", "type", text="Snap")
 
+        layout.separator()
+        layout.menu("VIEW3D_MT_motion_path")
+
 
 class GRAPH_MT_pivot_pie(Menu):
     bl_label = "Pivot Point"
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 2ac6358bd9c..5dedb3c4029 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -2383,6 +2383,25 @@ class VIEW3D_MT_object_clear(Menu):
         layout.operator("object.origin_clear", text="Origin")
 
 
+class VIEW3D_MT_motion_path(Menu):
+    bl_label = "Motion Paths"
+
+    def draw(self, _context):
+        layout = self.layout
+        ob = _context.object
+        if ob.mode == 'OBJECT':
+            layout.operator("object.paths_calculate")
+            layout.operator("object.paths_update")
+            layout.operator("object.paths_update_visible")
+            layout.operator("object.paths_clear", text="Clear all").only_selected = False
+            layout.operator("object.paths_clear", text="Clear selected").only_selected = True
+        elif ob.mode == 'POSE':
+            layout.operator("pose.paths_calculate")
+            layout.operator("pose.paths_update")
+            layout.operator("pose.paths_clear", text="Clear all").only_selected = False
+            layout.operator("pose.paths_clear", text="Clear selected").only_selected = True
+
+
 class VIEW3D_MT_object_context_menu(Menu):
     bl_label = "Object Context Menu"
 
@@ -2584,6 +2603,7 @@ class VIEW3D_MT_object_context_menu(Menu):
         layout.menu("VIEW3D_MT_mirror")
         layout.menu("VIEW3D_MT_snap")
         layout.menu("VIEW3D_MT_object_parent")
+        layout.menu("VIEW3D_MT_motion_path")
         layout.operator_context = 'INVOKE_REGION_WIN'
 
         if view and view.local_view:
@@ -3631,10 +3651,10 @@ class VIEW3D_MT_pose_context_menu(Menu):
 
         layout.separator()
 
-        layout.operator("pose.paths_calculate", text="Calculate Motion Paths")
-        layout.operator("pose.paths_clear", text="Clear Motion Paths")
-        layout.operator("pose.paths_update", text="Update Armature Motion Paths")
-        layout.operator("object.paths_update_visible", text="Update All Motion Paths")
+        layout.operator("pose.paths_calculate")
+        layout.operator("pose.paths_update")
+        layout.operator("pose.paths_clear", text="Clear all").only_selected = False
+        layout.operator("pose.paths_clear", text="Clear selected").only_selected = True
 
         layout.separator()
 
@@ -7623,6 +7643,7 @@ classes = (
     VIEW3D_MT_object_quick_effects,
     VIEW3D_MT_object_showhide,
     VIEW3D_MT_object_cleanup,
+    VIEW3D_MT_motion_path,
     VIEW3D_MT_make_single_user,
     VIEW3D_MT_make_links,
     VIEW3D_MT_brush_paint_modes,
diff --git a/source/blender/blenkernel/intern/anim_visualization.c b/source/blender/blenkernel/intern/anim_visualization.c
index 53a3a7e3712..f4c6a29c252 100644
--- a/source/blender/blenkernel/intern/anim_visualization.c
+++ b/source/blender/blenkernel/intern/anim_visualization.c
@@ -153,6 +153,11 @@ bMotionPath *animviz_verify_motionpaths(ReportList *reports,
     if ((mpath->start_frame != mpath->end_frame) && (mpath->length > 0)) {
       /* outer check ensures that we have some curve data for this path */
       if (mpath->length == expected_length) {
+        /* The length might be the same, but the start and end could be different */
+        if (mpath->start_frame != avs->path_sf) {
+          mpath->start_frame = avs->path_sf;
+          mpath->end_frame = avs->path_ef;
+        }
         /* return/use this as it is already valid length */
         return mpath;
       }
diff --git a/source/blender/draw/engines/overlay/overlay_motion_path.c b/source/blender/draw/engines/overlay/overlay_motion_path.c
index 58825923f37..aeba721e7ac 100644
--- a/source/blender/draw/engines/overlay/overlay_motion_path.c
+++ b/source/blender/draw/engines/overlay/overlay_motion_path.c
@@ -90,8 +90,8 @@ static void motion_path_get_frame_range_to_draw(bAnimVizSettings *avs,
     end = current_frame + avs->path_ac + 1;
   }
   else {
-    start = avs->path_sf;
-    end = avs->path_ef;
+    start = mpath->start_frame;
+    end = mpath->end_frame;
   }
 
   if (start > end) {
diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c
index 539227933cf..2c99cd1cc1f 100644
--- a/source/blender/editors/animation/anim_motion_paths.c
+++ b/source/blender/editors/animation/anim_motion_paths.c
@@ -340,6 +340,55 @@ static void motionpath_free_free_tree_data(ListBase *targets)
   }
 }
 
+void animviz_motionpath_compute_range(Object *ob, Scene *scene)
+{
+  struct AnimKeylist *keylist = ED_keylist_create();
+  bAnimVizSettings *avs;
+  if (ob->mode == OB_MODE_POSE) {
+    avs = &ob->pose->avs;
+    bArmature *arm = ob->data;
+
+    if (!ELEM(NULL, ob->adt, ob->adt->action, arm->adt)) {
+      /* Loop through all the fcurves and get only the keylists for the bone location fcurves */
+      LISTBASE_FOREACH (FCurve *, fcu, &ob->adt->action->curves) {
+        if (strstr(fcu->rna_path, "pose.bones[") && strstr(fcu->rna_path, "location")) {
+          fcurve_to_keylist(arm->adt, fcu, keylist, 0);
+        }
+      }
+    }
+  }
+  else {
+    avs = &ob->avs;
+
+    if (!ELEM(NULL, ob->adt, ob->adt->action)) {
+      /* Loop through all the fcurves and get only the keylists for the location fcurves */
+      LISTBASE_FOREACH (FCurve *, fcu, &ob->adt->action->curves) {
+        if (strcmp(fcu->rna_path, "location") == 0) {
+          fcurve_to_keylist(ob->adt, fcu, keylist, 0);
+        }
+      }
+    }
+  }
+
+  if (ED_keylist_is_empty(keylist) || (avs->path_range == MOTIONPATH_RANGE_SCENE)) {
+    /* Apply scene frame range if no keys where found or if scene range is selected */
+    avs->path_sf = PSFRA;
+    avs->path_ef = PEFRA;
+  }
+  else {
+    /* Compute keys range */
+    Range2f frame_range;
+    const bool only_selected = avs->path_range == MOTIONPATH_RANGE_KEYS_SELECTED;
+    /* Get range for all keys if selected_only is false or if no keys are selected */
+    if (!(only_selected && ED_keylist_selected_keys_frame_range(keylist, &frame_range))) {
+      ED_keylist_all_keys_frame_range(keylist, &frame_range);
+    }
+    avs->path_sf = frame_range.min;
+    avs->path_ef = frame_range.max;
+  }
+  ED_keylist_free(keylist);
+}
+
 void animviz_calc_motionpaths(Depsgraph *depsgraph,
                               Main *bmain,
                               Scene *scene,
diff --git a/source/blender/editors/animation/keyframes_keylist.cc b/source/blender/editors/animation/keyframes_keylist.cc
index 3f9592fb4ae..0b795fea278 100644
--- a/source/blender/editors/animation/keyframes_keylist.cc
+++ b/source/blender/editors/animation/keyframes_keylist.cc
@@ -304,7 +304,21 @@ const struct ListBase *ED_keylist_listbase(const AnimKeylist *keylist)
   return &keylist->key_columns;
 }
 
-bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range)
+static void keylist_first_last(const struct AnimKeylist *keylist,
+                               const struct ActKeyColumn **first_column,
+                               const struct 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];
+  }
+  else {
+    *first_column = static_cast(keylist->key_columns.first);
+    *last_column = static_cast(keylist->key_columns.last);
+  }
+}
+
+bool ED_keylist_all_keys_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range)
 {
   BLI_assert(r_frame_range);
 
@@ -314,13 +328,33 @@ bool ED_keylist_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_
 
   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];
+  keylist_first_last(keylist, &first_column, &last_column);
+  r_frame_range->min = first_column->cfra;
+  r_frame_range->max = last_column->cfra;
+
+  return true;
+}
+
+bool ED_keylist_selected_keys_frame_range(const struct AnimKeylist *keylist,
+                                          Range2f *r_frame_range)
+{
+  BLI_assert(r_frame_range);
+
+  if (ED_keylist_is_empty(keylist)) {
+    return false;
   }
-  else {
-    first_column = static_cast(keylist->key_columns.first);
-    last_column = static_cast(keylist->key_columns.last);
+
+  const ActKeyColumn *first_column;
+  const ActKeyColumn *last_column;
+  keylist_first_last(keylist, &first_column, &last_column);
+  while (first_column && !(first_column->sel & SELECT)) {
+    first_column = first_column->next;
+  }
+  while (last_column && !(last_column->sel & SELECT)) {
+    last_column = last_column->prev;
+  }
+  if (!first_column || !last_column || first_column == last_column) {
+    return false;
   }
   r_frame_range->min = first_column->cfra;
   r_frame_range->max = last_column->cfra;
diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c
index 13abcefa632..128126e515e 100644
--- a/source/blender/editors/armature/pose_edit.c
+++ b/source/blender/editors/armature/pose_edit.c
@@ -220,8 +220,8 @@ static int pose_calculate_paths_invoke(bContext *C, wmOperator *op, const wmEven
     bAnimVizSettings *avs = &ob->pose->avs;
     PointerRNA avs_ptr;
 
-    RNA_int_set(op->ptr, "start_frame", avs->path_sf);
-    RNA_int_set(op->ptr, "end_frame", avs->path_ef);
+    RNA_enum_set(op->ptr, "display_type", avs->path_type);
+    RNA_enum_set(op->ptr, "range", avs->path_range);
 
     RNA_pointer_create(NULL, &RNA_AnimVizMotionPaths, avs, &avs_ptr);
     RNA_enum_set(op->ptr, "bake_location", RNA_enum_get(&avs_ptr, "bake_location"));
@@ -229,7 +229,7 @@ static int pose_calculate_paths_invoke(bContext *C, wmOperator *op, const wmEven
 
   /* show popup dialog to allow editing of range... */
   /* FIXME: hard-coded dimensions here are just arbitrary. */
-  return WM_operator_props_dialog_popup(C, op, 200);
+  return WM_operator_props_dialog_popup(C, op, 270);
 }
 
 /* For the object with pose/action: create path curves for selected bones
@@ -249,8 +249,9 @@ static int pose_calculate_paths_exec(bContext *C, wmOperator *op)
     bAnimVizSettings *avs = &ob->pose->avs;
     PointerRNA avs_ptr;
 
-    avs->path_sf = RNA_int_get(op->ptr, "start_frame");
-    avs->path_ef = RNA_int_get(op->ptr, "end_frame");
+    avs->path_type = RNA_enum_get(op->ptr, "display_type");
+    avs->path_range = RNA_enum_get(op->ptr, "range");
+    animviz_motionpath_compute_range(ob, scene);
 
     RNA_pointer_create(NULL, &RNA_AnimVizMotionPaths, avs, &avs_ptr);
     RNA_enum_set(&avs_ptr, "bake_location", RNA_enum_get(op->ptr, "bake_location"));
@@ -258,7 +259,6 @@ static int pose_calculate_paths_exec(bContext *C, wmOperator *op)
 
   /* set up path data for bones being calculated */
   CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones_from_active_object) {
-    /* verify makes sure that the selected bone has a bone with the appropriate settings */
     animviz_verify_motionpaths(op->reports, scene, ob, pchan);
   }
   CTX_DATA_END;
@@ -281,6 +281,19 @@ static int pose_calculate_paths_exec(bContext *C, wmOperator *op)
   return OPERATOR_FINISHED;
 }
 
+static bool pose_calculate_paths_poll(bContext *C)
+{
+  if (!ED_operator_posemode_exclusive(C)) {
+    return false;
+  }
+  Object *ob = CTX_data_active_object(C);
+  bArmature *arm = ob->data;
+  if (ELEM(NULL, ob, arm, ob->pose)) {
+    return false;
+  }
+  return true;
+}
+
 void POSE_OT_paths_calculate(wmOperatorType *ot)
 {
   /* identifiers */
@@ -291,30 +304,24 @@ void POSE_OT_paths_calculate(wmOperatorType *ot)
   /* api callbacks */
   ot->invoke = pose_calculate_paths_invoke;
   ot->exec = pose_calculate_paths_exec;
-  ot->poll = ED_operator_posemode_exclusive;
+  ot->poll = pose_calculate_paths_poll;
 
   /* flags */
   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
   /* properties */
-  RNA_def_int(ot->srna,
-              "start_frame",
-              1,
-              MINAFRAME,
-              MAXFRAME,
-              "Start",
-              "First frame to calculate bone paths on",
-              MINFRAME,
-              MAXFRAME / 2.0);
-  RNA_def_int(ot->srna,
-              "end_frame",
-              250,
-              MINAFRAME,
-              MAXFRAME,
-              "End",
-              "Last frame to calculate bone paths on",
-              MINFRAME,
-              MAXFRAME / 2.0);
+  RNA_def_enum(ot->srna,
+               "display_type",
+               rna_enum_motionpath_display_type_items,
+               MOTIONPATH_TYPE_RANGE,
+               "Display type",
+               "");
+  RNA_def_enum(ot->srna,
+               "range",
+               rna_enum_motionpath_range_items,
+               MOTIONPATH_RANGE_SCENE,
+               "Computation Range",
+               "");
 
   RNA_def_enum(ot->srna,
                "bake_location",
@@ -336,7 +343,7 @@ static bool pose_update_paths_poll(bContext *C)
   return false;
 }
 
-static int pose_update_paths_exec(bContext *C, wmOperator *UNUSED(op))
+static int pose_update_paths_exec(bContext *C, wmOperator *op)
 {
   Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
   Scene *scene = CTX_data_scene(C);
@@ -344,6 +351,13 @@ static int pose_update_paths_exec(bContext *C, wmOperator *UNUSED(op))
   if (ELEM(NULL, ob, scene)) {
     return OPERATOR_CANCELLED;
   }
+  animviz_motionpath_compute_range(ob, scene);
+
+  /* set up path data for bones being calculated */
+  CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones_from_active_object) {
+    animviz_verify_motionpaths(op->reports, scene, ob, pchan);
+  }
+  CTX_DATA_END;
 
   /* Calculate the bones that now have motion-paths. */
   /* TODO: only make for the selected bones? */
diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h
index e6a68f7fab7..3e045c9c43a 100644
--- a/source/blender/editors/include/ED_anim_api.h
+++ b/source/blender/editors/include/ED_anim_api.h
@@ -1089,6 +1089,15 @@ void animviz_calc_motionpaths(struct Depsgraph *depsgraph,
                               eAnimvizCalcRange range,
                               bool restore);
 
+/**
+ * Update motion path computation range (in ob.avs or armature.avs) from user choice in
+ *  ob.avs.path_range or arm.avs.path_range, depending on active user mode.
+ *
+ * @param ob: Object to compute range for (must be provided)
+ * @param scene: Used when scene range is choosen
+ */
+void animviz_motionpath_compute_range(struct Object *ob, struct Scene *scene);
+
 /**
  * Get list of motion paths to be baked for the given object.
  * - assumes the given list is ready to be used.
diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h
index 6a8820d0083..39f050c9739 100644
--- a/source/blender/editors/include/ED_keyframes_keylist.h
+++ b/source/blender/editors/include/ED_keyframes_keylist.h
@@ -131,7 +131,11 @@ const struct ActKeyColumn *ED_keylist_find_any_between(const struct AnimKeylist
                                                        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);
+bool ED_keylist_all_keys_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range);
+/* Return the selected keyframe's range. If none are selected, return False and does not affect
+ * the frame range. */
+bool ED_keylist_selected_keys_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);
 
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 4da278b7a31..96970c3e208 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -21,6 +21,7 @@
 
 #include "BLT_translation.h"
 
+#include "DNA_anim_types.h"
 #include "DNA_armature_types.h"
 #include "DNA_collection_types.h"
 #include "DNA_curve_types.h"
@@ -73,6 +74,7 @@
 #include "ED_curve.h"
 #include "ED_gpencil.h"
 #include "ED_image.h"
+#include "ED_keyframes_keylist.h"
 #include "ED_lattice.h"
 #include "ED_mball.h"
 #include "ED_mesh.h"
@@ -1209,30 +1211,32 @@ static int object_calculate_paths_invoke(bContext *C, wmOperator *op, const wmEv
   /* set default settings from existing/stored settings */
   {
     bAnimVizSettings *avs = &ob->avs;
-
-    RNA_int_set(op->ptr, "start_frame", avs->path_sf);
-    RNA_int_set(op->ptr, "end_frame", avs->path_ef);
+    RNA_enum_set(op->ptr, "display_type", avs->path_type);
+    RNA_enum_set(op->ptr, "range", avs->path_range);
   }
 
   /* show popup dialog to allow editing of range... */
   /* FIXME: hard-coded dimensions here are just arbitrary. */
-  return WM_operator_props_dialog_popup(C, op, 200);
+  return WM_operator_props_dialog_popup(C, op, 270);
 }
 
 /* Calculate/recalculate whole paths (avs.path_sf to avs.path_ef) */
 static int object_calculate_paths_exec(bContext *C, wmOperator *op)
 {
   Scene *scene = CTX_data_scene(C);
-  int start = RNA_int_get(op->ptr, "start_frame");
-  int end = RNA_int_get(op->ptr, "end_frame");
+  short path_type = RNA_enum_get(op->ptr, "display_type");
+  short path_range = RNA_enum_get(op->ptr, "range");
 
-  /* set up path data for bones being calculated */
+  /* set up path data for objects being calculated */
   CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
-    bAnimVizSettings *avs = &ob->avs;
-
-    /* grab baking settings from operator settings */
-    avs->path_sf = start;
-    avs->path_ef = end;
+    /* When operator is not invoked, dismiss the operator settings */
+    if (op->flag & OP_IS_INVOKE) {
+      bAnimVizSettings *avs = &ob->avs;
+      /* grab baking settings from operator settings */
+      avs->path_type = path_type;
+      avs->path_range = path_range;
+    }
+    animviz_motionpath_compute_range(ob, scene);
 
     /* verify that the selected object has the appropriate settings */
     animviz_verify_motionpaths(op->reports, scene, ob, NULL);
@@ -1251,9 +1255,9 @@ static int object_calculate_paths_exec(bContext *C, wmOperator *op)
 void OBJECT_OT_paths_calculate(wmOperatorType *ot)
 {
   /* identifiers */
-  ot->name = "Calculate Object Paths";
+  ot->name = "Calculate Object Motion Paths";
   ot->idname = "OBJECT_OT_paths_calculate";
-  ot->description = "Calculate motion paths for the selected objects";
+  ot->description = "Generate motion paths for the selected objects";
 
   /* api callbacks */
   ot->invoke = object_calculate_paths_invoke;
@@ -1264,24 +1268,18 @@ void OBJECT_OT_paths_calculate(wmOperatorType *ot)
   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
   /* properties */
-  RNA_def_int(ot->srna,
-              "start_frame",
-              1,
-              MINAFRAME,
-              MAXFRAME,
-              "Start",
-              "First frame to calculate object paths on",
-              MINFRAME,
-              MAXFRAME / 2.0);
-  RNA_def_int(ot->srna,
-              "end_frame",
-              250,
-              MINAFRAME,
-              MAXFRAME,
-              "End",
-              "Last frame to calculate object paths on",
-              MINFRAME,
-              MAXFRAME / 2.0);
+  RNA_def_enum(ot->srna,
+               "display_type",
+               rna_enum_motionpath_display_type_items,
+               MOTIONPATH_TYPE_RANGE,
+               "Display type",
+               "");
+  RNA_def_enum(ot->srna,
+               "range",
+               rna_enum_motionpath_range_items,
+               MOTIONPATH_RANGE_SCENE,
+               "Computation Range",
+               "");
 }
 
 /** \} */
@@ -1300,13 +1298,19 @@ static bool object_update_paths_poll(bContext *C)
   return false;
 }
 
-static int object_update_paths_exec(bContext *C, wmOperator *UNUSED(op))
+static int object_update_paths_exec(bContext *C, wmOperator *op)
 {
   Scene *scene = CTX_data_scene(C);
 
   if (scene == NULL) {
     return OPERATOR_CANCELLED;
   }
+  CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
+    animviz_motionpath_compute_range(ob, scene);
+    /* verify that the selected object has the appropriate settings */
+    animviz_verify_motionpaths(op->reports, scene, ob, NULL);
+  }
+  CTX_DATA_END;
 
   /* calculate the paths for objects that have them (and are tagged to get refreshed) */
   ED_objects_recalculate_paths_selected(C, scene, OBJECT_PATH_CALC_RANGE_FULL);
@@ -1456,44 +1460,6 @@ void OBJECT_OT_paths_clear(wmOperatorType *ot)
 
 /** \} */
 
-/* -------------------------------------------------------------------- */
-/** \name Update Motion Paths Range from Scene Operator
- * \{ */
-
-static int object_update_paths_range_exec(bContext *C, wmOperator *UNUSED(op))
-{
-  Scene *scene = CTX_data_scene(C);
-
-  /* Loop over all editable objects in scene. */
-  CTX_DATA_BEGIN (C, Object *, ob, editable_objects) {
-    /* use Preview Range or Full Frame Range - whichever is in use */
-    ob->avs.path_sf = PSFRA;
-    ob->avs.path_ef = PEFRA;
-
-    /* tag for updates */
-    DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
-    WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
-  }
-  CTX_DATA_END;
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_paths_range_update(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Update Range from Scene";
-  ot->idname = "OBJECT_OT_paths_range_update";
-  ot->description = "Update frame range for motion paths from the Scene's current frame range";
-
-  /* callbacks */
-  ot->exec = object_update_paths_range_exec;
-  ot->poll = ED_operator_object_active_editable;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-}
-
 /** \} */
 
 /* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 0aac5957c9d..d88dc98318a 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -76,7 +76,6 @@ void OBJECT_OT_shade_flat(struct wmOperatorType *ot);
 void OBJECT_OT_paths_calculate(struct wmOperatorType *ot);
 void OBJECT_OT_paths_update(struct wmOperatorType *ot);
 void OBJECT_OT_paths_clear(struct wmOperatorType *ot);
-void OBJECT_OT_paths_range_update(struct wmOperatorType *ot);
 void OBJECT_OT_paths_update_visible(struct wmOperatorType *ot);
 void OBJECT_OT_forcefield_toggle(struct wmOperatorType *ot);
 
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index 35f5ede270d..45ee4daa441 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -45,7 +45,6 @@ void ED_operatortypes_object(void)
   WM_operatortype_append(OBJECT_OT_paths_calculate);
   WM_operatortype_append(OBJECT_OT_paths_update);
   WM_operatortype_append(OBJECT_OT_paths_clear);
-  WM_operatortype_append(OBJECT_OT_paths_range_update);
   WM_operatortype_append(OBJECT_OT_paths_update_visible);
   WM_operatortype_append(OBJECT_OT_forcefield_toggle);
 
diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c
index eda9f89b51c..d8a0fde6d07 100644
--- a/source/blender/editors/space_nla/nla_draw.c
+++ b/source/blender/editors/space_nla/nla_draw.c
@@ -110,7 +110,7 @@ static void nla_action_draw_keyframes(
    */
 
   Range2f frame_range;
-  ED_keylist_frame_range(keylist, &frame_range);
+  ED_keylist_all_keys_frame_range(keylist, &frame_range);
   immRectf(pos_id, frame_range.min, ymin + 2, frame_range.max, ymax - 2);
   immUnbindProgram();
 
diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h
index 2a6b86711ab..fa0898e6ea5 100644
--- a/source/blender/makesdna/DNA_action_types.h
+++ b/source/blender/makesdna/DNA_action_types.h
@@ -104,12 +104,14 @@ typedef struct bAnimVizSettings {
   short path_type;
   /** Number of frames between points indicated on the paths. */
   short path_step;
+  /** #eMotionPath_Ranges. */
+  short path_range;
 
   /** #eMotionPaths_ViewFlag. */
   short path_viewflag;
   /** #eMotionPaths_BakeFlag. */
   short path_bakeflag;
-  char _pad[6];
+  char _pad[4];
 
   /** Start and end frames of path-calculation range. */
   int path_sf, path_ef;
@@ -131,6 +133,14 @@ typedef enum eMotionPaths_Types {
   MOTIONPATH_TYPE_ACFRA = 1,
 } eMotionPath_Types;
 
+/* bAnimVizSettings->path_range */
+typedef enum eMotionPath_Ranges {
+  /* Default is scene */
+  MOTIONPATH_RANGE_SCENE = 0,
+  MOTIONPATH_RANGE_KEYS_SELECTED = 1,
+  MOTIONPATH_RANGE_KEYS_ALL = 2,
+} eMotionPath_Ranges;
+
 /* bAnimVizSettings->path_viewflag */
 typedef enum eMotionPaths_ViewFlag {
   /* show frames on path */
diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h
index eb899aae3de..352ff8f1f42 100644
--- a/source/blender/makesrna/RNA_enum_items.h
+++ b/source/blender/makesrna/RNA_enum_items.h
@@ -87,6 +87,8 @@ DEF_ENUM(rna_enum_keying_flag_items_api)
 DEF_ENUM(rna_enum_fmodifier_type_items)
 
 DEF_ENUM(rna_enum_motionpath_bake_location_items)
+DEF_ENUM(rna_enum_motionpath_display_type_items)
+DEF_ENUM(rna_enum_motionpath_range_items)
 
 DEF_ENUM(rna_enum_event_value_all_items)
 DEF_ENUM(rna_enum_event_value_keymouse_items)
diff --git a/source/blender/makesrna/intern/rna_animviz.c b/source/blender/makesrna/intern/rna_animviz.c
index cb993931296..0453b327b45 100644
--- a/source/blender/makesrna/intern/rna_animviz.c
+++ b/source/blender/makesrna/intern/rna_animviz.c
@@ -36,6 +36,27 @@ const EnumPropertyItem rna_enum_motionpath_bake_location_items[] = {
     {0, NULL, 0, NULL, NULL},
 };
 
+const EnumPropertyItem rna_enum_motionpath_display_type_items[] = {
+    {MOTIONPATH_TYPE_ACFRA,
+     "CURRENT_FRAME",
+     0,
+     "Around Frame",
+     "Display Paths of poses within a fixed number of frames around the current frame"},
+    {MOTIONPATH_TYPE_RANGE,
+     "RANGE",
+     0,
+     "In Range",
+     "Display Paths of poses within specified range"},
+    {0, NULL, 0, NULL, NULL},
+};
+
+const EnumPropertyItem rna_enum_motionpath_range_items[] = {
+    {MOTIONPATH_RANGE_KEYS_ALL, "KEYS_ALL", 0, "All keys range", ""},
+    {MOTIONPATH_RANGE_KEYS_SELECTED, "KEYS_SELECTED", 0, "Selected keys range", ""},
+    {MOTIONPATH_RANGE_SCENE, "SCENE", 0, "Scene frame range", ""},
+    {0, NULL, 0, NULL, NULL},
+};
+
 #ifdef RNA_RUNTIME
 
 static PointerRNA rna_AnimViz_motion_paths_get(PointerRNA *ptr)
@@ -173,20 +194,6 @@ static void rna_def_animviz_paths(BlenderRNA *brna)
   StructRNA *srna;
   PropertyRNA *prop;
 
-  static const EnumPropertyItem prop_type_items[] = {
-      {MOTIONPATH_TYPE_ACFRA,
-       "CURRENT_FRAME",
-       0,
-       "Around Frame",
-       "Display Paths of poses within a fixed number of frames around the current frame"},
-      {MOTIONPATH_TYPE_RANGE,
-       "RANGE",
-       0,
-       "In Range",
-       "Display Paths of poses within specified range"},
-      {0, NULL, 0, NULL, NULL},
-  };
-
   srna = RNA_def_struct(brna, "AnimVizMotionPaths", NULL);
   RNA_def_struct_sdna(srna, "bAnimVizSettings");
   RNA_def_struct_nested(brna, srna, "AnimViz");
@@ -198,10 +205,16 @@ static void rna_def_animviz_paths(BlenderRNA *brna)
   /* Enums */
   prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "path_type");
-  RNA_def_property_enum_items(prop, prop_type_items);
+  RNA_def_property_enum_items(prop, rna_enum_motionpath_display_type_items);
   RNA_def_property_ui_text(prop, "Paths Type", "Type of range to show for Motion Paths");
   RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, NULL);
 
+  prop = RNA_def_property(srna, "range", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "path_range");
+  RNA_def_property_enum_items(prop, rna_enum_motionpath_range_items);
+  RNA_def_property_ui_text(prop, "Paths Range", "Type of range to calculate for Motion Paths");
+  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, NULL);
+
   prop = RNA_def_property(srna, "bake_location", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_bitflag_sdna(prop, NULL, "path_bakeflag");
   RNA_def_property_enum_items(prop, rna_enum_motionpath_bake_location_items);
-- 
cgit v1.2.3


From 8e940f31fdca44816a5d6151c74567b2d6d75454 Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Mon, 28 Feb 2022 13:12:28 +0100
Subject: WM: Allow drop-box polls to use context from UI

The UI context was only set for the operator polls, but not for the
drop-box polls. Initially I thought this wouldn't be needed since the
drop-boxes should leave up context polls to the operator, but in
practice that may not be what API users expect. Plus the tooltip for the
drop-boxes will likely have to access context anyway, so they should be
able to check it beforehand.
---
 source/blender/windowmanager/intern/wm_dragdrop.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c
index 685d74f6d20..4ffb6b90e11 100644
--- a/source/blender/windowmanager/intern/wm_dragdrop.c
+++ b/source/blender/windowmanager/intern/wm_dragdrop.c
@@ -318,6 +318,10 @@ static wmDropBox *dropbox_active(bContext *C,
       wmEventHandler_Dropbox *handler = (wmEventHandler_Dropbox *)handler_base;
       if (handler->dropboxes) {
         LISTBASE_FOREACH (wmDropBox *, drop, handler->dropboxes) {
+          if (drag->drop_state.ui_context) {
+            CTX_store_set(C, drag->drop_state.ui_context);
+          }
+
           if (!drop->poll(C, drag, event)) {
             /* If the drop's poll fails, don't set the disabled-info. This would be too aggressive.
              * Instead show it only if the drop box could be used in principle, but the operator
@@ -326,10 +330,6 @@ static wmDropBox *dropbox_active(bContext *C,
           }
 
           const wmOperatorCallContext opcontext = wm_drop_operator_context_get(drop);
-          if (drag->drop_state.ui_context) {
-            CTX_store_set(C, drag->drop_state.ui_context);
-          }
-
           if (WM_operator_poll_context(C, drop->ot, opcontext)) {
             return drop;
           }
-- 
cgit v1.2.3


From 1e848281f94ff11d8a037469c34bd645d41e3ada Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Mon, 28 Feb 2022 14:35:49 +0100
Subject: 18n: Add new `EDITOR_VIEW3D` translation context.

---
 source/blender/blentranslation/BLT_translation.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/source/blender/blentranslation/BLT_translation.h b/source/blender/blentranslation/BLT_translation.h
index 08a0c9605e5..ebb0f604df7 100644
--- a/source/blender/blentranslation/BLT_translation.h
+++ b/source/blender/blentranslation/BLT_translation.h
@@ -129,6 +129,9 @@ bool BLT_lang_is_ime_supported(void);
 #define BLT_I18NCONTEXT_ID_MOVIECLIP "MovieClip"
 #define BLT_I18NCONTEXT_ID_MASK "Mask"
 
+/* Editors-types contexts. */
+#define BLT_I18NCONTEXT_EDITOR_VIEW3D "View3D"
+
 /* Helper for bpy.app.i18n object... */
 typedef struct {
   const char *c_id;
@@ -191,6 +194,7 @@ typedef struct {
         BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WORLD, "id_world"), \
         BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WORKSPACE, "id_workspace"), \
         BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "id_windowmanager"), \
+        BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_EDITOR_VIEW3D, "editor_view3d"), \
     { \
       NULL, NULL, NULL \
     } \
-- 
cgit v1.2.3


From db3f5ae48aca8fbc9922a94801c33b1120533cd6 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Mon, 28 Feb 2022 14:36:21 +0100
Subject: Fix T95506: Separate strings for "Back" (operator).

Add a new `EDITOR_VIEW3D` context to those view orientations.
---
 release/scripts/startup/bl_ui/space_view3d.py | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 5dedb3c4029..cd0306d31fd 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -1197,23 +1197,24 @@ class VIEW3D_MT_view_viewpoint(Menu):
 
     def draw(self, _context):
         layout = self.layout
+        i18n_text_ctxt = bpy.app.translations.contexts_C_to_py['BLT_I18NCONTEXT_EDITOR_VIEW3D']
 
-        layout.operator("view3d.view_camera", text="Camera")
+        layout.operator("view3d.view_camera", text="Camera", text_ctxt=i18n_text_ctxt)
 
         layout.separator()
 
-        layout.operator("view3d.view_axis", text="Top").type = 'TOP'
-        layout.operator("view3d.view_axis", text="Bottom").type = 'BOTTOM'
+        layout.operator("view3d.view_axis", text="Top", text_ctxt=i18n_text_ctxt).type = 'TOP'
+        layout.operator("view3d.view_axis", text="Bottom", text_ctxt=i18n_text_ctxt).type = 'BOTTOM'
 
         layout.separator()
 
-        layout.operator("view3d.view_axis", text="Front").type = 'FRONT'
-        layout.operator("view3d.view_axis", text="Back").type = 'BACK'
+        layout.operator("view3d.view_axis", text="Front", text_ctxt=i18n_text_ctxt).type = 'FRONT'
+        layout.operator("view3d.view_axis", text="Back", text_ctxt=i18n_text_ctxt).type = 'BACK'
 
         layout.separator()
 
-        layout.operator("view3d.view_axis", text="Right").type = 'RIGHT'
-        layout.operator("view3d.view_axis", text="Left").type = 'LEFT'
+        layout.operator("view3d.view_axis", text="Right", text_ctxt=i18n_text_ctxt).type = 'RIGHT'
+        layout.operator("view3d.view_axis", text="Left", text_ctxt=i18n_text_ctxt).type = 'LEFT'
 
 
 class VIEW3D_MT_view_navigation(Menu):
@@ -1280,31 +1281,31 @@ class VIEW3D_MT_view_align_selected(Menu):
     def draw(self, _context):
         layout = self.layout
 
-        props = layout.operator("view3d.view_axis", text="Top")
+        props = layout.operator("view3d.view_axis", text="Top", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'TOP'
 
-        props = layout.operator("view3d.view_axis", text="Bottom")
+        props = layout.operator("view3d.view_axis", text="Bottom", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'BOTTOM'
 
         layout.separator()
 
-        props = layout.operator("view3d.view_axis", text="Front")
+        props = layout.operator("view3d.view_axis", text="Front", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'FRONT'
 
-        props = layout.operator("view3d.view_axis", text="Back")
+        props = layout.operator("view3d.view_axis", text="Back", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'BACK'
 
         layout.separator()
 
-        props = layout.operator("view3d.view_axis", text="Right")
+        props = layout.operator("view3d.view_axis", text="Right", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'RIGHT'
 
-        props = layout.operator("view3d.view_axis", text="Left")
+        props = layout.operator("view3d.view_axis", text="Left", text_ctxt=i18n_text_ctxt)
         props.align_active = True
         props.type = 'LEFT'
 
-- 
cgit v1.2.3


From 413e87b6b7edf8cc1569818de3d26fccc391d235 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Mon, 28 Feb 2022 15:02:17 +0100
Subject: Fix T96048: Crash on appending with driver variables loop in
 shapekeys.

The usual 'shape keys snowflake' nightmare again...
---
 source/blender/blenkernel/intern/blendfile_link_append.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c
index 9b3f4c2fae8..f0da1b68821 100644
--- a/source/blender/blenkernel/intern/blendfile_link_append.c
+++ b/source/blender/blenkernel/intern/blendfile_link_append.c
@@ -941,8 +941,13 @@ static int foreach_libblock_link_append_callback(LibraryIDLinkCallbackData *cb_d
      * processed, so we need to recursively deal with them here. */
     /* NOTE: Since we are by-passing checks in `BKE_library_foreach_ID_link` by manually calling it
      * recursively, we need to take care of potential recursion cases ourselves (e.g.animdata of
-     * shape-key referencing the shape-key itself). */
-    if (id != cb_data->id_self) {
+     * shape-key referencing the shape-key itself).
+     * NOTE: in case both IDs (owner and 'used' ones) are non-linkable, we can assume we can break
+     * the dependency here. Indeed, either they are both linked in another way (through their own
+     * meshes for shape keys e.g.), or this is an unsupported case (two shapekeys depending on
+     * each-other need to be also 'linked' in by their respective meshes, independant shapekeys are
+     * not allowed). ref T96048. */
+    if (id != cb_data->id_self && BKE_idtype_idcode_is_linkable(GS(cb_data->id_self->name))) {
       BKE_library_foreach_ID_link(
           cb_data->bmain, id, foreach_libblock_link_append_callback, data, IDWALK_NOP);
     }
-- 
cgit v1.2.3


From 579f42ae525df065ff4436e186b7850dec86642f Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 10:08:26 -0500
Subject: Cleanup: Move mesh_validate.c to C++

This patch was tested on the build bots on all platforms.
---
 source/blender/blenkernel/BKE_mesh.h              |    2 +-
 source/blender/blenkernel/CMakeLists.txt          |    2 +-
 source/blender/blenkernel/intern/mesh_validate.c  | 1577 --------------------
 source/blender/blenkernel/intern/mesh_validate.cc | 1581 +++++++++++++++++++++
 4 files changed, 1583 insertions(+), 1579 deletions(-)
 delete mode 100644 source/blender/blenkernel/intern/mesh_validate.c
 create mode 100644 source/blender/blenkernel/intern/mesh_validate.cc

diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h
index 949266c779d..2373bb289cd 100644
--- a/source/blender/blenkernel/BKE_mesh.h
+++ b/source/blender/blenkernel/BKE_mesh.h
@@ -942,7 +942,7 @@ void BKE_mesh_calc_relative_deform(const struct MPoly *mpoly,
                                    const float (*vert_cos_org)[3],
                                    float (*vert_cos_new)[3]);
 
-/* *** mesh_validate.c *** */
+/* *** mesh_validate.cc *** */
 
 /**
  * Validates and corrects a Mesh.
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index ad8b5813492..2b77b2c5dae 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -200,7 +200,7 @@ set(SRC
   intern/mesh_sample.cc
   intern/mesh_tangent.c
   intern/mesh_tessellate.c
-  intern/mesh_validate.c
+  intern/mesh_validate.cc
   intern/mesh_wrapper.c
   intern/modifier.c
   intern/movieclip.c
diff --git a/source/blender/blenkernel/intern/mesh_validate.c b/source/blender/blenkernel/intern/mesh_validate.c
deleted file mode 100644
index 53e19e6d16d..00000000000
--- a/source/blender/blenkernel/intern/mesh_validate.c
+++ /dev/null
@@ -1,1577 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later
- * Copyright 2011 Blender Foundation. All rights reserved. */
-
-/** \file
- * \ingroup bke
- */
-
-#include 
-#include 
-#include 
-#include 
-
-#include "CLG_log.h"
-
-#include "BLI_bitmap.h"
-#include "DNA_mesh_types.h"
-#include "DNA_meshdata_types.h"
-#include "DNA_object_types.h"
-
-#include "BLI_sys_types.h"
-
-#include "BLI_edgehash.h"
-#include "BLI_math_base.h"
-#include "BLI_math_vector.h"
-#include "BLI_utildefines.h"
-
-#include "BKE_customdata.h"
-#include "BKE_deform.h"
-#include "BKE_mesh.h"
-
-#include "DEG_depsgraph.h"
-
-#include "MEM_guardedalloc.h"
-
-/* loop v/e are unsigned, so using max uint_32 value as invalid marker... */
-#define INVALID_LOOP_EDGE_MARKER 4294967295u
-
-static CLG_LogRef LOG = {"bke.mesh"};
-
-/* -------------------------------------------------------------------- */
-/** \name Internal functions
- * \{ */
-
-typedef union {
-  uint32_t verts[2];
-  int64_t edval;
-} EdgeUUID;
-
-typedef struct SortFace {
-  EdgeUUID es[4];
-  uint index;
-} SortFace;
-
-/* Used to detect polys (faces) using exactly the same vertices. */
-/* Used to detect loops used by no (disjoint) or more than one (intersect) polys. */
-typedef struct SortPoly {
-  int *verts;
-  int numverts;
-  int loopstart;
-  uint index;
-  bool invalid; /* Poly index. */
-} SortPoly;
-
-static void edge_store_assign(uint32_t verts[2], const uint32_t v1, const uint32_t v2)
-{
-  if (v1 < v2) {
-    verts[0] = v1;
-    verts[1] = v2;
-  }
-  else {
-    verts[0] = v2;
-    verts[1] = v1;
-  }
-}
-
-static void edge_store_from_mface_quad(EdgeUUID es[4], MFace *mf)
-{
-  edge_store_assign(es[0].verts, mf->v1, mf->v2);
-  edge_store_assign(es[1].verts, mf->v2, mf->v3);
-  edge_store_assign(es[2].verts, mf->v3, mf->v4);
-  edge_store_assign(es[3].verts, mf->v4, mf->v1);
-}
-
-static void edge_store_from_mface_tri(EdgeUUID es[4], MFace *mf)
-{
-  edge_store_assign(es[0].verts, mf->v1, mf->v2);
-  edge_store_assign(es[1].verts, mf->v2, mf->v3);
-  edge_store_assign(es[2].verts, mf->v3, mf->v1);
-  es[3].verts[0] = es[3].verts[1] = UINT_MAX;
-}
-
-static int int64_cmp(const void *v1, const void *v2)
-{
-  const int64_t x1 = *(const int64_t *)v1;
-  const int64_t x2 = *(const int64_t *)v2;
-
-  if (x1 > x2) {
-    return 1;
-  }
-  if (x1 < x2) {
-    return -1;
-  }
-
-  return 0;
-}
-
-static int search_face_cmp(const void *v1, const void *v2)
-{
-  const SortFace *sfa = v1, *sfb = v2;
-
-  if (sfa->es[0].edval > sfb->es[0].edval) {
-    return 1;
-  }
-  if (sfa->es[0].edval < sfb->es[0].edval) {
-    return -1;
-  }
-
-  if (sfa->es[1].edval > sfb->es[1].edval) {
-    return 1;
-  }
-  if (sfa->es[1].edval < sfb->es[1].edval) {
-    return -1;
-  }
-
-  if (sfa->es[2].edval > sfb->es[2].edval) {
-    return 1;
-  }
-  if (sfa->es[2].edval < sfb->es[2].edval) {
-    return -1;
-  }
-
-  if (sfa->es[3].edval > sfb->es[3].edval) {
-    return 1;
-  }
-  if (sfa->es[3].edval < sfb->es[3].edval) {
-    return -1;
-  }
-
-  return 0;
-}
-
-/* TODO: check there is not some standard define of this somewhere! */
-static int int_cmp(const void *v1, const void *v2)
-{
-  return *(int *)v1 > *(int *)v2 ? 1 : *(int *)v1 < *(int *)v2 ? -1 : 0;
-}
-
-static int search_poly_cmp(const void *v1, const void *v2)
-{
-  const SortPoly *sp1 = v1;
-  const SortPoly *sp2 = v2;
-
-  /* Reject all invalid polys at end of list! */
-  if (sp1->invalid || sp2->invalid) {
-    return sp1->invalid ? (sp2->invalid ? 0 : 1) : -1;
-  }
-  /* Else, sort on first non-equal verts (remember verts of valid polys are sorted). */
-  const int max_idx = sp1->numverts > sp2->numverts ? sp2->numverts : sp1->numverts;
-  for (int idx = 0; idx < max_idx; idx++) {
-    const int v1_i = sp1->verts[idx];
-    const int v2_i = sp2->verts[idx];
-    if (v1_i != v2_i) {
-      return (v1_i > v2_i) ? 1 : -1;
-    }
-  }
-  return sp1->numverts > sp2->numverts ? 1 : sp1->numverts < sp2->numverts ? -1 : 0;
-}
-
-static int search_polyloop_cmp(const void *v1, const void *v2)
-{
-  const SortPoly *sp1 = v1;
-  const SortPoly *sp2 = v2;
-
-  /* Reject all invalid polys at end of list! */
-  if (sp1->invalid || sp2->invalid) {
-    return sp1->invalid && sp2->invalid ? 0 : sp1->invalid ? 1 : -1;
-  }
-  /* Else, sort on loopstart. */
-  return sp1->loopstart > sp2->loopstart ? 1 : sp1->loopstart < sp2->loopstart ? -1 : 0;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Validation
- * \{ */
-
-#define PRINT_MSG(...) \
-  if (do_verbose) { \
-    CLOG_INFO(&LOG, 1, __VA_ARGS__); \
-  } \
-  ((void)0)
-
-#define PRINT_ERR(...) \
-  do { \
-    is_valid = false; \
-    if (do_verbose) { \
-      CLOG_ERROR(&LOG, __VA_ARGS__); \
-    } \
-  } while (0)
-
-/* NOLINTNEXTLINE: readability-function-size */
-bool BKE_mesh_validate_arrays(Mesh *mesh,
-                              MVert *mverts,
-                              uint totvert,
-                              MEdge *medges,
-                              uint totedge,
-                              MFace *mfaces,
-                              uint totface,
-                              MLoop *mloops,
-                              uint totloop,
-                              MPoly *mpolys,
-                              uint totpoly,
-                              MDeformVert *dverts, /* assume totvert length */
-                              const bool do_verbose,
-                              const bool do_fixes,
-                              bool *r_changed)
-{
-#define REMOVE_EDGE_TAG(_me) \
-  { \
-    _me->v2 = _me->v1; \
-    free_flag.edges = do_fixes; \
-  } \
-  (void)0
-#define IS_REMOVED_EDGE(_me) (_me->v2 == _me->v1)
-
-#define REMOVE_LOOP_TAG(_ml) \
-  { \
-    _ml->e = INVALID_LOOP_EDGE_MARKER; \
-    free_flag.polyloops = do_fixes; \
-  } \
-  (void)0
-#define REMOVE_POLY_TAG(_mp) \
-  { \
-    _mp->totloop *= -1; \
-    free_flag.polyloops = do_fixes; \
-  } \
-  (void)0
-
-  MVert *mv = mverts;
-  MEdge *me;
-  MLoop *ml;
-  MPoly *mp;
-  uint i, j;
-  int *v;
-
-  bool is_valid = true;
-
-  union {
-    struct {
-      int verts : 1;
-      int verts_weight : 1;
-      int loops_edge : 1;
-    };
-    int as_flag;
-  } fix_flag;
-
-  union {
-    struct {
-      int edges : 1;
-      int faces : 1;
-      /* This regroups loops and polys! */
-      int polyloops : 1;
-      int mselect : 1;
-    };
-    int as_flag;
-  } free_flag;
-
-  union {
-    struct {
-      int edges : 1;
-    };
-    int as_flag;
-  } recalc_flag;
-
-  EdgeHash *edge_hash = BLI_edgehash_new_ex(__func__, totedge);
-
-  BLI_assert(!(do_fixes && mesh == NULL));
-
-  fix_flag.as_flag = 0;
-  free_flag.as_flag = 0;
-  recalc_flag.as_flag = 0;
-
-  PRINT_MSG("verts(%u), edges(%u), loops(%u), polygons(%u)", totvert, totedge, totloop, totpoly);
-
-  if (totedge == 0 && totpoly != 0) {
-    PRINT_ERR("\tLogical error, %u polygons and 0 edges", totpoly);
-    recalc_flag.edges = do_fixes;
-  }
-
-  const float(*vert_normals)[3] = NULL;
-  BKE_mesh_assert_normals_dirty_or_calculated(mesh);
-  if (!BKE_mesh_vertex_normals_are_dirty(mesh)) {
-    vert_normals = BKE_mesh_vertex_normals_ensure(mesh);
-  }
-
-  for (i = 0; i < totvert; i++, mv++) {
-    bool fix_normal = true;
-
-    for (j = 0; j < 3; j++) {
-      if (!isfinite(mv->co[j])) {
-        PRINT_ERR("\tVertex %u: has invalid coordinate", i);
-
-        if (do_fixes) {
-          zero_v3(mv->co);
-
-          fix_flag.verts = true;
-        }
-      }
-
-      if (vert_normals && vert_normals[i][j] != 0.0f) {
-        fix_normal = false;
-        break;
-      }
-    }
-
-    if (vert_normals && fix_normal) {
-      /* If the vertex normal accumulates to zero or isn't part of a face, the location is used.
-       * When the location is also zero, a zero normal warning should not be raised.
-       * since this is the expected behavior of normal calculation.
-       *
-       * This avoids false positives but isn't foolproof as it's possible the vertex
-       * is part of a polygon that has a normal which this vertex should be using,
-       * although it's also possible degenerate/opposite faces accumulate to a zero vector.
-       * To detect this a full normal recalculation would be needed, which is out of scope
-       * for a basic validity check (see "Vertex Normal" in the doc-string). */
-      if (!is_zero_v3(mv->co)) {
-        PRINT_ERR("\tVertex %u: has zero normal, assuming Z-up normal", i);
-        if (do_fixes) {
-          float *normal = (float *)vert_normals[i];
-          normal[2] = 1.0f;
-          fix_flag.verts = true;
-        }
-      }
-    }
-  }
-
-  for (i = 0, me = medges; i < totedge; i++, me++) {
-    bool remove = false;
-
-    if (me->v1 == me->v2) {
-      PRINT_ERR("\tEdge %u: has matching verts, both %u", i, me->v1);
-      remove = do_fixes;
-    }
-    if (me->v1 >= totvert) {
-      PRINT_ERR("\tEdge %u: v1 index out of range, %u", i, me->v1);
-      remove = do_fixes;
-    }
-    if (me->v2 >= totvert) {
-      PRINT_ERR("\tEdge %u: v2 index out of range, %u", i, me->v2);
-      remove = do_fixes;
-    }
-
-    if ((me->v1 != me->v2) && BLI_edgehash_haskey(edge_hash, me->v1, me->v2)) {
-      PRINT_ERR("\tEdge %u: is a duplicate of %d",
-                i,
-                POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, me->v1, me->v2)));
-      remove = do_fixes;
-    }
-
-    if (remove == false) {
-      if (me->v1 != me->v2) {
-        BLI_edgehash_insert(edge_hash, me->v1, me->v2, POINTER_FROM_INT(i));
-      }
-    }
-    else {
-      REMOVE_EDGE_TAG(me);
-    }
-  }
-
-  if (mfaces && !mpolys) {
-#define REMOVE_FACE_TAG(_mf) \
-  { \
-    _mf->v3 = 0; \
-    free_flag.faces = do_fixes; \
-  } \
-  (void)0
-#define CHECK_FACE_VERT_INDEX(a, b) \
-  if (mf->a == mf->b) { \
-    PRINT_ERR("    face %u: verts invalid, " STRINGIFY(a) "/" STRINGIFY(b) " both %u", i, mf->a); \
-    remove = do_fixes; \
-  } \
-  (void)0
-#define CHECK_FACE_EDGE(a, b) \
-  if (!BLI_edgehash_haskey(edge_hash, mf->a, mf->b)) { \
-    PRINT_ERR("    face %u: edge " STRINGIFY(a) "/" STRINGIFY(b) " (%u,%u) is missing edge data", \
-              i, \
-              mf->a, \
-              mf->b); \
-    recalc_flag.edges = do_fixes; \
-  } \
-  (void)0
-
-    MFace *mf;
-    MFace *mf_prev;
-
-    SortFace *sort_faces = MEM_callocN(sizeof(SortFace) * totface, "search faces");
-    SortFace *sf;
-    SortFace *sf_prev;
-    uint totsortface = 0;
-
-    PRINT_ERR("No Polys, only tessellated Faces");
-
-    for (i = 0, mf = mfaces, sf = sort_faces; i < totface; i++, mf++) {
-      bool remove = false;
-      int fidx;
-      uint fv[4];
-
-      fidx = mf->v4 ? 3 : 2;
-      do {
-        fv[fidx] = *(&(mf->v1) + fidx);
-        if (fv[fidx] >= totvert) {
-          PRINT_ERR("\tFace %u: 'v%d' index out of range, %u", i, fidx + 1, fv[fidx]);
-          remove = do_fixes;
-        }
-      } while (fidx--);
-
-      if (remove == false) {
-        if (mf->v4) {
-          CHECK_FACE_VERT_INDEX(v1, v2);
-          CHECK_FACE_VERT_INDEX(v1, v3);
-          CHECK_FACE_VERT_INDEX(v1, v4);
-
-          CHECK_FACE_VERT_INDEX(v2, v3);
-          CHECK_FACE_VERT_INDEX(v2, v4);
-
-          CHECK_FACE_VERT_INDEX(v3, v4);
-        }
-        else {
-          CHECK_FACE_VERT_INDEX(v1, v2);
-          CHECK_FACE_VERT_INDEX(v1, v3);
-
-          CHECK_FACE_VERT_INDEX(v2, v3);
-        }
-
-        if (remove == false) {
-          if (totedge) {
-            if (mf->v4) {
-              CHECK_FACE_EDGE(v1, v2);
-              CHECK_FACE_EDGE(v2, v3);
-              CHECK_FACE_EDGE(v3, v4);
-              CHECK_FACE_EDGE(v4, v1);
-            }
-            else {
-              CHECK_FACE_EDGE(v1, v2);
-              CHECK_FACE_EDGE(v2, v3);
-              CHECK_FACE_EDGE(v3, v1);
-            }
-          }
-
-          sf->index = i;
-
-          if (mf->v4) {
-            edge_store_from_mface_quad(sf->es, mf);
-
-            qsort(sf->es, 4, sizeof(int64_t), int64_cmp);
-          }
-          else {
-            edge_store_from_mface_tri(sf->es, mf);
-            qsort(sf->es, 3, sizeof(int64_t), int64_cmp);
-          }
-
-          totsortface++;
-          sf++;
-        }
-      }
-
-      if (remove) {
-        REMOVE_FACE_TAG(mf);
-      }
-    }
-
-    qsort(sort_faces, totsortface, sizeof(SortFace), search_face_cmp);
-
-    sf = sort_faces;
-    sf_prev = sf;
-    sf++;
-
-    for (i = 1; i < totsortface; i++, sf++) {
-      bool remove = false;
-
-      /* on a valid mesh, code below will never run */
-      if (memcmp(sf->es, sf_prev->es, sizeof(sf_prev->es)) == 0) {
-        mf = mfaces + sf->index;
-
-        if (do_verbose) {
-          mf_prev = mfaces + sf_prev->index;
-
-          if (mf->v4) {
-            PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u,%u) (%u,%u,%u,%u)",
-                      sf->index,
-                      sf_prev->index,
-                      mf->v1,
-                      mf->v2,
-                      mf->v3,
-                      mf->v4,
-                      mf_prev->v1,
-                      mf_prev->v2,
-                      mf_prev->v3,
-                      mf_prev->v4);
-          }
-          else {
-            PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u) (%u,%u,%u)",
-                      sf->index,
-                      sf_prev->index,
-                      mf->v1,
-                      mf->v2,
-                      mf->v3,
-                      mf_prev->v1,
-                      mf_prev->v2,
-                      mf_prev->v3);
-          }
-        }
-
-        remove = do_fixes;
-      }
-      else {
-        sf_prev = sf;
-      }
-
-      if (remove) {
-        REMOVE_FACE_TAG(mf);
-      }
-    }
-
-    MEM_freeN(sort_faces);
-
-#undef REMOVE_FACE_TAG
-#undef CHECK_FACE_VERT_INDEX
-#undef CHECK_FACE_EDGE
-  }
-
-  /* Checking loops and polys is a bit tricky, as they are quite intricate...
-   *
-   * Polys must have:
-   * - a valid loopstart value.
-   * - a valid totloop value (>= 3 and loopstart+totloop < me.totloop).
-   *
-   * Loops must have:
-   * - a valid v value.
-   * - a valid e value (corresponding to the edge it defines with the next loop in poly).
-   *
-   * Also, loops not used by polys can be discarded.
-   * And "intersecting" loops (i.e. loops used by more than one poly) are invalid,
-   * so be sure to leave at most one poly per loop!
-   */
-  {
-    BLI_bitmap *vert_tag = BLI_BITMAP_NEW(mesh->totvert, __func__);
-
-    SortPoly *sort_polys = MEM_callocN(sizeof(SortPoly) * totpoly, "mesh validate's sort_polys");
-    SortPoly *prev_sp, *sp = sort_polys;
-    int prev_end;
-
-    for (i = 0, mp = mpolys; i < totpoly; i++, mp++, sp++) {
-      sp->index = i;
-
-      /* Material index, isolated from other tests here. While large indices are clamped,
-       * negative indices aren't supported by drawing, exporters etc.
-       * To check the indices are in range, use #BKE_mesh_validate_material_indices */
-      if (mp->mat_nr < 0) {
-        PRINT_ERR("\tPoly %u has invalid material (%d)", sp->index, mp->mat_nr);
-        if (do_fixes) {
-          mp->mat_nr = 0;
-        }
-      }
-
-      if (mp->loopstart < 0 || mp->totloop < 3) {
-        /* Invalid loop data. */
-        PRINT_ERR("\tPoly %u is invalid (loopstart: %d, totloop: %d)",
-                  sp->index,
-                  mp->loopstart,
-                  mp->totloop);
-        sp->invalid = true;
-      }
-      else if (mp->loopstart + mp->totloop > totloop) {
-        /* Invalid loop data. */
-        PRINT_ERR(
-            "\tPoly %u uses loops out of range (loopstart: %d, loopend: %d, max nbr of loops: %u)",
-            sp->index,
-            mp->loopstart,
-            mp->loopstart + mp->totloop - 1,
-            totloop - 1);
-        sp->invalid = true;
-      }
-      else {
-        /* Poly itself is valid, for now. */
-        int v1, v2; /* v1 is prev loop vert idx, v2 is current loop one. */
-        sp->invalid = false;
-        sp->verts = v = MEM_mallocN(sizeof(int) * mp->totloop, "Vert idx of SortPoly");
-        sp->numverts = mp->totloop;
-        sp->loopstart = mp->loopstart;
-
-        /* Ideally we would only have to do that once on all vertices
-         * before we start checking each poly, but several polys can use same vert,
-         * so we have to ensure here all verts of current poly are cleared. */
-        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++) {
-          if (ml->v < totvert) {
-            BLI_BITMAP_DISABLE(vert_tag, ml->v);
-          }
-        }
-
-        /* Test all poly's loops' vert idx. */
-        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++, v++) {
-          if (ml->v >= totvert) {
-            /* Invalid vert idx. */
-            PRINT_ERR("\tLoop %u has invalid vert reference (%u)", sp->loopstart + j, ml->v);
-            sp->invalid = true;
-          }
-          else if (BLI_BITMAP_TEST(vert_tag, ml->v)) {
-            PRINT_ERR("\tPoly %u has duplicated vert reference at corner (%u)", i, j);
-            sp->invalid = true;
-          }
-          else {
-            BLI_BITMAP_ENABLE(vert_tag, ml->v);
-          }
-          *v = ml->v;
-        }
-
-        if (sp->invalid) {
-          continue;
-        }
-
-        /* Test all poly's loops. */
-        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++) {
-          v1 = ml->v;
-          v2 = mloops[sp->loopstart + (j + 1) % mp->totloop].v;
-          if (!BLI_edgehash_haskey(edge_hash, v1, v2)) {
-            /* Edge not existing. */
-            PRINT_ERR("\tPoly %u needs missing edge (%d, %d)", sp->index, v1, v2);
-            if (do_fixes) {
-              recalc_flag.edges = true;
-            }
-            else {
-              sp->invalid = true;
-            }
-          }
-          else if (ml->e >= totedge) {
-            /* Invalid edge idx.
-             * We already know from previous text that a valid edge exists, use it (if allowed)! */
-            if (do_fixes) {
-              int prev_e = ml->e;
-              ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2));
-              fix_flag.loops_edge = true;
-              PRINT_ERR("\tLoop %u has invalid edge reference (%d), fixed using edge %u",
-                        sp->loopstart + j,
-                        prev_e,
-                        ml->e);
-            }
-            else {
-              PRINT_ERR("\tLoop %u has invalid edge reference (%u)", sp->loopstart + j, ml->e);
-              sp->invalid = true;
-            }
-          }
-          else {
-            me = &medges[ml->e];
-            if (IS_REMOVED_EDGE(me) ||
-                !((me->v1 == v1 && me->v2 == v2) || (me->v1 == v2 && me->v2 == v1))) {
-              /* The pointed edge is invalid (tagged as removed, or vert idx mismatch),
-               * and we already know from previous test that a valid one exists,
-               * use it (if allowed)! */
-              if (do_fixes) {
-                int prev_e = ml->e;
-                ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2));
-                fix_flag.loops_edge = true;
-                PRINT_ERR(
-                    "\tPoly %u has invalid edge reference (%d, is_removed: %d), fixed using edge "
-                    "%u",
-                    sp->index,
-                    prev_e,
-                    IS_REMOVED_EDGE(me),
-                    ml->e);
-              }
-              else {
-                PRINT_ERR("\tPoly %u has invalid edge reference (%u)", sp->index, ml->e);
-                sp->invalid = true;
-              }
-            }
-          }
-        }
-
-        if (!sp->invalid) {
-          /* Needed for checking polys using same verts below. */
-          qsort(sp->verts, sp->numverts, sizeof(int), int_cmp);
-        }
-      }
-    }
-
-    MEM_freeN(vert_tag);
-
-    /* Second check pass, testing polys using the same verts. */
-    qsort(sort_polys, totpoly, sizeof(SortPoly), search_poly_cmp);
-    sp = prev_sp = sort_polys;
-    sp++;
-
-    for (i = 1; i < totpoly; i++, sp++) {
-      int p1_nv = sp->numverts, p2_nv = prev_sp->numverts;
-      const int *p1_v = sp->verts, *p2_v = prev_sp->verts;
-
-      if (sp->invalid) {
-        /* Break, because all known invalid polys have been put at the end
-         * by qsort with search_poly_cmp. */
-        break;
-      }
-
-      /* Test same polys. */
-      if ((p1_nv == p2_nv) && (memcmp(p1_v, p2_v, p1_nv * sizeof(*p1_v)) == 0)) {
-        if (do_verbose) {
-          /* TODO: convert list to string */
-          PRINT_ERR("\tPolys %u and %u use same vertices (%d", prev_sp->index, sp->index, *p1_v);
-          for (j = 1; j < p1_nv; j++) {
-            PRINT_ERR(", %d", p1_v[j]);
-          }
-          PRINT_ERR("), considering poly %u as invalid.", sp->index);
-        }
-        else {
-          is_valid = false;
-        }
-        sp->invalid = true;
-      }
-      else {
-        prev_sp = sp;
-      }
-    }
-
-    /* Third check pass, testing loops used by none or more than one poly. */
-    qsort(sort_polys, totpoly, sizeof(SortPoly), search_polyloop_cmp);
-    sp = sort_polys;
-    prev_sp = NULL;
-    prev_end = 0;
-    for (i = 0; i < totpoly; i++, sp++) {
-      /* Free this now, we don't need it anymore, and avoid us another loop! */
-      if (sp->verts) {
-        MEM_freeN(sp->verts);
-      }
-
-      /* Note above prev_sp: in following code, we make sure it is always valid poly (or NULL). */
-      if (sp->invalid) {
-        if (do_fixes) {
-          REMOVE_POLY_TAG((&mpolys[sp->index]));
-          /* DO NOT REMOVE ITS LOOPS!!!
-           * As already invalid polys are at the end of the SortPoly list, the loops they
-           * were the only users have already been tagged as "to remove" during previous
-           * iterations, and we don't want to remove some loops that may be used by
-           * another valid poly! */
-        }
-      }
-      /* Test loops users. */
-      else {
-        /* Unused loops. */
-        if (prev_end < sp->loopstart) {
-          for (j = prev_end, ml = &mloops[prev_end]; j < sp->loopstart; j++, ml++) {
-            PRINT_ERR("\tLoop %u is unused.", j);
-            if (do_fixes) {
-              REMOVE_LOOP_TAG(ml);
-            }
-          }
-          prev_end = sp->loopstart + sp->numverts;
-          prev_sp = sp;
-        }
-        /* Multi-used loops. */
-        else if (prev_end > sp->loopstart) {
-          PRINT_ERR("\tPolys %u and %u share loops from %d to %d, considering poly %u as invalid.",
-                    prev_sp->index,
-                    sp->index,
-                    sp->loopstart,
-                    prev_end,
-                    sp->index);
-          if (do_fixes) {
-            REMOVE_POLY_TAG((&mpolys[sp->index]));
-            /* DO NOT REMOVE ITS LOOPS!!!
-             * They might be used by some next, valid poly!
-             * Just not updating prev_end/prev_sp vars is enough to ensure the loops
-             * effectively no more needed will be marked as "to be removed"! */
-          }
-        }
-        else {
-          prev_end = sp->loopstart + sp->numverts;
-          prev_sp = sp;
-        }
-      }
-    }
-    /* We may have some remaining unused loops to get rid of! */
-    if (prev_end < totloop) {
-      for (j = prev_end, ml = &mloops[prev_end]; j < totloop; j++, ml++) {
-        PRINT_ERR("\tLoop %u is unused.", j);
-        if (do_fixes) {
-          REMOVE_LOOP_TAG(ml);
-        }
-      }
-    }
-
-    MEM_freeN(sort_polys);
-  }
-
-  BLI_edgehash_free(edge_hash, NULL);
-
-  /* fix deform verts */
-  if (dverts) {
-    MDeformVert *dv;
-    for (i = 0, dv = dverts; i < totvert; i++, dv++) {
-      MDeformWeight *dw;
-
-      for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) {
-        /* NOTE: greater than max defgroups is accounted for in our code, but not < 0. */
-        if (!isfinite(dw->weight)) {
-          PRINT_ERR("\tVertex deform %u, group %u has weight: %f", i, dw->def_nr, dw->weight);
-          if (do_fixes) {
-            dw->weight = 0.0f;
-            fix_flag.verts_weight = true;
-          }
-        }
-        else if (dw->weight < 0.0f || dw->weight > 1.0f) {
-          PRINT_ERR("\tVertex deform %u, group %u has weight: %f", i, dw->def_nr, dw->weight);
-          if (do_fixes) {
-            CLAMP(dw->weight, 0.0f, 1.0f);
-            fix_flag.verts_weight = true;
-          }
-        }
-
-        /* Not technically incorrect since this is unsigned, however,
-         * a value over INT_MAX is almost certainly caused by wrapping an uint. */
-        if (dw->def_nr >= INT_MAX) {
-          PRINT_ERR("\tVertex deform %u, has invalid group %u", i, dw->def_nr);
-          if (do_fixes) {
-            BKE_defvert_remove_group(dv, dw);
-            fix_flag.verts_weight = true;
-
-            if (dv->dw) {
-              /* re-allocated, the new values compensate for stepping
-               * within the for loop and may not be valid */
-              j--;
-              dw = dv->dw + j;
-            }
-            else { /* all freed */
-              break;
-            }
-          }
-        }
-      }
-    }
-  }
-
-#undef REMOVE_EDGE_TAG
-#undef IS_REMOVED_EDGE
-#undef REMOVE_LOOP_TAG
-#undef REMOVE_POLY_TAG
-
-  if (mesh) {
-    if (free_flag.faces) {
-      BKE_mesh_strip_loose_faces(mesh);
-    }
-
-    if (free_flag.polyloops) {
-      BKE_mesh_strip_loose_polysloops(mesh);
-    }
-
-    if (free_flag.edges) {
-      BKE_mesh_strip_loose_edges(mesh);
-    }
-
-    if (recalc_flag.edges) {
-      BKE_mesh_calc_edges(mesh, true, false);
-    }
-  }
-
-  if (mesh && mesh->mselect) {
-    MSelect *msel;
-
-    for (i = 0, msel = mesh->mselect; i < mesh->totselect; i++, msel++) {
-      int tot_elem = 0;
-
-      if (msel->index < 0) {
-        PRINT_ERR(
-            "\tMesh select element %u type %d index is negative, "
-            "resetting selection stack.\n",
-            i,
-            msel->type);
-        free_flag.mselect = do_fixes;
-        break;
-      }
-
-      switch (msel->type) {
-        case ME_VSEL:
-          tot_elem = mesh->totvert;
-          break;
-        case ME_ESEL:
-          tot_elem = mesh->totedge;
-          break;
-        case ME_FSEL:
-          tot_elem = mesh->totpoly;
-          break;
-      }
-
-      if (msel->index > tot_elem) {
-        PRINT_ERR(
-            "\tMesh select element %u type %d index %d is larger than data array size %d, "
-            "resetting selection stack.\n",
-            i,
-            msel->type,
-            msel->index,
-            tot_elem);
-
-        free_flag.mselect = do_fixes;
-        break;
-      }
-    }
-
-    if (free_flag.mselect) {
-      MEM_freeN(mesh->mselect);
-      mesh->mselect = NULL;
-      mesh->totselect = 0;
-    }
-  }
-
-  PRINT_MSG("%s: finished\n\n", __func__);
-
-  *r_changed = (fix_flag.as_flag || free_flag.as_flag || recalc_flag.as_flag);
-
-  BLI_assert((*r_changed == false) || (do_fixes == true));
-
-  return is_valid;
-}
-
-static bool mesh_validate_customdata(CustomData *data,
-                                     CustomDataMask mask,
-                                     const uint totitems,
-                                     const bool do_verbose,
-                                     const bool do_fixes,
-                                     bool *r_change)
-{
-  bool is_valid = true;
-  bool has_fixes = false;
-  int i = 0;
-
-  PRINT_MSG("%s: Checking %d CD layers...\n", __func__, data->totlayer);
-
-  while (i < data->totlayer) {
-    CustomDataLayer *layer = &data->layers[i];
-    bool ok = true;
-
-    if (CustomData_layertype_is_singleton(layer->type)) {
-      const int layer_tot = CustomData_number_of_layers(data, layer->type);
-      if (layer_tot > 1) {
-        PRINT_ERR("\tCustomDataLayer type %d is a singleton, found %d in Mesh structure\n",
-                  layer->type,
-                  layer_tot);
-        ok = false;
-      }
-    }
-
-    if (mask != 0) {
-      CustomDataMask layer_typemask = CD_TYPE_AS_MASK(layer->type);
-      if ((layer_typemask & mask) == 0) {
-        PRINT_ERR("\tCustomDataLayer type %d which isn't in the mask\n", layer->type);
-        ok = false;
-      }
-    }
-
-    if (ok == false) {
-      if (do_fixes) {
-        CustomData_free_layer(data, layer->type, 0, i);
-        has_fixes = true;
-      }
-    }
-
-    if (ok) {
-      if (CustomData_layer_validate(layer, totitems, do_fixes)) {
-        PRINT_ERR("\tCustomDataLayer type %d has some invalid data\n", layer->type);
-        has_fixes = do_fixes;
-      }
-      i++;
-    }
-  }
-
-  PRINT_MSG("%s: Finished (is_valid=%d)\n\n", __func__, (int)!has_fixes);
-
-  *r_change = has_fixes;
-
-  return is_valid;
-}
-
-bool BKE_mesh_validate_all_customdata(CustomData *vdata,
-                                      const uint totvert,
-                                      CustomData *edata,
-                                      const uint totedge,
-                                      CustomData *ldata,
-                                      const uint totloop,
-                                      CustomData *pdata,
-                                      const uint totpoly,
-                                      const bool check_meshmask,
-                                      const bool do_verbose,
-                                      const bool do_fixes,
-                                      bool *r_change)
-{
-  bool is_valid = true;
-  bool is_change_v, is_change_e, is_change_l, is_change_p;
-  CustomData_MeshMasks mask = {0};
-  if (check_meshmask) {
-    mask = CD_MASK_MESH;
-    /* Normal data isn't in the mask since it is derived data,
-     * but it is valid and should not be removed. */
-    mask.vmask |= CD_MASK_NORMAL;
-    mask.pmask |= CD_MASK_NORMAL;
-  }
-
-  is_valid &= mesh_validate_customdata(
-      vdata, mask.vmask, totvert, do_verbose, do_fixes, &is_change_v);
-  is_valid &= mesh_validate_customdata(
-      edata, mask.emask, totedge, do_verbose, do_fixes, &is_change_e);
-  is_valid &= mesh_validate_customdata(
-      ldata, mask.lmask, totloop, do_verbose, do_fixes, &is_change_l);
-  is_valid &= mesh_validate_customdata(
-      pdata, mask.pmask, totpoly, do_verbose, do_fixes, &is_change_p);
-
-  const int tot_uvloop = CustomData_number_of_layers(ldata, CD_MLOOPUV);
-  const int tot_vcolloop = CustomData_number_of_layers(ldata, CD_MLOOPCOL);
-  if (tot_uvloop > MAX_MTFACE) {
-    PRINT_ERR(
-        "\tMore UV layers than %d allowed, %d last ones won't be available for render, shaders, "
-        "etc.\n",
-        MAX_MTFACE,
-        tot_uvloop - MAX_MTFACE);
-  }
-  if (tot_vcolloop > MAX_MCOL) {
-    PRINT_ERR(
-        "\tMore VCol layers than %d allowed, %d last ones won't be available for render, shaders, "
-        "etc.\n",
-        MAX_MCOL,
-        tot_vcolloop - MAX_MCOL);
-  }
-
-  /* check indices of clone/stencil */
-  if (do_fixes && CustomData_get_clone_layer(ldata, CD_MLOOPUV) >= tot_uvloop) {
-    CustomData_set_layer_clone(ldata, CD_MLOOPUV, 0);
-    is_change_l = true;
-  }
-  if (do_fixes && CustomData_get_stencil_layer(ldata, CD_MLOOPUV) >= tot_uvloop) {
-    CustomData_set_layer_stencil(ldata, CD_MLOOPUV, 0);
-    is_change_l = true;
-  }
-
-  *r_change = (is_change_v || is_change_e || is_change_l || is_change_p);
-
-  return is_valid;
-}
-
-bool BKE_mesh_validate(Mesh *me, const bool do_verbose, const bool cddata_check_mask)
-{
-  bool is_valid = true;
-  bool changed;
-
-  if (do_verbose) {
-    CLOG_INFO(&LOG, 0, "MESH: %s", me->id.name + 2);
-  }
-
-  is_valid &= BKE_mesh_validate_all_customdata(&me->vdata,
-                                               me->totvert,
-                                               &me->edata,
-                                               me->totedge,
-                                               &me->ldata,
-                                               me->totloop,
-                                               &me->pdata,
-                                               me->totpoly,
-                                               cddata_check_mask,
-                                               do_verbose,
-                                               true,
-                                               &changed);
-
-  is_valid &= BKE_mesh_validate_arrays(me,
-                                       me->mvert,
-                                       me->totvert,
-                                       me->medge,
-                                       me->totedge,
-                                       me->mface,
-                                       me->totface,
-                                       me->mloop,
-                                       me->totloop,
-                                       me->mpoly,
-                                       me->totpoly,
-                                       me->dvert,
-                                       do_verbose,
-                                       true,
-                                       &changed);
-
-  if (changed) {
-    DEG_id_tag_update(&me->id, ID_RECALC_GEOMETRY_ALL_MODES);
-    return true;
-  }
-
-  return false;
-}
-
-bool BKE_mesh_is_valid(Mesh *me)
-{
-  const bool do_verbose = true;
-  const bool do_fixes = false;
-
-  bool is_valid = true;
-  bool changed = true;
-
-  BKE_mesh_assert_normals_dirty_or_calculated(me);
-
-  is_valid &= BKE_mesh_validate_all_customdata(
-      &me->vdata,
-      me->totvert,
-      &me->edata,
-      me->totedge,
-      &me->ldata,
-      me->totloop,
-      &me->pdata,
-      me->totpoly,
-      false, /* setting mask here isn't useful, gives false positives */
-      do_verbose,
-      do_fixes,
-      &changed);
-
-  is_valid &= BKE_mesh_validate_arrays(me,
-                                       me->mvert,
-                                       me->totvert,
-                                       me->medge,
-                                       me->totedge,
-                                       me->mface,
-                                       me->totface,
-                                       me->mloop,
-                                       me->totloop,
-                                       me->mpoly,
-                                       me->totpoly,
-                                       me->dvert,
-                                       do_verbose,
-                                       do_fixes,
-                                       &changed);
-
-  BLI_assert(changed == false);
-
-  return is_valid;
-}
-
-bool BKE_mesh_validate_material_indices(Mesh *me)
-{
-  /* Cast to unsigned to catch negative indices too. */
-  const uint16_t mat_nr_max = max_ii(0, me->totcol - 1);
-  MPoly *mp;
-  const int totpoly = me->totpoly;
-  int i;
-  bool is_valid = true;
-
-  for (mp = me->mpoly, i = 0; i < totpoly; i++, mp++) {
-    if ((uint16_t)mp->mat_nr > mat_nr_max) {
-      mp->mat_nr = 0;
-      is_valid = false;
-    }
-  }
-
-  if (!is_valid) {
-    DEG_id_tag_update(&me->id, ID_RECALC_GEOMETRY_ALL_MODES);
-    return true;
-  }
-
-  return false;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Stripping (removing invalid data)
- * \{ */
-
-void BKE_mesh_strip_loose_faces(Mesh *me)
-{
-  /* NOTE: We need to keep this for edge creation (for now?), and some old `readfile.c` code. */
-  MFace *f;
-  int a, b;
-
-  for (a = b = 0, f = me->mface; a < me->totface; a++, f++) {
-    if (f->v3) {
-      if (a != b) {
-        memcpy(&me->mface[b], f, sizeof(me->mface[b]));
-        CustomData_copy_data(&me->fdata, &me->fdata, a, b, 1);
-      }
-      b++;
-    }
-  }
-  if (a != b) {
-    CustomData_free_elem(&me->fdata, b, a - b);
-    me->totface = b;
-  }
-}
-
-void BKE_mesh_strip_loose_polysloops(Mesh *me)
-{
-  MPoly *p;
-  MLoop *l;
-  int a, b;
-  /* New loops idx! */
-  int *new_idx = MEM_mallocN(sizeof(int) * me->totloop, __func__);
-
-  for (a = b = 0, p = me->mpoly; a < me->totpoly; a++, p++) {
-    bool invalid = false;
-    int i = p->loopstart;
-    int stop = i + p->totloop;
-
-    if (stop > me->totloop || stop < i || p->loopstart < 0) {
-      invalid = true;
-    }
-    else {
-      l = &me->mloop[i];
-      i = stop - i;
-      /* If one of the poly's loops is invalid, the whole poly is invalid! */
-      for (; i--; l++) {
-        if (l->e == INVALID_LOOP_EDGE_MARKER) {
-          invalid = true;
-          break;
-        }
-      }
-    }
-
-    if (p->totloop >= 3 && !invalid) {
-      if (a != b) {
-        memcpy(&me->mpoly[b], p, sizeof(me->mpoly[b]));
-        CustomData_copy_data(&me->pdata, &me->pdata, a, b, 1);
-      }
-      b++;
-    }
-  }
-  if (a != b) {
-    CustomData_free_elem(&me->pdata, b, a - b);
-    me->totpoly = b;
-  }
-
-  /* And now, get rid of invalid loops. */
-  for (a = b = 0, l = me->mloop; a < me->totloop; a++, l++) {
-    if (l->e != INVALID_LOOP_EDGE_MARKER) {
-      if (a != b) {
-        memcpy(&me->mloop[b], l, sizeof(me->mloop[b]));
-        CustomData_copy_data(&me->ldata, &me->ldata, a, b, 1);
-      }
-      new_idx[a] = b;
-      b++;
-    }
-    else {
-      /* XXX Theoretically, we should be able to not do this, as no remaining poly
-       *     should use any stripped loop. But for security's sake... */
-      new_idx[a] = -a;
-    }
-  }
-  if (a != b) {
-    CustomData_free_elem(&me->ldata, b, a - b);
-    me->totloop = b;
-  }
-
-  /* And now, update polys' start loop index. */
-  /* NOTE: At this point, there should never be any poly using a striped loop! */
-  for (a = 0, p = me->mpoly; a < me->totpoly; a++, p++) {
-    p->loopstart = new_idx[p->loopstart];
-  }
-
-  MEM_freeN(new_idx);
-}
-
-void BKE_mesh_strip_loose_edges(Mesh *me)
-{
-  MEdge *e;
-  MLoop *l;
-  int a, b;
-  uint *new_idx = MEM_mallocN(sizeof(int) * me->totedge, __func__);
-
-  for (a = b = 0, e = me->medge; a < me->totedge; a++, e++) {
-    if (e->v1 != e->v2) {
-      if (a != b) {
-        memcpy(&me->medge[b], e, sizeof(me->medge[b]));
-        CustomData_copy_data(&me->edata, &me->edata, a, b, 1);
-      }
-      new_idx[a] = b;
-      b++;
-    }
-    else {
-      new_idx[a] = INVALID_LOOP_EDGE_MARKER;
-    }
-  }
-  if (a != b) {
-    CustomData_free_elem(&me->edata, b, a - b);
-    me->totedge = b;
-  }
-
-  /* And now, update loops' edge indices. */
-  /* XXX We hope no loop was pointing to a striped edge!
-   *     Else, its e will be set to INVALID_LOOP_EDGE_MARKER :/ */
-  for (a = 0, l = me->mloop; a < me->totloop; a++, l++) {
-    l->e = new_idx[l->e];
-  }
-
-  MEM_freeN(new_idx);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Mesh Edge Calculation
- * \{ */
-
-/* make edges in a Mesh, for outside of editmode */
-
-struct EdgeSort {
-  uint v1, v2;
-  char is_loose, is_draw;
-};
-
-/* edges have to be added with lowest index first for sorting */
-static void to_edgesort(struct EdgeSort *ed, uint v1, uint v2, char is_loose, short is_draw)
-{
-  if (v1 < v2) {
-    ed->v1 = v1;
-    ed->v2 = v2;
-  }
-  else {
-    ed->v1 = v2;
-    ed->v2 = v1;
-  }
-  ed->is_loose = is_loose;
-  ed->is_draw = is_draw;
-}
-
-static int vergedgesort(const void *v1, const void *v2)
-{
-  const struct EdgeSort *x1 = v1, *x2 = v2;
-
-  if (x1->v1 > x2->v1) {
-    return 1;
-  }
-  if (x1->v1 < x2->v1) {
-    return -1;
-  }
-  if (x1->v2 > x2->v2) {
-    return 1;
-  }
-  if (x1->v2 < x2->v2) {
-    return -1;
-  }
-
-  return 0;
-}
-
-/* Create edges based on known verts and faces,
- * this function is only used when loading very old blend files */
-
-static void mesh_calc_edges_mdata(MVert *UNUSED(allvert),
-                                  MFace *allface,
-                                  MLoop *allloop,
-                                  MPoly *allpoly,
-                                  int UNUSED(totvert),
-                                  int totface,
-                                  int UNUSED(totloop),
-                                  int totpoly,
-                                  const bool use_old,
-                                  MEdge **r_medge,
-                                  int *r_totedge)
-{
-  MPoly *mpoly;
-  MFace *mface;
-  MEdge *medge, *med;
-  EdgeHash *hash;
-  struct EdgeSort *edsort, *ed;
-  int a, totedge = 0;
-  uint totedge_final = 0;
-  uint edge_index;
-
-  /* we put all edges in array, sort them, and detect doubles that way */
-
-  for (a = totface, mface = allface; a > 0; a--, mface++) {
-    if (mface->v4) {
-      totedge += 4;
-    }
-    else if (mface->v3) {
-      totedge += 3;
-    }
-    else {
-      totedge += 1;
-    }
-  }
-
-  if (totedge == 0) {
-    /* flag that mesh has edges */
-    (*r_medge) = MEM_callocN(0, __func__);
-    (*r_totedge) = 0;
-    return;
-  }
-
-  ed = edsort = MEM_mallocN(totedge * sizeof(struct EdgeSort), "EdgeSort");
-
-  for (a = totface, mface = allface; a > 0; a--, mface++) {
-    to_edgesort(ed++, mface->v1, mface->v2, !mface->v3, mface->edcode & ME_V1V2);
-    if (mface->v4) {
-      to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
-      to_edgesort(ed++, mface->v3, mface->v4, 0, mface->edcode & ME_V3V4);
-      to_edgesort(ed++, mface->v4, mface->v1, 0, mface->edcode & ME_V4V1);
-    }
-    else if (mface->v3) {
-      to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
-      to_edgesort(ed++, mface->v3, mface->v1, 0, mface->edcode & ME_V3V1);
-    }
-  }
-
-  qsort(edsort, totedge, sizeof(struct EdgeSort), vergedgesort);
-
-  /* count final amount */
-  for (a = totedge, ed = edsort; a > 1; a--, ed++) {
-    /* edge is unique when it differs from next edge, or is last */
-    if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
-      totedge_final++;
-    }
-  }
-  totedge_final++;
-
-  medge = MEM_callocN(sizeof(MEdge) * totedge_final, __func__);
-
-  for (a = totedge, med = medge, ed = edsort; a > 1; a--, ed++) {
-    /* edge is unique when it differs from next edge, or is last */
-    if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
-      med->v1 = ed->v1;
-      med->v2 = ed->v2;
-      if (use_old == false || ed->is_draw) {
-        med->flag = ME_EDGEDRAW | ME_EDGERENDER;
-      }
-      if (ed->is_loose) {
-        med->flag |= ME_LOOSEEDGE;
-      }
-
-      /* order is swapped so extruding this edge as a surface won't flip face normals
-       * with cyclic curves */
-      if (ed->v1 + 1 != ed->v2) {
-        SWAP(uint, med->v1, med->v2);
-      }
-      med++;
-    }
-    else {
-      /* equal edge, we merge the drawflag */
-      (ed + 1)->is_draw |= ed->is_draw;
-    }
-  }
-  /* last edge */
-  med->v1 = ed->v1;
-  med->v2 = ed->v2;
-  med->flag = ME_EDGEDRAW;
-  if (ed->is_loose) {
-    med->flag |= ME_LOOSEEDGE;
-  }
-  med->flag |= ME_EDGERENDER;
-
-  MEM_freeN(edsort);
-
-  /* set edge members of mloops */
-  hash = BLI_edgehash_new_ex(__func__, totedge_final);
-  for (edge_index = 0, med = medge; edge_index < totedge_final; edge_index++, med++) {
-    BLI_edgehash_insert(hash, med->v1, med->v2, POINTER_FROM_UINT(edge_index));
-  }
-
-  mpoly = allpoly;
-  for (a = 0; a < totpoly; a++, mpoly++) {
-    MLoop *ml, *ml_next;
-    int i = mpoly->totloop;
-
-    ml_next = allloop + mpoly->loopstart; /* first loop */
-    ml = &ml_next[i - 1];                 /* last loop */
-
-    while (i-- != 0) {
-      ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(hash, ml->v, ml_next->v));
-      ml = ml_next;
-      ml_next++;
-    }
-  }
-
-  BLI_edgehash_free(hash, NULL);
-
-  *r_medge = medge;
-  *r_totedge = totedge_final;
-}
-
-void BKE_mesh_calc_edges_legacy(Mesh *me, const bool use_old)
-{
-  MEdge *medge;
-  int totedge = 0;
-
-  mesh_calc_edges_mdata(me->mvert,
-                        me->mface,
-                        me->mloop,
-                        me->mpoly,
-                        me->totvert,
-                        me->totface,
-                        me->totloop,
-                        me->totpoly,
-                        use_old,
-                        &medge,
-                        &totedge);
-
-  if (totedge == 0) {
-    /* flag that mesh has edges */
-    me->medge = medge;
-    me->totedge = 0;
-    return;
-  }
-
-  medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_ASSIGN, medge, totedge);
-  me->medge = medge;
-  me->totedge = totedge;
-
-  BKE_mesh_strip_loose_faces(me);
-}
-
-void BKE_mesh_calc_edges_loose(Mesh *mesh)
-{
-  MEdge *med = mesh->medge;
-  for (int i = 0; i < mesh->totedge; i++, med++) {
-    med->flag |= ME_LOOSEEDGE;
-  }
-  MLoop *ml = mesh->mloop;
-  for (int i = 0; i < mesh->totloop; i++, ml++) {
-    mesh->medge[ml->e].flag &= ~ME_LOOSEEDGE;
-  }
-  med = mesh->medge;
-  for (int i = 0; i < mesh->totedge; i++, med++) {
-    if (med->flag & ME_LOOSEEDGE) {
-      med->flag |= ME_EDGEDRAW;
-    }
-  }
-}
-
-void BKE_mesh_calc_edges_tessface(Mesh *mesh)
-{
-  const int numFaces = mesh->totface;
-  EdgeSet *eh = BLI_edgeset_new_ex(__func__, BLI_EDGEHASH_SIZE_GUESS_FROM_POLYS(numFaces));
-
-  MFace *mf = mesh->mface;
-  for (int i = 0; i < numFaces; i++, mf++) {
-    BLI_edgeset_add(eh, mf->v1, mf->v2);
-    BLI_edgeset_add(eh, mf->v2, mf->v3);
-
-    if (mf->v4) {
-      BLI_edgeset_add(eh, mf->v3, mf->v4);
-      BLI_edgeset_add(eh, mf->v4, mf->v1);
-    }
-    else {
-      BLI_edgeset_add(eh, mf->v3, mf->v1);
-    }
-  }
-
-  const int numEdges = BLI_edgeset_len(eh);
-
-  /* write new edges into a temporary CustomData */
-  CustomData edgeData;
-  CustomData_reset(&edgeData);
-  CustomData_add_layer(&edgeData, CD_MEDGE, CD_CALLOC, NULL, numEdges);
-  CustomData_add_layer(&edgeData, CD_ORIGINDEX, CD_CALLOC, NULL, numEdges);
-
-  MEdge *med = CustomData_get_layer(&edgeData, CD_MEDGE);
-  int *index = CustomData_get_layer(&edgeData, CD_ORIGINDEX);
-
-  EdgeSetIterator *ehi = BLI_edgesetIterator_new(eh);
-  for (int i = 0; BLI_edgesetIterator_isDone(ehi) == false;
-       BLI_edgesetIterator_step(ehi), i++, med++, index++) {
-    BLI_edgesetIterator_getKey(ehi, &med->v1, &med->v2);
-
-    med->flag = ME_EDGEDRAW | ME_EDGERENDER;
-    *index = ORIGINDEX_NONE;
-  }
-  BLI_edgesetIterator_free(ehi);
-
-  /* free old CustomData and assign new one */
-  CustomData_free(&mesh->edata, mesh->totedge);
-  mesh->edata = edgeData;
-  mesh->totedge = numEdges;
-
-  mesh->medge = CustomData_get_layer(&mesh->edata, CD_MEDGE);
-
-  BLI_edgeset_free(eh);
-}
-
-/** \} */
diff --git a/source/blender/blenkernel/intern/mesh_validate.cc b/source/blender/blenkernel/intern/mesh_validate.cc
new file mode 100644
index 00000000000..cc7404e2a11
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_validate.cc
@@ -0,0 +1,1581 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2011 Blender Foundation. All rights reserved. */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#include "CLG_log.h"
+
+#include "BLI_bitmap.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_sys_types.h"
+
+#include "BLI_edgehash.h"
+#include "BLI_math_base.h"
+#include "BLI_math_vector.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_customdata.h"
+#include "BKE_deform.h"
+#include "BKE_mesh.h"
+
+#include "DEG_depsgraph.h"
+
+#include "MEM_guardedalloc.h"
+
+/* loop v/e are unsigned, so using max uint_32 value as invalid marker... */
+#define INVALID_LOOP_EDGE_MARKER 4294967295u
+
+static CLG_LogRef LOG = {"bke.mesh"};
+
+/* -------------------------------------------------------------------- */
+/** \name Internal functions
+ * \{ */
+
+union EdgeUUID {
+  uint32_t verts[2];
+  int64_t edval;
+};
+
+struct SortFace {
+  EdgeUUID es[4];
+  uint index;
+};
+
+/* Used to detect polys (faces) using exactly the same vertices. */
+/* Used to detect loops used by no (disjoint) or more than one (intersect) polys. */
+struct SortPoly {
+  int *verts;
+  int numverts;
+  int loopstart;
+  uint index;
+  bool invalid; /* Poly index. */
+};
+
+static void edge_store_assign(uint32_t verts[2], const uint32_t v1, const uint32_t v2)
+{
+  if (v1 < v2) {
+    verts[0] = v1;
+    verts[1] = v2;
+  }
+  else {
+    verts[0] = v2;
+    verts[1] = v1;
+  }
+}
+
+static void edge_store_from_mface_quad(EdgeUUID es[4], MFace *mf)
+{
+  edge_store_assign(es[0].verts, mf->v1, mf->v2);
+  edge_store_assign(es[1].verts, mf->v2, mf->v3);
+  edge_store_assign(es[2].verts, mf->v3, mf->v4);
+  edge_store_assign(es[3].verts, mf->v4, mf->v1);
+}
+
+static void edge_store_from_mface_tri(EdgeUUID es[4], MFace *mf)
+{
+  edge_store_assign(es[0].verts, mf->v1, mf->v2);
+  edge_store_assign(es[1].verts, mf->v2, mf->v3);
+  edge_store_assign(es[2].verts, mf->v3, mf->v1);
+  es[3].verts[0] = es[3].verts[1] = UINT_MAX;
+}
+
+static int int64_cmp(const void *v1, const void *v2)
+{
+  const int64_t x1 = *(const int64_t *)v1;
+  const int64_t x2 = *(const int64_t *)v2;
+
+  if (x1 > x2) {
+    return 1;
+  }
+  if (x1 < x2) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int search_face_cmp(const void *v1, const void *v2)
+{
+  const SortFace *sfa = static_cast(v1);
+  const SortFace *sfb = static_cast(v2);
+
+  if (sfa->es[0].edval > sfb->es[0].edval) {
+    return 1;
+  }
+  if (sfa->es[0].edval < sfb->es[0].edval) {
+    return -1;
+  }
+
+  if (sfa->es[1].edval > sfb->es[1].edval) {
+    return 1;
+  }
+  if (sfa->es[1].edval < sfb->es[1].edval) {
+    return -1;
+  }
+
+  if (sfa->es[2].edval > sfb->es[2].edval) {
+    return 1;
+  }
+  if (sfa->es[2].edval < sfb->es[2].edval) {
+    return -1;
+  }
+
+  if (sfa->es[3].edval > sfb->es[3].edval) {
+    return 1;
+  }
+  if (sfa->es[3].edval < sfb->es[3].edval) {
+    return -1;
+  }
+
+  return 0;
+}
+
+/* TODO: check there is not some standard define of this somewhere! */
+static int int_cmp(const void *v1, const void *v2)
+{
+  return *(int *)v1 > *(int *)v2 ? 1 : *(int *)v1 < *(int *)v2 ? -1 : 0;
+}
+
+static int search_poly_cmp(const void *v1, const void *v2)
+{
+  const SortPoly *sp1 = static_cast(v1);
+  const SortPoly *sp2 = static_cast(v2);
+
+  /* Reject all invalid polys at end of list! */
+  if (sp1->invalid || sp2->invalid) {
+    return sp1->invalid ? (sp2->invalid ? 0 : 1) : -1;
+  }
+  /* Else, sort on first non-equal verts (remember verts of valid polys are sorted). */
+  const int max_idx = sp1->numverts > sp2->numverts ? sp2->numverts : sp1->numverts;
+  for (int idx = 0; idx < max_idx; idx++) {
+    const int v1_i = sp1->verts[idx];
+    const int v2_i = sp2->verts[idx];
+    if (v1_i != v2_i) {
+      return (v1_i > v2_i) ? 1 : -1;
+    }
+  }
+  return sp1->numverts > sp2->numverts ? 1 : sp1->numverts < sp2->numverts ? -1 : 0;
+}
+
+static int search_polyloop_cmp(const void *v1, const void *v2)
+{
+  const SortPoly *sp1 = static_cast(v1);
+  const SortPoly *sp2 = static_cast(v2);
+
+  /* Reject all invalid polys at end of list! */
+  if (sp1->invalid || sp2->invalid) {
+    return sp1->invalid && sp2->invalid ? 0 : sp1->invalid ? 1 : -1;
+  }
+  /* Else, sort on loopstart. */
+  return sp1->loopstart > sp2->loopstart ? 1 : sp1->loopstart < sp2->loopstart ? -1 : 0;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Validation
+ * \{ */
+
+#define PRINT_MSG(...) \
+  if (do_verbose) { \
+    CLOG_INFO(&LOG, 1, __VA_ARGS__); \
+  } \
+  ((void)0)
+
+#define PRINT_ERR(...) \
+  do { \
+    is_valid = false; \
+    if (do_verbose) { \
+      CLOG_ERROR(&LOG, __VA_ARGS__); \
+    } \
+  } while (0)
+
+/* NOLINTNEXTLINE: readability-function-size */
+bool BKE_mesh_validate_arrays(Mesh *mesh,
+                              MVert *mverts,
+                              uint totvert,
+                              MEdge *medges,
+                              uint totedge,
+                              MFace *mfaces,
+                              uint totface,
+                              MLoop *mloops,
+                              uint totloop,
+                              MPoly *mpolys,
+                              uint totpoly,
+                              MDeformVert *dverts, /* assume totvert length */
+                              const bool do_verbose,
+                              const bool do_fixes,
+                              bool *r_changed)
+{
+#define REMOVE_EDGE_TAG(_me) \
+  { \
+    _me->v2 = _me->v1; \
+    free_flag.edges = do_fixes; \
+  } \
+  (void)0
+#define IS_REMOVED_EDGE(_me) (_me->v2 == _me->v1)
+
+#define REMOVE_LOOP_TAG(_ml) \
+  { \
+    _ml->e = INVALID_LOOP_EDGE_MARKER; \
+    free_flag.polyloops = do_fixes; \
+  } \
+  (void)0
+#define REMOVE_POLY_TAG(_mp) \
+  { \
+    _mp->totloop *= -1; \
+    free_flag.polyloops = do_fixes; \
+  } \
+  (void)0
+
+  MVert *mv = mverts;
+  MEdge *me;
+  MLoop *ml;
+  MPoly *mp;
+  uint i, j;
+  int *v;
+
+  bool is_valid = true;
+
+  union {
+    struct {
+      int verts : 1;
+      int verts_weight : 1;
+      int loops_edge : 1;
+    };
+    int as_flag;
+  } fix_flag;
+
+  union {
+    struct {
+      int edges : 1;
+      int faces : 1;
+      /* This regroups loops and polys! */
+      int polyloops : 1;
+      int mselect : 1;
+    };
+    int as_flag;
+  } free_flag;
+
+  union {
+    struct {
+      int edges : 1;
+    };
+    int as_flag;
+  } recalc_flag;
+
+  EdgeHash *edge_hash = BLI_edgehash_new_ex(__func__, totedge);
+
+  BLI_assert(!(do_fixes && mesh == nullptr));
+
+  fix_flag.as_flag = 0;
+  free_flag.as_flag = 0;
+  recalc_flag.as_flag = 0;
+
+  PRINT_MSG("verts(%u), edges(%u), loops(%u), polygons(%u)", totvert, totedge, totloop, totpoly);
+
+  if (totedge == 0 && totpoly != 0) {
+    PRINT_ERR("\tLogical error, %u polygons and 0 edges", totpoly);
+    recalc_flag.edges = do_fixes;
+  }
+
+  const float(*vert_normals)[3] = nullptr;
+  BKE_mesh_assert_normals_dirty_or_calculated(mesh);
+  if (!BKE_mesh_vertex_normals_are_dirty(mesh)) {
+    vert_normals = BKE_mesh_vertex_normals_ensure(mesh);
+  }
+
+  for (i = 0; i < totvert; i++, mv++) {
+    bool fix_normal = true;
+
+    for (j = 0; j < 3; j++) {
+      if (!isfinite(mv->co[j])) {
+        PRINT_ERR("\tVertex %u: has invalid coordinate", i);
+
+        if (do_fixes) {
+          zero_v3(mv->co);
+
+          fix_flag.verts = true;
+        }
+      }
+
+      if (vert_normals && vert_normals[i][j] != 0.0f) {
+        fix_normal = false;
+        break;
+      }
+    }
+
+    if (vert_normals && fix_normal) {
+      /* If the vertex normal accumulates to zero or isn't part of a face, the location is used.
+       * When the location is also zero, a zero normal warning should not be raised.
+       * since this is the expected behavior of normal calculation.
+       *
+       * This avoids false positives but isn't foolproof as it's possible the vertex
+       * is part of a polygon that has a normal which this vertex should be using,
+       * although it's also possible degenerate/opposite faces accumulate to a zero vector.
+       * To detect this a full normal recalculation would be needed, which is out of scope
+       * for a basic validity check (see "Vertex Normal" in the doc-string). */
+      if (!is_zero_v3(mv->co)) {
+        PRINT_ERR("\tVertex %u: has zero normal, assuming Z-up normal", i);
+        if (do_fixes) {
+          float *normal = (float *)vert_normals[i];
+          normal[2] = 1.0f;
+          fix_flag.verts = true;
+        }
+      }
+    }
+  }
+
+  for (i = 0, me = medges; i < totedge; i++, me++) {
+    bool remove = false;
+
+    if (me->v1 == me->v2) {
+      PRINT_ERR("\tEdge %u: has matching verts, both %u", i, me->v1);
+      remove = do_fixes;
+    }
+    if (me->v1 >= totvert) {
+      PRINT_ERR("\tEdge %u: v1 index out of range, %u", i, me->v1);
+      remove = do_fixes;
+    }
+    if (me->v2 >= totvert) {
+      PRINT_ERR("\tEdge %u: v2 index out of range, %u", i, me->v2);
+      remove = do_fixes;
+    }
+
+    if ((me->v1 != me->v2) && BLI_edgehash_haskey(edge_hash, me->v1, me->v2)) {
+      PRINT_ERR("\tEdge %u: is a duplicate of %d",
+                i,
+                POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, me->v1, me->v2)));
+      remove = do_fixes;
+    }
+
+    if (remove == false) {
+      if (me->v1 != me->v2) {
+        BLI_edgehash_insert(edge_hash, me->v1, me->v2, POINTER_FROM_INT(i));
+      }
+    }
+    else {
+      REMOVE_EDGE_TAG(me);
+    }
+  }
+
+  if (mfaces && !mpolys) {
+#define REMOVE_FACE_TAG(_mf) \
+  { \
+    _mf->v3 = 0; \
+    free_flag.faces = do_fixes; \
+  } \
+  (void)0
+#define CHECK_FACE_VERT_INDEX(a, b) \
+  if (mf->a == mf->b) { \
+    PRINT_ERR("    face %u: verts invalid, " STRINGIFY(a) "/" STRINGIFY(b) " both %u", i, mf->a); \
+    remove = do_fixes; \
+  } \
+  (void)0
+#define CHECK_FACE_EDGE(a, b) \
+  if (!BLI_edgehash_haskey(edge_hash, mf->a, mf->b)) { \
+    PRINT_ERR("    face %u: edge " STRINGIFY(a) "/" STRINGIFY(b) " (%u,%u) is missing edge data", \
+              i, \
+              mf->a, \
+              mf->b); \
+    recalc_flag.edges = do_fixes; \
+  } \
+  (void)0
+
+    MFace *mf;
+    MFace *mf_prev;
+
+    SortFace *sort_faces = (SortFace *)MEM_callocN(sizeof(SortFace) * totface, "search faces");
+    SortFace *sf;
+    SortFace *sf_prev;
+    uint totsortface = 0;
+
+    PRINT_ERR("No Polys, only tessellated Faces");
+
+    for (i = 0, mf = mfaces, sf = sort_faces; i < totface; i++, mf++) {
+      bool remove = false;
+      int fidx;
+      uint fv[4];
+
+      fidx = mf->v4 ? 3 : 2;
+      do {
+        fv[fidx] = *(&(mf->v1) + fidx);
+        if (fv[fidx] >= totvert) {
+          PRINT_ERR("\tFace %u: 'v%d' index out of range, %u", i, fidx + 1, fv[fidx]);
+          remove = do_fixes;
+        }
+      } while (fidx--);
+
+      if (remove == false) {
+        if (mf->v4) {
+          CHECK_FACE_VERT_INDEX(v1, v2);
+          CHECK_FACE_VERT_INDEX(v1, v3);
+          CHECK_FACE_VERT_INDEX(v1, v4);
+
+          CHECK_FACE_VERT_INDEX(v2, v3);
+          CHECK_FACE_VERT_INDEX(v2, v4);
+
+          CHECK_FACE_VERT_INDEX(v3, v4);
+        }
+        else {
+          CHECK_FACE_VERT_INDEX(v1, v2);
+          CHECK_FACE_VERT_INDEX(v1, v3);
+
+          CHECK_FACE_VERT_INDEX(v2, v3);
+        }
+
+        if (remove == false) {
+          if (totedge) {
+            if (mf->v4) {
+              CHECK_FACE_EDGE(v1, v2);
+              CHECK_FACE_EDGE(v2, v3);
+              CHECK_FACE_EDGE(v3, v4);
+              CHECK_FACE_EDGE(v4, v1);
+            }
+            else {
+              CHECK_FACE_EDGE(v1, v2);
+              CHECK_FACE_EDGE(v2, v3);
+              CHECK_FACE_EDGE(v3, v1);
+            }
+          }
+
+          sf->index = i;
+
+          if (mf->v4) {
+            edge_store_from_mface_quad(sf->es, mf);
+
+            qsort(sf->es, 4, sizeof(int64_t), int64_cmp);
+          }
+          else {
+            edge_store_from_mface_tri(sf->es, mf);
+            qsort(sf->es, 3, sizeof(int64_t), int64_cmp);
+          }
+
+          totsortface++;
+          sf++;
+        }
+      }
+
+      if (remove) {
+        REMOVE_FACE_TAG(mf);
+      }
+    }
+
+    qsort(sort_faces, totsortface, sizeof(SortFace), search_face_cmp);
+
+    sf = sort_faces;
+    sf_prev = sf;
+    sf++;
+
+    for (i = 1; i < totsortface; i++, sf++) {
+      bool remove = false;
+
+      /* on a valid mesh, code below will never run */
+      if (memcmp(sf->es, sf_prev->es, sizeof(sf_prev->es)) == 0) {
+        mf = mfaces + sf->index;
+
+        if (do_verbose) {
+          mf_prev = mfaces + sf_prev->index;
+
+          if (mf->v4) {
+            PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u,%u) (%u,%u,%u,%u)",
+                      sf->index,
+                      sf_prev->index,
+                      mf->v1,
+                      mf->v2,
+                      mf->v3,
+                      mf->v4,
+                      mf_prev->v1,
+                      mf_prev->v2,
+                      mf_prev->v3,
+                      mf_prev->v4);
+          }
+          else {
+            PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u) (%u,%u,%u)",
+                      sf->index,
+                      sf_prev->index,
+                      mf->v1,
+                      mf->v2,
+                      mf->v3,
+                      mf_prev->v1,
+                      mf_prev->v2,
+                      mf_prev->v3);
+          }
+        }
+
+        remove = do_fixes;
+      }
+      else {
+        sf_prev = sf;
+      }
+
+      if (remove) {
+        REMOVE_FACE_TAG(mf);
+      }
+    }
+
+    MEM_freeN(sort_faces);
+
+#undef REMOVE_FACE_TAG
+#undef CHECK_FACE_VERT_INDEX
+#undef CHECK_FACE_EDGE
+  }
+
+  /* Checking loops and polys is a bit tricky, as they are quite intricate...
+   *
+   * Polys must have:
+   * - a valid loopstart value.
+   * - a valid totloop value (>= 3 and loopstart+totloop < me.totloop).
+   *
+   * Loops must have:
+   * - a valid v value.
+   * - a valid e value (corresponding to the edge it defines with the next loop in poly).
+   *
+   * Also, loops not used by polys can be discarded.
+   * And "intersecting" loops (i.e. loops used by more than one poly) are invalid,
+   * so be sure to leave at most one poly per loop!
+   */
+  {
+    BLI_bitmap *vert_tag = BLI_BITMAP_NEW(mesh->totvert, __func__);
+
+    SortPoly *sort_polys = (SortPoly *)MEM_callocN(sizeof(SortPoly) * totpoly,
+                                                   "mesh validate's sort_polys");
+    SortPoly *prev_sp, *sp = sort_polys;
+    int prev_end;
+
+    for (i = 0, mp = mpolys; i < totpoly; i++, mp++, sp++) {
+      sp->index = i;
+
+      /* Material index, isolated from other tests here. While large indices are clamped,
+       * negative indices aren't supported by drawing, exporters etc.
+       * To check the indices are in range, use #BKE_mesh_validate_material_indices */
+      if (mp->mat_nr < 0) {
+        PRINT_ERR("\tPoly %u has invalid material (%d)", sp->index, mp->mat_nr);
+        if (do_fixes) {
+          mp->mat_nr = 0;
+        }
+      }
+
+      if (mp->loopstart < 0 || mp->totloop < 3) {
+        /* Invalid loop data. */
+        PRINT_ERR("\tPoly %u is invalid (loopstart: %d, totloop: %d)",
+                  sp->index,
+                  mp->loopstart,
+                  mp->totloop);
+        sp->invalid = true;
+      }
+      else if (mp->loopstart + mp->totloop > totloop) {
+        /* Invalid loop data. */
+        PRINT_ERR(
+            "\tPoly %u uses loops out of range (loopstart: %d, loopend: %d, max nbr of loops: %u)",
+            sp->index,
+            mp->loopstart,
+            mp->loopstart + mp->totloop - 1,
+            totloop - 1);
+        sp->invalid = true;
+      }
+      else {
+        /* Poly itself is valid, for now. */
+        int v1, v2; /* v1 is prev loop vert idx, v2 is current loop one. */
+        sp->invalid = false;
+        sp->verts = v = (int *)MEM_mallocN(sizeof(int) * mp->totloop, "Vert idx of SortPoly");
+        sp->numverts = mp->totloop;
+        sp->loopstart = mp->loopstart;
+
+        /* Ideally we would only have to do that once on all vertices
+         * before we start checking each poly, but several polys can use same vert,
+         * so we have to ensure here all verts of current poly are cleared. */
+        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++) {
+          if (ml->v < totvert) {
+            BLI_BITMAP_DISABLE(vert_tag, ml->v);
+          }
+        }
+
+        /* Test all poly's loops' vert idx. */
+        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++, v++) {
+          if (ml->v >= totvert) {
+            /* Invalid vert idx. */
+            PRINT_ERR("\tLoop %u has invalid vert reference (%u)", sp->loopstart + j, ml->v);
+            sp->invalid = true;
+          }
+          else if (BLI_BITMAP_TEST(vert_tag, ml->v)) {
+            PRINT_ERR("\tPoly %u has duplicated vert reference at corner (%u)", i, j);
+            sp->invalid = true;
+          }
+          else {
+            BLI_BITMAP_ENABLE(vert_tag, ml->v);
+          }
+          *v = ml->v;
+        }
+
+        if (sp->invalid) {
+          continue;
+        }
+
+        /* Test all poly's loops. */
+        for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++) {
+          v1 = ml->v;
+          v2 = mloops[sp->loopstart + (j + 1) % mp->totloop].v;
+          if (!BLI_edgehash_haskey(edge_hash, v1, v2)) {
+            /* Edge not existing. */
+            PRINT_ERR("\tPoly %u needs missing edge (%d, %d)", sp->index, v1, v2);
+            if (do_fixes) {
+              recalc_flag.edges = true;
+            }
+            else {
+              sp->invalid = true;
+            }
+          }
+          else if (ml->e >= totedge) {
+            /* Invalid edge idx.
+             * We already know from previous text that a valid edge exists, use it (if allowed)! */
+            if (do_fixes) {
+              int prev_e = ml->e;
+              ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2));
+              fix_flag.loops_edge = true;
+              PRINT_ERR("\tLoop %u has invalid edge reference (%d), fixed using edge %u",
+                        sp->loopstart + j,
+                        prev_e,
+                        ml->e);
+            }
+            else {
+              PRINT_ERR("\tLoop %u has invalid edge reference (%u)", sp->loopstart + j, ml->e);
+              sp->invalid = true;
+            }
+          }
+          else {
+            me = &medges[ml->e];
+            if (IS_REMOVED_EDGE(me) ||
+                !((me->v1 == v1 && me->v2 == v2) || (me->v1 == v2 && me->v2 == v1))) {
+              /* The pointed edge is invalid (tagged as removed, or vert idx mismatch),
+               * and we already know from previous test that a valid one exists,
+               * use it (if allowed)! */
+              if (do_fixes) {
+                int prev_e = ml->e;
+                ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2));
+                fix_flag.loops_edge = true;
+                PRINT_ERR(
+                    "\tPoly %u has invalid edge reference (%d, is_removed: %d), fixed using edge "
+                    "%u",
+                    sp->index,
+                    prev_e,
+                    IS_REMOVED_EDGE(me),
+                    ml->e);
+              }
+              else {
+                PRINT_ERR("\tPoly %u has invalid edge reference (%u)", sp->index, ml->e);
+                sp->invalid = true;
+              }
+            }
+          }
+        }
+
+        if (!sp->invalid) {
+          /* Needed for checking polys using same verts below. */
+          qsort(sp->verts, sp->numverts, sizeof(int), int_cmp);
+        }
+      }
+    }
+
+    MEM_freeN(vert_tag);
+
+    /* Second check pass, testing polys using the same verts. */
+    qsort(sort_polys, totpoly, sizeof(SortPoly), search_poly_cmp);
+    sp = prev_sp = sort_polys;
+    sp++;
+
+    for (i = 1; i < totpoly; i++, sp++) {
+      int p1_nv = sp->numverts, p2_nv = prev_sp->numverts;
+      const int *p1_v = sp->verts, *p2_v = prev_sp->verts;
+
+      if (sp->invalid) {
+        /* Break, because all known invalid polys have been put at the end
+         * by qsort with search_poly_cmp. */
+        break;
+      }
+
+      /* Test same polys. */
+      if ((p1_nv == p2_nv) && (memcmp(p1_v, p2_v, p1_nv * sizeof(*p1_v)) == 0)) {
+        if (do_verbose) {
+          /* TODO: convert list to string */
+          PRINT_ERR("\tPolys %u and %u use same vertices (%d", prev_sp->index, sp->index, *p1_v);
+          for (j = 1; j < p1_nv; j++) {
+            PRINT_ERR(", %d", p1_v[j]);
+          }
+          PRINT_ERR("), considering poly %u as invalid.", sp->index);
+        }
+        else {
+          is_valid = false;
+        }
+        sp->invalid = true;
+      }
+      else {
+        prev_sp = sp;
+      }
+    }
+
+    /* Third check pass, testing loops used by none or more than one poly. */
+    qsort(sort_polys, totpoly, sizeof(SortPoly), search_polyloop_cmp);
+    sp = sort_polys;
+    prev_sp = nullptr;
+    prev_end = 0;
+    for (i = 0; i < totpoly; i++, sp++) {
+      /* Free this now, we don't need it anymore, and avoid us another loop! */
+      if (sp->verts) {
+        MEM_freeN(sp->verts);
+      }
+
+      /* Note above prev_sp: in following code, we make sure it is always valid poly (or nullptr).
+       */
+      if (sp->invalid) {
+        if (do_fixes) {
+          REMOVE_POLY_TAG((&mpolys[sp->index]));
+          /* DO NOT REMOVE ITS LOOPS!!!
+           * As already invalid polys are at the end of the SortPoly list, the loops they
+           * were the only users have already been tagged as "to remove" during previous
+           * iterations, and we don't want to remove some loops that may be used by
+           * another valid poly! */
+        }
+      }
+      /* Test loops users. */
+      else {
+        /* Unused loops. */
+        if (prev_end < sp->loopstart) {
+          for (j = prev_end, ml = &mloops[prev_end]; j < sp->loopstart; j++, ml++) {
+            PRINT_ERR("\tLoop %u is unused.", j);
+            if (do_fixes) {
+              REMOVE_LOOP_TAG(ml);
+            }
+          }
+          prev_end = sp->loopstart + sp->numverts;
+          prev_sp = sp;
+        }
+        /* Multi-used loops. */
+        else if (prev_end > sp->loopstart) {
+          PRINT_ERR("\tPolys %u and %u share loops from %d to %d, considering poly %u as invalid.",
+                    prev_sp->index,
+                    sp->index,
+                    sp->loopstart,
+                    prev_end,
+                    sp->index);
+          if (do_fixes) {
+            REMOVE_POLY_TAG((&mpolys[sp->index]));
+            /* DO NOT REMOVE ITS LOOPS!!!
+             * They might be used by some next, valid poly!
+             * Just not updating prev_end/prev_sp vars is enough to ensure the loops
+             * effectively no more needed will be marked as "to be removed"! */
+          }
+        }
+        else {
+          prev_end = sp->loopstart + sp->numverts;
+          prev_sp = sp;
+        }
+      }
+    }
+    /* We may have some remaining unused loops to get rid of! */
+    if (prev_end < totloop) {
+      for (j = prev_end, ml = &mloops[prev_end]; j < totloop; j++, ml++) {
+        PRINT_ERR("\tLoop %u is unused.", j);
+        if (do_fixes) {
+          REMOVE_LOOP_TAG(ml);
+        }
+      }
+    }
+
+    MEM_freeN(sort_polys);
+  }
+
+  BLI_edgehash_free(edge_hash, nullptr);
+
+  /* fix deform verts */
+  if (dverts) {
+    MDeformVert *dv;
+    for (i = 0, dv = dverts; i < totvert; i++, dv++) {
+      MDeformWeight *dw;
+
+      for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) {
+        /* NOTE: greater than max defgroups is accounted for in our code, but not < 0. */
+        if (!isfinite(dw->weight)) {
+          PRINT_ERR("\tVertex deform %u, group %u has weight: %f", i, dw->def_nr, dw->weight);
+          if (do_fixes) {
+            dw->weight = 0.0f;
+            fix_flag.verts_weight = true;
+          }
+        }
+        else if (dw->weight < 0.0f || dw->weight > 1.0f) {
+          PRINT_ERR("\tVertex deform %u, group %u has weight: %f", i, dw->def_nr, dw->weight);
+          if (do_fixes) {
+            CLAMP(dw->weight, 0.0f, 1.0f);
+            fix_flag.verts_weight = true;
+          }
+        }
+
+        /* Not technically incorrect since this is unsigned, however,
+         * a value over INT_MAX is almost certainly caused by wrapping an uint. */
+        if (dw->def_nr >= INT_MAX) {
+          PRINT_ERR("\tVertex deform %u, has invalid group %u", i, dw->def_nr);
+          if (do_fixes) {
+            BKE_defvert_remove_group(dv, dw);
+            fix_flag.verts_weight = true;
+
+            if (dv->dw) {
+              /* re-allocated, the new values compensate for stepping
+               * within the for loop and may not be valid */
+              j--;
+              dw = dv->dw + j;
+            }
+            else { /* all freed */
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+#undef REMOVE_EDGE_TAG
+#undef IS_REMOVED_EDGE
+#undef REMOVE_LOOP_TAG
+#undef REMOVE_POLY_TAG
+
+  if (mesh) {
+    if (free_flag.faces) {
+      BKE_mesh_strip_loose_faces(mesh);
+    }
+
+    if (free_flag.polyloops) {
+      BKE_mesh_strip_loose_polysloops(mesh);
+    }
+
+    if (free_flag.edges) {
+      BKE_mesh_strip_loose_edges(mesh);
+    }
+
+    if (recalc_flag.edges) {
+      BKE_mesh_calc_edges(mesh, true, false);
+    }
+  }
+
+  if (mesh && mesh->mselect) {
+    MSelect *msel;
+
+    for (i = 0, msel = mesh->mselect; i < mesh->totselect; i++, msel++) {
+      int tot_elem = 0;
+
+      if (msel->index < 0) {
+        PRINT_ERR(
+            "\tMesh select element %u type %d index is negative, "
+            "resetting selection stack.\n",
+            i,
+            msel->type);
+        free_flag.mselect = do_fixes;
+        break;
+      }
+
+      switch (msel->type) {
+        case ME_VSEL:
+          tot_elem = mesh->totvert;
+          break;
+        case ME_ESEL:
+          tot_elem = mesh->totedge;
+          break;
+        case ME_FSEL:
+          tot_elem = mesh->totpoly;
+          break;
+      }
+
+      if (msel->index > tot_elem) {
+        PRINT_ERR(
+            "\tMesh select element %u type %d index %d is larger than data array size %d, "
+            "resetting selection stack.\n",
+            i,
+            msel->type,
+            msel->index,
+            tot_elem);
+
+        free_flag.mselect = do_fixes;
+        break;
+      }
+    }
+
+    if (free_flag.mselect) {
+      MEM_freeN(mesh->mselect);
+      mesh->mselect = nullptr;
+      mesh->totselect = 0;
+    }
+  }
+
+  PRINT_MSG("%s: finished\n\n", __func__);
+
+  *r_changed = (fix_flag.as_flag || free_flag.as_flag || recalc_flag.as_flag);
+
+  BLI_assert((*r_changed == false) || (do_fixes == true));
+
+  return is_valid;
+}
+
+static bool mesh_validate_customdata(CustomData *data,
+                                     CustomDataMask mask,
+                                     const uint totitems,
+                                     const bool do_verbose,
+                                     const bool do_fixes,
+                                     bool *r_change)
+{
+  bool is_valid = true;
+  bool has_fixes = false;
+  int i = 0;
+
+  PRINT_MSG("%s: Checking %d CD layers...\n", __func__, data->totlayer);
+
+  while (i < data->totlayer) {
+    CustomDataLayer *layer = &data->layers[i];
+    bool ok = true;
+
+    if (CustomData_layertype_is_singleton(layer->type)) {
+      const int layer_tot = CustomData_number_of_layers(data, layer->type);
+      if (layer_tot > 1) {
+        PRINT_ERR("\tCustomDataLayer type %d is a singleton, found %d in Mesh structure\n",
+                  layer->type,
+                  layer_tot);
+        ok = false;
+      }
+    }
+
+    if (mask != 0) {
+      CustomDataMask layer_typemask = CD_TYPE_AS_MASK(layer->type);
+      if ((layer_typemask & mask) == 0) {
+        PRINT_ERR("\tCustomDataLayer type %d which isn't in the mask\n", layer->type);
+        ok = false;
+      }
+    }
+
+    if (ok == false) {
+      if (do_fixes) {
+        CustomData_free_layer(data, layer->type, 0, i);
+        has_fixes = true;
+      }
+    }
+
+    if (ok) {
+      if (CustomData_layer_validate(layer, totitems, do_fixes)) {
+        PRINT_ERR("\tCustomDataLayer type %d has some invalid data\n", layer->type);
+        has_fixes = do_fixes;
+      }
+      i++;
+    }
+  }
+
+  PRINT_MSG("%s: Finished (is_valid=%d)\n\n", __func__, (int)!has_fixes);
+
+  *r_change = has_fixes;
+
+  return is_valid;
+}
+
+bool BKE_mesh_validate_all_customdata(CustomData *vdata,
+                                      const uint totvert,
+                                      CustomData *edata,
+                                      const uint totedge,
+                                      CustomData *ldata,
+                                      const uint totloop,
+                                      CustomData *pdata,
+                                      const uint totpoly,
+                                      const bool check_meshmask,
+                                      const bool do_verbose,
+                                      const bool do_fixes,
+                                      bool *r_change)
+{
+  bool is_valid = true;
+  bool is_change_v, is_change_e, is_change_l, is_change_p;
+  CustomData_MeshMasks mask = {0};
+  if (check_meshmask) {
+    mask = CD_MASK_MESH;
+    /* Normal data isn't in the mask since it is derived data,
+     * but it is valid and should not be removed. */
+    mask.vmask |= CD_MASK_NORMAL;
+    mask.pmask |= CD_MASK_NORMAL;
+  }
+
+  is_valid &= mesh_validate_customdata(
+      vdata, mask.vmask, totvert, do_verbose, do_fixes, &is_change_v);
+  is_valid &= mesh_validate_customdata(
+      edata, mask.emask, totedge, do_verbose, do_fixes, &is_change_e);
+  is_valid &= mesh_validate_customdata(
+      ldata, mask.lmask, totloop, do_verbose, do_fixes, &is_change_l);
+  is_valid &= mesh_validate_customdata(
+      pdata, mask.pmask, totpoly, do_verbose, do_fixes, &is_change_p);
+
+  const int tot_uvloop = CustomData_number_of_layers(ldata, CD_MLOOPUV);
+  const int tot_vcolloop = CustomData_number_of_layers(ldata, CD_MLOOPCOL);
+  if (tot_uvloop > MAX_MTFACE) {
+    PRINT_ERR(
+        "\tMore UV layers than %d allowed, %d last ones won't be available for render, shaders, "
+        "etc.\n",
+        MAX_MTFACE,
+        tot_uvloop - MAX_MTFACE);
+  }
+  if (tot_vcolloop > MAX_MCOL) {
+    PRINT_ERR(
+        "\tMore VCol layers than %d allowed, %d last ones won't be available for render, shaders, "
+        "etc.\n",
+        MAX_MCOL,
+        tot_vcolloop - MAX_MCOL);
+  }
+
+  /* check indices of clone/stencil */
+  if (do_fixes && CustomData_get_clone_layer(ldata, CD_MLOOPUV) >= tot_uvloop) {
+    CustomData_set_layer_clone(ldata, CD_MLOOPUV, 0);
+    is_change_l = true;
+  }
+  if (do_fixes && CustomData_get_stencil_layer(ldata, CD_MLOOPUV) >= tot_uvloop) {
+    CustomData_set_layer_stencil(ldata, CD_MLOOPUV, 0);
+    is_change_l = true;
+  }
+
+  *r_change = (is_change_v || is_change_e || is_change_l || is_change_p);
+
+  return is_valid;
+}
+
+bool BKE_mesh_validate(Mesh *me, const bool do_verbose, const bool cddata_check_mask)
+{
+  bool is_valid = true;
+  bool changed;
+
+  if (do_verbose) {
+    CLOG_INFO(&LOG, 0, "MESH: %s", me->id.name + 2);
+  }
+
+  is_valid &= BKE_mesh_validate_all_customdata(&me->vdata,
+                                               me->totvert,
+                                               &me->edata,
+                                               me->totedge,
+                                               &me->ldata,
+                                               me->totloop,
+                                               &me->pdata,
+                                               me->totpoly,
+                                               cddata_check_mask,
+                                               do_verbose,
+                                               true,
+                                               &changed);
+
+  is_valid &= BKE_mesh_validate_arrays(me,
+                                       me->mvert,
+                                       me->totvert,
+                                       me->medge,
+                                       me->totedge,
+                                       me->mface,
+                                       me->totface,
+                                       me->mloop,
+                                       me->totloop,
+                                       me->mpoly,
+                                       me->totpoly,
+                                       me->dvert,
+                                       do_verbose,
+                                       true,
+                                       &changed);
+
+  if (changed) {
+    DEG_id_tag_update(&me->id, ID_RECALC_GEOMETRY_ALL_MODES);
+    return true;
+  }
+
+  return false;
+}
+
+bool BKE_mesh_is_valid(Mesh *me)
+{
+  const bool do_verbose = true;
+  const bool do_fixes = false;
+
+  bool is_valid = true;
+  bool changed = true;
+
+  BKE_mesh_assert_normals_dirty_or_calculated(me);
+
+  is_valid &= BKE_mesh_validate_all_customdata(
+      &me->vdata,
+      me->totvert,
+      &me->edata,
+      me->totedge,
+      &me->ldata,
+      me->totloop,
+      &me->pdata,
+      me->totpoly,
+      false, /* setting mask here isn't useful, gives false positives */
+      do_verbose,
+      do_fixes,
+      &changed);
+
+  is_valid &= BKE_mesh_validate_arrays(me,
+                                       me->mvert,
+                                       me->totvert,
+                                       me->medge,
+                                       me->totedge,
+                                       me->mface,
+                                       me->totface,
+                                       me->mloop,
+                                       me->totloop,
+                                       me->mpoly,
+                                       me->totpoly,
+                                       me->dvert,
+                                       do_verbose,
+                                       do_fixes,
+                                       &changed);
+
+  BLI_assert(changed == false);
+
+  return is_valid;
+}
+
+bool BKE_mesh_validate_material_indices(Mesh *me)
+{
+  /* Cast to unsigned to catch negative indices too. */
+  const uint16_t mat_nr_max = max_ii(0, me->totcol - 1);
+  MPoly *mp;
+  const int totpoly = me->totpoly;
+  int i;
+  bool is_valid = true;
+
+  for (mp = me->mpoly, i = 0; i < totpoly; i++, mp++) {
+    if ((uint16_t)mp->mat_nr > mat_nr_max) {
+      mp->mat_nr = 0;
+      is_valid = false;
+    }
+  }
+
+  if (!is_valid) {
+    DEG_id_tag_update(&me->id, ID_RECALC_GEOMETRY_ALL_MODES);
+    return true;
+  }
+
+  return false;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Stripping (removing invalid data)
+ * \{ */
+
+void BKE_mesh_strip_loose_faces(Mesh *me)
+{
+  /* NOTE: We need to keep this for edge creation (for now?), and some old `readfile.c` code. */
+  MFace *f;
+  int a, b;
+
+  for (a = b = 0, f = me->mface; a < me->totface; a++, f++) {
+    if (f->v3) {
+      if (a != b) {
+        memcpy(&me->mface[b], f, sizeof(me->mface[b]));
+        CustomData_copy_data(&me->fdata, &me->fdata, a, b, 1);
+      }
+      b++;
+    }
+  }
+  if (a != b) {
+    CustomData_free_elem(&me->fdata, b, a - b);
+    me->totface = b;
+  }
+}
+
+void BKE_mesh_strip_loose_polysloops(Mesh *me)
+{
+  MPoly *p;
+  MLoop *l;
+  int a, b;
+  /* New loops idx! */
+  int *new_idx = (int *)MEM_mallocN(sizeof(int) * me->totloop, __func__);
+
+  for (a = b = 0, p = me->mpoly; a < me->totpoly; a++, p++) {
+    bool invalid = false;
+    int i = p->loopstart;
+    int stop = i + p->totloop;
+
+    if (stop > me->totloop || stop < i || p->loopstart < 0) {
+      invalid = true;
+    }
+    else {
+      l = &me->mloop[i];
+      i = stop - i;
+      /* If one of the poly's loops is invalid, the whole poly is invalid! */
+      for (; i--; l++) {
+        if (l->e == INVALID_LOOP_EDGE_MARKER) {
+          invalid = true;
+          break;
+        }
+      }
+    }
+
+    if (p->totloop >= 3 && !invalid) {
+      if (a != b) {
+        memcpy(&me->mpoly[b], p, sizeof(me->mpoly[b]));
+        CustomData_copy_data(&me->pdata, &me->pdata, a, b, 1);
+      }
+      b++;
+    }
+  }
+  if (a != b) {
+    CustomData_free_elem(&me->pdata, b, a - b);
+    me->totpoly = b;
+  }
+
+  /* And now, get rid of invalid loops. */
+  for (a = b = 0, l = me->mloop; a < me->totloop; a++, l++) {
+    if (l->e != INVALID_LOOP_EDGE_MARKER) {
+      if (a != b) {
+        memcpy(&me->mloop[b], l, sizeof(me->mloop[b]));
+        CustomData_copy_data(&me->ldata, &me->ldata, a, b, 1);
+      }
+      new_idx[a] = b;
+      b++;
+    }
+    else {
+      /* XXX Theoretically, we should be able to not do this, as no remaining poly
+       *     should use any stripped loop. But for security's sake... */
+      new_idx[a] = -a;
+    }
+  }
+  if (a != b) {
+    CustomData_free_elem(&me->ldata, b, a - b);
+    me->totloop = b;
+  }
+
+  /* And now, update polys' start loop index. */
+  /* NOTE: At this point, there should never be any poly using a striped loop! */
+  for (a = 0, p = me->mpoly; a < me->totpoly; a++, p++) {
+    p->loopstart = new_idx[p->loopstart];
+  }
+
+  MEM_freeN(new_idx);
+}
+
+void BKE_mesh_strip_loose_edges(Mesh *me)
+{
+  MEdge *e;
+  MLoop *l;
+  int a, b;
+  uint *new_idx = (uint *)MEM_mallocN(sizeof(int) * me->totedge, __func__);
+
+  for (a = b = 0, e = me->medge; a < me->totedge; a++, e++) {
+    if (e->v1 != e->v2) {
+      if (a != b) {
+        memcpy(&me->medge[b], e, sizeof(me->medge[b]));
+        CustomData_copy_data(&me->edata, &me->edata, a, b, 1);
+      }
+      new_idx[a] = b;
+      b++;
+    }
+    else {
+      new_idx[a] = INVALID_LOOP_EDGE_MARKER;
+    }
+  }
+  if (a != b) {
+    CustomData_free_elem(&me->edata, b, a - b);
+    me->totedge = b;
+  }
+
+  /* And now, update loops' edge indices. */
+  /* XXX We hope no loop was pointing to a striped edge!
+   *     Else, its e will be set to INVALID_LOOP_EDGE_MARKER :/ */
+  for (a = 0, l = me->mloop; a < me->totloop; a++, l++) {
+    l->e = new_idx[l->e];
+  }
+
+  MEM_freeN(new_idx);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Edge Calculation
+ * \{ */
+
+/* make edges in a Mesh, for outside of editmode */
+
+struct EdgeSort {
+  uint v1, v2;
+  char is_loose, is_draw;
+};
+
+/* edges have to be added with lowest index first for sorting */
+static void to_edgesort(struct EdgeSort *ed, uint v1, uint v2, char is_loose, short is_draw)
+{
+  if (v1 < v2) {
+    ed->v1 = v1;
+    ed->v2 = v2;
+  }
+  else {
+    ed->v1 = v2;
+    ed->v2 = v1;
+  }
+  ed->is_loose = is_loose;
+  ed->is_draw = is_draw;
+}
+
+static int vergedgesort(const void *v1, const void *v2)
+{
+  const struct EdgeSort *x1 = static_cast(v1);
+  const struct EdgeSort *x2 = static_cast(v2);
+
+  if (x1->v1 > x2->v1) {
+    return 1;
+  }
+  if (x1->v1 < x2->v1) {
+    return -1;
+  }
+  if (x1->v2 > x2->v2) {
+    return 1;
+  }
+  if (x1->v2 < x2->v2) {
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Create edges based on known verts and faces,
+ * this function is only used when loading very old blend files */
+
+static void mesh_calc_edges_mdata(MVert *UNUSED(allvert),
+                                  MFace *allface,
+                                  MLoop *allloop,
+                                  MPoly *allpoly,
+                                  int UNUSED(totvert),
+                                  int totface,
+                                  int UNUSED(totloop),
+                                  int totpoly,
+                                  const bool use_old,
+                                  MEdge **r_medge,
+                                  int *r_totedge)
+{
+  MPoly *mpoly;
+  MFace *mface;
+  MEdge *medge, *med;
+  EdgeHash *hash;
+  struct EdgeSort *edsort, *ed;
+  int a, totedge = 0;
+  uint totedge_final = 0;
+  uint edge_index;
+
+  /* we put all edges in array, sort them, and detect doubles that way */
+
+  for (a = totface, mface = allface; a > 0; a--, mface++) {
+    if (mface->v4) {
+      totedge += 4;
+    }
+    else if (mface->v3) {
+      totedge += 3;
+    }
+    else {
+      totedge += 1;
+    }
+  }
+
+  if (totedge == 0) {
+    /* flag that mesh has edges */
+    (*r_medge) = (MEdge *)MEM_callocN(0, __func__);
+    (*r_totedge) = 0;
+    return;
+  }
+
+  ed = edsort = (EdgeSort *)MEM_mallocN(totedge * sizeof(struct EdgeSort), "EdgeSort");
+
+  for (a = totface, mface = allface; a > 0; a--, mface++) {
+    to_edgesort(ed++, mface->v1, mface->v2, !mface->v3, mface->edcode & ME_V1V2);
+    if (mface->v4) {
+      to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
+      to_edgesort(ed++, mface->v3, mface->v4, 0, mface->edcode & ME_V3V4);
+      to_edgesort(ed++, mface->v4, mface->v1, 0, mface->edcode & ME_V4V1);
+    }
+    else if (mface->v3) {
+      to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
+      to_edgesort(ed++, mface->v3, mface->v1, 0, mface->edcode & ME_V3V1);
+    }
+  }
+
+  qsort(edsort, totedge, sizeof(struct EdgeSort), vergedgesort);
+
+  /* count final amount */
+  for (a = totedge, ed = edsort; a > 1; a--, ed++) {
+    /* edge is unique when it differs from next edge, or is last */
+    if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
+      totedge_final++;
+    }
+  }
+  totedge_final++;
+
+  medge = (MEdge *)MEM_callocN(sizeof(MEdge) * totedge_final, __func__);
+
+  for (a = totedge, med = medge, ed = edsort; a > 1; a--, ed++) {
+    /* edge is unique when it differs from next edge, or is last */
+    if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
+      med->v1 = ed->v1;
+      med->v2 = ed->v2;
+      if (use_old == false || ed->is_draw) {
+        med->flag = ME_EDGEDRAW | ME_EDGERENDER;
+      }
+      if (ed->is_loose) {
+        med->flag |= ME_LOOSEEDGE;
+      }
+
+      /* order is swapped so extruding this edge as a surface won't flip face normals
+       * with cyclic curves */
+      if (ed->v1 + 1 != ed->v2) {
+        SWAP(uint, med->v1, med->v2);
+      }
+      med++;
+    }
+    else {
+      /* equal edge, we merge the drawflag */
+      (ed + 1)->is_draw |= ed->is_draw;
+    }
+  }
+  /* last edge */
+  med->v1 = ed->v1;
+  med->v2 = ed->v2;
+  med->flag = ME_EDGEDRAW;
+  if (ed->is_loose) {
+    med->flag |= ME_LOOSEEDGE;
+  }
+  med->flag |= ME_EDGERENDER;
+
+  MEM_freeN(edsort);
+
+  /* set edge members of mloops */
+  hash = BLI_edgehash_new_ex(__func__, totedge_final);
+  for (edge_index = 0, med = medge; edge_index < totedge_final; edge_index++, med++) {
+    BLI_edgehash_insert(hash, med->v1, med->v2, POINTER_FROM_UINT(edge_index));
+  }
+
+  mpoly = allpoly;
+  for (a = 0; a < totpoly; a++, mpoly++) {
+    MLoop *ml, *ml_next;
+    int i = mpoly->totloop;
+
+    ml_next = allloop + mpoly->loopstart; /* first loop */
+    ml = &ml_next[i - 1];                 /* last loop */
+
+    while (i-- != 0) {
+      ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(hash, ml->v, ml_next->v));
+      ml = ml_next;
+      ml_next++;
+    }
+  }
+
+  BLI_edgehash_free(hash, nullptr);
+
+  *r_medge = medge;
+  *r_totedge = totedge_final;
+}
+
+void BKE_mesh_calc_edges_legacy(Mesh *me, const bool use_old)
+{
+  MEdge *medge;
+  int totedge = 0;
+
+  mesh_calc_edges_mdata(me->mvert,
+                        me->mface,
+                        me->mloop,
+                        me->mpoly,
+                        me->totvert,
+                        me->totface,
+                        me->totloop,
+                        me->totpoly,
+                        use_old,
+                        &medge,
+                        &totedge);
+
+  if (totedge == 0) {
+    /* flag that mesh has edges */
+    me->medge = medge;
+    me->totedge = 0;
+    return;
+  }
+
+  medge = (MEdge *)CustomData_add_layer(&me->edata, CD_MEDGE, CD_ASSIGN, medge, totedge);
+  me->medge = medge;
+  me->totedge = totedge;
+
+  BKE_mesh_strip_loose_faces(me);
+}
+
+void BKE_mesh_calc_edges_loose(Mesh *mesh)
+{
+  MEdge *med = mesh->medge;
+  for (int i = 0; i < mesh->totedge; i++, med++) {
+    med->flag |= ME_LOOSEEDGE;
+  }
+  MLoop *ml = mesh->mloop;
+  for (int i = 0; i < mesh->totloop; i++, ml++) {
+    mesh->medge[ml->e].flag &= ~ME_LOOSEEDGE;
+  }
+  med = mesh->medge;
+  for (int i = 0; i < mesh->totedge; i++, med++) {
+    if (med->flag & ME_LOOSEEDGE) {
+      med->flag |= ME_EDGEDRAW;
+    }
+  }
+}
+
+void BKE_mesh_calc_edges_tessface(Mesh *mesh)
+{
+  const int numFaces = mesh->totface;
+  EdgeSet *eh = BLI_edgeset_new_ex(__func__, BLI_EDGEHASH_SIZE_GUESS_FROM_POLYS(numFaces));
+
+  MFace *mf = mesh->mface;
+  for (int i = 0; i < numFaces; i++, mf++) {
+    BLI_edgeset_add(eh, mf->v1, mf->v2);
+    BLI_edgeset_add(eh, mf->v2, mf->v3);
+
+    if (mf->v4) {
+      BLI_edgeset_add(eh, mf->v3, mf->v4);
+      BLI_edgeset_add(eh, mf->v4, mf->v1);
+    }
+    else {
+      BLI_edgeset_add(eh, mf->v3, mf->v1);
+    }
+  }
+
+  const int numEdges = BLI_edgeset_len(eh);
+
+  /* write new edges into a temporary CustomData */
+  CustomData edgeData;
+  CustomData_reset(&edgeData);
+  CustomData_add_layer(&edgeData, CD_MEDGE, CD_CALLOC, nullptr, numEdges);
+  CustomData_add_layer(&edgeData, CD_ORIGINDEX, CD_CALLOC, nullptr, numEdges);
+
+  MEdge *med = (MEdge *)CustomData_get_layer(&edgeData, CD_MEDGE);
+  int *index = (int *)CustomData_get_layer(&edgeData, CD_ORIGINDEX);
+
+  EdgeSetIterator *ehi = BLI_edgesetIterator_new(eh);
+  for (int i = 0; BLI_edgesetIterator_isDone(ehi) == false;
+       BLI_edgesetIterator_step(ehi), i++, med++, index++) {
+    BLI_edgesetIterator_getKey(ehi, &med->v1, &med->v2);
+
+    med->flag = ME_EDGEDRAW | ME_EDGERENDER;
+    *index = ORIGINDEX_NONE;
+  }
+  BLI_edgesetIterator_free(ehi);
+
+  /* free old CustomData and assign new one */
+  CustomData_free(&mesh->edata, mesh->totedge);
+  mesh->edata = edgeData;
+  mesh->totedge = numEdges;
+
+  mesh->medge = (MEdge *)CustomData_get_layer(&mesh->edata, CD_MEDGE);
+
+  BLI_edgeset_free(eh);
+}
+
+/** \} */
-- 
cgit v1.2.3


From 81bb86c7506c0ae5f07780a63f954a8416a0d4e6 Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Mon, 28 Feb 2022 16:31:02 +0100
Subject: Cleanup: Remove duplicated comment

Something went wrong here in 61776befc3f8.
---
 .../editors/space_outliner/tree/tree_display_override_library.cc     | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/source/blender/editors/space_outliner/tree/tree_display_override_library.cc b/source/blender/editors/space_outliner/tree/tree_display_override_library.cc
index 43d67ee106d..f94727ba356 100644
--- a/source/blender/editors/space_outliner/tree/tree_display_override_library.cc
+++ b/source/blender/editors/space_outliner/tree/tree_display_override_library.cc
@@ -22,11 +22,6 @@
 
 namespace blender::ed::outliner {
 
-/* Convenience/readability. */
-/* Convenience/readability. */
-/* Convenience/readability. */
-/* Convenience/readability. */
-/* Convenience/readability. */
 /* Convenience/readability. */
 template using List = ListBaseWrapper;
 
-- 
cgit v1.2.3


From 37d2c774c1240014dca8933545a735091b156e5b Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Mon, 28 Feb 2022 16:42:15 +0100
Subject: Fix T96073: Don't remove links when inserting reroute nodes

This was an oversight in rB06ac5992618a75c453e495e06af7c5faf30499a7.
---
 .../editors/space_node/node_relationships.cc       | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc
index d92f86c2cfc..1ebbc71c177 100644
--- a/source/blender/editors/space_node/node_relationships.cc
+++ b/source/blender/editors/space_node/node_relationships.cc
@@ -2452,16 +2452,18 @@ void ED_node_link_insert(Main *bmain, ScrArea *area)
   bNodeSocket *best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN);
   bNodeSocket *best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT);
 
-  /* Ignore main sockets when the types don't match. */
-  if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr &&
-      !ntree.typeinfo->validate_link(static_cast(old_link->fromsock->type),
-                                     static_cast(best_input->type))) {
-    best_input = nullptr;
-  }
-  if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr &&
-      !ntree.typeinfo->validate_link(static_cast(best_output->type),
-                                     static_cast(old_link->tosock->type))) {
-    best_output = nullptr;
+  if (node_to_insert->type != NODE_REROUTE) {
+    /* Ignore main sockets when the types don't match. */
+    if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr &&
+        !ntree.typeinfo->validate_link(static_cast(old_link->fromsock->type),
+                                       static_cast(best_input->type))) {
+      best_input = nullptr;
+    }
+    if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr &&
+        !ntree.typeinfo->validate_link(static_cast(best_output->type),
+                                       static_cast(old_link->tosock->type))) {
+      best_output = nullptr;
+    }
   }
 
   bNode *from_node = old_link->fromnode;
-- 
cgit v1.2.3


From 9ec12c26f16ea3da1e6de95d5d5daf1057464830 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 10:46:34 -0500
Subject: Geometry Nodes: Begin conversion to new curves

This commit changes `CurveComponent` to store the new curve
type by adding conversions to and from `CurveEval` in most nodes.
This will temporarily make performance of curves in geometry nodes
much worse, but as functionality is implemented for the new type
and it is used in more places, performance will become better than
before.

We still use `CurveEval` for drawing curves, because the new `Curves`
data-block has no evaluated points yet. So the `Curve` ID is still
generated for rendering in the same way as before. It's also still
needed for drawing curve object edit mode overlays.

The old curve component isn't removed yet, because it is still used
to implement the conversions to and from `CurveEval`.

A few more attributes are added to make this possible:
- `nurbs_weight`: The weight for each control point on NURBS curves.
- `nurbs_order`: The order of the NURBS curve
- `knots_mode`: Necessary for conversion, not defined yet.
- `handle_type_{left/right}`: An 8 bit integer attribute.

Differential Revision: https://developer.blender.org/D14145
---
 source/blender/blenkernel/BKE_geometry_set.hh      |  73 ++-
 source/blender/blenkernel/BKE_spline.hh            |   3 +
 source/blender/blenkernel/CMakeLists.txt           |   1 +
 source/blender/blenkernel/intern/curve.cc          |   2 +
 source/blender/blenkernel/intern/curve_eval.cc     | 189 ++++++++
 source/blender/blenkernel/intern/displist.cc       |   4 +-
 .../blenkernel/intern/geometry_component_curve.cc  | 167 +------
 .../blenkernel/intern/geometry_component_curves.cc | 521 +++++++++++++++++++++
 source/blender/blenkernel/intern/geometry_set.cc   |  26 +-
 source/blender/blenkernel/intern/mesh_convert.cc   |   7 +-
 .../blender/geometry/intern/realize_instances.cc   |  39 +-
 .../legacy/node_geo_legacy_curve_endpoints.cc      |  10 +-
 .../nodes/legacy/node_geo_legacy_curve_reverse.cc  |   6 +-
 .../node_geo_legacy_curve_select_by_handle_type.cc |   5 +-
 .../legacy/node_geo_legacy_curve_set_handles.cc    |   6 +-
 .../legacy/node_geo_legacy_curve_spline_type.cc    |  16 +-
 .../legacy/node_geo_legacy_curve_subdivide.cc      |   6 +-
 .../legacy/node_geo_legacy_curve_to_points.cc      |  10 +-
 .../legacy/node_geo_legacy_delete_geometry.cc      |   4 +-
 .../nodes/legacy/node_geo_legacy_mesh_to_curve.cc  |   2 +-
 .../nodes/geometry/nodes/node_geo_convex_hull.cc   |  12 +-
 .../nodes/node_geo_curve_endpoint_selection.cc     |   7 +-
 .../nodes/geometry/nodes/node_geo_curve_fill.cc    |   7 +-
 .../nodes/geometry/nodes/node_geo_curve_fillet.cc  |   6 +-
 .../nodes/node_geo_curve_handle_type_selection.cc  |   4 +-
 .../nodes/geometry/nodes/node_geo_curve_length.cc  |   4 +-
 .../geometry/nodes/node_geo_curve_primitive_arc.cc |   4 +-
 .../node_geo_curve_primitive_bezier_segment.cc     |   2 +-
 .../nodes/node_geo_curve_primitive_circle.cc       |   2 +-
 .../nodes/node_geo_curve_primitive_line.cc         |   2 +-
 .../node_geo_curve_primitive_quadratic_bezier.cc   |   2 +-
 .../node_geo_curve_primitive_quadrilateral.cc      |   2 +-
 .../nodes/node_geo_curve_primitive_spiral.cc       |   2 +-
 .../nodes/node_geo_curve_primitive_star.cc         |   2 +-
 .../geometry/nodes/node_geo_curve_resample.cc      |   4 +-
 .../nodes/geometry/nodes/node_geo_curve_reverse.cc |   6 +-
 .../nodes/geometry/nodes/node_geo_curve_sample.cc  |   8 +-
 .../geometry/nodes/node_geo_curve_set_handles.cc   |   6 +-
 .../nodes/node_geo_curve_spline_parameter.cc       |  14 +-
 .../geometry/nodes/node_geo_curve_spline_type.cc   |  21 +-
 .../geometry/nodes/node_geo_curve_subdivide.cc     |   6 +-
 .../nodes/geometry/nodes/node_geo_curve_to_mesh.cc |   8 +-
 .../geometry/nodes/node_geo_curve_to_points.cc     |  13 +-
 .../nodes/geometry/nodes/node_geo_curve_trim.cc    |   6 +-
 .../geometry/nodes/node_geo_delete_geometry.cc     |   4 +-
 .../geometry/nodes/node_geo_duplicate_elements.cc  |  24 +-
 .../geometry/nodes/node_geo_input_spline_length.cc |   8 +-
 .../nodes/geometry/nodes/node_geo_input_tangent.cc |  15 +-
 .../nodes/geometry/nodes/node_geo_mesh_to_curve.cc |   2 +-
 .../geometry/nodes/node_geo_set_curve_handles.cc   |  15 +-
 .../nodes/node_geo_set_spline_resolution.cc        |   4 +-
 .../geometry/nodes/node_geo_string_to_curves.cc    |   3 +-
 .../nodes/geometry/nodes/node_geo_transform.cc     |   9 +-
 53 files changed, 1004 insertions(+), 327 deletions(-)
 create mode 100644 source/blender/blenkernel/intern/geometry_component_curves.cc

diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index d93b3ca95e7..f11bfb7692a 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -24,6 +24,7 @@
 
 #include "FN_field.hh"
 
+struct Curves;
 struct Collection;
 struct Curve;
 struct CurveEval;
@@ -415,7 +416,7 @@ struct GeometrySet {
    * Create a new geometry set that only contains the given curve.
    */
   static GeometrySet create_with_curve(
-      CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+      Curves *curves, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
 
   /* Utility methods for access. */
   /**
@@ -462,7 +463,7 @@ struct GeometrySet {
   /**
    * Returns a read-only curve or null.
    */
-  const CurveEval *get_curve_for_read() const;
+  const Curves *get_curve_for_read() const;
 
   /**
    * Returns a mutable mesh or null. No ownership is transferred.
@@ -479,7 +480,7 @@ struct GeometrySet {
   /**
    * Returns a mutable curve or null. No ownership is transferred.
    */
-  CurveEval *get_curve_for_write();
+  Curves *get_curve_for_write();
 
   /* Utility methods for replacement. */
   /**
@@ -499,7 +500,7 @@ struct GeometrySet {
   /**
    * Clear the existing curve and replace it with the given one.
    */
-  void replace_curve(CurveEval *curve,
+  void replace_curve(Curves *curves,
                      GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
 
  private:
@@ -632,17 +633,59 @@ class PointCloudComponent : public GeometryComponent {
 };
 
 /**
- * A geometry component that stores curve data, in other words, a group of splines.
- * Curves are stored differently than other geometry components, because the data structure used
- * here does not correspond exactly to the #Curve DNA data structure. A #CurveEval is stored here
- * instead, though the component does give access to a #Curve for interfacing with render engines
- * and other areas of Blender that expect to use a data-block with an #ID.
+ * Legacy runtime-only curves type.
+ * These curves are stored differently than other geometry components, because the data structure
+ * used here does not correspond exactly to the #Curve DNA data structure. A #CurveEval is stored
+ * here instead, though the component does give access to a #Curve for interfacing with render
+ * engines and other areas of Blender that expect to use a data-block with an #ID.
  */
-class CurveComponent : public GeometryComponent {
+class CurveComponentLegacy : public GeometryComponent {
  private:
   CurveEval *curve_ = nullptr;
   GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
 
+ public:
+  CurveComponentLegacy();
+  ~CurveComponentLegacy();
+  GeometryComponent *copy() const override;
+
+  void clear();
+  bool has_curve() const;
+  /**
+   * Clear the component and replace it with the new curve.
+   */
+  void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+  CurveEval *release();
+
+  const CurveEval *get_for_read() const;
+  CurveEval *get_for_write();
+
+  int attribute_domain_size(AttributeDomain domain) const final;
+
+  bool is_empty() const final;
+
+  bool owns_direct_data() const override;
+  void ensure_owns_direct_data() override;
+
+  static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE;
+
+ private:
+  const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
+
+  blender::fn::GVArray attribute_try_adapt_domain_impl(const blender::fn::GVArray &varray,
+                                                       AttributeDomain from_domain,
+                                                       AttributeDomain to_domain) const final;
+};
+
+/**
+ * A geometry component that stores a group of curves, corresponding the the #Curves and
+ * #CurvesGeometry types.
+ */
+class CurveComponent : public GeometryComponent {
+ private:
+  Curves *curves_ = nullptr;
+  GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
+
   /**
    * Curve data necessary to hold the draw cache for rendering, consistent over multiple redraws.
    * This is necessary because Blender assumes that objects evaluate to an object data type, and
@@ -658,15 +701,15 @@ class CurveComponent : public GeometryComponent {
   GeometryComponent *copy() const override;
 
   void clear();
-  bool has_curve() const;
+  bool has_curves() const;
   /**
    * Clear the component and replace it with the new curve.
    */
-  void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
-  CurveEval *release();
+  void replace(Curves *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+  Curves *release();
 
-  const CurveEval *get_for_read() const;
-  CurveEval *get_for_write();
+  const Curves *get_for_read() const;
+  Curves *get_for_write();
 
   int attribute_domain_size(AttributeDomain domain) const final;
 
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 439f20ee471..42b4702ee44 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -20,6 +20,7 @@
 #include "BKE_attribute_math.hh"
 
 struct Curve;
+struct Curves;
 struct ListBase;
 
 class Spline;
@@ -691,3 +692,5 @@ struct CurveEval {
 std::unique_ptr curve_eval_from_dna_curve(const Curve &curve,
                                                      const ListBase &nurbs_list);
 std::unique_ptr curve_eval_from_dna_curve(const Curve &dna_curve);
+std::unique_ptr curves_to_curve_eval(const Curves &curves);
+Curves *curve_eval_to_curves(const CurveEval &curve_eval);
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 2b77b2c5dae..a12a956cbf5 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -131,6 +131,7 @@ set(SRC
   intern/fmodifier.c
   intern/freestyle.c
   intern/geometry_component_curve.cc
+  intern/geometry_component_curves.cc
   intern/geometry_component_instances.cc
   intern/geometry_component_mesh.cc
   intern/geometry_component_pointcloud.cc
diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc
index b0f58dd4ec9..6be04b79761 100644
--- a/source/blender/blenkernel/intern/curve.cc
+++ b/source/blender/blenkernel/intern/curve.cc
@@ -115,6 +115,8 @@ static void curve_free_data(ID *id)
   MEM_SAFE_FREE(curve->str);
   MEM_SAFE_FREE(curve->strinfo);
   MEM_SAFE_FREE(curve->tb);
+
+  delete curve->curve_eval;
 }
 
 static void curve_foreach_id(ID *id, LibraryForeachIDData *data)
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 8529e7ad194..78dafe34b4f 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -13,6 +13,8 @@
 
 #include "BKE_anonymous_attribute.hh"
 #include "BKE_curve.h"
+#include "BKE_curves.hh"
+#include "BKE_geometry_set.hh"
 #include "BKE_spline.hh"
 
 using blender::Array;
@@ -23,8 +25,15 @@ using blender::Map;
 using blender::MutableSpan;
 using blender::Span;
 using blender::StringRefNull;
+using blender::VArray;
+using blender::VArray_Span;
 using blender::Vector;
 using blender::bke::AttributeIDRef;
+using blender::bke::OutputAttribute;
+using blender::bke::OutputAttribute_Typed;
+using blender::bke::ReadAttributeLookup;
+using blender::fn::GVArray;
+using blender::fn::GVArray_GSpan;
 
 blender::Span CurveEval::splines() const
 {
@@ -336,6 +345,186 @@ std::unique_ptr curve_eval_from_dna_curve(const Curve &dna_curve)
   return curve_eval_from_dna_curve(dna_curve, *BKE_curve_nurbs_get_for_read(&dna_curve));
 }
 
+static void copy_attributes_between_components(const GeometryComponent &src_component,
+                                               GeometryComponent &dst_component,
+                                               Span skip)
+{
+  src_component.attribute_foreach(
+      [&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
+        if (id.is_named() && skip.contains(id.name())) {
+          return true;
+        }
+
+        GVArray src_attribute = src_component.attribute_try_get_for_read(
+            id, meta_data.domain, meta_data.data_type);
+        if (!src_attribute) {
+          return true;
+        }
+        GVArray_GSpan src_attribute_data{src_attribute};
+
+        OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+            id, meta_data.domain, meta_data.data_type);
+        if (!dst_attribute) {
+          return true;
+        }
+        dst_attribute.varray().set_all(src_attribute_data.data());
+        dst_attribute.save();
+        return true;
+      });
+}
+
+std::unique_ptr curves_to_curve_eval(const Curves &curves)
+{
+  CurveComponent src_component;
+  src_component.replace(&const_cast(curves), GeometryOwnershipType::ReadOnly);
+  const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(
+      curves.geometry);
+
+  VArray_Span nurbs_weights{
+      src_component.attribute_get_for_read("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)};
+  VArray_Span nurbs_orders{
+      src_component.attribute_get_for_read("nurbs_order", ATTR_DOMAIN_CURVE, 4)};
+  VArray_Span nurbs_knots_modes{
+      src_component.attribute_get_for_read("knots_mode", ATTR_DOMAIN_CURVE, 0)};
+
+  VArray_Span handle_types_right{
+      src_component.attribute_get_for_read("handle_type_right", ATTR_DOMAIN_POINT, 0)};
+  VArray_Span handle_types_left{
+      src_component.attribute_get_for_read("handle_type_left", ATTR_DOMAIN_POINT, 0)};
+
+  /* Create splines with the correct size and type. */
+  VArray curve_types = geometry.curve_types();
+  std::unique_ptr curve_eval = std::make_unique();
+  for (const int curve_index : curve_types.index_range()) {
+    const IndexRange point_range = geometry.range_for_curve(curve_index);
+
+    std::unique_ptr spline;
+    switch (curve_types[curve_index]) {
+      case CURVE_TYPE_POLY: {
+        spline = std::make_unique();
+        spline->resize(point_range.size());
+        break;
+      }
+      case CURVE_TYPE_BEZIER: {
+        std::unique_ptr bezier_spline = std::make_unique();
+        bezier_spline->resize(point_range.size());
+        bezier_spline->handle_types_left().copy_from(handle_types_left.slice(point_range));
+        bezier_spline->handle_types_right().copy_from(handle_types_right.slice(point_range));
+
+        spline = std::move(bezier_spline);
+        break;
+      }
+      case CURVE_TYPE_NURBS: {
+        std::unique_ptr nurb_spline = std::make_unique();
+        nurb_spline->resize(point_range.size());
+        nurb_spline->weights().copy_from(nurbs_weights.slice(point_range));
+        nurb_spline->set_order(nurbs_orders[curve_index]);
+        nurb_spline->knots_mode = static_cast(
+            nurbs_knots_modes[curve_index]);
+
+        spline = std::move(nurb_spline);
+        break;
+      }
+      case CURVE_TYPE_CATMULL_ROM:
+        /* Not supported yet. */
+        BLI_assert_unreachable();
+        continue;
+    }
+    spline->positions().fill(float3(0));
+    spline->tilts().fill(0.0f);
+    spline->radii().fill(0.0f);
+    curve_eval->add_spline(std::move(spline));
+  }
+
+  CurveComponentLegacy dst_component;
+  dst_component.replace(curve_eval.get(), GeometryOwnershipType::Editable);
+
+  copy_attributes_between_components(src_component,
+                                     dst_component,
+                                     {"curve_type",
+                                      "nurbs_weight",
+                                      "nurbs_order",
+                                      "knots_mode",
+                                      "handle_type_right",
+                                      "handle_type_left"});
+
+  return curve_eval;
+}
+
+Curves *curve_eval_to_curves(const CurveEval &curve_eval)
+{
+  Curves *curves = blender::bke::curves_new_nomain(curve_eval.total_control_point_size(),
+                                                   curve_eval.splines().size());
+  CurveComponent dst_component;
+  dst_component.replace(curves, GeometryOwnershipType::Editable);
+
+  blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry);
+  geometry.offsets().copy_from(curve_eval.control_point_offsets());
+  MutableSpan curve_types = geometry.curve_types();
+
+  OutputAttribute_Typed nurbs_weight;
+  OutputAttribute_Typed nurbs_order;
+  OutputAttribute_Typed nurbs_knots_mode;
+  if (curve_eval.has_spline_with_type(CURVE_TYPE_NURBS)) {
+    nurbs_weight = dst_component.attribute_try_get_for_output_only("nurbs_weight",
+                                                                          ATTR_DOMAIN_POINT);
+    nurbs_order = dst_component.attribute_try_get_for_output_only("nurbs_order",
+                                                                       ATTR_DOMAIN_CURVE);
+    nurbs_knots_mode = dst_component.attribute_try_get_for_output_only("knots_mode",
+                                                                               ATTR_DOMAIN_CURVE);
+  }
+  OutputAttribute_Typed handle_type_right;
+  OutputAttribute_Typed handle_type_left;
+  if (curve_eval.has_spline_with_type(CURVE_TYPE_BEZIER)) {
+    handle_type_right = dst_component.attribute_try_get_for_output_only(
+        "handle_type_right", ATTR_DOMAIN_POINT);
+    handle_type_left = dst_component.attribute_try_get_for_output_only("handle_type_left",
+                                                                               ATTR_DOMAIN_POINT);
+  }
+
+  for (const int curve_index : curve_eval.splines().index_range()) {
+    const Spline &spline = *curve_eval.splines()[curve_index];
+    curve_types[curve_index] = curve_eval.splines()[curve_index]->type();
+
+    const IndexRange point_range = geometry.range_for_curve(curve_index);
+
+    switch (spline.type()) {
+      case CURVE_TYPE_POLY:
+        break;
+      case CURVE_TYPE_BEZIER: {
+        const BezierSpline &src = static_cast(spline);
+        handle_type_right.as_span().slice(point_range).copy_from(src.handle_types_right());
+        handle_type_left.as_span().slice(point_range).copy_from(src.handle_types_left());
+        break;
+      }
+      case CURVE_TYPE_NURBS: {
+        const NURBSpline &src = static_cast(spline);
+        nurbs_knots_mode.as_span()[curve_index] = static_cast(src.knots_mode);
+        nurbs_order.as_span()[curve_index] = src.order();
+        nurbs_weight.as_span().slice(point_range).copy_from(src.weights());
+        break;
+      }
+      case CURVE_TYPE_CATMULL_ROM: {
+        BLI_assert_unreachable();
+        break;
+      }
+    }
+  }
+
+  nurbs_weight.save();
+  nurbs_order.save();
+  nurbs_knots_mode.save();
+  handle_type_right.save();
+  handle_type_left.save();
+
+  CurveComponentLegacy src_component;
+  src_component.replace(&const_cast(curve_eval), GeometryOwnershipType::ReadOnly);
+
+  copy_attributes_between_components(src_component, dst_component, {});
+
+  return curves;
+}
+
 void CurveEval::assert_valid_point_attributes() const
 {
 #ifdef DEBUG
diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc
index 5c761e94bb9..97445851ffa 100644
--- a/source/blender/blenkernel/intern/displist.cc
+++ b/source/blender/blenkernel/intern/displist.cc
@@ -865,7 +865,7 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph,
   else {
     std::unique_ptr curve_eval = curve_eval_from_dna_curve(
         *cu, ob->runtime.curve_cache->deformed_nurbs);
-    geometry_set.replace_curve(curve_eval.release());
+    geometry_set.replace_curve(curve_eval_to_curves(*curve_eval));
   }
 
   for (; md; md = md->next) {
@@ -1497,7 +1497,7 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
        * 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();
-      cow_curve.curve_eval = curve_component.get_for_write();
+      cow_curve.curve_eval = curves_to_curve_eval(*curve_component.get_for_read()).release();
       BKE_object_eval_assign_data(ob, &cow_curve.id, false);
     }
 
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index c22b6ff07ec..0926d65b306 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -23,18 +23,18 @@ using blender::fn::GVArray_GSpan;
 /** \name Geometry Component Implementation
  * \{ */
 
-CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
+CurveComponentLegacy::CurveComponentLegacy() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
 {
 }
 
-CurveComponent::~CurveComponent()
+CurveComponentLegacy::~CurveComponentLegacy()
 {
   this->clear();
 }
 
-GeometryComponent *CurveComponent::copy() const
+GeometryComponent *CurveComponentLegacy::copy() const
 {
-  CurveComponent *new_component = new CurveComponent();
+  CurveComponentLegacy *new_component = new CurveComponentLegacy();
   if (curve_ != nullptr) {
     new_component->curve_ = new CurveEval(*curve_);
     new_component->ownership_ = GeometryOwnershipType::Owned;
@@ -42,30 +42,23 @@ GeometryComponent *CurveComponent::copy() const
   return new_component;
 }
 
-void CurveComponent::clear()
+void CurveComponentLegacy::clear()
 {
   BLI_assert(this->is_mutable());
   if (curve_ != nullptr) {
     if (ownership_ == GeometryOwnershipType::Owned) {
       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;
-    }
-
     curve_ = nullptr;
   }
 }
 
-bool CurveComponent::has_curve() const
+bool CurveComponentLegacy::has_curve() const
 {
   return curve_ != nullptr;
 }
 
-void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership)
+void CurveComponentLegacy::replace(CurveEval *curve, GeometryOwnershipType ownership)
 {
   BLI_assert(this->is_mutable());
   this->clear();
@@ -73,7 +66,7 @@ void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership)
   ownership_ = ownership;
 }
 
-CurveEval *CurveComponent::release()
+CurveEval *CurveComponentLegacy::release()
 {
   BLI_assert(this->is_mutable());
   CurveEval *curve = curve_;
@@ -81,12 +74,12 @@ CurveEval *CurveComponent::release()
   return curve;
 }
 
-const CurveEval *CurveComponent::get_for_read() const
+const CurveEval *CurveComponentLegacy::get_for_read() const
 {
   return curve_;
 }
 
-CurveEval *CurveComponent::get_for_write()
+CurveEval *CurveComponentLegacy::get_for_write()
 {
   BLI_assert(this->is_mutable());
   if (ownership_ == GeometryOwnershipType::ReadOnly) {
@@ -96,17 +89,17 @@ CurveEval *CurveComponent::get_for_write()
   return curve_;
 }
 
-bool CurveComponent::is_empty() const
+bool CurveComponentLegacy::is_empty() const
 {
   return curve_ == nullptr;
 }
 
-bool CurveComponent::owns_direct_data() const
+bool CurveComponentLegacy::owns_direct_data() const
 {
   return ownership_ == GeometryOwnershipType::Owned;
 }
 
-void CurveComponent::ensure_owns_direct_data()
+void CurveComponentLegacy::ensure_owns_direct_data()
 {
   BLI_assert(this->is_mutable());
   if (ownership_ != GeometryOwnershipType::Owned) {
@@ -115,32 +108,13 @@ void CurveComponent::ensure_owns_direct_data()
   }
 }
 
-const Curve *CurveComponent::get_curve_for_render() const
-{
-  if (curve_ == nullptr) {
-    return nullptr;
-  }
-  if (curve_for_render_ != nullptr) {
-    return curve_for_render_;
-  }
-  std::lock_guard lock{curve_for_render_mutex_};
-  if (curve_for_render_ != nullptr) {
-    return curve_for_render_;
-  }
-
-  curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU_LEGACY, nullptr);
-  curve_for_render_->curve_eval = curve_;
-
-  return curve_for_render_;
-}
-
 /** \} */
 
 /* -------------------------------------------------------------------- */
 /** \name Attribute Access Helper Functions
  * \{ */
 
-int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
+int CurveComponentLegacy::attribute_domain_size(const AttributeDomain domain) const
 {
   if (curve_ == nullptr) {
     return 0;
@@ -334,9 +308,10 @@ static GVArray adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArra
 
 }  // namespace blender::bke
 
-GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray,
-                                                        const AttributeDomain from_domain,
-                                                        const AttributeDomain to_domain) const
+GVArray CurveComponentLegacy::attribute_try_adapt_domain_impl(
+    const GVArray &varray,
+    const AttributeDomain from_domain,
+    const AttributeDomain to_domain) const
 {
   if (!varray) {
     return {};
@@ -361,14 +336,15 @@ GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray,
 static CurveEval *get_curve_from_component_for_write(GeometryComponent &component)
 {
   BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
-  CurveComponent &curve_component = static_cast(component);
+  CurveComponentLegacy &curve_component = static_cast(component);
   return curve_component.get_for_write();
 }
 
 static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component)
 {
   BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
-  const CurveComponent &curve_component = static_cast(component);
+  const CurveComponentLegacy &curve_component = static_cast(
+      component);
   return curve_component.get_for_read();
 }
 
@@ -376,101 +352,6 @@ static const CurveEval *get_curve_from_component_for_read(const GeometryComponen
 
 namespace blender::bke {
 
-/* -------------------------------------------------------------------- */
-/** \name Curve Normals Access
- * \{ */
-
-static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan normals)
-{
-  Span offsets = spline.control_point_offsets();
-  Span evaluated_normals = spline.evaluated_normals();
-  for (const int i : IndexRange(spline.size())) {
-    normals[i] = evaluated_normals[offsets[i]];
-  }
-}
-
-static void calculate_poly_normals(const PolySpline &spline, MutableSpan normals)
-{
-  normals.copy_from(spline.evaluated_normals());
-}
-
-/**
- * Because NURBS control points are not necessarily on the path, the normal at the control points
- * is not well defined, so create a temporary poly spline to find the normals. This requires extra
- * copying currently, but may be more efficient in the future if attributes have some form of CoW.
- */
-static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan normals)
-{
-  PolySpline poly_spline;
-  poly_spline.resize(spline.size());
-  poly_spline.positions().copy_from(spline.positions());
-  poly_spline.tilts().copy_from(spline.tilts());
-  normals.copy_from(poly_spline.evaluated_normals());
-}
-
-static Array curve_normal_point_domain(const CurveEval &curve)
-{
-  Span splines = curve.splines();
-  Array offsets = curve.control_point_offsets();
-  const int total_size = offsets.last();
-  Array normals(total_size);
-
-  threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
-    for (const int i : range) {
-      const Spline &spline = *splines[i];
-      MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
-      switch (splines[i]->type()) {
-        case CURVE_TYPE_BEZIER:
-          calculate_bezier_normals(static_cast(spline), spline_normals);
-          break;
-        case CURVE_TYPE_POLY:
-          calculate_poly_normals(static_cast(spline), spline_normals);
-          break;
-        case CURVE_TYPE_NURBS:
-          calculate_nurbs_normals(static_cast(spline), spline_normals);
-          break;
-        case CURVE_TYPE_CATMULL_ROM:
-          BLI_assert_unreachable();
-          break;
-      }
-    }
-  });
-  return normals;
-}
-
-VArray curve_normals_varray(const CurveComponent &component, const AttributeDomain domain)
-{
-  const CurveEval *curve = component.get_for_read();
-  if (curve == nullptr) {
-    return nullptr;
-  }
-
-  if (domain == ATTR_DOMAIN_POINT) {
-    const Span splines = curve->splines();
-
-    /* Use a reference to evaluated normals if possible to avoid an allocation and a copy.
-     * This is only possible when there is only one poly spline. */
-    if (splines.size() == 1 && splines.first()->type() == CURVE_TYPE_POLY) {
-      const PolySpline &spline = static_cast(*splines.first());
-      return VArray::ForSpan(spline.evaluated_normals());
-    }
-
-    Array normals = curve_normal_point_domain(*curve);
-    return VArray::ForContainer(std::move(normals));
-  }
-
-  if (domain == ATTR_DOMAIN_CURVE) {
-    Array point_normals = curve_normal_point_domain(*curve);
-    VArray varray = VArray::ForContainer(std::move(point_normals));
-    return component.attribute_try_adapt_domain(
-        std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
-  }
-
-  return nullptr;
-}
-
-/** \} */
-
 /* -------------------------------------------------------------------- */
 /** \name Builtin Spline Attributes
  *
@@ -1306,7 +1187,8 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
  private:
   static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 |
                                                    CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 |
-                                                   CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL;
+                                                   CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL |
+                                                   CD_MASK_PROP_INT8;
 
  public:
   ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
@@ -1551,7 +1433,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
 
 }  // namespace blender::bke
 
-const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const
+const blender::bke::ComponentAttributeProviders *CurveComponentLegacy::get_attribute_providers()
+    const
 {
   static blender::bke::ComponentAttributeProviders providers =
       blender::bke::create_attribute_providers_for_curve();
diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc
new file mode 100644
index 00000000000..e32dd852e0e
--- /dev/null
+++ b/source/blender/blenkernel/intern/geometry_component_curves.cc
@@ -0,0 +1,521 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_task.hh"
+
+#include "DNA_ID_enums.h"
+#include "DNA_curve_types.h"
+
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
+#include "BKE_curve.h"
+#include "BKE_curves.hh"
+#include "BKE_geometry_set.hh"
+#include "BKE_lib_id.h"
+#include "BKE_spline.hh"
+
+#include "attribute_access_intern.hh"
+
+using blender::fn::GVArray;
+
+/* -------------------------------------------------------------------- */
+/** \name Geometry Component Implementation
+ * \{ */
+
+CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
+{
+}
+
+CurveComponent::~CurveComponent()
+{
+  this->clear();
+}
+
+GeometryComponent *CurveComponent::copy() const
+{
+  CurveComponent *new_component = new CurveComponent();
+  if (curves_ != nullptr) {
+    new_component->curves_ = BKE_curves_copy_for_eval(curves_, false);
+    new_component->ownership_ = GeometryOwnershipType::Owned;
+  }
+  return new_component;
+}
+
+void CurveComponent::clear()
+{
+  BLI_assert(this->is_mutable());
+  if (curves_ != nullptr) {
+    if (ownership_ == GeometryOwnershipType::Owned) {
+      BKE_id_free(nullptr, curves_);
+    }
+    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;
+    }
+
+    curves_ = nullptr;
+  }
+}
+
+bool CurveComponent::has_curves() const
+{
+  return curves_ != nullptr;
+}
+
+void CurveComponent::replace(Curves *curves, GeometryOwnershipType ownership)
+{
+  BLI_assert(this->is_mutable());
+  this->clear();
+  curves_ = curves;
+  ownership_ = ownership;
+}
+
+Curves *CurveComponent::release()
+{
+  BLI_assert(this->is_mutable());
+  Curves *curves = curves_;
+  curves_ = nullptr;
+  return curves;
+}
+
+const Curves *CurveComponent::get_for_read() const
+{
+  return curves_;
+}
+
+Curves *CurveComponent::get_for_write()
+{
+  BLI_assert(this->is_mutable());
+  if (ownership_ == GeometryOwnershipType::ReadOnly) {
+    curves_ = BKE_curves_copy_for_eval(curves_, false);
+    ownership_ = GeometryOwnershipType::Owned;
+  }
+  return curves_;
+}
+
+bool CurveComponent::is_empty() const
+{
+  return curves_ == nullptr;
+}
+
+bool CurveComponent::owns_direct_data() const
+{
+  return ownership_ == GeometryOwnershipType::Owned;
+}
+
+void CurveComponent::ensure_owns_direct_data()
+{
+  BLI_assert(this->is_mutable());
+  if (ownership_ != GeometryOwnershipType::Owned) {
+    curves_ = BKE_curves_copy_for_eval(curves_, false);
+    ownership_ = GeometryOwnershipType::Owned;
+  }
+}
+
+const Curve *CurveComponent::get_curve_for_render() const
+{
+  if (curves_ == nullptr) {
+    return nullptr;
+  }
+  if (curve_for_render_ != nullptr) {
+    return curve_for_render_;
+  }
+  std::lock_guard lock{curve_for_render_mutex_};
+  if (curve_for_render_ != nullptr) {
+    return curve_for_render_;
+  }
+
+  curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU_LEGACY, nullptr);
+  curve_for_render_->curve_eval = curves_to_curve_eval(*curves_).release();
+
+  return curve_for_render_;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Curve Normals Access
+ * \{ */
+
+namespace blender::bke {
+
+static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan normals)
+{
+  Span offsets = spline.control_point_offsets();
+  Span evaluated_normals = spline.evaluated_normals();
+  for (const int i : IndexRange(spline.size())) {
+    normals[i] = evaluated_normals[offsets[i]];
+  }
+}
+
+static void calculate_poly_normals(const PolySpline &spline, MutableSpan normals)
+{
+  normals.copy_from(spline.evaluated_normals());
+}
+
+/**
+ * Because NURBS control points are not necessarily on the path, the normal at the control points
+ * is not well defined, so create a temporary poly spline to find the normals. This requires extra
+ * copying currently, but may be more efficient in the future if attributes have some form of CoW.
+ */
+static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan normals)
+{
+  PolySpline poly_spline;
+  poly_spline.resize(spline.size());
+  poly_spline.positions().copy_from(spline.positions());
+  poly_spline.tilts().copy_from(spline.tilts());
+  normals.copy_from(poly_spline.evaluated_normals());
+}
+
+static Array curve_normal_point_domain(const CurveEval &curve)
+{
+  Span splines = curve.splines();
+  Array offsets = curve.control_point_offsets();
+  const int total_size = offsets.last();
+  Array normals(total_size);
+
+  threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
+    for (const int i : range) {
+      const Spline &spline = *splines[i];
+      MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
+      switch (splines[i]->type()) {
+        case CURVE_TYPE_BEZIER:
+          calculate_bezier_normals(static_cast(spline), spline_normals);
+          break;
+        case CURVE_TYPE_POLY:
+          calculate_poly_normals(static_cast(spline), spline_normals);
+          break;
+        case CURVE_TYPE_NURBS:
+          calculate_nurbs_normals(static_cast(spline), spline_normals);
+          break;
+        case CURVE_TYPE_CATMULL_ROM:
+          BLI_assert_unreachable();
+          break;
+      }
+    }
+  });
+  return normals;
+}
+
+VArray curve_normals_varray(const CurveComponent &component, const AttributeDomain domain)
+{
+  if (component.is_empty()) {
+    return nullptr;
+  }
+  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
+
+  if (domain == ATTR_DOMAIN_POINT) {
+    Array normals = curve_normal_point_domain(*curve);
+    return VArray::ForContainer(std::move(normals));
+  }
+
+  if (domain == ATTR_DOMAIN_CURVE) {
+    Array point_normals = curve_normal_point_domain(*curve);
+    VArray varray = VArray::ForContainer(std::move(point_normals));
+    return component.attribute_try_adapt_domain(
+        std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+  }
+
+  return nullptr;
+}
+
+}  // namespace blender::bke
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Attribute Access Helper Functions
+ * \{ */
+
+int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
+{
+  if (curves_ == nullptr) {
+    return 0;
+  }
+  const blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(
+      curves_->geometry);
+  if (domain == ATTR_DOMAIN_POINT) {
+    return geometry.points_size();
+  }
+  if (domain == ATTR_DOMAIN_CURVE) {
+    return geometry.curves_size();
+  }
+  return 0;
+}
+
+GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray,
+                                                        const AttributeDomain from_domain,
+                                                        const AttributeDomain to_domain) const
+{
+  return blender::bke::CurvesGeometry::wrap(curves_->geometry)
+      .adapt_domain(varray, from_domain, to_domain);
+}
+
+static Curves *get_curves_from_component_for_write(GeometryComponent &component)
+{
+  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
+  CurveComponent &curve_component = static_cast(component);
+  return curve_component.get_for_write();
+}
+
+static const Curves *get_curves_from_component_for_read(const GeometryComponent &component)
+{
+  BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
+  const CurveComponent &curve_component = static_cast(component);
+  return curve_component.get_for_read();
+}
+
+static void tag_component_topology_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_topology_changed();
+  }
+}
+
+static void tag_component_positions_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_positions_changed();
+  }
+}
+
+static void tag_component_normals_changed(GeometryComponent &component)
+{
+  Curves *curves = get_curves_from_component_for_write(component);
+  if (curves) {
+    blender::bke::CurvesGeometry::wrap(curves->geometry).tag_normals_changed();
+  }
+}
+
+/** \} */
+
+namespace blender::bke {
+
+/* -------------------------------------------------------------------- */
+/** \name Attribute Provider Declaration
+ * \{ */
+
+/**
+ * In this function all the attribute providers for a curves component are created.
+ * Most data in this function is statically allocated, because it does not change over time.
+ */
+static ComponentAttributeProviders create_attribute_providers_for_curve()
+{
+  static CustomDataAccessInfo curve_access = {
+      [](GeometryComponent &component) -> CustomData * {
+        Curves *curves = get_curves_from_component_for_write(component);
+        return curves ? &curves->geometry.curve_data : nullptr;
+      },
+      [](const GeometryComponent &component) -> const CustomData * {
+        const Curves *curves = get_curves_from_component_for_read(component);
+        return curves ? &curves->geometry.curve_data : nullptr;
+      },
+      [](GeometryComponent &component) {
+        Curves *curves = get_curves_from_component_for_write(component);
+        if (curves) {
+          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
+        }
+      }};
+  static CustomDataAccessInfo point_access = {
+      [](GeometryComponent &component) -> CustomData * {
+        Curves *curves = get_curves_from_component_for_write(component);
+        return curves ? &curves->geometry.point_data : nullptr;
+      },
+      [](const GeometryComponent &component) -> const CustomData * {
+        const Curves *curves = get_curves_from_component_for_read(component);
+        return curves ? &curves->geometry.point_data : nullptr;
+      },
+      [](GeometryComponent &component) {
+        Curves *curves = get_curves_from_component_for_write(component);
+        if (curves) {
+          blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers();
+        }
+      }};
+
+  static BuiltinCustomDataLayerProvider position("position",
+                                                 ATTR_DOMAIN_POINT,
+                                                 CD_PROP_FLOAT3,
+                                                 CD_PROP_FLOAT3,
+                                                 BuiltinAttributeProvider::NonCreatable,
+                                                 BuiltinAttributeProvider::Writable,
+                                                 BuiltinAttributeProvider::NonDeletable,
+                                                 point_access,
+                                                 make_array_read_attribute,
+                                                 make_array_write_attribute,
+                                                 tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider radius("radius",
+                                               ATTR_DOMAIN_POINT,
+                                               CD_PROP_FLOAT,
+                                               CD_PROP_FLOAT,
+                                               BuiltinAttributeProvider::Creatable,
+                                               BuiltinAttributeProvider::Writable,
+                                               BuiltinAttributeProvider::Deletable,
+                                               point_access,
+                                               make_array_read_attribute,
+                                               make_array_write_attribute,
+                                               tag_component_normals_changed);
+
+  static BuiltinCustomDataLayerProvider id("id",
+                                           ATTR_DOMAIN_POINT,
+                                           CD_PROP_INT32,
+                                           CD_PROP_INT32,
+                                           BuiltinAttributeProvider::Creatable,
+                                           BuiltinAttributeProvider::Writable,
+                                           BuiltinAttributeProvider::Deletable,
+                                           point_access,
+                                           make_array_read_attribute,
+                                           make_array_write_attribute,
+                                           nullptr);
+
+  static BuiltinCustomDataLayerProvider tilt("tilt",
+                                             ATTR_DOMAIN_POINT,
+                                             CD_PROP_FLOAT,
+                                             CD_PROP_FLOAT,
+                                             BuiltinAttributeProvider::Creatable,
+                                             BuiltinAttributeProvider::Writable,
+                                             BuiltinAttributeProvider::Deletable,
+                                             point_access,
+                                             make_array_read_attribute,
+                                             make_array_write_attribute,
+                                             tag_component_normals_changed);
+
+  static BuiltinCustomDataLayerProvider handle_right("handle_right",
+                                                     ATTR_DOMAIN_POINT,
+                                                     CD_PROP_FLOAT3,
+                                                     CD_PROP_FLOAT3,
+                                                     BuiltinAttributeProvider::Creatable,
+                                                     BuiltinAttributeProvider::Writable,
+                                                     BuiltinAttributeProvider::Deletable,
+                                                     point_access,
+                                                     make_array_read_attribute,
+                                                     make_array_write_attribute,
+                                                     tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider handle_left("handle_left",
+                                                    ATTR_DOMAIN_POINT,
+                                                    CD_PROP_FLOAT3,
+                                                    CD_PROP_FLOAT3,
+                                                    BuiltinAttributeProvider::Creatable,
+                                                    BuiltinAttributeProvider::Writable,
+                                                    BuiltinAttributeProvider::Deletable,
+                                                    point_access,
+                                                    make_array_read_attribute,
+                                                    make_array_write_attribute,
+                                                    tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
+                                                          ATTR_DOMAIN_POINT,
+                                                          CD_PROP_INT8,
+                                                          CD_PROP_INT8,
+                                                          BuiltinAttributeProvider::Creatable,
+                                                          BuiltinAttributeProvider::Writable,
+                                                          BuiltinAttributeProvider::Deletable,
+                                                          point_access,
+                                                          make_array_read_attribute,
+                                                          make_array_write_attribute,
+                                                          tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
+                                                         ATTR_DOMAIN_POINT,
+                                                         CD_PROP_INT8,
+                                                         CD_PROP_INT8,
+                                                         BuiltinAttributeProvider::Creatable,
+                                                         BuiltinAttributeProvider::Writable,
+                                                         BuiltinAttributeProvider::Deletable,
+                                                         point_access,
+                                                         make_array_read_attribute,
+                                                         make_array_write_attribute,
+                                                         tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
+                                                     ATTR_DOMAIN_POINT,
+                                                     CD_PROP_FLOAT,
+                                                     CD_PROP_FLOAT,
+                                                     BuiltinAttributeProvider::Creatable,
+                                                     BuiltinAttributeProvider::Writable,
+                                                     BuiltinAttributeProvider::Deletable,
+                                                     point_access,
+                                                     make_array_read_attribute,
+                                                     make_array_write_attribute,
+                                                     tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
+                                                    ATTR_DOMAIN_CURVE,
+                                                    CD_PROP_INT32,
+                                                    CD_PROP_INT32,
+                                                    BuiltinAttributeProvider::Creatable,
+                                                    BuiltinAttributeProvider::Writable,
+                                                    BuiltinAttributeProvider::Deletable,
+                                                    curve_access,
+                                                    make_array_read_attribute,
+                                                    make_array_write_attribute,
+                                                    tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
+                                                         ATTR_DOMAIN_CURVE,
+                                                         CD_PROP_INT8,
+                                                         CD_PROP_INT8,
+                                                         BuiltinAttributeProvider::Creatable,
+                                                         BuiltinAttributeProvider::Writable,
+                                                         BuiltinAttributeProvider::Deletable,
+                                                         curve_access,
+                                                         make_array_read_attribute,
+                                                         make_array_write_attribute,
+                                                         tag_component_topology_changed);
+
+  static BuiltinCustomDataLayerProvider resolution("resolution",
+                                                   ATTR_DOMAIN_CURVE,
+                                                   CD_PROP_INT32,
+                                                   CD_PROP_INT32,
+                                                   BuiltinAttributeProvider::Creatable,
+                                                   BuiltinAttributeProvider::Writable,
+                                                   BuiltinAttributeProvider::Deletable,
+                                                   curve_access,
+                                                   make_array_read_attribute,
+                                                   make_array_write_attribute,
+                                                   tag_component_positions_changed);
+
+  static BuiltinCustomDataLayerProvider cyclic("cyclic",
+                                               ATTR_DOMAIN_CURVE,
+                                               CD_PROP_BOOL,
+                                               CD_PROP_BOOL,
+                                               BuiltinAttributeProvider::Creatable,
+                                               BuiltinAttributeProvider::Writable,
+                                               BuiltinAttributeProvider::Deletable,
+                                               curve_access,
+                                               make_array_read_attribute,
+                                               make_array_write_attribute,
+                                               tag_component_topology_changed);
+
+  static CustomDataAttributeProvider curve_custom_data(ATTR_DOMAIN_CURVE, curve_access);
+  static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access);
+
+  return ComponentAttributeProviders({&position,
+                                      &radius,
+                                      &id,
+                                      &tilt,
+                                      &handle_right,
+                                      &handle_left,
+                                      &handle_type_right,
+                                      &handle_type_left,
+                                      &nurbs_order,
+                                      &nurbs_weight,
+                                      &resolution,
+                                      &cyclic},
+                                     {&curve_custom_data, &point_custom_data});
+}
+
+/** \} */
+
+}  // namespace blender::bke
+
+const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const
+{
+  static blender::bke::ComponentAttributeProviders providers =
+      blender::bke::create_attribute_providers_for_curve();
+  return &providers;
+}
diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc
index 13441b4914a..73572e30d61 100644
--- a/source/blender/blenkernel/intern/geometry_set.cc
+++ b/source/blender/blenkernel/intern/geometry_set.cc
@@ -7,6 +7,7 @@
 
 #include "BKE_attribute.h"
 #include "BKE_attribute_access.hh"
+#include "BKE_curves.hh"
 #include "BKE_geometry_set.hh"
 #include "BKE_lib_id.h"
 #include "BKE_mesh.h"
@@ -186,8 +187,9 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma
   if (volume != nullptr) {
     have_minmax |= BKE_volume_min_max(volume, *r_min, *r_max);
   }
-  const CurveEval *curve = this->get_curve_for_read();
-  if (curve != nullptr) {
+  const Curves *curves = this->get_curve_for_read();
+  if (curves != nullptr) {
+    std::unique_ptr curve = curves_to_curve_eval(*curves);
     /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */
     have_minmax |= curve->bounds_min_max(*r_min, *r_max, true);
   }
@@ -258,7 +260,7 @@ const Volume *GeometrySet::get_volume_for_read() const
   return (component == nullptr) ? nullptr : component->get_for_read();
 }
 
-const CurveEval *GeometrySet::get_curve_for_read() const
+const Curves *GeometrySet::get_curve_for_read() const
 {
   const CurveComponent *component = this->get_component_for_read();
   return (component == nullptr) ? nullptr : component->get_for_read();
@@ -285,7 +287,7 @@ bool GeometrySet::has_volume() const
 bool GeometrySet::has_curve() const
 {
   const CurveComponent *component = this->get_component_for_read();
-  return component != nullptr && component->has_curve();
+  return component != nullptr && component->has_curves();
 }
 
 bool GeometrySet::has_realized_data() const
@@ -327,12 +329,12 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud,
   return geometry_set;
 }
 
-GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership)
+GeometrySet GeometrySet::create_with_curve(Curves *curves, GeometryOwnershipType ownership)
 {
   GeometrySet geometry_set;
-  if (curve != nullptr) {
+  if (curves != nullptr) {
     CurveComponent &component = geometry_set.get_component_for_write();
-    component.replace(curve, ownership);
+    component.replace(curves, ownership);
   }
   return geometry_set;
 }
@@ -351,18 +353,18 @@ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership)
   component.replace(mesh, ownership);
 }
 
-void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership)
+void GeometrySet::replace_curve(Curves *curves, GeometryOwnershipType ownership)
 {
-  if (curve == nullptr) {
+  if (curves == nullptr) {
     this->remove();
     return;
   }
-  if (curve == this->get_curve_for_read()) {
+  if (curves == this->get_curve_for_read()) {
     return;
   }
   this->remove();
   CurveComponent &component = this->get_component_for_write();
-  component.replace(curve, ownership);
+  component.replace(curves, ownership);
 }
 
 void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership)
@@ -411,7 +413,7 @@ Volume *GeometrySet::get_volume_for_write()
   return component == nullptr ? nullptr : component->get_for_write();
 }
 
-CurveEval *GeometrySet::get_curve_for_write()
+Curves *GeometrySet::get_curve_for_write()
 {
   CurveComponent *component = this->get_component_ptr();
   return component == nullptr ? nullptr : component->get_for_write();
diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc
index 5af599f0302..6b5c04ac25e 100644
--- a/source/blender/blenkernel/intern/mesh_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_convert.cc
@@ -953,7 +953,7 @@ static const Mesh *get_evaluated_mesh_from_object(const Object *object)
   return nullptr;
 }
 
-static const CurveEval *get_evaluated_curve_from_object(const Object *object)
+static const Curves *get_evaluated_curves_from_object(const Object *object)
 {
   GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval;
   if (geometry_set_eval) {
@@ -968,8 +968,9 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o
   if (mesh) {
     return BKE_mesh_copy_for_eval(mesh, false);
   }
-  const CurveEval *curve = get_evaluated_curve_from_object(evaluated_object);
-  if (curve) {
+  const Curves *curves = get_evaluated_curves_from_object(evaluated_object);
+  if (curves) {
+    std::unique_ptr curve = curves_to_curve_eval(*curves);
     return blender::bke::curve_to_wire_mesh(*curve);
   }
   return nullptr;
diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc
index c98bd9d6b74..149ae70dda1 100644
--- a/source/blender/geometry/intern/realize_instances.cc
+++ b/source/blender/geometry/intern/realize_instances.cc
@@ -13,6 +13,7 @@
 #include "BLI_task.hh"
 
 #include "BKE_collection.h"
+#include "BKE_curves.hh"
 #include "BKE_geometry_set_instances.hh"
 #include "BKE_material.h"
 #include "BKE_mesh.h"
@@ -118,7 +119,7 @@ struct RealizeMeshTask {
 };
 
 struct RealizeCurveInfo {
-  const CurveEval *curve = nullptr;
+  const Curves *curves;
   /**
    * Matches the order in #AllCurvesInfo.attributes. For point attributes, the `std::optional`
    * will be empty.
@@ -163,7 +164,7 @@ struct AllCurvesInfo {
   /** Ordering of all attributes that are propagated to the output curve generically. */
   OrderedAttributes attributes;
   /** Ordering of the original curves that are joined. */
-  VectorSet order;
+  VectorSet order;
   /** Preprocessed data about every original curve. This is ordered by #order. */
   Array realize_info;
   bool create_id_attribute = false;
@@ -443,16 +444,16 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info,
       }
       case GEO_COMPONENT_TYPE_CURVE: {
         const CurveComponent &curve_component = *static_cast(component);
-        const CurveEval *curve = curve_component.get_for_read();
-        if (curve != nullptr && !curve->splines().is_empty()) {
-          const int curve_index = gather_info.curves.order.index_of(curve);
+        const Curves *curves = curve_component.get_for_read();
+        if (curves != nullptr && curves->geometry.curve_size > 0) {
+          const int curve_index = gather_info.curves.order.index_of(curves);
           const RealizeCurveInfo &curve_info = gather_info.curves.realize_info[curve_index];
           gather_info.r_tasks.curve_tasks.append({gather_info.r_offsets.spline_offset,
                                                   &curve_info,
                                                   base_transform,
                                                   base_instance_context.curves,
                                                   base_instance_context.id});
-          gather_info.r_offsets.spline_offset += curve->splines().size();
+          gather_info.r_offsets.spline_offset += curves->geometry.curve_size;
         }
         break;
       }
@@ -1038,11 +1039,11 @@ static OrderedAttributes gather_generic_curve_attributes_to_propagate(
 }
 
 static void gather_curves_to_realize(const GeometrySet &geometry_set,
-                                     VectorSet &r_curves)
+                                     VectorSet &r_curves)
 {
-  if (const CurveEval *curve = geometry_set.get_curve_for_read()) {
-    if (!curve->splines().is_empty()) {
-      r_curves.add(curve);
+  if (const Curves *curves = geometry_set.get_curve_for_read()) {
+    if (curves->geometry.curve_size != 0) {
+      r_curves.add(curves);
     }
   }
   if (const InstancesComponent *instances =
@@ -1064,12 +1065,12 @@ static AllCurvesInfo preprocess_curves(const GeometrySet &geometry_set,
   info.realize_info.reinitialize(info.order.size());
   for (const int curve_index : info.realize_info.index_range()) {
     RealizeCurveInfo &curve_info = info.realize_info[curve_index];
-    const CurveEval *curve = info.order[curve_index];
-    curve_info.curve = curve;
+    const Curves *curves = info.order[curve_index];
+    curve_info.curves = curves;
 
     /* Access attributes. */
     CurveComponent component;
-    component.replace(const_cast(curve), GeometryOwnershipType::ReadOnly);
+    component.replace(const_cast(curves), GeometryOwnershipType::ReadOnly);
     curve_info.spline_attributes.reinitialize(info.attributes.size());
     for (const int attribute_index : info.attributes.index_range()) {
       const AttributeDomain domain = info.attributes.kinds[attribute_index].domain;
@@ -1095,9 +1096,9 @@ static void execute_realize_curve_task(const RealizeInstancesOptions &options,
                                        MutableSpan dst_spline_attributes)
 {
   const RealizeCurveInfo &curve_info = *task.curve_info;
-  const CurveEval &curve = *curve_info.curve;
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_info.curves);
 
-  const Span src_splines = curve.splines();
+  const Span src_splines = curve->splines();
 
   /* Initialize point attributes. */
   threading::parallel_for(src_splines.index_range(), 100, [&](const IndexRange src_spline_range) {
@@ -1206,12 +1207,12 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options,
   }
 
   const RealizeCurveTask &last_task = tasks.last();
-  const CurveEval &last_curve = *last_task.curve_info->curve;
-  const int tot_splines = last_task.start_spline_index + last_curve.splines().size();
+  const Curves &last_curves = *last_task.curve_info->curves;
+  const int tot_splines = last_task.start_spline_index + last_curves.geometry.curve_size;
 
   Array dst_splines(tot_splines);
 
-  CurveEval *dst_curve = new CurveEval();
+  std::unique_ptr dst_curve = std::make_unique();
   dst_curve->attributes.reallocate(tot_splines);
   CustomDataAttributes &spline_attributes = dst_curve->attributes;
 
@@ -1242,7 +1243,7 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options,
   dst_curve->add_splines(dst_splines);
 
   CurveComponent &dst_component = r_realized_geometry.get_component_for_write();
-  dst_component.replace(dst_curve);
+  dst_component.replace(curve_eval_to_curves(*dst_curve));
 }
 
 /** \} */
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
index b83aa8b69a9..e6f3e483f1f 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
@@ -143,9 +143,9 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   const CurveComponent &curve_component = *geometry_set.get_component_for_read();
-  const CurveEval &curve = *curve_component.get_for_read();
-  const Span splines = curve.splines();
-  curve.assert_valid_point_attributes();
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
+  const Span splines = curve->splines();
+  curve->assert_valid_point_attributes();
 
   evaluate_splines(splines);
 
@@ -167,9 +167,9 @@ static void node_geo_exec(GeoNodeExecParams params)
       end_result.get_component_for_write();
 
   CurveToPointsResults start_attributes = curve_to_points_create_result_attributes(
-      start_point_component, curve);
+      start_point_component, *curve);
   CurveToPointsResults end_attributes = curve_to_points_create_result_attributes(
-      end_point_component, curve);
+      end_point_component, *curve);
 
   copy_endpoint_attributes(splines, offsets.as_span(), start_attributes, end_attributes);
   copy_spline_domain_attributes(curve_component, offsets.as_span(), start_point_component);
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
index 6deaf5b554a..78e36784be7 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
@@ -26,8 +26,8 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   /* Retrieve data for write access so we can avoid new allocations for the reversed data. */
   CurveComponent &curve_component = geometry_set.get_component_for_write();
-  CurveEval &curve = *curve_component.get_for_write();
-  MutableSpan splines = curve.splines();
+  std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
+  MutableSpan splines = curve->splines();
 
   const std::string selection_name = params.extract_input("Selection");
   VArray selection = curve_component.attribute_get_for_read(
@@ -41,6 +41,8 @@ static void node_geo_exec(GeoNodeExecParams params)
     }
   });
 
+  geometry_set.replace_curve(curve_eval_to_curves(*curve));
+
   params.set_output("Curve", geometry_set);
 }
 
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
index a09a751b550..729ccca5f04 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc
@@ -89,9 +89,8 @@ static void node_geo_exec(GeoNodeExecParams params)
   geometry_set = geometry::realize_instances_legacy(geometry_set);
 
   CurveComponent &curve_component = geometry_set.get_component_for_write();
-  const CurveEval *curve = curve_component.get_for_read();
-
-  if (curve != nullptr) {
+  if (curve_component.has_curves()) {
+    const std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
     const std::string selection_name = params.extract_input("Selection");
     OutputAttribute_Typed selection =
         curve_component.attribute_try_get_for_output_only(selection_name, ATTR_DOMAIN_POINT);
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
index e5d4d6c1d0f..8ab43909a20 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
@@ -63,8 +63,8 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   /* Retrieve data for write access so we can avoid new allocations for the handles data. */
   CurveComponent &curve_component = geometry_set.get_component_for_write();
-  CurveEval &curve = *curve_component.get_for_write();
-  MutableSpan splines = curve.splines();
+  std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
+  MutableSpan splines = curve->splines();
 
   const std::string selection_name = params.extract_input("Selection");
   VArray selection = curve_component.attribute_get_for_read(
@@ -101,6 +101,8 @@ static void node_geo_exec(GeoNodeExecParams params)
     bezier_spline.mark_cache_invalid();
   }
 
+  geometry_set.replace_curve(curve_eval_to_curves(*curve));
+
   if (!has_bezier_spline) {
     params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve"));
   }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
index 87b8bbf8786..e15e7339107 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
@@ -242,34 +242,34 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   const CurveComponent *curve_component = geometry_set.get_component_for_read();
-  const CurveEval &curve = *curve_component->get_for_read();
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_component->get_for_read());
 
   const std::string selection_name = params.extract_input("Selection");
   VArray selection = curve_component->attribute_get_for_read(
       selection_name, ATTR_DOMAIN_CURVE, true);
 
   std::unique_ptr new_curve = std::make_unique();
-  for (const int i : curve.splines().index_range()) {
+  for (const int i : curve->splines().index_range()) {
     if (selection[i]) {
       switch (output_type) {
         case GEO_NODE_SPLINE_TYPE_POLY:
-          new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i]));
+          new_curve->add_spline(convert_to_poly_spline(*curve->splines()[i]));
           break;
         case GEO_NODE_SPLINE_TYPE_BEZIER:
-          new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params));
+          new_curve->add_spline(convert_to_bezier(*curve->splines()[i], params));
           break;
         case GEO_NODE_SPLINE_TYPE_NURBS:
-          new_curve->add_spline(convert_to_nurbs(*curve.splines()[i]));
+          new_curve->add_spline(convert_to_nurbs(*curve->splines()[i]));
           break;
       }
     }
     else {
-      new_curve->add_spline(curve.splines()[i]->copy());
+      new_curve->add_spline(curve->splines()[i]->copy());
     }
   }
 
-  new_curve->attributes = curve.attributes;
-  params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release()));
+  new_curve->attributes = curve->attributes;
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*new_curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_curve_spline_type_cc
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
index bce320496a1..2a8ab2990db 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
@@ -354,9 +354,11 @@ static void node_geo_exec(GeoNodeExecParams params)
     return;
   }
 
-  std::unique_ptr output_curve = subdivide_curve(*component.get_for_read(), cuts);
+  std::unique_ptr output_curve = subdivide_curve(
+      *curves_to_curve_eval(*component.get_for_read()), cuts);
 
-  params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release()));
+  params.set_output("Geometry",
+                    GeometrySet::create_with_curve(curve_eval_to_curves(*output_curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_curve_subdivide_cc
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
index 64627e61910..f68dd6b6b0c 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
@@ -289,13 +289,13 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   const CurveComponent &curve_component = *geometry_set.get_component_for_read();
-  const CurveEval &curve = *curve_component.get_for_read();
-  const Span splines = curve.splines();
-  curve.assert_valid_point_attributes();
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
+  const Span splines = curve->splines();
+  curve->assert_valid_point_attributes();
 
   evaluate_splines(splines);
 
-  const Array offsets = calculate_spline_point_offsets(params, mode, curve, splines);
+  const Array offsets = calculate_spline_point_offsets(params, mode, *curve, splines);
   const int total_size = offsets.last();
   if (total_size == 0) {
     params.set_output("Geometry", GeometrySet());
@@ -306,7 +306,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   PointCloudComponent &point_component = result.get_component_for_write();
 
   CurveToPointsResults new_attributes = curve_to_points_create_result_attributes(point_component,
-                                                                                 curve);
+                                                                                 *curve);
   switch (mode) {
     case GEO_NODE_CURVE_RESAMPLE_COUNT:
     case GEO_NODE_CURVE_RESAMPLE_LENGTH:
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
index 897a1c1cd2d..ca98d83c137 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc
@@ -235,9 +235,9 @@ static void delete_curve_selection(const CurveComponent &in_component,
                                    const bool invert)
 {
   std::unique_ptr r_curve = curve_delete(
-      *in_component.get_for_read(), selection_name, invert);
+      *curves_to_curve_eval(*in_component.get_for_read()), selection_name, invert);
   if (r_curve) {
-    r_component.replace(r_curve.release());
+    r_component.replace(curve_eval_to_curves(*r_curve));
   }
   else {
     r_component.clear();
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
index ff86a92f2c7..cb5a757811e 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
@@ -48,7 +48,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   std::unique_ptr curve = geometry::mesh_to_curve_convert(
       component, IndexMask(selected_edge_indices));
 
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_mesh_to_curve_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
index 59147e9b23f..44b9857e791 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
@@ -162,8 +162,9 @@ static Mesh *compute_hull(const GeometrySet &geometry_set)
   }
 
   if (geometry_set.has_curve()) {
-    const CurveEval &curve = *geometry_set.get_curve_for_read();
-    for (const SplinePtr &spline : curve.splines()) {
+    const std::unique_ptr curve = curves_to_curve_eval(
+        *geometry_set.get_curve_for_read());
+    for (const SplinePtr &spline : curve->splines()) {
       positions_span = spline->evaluated_positions();
       total_size += positions_span.size();
       count++;
@@ -202,8 +203,9 @@ static Mesh *compute_hull(const GeometrySet &geometry_set)
   }
 
   if (geometry_set.has_curve()) {
-    const CurveEval &curve = *geometry_set.get_curve_for_read();
-    for (const SplinePtr &spline : curve.splines()) {
+    const std::unique_ptr curve = curves_to_curve_eval(
+        *geometry_set.get_curve_for_read());
+    for (const SplinePtr &spline : curve->splines()) {
       Span array = spline->evaluated_positions();
       positions.as_mutable_span().slice(offset, array.size()).copy_from(array);
       offset += array.size();
@@ -273,7 +275,7 @@ static Mesh *convex_hull_from_instances(const GeometrySet &geometry_set)
       read_positions(*set.get_component_for_read(), transforms, &coords);
     }
     if (set.has_curve()) {
-      read_curve_positions(*set.get_curve_for_read(), transforms, &coords);
+      read_curve_positions(*curves_to_curve_eval(*set.get_curve_for_read()), transforms, &coords);
     }
   }
   return hull_from_bullet(nullptr, coords);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
index 65aad0fcbf1..ce3058c7d42 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
@@ -59,10 +59,13 @@ class EndpointFieldInput final : public GeometryFieldInput {
     }
 
     const CurveComponent &curve_component = static_cast(component);
-    const CurveEval *curve = curve_component.get_for_read();
+    if (!curve_component.has_curves()) {
+      return nullptr;
+    }
 
-    Array control_point_offsets = curve->control_point_offsets();
+    const std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
 
+    Array control_point_offsets = curve->control_point_offsets();
     if (curve == nullptr || control_point_offsets.last() == 0) {
       return nullptr;
     }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
index 9824b2b2ece..c1220746c22 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
@@ -117,8 +117,9 @@ static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCu
     return;
   }
 
-  const CurveEval &curve = *geometry_set.get_curve_for_read();
-  if (curve.splines().is_empty()) {
+  const std::unique_ptr curve = curves_to_curve_eval(
+      *geometry_set.get_curve_for_read());
+  if (curve->splines().is_empty()) {
     geometry_set.replace_curve(nullptr);
     return;
   }
@@ -127,7 +128,7 @@ static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCu
                                           CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES :
                                           CDT_INSIDE_WITH_HOLES;
 
-  const blender::meshintersect::CDT_result results = do_cdt(curve, output_type);
+  const blender::meshintersect::CDT_result results = do_cdt(*curve, output_type);
   Mesh *mesh = cdt_to_mesh(results);
 
   geometry_set.replace_mesh(mesh);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
index b8f317a9679..6742b1103ee 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -603,10 +603,10 @@ static void calculate_curve_fillet(GeometrySet &geometry_set,
 
   fillet_param.limit_radius = limit_radius;
 
-  const CurveEval &input_curve = *geometry_set.get_curve_for_read();
-  std::unique_ptr output_curve = fillet_curve(input_curve, fillet_param);
+  const std::unique_ptr input_curve = curves_to_curve_eval(*component.get_for_read());
+  std::unique_ptr output_curve = fillet_curve(*input_curve, fillet_param);
 
-  geometry_set.replace_curve(output_curve.release());
+  geometry_set.replace_curve(curve_eval_to_curves(*output_curve));
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
index 8dc74aa3dea..ccd3a587e63 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc
@@ -92,14 +92,14 @@ class HandleTypeFieldInput final : public GeometryFieldInput {
     }
 
     const CurveComponent &curve_component = static_cast(component);
-    const CurveEval *curve = curve_component.get_for_read();
+    const Curves *curve = curve_component.get_for_read();
     if (curve == nullptr) {
       return {};
     }
 
     if (domain == ATTR_DOMAIN_POINT) {
       Array selection(mask.min_array_size());
-      select_by_handle_type(*curve, type_, mode_, selection);
+      select_by_handle_type(*curves_to_curve_eval(*curve), type_, mode_, selection);
       return VArray::ForContainer(std::move(selection));
     }
     return {};
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
index 82621189964..c7c822db983 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
@@ -18,9 +18,9 @@ static void node_geo_exec(GeoNodeExecParams params)
     params.set_default_remaining_outputs();
     return;
   }
-  const CurveEval &curve = *curve_set.get_curve_for_read();
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_set.get_curve_for_read());
   float length = 0.0f;
-  for (const SplinePtr &spline : curve.splines()) {
+  for (const SplinePtr &spline : curve->splines()) {
     length += spline->length();
   }
   params.set_output("Length", length);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
index 9919e24473e..9fbc01935ce 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
@@ -335,7 +335,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                            r_center,
                                            r_normal,
                                            r_radius);
-      params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+      params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
       params.set_output("Center", r_center);
       params.set_output("Normal", r_normal);
       params.set_output("Radius", r_radius);
@@ -350,7 +350,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                            params.extract_input("Connect Center"),
                                            params.extract_input("Invert Arc"));
 
-      params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+      params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
       break;
     }
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
index 1d006aea1ef..eabd6aa1aa0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
@@ -113,7 +113,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("End Handle"),
       std::max(params.extract_input("Resolution"), 1),
       mode);
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
index 44505b61a27..73929dce1af 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
@@ -203,7 +203,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   if (curve) {
-    params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+    params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
   }
   else {
     params.set_default_remaining_outputs();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
index d11af3b1cc0..066947de496 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
@@ -111,7 +111,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                         params.extract_input("Length"));
   }
 
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_line_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
index 456f6e55c1e..1b055a3a3d5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
@@ -61,7 +61,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("Middle"),
       params.extract_input("End"),
       std::max(params.extract_input("Resolution"), 3));
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc
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 b6a847eebf4..7842ad028b7 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
@@ -264,7 +264,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   curve->add_spline(std::move(spline));
   curve->attributes.reallocate(curve->splines().size());
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc
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 f448ddabd2b..d5bc70e37cd 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
@@ -86,7 +86,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("End Radius"),
       params.extract_input("Height"),
       params.extract_input("Reverse"));
-  params.set_output("Curve", GeometrySet::create_with_curve(curve.release()));
+  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_spiral_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
index 5969af43bc1..e02bcfb73cc 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
@@ -83,7 +83,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       std::max(params.extract_input("Outer Radius"), 0.0f),
       params.extract_input("Twist"),
       std::max(params.extract_input("Points"), 3));
-  GeometrySet output = GeometrySet::create_with_curve(curve.release());
+  GeometrySet output = GeometrySet::create_with_curve(curve_eval_to_curves(*curve));
 
   if (params.output_is_required("Outer Points")) {
     StrongAnonymousAttributeID attribute_output("Outer Points");
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 d2afeaa7094..fffc6188dfd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
@@ -158,7 +158,7 @@ static SplinePtr resample_spline_evaluated(const Spline &src)
 static std::unique_ptr resample_curve(const CurveComponent *component,
                                                  const SampleModeParam &mode_param)
 {
-  const CurveEval *input_curve = component->get_for_read();
+  const std::unique_ptr input_curve = curves_to_curve_eval(*component->get_for_read());
   GeometryComponentFieldContext field_context{*component, ATTR_DOMAIN_CURVE};
   const int domain_size = component->attribute_domain_size(ATTR_DOMAIN_CURVE);
 
@@ -242,7 +242,7 @@ static void geometry_set_curve_resample(GeometrySet &geometry_set,
   std::unique_ptr output_curve = resample_curve(
       geometry_set.get_component_for_read(), mode_param);
 
-  geometry_set.replace_curve(output_curve.release());
+  geometry_set.replace_curve(curve_eval_to_curves(*output_curve));
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
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 0ef3230937b..06ca0483063 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
@@ -34,13 +34,15 @@ static void node_geo_exec(GeoNodeExecParams params)
     selection_evaluator.evaluate();
     const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
 
-    CurveEval &curve = *component.get_for_write();
-    MutableSpan splines = curve.splines();
+    std::unique_ptr curve = curves_to_curve_eval(*component.get_for_write());
+    MutableSpan splines = curve->splines();
     threading::parallel_for(selection.index_range(), 128, [&](IndexRange range) {
       for (const int i : range) {
         splines[selection[i]]->reverse();
       }
     });
+
+    component.replace(curve_eval_to_curves(*curve));
   });
 
   params.set_output("Curve", std::move(geometry_set));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
index 152828b284c..222c28dd21b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
@@ -127,7 +127,8 @@ class SampleCurveFunction : public fn::MultiFunction {
     }
 
     const CurveComponent *curve_component = geometry_set_.get_component_for_read();
-    const CurveEval *curve = curve_component->get_for_read();
+    const std::unique_ptr curve = curves_to_curve_eval(
+        *curve_component->get_for_read());
     Span splines = curve->splines();
     if (splines.is_empty()) {
       return return_default();
@@ -234,12 +235,13 @@ static void node_geo_exec(GeoNodeExecParams params)
     return;
   }
 
-  const CurveEval *curve = component->get_for_read();
-  if (curve == nullptr) {
+  if (!component->has_curves()) {
     params.set_default_remaining_outputs();
     return;
   }
 
+  const std::unique_ptr curve = curves_to_curve_eval(*component->get_for_read());
+
   if (curve->splines().is_empty()) {
     params.set_default_remaining_outputs();
     return;
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 9f50b29d995..a892ff419e5 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
@@ -66,8 +66,8 @@ static void node_geo_exec(GeoNodeExecParams params)
 
     /* Retrieve data for write access so we can avoid new allocations for the handles data. */
     CurveComponent &curve_component = geometry_set.get_component_for_write();
-    CurveEval &curve = *curve_component.get_for_write();
-    MutableSpan splines = curve.splines();
+    std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
+    MutableSpan splines = curve->splines();
 
     GeometryComponentFieldContext field_context{curve_component, ATTR_DOMAIN_POINT};
     const int domain_size = curve_component.attribute_domain_size(ATTR_DOMAIN_POINT);
@@ -108,6 +108,8 @@ static void node_geo_exec(GeoNodeExecParams params)
       }
       bezier_spline.mark_cache_invalid();
     }
+
+    curve_component.replace(curve_eval_to_curves(*curve));
   });
   if (!has_bezier_spline) {
     params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve"));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
index 3fcbd1b88c3..3edaccba506 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc
@@ -205,8 +205,9 @@ class CurveParameterFieldInput final : public GeometryFieldInput {
   {
     if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
       const CurveComponent &curve_component = static_cast(component);
-      const CurveEval *curve = curve_component.get_for_read();
-      if (curve) {
+      if (curve_component.has_curves()) {
+        const std::unique_ptr curve = curves_to_curve_eval(
+            *curve_component.get_for_read());
         return construct_curve_parameter_varray(*curve, mask, domain);
       }
     }
@@ -238,8 +239,8 @@ class CurveLengthFieldInput final : public GeometryFieldInput {
   {
     if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
       const CurveComponent &curve_component = static_cast(component);
-      const CurveEval *curve = curve_component.get_for_read();
-      if (curve) {
+      if (curve_component.has_curves()) {
+        std::unique_ptr curve = curves_to_curve_eval(*curve_component.get_for_read());
         return construct_curve_length_varray(*curve, mask, domain);
       }
     }
@@ -271,8 +272,9 @@ class IndexOnSplineFieldInput final : public GeometryFieldInput {
   {
     if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
       const CurveComponent &curve_component = static_cast(component);
-      const CurveEval *curve = curve_component.get_for_read();
-      if (curve) {
+      if (curve_component.has_curves()) {
+        const std::unique_ptr curve = curves_to_curve_eval(
+            *curve_component.get_for_read());
         return construct_index_on_spline_varray(*curve, mask, domain);
       }
     }
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 053df030f15..d691726da27 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
@@ -367,40 +367,43 @@ static void node_geo_exec(GeoNodeExecParams params)
     }
 
     const CurveComponent *curve_component = geometry_set.get_component_for_read();
-    const CurveEval &curve = *curve_component->get_for_read();
+    const std::unique_ptr curve = curves_to_curve_eval(
+        *curve_component->get_for_read());
     GeometryComponentFieldContext field_context{*curve_component, ATTR_DOMAIN_CURVE};
     const int domain_size = curve_component->attribute_domain_size(ATTR_DOMAIN_CURVE);
 
+    Span src_splines = curve->splines();
+
     fn::FieldEvaluator selection_evaluator{field_context, domain_size};
     selection_evaluator.add(selection_field);
     selection_evaluator.evaluate();
     const VArray &selection = selection_evaluator.get_evaluated(0);
 
     std::unique_ptr new_curve = std::make_unique();
-    new_curve->resize(curve.splines().size());
+    new_curve->resize(src_splines.size());
 
-    threading::parallel_for(curve.splines().index_range(), 512, [&](IndexRange range) {
+    threading::parallel_for(src_splines.index_range(), 512, [&](IndexRange range) {
       for (const int i : range) {
         if (selection[i]) {
           switch (output_type) {
             case GEO_NODE_SPLINE_TYPE_POLY:
-              new_curve->splines()[i] = convert_to_poly_spline(*curve.splines()[i]);
+              new_curve->splines()[i] = convert_to_poly_spline(*src_splines[i]);
               break;
             case GEO_NODE_SPLINE_TYPE_BEZIER:
-              new_curve->splines()[i] = convert_to_bezier(*curve.splines()[i], params);
+              new_curve->splines()[i] = convert_to_bezier(*src_splines[i], params);
               break;
             case GEO_NODE_SPLINE_TYPE_NURBS:
-              new_curve->splines()[i] = convert_to_nurbs(*curve.splines()[i]);
+              new_curve->splines()[i] = convert_to_nurbs(*src_splines[i]);
               break;
           }
         }
         else {
-          new_curve->splines()[i] = curve.splines()[i]->copy();
+          new_curve->splines()[i] = src_splines[i]->copy();
         }
       }
     });
-    new_curve->attributes = curve.attributes;
-    geometry_set.replace_curve(new_curve.release());
+    new_curve->attributes = curve->attributes;
+    geometry_set.replace_curve(curve_eval_to_curves(*new_curve));
   });
 
   params.set_output("Curve", std::move(geometry_set));
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 81c3f14d8b1..3297ddcae85 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -340,9 +340,9 @@ static void node_geo_exec(GeoNodeExecParams params)
     if (cuts.is_single() && cuts.get_internal_single() < 1) {
       return;
     }
-
-    std::unique_ptr output_curve = subdivide_curve(*component.get_for_read(), cuts);
-    geometry_set.replace_curve(output_curve.release());
+    std::unique_ptr output_curve = subdivide_curve(
+        *curves_to_curve_eval(*component.get_for_read()), cuts);
+    geometry_set.replace_curve(curve_eval_to_curves(*output_curve));
   });
   params.set_output("Curve", geometry_set);
 }
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 ed497b6fbe0..753a6fe7278 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
@@ -27,14 +27,16 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
                                        const GeometrySet &profile_set,
                                        const bool fill_caps)
 {
-  const CurveEval *curve = geometry_set.get_curve_for_read();
-  const CurveEval *profile_curve = profile_set.get_curve_for_read();
+  const std::unique_ptr curve = curves_to_curve_eval(
+      *geometry_set.get_curve_for_read());
+  const Curves *profile_curves = profile_set.get_curve_for_read();
 
-  if (profile_curve == nullptr) {
+  if (profile_curves == nullptr) {
     Mesh *mesh = bke::curve_to_wire_mesh(*curve);
     geometry_set.replace_mesh(mesh);
   }
   else {
+    const std::unique_ptr profile_curve = curves_to_curve_eval(*profile_curves);
     Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps);
     geometry_set.replace_mesh(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 7481f7248a1..33e7a818c87 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
@@ -325,11 +325,12 @@ static void node_geo_exec(GeoNodeExecParams params)
       geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
       return;
     }
-    const CurveEval &curve = *geometry_set.get_curve_for_read();
-    const Span splines = curve.splines();
-    curve.assert_valid_point_attributes();
+    const std::unique_ptr curve = curves_to_curve_eval(
+        *geometry_set.get_curve_for_read());
+    const Span splines = curve->splines();
+    curve->assert_valid_point_attributes();
 
-    const Array offsets = calculate_spline_point_offsets(params, mode, curve, splines);
+    const Array offsets = calculate_spline_point_offsets(params, mode, *curve, splines);
     const int total_size = offsets.last();
     if (total_size == 0) {
       geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
@@ -339,7 +340,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     geometry_set.replace_pointcloud(BKE_pointcloud_new_nomain(total_size));
     PointCloudComponent &points = geometry_set.get_component_for_write();
     ResultAttributes point_attributes = create_attributes_for_transfer(
-        points, curve, attribute_outputs);
+        points, *curve, attribute_outputs);
 
     switch (mode) {
       case GEO_NODE_CURVE_RESAMPLE_COUNT:
@@ -351,7 +352,7 @@ static void node_geo_exec(GeoNodeExecParams params)
         break;
     }
 
-    copy_spline_domain_attributes(curve, offsets, points);
+    copy_spline_domain_attributes(*curve, offsets, points);
 
     if (!point_attributes.rotations.is_empty()) {
       curve_create_default_rotation_attribute(
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 6f2eb9f23c4..649391a2346 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -515,8 +515,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
   const blender::VArray &starts = evaluator.get_evaluated(0);
   const blender::VArray &ends = evaluator.get_evaluated(1);
 
-  CurveEval &curve = *geometry_set.get_curve_for_write();
-  MutableSpan splines = curve.splines();
+  std::unique_ptr curve = curves_to_curve_eval(*geometry_set.get_curve_for_read());
+  MutableSpan splines = curve->splines();
 
   threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
     for (const int i : range) {
@@ -565,6 +565,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
       }
     }
   });
+
+  geometry_set.replace_curve(curve_eval_to_curves(*curve));
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
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 c11b82a7d99..9fc71ebe8d1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -475,9 +475,9 @@ static void separate_curve_selection(GeometrySet &geometry_set,
   selection_evaluator.evaluate();
   const VArray_Span &selection = selection_evaluator.get_evaluated(0);
   std::unique_ptr r_curve = curve_separate(
-      *src_component.get_for_read(), selection, selection_domain, invert);
+      *curves_to_curve_eval(*src_component.get_for_read()), selection, selection_domain, invert);
   if (r_curve) {
-    geometry_set.replace_curve(r_curve.release());
+    geometry_set.replace_curve(curve_eval_to_curves(*r_curve));
   }
   else {
     geometry_set.replace_curve(nullptr);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index c247a255e5b..41f1e6663d3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -529,7 +529,8 @@ static void duplicate_splines(GeometrySet &geometry_set,
 
   const GeometryComponent &src_component = *geometry_set.get_component_for_read(
       GEO_COMPONENT_TYPE_CURVE);
-  const CurveEval &curve = *geometry_set.get_curve_for_read();
+  const std::unique_ptr curve = curves_to_curve_eval(
+      *geometry_set.get_curve_for_read());
   const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
   GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
   FieldEvaluator evaluator{field_context, domain_size};
@@ -547,11 +548,11 @@ static void duplicate_splines(GeometrySet &geometry_set,
     int count = std::max(counts[selection[i_spline]], 0);
     curve_offsets[i_spline] = dst_splines_size;
     dst_splines_size += count;
-    dst_points_size += count * curve.splines()[selection[i_spline]]->size();
+    dst_points_size += count * curve->splines()[selection[i_spline]]->size();
   }
   curve_offsets.last() = dst_splines_size;
 
-  Array control_point_offsets = curve.control_point_offsets();
+  Array control_point_offsets = curve->control_point_offsets();
   Array point_mapping(dst_points_size);
 
   std::unique_ptr new_curve = std::make_unique();
@@ -559,8 +560,8 @@ static void duplicate_splines(GeometrySet &geometry_set,
   for (const int i_spline : selection.index_range()) {
     const IndexRange spline_range = range_for_offsets_index(curve_offsets, i_spline);
     for ([[maybe_unused]] const int i_duplicate : IndexRange(spline_range.size())) {
-      SplinePtr spline = curve.splines()[selection[i_spline]]->copy();
-      for (const int i_point : IndexRange(curve.splines()[selection[i_spline]]->size())) {
+      SplinePtr spline = curve->splines()[selection[i_spline]]->copy();
+      for (const int i_point : IndexRange(curve->splines()[selection[i_spline]]->size())) {
         point_mapping[point_index++] = control_point_offsets[selection[i_spline]] + i_point;
       }
       new_curve->add_spline(std::move(spline));
@@ -569,7 +570,7 @@ static void duplicate_splines(GeometrySet &geometry_set,
   new_curve->attributes.reallocate(new_curve->splines().size());
 
   CurveComponent dst_component;
-  dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+  dst_component.replace(curve_eval_to_curves(*new_curve), GeometryOwnershipType::Editable);
 
   Vector skip(
       {"position", "radius", "resolution", "cyclic", "tilt", "handle_left", "handle_right"});
@@ -577,7 +578,7 @@ static void duplicate_splines(GeometrySet &geometry_set,
   copy_spline_attributes_without_id(
       geometry_set, point_mapping, curve_offsets, skip, src_component, dst_component);
 
-  copy_stable_id_splines(curve, selection, curve_offsets, src_component, dst_component);
+  copy_stable_id_splines(*curve, selection, curve_offsets, src_component, dst_component);
 
   if (attributes.duplicate_index) {
     create_duplicate_index_attribute(
@@ -786,8 +787,9 @@ static void duplicate_points_curve(const GeometryComponentType component_type,
   Array offsets = accumulate_counts_to_offsets(selection, counts);
 
   CurveComponent &curve_component = geometry_set.get_component_for_write();
-  const CurveEval &curve = *geometry_set.get_curve_for_read();
-  Array control_point_offsets = curve.control_point_offsets();
+  const std::unique_ptr curve = curves_to_curve_eval(
+      *geometry_set.get_curve_for_read());
+  Array control_point_offsets = curve->control_point_offsets();
   std::unique_ptr new_curve = std::make_unique();
 
   Array parent(domain_size);
@@ -802,7 +804,7 @@ static void duplicate_points_curve(const GeometryComponentType component_type,
   for (const int i_point : selection) {
     const IndexRange point_range = range_for_offsets_index(offsets, i_point);
     for ([[maybe_unused]] const int i_duplicate : IndexRange(point_range.size())) {
-      const SplinePtr &parent_spline = curve.splines()[parent[i_point]];
+      const SplinePtr &parent_spline = curve->splines()[parent[i_point]];
       switch (parent_spline->type()) {
         case CurveType::CURVE_TYPE_BEZIER: {
           std::unique_ptr spline = std::make_unique();
@@ -833,7 +835,7 @@ static void duplicate_points_curve(const GeometryComponentType component_type,
   }
   new_curve->attributes.reallocate(new_curve->splines().size());
   CurveComponent dst_component;
-  dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+  dst_component.replace(curve_eval_to_curves(*new_curve), GeometryOwnershipType::Editable);
 
   copy_point_attributes_without_id(
       geometry_set, GEO_COMPONENT_TYPE_CURVE, false, offsets, src_component, dst_component);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc
index 4537721d173..f952e15fbbe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc
@@ -19,10 +19,10 @@ static void node_declare(NodeDeclarationBuilder &b)
 static VArray construct_spline_length_gvarray(const CurveComponent &component,
                                                      const AttributeDomain domain)
 {
-  const CurveEval *curve = component.get_for_read();
-  if (curve == nullptr) {
+  if (!component.has_curves()) {
     return {};
   }
+  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
 
   Span splines = curve->splines();
   auto length_fn = [splines](int i) { return splines[i]->length(); };
@@ -76,10 +76,10 @@ class SplineLengthFieldInput final : public GeometryFieldInput {
 static VArray construct_spline_count_gvarray(const CurveComponent &component,
                                                   const AttributeDomain domain)
 {
-  const CurveEval *curve = component.get_for_read();
-  if (curve == nullptr) {
+  if (!component.has_curves()) {
     return {};
   }
+  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
 
   Span splines = curve->splines();
   auto count_fn = [splines](int i) { return splines[i]->size(); };
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
index 4ee7c52a872..435dd969c03 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
@@ -77,21 +77,12 @@ static Array curve_tangent_point_domain(const CurveEval &curve)
 static VArray construct_curve_tangent_gvarray(const CurveComponent &component,
                                                       const AttributeDomain domain)
 {
-  const CurveEval *curve = component.get_for_read();
-  if (curve == nullptr) {
-    return nullptr;
+  if (!component.has_curves()) {
+    return {};
   }
+  const std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
 
   if (domain == ATTR_DOMAIN_POINT) {
-    const Span splines = curve->splines();
-
-    /* Use a reference to evaluated tangents if possible to avoid an allocation and a copy.
-     * This is only possible when there is only one poly spline. */
-    if (splines.size() == 1 && splines.first()->type() == CURVE_TYPE_POLY) {
-      const PolySpline &spline = static_cast(*splines.first());
-      return VArray::ForSpan(spline.evaluated_tangents());
-    }
-
     Array tangents = curve_tangent_point_domain(*curve);
     return VArray::ForContainer(std::move(tangents));
   }
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 3284378a2cb..91cde52f9eb 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
@@ -35,7 +35,7 @@ static void node_geo_exec(GeoNodeExecParams params)
     }
 
     std::unique_ptr curve = geometry::mesh_to_curve_convert(component, selection);
-    geometry_set.replace_curve(curve.release());
+    geometry_set.replace_curve(curve_eval_to_curves(*curve));
     geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
   });
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index 808d679fb73..85b042ddb9b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -35,7 +35,7 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
 }
 
 static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
-                                      GeometryComponent &component,
+                                      CurveComponent &component,
                                       const Field &selection_field,
                                       const Field &position_field,
                                       const Field &offset_field)
@@ -53,8 +53,7 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
   evaluator.evaluate();
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
 
-  CurveComponent &curve_component = *static_cast(&component);
-  CurveEval *curve = curve_component.get_for_write();
+  std::unique_ptr curve = curves_to_curve_eval(*component.get_for_read());
 
   int current_point = 0;
   int current_mask = 0;
@@ -126,6 +125,8 @@ static void set_position_in_component(const GeometryNodeCurveHandleMode mode,
       }
     }
   }
+
+  component.replace(curve_eval_to_curves(*curve), GeometryOwnershipType::Owned);
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -140,9 +141,11 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   bool has_bezier = false;
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve() &&
-        geometry_set.get_curve_for_read()->has_spline_with_type(CURVE_TYPE_BEZIER)) {
-      has_bezier = true;
+    if (geometry_set.has_curve()) {
+      const std::unique_ptr curve = curves_to_curve_eval(
+          *geometry_set.get_curve_for_read());
+      has_bezier = curve->has_spline_with_type(CURVE_TYPE_BEZIER);
+
       set_position_in_component(mode,
                                 geometry_set.get_component_for_write(),
                                 selection_field,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
index f1353bda9ce..e1c252d0081 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
@@ -45,7 +45,9 @@ static void node_geo_exec(GeoNodeExecParams params)
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
     if (geometry_set.has_curve()) {
       if (only_poly) {
-        for (const SplinePtr &spline : geometry_set.get_curve_for_read()->splines()) {
+        const std::unique_ptr curve = curves_to_curve_eval(
+            *geometry_set.get_curve_for_read());
+        for (const SplinePtr &spline : curve->splines()) {
           if (ELEM(spline->type(), CURVE_TYPE_BEZIER, CURVE_TYPE_NURBS)) {
             only_poly = false;
             break;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
index ddc0bb2bc11..45156e0cbf8 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
@@ -298,7 +298,8 @@ static Map create_curve_instances(GeoNodeExecParams ¶ms,
       layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
     }
 
-    GeometrySet geometry_set_curve = GeometrySet::create_with_curve(curve_eval.release());
+    GeometrySet geometry_set_curve = GeometrySet::create_with_curve(
+        curve_eval_to_curves(*curve_eval));
     handles.add_new(layout.char_codes[i],
                     instance_component.add_reference(std::move(geometry_set_curve)));
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
index 5950a2a16d2..045dea77f38 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
@@ -10,6 +10,7 @@
 #include "DNA_pointcloud_types.h"
 #include "DNA_volume_types.h"
 
+#include "BKE_curves.hh"
 #include "BKE_mesh.h"
 #include "BKE_pointcloud.h"
 #include "BKE_spline.hh"
@@ -125,8 +126,10 @@ static void translate_geometry_set(GeometrySet &geometry,
                                    const float3 translation,
                                    const Depsgraph &depsgraph)
 {
-  if (CurveEval *curve = geometry.get_curve_for_write()) {
+  if (Curves *curves = geometry.get_curve_for_write()) {
+    std::unique_ptr curve = curves_to_curve_eval(*curves);
     curve->translate(translation);
+    geometry.replace_curve(curve_eval_to_curves(*curve));
   }
   if (Mesh *mesh = geometry.get_mesh_for_write()) {
     translate_mesh(*mesh, translation);
@@ -146,8 +149,10 @@ void transform_geometry_set(GeometrySet &geometry,
                             const float4x4 &transform,
                             const Depsgraph &depsgraph)
 {
-  if (CurveEval *curve = geometry.get_curve_for_write()) {
+  if (Curves *curves = geometry.get_curve_for_write()) {
+    std::unique_ptr curve = curves_to_curve_eval(*curves);
     curve->transform(transform);
+    geometry.replace_curve(curve_eval_to_curves(*curve));
   }
   if (Mesh *mesh = geometry.get_mesh_for_write()) {
     transform_mesh(*mesh, transform);
-- 
cgit v1.2.3


From 245722866d6977c8b440e0f468ebf6528d3970e1 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 10:53:30 -0500
Subject: Cleanup: Rename geometry set "curve" functions to "curves"

Ref T95355
---
 source/blender/blenkernel/BKE_geometry_set.hh          | 18 +++++++++---------
 source/blender/blenkernel/intern/displist.cc           |  2 +-
 source/blender/blenkernel/intern/geometry_set.cc       | 16 ++++++++--------
 source/blender/blenkernel/intern/mesh_convert.cc       |  2 +-
 source/blender/geometry/intern/realize_instances.cc    |  2 +-
 .../nodes/legacy/node_geo_legacy_curve_endpoints.cc    |  2 +-
 .../nodes/legacy/node_geo_legacy_curve_reverse.cc      |  2 +-
 .../nodes/legacy/node_geo_legacy_curve_set_handles.cc  |  2 +-
 .../nodes/legacy/node_geo_legacy_curve_spline_type.cc  |  4 ++--
 .../nodes/legacy/node_geo_legacy_curve_subdivide.cc    |  4 ++--
 .../nodes/legacy/node_geo_legacy_curve_to_points.cc    |  2 +-
 .../nodes/legacy/node_geo_legacy_mesh_to_curve.cc      |  2 +-
 .../geometry/nodes/node_geo_attribute_domain_size.cc   |  2 +-
 .../nodes/geometry/nodes/node_geo_convex_hull.cc       | 12 ++++++------
 .../nodes/geometry/nodes/node_geo_curve_fill.cc        |  4 ++--
 .../nodes/geometry/nodes/node_geo_curve_fillet.cc      |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_length.cc      |  4 ++--
 .../geometry/nodes/node_geo_curve_primitive_arc.cc     |  4 ++--
 .../nodes/node_geo_curve_primitive_bezier_segment.cc   |  2 +-
 .../geometry/nodes/node_geo_curve_primitive_circle.cc  |  2 +-
 .../geometry/nodes/node_geo_curve_primitive_line.cc    |  2 +-
 .../nodes/node_geo_curve_primitive_quadratic_bezier.cc |  2 +-
 .../nodes/node_geo_curve_primitive_quadrilateral.cc    |  2 +-
 .../geometry/nodes/node_geo_curve_primitive_spiral.cc  |  2 +-
 .../geometry/nodes/node_geo_curve_primitive_star.cc    |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_resample.cc    |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_reverse.cc     |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_sample.cc      |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_set_handles.cc |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_spline_type.cc |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_subdivide.cc   |  2 +-
 .../nodes/geometry/nodes/node_geo_curve_to_mesh.cc     | 10 +++++-----
 .../nodes/geometry/nodes/node_geo_curve_to_points.cc   |  4 ++--
 .../nodes/geometry/nodes/node_geo_curve_trim.cc        |  4 ++--
 .../nodes/geometry/nodes/node_geo_delete_geometry.cc   |  2 +-
 .../geometry/nodes/node_geo_duplicate_elements.cc      |  8 ++++----
 .../nodes/geometry/nodes/node_geo_set_curve_handles.cc |  4 ++--
 .../nodes/geometry/nodes/node_geo_set_curve_radius.cc  |  2 +-
 .../nodes/geometry/nodes/node_geo_set_curve_tilt.cc    |  2 +-
 .../nodes/geometry/nodes/node_geo_set_spline_cyclic.cc |  2 +-
 .../geometry/nodes/node_geo_set_spline_resolution.cc   |  4 ++--
 .../nodes/geometry/nodes/node_geo_string_to_curves.cc  |  2 +-
 .../geometry/nodes/node_geo_transfer_attribute.cc      |  2 +-
 .../blender/nodes/geometry/nodes/node_geo_transform.cc |  4 ++--
 44 files changed, 81 insertions(+), 81 deletions(-)

diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index f11bfb7692a..f7767cc2a60 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -413,9 +413,9 @@ struct GeometrySet {
   static GeometrySet create_with_pointcloud(
       PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
   /**
-   * Create a new geometry set that only contains the given curve.
+   * Create a new geometry set that only contains the given curves.
    */
-  static GeometrySet create_with_curve(
+  static GeometrySet create_with_curves(
       Curves *curves, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
 
   /* Utility methods for access. */
@@ -436,9 +436,9 @@ struct GeometrySet {
    */
   bool has_volume() const;
   /**
-   * Returns true when the geometry set has a curve component that has a curve.
+   * Returns true when the geometry set has a curves component that has a curves data-block.
    */
-  bool has_curve() const;
+  bool has_curves() const;
   /**
    * Returns true when the geometry set has any data that is not an instance.
    */
@@ -461,9 +461,9 @@ struct GeometrySet {
    */
   const Volume *get_volume_for_read() const;
   /**
-   * Returns a read-only curve or null.
+   * Returns a read-only curves data-block or null.
    */
-  const Curves *get_curve_for_read() const;
+  const Curves *get_curves_for_read() const;
 
   /**
    * Returns a mutable mesh or null. No ownership is transferred.
@@ -478,9 +478,9 @@ struct GeometrySet {
    */
   Volume *get_volume_for_write();
   /**
-   * Returns a mutable curve or null. No ownership is transferred.
+   * Returns a mutable curves data-block or null. No ownership is transferred.
    */
-  Curves *get_curve_for_write();
+  Curves *get_curves_for_write();
 
   /* Utility methods for replacement. */
   /**
@@ -498,7 +498,7 @@ struct GeometrySet {
   void replace_volume(Volume *volume,
                       GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
   /**
-   * Clear the existing curve and replace it with the given one.
+   * Clear the existing curves data-block and replace it with the given one.
    */
   void replace_curve(Curves *curves,
                      GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc
index 97445851ffa..f0894ee04e2 100644
--- a/source/blender/blenkernel/intern/displist.cc
+++ b/source/blender/blenkernel/intern/displist.cc
@@ -1490,7 +1490,7 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
   else {
     GeometrySet geometry = evaluate_curve_type_object(depsgraph, scene, ob, for_render, dispbase);
 
-    if (geometry.has_curve()) {
+    if (geometry.has_curves()) {
       /* 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
diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc
index 73572e30d61..ca372ba8f38 100644
--- a/source/blender/blenkernel/intern/geometry_set.cc
+++ b/source/blender/blenkernel/intern/geometry_set.cc
@@ -187,7 +187,7 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma
   if (volume != nullptr) {
     have_minmax |= BKE_volume_min_max(volume, *r_min, *r_max);
   }
-  const Curves *curves = this->get_curve_for_read();
+  const Curves *curves = this->get_curves_for_read();
   if (curves != nullptr) {
     std::unique_ptr curve = curves_to_curve_eval(*curves);
     /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */
@@ -260,7 +260,7 @@ const Volume *GeometrySet::get_volume_for_read() const
   return (component == nullptr) ? nullptr : component->get_for_read();
 }
 
-const Curves *GeometrySet::get_curve_for_read() const
+const Curves *GeometrySet::get_curves_for_read() const
 {
   const CurveComponent *component = this->get_component_for_read();
   return (component == nullptr) ? nullptr : component->get_for_read();
@@ -284,7 +284,7 @@ bool GeometrySet::has_volume() const
   return component != nullptr && component->has_volume();
 }
 
-bool GeometrySet::has_curve() const
+bool GeometrySet::has_curves() const
 {
   const CurveComponent *component = this->get_component_for_read();
   return component != nullptr && component->has_curves();
@@ -304,8 +304,8 @@ bool GeometrySet::has_realized_data() const
 
 bool GeometrySet::is_empty() const
 {
-  return !(this->has_mesh() || this->has_curve() || this->has_pointcloud() || this->has_volume() ||
-           this->has_instances());
+  return !(this->has_mesh() || this->has_curves() || this->has_pointcloud() ||
+           this->has_volume() || this->has_instances());
 }
 
 GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership)
@@ -329,7 +329,7 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud,
   return geometry_set;
 }
 
-GeometrySet GeometrySet::create_with_curve(Curves *curves, GeometryOwnershipType ownership)
+GeometrySet GeometrySet::create_with_curves(Curves *curves, GeometryOwnershipType ownership)
 {
   GeometrySet geometry_set;
   if (curves != nullptr) {
@@ -359,7 +359,7 @@ void GeometrySet::replace_curve(Curves *curves, GeometryOwnershipType ownership)
     this->remove();
     return;
   }
-  if (curves == this->get_curve_for_read()) {
+  if (curves == this->get_curves_for_read()) {
     return;
   }
   this->remove();
@@ -413,7 +413,7 @@ Volume *GeometrySet::get_volume_for_write()
   return component == nullptr ? nullptr : component->get_for_write();
 }
 
-Curves *GeometrySet::get_curve_for_write()
+Curves *GeometrySet::get_curves_for_write()
 {
   CurveComponent *component = this->get_component_ptr();
   return component == nullptr ? nullptr : component->get_for_write();
diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc
index 6b5c04ac25e..1542f7119d1 100644
--- a/source/blender/blenkernel/intern/mesh_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_convert.cc
@@ -957,7 +957,7 @@ static const Curves *get_evaluated_curves_from_object(const Object *object)
 {
   GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval;
   if (geometry_set_eval) {
-    return geometry_set_eval->get_curve_for_read();
+    return geometry_set_eval->get_curves_for_read();
   }
   return nullptr;
 }
diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc
index 149ae70dda1..4f7024eea82 100644
--- a/source/blender/geometry/intern/realize_instances.cc
+++ b/source/blender/geometry/intern/realize_instances.cc
@@ -1041,7 +1041,7 @@ static OrderedAttributes gather_generic_curve_attributes_to_propagate(
 static void gather_curves_to_realize(const GeometrySet &geometry_set,
                                      VectorSet &r_curves)
 {
-  if (const Curves *curves = geometry_set.get_curve_for_read()) {
+  if (const Curves *curves = geometry_set.get_curves_for_read()) {
     if (curves->geometry.curve_size != 0) {
       r_curves.add(curves);
     }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
index e6f3e483f1f..0980c2d6e72 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc
@@ -137,7 +137,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   geometry_set = geometry::realize_instances_legacy(geometry_set);
 
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_default_remaining_outputs();
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
index 78e36784be7..2fe06a17adf 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc
@@ -19,7 +19,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 {
   GeometrySet geometry_set = params.extract_input("Curve");
   geometry_set = geometry::realize_instances_legacy(geometry_set);
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_output("Curve", geometry_set);
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
index 8ab43909a20..537c7c42610 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc
@@ -56,7 +56,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   GeometrySet geometry_set = params.extract_input("Curve");
   geometry_set = geometry::realize_instances_legacy(geometry_set);
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_output("Curve", geometry_set);
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
index e15e7339107..4e3b0839da7 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc
@@ -236,7 +236,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   GeometrySet geometry_set = params.extract_input("Curve");
   geometry_set = geometry::realize_instances_legacy(geometry_set);
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_output("Curve", geometry_set);
     return;
   }
@@ -269,7 +269,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   new_curve->attributes = curve->attributes;
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*new_curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*new_curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_curve_spline_type_cc
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
index 2a8ab2990db..03f7aec8838 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc
@@ -342,7 +342,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   geometry_set = geometry::realize_instances_legacy(geometry_set);
 
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_output("Geometry", geometry_set);
     return;
   }
@@ -358,7 +358,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       *curves_to_curve_eval(*component.get_for_read()), cuts);
 
   params.set_output("Geometry",
-                    GeometrySet::create_with_curve(curve_eval_to_curves(*output_curve)));
+                    GeometrySet::create_with_curves(curve_eval_to_curves(*output_curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_curve_subdivide_cc
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
index f68dd6b6b0c..f8fcc3cc363 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc
@@ -283,7 +283,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   geometry_set = geometry::realize_instances_legacy(geometry_set);
 
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     params.set_output("Geometry", GeometrySet());
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
index cb5a757811e..8991261a21a 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc
@@ -48,7 +48,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   std::unique_ptr curve = geometry::mesh_to_curve_convert(
       component, IndexMask(selected_edge_indices));
 
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_legacy_mesh_to_curve_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc
index b6d677154d0..b3fe9d160b3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc
@@ -84,7 +84,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       break;
     }
     case GEO_COMPONENT_TYPE_CURVE: {
-      if (geometry_set.has_curve()) {
+      if (geometry_set.has_curves()) {
         const CurveComponent *component = geometry_set.get_component_for_read();
         params.set_output("Point Count", component->attribute_domain_size(ATTR_DOMAIN_POINT));
         params.set_output("Spline Count", component->attribute_domain_size(ATTR_DOMAIN_CURVE));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
index 44b9857e791..412f35d62fd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
@@ -161,9 +161,9 @@ static Mesh *compute_hull(const GeometrySet &geometry_set)
     positions_span = varray.get_internal_span();
   }
 
-  if (geometry_set.has_curve()) {
+  if (geometry_set.has_curves()) {
     const std::unique_ptr curve = curves_to_curve_eval(
-        *geometry_set.get_curve_for_read());
+        *geometry_set.get_curves_for_read());
     for (const SplinePtr &spline : curve->splines()) {
       positions_span = spline->evaluated_positions();
       total_size += positions_span.size();
@@ -202,9 +202,9 @@ static Mesh *compute_hull(const GeometrySet &geometry_set)
     offset += varray.size();
   }
 
-  if (geometry_set.has_curve()) {
+  if (geometry_set.has_curves()) {
     const std::unique_ptr curve = curves_to_curve_eval(
-        *geometry_set.get_curve_for_read());
+        *geometry_set.get_curves_for_read());
     for (const SplinePtr &spline : curve->splines()) {
       Span array = spline->evaluated_positions();
       positions.as_mutable_span().slice(offset, array.size()).copy_from(array);
@@ -274,8 +274,8 @@ static Mesh *convex_hull_from_instances(const GeometrySet &geometry_set)
     if (set.has_mesh()) {
       read_positions(*set.get_component_for_read(), transforms, &coords);
     }
-    if (set.has_curve()) {
-      read_curve_positions(*curves_to_curve_eval(*set.get_curve_for_read()), transforms, &coords);
+    if (set.has_curves()) {
+      read_curve_positions(*curves_to_curve_eval(*set.get_curves_for_read()), transforms, &coords);
     }
   }
   return hull_from_bullet(nullptr, coords);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
index c1220746c22..6702ee6c0aa 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
@@ -113,12 +113,12 @@ static Mesh *cdt_to_mesh(const blender::meshintersect::CDT_result &resul
 
 static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode)
 {
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     return;
   }
 
   const std::unique_ptr curve = curves_to_curve_eval(
-      *geometry_set.get_curve_for_read());
+      *geometry_set.get_curves_for_read());
   if (curve->splines().is_empty()) {
     geometry_set.replace_curve(nullptr);
     return;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
index 6742b1103ee..24d72ad553b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -572,7 +572,7 @@ static void calculate_curve_fillet(GeometrySet &geometry_set,
                                    const std::optional> &count_field,
                                    const bool limit_radius)
 {
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     return;
   }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
index c7c822db983..d5769c691c8 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc
@@ -14,11 +14,11 @@ static void node_declare(NodeDeclarationBuilder &b)
 static void node_geo_exec(GeoNodeExecParams params)
 {
   GeometrySet curve_set = params.extract_input("Curve");
-  if (!curve_set.has_curve()) {
+  if (!curve_set.has_curves()) {
     params.set_default_remaining_outputs();
     return;
   }
-  const std::unique_ptr curve = curves_to_curve_eval(*curve_set.get_curve_for_read());
+  const std::unique_ptr curve = curves_to_curve_eval(*curve_set.get_curves_for_read());
   float length = 0.0f;
   for (const SplinePtr &spline : curve->splines()) {
     length += spline->length();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
index 9fbc01935ce..339e65321b1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
@@ -335,7 +335,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                            r_center,
                                            r_normal,
                                            r_radius);
-      params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+      params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
       params.set_output("Center", r_center);
       params.set_output("Normal", r_normal);
       params.set_output("Radius", r_radius);
@@ -350,7 +350,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                            params.extract_input("Connect Center"),
                                            params.extract_input("Invert Arc"));
 
-      params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+      params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
       break;
     }
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
index eabd6aa1aa0..78e1613b630 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
@@ -113,7 +113,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("End Handle"),
       std::max(params.extract_input("Resolution"), 1),
       mode);
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
index 73929dce1af..54d7c488fb7 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
@@ -203,7 +203,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   }
 
   if (curve) {
-    params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+    params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
   }
   else {
     params.set_default_remaining_outputs();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
index 066947de496..ece7e44cc35 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
@@ -111,7 +111,7 @@ static void node_geo_exec(GeoNodeExecParams params)
                                         params.extract_input("Length"));
   }
 
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_line_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
index 1b055a3a3d5..c6c33cffa54 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
@@ -61,7 +61,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("Middle"),
       params.extract_input("End"),
       std::max(params.extract_input("Resolution"), 3));
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc
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 7842ad028b7..cc2b9e4bc4e 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
@@ -264,7 +264,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   curve->add_spline(std::move(spline));
   curve->attributes.reallocate(curve->splines().size());
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc
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 d5bc70e37cd..d4f7c4ed5f1 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
@@ -86,7 +86,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       params.extract_input("End Radius"),
       params.extract_input("Height"),
       params.extract_input("Reverse"));
-  params.set_output("Curve", GeometrySet::create_with_curve(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_spiral_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
index e02bcfb73cc..5b6852abfc2 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
@@ -83,7 +83,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       std::max(params.extract_input("Outer Radius"), 0.0f),
       params.extract_input("Twist"),
       std::max(params.extract_input("Points"), 3));
-  GeometrySet output = GeometrySet::create_with_curve(curve_eval_to_curves(*curve));
+  GeometrySet output = GeometrySet::create_with_curves(curve_eval_to_curves(*curve));
 
   if (params.output_is_required("Outer Points")) {
     StrongAnonymousAttributeID attribute_output("Outer Points");
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 fffc6188dfd..c5814a9a1dd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
@@ -235,7 +235,7 @@ static std::unique_ptr resample_curve(const CurveComponent *component
 static void geometry_set_curve_resample(GeometrySet &geometry_set,
                                         const SampleModeParam &mode_param)
 {
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     return;
   }
 
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 06ca0483063..8393f9615aa 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
@@ -20,7 +20,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   GeometrySet geometry_set = params.extract_input("Curve");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (!geometry_set.has_curve()) {
+    if (!geometry_set.has_curves()) {
       return;
     }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
index 222c28dd21b..6661d03a851 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
@@ -122,7 +122,7 @@ class SampleCurveFunction : public fn::MultiFunction {
       }
     };
 
-    if (!geometry_set_.has_curve()) {
+    if (!geometry_set_.has_curves()) {
       return return_default();
     }
 
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 a892ff419e5..e8da4154586 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
@@ -60,7 +60,7 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   bool has_bezier_spline = false;
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (!geometry_set.has_curve()) {
+    if (!geometry_set.has_curves()) {
       return;
     }
 
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 d691726da27..55610ec86ab 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
@@ -362,7 +362,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   Field selection_field = params.extract_input>("Selection");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (!geometry_set.has_curve()) {
+    if (!geometry_set.has_curves()) {
       return;
     }
 
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 3297ddcae85..bbe57b2b3fa 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -324,7 +324,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   Field cuts_field = params.extract_input>("Cuts");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (!geometry_set.has_curve()) {
+    if (!geometry_set.has_curves()) {
       return;
     }
 
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 753a6fe7278..e7a8c61290b 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
@@ -28,8 +28,8 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
                                        const bool fill_caps)
 {
   const std::unique_ptr curve = curves_to_curve_eval(
-      *geometry_set.get_curve_for_read());
-  const Curves *profile_curves = profile_set.get_curve_for_read();
+      *geometry_set.get_curves_for_read());
+  const Curves *profile_curves = profile_set.get_curves_for_read();
 
   if (profile_curves == nullptr) {
     Mesh *mesh = bke::curve_to_wire_mesh(*curve);
@@ -48,10 +48,10 @@ static void node_geo_exec(GeoNodeExecParams params)
   GeometrySet profile_set = params.extract_input("Profile Curve");
   const bool fill_caps = params.extract_input("Fill Caps");
 
-  bool has_curve = false;
+  bool has_curves = false;
   curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
-      has_curve = true;
+    if (geometry_set.has_curves()) {
+      has_curves = true;
       geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps);
     }
     geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
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 33e7a818c87..1eb18b2f910 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
@@ -321,12 +321,12 @@ static void node_geo_exec(GeoNodeExecParams params)
   attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (!geometry_set.has_curve()) {
+    if (!geometry_set.has_curves()) {
       geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
       return;
     }
     const std::unique_ptr curve = curves_to_curve_eval(
-        *geometry_set.get_curve_for_read());
+        *geometry_set.get_curves_for_read());
     const Span splines = curve->splines();
     curve->assert_valid_point_attributes();
 
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 649391a2346..a3dab1b50fe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -500,7 +500,7 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
                                     Field &start_field,
                                     Field &end_field)
 {
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     return;
   }
 
@@ -515,7 +515,7 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
   const blender::VArray &starts = evaluator.get_evaluated(0);
   const blender::VArray &ends = evaluator.get_evaluated(1);
 
-  std::unique_ptr curve = curves_to_curve_eval(*geometry_set.get_curve_for_read());
+  std::unique_ptr curve = curves_to_curve_eval(*geometry_set.get_curves_for_read());
   MutableSpan splines = curve->splines();
 
   threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
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 9fc71ebe8d1..3baee8a25bb 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -1286,7 +1286,7 @@ void separate_geometry(GeometrySet &geometry_set,
       some_valid_domain = true;
     }
   }
-  if (geometry_set.has_curve()) {
+  if (geometry_set.has_curves()) {
     if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) {
       file_ns::separate_curve_selection(geometry_set, selection_field, domain, invert);
       some_valid_domain = true;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index 41f1e6663d3..c58bab239e6 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -521,7 +521,7 @@ static void duplicate_splines(GeometrySet &geometry_set,
                               const Field &selection_field,
                               IndexAttributes &attributes)
 {
-  if (!geometry_set.has_curve()) {
+  if (!geometry_set.has_curves()) {
     geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
     return;
   }
@@ -530,7 +530,7 @@ static void duplicate_splines(GeometrySet &geometry_set,
   const GeometryComponent &src_component = *geometry_set.get_component_for_read(
       GEO_COMPONENT_TYPE_CURVE);
   const std::unique_ptr curve = curves_to_curve_eval(
-      *geometry_set.get_curve_for_read());
+      *geometry_set.get_curves_for_read());
   const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
   GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
   FieldEvaluator evaluator{field_context, domain_size};
@@ -788,7 +788,7 @@ static void duplicate_points_curve(const GeometryComponentType component_type,
 
   CurveComponent &curve_component = geometry_set.get_component_for_write();
   const std::unique_ptr curve = curves_to_curve_eval(
-      *geometry_set.get_curve_for_read());
+      *geometry_set.get_curves_for_read());
   Array control_point_offsets = curve->control_point_offsets();
   std::unique_ptr new_curve = std::make_unique();
 
@@ -933,7 +933,7 @@ static void duplicate_points(GeometrySet &geometry_set,
                              const Field &selection_field,
                              IndexAttributes &attributes)
 {
-  if (!geometry_set.has_mesh() && !geometry_set.has_curve() && !geometry_set.has_pointcloud()) {
+  if (!geometry_set.has_mesh() && !geometry_set.has_curves() && !geometry_set.has_pointcloud()) {
     geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index 85b042ddb9b..301410f5126 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -141,9 +141,9 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   bool has_bezier = false;
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
+    if (geometry_set.has_curves()) {
       const std::unique_ptr curve = curves_to_curve_eval(
-          *geometry_set.get_curve_for_read());
+          *geometry_set.get_curves_for_read());
       has_bezier = curve->has_spline_with_type(CURVE_TYPE_BEZIER);
 
       set_position_in_component(mode,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc
index 2f59d008df0..a23a6c09551 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc
@@ -44,7 +44,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   Field radii_field = params.extract_input>("Radius");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
+    if (geometry_set.has_curves()) {
       set_radius_in_component(
           geometry_set.get_component_for_write(), selection_field, radii_field);
     }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc
index b94782b8c4c..1155c97dc38 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc
@@ -40,7 +40,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   Field tilt_field = params.extract_input>("Tilt");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
+    if (geometry_set.has_curves()) {
       set_tilt_in_component(
           geometry_set.get_component_for_write(), selection_field, tilt_field);
     }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc
index 70e363064cd..dc7f3b1343a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc
@@ -40,7 +40,7 @@ static void node_geo_exec(GeoNodeExecParams params)
   Field cyclic_field = params.extract_input>("Cyclic");
 
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
+    if (geometry_set.has_curves()) {
       set_cyclic_in_component(
           geometry_set.get_component_for_write(), selection_field, cyclic_field);
     }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
index e1c252d0081..da8d7bcf255 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc
@@ -43,10 +43,10 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   bool only_poly = true;
   geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_curve()) {
+    if (geometry_set.has_curves()) {
       if (only_poly) {
         const std::unique_ptr curve = curves_to_curve_eval(
-            *geometry_set.get_curve_for_read());
+            *geometry_set.get_curves_for_read());
         for (const SplinePtr &spline : curve->splines()) {
           if (ELEM(spline->type(), CURVE_TYPE_BEZIER, CURVE_TYPE_NURBS)) {
             only_poly = false;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
index 45156e0cbf8..bc34a1a6f2c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
@@ -298,7 +298,7 @@ static Map create_curve_instances(GeoNodeExecParams ¶ms,
       layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
     }
 
-    GeometrySet geometry_set_curve = GeometrySet::create_with_curve(
+    GeometrySet geometry_set_curve = GeometrySet::create_with_curves(
         curve_eval_to_curves(*curve_eval));
     handles.add_new(layout.char_codes[i],
                     instance_component.add_reference(std::move(geometry_set_curve)));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
index 789478873f6..2d5b0e58367 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
@@ -778,7 +778,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       break;
     }
     case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: {
-      if (geometry.has_curve() && !geometry.has_mesh() && !geometry.has_pointcloud()) {
+      if (geometry.has_curves() && !geometry.has_mesh() && !geometry.has_pointcloud()) {
         params.error_message_add(NodeWarningType::Error,
                                  TIP_("The source geometry must contain a mesh or a point cloud"));
         return return_default();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
index 045dea77f38..95cec8eab11 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
@@ -126,7 +126,7 @@ static void translate_geometry_set(GeometrySet &geometry,
                                    const float3 translation,
                                    const Depsgraph &depsgraph)
 {
-  if (Curves *curves = geometry.get_curve_for_write()) {
+  if (Curves *curves = geometry.get_curves_for_write()) {
     std::unique_ptr curve = curves_to_curve_eval(*curves);
     curve->translate(translation);
     geometry.replace_curve(curve_eval_to_curves(*curve));
@@ -149,7 +149,7 @@ void transform_geometry_set(GeometrySet &geometry,
                             const float4x4 &transform,
                             const Depsgraph &depsgraph)
 {
-  if (Curves *curves = geometry.get_curve_for_write()) {
+  if (Curves *curves = geometry.get_curves_for_write()) {
     std::unique_ptr curve = curves_to_curve_eval(*curves);
     curve->transform(transform);
     geometry.replace_curve(curve_eval_to_curves(*curve));
-- 
cgit v1.2.3


From 8c932ab43c28d476bd0bf671ef70448f075295ef Mon Sep 17 00:00:00 2001
From: Henrik Dick 
Date: Mon, 28 Feb 2022 18:26:58 +0100
Subject: Fix T96032: add null check to constraint operators

The constraint operators for delete, apply, copy and copy to selected
were missing null checks and could crash blender when called wrongly
from the python api.

Differential Revision: http://developer.blender.org/D14195
---
 source/blender/editors/object/object_constraint.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c
index d717271b9cb..3f4ed27a175 100644
--- a/source/blender/editors/object/object_constraint.c
+++ b/source/blender/editors/object/object_constraint.c
@@ -1443,6 +1443,11 @@ static int constraint_delete_exec(bContext *C, wmOperator *op)
   Main *bmain = CTX_data_main(C);
   Object *ob = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(C, op, ob, 0);
+
+  if (con == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
   ListBase *lb = ED_object_constraint_list_from_constraint(ob, con, NULL);
 
   /* Store name temporarily for report. */
@@ -1510,6 +1515,11 @@ static int constraint_apply_exec(bContext *C, wmOperator *op)
   Main *bmain = CTX_data_main(C);
   Object *ob = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(C, op, ob, 0);
+
+  if (con == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
   bPoseChannel *pchan;
   ListBase *constraints = ED_object_constraint_list_from_constraint(ob, con, &pchan);
 
@@ -1602,6 +1612,11 @@ static int constraint_copy_exec(bContext *C, wmOperator *op)
   Main *bmain = CTX_data_main(C);
   Object *ob = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(C, op, ob, 0);
+
+  if (con == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
   bPoseChannel *pchan;
   ListBase *constraints = ED_object_constraint_list_from_constraint(ob, con, &pchan);
 
@@ -1682,6 +1697,11 @@ static int constraint_copy_to_selected_exec(bContext *C, wmOperator *op)
   Main *bmain = CTX_data_main(C);
   Object *obact = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(C, op, obact, 0);
+
+  if (con == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
   bPoseChannel *pchan;
   ED_object_constraint_list_from_constraint(obact, con, &pchan);
 
-- 
cgit v1.2.3


From 44b0c70919e92f7b70f0e03eba9570ca8906ab90 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Mon, 28 Feb 2022 17:17:40 +0100
Subject: Fix T92288, T96041: instancing of shared mesh objects without
 modifiers broken

New code from the vertex normal refactor cfa53e0fbeed combined with older code
from 592759e3d62a that disabled instancing for custom normals and autosmooth
meant that instancing was always disabled.

However we do not need to disable instancing for custom normals and autosmooth
at all, this can be shared between instances just fine.
---
 source/blender/blenkernel/intern/DerivedMesh.cc | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc
index 411e5f81262..1fcf1bf1839 100644
--- a/source/blender/blenkernel/intern/DerivedMesh.cc
+++ b/source/blender/blenkernel/intern/DerivedMesh.cc
@@ -1304,12 +1304,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
    * we need to apply these back onto the Mesh. If we have no
    * Mesh then we need to build one. */
   if (mesh_final == nullptr) {
-    /* NOTE: this check on cdmask is a bit dodgy, it handles the issue at stake here (see T68211),
-     * but other cases might require similar handling?
-     * Could be a good idea to define a proper CustomData_MeshMask for that then. */
-    if (deformed_verts == nullptr && allow_shared_mesh &&
-        (final_datamask.lmask & CD_MASK_NORMAL) == 0 &&
-        (final_datamask.pmask & CD_MASK_NORMAL) == 0) {
+    if (deformed_verts == nullptr && allow_shared_mesh) {
       mesh_final = mesh_input;
     }
     else {
-- 
cgit v1.2.3


From 59a133b61f4b48a7e41b1f89effe921a905d0cae Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Mon, 28 Feb 2022 14:38:13 -0500
Subject: Cmake: Re-enable real snow add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 8d7728d0577..0297fe63365 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -394,7 +394,6 @@ if(WITH_PYTHON)
 	  PATTERN "addons/amaranth" EXCLUDE
 	  PATTERN "addons/mesh_tiny_cad" EXCLUDE
 	  PATTERN "addons/mesh_tissue" EXCLUDE
-	  PATTERN "addons/real_snow.py" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From 611157057b5f8bb7760ffc9f253786e5413c44b2 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 15:10:11 -0500
Subject: Cleanup: Use bool instead of int

---
 source/blender/blenloader/intern/writefile.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index 495054923f9..490328106ca 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -580,7 +580,7 @@ static WriteData *mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *curren
 
 /**
  * END the mywrite wrapper
- * \return 1 if write failed
+ * \return True if write failed
  * \return unknown global variable otherwise
  * \warning Talks to other functions with global parameters
  */
@@ -1256,12 +1256,12 @@ static bool do_history(const char *name, ReportList *reports)
   int hisnr = U.versions;
 
   if (U.versions == 0) {
-    return 0;
+    return false;
   }
 
   if (strlen(name) < 2) {
     BKE_report(reports, RPT_ERROR, "Unable to make version backup: filename too short");
-    return 1;
+    return true;
   }
 
   while (hisnr > 1) {
@@ -1287,7 +1287,7 @@ static bool do_history(const char *name, ReportList *reports)
     }
   }
 
-  return 0;
+  return false;
 }
 
 /** \} */
@@ -1334,7 +1334,7 @@ bool BLO_write_file(Main *mainvar,
   if (ww.open(&ww, tempname) == false) {
     BKE_reportf(
         reports, RPT_ERROR, "Cannot open file %s for writing: %s", tempname, strerror(errno));
-    return 0;
+    return false;
   }
 
   if (remap_mode == BLO_WRITE_PATH_REMAP_ABSOLUTE) {
@@ -1426,7 +1426,7 @@ bool BLO_write_file(Main *mainvar,
     BKE_report(reports, RPT_ERROR, strerror(errno));
     remove(tempname);
 
-    return 0;
+    return false;
   }
 
   /* file save to temporary file was successful */
@@ -1435,13 +1435,13 @@ bool BLO_write_file(Main *mainvar,
     const bool err_hist = do_history(filepath, reports);
     if (err_hist) {
       BKE_report(reports, RPT_ERROR, "Version backup failed (file saved with @)");
-      return 0;
+      return false;
     }
   }
 
   if (BLI_rename(tempname, filepath) != 0) {
     BKE_report(reports, RPT_ERROR, "Cannot change old file (file saved with @)");
-    return 0;
+    return false;
   }
 
   if (G.debug & G_DEBUG_IO && mainvar->lock != NULL) {
@@ -1449,7 +1449,7 @@ bool BLO_write_file(Main *mainvar,
     BLO_main_validate_libraries(mainvar, reports);
   }
 
-  return 1;
+  return true;
 }
 
 bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, int write_flags)
-- 
cgit v1.2.3


From eeb0279e890e7b021e0ac9dd48d2bcf6bef8300b Mon Sep 17 00:00:00 2001
From: Wannes Malfait 
Date: Mon, 28 Feb 2022 15:16:01 -0500
Subject: Geometry Nodes: Use std::move in dual mesh node

Add a std::move in some places to prevent arrays from being copied.
These cases were potentially optimized by the compiler, but this makes
it more explicit.

Differential Revision: https://developer.blender.org/D14129
---
 source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
index f50c2ed5322..5a2c32a6c8e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
@@ -646,13 +646,13 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
         Array shared_edges(loop_indices.size());
         vertex_ok = sort_vertex_polys(
             mesh_in, i, false, edge_types, loop_indices, shared_edges, sorted_corners);
-        vertex_shared_edges[i] = shared_edges;
+        vertex_shared_edges[i] = std::move(shared_edges);
       }
       else {
         Array shared_edges(loop_indices.size() - 1);
         vertex_ok = sort_vertex_polys(
             mesh_in, i, true, edge_types, loop_indices, shared_edges, sorted_corners);
-        vertex_shared_edges[i] = shared_edges;
+        vertex_shared_edges[i] = std::move(shared_edges);
       }
       if (!vertex_ok) {
         /* The sorting failed which means that the vertex is non-manifold and should be ignored
@@ -660,7 +660,7 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
         vertex_types[i] = VertexType::NonManifold;
         continue;
       }
-      vertex_corners[i] = sorted_corners;
+      vertex_corners[i] = std::move(sorted_corners);
     }
   });
 
-- 
cgit v1.2.3


From 75bb99fa40dd09e4ae0e92cca9398b929f855a2c Mon Sep 17 00:00:00 2001
From: Leon Schittek 
Date: Mon, 28 Feb 2022 15:52:00 -0500
Subject: Nodes: Improve readability of selected node links

This commit improves the drawing of selected node links:
 - Highlight the entire link to make it easier to spot where the link
   is going/coming from.
 - Always draw selected links on top, so they are always clearly
   visible.
 - Don't fade selected node links when the sockets they are connected
   to are out out view.
 - Dragged node links still get a partial highlight when they are only
   attached to one socket.

Differential Revision: https://developer.blender.org/D11930
---
 source/blender/blenkernel/BKE_node.h             |  1 +
 source/blender/blenkernel/intern/node.cc         |  5 +++++
 source/blender/editors/space_node/drawnode.cc    | 18 +++++++++---------
 source/blender/editors/space_node/node_draw.cc   | 16 ++++++++++++----
 source/blender/editors/space_node/node_intern.hh |  6 ++++--
 5 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 8a65279ae74..315e24485fa 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -681,6 +681,7 @@ void nodeRemLink(struct bNodeTree *ntree, struct bNodeLink *link);
 void nodeRemSocketLinks(struct bNodeTree *ntree, struct bNodeSocket *sock);
 void nodeMuteLinkToggle(struct bNodeTree *ntree, struct bNodeLink *link);
 bool nodeLinkIsHidden(const struct bNodeLink *link);
+bool nodeLinkIsSelected(const struct bNodeLink *link);
 void nodeInternalRelink(struct bNodeTree *ntree, struct bNode *node);
 
 void nodeToView(const struct bNode *node, float x, float y, float *rx, float *ry);
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 9fd9f9a1492..876ccc5351c 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -2438,6 +2438,11 @@ bool nodeLinkIsHidden(const bNodeLink *link)
   return nodeSocketIsHidden(link->fromsock) || nodeSocketIsHidden(link->tosock);
 }
 
+bool nodeLinkIsSelected(const bNodeLink *link)
+{
+  return (link->fromnode->flag & NODE_SELECT) || (link->tonode->flag & NODE_SELECT);
+}
+
 /* Adjust the indices of links connected to the given multi input socket after deleting the link at
  * `deleted_index`. This function also works if the link has not yet been deleted. */
 static void adjust_multi_input_indices_after_removed_link(bNodeTree *ntree,
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index b2c361ecaba..afb205f9f9e 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -1967,9 +1967,10 @@ void node_draw_link_bezier(const bContext &C,
                            const bNodeLink &link,
                            const int th_col1,
                            const int th_col2,
-                           const int th_col3)
+                           const int th_col3,
+                           const bool selected)
 {
-  const float dim_factor = node_link_dim_factor(v2d, link);
+  const float dim_factor = selected ? 1.0f : node_link_dim_factor(v2d, link);
   float thickness = 1.5f;
   float dash_factor = 1.0f;
 
@@ -2025,19 +2026,17 @@ void node_draw_link_bezier(const bContext &C,
     }
 
     /* Highlight links connected to selected nodes. */
-    const bool is_fromnode_selected = link.fromnode && link.fromnode->flag & SELECT;
-    const bool is_tonode_selected = link.tonode && link.tonode->flag & SELECT;
-    if (is_fromnode_selected || is_tonode_selected) {
+    if (selected) {
       float color_selected[4];
       UI_GetThemeColor4fv(TH_EDGE_SELECT, color_selected);
       const float alpha = color_selected[3];
 
       /* Interpolate color if highlight color is not fully transparent. */
       if (alpha != 0.0) {
-        if (is_fromnode_selected) {
+        if (link.fromsock) {
           interp_v3_v3v3(colors[1], colors[1], color_selected, alpha);
         }
-        if (is_tonode_selected) {
+        if (link.tosock) {
           interp_v3_v3v3(colors[2], colors[2], color_selected, alpha);
         }
       }
@@ -2102,7 +2101,8 @@ void node_draw_link_bezier(const bContext &C,
 void node_draw_link(const bContext &C,
                     const View2D &v2d,
                     const SpaceNode &snode,
-                    const bNodeLink &link)
+                    const bNodeLink &link,
+                    const bool selected)
 {
   int th_col1 = TH_WIRE_INNER, th_col2 = TH_WIRE_INNER, th_col3 = TH_WIRE;
 
@@ -2146,7 +2146,7 @@ void node_draw_link(const bContext &C,
     }
   }
 
-  node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3);
+  node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3, selected);
 }
 
 }  // namespace blender::ed::space_node
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 7f3bc8ccb13..7b4578e6c05 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -660,7 +660,7 @@ static void node_draw_mute_line(const bContext &C,
   GPU_blend(GPU_BLEND_ALPHA);
 
   LISTBASE_FOREACH (const bNodeLink *, link, &node.internal_links) {
-    node_draw_link_bezier(C, v2d, snode, *link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE);
+    node_draw_link_bezier(C, v2d, snode, *link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false);
   }
 
   GPU_blend(GPU_BLEND_NONE);
@@ -2650,10 +2650,18 @@ static void node_draw_nodetree(const bContext &C,
   nodelink_batch_start(snode);
 
   LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
-    if (!nodeLinkIsHidden(link)) {
-      node_draw_link(C, region.v2d, snode, *link);
+    if (!nodeLinkIsHidden(link) && !nodeLinkIsSelected(link)) {
+      node_draw_link(C, region.v2d, snode, *link, false);
     }
   }
+
+  /* Draw selected node links after the unselected ones, so they are shown on top. */
+  LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
+    if (!nodeLinkIsHidden(link) && nodeLinkIsSelected(link)) {
+      node_draw_link(C, region.v2d, snode, *link, true);
+    }
+  }
+  
   nodelink_batch_end(snode);
   GPU_blend(GPU_BLEND_NONE);
 
@@ -2838,7 +2846,7 @@ void node_draw_space(const bContext &C, ARegion ®ion)
     GPU_line_smooth(true);
     if (snode.runtime->linkdrag) {
       for (const bNodeLink *link : snode.runtime->linkdrag->links) {
-        node_draw_link(C, v2d, snode, *link);
+        node_draw_link(C, v2d, snode, *link, true);
       }
     }
     GPU_line_smooth(false);
diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh
index 592db3f7877..6b5beb6c0d6 100644
--- a/source/blender/editors/space_node/node_intern.hh
+++ b/source/blender/editors/space_node/node_intern.hh
@@ -196,7 +196,8 @@ void nodelink_batch_end(SpaceNode &snode);
 void node_draw_link(const bContext &C,
                     const View2D &v2d,
                     const SpaceNode &snode,
-                    const bNodeLink &link);
+                    const bNodeLink &link,
+                    bool selected);
 /**
  * Don't do shadows if th_col3 is -1.
  */
@@ -206,7 +207,8 @@ void node_draw_link_bezier(const bContext &C,
                            const bNodeLink &link,
                            int th_col1,
                            int th_col2,
-                           int th_col3);
+                           int th_col3,
+                           bool selected);
 /** If v2d not nullptr, it clips and returns 0 if not visible. */
 bool node_link_bezier_points(const View2D *v2d,
                              const SpaceNode *snode,
-- 
cgit v1.2.3


From 4c407f20a62c7d437a9b3f2498b658f968102573 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 16:20:55 -0500
Subject: Cleanup: Move object_add.c to C++

This patch was tested on the buildbot first, it builds on all platforms.
---
 source/blender/editors/object/CMakeLists.txt  |    2 +-
 source/blender/editors/object/object_add.c    | 3864 ------------------------
 source/blender/editors/object/object_add.cc   | 3885 +++++++++++++++++++++++++
 source/blender/editors/object/object_intern.h |    2 +-
 4 files changed, 3887 insertions(+), 3866 deletions(-)
 delete mode 100644 source/blender/editors/object/object_add.c
 create mode 100644 source/blender/editors/object/object_add.cc

diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt
index 54db59dc2fa..a94387961ee 100644
--- a/source/blender/editors/object/CMakeLists.txt
+++ b/source/blender/editors/object/CMakeLists.txt
@@ -28,7 +28,7 @@ set(INC
 )
 
 set(SRC
-  object_add.c
+  object_add.cc
   object_bake.c
   object_bake_api.c
   object_collection.c
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
deleted file mode 100644
index 7771012f2a1..00000000000
--- a/source/blender/editors/object/object_add.c
+++ /dev/null
@@ -1,3864 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later
- * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
-
-/** \file
- * \ingroup edobj
- */
-
-#include 
-#include 
-#include 
-
-#include "MEM_guardedalloc.h"
-
-#include "DNA_anim_types.h"
-#include "DNA_camera_types.h"
-#include "DNA_collection_types.h"
-#include "DNA_curve_types.h"
-#include "DNA_gpencil_modifier_types.h"
-#include "DNA_gpencil_types.h"
-#include "DNA_key_types.h"
-#include "DNA_light_types.h"
-#include "DNA_lightprobe_types.h"
-#include "DNA_material_types.h"
-#include "DNA_mesh_types.h"
-#include "DNA_meta_types.h"
-#include "DNA_object_fluidsim_types.h"
-#include "DNA_object_force_types.h"
-#include "DNA_object_types.h"
-#include "DNA_pointcloud_types.h"
-#include "DNA_scene_types.h"
-#include "DNA_vfont_types.h"
-
-#include "BLI_ghash.h"
-#include "BLI_listbase.h"
-#include "BLI_math.h"
-#include "BLI_string.h"
-#include "BLI_utildefines.h"
-
-#include "BLT_translation.h"
-
-#include "BKE_action.h"
-#include "BKE_anim_data.h"
-#include "BKE_armature.h"
-#include "BKE_camera.h"
-#include "BKE_collection.h"
-#include "BKE_constraint.h"
-#include "BKE_context.h"
-#include "BKE_curve.h"
-#include "BKE_curves.h"
-#include "BKE_displist.h"
-#include "BKE_duplilist.h"
-#include "BKE_effect.h"
-#include "BKE_geometry_set.h"
-#include "BKE_gpencil_curve.h"
-#include "BKE_gpencil_geom.h"
-#include "BKE_gpencil_modifier.h"
-#include "BKE_key.h"
-#include "BKE_lattice.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_light.h"
-#include "BKE_lightprobe.h"
-#include "BKE_main.h"
-#include "BKE_material.h"
-#include "BKE_mball.h"
-#include "BKE_mesh.h"
-#include "BKE_mesh_runtime.h"
-#include "BKE_nla.h"
-#include "BKE_object.h"
-#include "BKE_particle.h"
-#include "BKE_pointcloud.h"
-#include "BKE_report.h"
-#include "BKE_scene.h"
-#include "BKE_speaker.h"
-#include "BKE_vfont.h"
-#include "BKE_volume.h"
-
-#include "DEG_depsgraph.h"
-#include "DEG_depsgraph_build.h"
-#include "DEG_depsgraph_query.h"
-
-#include "RNA_access.h"
-#include "RNA_define.h"
-#include "RNA_enum_types.h"
-
-#include "UI_interface.h"
-
-#include "WM_api.h"
-#include "WM_types.h"
-
-#include "ED_armature.h"
-#include "ED_curve.h"
-#include "ED_gpencil.h"
-#include "ED_mball.h"
-#include "ED_mesh.h"
-#include "ED_node.h"
-#include "ED_object.h"
-#include "ED_outliner.h"
-#include "ED_physics.h"
-#include "ED_render.h"
-#include "ED_screen.h"
-#include "ED_select_utils.h"
-#include "ED_transform.h"
-#include "ED_view3d.h"
-
-#include "UI_resources.h"
-
-#include "object_intern.h"
-
-/* -------------------------------------------------------------------- */
-/** \name Local Enum Declarations
- * \{ */
-
-/* This is an exact copy of the define in `rna_light.c`
- * kept here because of linking order.
- * Icons are only defined here */
-const EnumPropertyItem rna_enum_light_type_items[] = {
-    {LA_LOCAL, "POINT", ICON_LIGHT_POINT, "Point", "Omnidirectional point light source"},
-    {LA_SUN, "SUN", ICON_LIGHT_SUN, "Sun", "Constant direction parallel ray light source"},
-    {LA_SPOT, "SPOT", ICON_LIGHT_SPOT, "Spot", "Directional cone light source"},
-    {LA_AREA, "AREA", ICON_LIGHT_AREA, "Area", "Directional area light source"},
-    {0, NULL, 0, NULL, NULL},
-};
-
-/* copy from rna_object_force.c */
-static const EnumPropertyItem field_type_items[] = {
-    {PFIELD_FORCE, "FORCE", ICON_FORCE_FORCE, "Force", ""},
-    {PFIELD_WIND, "WIND", ICON_FORCE_WIND, "Wind", ""},
-    {PFIELD_VORTEX, "VORTEX", ICON_FORCE_VORTEX, "Vortex", ""},
-    {PFIELD_MAGNET, "MAGNET", ICON_FORCE_MAGNETIC, "Magnetic", ""},
-    {PFIELD_HARMONIC, "HARMONIC", ICON_FORCE_HARMONIC, "Harmonic", ""},
-    {PFIELD_CHARGE, "CHARGE", ICON_FORCE_CHARGE, "Charge", ""},
-    {PFIELD_LENNARDJ, "LENNARDJ", ICON_FORCE_LENNARDJONES, "Lennard-Jones", ""},
-    {PFIELD_TEXTURE, "TEXTURE", ICON_FORCE_TEXTURE, "Texture", ""},
-    {PFIELD_GUIDE, "GUIDE", ICON_FORCE_CURVE, "Curve Guide", ""},
-    {PFIELD_BOID, "BOID", ICON_FORCE_BOID, "Boid", ""},
-    {PFIELD_TURBULENCE, "TURBULENCE", ICON_FORCE_TURBULENCE, "Turbulence", ""},
-    {PFIELD_DRAG, "DRAG", ICON_FORCE_DRAG, "Drag", ""},
-    {PFIELD_FLUIDFLOW, "FLUID", ICON_FORCE_FLUIDFLOW, "Fluid Flow", ""},
-    {0, NULL, 0, NULL, NULL},
-};
-
-static EnumPropertyItem lightprobe_type_items[] = {
-    {LIGHTPROBE_TYPE_CUBE,
-     "CUBEMAP",
-     ICON_LIGHTPROBE_CUBEMAP,
-     "Reflection Cubemap",
-     "Reflection probe with spherical or cubic attenuation"},
-    {LIGHTPROBE_TYPE_PLANAR,
-     "PLANAR",
-     ICON_LIGHTPROBE_PLANAR,
-     "Reflection Plane",
-     "Planar reflection probe"},
-    {LIGHTPROBE_TYPE_GRID,
-     "GRID",
-     ICON_LIGHTPROBE_GRID,
-     "Irradiance Volume",
-     "Irradiance probe to capture diffuse indirect lighting"},
-    {0, NULL, 0, NULL, NULL},
-};
-
-enum {
-  ALIGN_WORLD = 0,
-  ALIGN_VIEW,
-  ALIGN_CURSOR,
-};
-
-static const EnumPropertyItem align_options[] = {
-    {ALIGN_WORLD, "WORLD", 0, "World", "Align the new object to the world"},
-    {ALIGN_VIEW, "VIEW", 0, "View", "Align the new object to the view"},
-    {ALIGN_CURSOR, "CURSOR", 0, "3D Cursor", "Use the 3D cursor orientation for the new object"},
-    {0, NULL, 0, NULL, NULL},
-};
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Local Helpers
- * \{ */
-
-/**
- * Operator properties for creating an object under a screen space (2D) coordinate.
- * Used for object dropping like behavior (drag object and drop into 3D View).
- */
-static void object_add_drop_xy_props(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  prop = RNA_def_int(ot->srna,
-                     "drop_x",
-                     0,
-                     INT_MIN,
-                     INT_MAX,
-                     "Drop X",
-                     "X-coordinate (screen space) to place the new object under",
-                     INT_MIN,
-                     INT_MAX);
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  prop = RNA_def_int(ot->srna,
-                     "drop_y",
-                     0,
-                     INT_MIN,
-                     INT_MAX,
-                     "Drop Y",
-                     "Y-coordinate (screen space) to place the new object under",
-                     INT_MIN,
-                     INT_MAX);
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-}
-
-static bool object_add_drop_xy_is_set(const wmOperator *op)
-{
-  return RNA_struct_property_is_set(op->ptr, "drop_x") &&
-         RNA_struct_property_is_set(op->ptr, "drop_y");
-}
-
-/**
- * Query the currently set X- and Y-coordinate to position the new object under.
- * \param r_mval: Returned pointer to the coordinate in region-space.
- */
-static bool object_add_drop_xy_get(bContext *C, wmOperator *op, int (*r_mval)[2])
-{
-  if (!object_add_drop_xy_is_set(op)) {
-    (*r_mval)[0] = 0.0f;
-    (*r_mval)[1] = 0.0f;
-    return false;
-  }
-
-  const ARegion *region = CTX_wm_region(C);
-  (*r_mval)[0] = RNA_int_get(op->ptr, "drop_x") - region->winrct.xmin;
-  (*r_mval)[1] = RNA_int_get(op->ptr, "drop_y") - region->winrct.ymin;
-
-  return true;
-}
-
-/**
- * Set the drop coordinate to the mouse position (if not already set) and call the operator's
- * `exec()` callback.
- */
-static int object_add_drop_xy_generic_invoke(bContext *C, wmOperator *op, const wmEvent *event)
-{
-  if (!object_add_drop_xy_is_set(op)) {
-    RNA_int_set(op->ptr, "drop_x", event->xy[0]);
-    RNA_int_set(op->ptr, "drop_y", event->xy[1]);
-  }
-  return op->type->exec(C, op);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Public Add Object API
- * \{ */
-
-void ED_object_location_from_view(bContext *C, float loc[3])
-{
-  const Scene *scene = CTX_data_scene(C);
-  copy_v3_v3(loc, scene->cursor.location);
-}
-
-void ED_object_rotation_from_quat(float rot[3], const float viewquat[4], const char align_axis)
-{
-  BLI_assert(align_axis >= 'X' && align_axis <= 'Z');
-
-  switch (align_axis) {
-    case 'X': {
-      /* Same as 'rv3d->viewinv[1]' */
-      const float axis_y[4] = {0.0f, 1.0f, 0.0f};
-      float quat_y[4], quat[4];
-      axis_angle_to_quat(quat_y, axis_y, M_PI_2);
-      mul_qt_qtqt(quat, viewquat, quat_y);
-      quat_to_eul(rot, quat);
-      break;
-    }
-    case 'Y': {
-      quat_to_eul(rot, viewquat);
-      rot[0] -= (float)M_PI_2;
-      break;
-    }
-    case 'Z': {
-      quat_to_eul(rot, viewquat);
-      break;
-    }
-  }
-}
-
-void ED_object_rotation_from_view(bContext *C, float rot[3], const char align_axis)
-{
-  RegionView3D *rv3d = CTX_wm_region_view3d(C);
-  BLI_assert(align_axis >= 'X' && align_axis <= 'Z');
-  if (rv3d) {
-    float viewquat[4];
-    copy_qt_qt(viewquat, rv3d->viewquat);
-    viewquat[0] *= -1.0f;
-    ED_object_rotation_from_quat(rot, viewquat, align_axis);
-  }
-  else {
-    zero_v3(rot);
-  }
-}
-
-void ED_object_base_init_transform_on_add(Object *object, const float loc[3], const float rot[3])
-{
-  if (loc) {
-    copy_v3_v3(object->loc, loc);
-  }
-
-  if (rot) {
-    copy_v3_v3(object->rot, rot);
-  }
-
-  BKE_object_to_mat4(object, object->obmat);
-}
-
-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);
-  float mat[3][3], rmat[3][3], cmat[3][3], imat[3][3];
-
-  unit_m4(r_primmat);
-
-  eul_to_mat3(rmat, rot);
-  invert_m3(rmat);
-
-  /* inverse transform for initial rotation and object */
-  copy_m3_m4(mat, obedit->obmat);
-  mul_m3_m3m3(cmat, rmat, mat);
-  invert_m3_m3(imat, cmat);
-  copy_m4_m3(r_primmat, imat);
-
-  /* center */
-  copy_v3_v3(r_primmat[3], loc);
-  sub_v3_v3v3(r_primmat[3], r_primmat[3], obedit->obmat[3]);
-  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);
-    return dia;
-  }
-
-  /* return 1.0f; */
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Object Operator
- * \{ */
-
-static void view_align_update(struct Main *UNUSED(main),
-                              struct Scene *UNUSED(scene),
-                              struct PointerRNA *ptr)
-{
-  RNA_struct_idprops_unset(ptr, "rotation");
-}
-
-void ED_object_add_unit_props_size(wmOperatorType *ot)
-{
-  RNA_def_float_distance(
-      ot->srna, "size", 2.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Size", "", 0.001, 100.00);
-}
-
-void ED_object_add_unit_props_radius_ex(wmOperatorType *ot, float default_value)
-{
-  RNA_def_float_distance(
-      ot->srna, "radius", default_value, 0.0, OBJECT_ADD_SIZE_MAXF, "Radius", "", 0.001, 100.00);
-}
-
-void ED_object_add_unit_props_radius(wmOperatorType *ot)
-{
-  ED_object_add_unit_props_radius_ex(ot, 1.0f);
-}
-
-void ED_object_add_generic_props(wmOperatorType *ot, bool do_editmode)
-{
-  PropertyRNA *prop;
-
-  if (do_editmode) {
-    prop = RNA_def_boolean(ot->srna,
-                           "enter_editmode",
-                           0,
-                           "Enter Edit Mode",
-                           "Enter edit mode when adding this object");
-    RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  }
-  /* NOTE: this property gets hidden for add-camera operator. */
-  prop = RNA_def_enum(
-      ot->srna, "align", align_options, ALIGN_WORLD, "Align", "The alignment of the new object");
-  RNA_def_property_update_runtime(prop, view_align_update);
-
-  prop = RNA_def_float_vector_xyz(ot->srna,
-                                  "location",
-                                  3,
-                                  NULL,
-                                  -OBJECT_ADD_SIZE_MAXF,
-                                  OBJECT_ADD_SIZE_MAXF,
-                                  "Location",
-                                  "Location for the newly added object",
-                                  -1000.0f,
-                                  1000.0f);
-  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
-  prop = RNA_def_float_rotation(ot->srna,
-                                "rotation",
-                                3,
-                                NULL,
-                                -OBJECT_ADD_SIZE_MAXF,
-                                OBJECT_ADD_SIZE_MAXF,
-                                "Rotation",
-                                "Rotation for the newly added object",
-                                DEG2RADF(-360.0f),
-                                DEG2RADF(360.0f));
-  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
-
-  prop = RNA_def_float_vector_xyz(ot->srna,
-                                  "scale",
-                                  3,
-                                  NULL,
-                                  -OBJECT_ADD_SIZE_MAXF,
-                                  OBJECT_ADD_SIZE_MAXF,
-                                  "Scale",
-                                  "Scale for the newly added object",
-                                  -1000.0f,
-                                  1000.0f);
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-}
-
-void ED_object_add_mesh_props(wmOperatorType *ot)
-{
-  RNA_def_boolean(ot->srna, "calc_uvs", true, "Generate UVs", "Generate a default UV map");
-}
-
-bool ED_object_add_generic_get_opts(bContext *C,
-                                    wmOperator *op,
-                                    const char view_align_axis,
-                                    float r_loc[3],
-                                    float r_rot[3],
-                                    float r_scale[3],
-                                    bool *r_enter_editmode,
-                                    ushort *r_local_view_bits,
-                                    bool *r_is_view_aligned)
-{
-  /* Edit Mode! (optional) */
-  {
-    bool _enter_editmode;
-    if (!r_enter_editmode) {
-      r_enter_editmode = &_enter_editmode;
-    }
-    /* Only to ensure the value is _always_ set.
-     * Typically the property will exist when the argument is non-NULL. */
-    *r_enter_editmode = false;
-
-    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "enter_editmode");
-    if (prop != NULL) {
-      if (RNA_property_is_set(op->ptr, prop) && r_enter_editmode) {
-        *r_enter_editmode = RNA_property_boolean_get(op->ptr, prop);
-      }
-      else {
-        *r_enter_editmode = (U.flag & USER_ADD_EDITMODE) != 0;
-        RNA_property_boolean_set(op->ptr, prop, *r_enter_editmode);
-      }
-    }
-  }
-
-  if (r_local_view_bits) {
-    View3D *v3d = CTX_wm_view3d(C);
-    *r_local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
-  }
-
-  /* Location! */
-  {
-    float _loc[3];
-    if (!r_loc) {
-      r_loc = _loc;
-    }
-
-    if (RNA_struct_property_is_set(op->ptr, "location")) {
-      RNA_float_get_array(op->ptr, "location", r_loc);
-    }
-    else {
-      ED_object_location_from_view(C, r_loc);
-      RNA_float_set_array(op->ptr, "location", r_loc);
-    }
-  }
-
-  /* Rotation! */
-  {
-    bool _is_view_aligned;
-    float _rot[3];
-    if (!r_is_view_aligned) {
-      r_is_view_aligned = &_is_view_aligned;
-    }
-    if (!r_rot) {
-      r_rot = _rot;
-    }
-
-    if (RNA_struct_property_is_set(op->ptr, "rotation")) {
-      /* If rotation is set, always use it. Alignment (and corresponding user preference)
-       * can be ignored since this is in world space anyways.
-       * To not confuse (e.g. on redo), don't set it to #ALIGN_WORLD in the op UI though. */
-      *r_is_view_aligned = false;
-      RNA_float_get_array(op->ptr, "rotation", r_rot);
-    }
-    else {
-      int alignment = ALIGN_WORLD;
-      PropertyRNA *prop = RNA_struct_find_property(op->ptr, "align");
-
-      if (RNA_property_is_set(op->ptr, prop)) {
-        /* If alignment is set, always use it. */
-        *r_is_view_aligned = alignment == ALIGN_VIEW;
-        alignment = RNA_property_enum_get(op->ptr, prop);
-      }
-      else {
-        /* If alignment is not set, use User Preferences. */
-        *r_is_view_aligned = (U.flag & USER_ADD_VIEWALIGNED) != 0;
-        if (*r_is_view_aligned) {
-          RNA_property_enum_set(op->ptr, prop, ALIGN_VIEW);
-          alignment = ALIGN_VIEW;
-        }
-        else if ((U.flag & USER_ADD_CURSORALIGNED) != 0) {
-          RNA_property_enum_set(op->ptr, prop, ALIGN_CURSOR);
-          alignment = ALIGN_CURSOR;
-        }
-        else {
-          RNA_property_enum_set(op->ptr, prop, ALIGN_WORLD);
-          alignment = ALIGN_WORLD;
-        }
-      }
-      switch (alignment) {
-        case ALIGN_WORLD:
-          RNA_float_get_array(op->ptr, "rotation", r_rot);
-          break;
-        case ALIGN_VIEW:
-          ED_object_rotation_from_view(C, r_rot, view_align_axis);
-          RNA_float_set_array(op->ptr, "rotation", r_rot);
-          break;
-        case ALIGN_CURSOR: {
-          const Scene *scene = CTX_data_scene(C);
-          float tmat[3][3];
-          BKE_scene_cursor_rot_to_mat3(&scene->cursor, tmat);
-          mat3_normalized_to_eul(r_rot, tmat);
-          RNA_float_set_array(op->ptr, "rotation", r_rot);
-          break;
-        }
-      }
-    }
-  }
-
-  /* Scale! */
-  {
-    float _scale[3];
-    if (!r_scale) {
-      r_scale = _scale;
-    }
-
-    /* For now this is optional, we can make it always use. */
-    copy_v3_fl(r_scale, 1.0f);
-
-    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "scale");
-    if (prop != NULL) {
-      if (RNA_property_is_set(op->ptr, prop)) {
-        RNA_property_float_get_array(op->ptr, prop, r_scale);
-      }
-      else {
-        copy_v3_fl(r_scale, 1.0f);
-        RNA_property_float_set_array(op->ptr, prop, r_scale);
-      }
-    }
-  }
-
-  return true;
-}
-
-Object *ED_object_add_type_with_obdata(bContext *C,
-                                       const int type,
-                                       const char *name,
-                                       const float loc[3],
-                                       const float rot[3],
-                                       const bool enter_editmode,
-                                       const ushort local_view_bits,
-                                       ID *obdata)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-
-  {
-    Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
-    if (obedit != NULL) {
-      ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
-    }
-  }
-
-  /* deselects all, sets active object */
-  Object *ob;
-  if (obdata != NULL) {
-    BLI_assert(type == BKE_object_obdata_to_type(obdata));
-    ob = BKE_object_add_for_data(bmain, view_layer, type, name, obdata, true);
-    const short *materials_len_p = BKE_id_material_len_p(obdata);
-    if (materials_len_p && *materials_len_p > 0) {
-      BKE_object_materials_test(bmain, ob, ob->data);
-    }
-  }
-  else {
-    ob = BKE_object_add(bmain, view_layer, type, name);
-  }
-  BASACT(view_layer)->local_view_bits = local_view_bits;
-  /* editor level activate, notifiers */
-  ED_object_base_activate(C, view_layer->basact);
-
-  /* more editor stuff */
-  ED_object_base_init_transform_on_add(ob, loc, rot);
-
-  /* TODO(sergey): This is weird to manually tag objects for update, better to
-   * use DEG_id_tag_update here perhaps.
-   */
-  DEG_id_type_tag(bmain, ID_OB);
-  DEG_relations_tag_update(bmain);
-  if (ob->data != NULL) {
-    DEG_id_tag_update_ex(bmain, (ID *)ob->data, ID_RECALC_EDITORS);
-  }
-
-  if (enter_editmode) {
-    ED_object_editmode_enter_ex(bmain, scene, ob, 0);
-  }
-
-  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
-
-  /* TODO(sergey): Use proper flag for tagging here. */
-  DEG_id_tag_update(&scene->id, 0);
-
-  ED_outliner_select_sync_from_object_tag(C);
-
-  return ob;
-}
-
-Object *ED_object_add_type(bContext *C,
-                           const int type,
-                           const char *name,
-                           const float loc[3],
-                           const float rot[3],
-                           const bool enter_editmode,
-                           const ushort local_view_bits)
-{
-  return ED_object_add_type_with_obdata(
-      C, type, name, loc, rot, enter_editmode, local_view_bits, NULL);
-}
-
-/* for object add operator */
-static int object_add_exec(bContext *C, wmOperator *op)
-{
-  ushort local_view_bits;
-  bool enter_editmode;
-  float loc[3], rot[3], radius;
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  radius = RNA_float_get(op->ptr, "radius");
-  Object *ob = ED_object_add_type(
-      C, RNA_enum_get(op->ptr, "type"), NULL, loc, rot, enter_editmode, local_view_bits);
-
-  if (ob->type == OB_LATTICE) {
-    /* lattice is a special case!
-     * we never want to scale the obdata since that is the rest-state */
-    copy_v3_fl(ob->scale, radius);
-  }
-  else {
-    BKE_object_obdata_size_init(ob, radius);
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Object";
-  ot->description = "Add an object to the scene";
-  ot->idname = "OBJECT_OT_add";
-
-  /* api callbacks */
-  ot->exec = object_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ED_object_add_unit_props_radius(ot);
-  PropertyRNA *prop = RNA_def_enum(ot->srna, "type", rna_enum_object_type_items, 0, "Type", "");
-  RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
-
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Probe Operator
- * \{ */
-
-/* for object add operator */
-static const char *get_lightprobe_defname(int type)
-{
-  switch (type) {
-    case LIGHTPROBE_TYPE_GRID:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "IrradianceVolume");
-    case LIGHTPROBE_TYPE_PLANAR:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionPlane");
-    case LIGHTPROBE_TYPE_CUBE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionCubemap");
-    default:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "LightProbe");
-  }
-}
-
-static int lightprobe_add_exec(bContext *C, wmOperator *op)
-{
-  bool enter_editmode;
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  int type = RNA_enum_get(op->ptr, "type");
-  float radius = RNA_float_get(op->ptr, "radius");
-
-  Object *ob = ED_object_add_type(
-      C, OB_LIGHTPROBE, get_lightprobe_defname(type), loc, rot, false, local_view_bits);
-  copy_v3_fl(ob->scale, radius);
-
-  LightProbe *probe = (LightProbe *)ob->data;
-
-  BKE_lightprobe_type_set(probe, type);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_lightprobe_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Light Probe";
-  ot->description = "Add a light probe object";
-  ot->idname = "OBJECT_OT_lightprobe_add";
-
-  /* api callbacks */
-  ot->exec = lightprobe_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ot->prop = RNA_def_enum(ot->srna, "type", lightprobe_type_items, 0, "Type", "");
-
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Effector Operator
- * \{ */
-
-/* for object add operator */
-
-static const char *get_effector_defname(ePFieldType type)
-{
-  switch (type) {
-    case PFIELD_FORCE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Force");
-    case PFIELD_VORTEX:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Vortex");
-    case PFIELD_MAGNET:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Magnet");
-    case PFIELD_WIND:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Wind");
-    case PFIELD_GUIDE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "CurveGuide");
-    case PFIELD_TEXTURE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "TextureField");
-    case PFIELD_HARMONIC:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Harmonic");
-    case PFIELD_CHARGE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Charge");
-    case PFIELD_LENNARDJ:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Lennard-Jones");
-    case PFIELD_BOID:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Boid");
-    case PFIELD_TURBULENCE:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Turbulence");
-    case PFIELD_DRAG:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Drag");
-    case PFIELD_FLUIDFLOW:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "FluidField");
-    case PFIELD_NULL:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Field");
-    case NUM_PFIELD_TYPES:
-      break;
-  }
-
-  BLI_assert(false);
-  return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Field");
-}
-
-static int effector_add_exec(bContext *C, wmOperator *op)
-{
-  bool enter_editmode;
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  int type = RNA_enum_get(op->ptr, "type");
-  float dia = RNA_float_get(op->ptr, "radius");
-
-  Object *ob;
-  if (type == PFIELD_GUIDE) {
-    Main *bmain = CTX_data_main(C);
-    Scene *scene = CTX_data_scene(C);
-    Curve *cu;
-    ob = ED_object_add_type(
-        C, OB_CURVES_LEGACY, get_effector_defname(type), loc, rot, false, local_view_bits);
-
-    cu = ob->data;
-    cu->flag |= CU_PATH | CU_3D;
-    ED_object_editmode_enter_ex(bmain, scene, ob, 0);
-
-    float mat[4][4];
-    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));
-    if (!enter_editmode) {
-      ED_object_editmode_exit_ex(bmain, scene, ob, EM_FREEDATA);
-    }
-  }
-  else {
-    ob = ED_object_add_type(
-        C, OB_EMPTY, get_effector_defname(type), loc, rot, false, local_view_bits);
-    BKE_object_obdata_size_init(ob, dia);
-    if (ELEM(type, PFIELD_WIND, PFIELD_VORTEX)) {
-      ob->empty_drawtype = OB_SINGLE_ARROW;
-    }
-  }
-
-  ob->pd = BKE_partdeflect_new(type);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_effector_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Effector";
-  ot->description = "Add an empty object with a physics effector to the scene";
-  ot->idname = "OBJECT_OT_effector_add";
-
-  /* api callbacks */
-  ot->exec = effector_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ot->prop = RNA_def_enum(ot->srna, "type", field_type_items, 0, "Type", "");
-
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Camera Operator
- * \{ */
-
-static int object_camera_add_exec(bContext *C, wmOperator *op)
-{
-  View3D *v3d = CTX_wm_view3d(C);
-  Scene *scene = CTX_data_scene(C);
-
-  /* force view align for cameras */
-  RNA_enum_set(op->ptr, "align", ALIGN_VIEW);
-
-  ushort local_view_bits;
-  bool enter_editmode;
-  float loc[3], rot[3];
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  Object *ob = ED_object_add_type(C, OB_CAMERA, NULL, loc, rot, false, local_view_bits);
-
-  if (v3d) {
-    if (v3d->camera == NULL) {
-      v3d->camera = ob;
-    }
-    if (v3d->scenelock && scene->camera == NULL) {
-      scene->camera = ob;
-    }
-  }
-
-  Camera *cam = ob->data;
-  cam->drawsize = v3d ? ED_view3d_grid_scale(scene, v3d, NULL) : ED_scene_grid_scale(scene, NULL);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_camera_add(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  /* identifiers */
-  ot->name = "Add Camera";
-  ot->description = "Add a camera object to the scene";
-  ot->idname = "OBJECT_OT_camera_add";
-
-  /* api callbacks */
-  ot->exec = object_camera_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  ED_object_add_generic_props(ot, true);
-
-  /* hide this for cameras, default */
-  prop = RNA_struct_type_find_property(ot->srna, "align");
-  RNA_def_property_flag(prop, PROP_HIDDEN);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Metaball Operator
- * \{ */
-
-static int object_metaball_add_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-
-  ushort local_view_bits;
-  bool enter_editmode;
-  float loc[3], rot[3];
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  bool newob = false;
-  Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
-  if (obedit == NULL || obedit->type != OB_MBALL) {
-    obedit = ED_object_add_type(C, OB_MBALL, NULL, loc, rot, true, local_view_bits);
-    newob = true;
-  }
-  else {
-    DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY);
-  }
-
-  float mat[4][4];
-  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. */
-  float dia = RNA_float_get(op->ptr, "radius") / 2;
-
-  ED_mball_add_primitive(C, obedit, newob, mat, dia, RNA_enum_get(op->ptr, "type"));
-
-  /* userdef */
-  if (newob && !enter_editmode) {
-    ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
-  }
-  else {
-    /* Only needed in edit-mode (#ED_object_add_type normally handles this). */
-    WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obedit);
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_metaball_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Metaball";
-  ot->description = "Add an metaball object to the scene";
-  ot->idname = "OBJECT_OT_metaball_add";
-
-  /* api callbacks */
-  ot->invoke = WM_menu_invoke;
-  ot->exec = object_metaball_add_exec;
-  ot->poll = ED_operator_scene_editable;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_metaelem_type_items, 0, "Primitive", "");
-
-  ED_object_add_unit_props_radius_ex(ot, 2.0f);
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Text Operator
- * \{ */
-
-static int object_add_text_exec(bContext *C, wmOperator *op)
-{
-  Object *obedit = CTX_data_edit_object(C);
-  bool enter_editmode;
-  ushort local_view_bits;
-  float loc[3], rot[3];
-
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  if (obedit && obedit->type == OB_FONT) {
-    return OPERATOR_CANCELLED;
-  }
-
-  obedit = ED_object_add_type(C, OB_FONT, NULL, loc, rot, enter_editmode, local_view_bits);
-  BKE_object_obdata_size_init(obedit, RNA_float_get(op->ptr, "radius"));
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_text_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Text";
-  ot->description = "Add a text object to the scene";
-  ot->idname = "OBJECT_OT_text_add";
-
-  /* api callbacks */
-  ot->exec = object_add_text_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Armature Operator
- * \{ */
-
-static int object_armature_add_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
-
-  RegionView3D *rv3d = CTX_wm_region_view3d(C);
-  bool newob = false;
-  bool enter_editmode;
-  ushort local_view_bits;
-  float loc[3], rot[3], dia;
-  bool view_aligned = rv3d && (U.flag & USER_ADD_VIEWALIGNED);
-
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(
-          C, op, 'Z', loc, rot, NULL, &enter_editmode, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  if ((obedit == NULL) || (obedit->type != OB_ARMATURE)) {
-    obedit = ED_object_add_type(C, OB_ARMATURE, NULL, loc, rot, true, local_view_bits);
-    ED_object_editmode_enter_ex(bmain, scene, obedit, 0);
-    newob = true;
-  }
-  else {
-    DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY);
-  }
-
-  if (obedit == NULL) {
-    BKE_report(op->reports, RPT_ERROR, "Cannot create editmode armature");
-    return OPERATOR_CANCELLED;
-  }
-
-  dia = RNA_float_get(op->ptr, "radius");
-  ED_armature_ebone_add_primitive(obedit, dia, view_aligned);
-
-  /* userdef */
-  if (newob && !enter_editmode) {
-    ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_armature_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Armature";
-  ot->description = "Add an armature object to the scene";
-  ot->idname = "OBJECT_OT_armature_add";
-
-  /* api callbacks */
-  ot->exec = object_armature_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Empty Operator
- * \{ */
-
-static int object_empty_add_exec(bContext *C, wmOperator *op)
-{
-  Object *ob;
-  int type = RNA_enum_get(op->ptr, "type");
-  ushort local_view_bits;
-  float loc[3], rot[3];
-
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  ob = ED_object_add_type(C, OB_EMPTY, NULL, loc, rot, false, local_view_bits);
-
-  BKE_object_empty_draw_type_set(ob, type);
-  BKE_object_obdata_size_init(ob, RNA_float_get(op->ptr, "radius"));
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_empty_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Empty";
-  ot->description = "Add an empty object to the scene";
-  ot->idname = "OBJECT_OT_empty_add";
-
-  /* api callbacks */
-  ot->invoke = WM_menu_invoke;
-  ot->exec = object_empty_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_empty_drawtype_items, 0, "Type", "");
-
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, false);
-}
-
-static int empty_drop_named_image_invoke(bContext *C, wmOperator *op, const wmEvent *event)
-{
-  Scene *scene = CTX_data_scene(C);
-
-  Image *ima = NULL;
-
-  ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM);
-  if (!ima) {
-    return OPERATOR_CANCELLED;
-  }
-  /* handled below */
-  id_us_min(&ima->id);
-
-  Object *ob = NULL;
-  Object *ob_cursor = ED_view3d_give_object_under_cursor(C, event->mval);
-
-  /* either change empty under cursor or create a new empty */
-  if (ob_cursor && ob_cursor->type == OB_EMPTY) {
-    WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
-    DEG_id_tag_update((ID *)ob_cursor, ID_RECALC_TRANSFORM);
-    ob = ob_cursor;
-  }
-  else {
-    /* add new empty */
-    ushort local_view_bits;
-    float rot[3];
-
-    if (!ED_object_add_generic_get_opts(
-            C, op, 'Z', NULL, rot, NULL, NULL, &local_view_bits, NULL)) {
-      return OPERATOR_CANCELLED;
-    }
-    ob = ED_object_add_type(C, OB_EMPTY, NULL, NULL, rot, false, local_view_bits);
-
-    ED_object_location_from_view(C, ob->loc);
-    ED_view3d_cursor3d_position(C, event->mval, false, ob->loc);
-    ED_object_rotation_from_view(C, ob->rot, 'Z');
-    ob->empty_drawsize = 5.0f;
-  }
-
-  BKE_object_empty_draw_type_set(ob, OB_EMPTY_IMAGE);
-
-  id_us_min(ob->data);
-  ob->data = ima;
-  id_us_plus(ob->data);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_drop_named_image(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  /* identifiers */
-  ot->name = "Add Empty Image/Drop Image to Empty";
-  ot->description = "Add an empty image type to scene with data";
-  ot->idname = "OBJECT_OT_drop_named_image";
-
-  /* api callbacks */
-  ot->invoke = empty_drop_named_image_invoke;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  prop = RNA_def_string(ot->srna, "filepath", NULL, FILE_MAX, "Filepath", "Path to image file");
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  RNA_def_boolean(ot->srna,
-                  "relative_path",
-                  true,
-                  "Relative Path",
-                  "Select the file relative to the blend file");
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  prop = RNA_def_string(ot->srna, "name", NULL, MAX_ID_NAME - 2, "Name", "Image name to assign");
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  ED_object_add_generic_props(ot, false);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Gpencil Operator
- * \{ */
-
-static bool object_gpencil_add_poll(bContext *C)
-{
-  Scene *scene = CTX_data_scene(C);
-  Object *obact = CTX_data_active_object(C);
-
-  if ((scene == NULL) || (ID_IS_LINKED(scene))) {
-    return false;
-  }
-
-  if (obact && obact->type == OB_GPENCIL) {
-    if (obact->mode != OB_MODE_OBJECT) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-static int object_gpencil_add_exec(bContext *C, wmOperator *op)
-{
-  Object *ob = CTX_data_active_object(C), *ob_orig = ob;
-  bGPdata *gpd = (ob && (ob->type == OB_GPENCIL)) ? ob->data : NULL;
-
-  const int type = RNA_enum_get(op->ptr, "type");
-  const bool use_in_front = RNA_boolean_get(op->ptr, "use_in_front");
-  const bool use_lights = RNA_boolean_get(op->ptr, "use_lights");
-  const int stroke_depth_order = RNA_enum_get(op->ptr, "stroke_depth_order");
-  const float stroke_depth_offset = RNA_float_get(op->ptr, "stroke_depth_offset");
-
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  bool newob = false;
-
-  /* NOTE: We use 'Y' here (not 'Z'), as. */
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(C, op, 'Y', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  /* Add new object if not currently editing a GP object. */
-  if ((gpd == NULL) || (GPENCIL_ANY_MODE(gpd) == false)) {
-    const char *ob_name = NULL;
-    switch (type) {
-      case GP_EMPTY: {
-        ob_name = "GPencil";
-        break;
-      }
-      case GP_MONKEY: {
-        ob_name = "Suzanne";
-        break;
-      }
-      case GP_STROKE: {
-        ob_name = "Stroke";
-        break;
-      }
-      case GP_LRT_OBJECT:
-      case GP_LRT_SCENE:
-      case GP_LRT_COLLECTION: {
-        ob_name = "Line Art";
-        break;
-      }
-      default: {
-        break;
-      }
-    }
-
-    ob = ED_object_add_type(C, OB_GPENCIL, ob_name, loc, rot, true, local_view_bits);
-    gpd = ob->data;
-    newob = true;
-  }
-  else {
-    DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
-    WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_ADDED, NULL);
-  }
-
-  /* create relevant geometry */
-  switch (type) {
-    case GP_EMPTY: {
-      float mat[4][4];
-
-      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, 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, scale, mat);
-
-      ED_gpencil_create_monkey(C, ob, mat);
-      break;
-    }
-    case GP_LRT_SCENE:
-    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, scale, mat);
-
-      ED_gpencil_create_lineart(C, ob);
-
-      gpd = ob->data;
-
-      /* Add Line Art modifier */
-      LineartGpencilModifierData *md = (LineartGpencilModifierData *)BKE_gpencil_modifier_new(
-          eGpencilModifierType_Lineart);
-      BLI_addtail(&ob->greasepencil_modifiers, md);
-      BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, (GpencilModifierData *)md);
-
-      if (type == GP_LRT_COLLECTION) {
-        md->source_type = LRT_SOURCE_COLLECTION;
-        md->source_collection = CTX_data_collection(C);
-      }
-      else if (type == GP_LRT_OBJECT) {
-        md->source_type = LRT_SOURCE_OBJECT;
-        md->source_object = ob_orig;
-      }
-      else {
-        /* Whole scene. */
-        md->source_type = LRT_SOURCE_SCENE;
-      }
-      /* Only created one layer and one material. */
-      strcpy(md->target_layer, ((bGPDlayer *)gpd->layers.first)->info);
-      md->target_material = BKE_gpencil_material(ob, 1);
-      if (md->target_material) {
-        id_us_plus(&md->target_material->id);
-      }
-
-      if (use_lights) {
-        ob->dtx |= OB_USE_GPENCIL_LIGHTS;
-      }
-      else {
-        ob->dtx &= ~OB_USE_GPENCIL_LIGHTS;
-      }
-
-      /* Stroke object is drawn in front of meshes by default. */
-      if (use_in_front) {
-        ob->dtx |= OB_DRAW_IN_FRONT;
-      }
-      else {
-        if (stroke_depth_order == GP_DRAWMODE_3D) {
-          gpd->draw_mode = GP_DRAWMODE_3D;
-        }
-        md->stroke_depth_offset = stroke_depth_offset;
-      }
-
-      break;
-    }
-    default:
-      BKE_report(op->reports, RPT_WARNING, "Not implemented");
-      break;
-  }
-
-  /* If this is a new object, initialize default stuff (colors, etc.) */
-  if (newob) {
-    /* set default viewport color to black */
-    copy_v3_fl(ob->color, 0.0f);
-
-    ED_gpencil_add_defaults(C, ob);
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-static void object_add_ui(bContext *UNUSED(C), wmOperator *op)
-{
-  uiLayout *layout = op->layout;
-
-  uiLayoutSetPropSep(layout, true);
-
-  uiItemR(layout, op->ptr, "radius", 0, NULL, ICON_NONE);
-  uiItemR(layout, op->ptr, "align", 0, NULL, ICON_NONE);
-  uiItemR(layout, op->ptr, "location", 0, NULL, ICON_NONE);
-  uiItemR(layout, op->ptr, "rotation", 0, NULL, ICON_NONE);
-  uiItemR(layout, op->ptr, "type", 0, NULL, ICON_NONE);
-
-  int type = RNA_enum_get(op->ptr, "type");
-  if (ELEM(type, GP_LRT_COLLECTION, GP_LRT_OBJECT, GP_LRT_SCENE)) {
-    uiItemR(layout, op->ptr, "use_lights", 0, NULL, ICON_NONE);
-    uiItemR(layout, op->ptr, "use_in_front", 0, NULL, ICON_NONE);
-    bool in_front = RNA_boolean_get(op->ptr, "use_in_front");
-    uiLayout *col = uiLayoutColumn(layout, false);
-    uiLayoutSetActive(col, !in_front);
-    uiItemR(col, op->ptr, "stroke_depth_offset", 0, NULL, ICON_NONE);
-    uiItemR(col, op->ptr, "stroke_depth_order", 0, NULL, ICON_NONE);
-  }
-}
-
-static EnumPropertyItem rna_enum_gpencil_add_stroke_depth_order_items[] = {
-    {GP_DRAWMODE_2D,
-     "2D",
-     0,
-     "2D Layers",
-     "Display strokes using grease pencil layers to define order"},
-    {GP_DRAWMODE_3D, "3D", 0, "3D Location", "Display strokes using real 3D position in 3D space"},
-    {0, NULL, 0, NULL, NULL},
-};
-
-void OBJECT_OT_gpencil_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Grease Pencil";
-  ot->description = "Add a Grease Pencil object to the scene";
-  ot->idname = "OBJECT_OT_gpencil_add";
-
-  /* api callbacks */
-  ot->invoke = WM_menu_invoke;
-  ot->exec = object_gpencil_add_exec;
-  ot->poll = object_gpencil_add_poll;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* ui */
-  ot->ui = object_add_ui;
-
-  /* properties */
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, false);
-
-  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", "");
-  RNA_def_boolean(ot->srna,
-                  "use_in_front",
-                  true,
-                  "Show In Front",
-                  "Show line art grease pencil in front of everything");
-  RNA_def_float(ot->srna,
-                "stroke_depth_offset",
-                0.05f,
-                0.0f,
-                FLT_MAX,
-                "Stroke Offset",
-                "Stroke offset for the line art modifier",
-                0.0f,
-                0.5f);
-  RNA_def_boolean(
-      ot->srna, "use_lights", false, "Use Lights", "Use lights for this grease pencil object");
-  RNA_def_enum(
-      ot->srna,
-      "stroke_depth_order",
-      rna_enum_gpencil_add_stroke_depth_order_items,
-      GP_DRAWMODE_3D,
-      "Stroke Depth Order",
-      "Defines how the strokes are ordered in 3D space for objects not displayed 'In Front')");
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Light Operator
- * \{ */
-
-static const char *get_light_defname(int type)
-{
-  switch (type) {
-    case LA_LOCAL:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Point");
-    case LA_SUN:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Sun");
-    case LA_SPOT:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Spot");
-    case LA_AREA:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Area");
-    default:
-      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Light");
-  }
-}
-
-static int object_light_add_exec(bContext *C, wmOperator *op)
-{
-  Object *ob;
-  Light *la;
-  int type = RNA_enum_get(op->ptr, "type");
-  ushort local_view_bits;
-  float loc[3], rot[3];
-
-  WM_operator_view3d_unit_defaults(C, op);
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  ob = ED_object_add_type(C, OB_LAMP, get_light_defname(type), loc, rot, false, local_view_bits);
-
-  float size = RNA_float_get(op->ptr, "radius");
-  /* Better defaults for light size. */
-  switch (type) {
-    case LA_LOCAL:
-    case LA_SPOT:
-      break;
-    case LA_AREA:
-      size *= 4.0f;
-      break;
-    default:
-      size *= 0.5f;
-      break;
-  }
-  BKE_object_obdata_size_init(ob, size);
-
-  la = (Light *)ob->data;
-  la->type = type;
-
-  if (type == LA_SUN) {
-    la->energy = 1.0f;
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_light_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Light";
-  ot->description = "Add a light object to the scene";
-  ot->idname = "OBJECT_OT_light_add";
-
-  /* api callbacks */
-  ot->invoke = WM_menu_invoke;
-  ot->exec = object_light_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_light_type_items, 0, "Type", "");
-  RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_LIGHT);
-
-  ED_object_add_unit_props_radius(ot);
-  ED_object_add_generic_props(ot, false);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Collection Instance Operator
- * \{ */
-
-static int collection_instance_add_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Collection *collection;
-  ushort local_view_bits;
-  float loc[3], rot[3];
-
-  PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
-  PropertyRNA *prop_location = RNA_struct_find_property(op->ptr, "location");
-  PropertyRNA *prop_session_uuid = RNA_struct_find_property(op->ptr, "session_uuid");
-
-  bool update_location_if_necessary = false;
-  if (RNA_property_is_set(op->ptr, prop_name)) {
-    char name[MAX_ID_NAME - 2];
-    RNA_property_string_get(op->ptr, prop_name, name);
-    collection = (Collection *)BKE_libblock_find_name(bmain, ID_GR, name);
-    update_location_if_necessary = true;
-  }
-  else if (RNA_property_is_set(op->ptr, prop_session_uuid)) {
-    const uint32_t session_uuid = (uint32_t)RNA_property_int_get(op->ptr, prop_session_uuid);
-    collection = (Collection *)BKE_libblock_find_session_uuid(bmain, ID_GR, session_uuid);
-    update_location_if_necessary = true;
-  }
-  else {
-    collection = BLI_findlink(&bmain->collections, RNA_enum_get(op->ptr, "collection"));
-  }
-
-  if (update_location_if_necessary) {
-    int mval[2];
-    if (!RNA_property_is_set(op->ptr, prop_location) && object_add_drop_xy_get(C, op, &mval)) {
-      ED_object_location_from_view(C, loc);
-      ED_view3d_cursor3d_position(C, mval, false, loc);
-      RNA_property_float_set_array(op->ptr, prop_location, loc);
-    }
-  }
-
-  if (collection == NULL) {
-    return OPERATOR_CANCELLED;
-  }
-
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-
-  /* Avoid dependency cycles. */
-  LayerCollection *active_lc = BKE_layer_collection_get_active(view_layer);
-  while (BKE_collection_cycle_find(active_lc->collection, collection)) {
-    active_lc = BKE_layer_collection_activate_parent(view_layer, active_lc);
-  }
-
-  Object *ob = ED_object_add_type(
-      C, OB_EMPTY, collection->id.name + 2, loc, rot, false, local_view_bits);
-  ob->instance_collection = collection;
-  ob->empty_drawsize = U.collection_instance_empty_size;
-  ob->transflag |= OB_DUPLICOLLECTION;
-  id_us_plus(&collection->id);
-
-  return OPERATOR_FINISHED;
-}
-
-static int object_instance_add_invoke(bContext *C, wmOperator *op, const wmEvent *event)
-{
-  if (!object_add_drop_xy_is_set(op)) {
-    RNA_int_set(op->ptr, "drop_x", event->xy[0]);
-    RNA_int_set(op->ptr, "drop_y", event->xy[1]);
-  }
-
-  if (!RNA_struct_property_is_set(op->ptr, "name") &&
-      !RNA_struct_property_is_set(op->ptr, "session_uuid")) {
-    return WM_enum_search_invoke(C, op, event);
-  }
-  return op->type->exec(C, op);
-}
-
-void OBJECT_OT_collection_instance_add(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  /* identifiers */
-  ot->name = "Add Collection Instance";
-  ot->description = "Add a collection instance";
-  ot->idname = "OBJECT_OT_collection_instance_add";
-
-  /* api callbacks */
-  ot->invoke = object_instance_add_invoke;
-  ot->exec = collection_instance_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  RNA_def_string(
-      ot->srna, "name", "Collection", MAX_ID_NAME - 2, "Name", "Collection name to add");
-  prop = RNA_def_enum(ot->srna, "collection", DummyRNA_NULL_items, 0, "Collection", "");
-  RNA_def_enum_funcs(prop, RNA_collection_itemf);
-  RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
-  ot->prop = prop;
-  ED_object_add_generic_props(ot, false);
-
-  prop = RNA_def_int(ot->srna,
-                     "session_uuid",
-                     0,
-                     INT32_MIN,
-                     INT32_MAX,
-                     "Session UUID",
-                     "Session UUID of the collection to add",
-                     INT32_MIN,
-                     INT32_MAX);
-  RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
-
-  object_add_drop_xy_props(ot);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Data Instance Operator
- *
- * Use for dropping ID's from the outliner.
- * \{ */
-
-static int object_data_instance_add_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  ID *id = NULL;
-  ushort local_view_bits;
-  float loc[3], rot[3];
-
-  PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
-  PropertyRNA *prop_type = RNA_struct_find_property(op->ptr, "type");
-  PropertyRNA *prop_location = RNA_struct_find_property(op->ptr, "location");
-
-  /* These shouldn't fail when created by outliner dropping as it checks the ID is valid. */
-  if (!RNA_property_is_set(op->ptr, prop_name) || !RNA_property_is_set(op->ptr, prop_type)) {
-    return OPERATOR_CANCELLED;
-  }
-  const short id_type = RNA_property_enum_get(op->ptr, prop_type);
-  char name[MAX_ID_NAME - 2];
-  RNA_property_string_get(op->ptr, prop_name, name);
-  id = BKE_libblock_find_name(bmain, id_type, name);
-  if (id == NULL) {
-    return OPERATOR_CANCELLED;
-  }
-  const int object_type = BKE_object_obdata_to_type(id);
-  if (object_type == -1) {
-    return OPERATOR_CANCELLED;
-  }
-
-  int mval[2];
-  if (!RNA_property_is_set(op->ptr, prop_location) && object_add_drop_xy_get(C, op, &mval)) {
-    ED_object_location_from_view(C, loc);
-    ED_view3d_cursor3d_position(C, mval, false, loc);
-    RNA_property_float_set_array(op->ptr, prop_location, loc);
-  }
-
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  ED_object_add_type_with_obdata(
-      C, object_type, id->name + 2, loc, rot, false, local_view_bits, id);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_data_instance_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Object Data Instance";
-  ot->description = "Add an object data instance";
-  ot->idname = "OBJECT_OT_data_instance_add";
-
-  /* api callbacks */
-  ot->invoke = object_add_drop_xy_generic_invoke;
-  ot->exec = object_data_instance_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  RNA_def_string(ot->srna, "name", "Name", MAX_ID_NAME - 2, "Name", "ID name to add");
-  PropertyRNA *prop = RNA_def_enum(ot->srna, "type", rna_enum_id_type_items, 0, "Type", "");
-  RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
-  ED_object_add_generic_props(ot, false);
-
-  object_add_drop_xy_props(ot);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Speaker Operator
- * \{ */
-
-static int object_speaker_add_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-  Object *ob = ED_object_add_type(C, OB_SPEAKER, NULL, loc, rot, false, local_view_bits);
-  const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ob);
-
-  /* To make it easier to start using this immediately in NLA, a default sound clip is created
-   * ready to be moved around to re-time the sound and/or make new sound clips. */
-  {
-    /* create new data for NLA hierarchy */
-    AnimData *adt = BKE_animdata_ensure_id(&ob->id);
-    NlaTrack *nlt = BKE_nlatrack_add(adt, NULL, is_liboverride);
-    NlaStrip *strip = BKE_nla_add_soundstrip(bmain, scene, ob->data);
-    strip->start = CFRA;
-    strip->end += strip->start;
-
-    /* hook them up */
-    BKE_nlatrack_add_strip(nlt, strip, is_liboverride);
-
-    /* Auto-name the strip, and give the track an interesting name. */
-    BLI_strncpy(nlt->name, DATA_("SoundTrack"), sizeof(nlt->name));
-    BKE_nlastrip_validate_name(adt, strip);
-
-    WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL);
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_speaker_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Speaker";
-  ot->description = "Add a speaker object to the scene";
-  ot->idname = "OBJECT_OT_speaker_add";
-
-  /* api callbacks */
-  ot->exec = object_speaker_add_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  ED_object_add_generic_props(ot, true);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Hair Curves Operator
- * \{ */
-
-static bool object_hair_curves_add_poll(bContext *C)
-{
-  if (!U.experimental.use_new_curves_type) {
-    return false;
-  }
-  return ED_operator_objectmode(C);
-}
-
-static int object_hair_curves_add_exec(bContext *C, wmOperator *op)
-{
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  Object *object = ED_object_add_type(C, OB_CURVES, NULL, loc, rot, false, local_view_bits);
-  object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_hair_curves_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Hair Curves";
-  ot->description = "Add a hair curves object to the scene";
-  ot->idname = "OBJECT_OT_hair_curves_add";
-
-  /* api callbacks */
-  ot->exec = object_hair_curves_add_exec;
-  ot->poll = object_hair_curves_add_poll;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  ED_object_add_generic_props(ot, false);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Point Cloud Operator
- * \{ */
-
-static bool object_pointcloud_add_poll(bContext *C)
-{
-  if (!U.experimental.use_new_point_cloud_type) {
-    return false;
-  }
-  return ED_operator_objectmode(C);
-}
-
-static int object_pointcloud_add_exec(bContext *C, wmOperator *op)
-{
-  ushort local_view_bits;
-  float loc[3], rot[3];
-  if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, NULL, &local_view_bits, NULL)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  Object *object = ED_object_add_type(C, OB_POINTCLOUD, NULL, loc, rot, false, local_view_bits);
-  object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_pointcloud_add(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Point Cloud";
-  ot->description = "Add a point cloud object to the scene";
-  ot->idname = "OBJECT_OT_pointcloud_add";
-
-  /* api callbacks */
-  ot->exec = object_pointcloud_add_exec;
-  ot->poll = object_pointcloud_add_poll;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  ED_object_add_generic_props(ot, false);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Delete Object Operator
- * \{ */
-
-void ED_object_base_free_and_unlink(Main *bmain, Scene *scene, Object *ob)
-{
-  if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 &&
-      BKE_library_ID_is_indirectly_used(bmain, ob)) {
-    /* We cannot delete indirectly used object... */
-    printf(
-        "WARNING, undeletable object '%s', should have been caught before reaching this "
-        "function!",
-        ob->id.name + 2);
-    return;
-  }
-  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
-    /* Do not delete objects used by overrides of collections. */
-    return;
-  }
-
-  DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_BASE_FLAGS);
-
-  BKE_scene_collections_object_remove(bmain, scene, ob, true);
-}
-
-void ED_object_base_free_and_unlink_no_indirect_check(Main *bmain, Scene *scene, Object *ob)
-{
-  BLI_assert(!BKE_library_ID_is_indirectly_used(bmain, ob));
-  DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_BASE_FLAGS);
-  BKE_scene_collections_object_remove(bmain, scene, ob, true);
-}
-
-static int object_delete_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  wmWindowManager *wm = CTX_wm_manager(C);
-  const bool use_global = RNA_boolean_get(op->ptr, "use_global");
-  const bool confirm = op->flag & OP_IS_INVOKE;
-  uint changed_count = 0;
-  uint tagged_count = 0;
-
-  if (CTX_data_edit_object(C)) {
-    return OPERATOR_CANCELLED;
-  }
-
-  BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
-
-  CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
-    if (ob->id.tag & LIB_TAG_INDIRECT) {
-      /* Can this case ever happen? */
-      BKE_reportf(op->reports,
-                  RPT_WARNING,
-                  "Cannot delete indirectly linked object '%s'",
-                  ob->id.name + 2);
-      continue;
-    }
-
-    if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
-      BKE_reportf(op->reports,
-                  RPT_WARNING,
-                  "Cannot delete object '%s' as it is used by override collections",
-                  ob->id.name + 2);
-      continue;
-    }
-
-    if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 &&
-        BKE_library_ID_is_indirectly_used(bmain, ob)) {
-      BKE_reportf(op->reports,
-                  RPT_WARNING,
-                  "Cannot delete object '%s' from scene '%s', indirectly used objects need at "
-                  "least one user",
-                  ob->id.name + 2,
-                  scene->id.name + 2);
-      continue;
-    }
-
-    /* if grease pencil object, set cache as dirty */
-    if (ob->type == OB_GPENCIL) {
-      bGPdata *gpd = (bGPdata *)ob->data;
-      DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
-    }
-
-    /* Use multi tagged delete if `use_global=True`, or the object is used only in one scene. */
-    if (use_global || ID_REAL_USERS(ob) <= 1) {
-      ob->id.tag |= LIB_TAG_DOIT;
-      tagged_count += 1;
-    }
-    else {
-      /* Object is used in multiple scenes. Delete the object from the current scene only. */
-      ED_object_base_free_and_unlink_no_indirect_check(bmain, scene, ob);
-      changed_count += 1;
-
-      /* FIXME: this will also remove parent from grease pencil from other scenes. */
-      /* Remove from Grease Pencil parent */
-      for (bGPdata *gpd = bmain->gpencils.first; gpd; gpd = gpd->id.next) {
-        LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
-          if (gpl->parent != NULL) {
-            if (gpl->parent == ob) {
-              gpl->parent = NULL;
-            }
-          }
-        }
-      }
-    }
-  }
-  CTX_DATA_END;
-
-  if ((changed_count + tagged_count) == 0) {
-    return OPERATOR_CANCELLED;
-  }
-
-  if (tagged_count > 0) {
-    BKE_id_multi_tagged_delete(bmain);
-  }
-
-  if (confirm) {
-    BKE_reportf(op->reports, RPT_INFO, "Deleted %u object(s)", (changed_count + tagged_count));
-  }
-
-  /* delete has to handle all open scenes */
-  BKE_main_id_tag_listbase(&bmain->scenes, LIB_TAG_DOIT, true);
-  LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
-    scene = WM_window_get_active_scene(win);
-
-    if (scene->id.tag & LIB_TAG_DOIT) {
-      scene->id.tag &= ~LIB_TAG_DOIT;
-
-      DEG_relations_tag_update(bmain);
-
-      DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
-      WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
-      WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
-    }
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_delete(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Delete";
-  ot->description = "Delete selected objects";
-  ot->idname = "OBJECT_OT_delete";
-
-  /* api callbacks */
-  ot->invoke = WM_operator_confirm_or_exec;
-  ot->exec = object_delete_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  PropertyRNA *prop;
-  prop = RNA_def_boolean(
-      ot->srna, "use_global", 0, "Delete Globally", "Remove object from all scenes");
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-  WM_operator_properties_confirm_or_exec(ot);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Copy Object Utilities
- * \{ */
-
-/* after copying objects, copied data should get new pointers */
-static void copy_object_set_idnew(bContext *C)
-{
-  Main *bmain = CTX_data_main(C);
-
-  CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
-    BKE_libblock_relink_to_newid(bmain, &ob->id, 0);
-  }
-  CTX_DATA_END;
-
-#ifndef NDEBUG
-  /* Call to `BKE_libblock_relink_to_newid` above is supposed to have cleared all those flags. */
-  ID *id_iter;
-  FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
-    if (GS(id_iter->name) == ID_OB) {
-      /* Not all duplicated objects would be used by other newly duplicated data, so their flag
-       * will not always be cleared. */
-      continue;
-    }
-    BLI_assert((id_iter->tag & LIB_TAG_NEW) == 0);
-  }
-  FOREACH_MAIN_ID_END;
-#endif
-
-  BKE_main_id_newptr_and_tag_clear(bmain);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Make Instanced Objects Real Operator
- * \{ */
-
-/* XXX TODO: That whole hierarchy handling based on persistent_id tricks is
- * very confusing and convoluted, and it will fail in many cases besides basic ones.
- * Think this should be replaced by a proper tree-like representation of the instantiations,
- * should help a lot in both readability, and precise consistent rebuilding of hierarchy.
- */
-
-/**
- * \note regarding hashing dupli-objects which come from OB_DUPLICOLLECTION,
- * skip the first member of #DupliObject.persistent_id
- * since its a unique index and we only want to know if the group objects are from the same
- * dupli-group instance.
- *
- * \note regarding hashing dupli-objects which come from non-OB_DUPLICOLLECTION,
- * include the first member of #DupliObject.persistent_id
- * since its the index of the vertex/face the object is instantiated on and we want to identify
- * objects on the same vertex/face.
- * In other words, we consider each group of objects from a same item as being
- * the 'local group' where to check for parents.
- */
-static uint dupliobject_hash(const void *ptr)
-{
-  const DupliObject *dob = ptr;
-  uint hash = BLI_ghashutil_ptrhash(dob->ob);
-
-  if (dob->type == OB_DUPLICOLLECTION) {
-    for (int i = 1; (i < MAX_DUPLI_RECUR) && dob->persistent_id[i] != INT_MAX; i++) {
-      hash ^= (dob->persistent_id[i] ^ i);
-    }
-  }
-  else {
-    hash ^= (dob->persistent_id[0] ^ 0);
-  }
-  return hash;
-}
-
-/**
- * \note regarding hashing dupli-objects when using OB_DUPLICOLLECTION,
- * skip the first member of #DupliObject.persistent_id
- * since its a unique index and we only want to know if the group objects are from the same
- * dupli-group instance.
- */
-static uint dupliobject_instancer_hash(const void *ptr)
-{
-  const DupliObject *dob = ptr;
-  uint hash = BLI_ghashutil_inthash(dob->persistent_id[0]);
-  for (int i = 1; (i < MAX_DUPLI_RECUR) && dob->persistent_id[i] != INT_MAX; i++) {
-    hash ^= (dob->persistent_id[i] ^ i);
-  }
-  return hash;
-}
-
-/* Compare function that matches dupliobject_hash */
-static bool dupliobject_cmp(const void *a_, const void *b_)
-{
-  const DupliObject *a = a_;
-  const DupliObject *b = b_;
-
-  if (a->ob != b->ob) {
-    return true;
-  }
-
-  if (a->type != b->type) {
-    return true;
-  }
-
-  if (a->type == OB_DUPLICOLLECTION) {
-    for (int i = 1; (i < MAX_DUPLI_RECUR); i++) {
-      if (a->persistent_id[i] != b->persistent_id[i]) {
-        return true;
-      }
-      if (a->persistent_id[i] == INT_MAX) {
-        break;
-      }
-    }
-  }
-  else {
-    if (a->persistent_id[0] != b->persistent_id[0]) {
-      return true;
-    }
-  }
-
-  /* matching */
-  return false;
-}
-
-/* Compare function that matches dupliobject_instancer_hash. */
-static bool dupliobject_instancer_cmp(const void *a_, const void *b_)
-{
-  const DupliObject *a = a_;
-  const DupliObject *b = b_;
-
-  for (int i = 0; (i < MAX_DUPLI_RECUR); i++) {
-    if (a->persistent_id[i] != b->persistent_id[i]) {
-      return true;
-    }
-    if (a->persistent_id[i] == INT_MAX) {
-      break;
-    }
-  }
-
-  /* matching */
-  return false;
-}
-
-static void make_object_duplilist_real(bContext *C,
-                                       Depsgraph *depsgraph,
-                                       Scene *scene,
-                                       Base *base,
-                                       const bool use_base_parent,
-                                       const bool use_hierarchy)
-{
-  Main *bmain = CTX_data_main(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  GHash *parent_gh = NULL, *instancer_gh = NULL;
-
-  Object *object_eval = DEG_get_evaluated_object(depsgraph, base->object);
-
-  if (!(base->object->transflag & OB_DUPLI) &&
-      !BKE_object_has_geometry_set_instances(object_eval)) {
-    return;
-  }
-
-  ListBase *lb_duplis = object_duplilist(depsgraph, scene, object_eval);
-
-  if (BLI_listbase_is_empty(lb_duplis)) {
-    free_object_duplilist(lb_duplis);
-    return;
-  }
-
-  GHash *dupli_gh = BLI_ghash_ptr_new(__func__);
-  if (use_hierarchy) {
-    parent_gh = BLI_ghash_new(dupliobject_hash, dupliobject_cmp, __func__);
-
-    if (use_base_parent) {
-      instancer_gh = BLI_ghash_new(
-          dupliobject_instancer_hash, dupliobject_instancer_cmp, __func__);
-    }
-  }
-
-  LISTBASE_FOREACH (DupliObject *, dob, lb_duplis) {
-    Object *ob_src = DEG_get_original_object(dob->ob);
-    Object *ob_dst = ID_NEW_SET(ob_src, BKE_id_copy(bmain, &ob_src->id));
-    id_us_min(&ob_dst->id);
-
-    /* font duplis can have a totcol without material, we get them from parent
-     * should be implemented better...
-     */
-    if (ob_dst->mat == NULL) {
-      ob_dst->totcol = 0;
-    }
-
-    BKE_collection_object_add_from(bmain, scene, base->object, ob_dst);
-    Base *base_dst = BKE_view_layer_base_find(view_layer, ob_dst);
-    BLI_assert(base_dst != NULL);
-
-    ED_object_base_select(base_dst, BA_SELECT);
-    DEG_id_tag_update(&ob_dst->id, ID_RECALC_SELECT);
-
-    BKE_scene_object_base_flag_sync_from_base(base_dst);
-
-    /* make sure apply works */
-    BKE_animdata_free(&ob_dst->id, true);
-    ob_dst->adt = NULL;
-
-    ob_dst->parent = NULL;
-    BKE_constraints_free(&ob_dst->constraints);
-    ob_dst->runtime.curve_cache = NULL;
-    const bool is_dupli_instancer = (ob_dst->transflag & OB_DUPLI) != 0;
-    ob_dst->transflag &= ~OB_DUPLI;
-    /* Remove instantiated collection, it's annoying to keep it here
-     * (and get potentially a lot of usages of it then...). */
-    id_us_min((ID *)ob_dst->instance_collection);
-    ob_dst->instance_collection = NULL;
-
-    copy_m4_m4(ob_dst->obmat, dob->mat);
-    BKE_object_apply_mat4(ob_dst, ob_dst->obmat, false, false);
-
-    BLI_ghash_insert(dupli_gh, dob, ob_dst);
-    if (parent_gh) {
-      void **val;
-      /* Due to nature of hash/comparison of this ghash, a lot of duplis may be considered as
-       * 'the same', this avoids trying to insert same key several time and
-       * raise asserts in debug builds... */
-      if (!BLI_ghash_ensure_p(parent_gh, dob, &val)) {
-        *val = ob_dst;
-      }
-
-      if (is_dupli_instancer && instancer_gh) {
-        /* Same as above, we may have several 'hits'. */
-        if (!BLI_ghash_ensure_p(instancer_gh, dob, &val)) {
-          *val = ob_dst;
-        }
-      }
-    }
-  }
-
-  LISTBASE_FOREACH (DupliObject *, dob, lb_duplis) {
-    Object *ob_src = dob->ob;
-    Object *ob_dst = BLI_ghash_lookup(dupli_gh, dob);
-
-    /* Remap new object to itself, and clear again newid pointer of orig object. */
-    BKE_libblock_relink_to_newid(bmain, &ob_dst->id, 0);
-
-    DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY);
-
-    if (use_hierarchy) {
-      /* original parents */
-      Object *ob_src_par = ob_src->parent;
-      Object *ob_dst_par = NULL;
-
-      /* find parent that was also made real */
-      if (ob_src_par) {
-        /* OK to keep most of the members uninitialized,
-         * they won't be read, this is simply for a hash lookup. */
-        DupliObject dob_key;
-        dob_key.ob = ob_src_par;
-        dob_key.type = dob->type;
-        if (dob->type == OB_DUPLICOLLECTION) {
-          memcpy(&dob_key.persistent_id[1],
-                 &dob->persistent_id[1],
-                 sizeof(dob->persistent_id[1]) * (MAX_DUPLI_RECUR - 1));
-        }
-        else {
-          dob_key.persistent_id[0] = dob->persistent_id[0];
-        }
-        ob_dst_par = BLI_ghash_lookup(parent_gh, &dob_key);
-      }
-
-      if (ob_dst_par) {
-        /* allow for all possible parent types */
-        ob_dst->partype = ob_src->partype;
-        BLI_strncpy(ob_dst->parsubstr, ob_src->parsubstr, sizeof(ob_dst->parsubstr));
-        ob_dst->par1 = ob_src->par1;
-        ob_dst->par2 = ob_src->par2;
-        ob_dst->par3 = ob_src->par3;
-
-        copy_m4_m4(ob_dst->parentinv, ob_src->parentinv);
-
-        ob_dst->parent = ob_dst_par;
-      }
-    }
-    if (use_base_parent && ob_dst->parent == NULL) {
-      Object *ob_dst_par = NULL;
-
-      if (instancer_gh != NULL) {
-        /* OK to keep most of the members uninitialized,
-         * they won't be read, this is simply for a hash lookup. */
-        DupliObject dob_key;
-        /* We are looking one step upper in hierarchy, so we need to 'shift' the `persistent_id`,
-         * ignoring the first item.
-         * We only check on persistent_id here, since we have no idea what object it might be. */
-        memcpy(&dob_key.persistent_id[0],
-               &dob->persistent_id[1],
-               sizeof(dob_key.persistent_id[0]) * (MAX_DUPLI_RECUR - 1));
-        ob_dst_par = BLI_ghash_lookup(instancer_gh, &dob_key);
-      }
-
-      if (ob_dst_par == NULL) {
-        /* Default to parenting to root object...
-         * Always the case when use_hierarchy is false. */
-        ob_dst_par = base->object;
-      }
-
-      ob_dst->parent = ob_dst_par;
-      ob_dst->partype = PAROBJECT;
-    }
-
-    if (ob_dst->parent) {
-      /* NOTE: this may be the parent of other objects, but it should
-       * still work out ok */
-      BKE_object_apply_mat4(ob_dst, dob->mat, false, true);
-
-      /* to set ob_dst->orig and in case there's any other discrepancies */
-      DEG_id_tag_update(&ob_dst->id, ID_RECALC_TRANSFORM);
-    }
-  }
-
-  if (base->object->transflag & OB_DUPLICOLLECTION && base->object->instance_collection) {
-    base->object->instance_collection = NULL;
-  }
-
-  ED_object_base_select(base, BA_DESELECT);
-  DEG_id_tag_update(&base->object->id, ID_RECALC_SELECT);
-
-  BLI_ghash_free(dupli_gh, NULL, NULL);
-  if (parent_gh) {
-    BLI_ghash_free(parent_gh, NULL, NULL);
-  }
-  if (instancer_gh) {
-    BLI_ghash_free(instancer_gh, NULL, NULL);
-  }
-
-  free_object_duplilist(lb_duplis);
-
-  BKE_main_id_newptr_and_tag_clear(bmain);
-
-  base->object->transflag &= ~OB_DUPLI;
-  DEG_id_tag_update(&base->object->id, ID_RECALC_COPY_ON_WRITE);
-}
-
-static int object_duplicates_make_real_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
-  Scene *scene = CTX_data_scene(C);
-
-  const bool use_base_parent = RNA_boolean_get(op->ptr, "use_base_parent");
-  const bool use_hierarchy = RNA_boolean_get(op->ptr, "use_hierarchy");
-
-  BKE_main_id_newptr_and_tag_clear(bmain);
-
-  CTX_DATA_BEGIN (C, Base *, base, selected_editable_bases) {
-    make_object_duplilist_real(C, depsgraph, scene, base, use_base_parent, use_hierarchy);
-
-    /* dependencies were changed */
-    WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, base->object);
-  }
-  CTX_DATA_END;
-
-  DEG_relations_tag_update(bmain);
-  WM_event_add_notifier(C, NC_SCENE, scene);
-  WM_main_add_notifier(NC_OBJECT | ND_DRAW, NULL);
-  ED_outliner_select_sync_from_object_tag(C);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_duplicates_make_real(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Make Instances Real";
-  ot->description = "Make instanced objects attached to this object real";
-  ot->idname = "OBJECT_OT_duplicates_make_real";
-
-  /* api callbacks */
-  ot->exec = object_duplicates_make_real_exec;
-
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  RNA_def_boolean(ot->srna,
-                  "use_base_parent",
-                  0,
-                  "Parent",
-                  "Parent newly created objects to the original instancer");
-  RNA_def_boolean(
-      ot->srna, "use_hierarchy", 0, "Keep Hierarchy", "Maintain parent child relationships");
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Data Convert Operator
- * \{ */
-
-static const EnumPropertyItem convert_target_items[] = {
-    {OB_CURVES_LEGACY,
-     "CURVE",
-     ICON_OUTLINER_OB_CURVE,
-     "Curve",
-     "Curve from Mesh or Text objects"},
-    {OB_MESH,
-     "MESH",
-     ICON_OUTLINER_OB_MESH,
-     "Mesh",
-#ifdef WITH_POINT_CLOUD
-     "Mesh from Curve, Surface, Metaball, Text, or Point Cloud objects"},
-#else
-     "Mesh from Curve, Surface, Metaball, or Text objects"},
-#endif
-    {OB_GPENCIL,
-     "GPENCIL",
-     ICON_OUTLINER_OB_GREASEPENCIL,
-     "Grease Pencil",
-     "Grease Pencil from Curve or Mesh objects"},
-#ifdef WITH_POINT_CLOUD
-    {OB_POINTCLOUD,
-     "POINTCLOUD",
-     ICON_OUTLINER_OB_POINTCLOUD,
-     "Point Cloud",
-     "Point Cloud from Mesh objects"},
-#endif
-    {0, NULL, 0, NULL, NULL},
-};
-
-static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene *scene, Object *ob)
-{
-  if (ob->runtime.curve_cache == NULL) {
-    /* Force creation. This is normally not needed but on operator
-     * redo we might end up with an object which isn't evaluated yet.
-     * Also happens in case we are working on a copy of the object
-     * (all its caches have been nuked then).
-     */
-    if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)) {
-      /* We need 'for render' ON here, to enable computing bevel dipslist if needed.
-       * Also makes sense anyway, we would not want e.g. to lose hidden parts etc. */
-      BKE_displist_make_curveTypes(depsgraph, scene, ob, true);
-    }
-    else if (ob->type == OB_MBALL) {
-      BKE_displist_make_mball(depsgraph, scene, ob);
-    }
-  }
-}
-
-static void object_data_convert_curve_to_mesh(Main *bmain, Depsgraph *depsgraph, Object *ob)
-{
-  Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
-  Curve *curve = ob->data;
-
-  Mesh *mesh = BKE_mesh_new_from_object_to_bmain(bmain, depsgraph, object_eval, true);
-  if (mesh == NULL) {
-    /* Unable to convert the curve to a mesh. */
-    return;
-  }
-
-  BKE_object_free_modifiers(ob, 0);
-  /* Replace curve used by the object itself. */
-  ob->data = mesh;
-  ob->type = OB_MESH;
-  id_us_min(&curve->id);
-  id_us_plus(&mesh->id);
-  /* Change objects which are using same curve.
-   * A bit annoying, but:
-   * - It's possible to have multiple curve objects selected which are sharing the same curve
-   *   data-block. We don't want mesh to be created for every of those objects.
-   * - This is how conversion worked for a long time. */
-  LISTBASE_FOREACH (Object *, other_object, &bmain->objects) {
-    if (other_object->data == curve) {
-      other_object->type = OB_MESH;
-
-      id_us_min((ID *)other_object->data);
-      other_object->data = ob->data;
-      id_us_plus((ID *)other_object->data);
-    }
-  }
-}
-
-static bool object_convert_poll(bContext *C)
-{
-  Scene *scene = CTX_data_scene(C);
-  Base *base_act = CTX_data_active_base(C);
-  Object *obact = base_act ? base_act->object : NULL;
-
-  if (obact == NULL || obact->data == NULL || ID_IS_LINKED(obact) ||
-      ID_IS_OVERRIDE_LIBRARY(obact) || ID_IS_OVERRIDE_LIBRARY(obact->data)) {
-    return false;
-  }
-
-  return (!ID_IS_LINKED(scene) && (BKE_object_is_in_editmode(obact) == false) &&
-          (base_act->flag & BASE_SELECTED));
-}
-
-/* Helper for object_convert_exec */
-static Base *duplibase_for_convert(
-    Main *bmain, Depsgraph *depsgraph, Scene *scene, ViewLayer *view_layer, Base *base, Object *ob)
-{
-  if (ob == NULL) {
-    ob = base->object;
-  }
-
-  Object *obn = (Object *)BKE_id_copy(bmain, &ob->id);
-  id_us_min(&obn->id);
-  DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
-  BKE_collection_object_add_from(bmain, scene, ob, obn);
-
-  Base *basen = BKE_view_layer_base_find(view_layer, obn);
-  ED_object_base_select(basen, BA_SELECT);
-  ED_object_base_select(base, BA_DESELECT);
-
-  /* XXX: An ugly hack needed because if we re-run depsgraph with some new meta-ball objects
-   * having same 'family name' as orig ones, they will affect end result of meta-ball computation.
-   * For until we get rid of that name-based thingy in MBalls, that should do the trick
-   * (this is weak, but other solution (to change name of `obn`) is even worse imho).
-   * See T65996. */
-  const bool is_meta_ball = (obn->type == OB_MBALL);
-  void *obdata = obn->data;
-  if (is_meta_ball) {
-    obn->type = OB_EMPTY;
-    obn->data = NULL;
-  }
-
-  /* XXX Doing that here is stupid, it means we update and re-evaluate the whole depsgraph every
-   * time we need to duplicate an object to convert it. Even worse, this is not 100% correct, since
-   * we do not yet have duplicated obdata.
-   * However, that is a safe solution for now. Proper, longer-term solution is to refactor
-   * object_convert_exec to:
-   *  - duplicate all data it needs to in a first loop.
-   *  - do a single update.
-   *  - convert data in a second loop. */
-  DEG_graph_tag_relations_update(depsgraph);
-  CustomData_MeshMasks customdata_mask_prev = scene->customdata_mask;
-  CustomData_MeshMasks_update(&scene->customdata_mask, &CD_MASK_MESH);
-  BKE_scene_graph_update_tagged(depsgraph, bmain);
-  scene->customdata_mask = customdata_mask_prev;
-
-  if (is_meta_ball) {
-    obn->type = OB_MBALL;
-    obn->data = obdata;
-  }
-
-  return basen;
-}
-
-static int object_convert_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  View3D *v3d = CTX_wm_view3d(C);
-  Base *basen = NULL, *basact = NULL;
-  Object *ob1, *obact = CTX_data_active_object(C);
-  const short target = RNA_enum_get(op->ptr, "target");
-  bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
-
-  const float angle = RNA_float_get(op->ptr, "angle");
-  const int thickness = RNA_int_get(op->ptr, "thickness");
-  const bool use_seams = RNA_boolean_get(op->ptr, "seams");
-  const bool use_faces = RNA_boolean_get(op->ptr, "faces");
-  const float offset = RNA_float_get(op->ptr, "offset");
-
-  int a, mballConverted = 0;
-  bool gpencilConverted = false;
-  bool gpencilCurveConverted = false;
-
-  /* don't forget multiple users! */
-
-  {
-    FOREACH_SCENE_OBJECT_BEGIN (scene, ob) {
-      ob->flag &= ~OB_DONE;
-
-      /* flag data that's not been edited (only needed for !keep_original) */
-      if (ob->data) {
-        ((ID *)ob->data)->tag |= LIB_TAG_DOIT;
-      }
-
-      /* possible metaball basis is not in this scene */
-      if (ob->type == OB_MBALL && target == OB_MESH) {
-        if (BKE_mball_is_basis(ob) == false) {
-          Object *ob_basis;
-          ob_basis = BKE_mball_basis_find(scene, ob);
-          if (ob_basis) {
-            ob_basis->flag &= ~OB_DONE;
-          }
-        }
-      }
-    }
-    FOREACH_SCENE_OBJECT_END;
-  }
-
-  ListBase selected_editable_bases;
-  CTX_data_selected_editable_bases(C, &selected_editable_bases);
-
-  /* Ensure we get all meshes calculated with a sufficient data-mask,
-   * needed since re-evaluating single modifiers causes bugs if they depend
-   * on other objects data masks too, see: T50950. */
-  {
-    LISTBASE_FOREACH (CollectionPointerLink *, link, &selected_editable_bases) {
-      Base *base = link->ptr.data;
-      Object *ob = base->object;
-
-      /* The way object type conversion works currently (enforcing conversion of *all* objects
-       * using converted object-data, even some un-selected/hidden/another scene ones,
-       * sounds totally bad to me.
-       * However, changing this is more design than bug-fix, not to mention convoluted code below,
-       * so that will be for later.
-       * But at the very least, do not do that with linked IDs! */
-      if ((ID_IS_LINKED(ob) || (ob->data && ID_IS_LINKED(ob->data))) && !keep_original) {
-        keep_original = true;
-        BKE_report(
-            op->reports,
-            RPT_INFO,
-            "Converting some linked object/object data, enforcing 'Keep Original' option to True");
-      }
-
-      DEG_id_tag_update(&base->object->id, ID_RECALC_GEOMETRY);
-    }
-
-    CustomData_MeshMasks customdata_mask_prev = scene->customdata_mask;
-    CustomData_MeshMasks_update(&scene->customdata_mask, &CD_MASK_MESH);
-    BKE_scene_graph_update_tagged(depsgraph, bmain);
-    scene->customdata_mask = customdata_mask_prev;
-  }
-
-  LISTBASE_FOREACH (CollectionPointerLink *, link, &selected_editable_bases) {
-    Object *newob = NULL;
-    Base *base = link->ptr.data;
-    Object *ob = base->object;
-
-    if (ob->flag & OB_DONE || !IS_TAGGED(ob->data)) {
-      if (ob->type != target) {
-        base->flag &= ~SELECT;
-        ob->flag &= ~SELECT;
-      }
-
-      /* obdata already modified */
-      if (!IS_TAGGED(ob->data)) {
-        /* When 2 objects with linked data are selected, converting both
-         * would keep modifiers on all but the converted object T26003. */
-        if (ob->type == OB_MESH) {
-          BKE_object_free_modifiers(ob, 0); /* after derivedmesh calls! */
-        }
-        if (ob->type == OB_GPENCIL) {
-          BKE_object_free_modifiers(ob, 0); /* after derivedmesh calls! */
-          BKE_object_free_shaderfx(ob, 0);
-        }
-      }
-    }
-    else if (ob->type == OB_MESH && target == OB_CURVES_LEGACY) {
-      ob->flag |= OB_DONE;
-
-      if (keep_original) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-        newob = basen->object;
-
-        /* Decrement original mesh's usage count. */
-        Mesh *me = newob->data;
-        id_us_min(&me->id);
-
-        /* Make a new copy of the mesh. */
-        newob->data = BKE_id_copy(bmain, &me->id);
-      }
-      else {
-        newob = ob;
-      }
-
-      BKE_mesh_to_curve(bmain, depsgraph, scene, newob);
-
-      if (newob->type == OB_CURVES_LEGACY) {
-        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
-        if (newob->rigidbody_object != NULL) {
-          ED_rigidbody_object_remove(bmain, scene, newob);
-        }
-      }
-    }
-    else if (ob->type == OB_MESH && target == OB_GPENCIL) {
-      ob->flag |= OB_DONE;
-
-      /* Create a new grease pencil object and copy transformations. */
-      ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
-      float loc[3], size[3], rot[3][3], eul[3];
-      float matrix[4][4];
-      mat4_to_loc_rot_size(loc, rot, size, ob->obmat);
-      mat3_to_eul(eul, rot);
-
-      Object *ob_gpencil = ED_gpencil_add_object(C, loc, local_view_bits);
-      copy_v3_v3(ob_gpencil->loc, loc);
-      copy_v3_v3(ob_gpencil->rot, eul);
-      copy_v3_v3(ob_gpencil->scale, size);
-      unit_m4(matrix);
-      /* Set object in 3D mode. */
-      bGPdata *gpd = (bGPdata *)ob_gpencil->data;
-      gpd->draw_mode = GP_DRAWMODE_3D;
-
-      gpencilConverted |= BKE_gpencil_convert_mesh(bmain,
-                                                   depsgraph,
-                                                   scene,
-                                                   ob_gpencil,
-                                                   ob,
-                                                   angle,
-                                                   thickness,
-                                                   offset,
-                                                   matrix,
-                                                   0,
-                                                   use_seams,
-                                                   use_faces,
-                                                   true);
-
-      /* Remove unused materials. */
-      int actcol = ob_gpencil->actcol;
-      for (int slot = 1; slot <= ob_gpencil->totcol; slot++) {
-        while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) {
-          ob_gpencil->actcol = slot;
-          BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil);
-
-          if (actcol >= slot) {
-            actcol--;
-          }
-        }
-      }
-      ob_gpencil->actcol = actcol;
-    }
-    else if (ob->type == OB_MESH && target == OB_POINTCLOUD) {
-      ob->flag |= OB_DONE;
-
-      if (keep_original) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-        newob = basen->object;
-
-        /* Decrement original mesh's usage count. */
-        Mesh *me = newob->data;
-        id_us_min(&me->id);
-
-        /* Make a new copy of the mesh. */
-        newob->data = BKE_id_copy(bmain, &me->id);
-      }
-      else {
-        newob = ob;
-      }
-
-      BKE_mesh_to_pointcloud(bmain, depsgraph, scene, newob);
-
-      if (newob->type == OB_POINTCLOUD) {
-        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
-        ED_rigidbody_object_remove(bmain, scene, newob);
-      }
-    }
-    else if (ob->type == OB_MESH) {
-      ob->flag |= OB_DONE;
-
-      if (keep_original) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-        newob = basen->object;
-
-        /* Decrement original mesh's usage count. */
-        Mesh *me = newob->data;
-        id_us_min(&me->id);
-
-        /* Make a new copy of the mesh. */
-        newob->data = BKE_id_copy(bmain, &me->id);
-      }
-      else {
-        newob = ob;
-        DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
-      }
-
-      /* make new mesh data from the original copy */
-      /* NOTE: get the mesh from the original, not from the copy in some
-       * cases this doesn't give correct results (when MDEF is used for eg)
-       */
-      Scene *scene_eval = (Scene *)DEG_get_evaluated_id(depsgraph, &scene->id);
-      Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
-      Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
-      me_eval = BKE_mesh_copy_for_eval(me_eval, false);
-      /* Full (edge-angle based) draw calculation should ideally be performed. */
-      BKE_mesh_edges_set_draw_render(me_eval);
-      BKE_object_material_from_eval_data(bmain, newob, &me_eval->id);
-      Mesh *new_mesh = (Mesh *)newob->data;
-      BKE_mesh_nomain_to_mesh(me_eval, new_mesh, newob, &CD_MASK_MESH, true);
-      /* Anonymous attributes shouldn't be available on the applied geometry. */
-      BKE_mesh_anonymous_attributes_remove(new_mesh);
-      BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
-    }
-    else if (ob->type == OB_FONT) {
-      ob->flag |= OB_DONE;
-
-      if (keep_original) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-        newob = basen->object;
-
-        /* Decrement original curve's usage count. */
-        id_us_min(&((Curve *)newob->data)->id);
-
-        /* Make a new copy of the curve. */
-        newob->data = BKE_id_copy(bmain, ob->data);
-      }
-      else {
-        newob = ob;
-      }
-
-      Curve *cu = newob->data;
-
-      Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
-      BKE_vfont_to_curve_ex(ob_eval, ob_eval->data, FO_EDIT, &cu->nurb, NULL, NULL, NULL, NULL);
-
-      newob->type = OB_CURVES_LEGACY;
-      cu->type = OB_CURVES_LEGACY;
-
-      if (cu->vfont) {
-        id_us_min(&cu->vfont->id);
-        cu->vfont = NULL;
-      }
-      if (cu->vfontb) {
-        id_us_min(&cu->vfontb->id);
-        cu->vfontb = NULL;
-      }
-      if (cu->vfonti) {
-        id_us_min(&cu->vfonti->id);
-        cu->vfonti = NULL;
-      }
-      if (cu->vfontbi) {
-        id_us_min(&cu->vfontbi->id);
-        cu->vfontbi = NULL;
-      }
-
-      if (!keep_original) {
-        /* other users */
-        if (ID_REAL_USERS(&cu->id) > 1) {
-          for (ob1 = bmain->objects.first; ob1; ob1 = ob1->id.next) {
-            if (ob1->data == ob->data) {
-              ob1->type = OB_CURVES_LEGACY;
-              DEG_id_tag_update(&ob1->id,
-                                ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
-            }
-          }
-        }
-      }
-
-      LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
-        nu->charidx = 0;
-      }
-
-      cu->flag &= ~CU_3D;
-      BKE_curve_dimension_update(cu);
-
-      if (target == OB_MESH) {
-        /* No assumption should be made that the resulting objects is a mesh, as conversion can
-         * fail. */
-        object_data_convert_curve_to_mesh(bmain, depsgraph, newob);
-        /* meshes doesn't use displist */
-        BKE_object_free_curve_cache(newob);
-      }
-      else if (target == OB_GPENCIL) {
-        ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
-        Object *ob_gpencil = ED_gpencil_add_object(C, newob->loc, local_view_bits);
-        copy_v3_v3(ob_gpencil->rot, newob->rot);
-        copy_v3_v3(ob_gpencil->scale, newob->scale);
-        BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, newob, false, 1.0f, 0.0f);
-        gpencilConverted = true;
-        gpencilCurveConverted = true;
-        basen = NULL;
-      }
-    }
-    else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) {
-      ob->flag |= OB_DONE;
-
-      if (target == OB_MESH) {
-        if (keep_original) {
-          basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-          newob = basen->object;
-
-          /* Decrement original curve's usage count. */
-          id_us_min(&((Curve *)newob->data)->id);
-
-          /* make a new copy of the curve */
-          newob->data = BKE_id_copy(bmain, ob->data);
-        }
-        else {
-          newob = ob;
-        }
-
-        /* No assumption should be made that the resulting objects is a mesh, as conversion can
-         * fail. */
-        object_data_convert_curve_to_mesh(bmain, depsgraph, newob);
-        /* meshes doesn't use displist */
-        BKE_object_free_curve_cache(newob);
-      }
-      else if (target == OB_GPENCIL) {
-        if (ob->type != OB_CURVES_LEGACY) {
-          ob->flag &= ~OB_DONE;
-          BKE_report(op->reports, RPT_ERROR, "Convert Surfaces to Grease Pencil is not supported");
-        }
-        else {
-          /* Create a new grease pencil object and copy transformations.
-           * Nurbs Surface are not supported.
-           */
-          ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
-          Object *ob_gpencil = ED_gpencil_add_object(C, ob->loc, local_view_bits);
-          copy_v3_v3(ob_gpencil->rot, ob->rot);
-          copy_v3_v3(ob_gpencil->scale, ob->scale);
-          BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, ob, false, 1.0f, 0.0f);
-          gpencilConverted = true;
-        }
-      }
-    }
-    else if (ob->type == OB_MBALL && target == OB_MESH) {
-      Object *baseob;
-
-      base->flag &= ~BASE_SELECTED;
-      ob->base_flag &= ~BASE_SELECTED;
-
-      baseob = BKE_mball_basis_find(scene, ob);
-
-      if (ob != baseob) {
-        /* if motherball is converting it would be marked as done later */
-        ob->flag |= OB_DONE;
-      }
-
-      if (!(baseob->flag & OB_DONE)) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, baseob);
-        newob = basen->object;
-
-        MetaBall *mb = newob->data;
-        id_us_min(&mb->id);
-
-        newob->data = BKE_mesh_add(bmain, "Mesh");
-        newob->type = OB_MESH;
-
-        Mesh *me = newob->data;
-        me->totcol = mb->totcol;
-        if (newob->totcol) {
-          me->mat = MEM_dupallocN(mb->mat);
-          for (a = 0; a < newob->totcol; a++) {
-            id_us_plus((ID *)me->mat[a]);
-          }
-        }
-
-        object_data_convert_ensure_curve_cache(depsgraph, scene, baseob);
-        BKE_mesh_from_metaball(&baseob->runtime.curve_cache->disp, newob->data);
-
-        if (obact->type == OB_MBALL) {
-          basact = basen;
-        }
-
-        baseob->flag |= OB_DONE;
-        mballConverted = 1;
-      }
-    }
-    else if (ob->type == OB_POINTCLOUD && target == OB_MESH) {
-      ob->flag |= OB_DONE;
-
-      if (keep_original) {
-        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, NULL);
-        newob = basen->object;
-
-        /* Decrement original point cloud's usage count. */
-        PointCloud *pointcloud = newob->data;
-        id_us_min(&pointcloud->id);
-
-        /* Make a new copy of the point cloud. */
-        newob->data = BKE_id_copy(bmain, &pointcloud->id);
-      }
-      else {
-        newob = ob;
-      }
-
-      BKE_pointcloud_to_mesh(bmain, depsgraph, scene, newob);
-
-      if (newob->type == OB_MESH) {
-        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
-        ED_rigidbody_object_remove(bmain, scene, newob);
-      }
-    }
-    else {
-      continue;
-    }
-
-    /* Ensure new object has consistent material data with its new obdata. */
-    if (newob) {
-      BKE_object_materials_test(bmain, newob, newob->data);
-    }
-
-    /* tag obdata if it was been changed */
-
-    /* If the original object is active then make this object active */
-    if (basen) {
-      if (ob == obact) {
-        /* store new active base to update BASACT */
-        basact = basen;
-      }
-
-      basen = NULL;
-    }
-
-    if (!keep_original && (ob->flag & OB_DONE)) {
-      /* NOTE: Tag transform for update because object parenting to curve with path is handled
-       * differently from all other cases. Converting curve to mesh and mesh to curve will likely
-       * affect the way children are evaluated.
-       * It is not enough to tag only geometry and rely on the curve parenting relations because
-       * this relation is lost when curve is converted to mesh. */
-      DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_TRANSFORM);
-      ((ID *)ob->data)->tag &= ~LIB_TAG_DOIT; /* flag not to convert this datablock again */
-    }
-  }
-  BLI_freelistN(&selected_editable_bases);
-
-  if (!keep_original) {
-    if (mballConverted) {
-      /* We need to remove non-basis MBalls first, otherwise we won't be able to detect them if
-       * their basis happens to be removed first. */
-      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_mball) {
-        if (ob_mball->type == OB_MBALL) {
-          Object *ob_basis = NULL;
-          if (!BKE_mball_is_basis(ob_mball) &&
-              ((ob_basis = BKE_mball_basis_find(scene, ob_mball)) && (ob_basis->flag & OB_DONE))) {
-            ED_object_base_free_and_unlink(bmain, scene, ob_mball);
-          }
-        }
-      }
-      FOREACH_SCENE_OBJECT_END;
-      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_mball) {
-        if (ob_mball->type == OB_MBALL) {
-          if (ob_mball->flag & OB_DONE) {
-            if (BKE_mball_is_basis(ob_mball)) {
-              ED_object_base_free_and_unlink(bmain, scene, ob_mball);
-            }
-          }
-        }
-      }
-      FOREACH_SCENE_OBJECT_END;
-    }
-    /* Remove curves and meshes converted to Grease Pencil object. */
-    if (gpencilConverted) {
-      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) {
-        if (ELEM(ob_delete->type, OB_CURVES_LEGACY, OB_MESH)) {
-          if (ob_delete->flag & OB_DONE) {
-            ED_object_base_free_and_unlink(bmain, scene, ob_delete);
-          }
-        }
-      }
-      FOREACH_SCENE_OBJECT_END;
-    }
-  }
-  else {
-    /* Remove Text curves converted to Grease Pencil object to avoid duplicated curves. */
-    if (gpencilCurveConverted) {
-      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) {
-        if (ELEM(ob_delete->type, OB_CURVES_LEGACY) && (ob_delete->flag & OB_DONE)) {
-          ED_object_base_free_and_unlink(bmain, scene, ob_delete);
-        }
-      }
-      FOREACH_SCENE_OBJECT_END;
-    }
-  }
-
-  // XXX  ED_object_editmode_enter(C, 0);
-  // XXX  exit_editmode(C, EM_FREEDATA|); /* freedata, but no undo */
-
-  if (basact) {
-    /* active base was changed */
-    ED_object_base_activate(C, basact);
-    BASACT(view_layer) = basact;
-  }
-  else if (BASACT(view_layer)->object->flag & OB_DONE) {
-    WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, BASACT(view_layer)->object);
-    WM_event_add_notifier(C, NC_OBJECT | ND_DATA, BASACT(view_layer)->object);
-  }
-
-  DEG_relations_tag_update(bmain);
-  DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
-  WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, scene);
-  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
-  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
-
-  return OPERATOR_FINISHED;
-}
-
-static void object_convert_ui(bContext *UNUSED(C), wmOperator *op)
-{
-  uiLayout *layout = op->layout;
-
-  uiLayoutSetPropSep(layout, true);
-
-  uiItemR(layout, op->ptr, "target", 0, NULL, ICON_NONE);
-  uiItemR(layout, op->ptr, "keep_original", 0, NULL, ICON_NONE);
-
-  if (RNA_enum_get(op->ptr, "target") == OB_GPENCIL) {
-    uiItemR(layout, op->ptr, "thickness", 0, NULL, ICON_NONE);
-    uiItemR(layout, op->ptr, "angle", 0, NULL, ICON_NONE);
-    uiItemR(layout, op->ptr, "offset", 0, NULL, ICON_NONE);
-    uiItemR(layout, op->ptr, "seams", 0, NULL, ICON_NONE);
-    uiItemR(layout, op->ptr, "faces", 0, NULL, ICON_NONE);
-  }
-}
-
-void OBJECT_OT_convert(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  /* identifiers */
-  ot->name = "Convert To";
-  ot->description = "Convert selected objects to another type";
-  ot->idname = "OBJECT_OT_convert";
-
-  /* api callbacks */
-  ot->invoke = WM_menu_invoke;
-  ot->exec = object_convert_exec;
-  ot->poll = object_convert_poll;
-  ot->ui = object_convert_ui;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* properties */
-  ot->prop = RNA_def_enum(
-      ot->srna, "target", convert_target_items, OB_MESH, "Target", "Type of object to convert to");
-  RNA_def_boolean(ot->srna,
-                  "keep_original",
-                  0,
-                  "Keep Original",
-                  "Keep original objects instead of replacing them");
-
-  prop = RNA_def_float_rotation(ot->srna,
-                                "angle",
-                                0,
-                                NULL,
-                                DEG2RADF(0.0f),
-                                DEG2RADF(180.0f),
-                                "Threshold Angle",
-                                "Threshold to determine ends of the strokes",
-                                DEG2RADF(0.0f),
-                                DEG2RADF(180.0f));
-  RNA_def_property_float_default(prop, DEG2RADF(70.0f));
-
-  RNA_def_int(ot->srna, "thickness", 5, 1, 100, "Thickness", "", 1, 100);
-  RNA_def_boolean(ot->srna, "seams", 0, "Only Seam Edges", "Convert only seam edges");
-  RNA_def_boolean(ot->srna, "faces", 1, "Export Faces", "Export faces as filled strokes");
-  RNA_def_float_distance(ot->srna,
-                         "offset",
-                         0.01f,
-                         0.0,
-                         OBJECT_ADD_SIZE_MAXF,
-                         "Stroke Offset",
-                         "Offset strokes from fill",
-                         0.0,
-                         100.00);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Duplicate Object Operator
- * \{ */
-
-/*
- * dupflag: a flag made from constants declared in DNA_userdef_types.h
- * The flag tells adduplicate() whether to copy data linked to the object,
- * or to reference the existing data.
- * U.dupflag for default operations or you can construct a flag as python does
- * if the dupflag is 0 then no data will be copied (linked duplicate). */
-
-/* used below, assumes id.new is correct */
-/* leaves selection of base/object unaltered */
-/* Does set ID->newid pointers. */
-static Base *object_add_duplicate_internal(Main *bmain,
-                                           Scene *scene,
-                                           ViewLayer *view_layer,
-                                           Object *ob,
-                                           const eDupli_ID_Flags dupflag,
-                                           const eLibIDDuplicateFlags duplicate_options)
-{
-  Base *base, *basen = NULL;
-  Object *obn;
-
-  if (ob->mode & OB_MODE_POSE) {
-    /* nothing? */
-  }
-  else {
-    obn = ID_NEW_SET(ob, BKE_object_duplicate(bmain, ob, dupflag, duplicate_options));
-    DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
-
-    base = BKE_view_layer_base_find(view_layer, ob);
-    if ((base != NULL) && (base->flag & BASE_VISIBLE_DEPSGRAPH)) {
-      BKE_collection_object_add_from(bmain, scene, ob, obn);
-    }
-    else {
-      LayerCollection *layer_collection = BKE_layer_collection_get_active(view_layer);
-      BKE_collection_object_add(bmain, layer_collection->collection, obn);
-    }
-
-    basen = BKE_view_layer_base_find(view_layer, obn);
-    if (base != NULL) {
-      basen->local_view_bits = base->local_view_bits;
-    }
-
-    /* 1) duplis should end up in same collection as the original
-     * 2) Rigid Body sim participants MUST always be part of a collection...
-     */
-    /* XXX: is 2) really a good measure here? */
-    if (ob->rigidbody_object || ob->rigidbody_constraint) {
-      Collection *collection;
-      for (collection = bmain->collections.first; collection; collection = collection->id.next) {
-        if (BKE_collection_has_object(collection, ob)) {
-          BKE_collection_object_add(bmain, collection, obn);
-        }
-      }
-    }
-  }
-  return basen;
-}
-
-Base *ED_object_add_duplicate(
-    Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base, const eDupli_ID_Flags dupflag)
-{
-  Base *basen;
-  Object *ob;
-
-  basen = object_add_duplicate_internal(bmain,
-                                        scene,
-                                        view_layer,
-                                        base->object,
-                                        dupflag,
-                                        LIB_ID_DUPLICATE_IS_SUBPROCESS |
-                                            LIB_ID_DUPLICATE_IS_ROOT_ID);
-  if (basen == NULL) {
-    return NULL;
-  }
-
-  ob = basen->object;
-
-  /* Link own references to the newly duplicated data T26816.
-   * Note that this function can be called from edit-mode code, in which case we may have to
-   * enforce remapping obdata (by default this is forbidden in edit mode). */
-  const int remap_flag = BKE_object_is_in_editmode(ob) ? ID_REMAP_FORCE_OBDATA_IN_EDITMODE : 0;
-  BKE_libblock_relink_to_newid(bmain, &ob->id, remap_flag);
-
-  /* DAG_relations_tag_update(bmain); */ /* caller must do */
-
-  if (ob->data != NULL) {
-    DEG_id_tag_update_ex(bmain, (ID *)ob->data, ID_RECALC_EDITORS);
-  }
-
-  BKE_main_id_newptr_and_tag_clear(bmain);
-
-  return basen;
-}
-
-/* contextual operator dupli */
-static int duplicate_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  const bool linked = RNA_boolean_get(op->ptr, "linked");
-  const eDupli_ID_Flags dupflag = (linked) ? 0 : (eDupli_ID_Flags)U.dupflag;
-
-  /* We need to handle that here ourselves, because we may duplicate several objects, in which case
-   * we also want to remap pointers between those... */
-  BKE_main_id_newptr_and_tag_clear(bmain);
-
-  CTX_DATA_BEGIN (C, Base *, base, selected_bases) {
-    Base *basen = object_add_duplicate_internal(bmain,
-                                                scene,
-                                                view_layer,
-                                                base->object,
-                                                dupflag,
-                                                LIB_ID_DUPLICATE_IS_SUBPROCESS |
-                                                    LIB_ID_DUPLICATE_IS_ROOT_ID);
-
-    /* note that this is safe to do with this context iterator,
-     * the list is made in advance */
-    ED_object_base_select(base, BA_DESELECT);
-    ED_object_base_select(basen, BA_SELECT);
-
-    if (basen == NULL) {
-      continue;
-    }
-
-    /* new object becomes active */
-    if (BASACT(view_layer) == base) {
-      ED_object_base_activate(C, basen);
-    }
-
-    if (basen->object->data) {
-      DEG_id_tag_update(basen->object->data, 0);
-    }
-  }
-  CTX_DATA_END;
-
-  /* Note that this will also clear newid pointers and tags. */
-  copy_object_set_idnew(C);
-
-  ED_outliner_select_sync_from_object_tag(C);
-
-  DEG_relations_tag_update(bmain);
-  DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT);
-
-  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
-  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_duplicate(wmOperatorType *ot)
-{
-  PropertyRNA *prop;
-
-  /* identifiers */
-  ot->name = "Duplicate Objects";
-  ot->description = "Duplicate selected objects";
-  ot->idname = "OBJECT_OT_duplicate";
-
-  /* api callbacks */
-  ot->exec = duplicate_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  /* to give to transform */
-  prop = RNA_def_boolean(ot->srna,
-                         "linked",
-                         0,
-                         "Linked",
-                         "Duplicate object but not object data, linking to the original data");
-  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
-
-  prop = RNA_def_enum(
-      ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", "");
-  RNA_def_property_flag(prop, PROP_HIDDEN);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Add Named Object Operator
- *
- * Use for drag & drop.
- * \{ */
-
-static int object_add_named_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  Base *basen;
-  Object *ob;
-  const bool linked = RNA_boolean_get(op->ptr, "linked");
-  const eDupli_ID_Flags dupflag = (linked) ? 0 : (eDupli_ID_Flags)U.dupflag;
-  char name[MAX_ID_NAME - 2];
-
-  /* find object, create fake base */
-  RNA_string_get(op->ptr, "name", name);
-  ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name);
-
-  if (ob == NULL) {
-    BKE_report(op->reports, RPT_ERROR, "Object not found");
-    return OPERATOR_CANCELLED;
-  }
-
-  /* prepare dupli */
-  basen = object_add_duplicate_internal(
-      bmain,
-      scene,
-      view_layer,
-      ob,
-      dupflag,
-      /* Sub-process flag because the new-ID remapping (#BKE_libblock_relink_to_newid()) in this
-       * function will only work if the object is already linked in the view layer, which is not
-       * the case here. So we have to do the new-ID relinking ourselves
-       * (#copy_object_set_idnew()).
-       */
-      LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID);
-
-  if (basen == NULL) {
-    BKE_report(op->reports, RPT_ERROR, "Object could not be duplicated");
-    return OPERATOR_CANCELLED;
-  }
-
-  basen->object->visibility_flag &= ~OB_HIDE_VIEWPORT;
-  /* Do immediately, as #copy_object_set_idnew() below operates on visible objects. */
-  BKE_base_eval_flags(basen);
-
-  /* object_add_duplicate_internal() doesn't deselect other objects, unlike object_add_common() or
-   * BKE_view_layer_base_deselect_all(). */
-  ED_object_base_deselect_all(view_layer, NULL, SEL_DESELECT);
-  ED_object_base_select(basen, BA_SELECT);
-  ED_object_base_activate(C, basen);
-
-  copy_object_set_idnew(C);
-
-  /* TODO(sergey): Only update relations for the current scene. */
-  DEG_relations_tag_update(bmain);
-
-  DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
-  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
-  WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
-  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
-  ED_outliner_select_sync_from_object_tag(C);
-
-  PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
-  if (RNA_property_is_set(op->ptr, prop_matrix)) {
-    Object *ob_add = basen->object;
-    RNA_property_float_get_array(op->ptr, prop_matrix, &ob_add->obmat[0][0]);
-    BKE_object_apply_mat4(ob_add, ob_add->obmat, true, true);
-
-    DEG_id_tag_update(&ob_add->id, ID_RECALC_TRANSFORM);
-  }
-  else {
-    int mval[2];
-    if (object_add_drop_xy_get(C, op, &mval)) {
-      ED_object_location_from_view(C, basen->object->loc);
-      ED_view3d_cursor3d_position(C, mval, false, basen->object->loc);
-    }
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_add_named(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Add Object";
-  ot->description = "Add named object";
-  ot->idname = "OBJECT_OT_add_named";
-
-  /* api callbacks */
-  ot->invoke = object_add_drop_xy_generic_invoke;
-  ot->exec = object_add_named_exec;
-  ot->poll = ED_operator_objectmode_poll_msg;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  PropertyRNA *prop;
-  RNA_def_boolean(ot->srna,
-                  "linked",
-                  false,
-                  "Linked",
-                  "Duplicate object but not object data, linking to the original data");
-
-  RNA_def_string(ot->srna, "name", NULL, MAX_ID_NAME - 2, "Name", "Object name to add");
-
-  prop = RNA_def_float_matrix(
-      ot->srna, "matrix", 4, 4, NULL, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-
-  object_add_drop_xy_props(ot);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Transform Object to Mouse Operator
- * \{ */
-
-/**
- * Alternate behavior for dropping an asset that positions the appended object(s).
- */
-static int object_transform_to_mouse_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  ViewLayer *view_layer = CTX_data_view_layer(C);
-  Object *ob;
-
-  if (RNA_struct_property_is_set(op->ptr, "name")) {
-    char name[MAX_ID_NAME - 2];
-    RNA_string_get(op->ptr, "name", name);
-    ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name);
-  }
-  else {
-    ob = OBACT(view_layer);
-  }
-
-  if (ob == NULL) {
-    BKE_report(op->reports, RPT_ERROR, "Object not found");
-    return OPERATOR_CANCELLED;
-  }
-
-  /* Don't transform a linked object. There's just nothing to do here in this case, so return
-   * #OPERATOR_FINISHED. */
-  if (ID_IS_LINKED(ob)) {
-    return OPERATOR_FINISHED;
-  }
-
-  /* Ensure the locations are updated so snap reads the evaluated active location. */
-  CTX_data_ensure_evaluated_depsgraph(C);
-
-  PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
-  if (RNA_property_is_set(op->ptr, prop_matrix)) {
-    uint objects_len;
-    Object **objects = BKE_view_layer_array_selected_objects(view_layer, NULL, &objects_len, {0});
-
-    float matrix[4][4];
-    RNA_property_float_get_array(op->ptr, prop_matrix, &matrix[0][0]);
-
-    float mat_src_unit[4][4];
-    float mat_dst_unit[4][4];
-    float final_delta[4][4];
-
-    normalize_m4_m4(mat_src_unit, ob->obmat);
-    normalize_m4_m4(mat_dst_unit, matrix);
-    invert_m4(mat_src_unit);
-    mul_m4_m4m4(final_delta, mat_dst_unit, mat_src_unit);
-
-    ED_object_xform_array_m4(objects, objects_len, final_delta);
-
-    MEM_freeN(objects);
-  }
-  else {
-    int mval[2];
-    if (object_add_drop_xy_get(C, op, &mval)) {
-      float cursor[3];
-      ED_object_location_from_view(C, cursor);
-      ED_view3d_cursor3d_position(C, mval, false, cursor);
-
-      /* Use the active objects location since this is the ID which the user selected to drop.
-       *
-       * This transforms all selected objects, so that dropping a single object which links in
-       * other objects will have their relative transformation preserved.
-       * For example a child/parent relationship or other objects used with a boolean modifier.
-       *
-       * The caller is responsible for ensuring the selection state gives useful results.
-       * Link/append does this using #FILE_AUTOSELECT. */
-      ED_view3d_snap_selected_to_location(C, cursor, V3D_AROUND_ACTIVE);
-    }
-  }
-
-  return OPERATOR_FINISHED;
-}
-
-void OBJECT_OT_transform_to_mouse(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Place Object Under Mouse";
-  ot->description = "Snap selected item(s) to the mouse location";
-  ot->idname = "OBJECT_OT_transform_to_mouse";
-
-  /* api callbacks */
-  ot->invoke = object_add_drop_xy_generic_invoke;
-  ot->exec = object_transform_to_mouse_exec;
-  ot->poll = ED_operator_objectmode;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
-  PropertyRNA *prop;
-  RNA_def_string(ot->srna,
-                 "name",
-                 NULL,
-                 MAX_ID_NAME - 2,
-                 "Name",
-                 "Object name to place (when unset use the active object)");
-
-  prop = RNA_def_float_matrix(
-      ot->srna, "matrix", 4, 4, NULL, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
-  RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
-
-  object_add_drop_xy_props(ot);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Join Object Operator
- * \{ */
-
-static bool object_join_poll(bContext *C)
-{
-  Object *ob = CTX_data_active_object(C);
-
-  if (ob == NULL || ob->data == NULL || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob) ||
-      ID_IS_OVERRIDE_LIBRARY(ob->data)) {
-    return false;
-  }
-
-  if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_ARMATURE, OB_GPENCIL)) {
-    return ED_operator_screenactive(C);
-  }
-  return false;
-}
-
-static int object_join_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Object *ob = CTX_data_active_object(C);
-
-  if (ob->mode & OB_MODE_EDIT) {
-    BKE_report(op->reports, RPT_ERROR, "This data does not support joining in edit mode");
-    return OPERATOR_CANCELLED;
-  }
-  if (BKE_object_obdata_is_libdata(ob)) {
-    BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
-    return OPERATOR_CANCELLED;
-  }
-  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
-    BKE_reportf(op->reports,
-                RPT_WARNING,
-                "Cannot edit object '%s' as it is used by override collections",
-                ob->id.name + 2);
-    return OPERATOR_CANCELLED;
-  }
-
-  if (ob->type == OB_GPENCIL) {
-    bGPdata *gpd = (bGPdata *)ob->data;
-    if ((!gpd) || GPENCIL_ANY_MODE(gpd)) {
-      BKE_report(op->reports, RPT_ERROR, "This data does not support joining in this mode");
-      return OPERATOR_CANCELLED;
-    }
-  }
-
-  int ret = OPERATOR_CANCELLED;
-  if (ob->type == OB_MESH) {
-    ret = ED_mesh_join_objects_exec(C, op);
-  }
-  else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) {
-    ret = ED_curve_join_objects_exec(C, op);
-  }
-  else if (ob->type == OB_ARMATURE) {
-    ret = ED_armature_join_objects_exec(C, op);
-  }
-  else if (ob->type == OB_GPENCIL) {
-    ret = ED_gpencil_join_objects_exec(C, op);
-  }
-
-  if (ret & OPERATOR_FINISHED) {
-    /* Even though internally failure to invert is accounted for with a fallback,
-     * show a warning since the result may not be what the user expects. See T80077.
-     *
-     * Failure to invert the matrix is typically caused by zero scaled axes
-     * (which can be caused by constraints, even if the input scale isn't zero).
-     *
-     * Internally the join functions use #invert_m4_m4_safe_ortho which creates
-     * an inevitable matrix from one that has one or more degenerate axes.
-     *
-     * In most cases we don't worry about special handling for non-inevitable matrices however for
-     * joining objects there may be flat 2D objects where it's not obvious the scale is zero.
-     * In this case, using #invert_m4_m4_safe_ortho works as well as we can expect,
-     * joining the contents, flattening on the axis that's zero scaled.
-     * If the zero scale is removed, the data on this axis remains un-scaled
-     * (something that wouldn't work for #invert_m4_m4_safe). */
-    float imat_test[4][4];
-    if (!invert_m4_m4(imat_test, ob->obmat)) {
-      BKE_report(op->reports,
-                 RPT_WARNING,
-                 "Active object final transform has one or more zero scaled axes");
-    }
-  }
-
-  return ret;
-}
-
-void OBJECT_OT_join(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Join";
-  ot->description = "Join selected objects into active object";
-  ot->idname = "OBJECT_OT_join";
-
-  /* api callbacks */
-  ot->exec = object_join_exec;
-  ot->poll = object_join_poll;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Join as Shape Key Operator
- * \{ */
-
-static bool join_shapes_poll(bContext *C)
-{
-  Object *ob = CTX_data_active_object(C);
-
-  if (ob == NULL || ob->data == NULL || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob) ||
-      ID_IS_OVERRIDE_LIBRARY(ob->data)) {
-    return false;
-  }
-
-  /* only meshes supported at the moment */
-  if (ob->type == OB_MESH) {
-    return ED_operator_screenactive(C);
-  }
-  return false;
-}
-
-static int join_shapes_exec(bContext *C, wmOperator *op)
-{
-  Main *bmain = CTX_data_main(C);
-  Object *ob = CTX_data_active_object(C);
-
-  if (ob->mode & OB_MODE_EDIT) {
-    BKE_report(op->reports, RPT_ERROR, "This data does not support joining in edit mode");
-    return OPERATOR_CANCELLED;
-  }
-  if (BKE_object_obdata_is_libdata(ob)) {
-    BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
-    return OPERATOR_CANCELLED;
-  }
-  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
-    BKE_reportf(op->reports,
-                RPT_WARNING,
-                "Cannot edit object '%s' as it is used by override collections",
-                ob->id.name + 2);
-    return OPERATOR_CANCELLED;
-  }
-
-  if (ob->type == OB_MESH) {
-    return ED_mesh_shapes_join_objects_exec(C, op);
-  }
-
-  return OPERATOR_CANCELLED;
-}
-
-void OBJECT_OT_join_shapes(wmOperatorType *ot)
-{
-  /* identifiers */
-  ot->name = "Join as Shapes";
-  ot->description = "Copy the current resulting shape of another selected object to this one";
-  ot->idname = "OBJECT_OT_join_shapes";
-
-  /* api callbacks */
-  ot->exec = join_shapes_exec;
-  ot->poll = join_shapes_poll;
-
-  /* flags */
-  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-}
-
-/** \} */
diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc
new file mode 100644
index 00000000000..ef11337254c
--- /dev/null
+++ b/source/blender/editors/object/object_add.cc
@@ -0,0 +1,3885 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
+
+/** \file
+ * \ingroup edobj
+ */
+
+#include 
+#include 
+#include 
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_anim_types.h"
+#include "DNA_camera_types.h"
+#include "DNA_collection_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_key_types.h"
+#include "DNA_light_types.h"
+#include "DNA_lightprobe_types.h"
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meta_types.h"
+#include "DNA_object_fluidsim_types.h"
+#include "DNA_object_force_types.h"
+#include "DNA_object_types.h"
+#include "DNA_pointcloud_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_vfont_types.h"
+
+#include "BLI_ghash.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_action.h"
+#include "BKE_anim_data.h"
+#include "BKE_armature.h"
+#include "BKE_camera.h"
+#include "BKE_collection.h"
+#include "BKE_constraint.h"
+#include "BKE_context.h"
+#include "BKE_curve.h"
+#include "BKE_curves.h"
+#include "BKE_displist.h"
+#include "BKE_duplilist.h"
+#include "BKE_effect.h"
+#include "BKE_geometry_set.h"
+#include "BKE_gpencil_curve.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_modifier.h"
+#include "BKE_key.h"
+#include "BKE_lattice.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_light.h"
+#include "BKE_lightprobe.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_mball.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_nla.h"
+#include "BKE_object.h"
+#include "BKE_particle.h"
+#include "BKE_pointcloud.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_speaker.h"
+#include "BKE_vfont.h"
+#include "BKE_volume.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+#include "DEG_depsgraph_query.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "UI_interface.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_armature.h"
+#include "ED_curve.h"
+#include "ED_gpencil.h"
+#include "ED_mball.h"
+#include "ED_mesh.h"
+#include "ED_node.h"
+#include "ED_object.h"
+#include "ED_outliner.h"
+#include "ED_physics.h"
+#include "ED_render.h"
+#include "ED_screen.h"
+#include "ED_select_utils.h"
+#include "ED_transform.h"
+#include "ED_view3d.h"
+
+#include "UI_resources.h"
+
+#include "object_intern.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Local Enum Declarations
+ * \{ */
+
+/* This is an exact copy of the define in `rna_light.c`
+ * kept here because of linking order.
+ * Icons are only defined here */
+const EnumPropertyItem rna_enum_light_type_items[] = {
+    {LA_LOCAL, "POINT", ICON_LIGHT_POINT, "Point", "Omnidirectional point light source"},
+    {LA_SUN, "SUN", ICON_LIGHT_SUN, "Sun", "Constant direction parallel ray light source"},
+    {LA_SPOT, "SPOT", ICON_LIGHT_SPOT, "Spot", "Directional cone light source"},
+    {LA_AREA, "AREA", ICON_LIGHT_AREA, "Area", "Directional area light source"},
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+/* copy from rna_object_force.c */
+static const EnumPropertyItem field_type_items[] = {
+    {PFIELD_FORCE, "FORCE", ICON_FORCE_FORCE, "Force", ""},
+    {PFIELD_WIND, "WIND", ICON_FORCE_WIND, "Wind", ""},
+    {PFIELD_VORTEX, "VORTEX", ICON_FORCE_VORTEX, "Vortex", ""},
+    {PFIELD_MAGNET, "MAGNET", ICON_FORCE_MAGNETIC, "Magnetic", ""},
+    {PFIELD_HARMONIC, "HARMONIC", ICON_FORCE_HARMONIC, "Harmonic", ""},
+    {PFIELD_CHARGE, "CHARGE", ICON_FORCE_CHARGE, "Charge", ""},
+    {PFIELD_LENNARDJ, "LENNARDJ", ICON_FORCE_LENNARDJONES, "Lennard-Jones", ""},
+    {PFIELD_TEXTURE, "TEXTURE", ICON_FORCE_TEXTURE, "Texture", ""},
+    {PFIELD_GUIDE, "GUIDE", ICON_FORCE_CURVE, "Curve Guide", ""},
+    {PFIELD_BOID, "BOID", ICON_FORCE_BOID, "Boid", ""},
+    {PFIELD_TURBULENCE, "TURBULENCE", ICON_FORCE_TURBULENCE, "Turbulence", ""},
+    {PFIELD_DRAG, "DRAG", ICON_FORCE_DRAG, "Drag", ""},
+    {PFIELD_FLUIDFLOW, "FLUID", ICON_FORCE_FLUIDFLOW, "Fluid Flow", ""},
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+static EnumPropertyItem lightprobe_type_items[] = {
+    {LIGHTPROBE_TYPE_CUBE,
+     "CUBEMAP",
+     ICON_LIGHTPROBE_CUBEMAP,
+     "Reflection Cubemap",
+     "Reflection probe with spherical or cubic attenuation"},
+    {LIGHTPROBE_TYPE_PLANAR,
+     "PLANAR",
+     ICON_LIGHTPROBE_PLANAR,
+     "Reflection Plane",
+     "Planar reflection probe"},
+    {LIGHTPROBE_TYPE_GRID,
+     "GRID",
+     ICON_LIGHTPROBE_GRID,
+     "Irradiance Volume",
+     "Irradiance probe to capture diffuse indirect lighting"},
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+enum {
+  ALIGN_WORLD = 0,
+  ALIGN_VIEW,
+  ALIGN_CURSOR,
+};
+
+static const EnumPropertyItem align_options[] = {
+    {ALIGN_WORLD, "WORLD", 0, "World", "Align the new object to the world"},
+    {ALIGN_VIEW, "VIEW", 0, "View", "Align the new object to the view"},
+    {ALIGN_CURSOR, "CURSOR", 0, "3D Cursor", "Use the 3D cursor orientation for the new object"},
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Local Helpers
+ * \{ */
+
+/**
+ * Operator properties for creating an object under a screen space (2D) coordinate.
+ * Used for object dropping like behavior (drag object and drop into 3D View).
+ */
+static void object_add_drop_xy_props(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  prop = RNA_def_int(ot->srna,
+                     "drop_x",
+                     0,
+                     INT_MIN,
+                     INT_MAX,
+                     "Drop X",
+                     "X-coordinate (screen space) to place the new object under",
+                     INT_MIN,
+                     INT_MAX);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  prop = RNA_def_int(ot->srna,
+                     "drop_y",
+                     0,
+                     INT_MIN,
+                     INT_MAX,
+                     "Drop Y",
+                     "Y-coordinate (screen space) to place the new object under",
+                     INT_MIN,
+                     INT_MAX);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+}
+
+static bool object_add_drop_xy_is_set(const wmOperator *op)
+{
+  return RNA_struct_property_is_set(op->ptr, "drop_x") &&
+         RNA_struct_property_is_set(op->ptr, "drop_y");
+}
+
+/**
+ * Query the currently set X- and Y-coordinate to position the new object under.
+ * \param r_mval: Returned pointer to the coordinate in region-space.
+ */
+static bool object_add_drop_xy_get(bContext *C, wmOperator *op, int (*r_mval)[2])
+{
+  if (!object_add_drop_xy_is_set(op)) {
+    (*r_mval)[0] = 0.0f;
+    (*r_mval)[1] = 0.0f;
+    return false;
+  }
+
+  const ARegion *region = CTX_wm_region(C);
+  (*r_mval)[0] = RNA_int_get(op->ptr, "drop_x") - region->winrct.xmin;
+  (*r_mval)[1] = RNA_int_get(op->ptr, "drop_y") - region->winrct.ymin;
+
+  return true;
+}
+
+/**
+ * Set the drop coordinate to the mouse position (if not already set) and call the operator's
+ * `exec()` callback.
+ */
+static int object_add_drop_xy_generic_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  if (!object_add_drop_xy_is_set(op)) {
+    RNA_int_set(op->ptr, "drop_x", event->xy[0]);
+    RNA_int_set(op->ptr, "drop_y", event->xy[1]);
+  }
+  return op->type->exec(C, op);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public Add Object API
+ * \{ */
+
+void ED_object_location_from_view(bContext *C, float loc[3])
+{
+  const Scene *scene = CTX_data_scene(C);
+  copy_v3_v3(loc, scene->cursor.location);
+}
+
+void ED_object_rotation_from_quat(float rot[3], const float viewquat[4], const char align_axis)
+{
+  BLI_assert(align_axis >= 'X' && align_axis <= 'Z');
+
+  switch (align_axis) {
+    case 'X': {
+      /* Same as 'rv3d->viewinv[1]' */
+      const float axis_y[4] = {0.0f, 1.0f, 0.0f};
+      float quat_y[4], quat[4];
+      axis_angle_to_quat(quat_y, axis_y, M_PI_2);
+      mul_qt_qtqt(quat, viewquat, quat_y);
+      quat_to_eul(rot, quat);
+      break;
+    }
+    case 'Y': {
+      quat_to_eul(rot, viewquat);
+      rot[0] -= (float)M_PI_2;
+      break;
+    }
+    case 'Z': {
+      quat_to_eul(rot, viewquat);
+      break;
+    }
+  }
+}
+
+void ED_object_rotation_from_view(bContext *C, float rot[3], const char align_axis)
+{
+  RegionView3D *rv3d = CTX_wm_region_view3d(C);
+  BLI_assert(align_axis >= 'X' && align_axis <= 'Z');
+  if (rv3d) {
+    float viewquat[4];
+    copy_qt_qt(viewquat, rv3d->viewquat);
+    viewquat[0] *= -1.0f;
+    ED_object_rotation_from_quat(rot, viewquat, align_axis);
+  }
+  else {
+    zero_v3(rot);
+  }
+}
+
+void ED_object_base_init_transform_on_add(Object *object, const float loc[3], const float rot[3])
+{
+  if (loc) {
+    copy_v3_v3(object->loc, loc);
+  }
+
+  if (rot) {
+    copy_v3_v3(object->rot, rot);
+  }
+
+  BKE_object_to_mat4(object, object->obmat);
+}
+
+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);
+  float mat[3][3], rmat[3][3], cmat[3][3], imat[3][3];
+
+  unit_m4(r_primmat);
+
+  eul_to_mat3(rmat, rot);
+  invert_m3(rmat);
+
+  /* inverse transform for initial rotation and object */
+  copy_m3_m4(mat, obedit->obmat);
+  mul_m3_m3m3(cmat, rmat, mat);
+  invert_m3_m3(imat, cmat);
+  copy_m4_m3(r_primmat, imat);
+
+  /* center */
+  copy_v3_v3(r_primmat[3], loc);
+  sub_v3_v3v3(r_primmat[3], r_primmat[3], obedit->obmat[3]);
+  invert_m3_m3(imat, mat);
+  mul_m3_v3(imat, r_primmat[3]);
+
+  if (scale != nullptr) {
+    rescale_m4(r_primmat, scale);
+  }
+
+  {
+    const float dia = v3d ? ED_view3d_grid_scale(scene, v3d, nullptr) :
+                            ED_scene_grid_scale(scene, nullptr);
+    return dia;
+  }
+
+  /* return 1.0f; */
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Object Operator
+ * \{ */
+
+static void view_align_update(struct Main *UNUSED(main),
+                              struct Scene *UNUSED(scene),
+                              struct PointerRNA *ptr)
+{
+  RNA_struct_idprops_unset(ptr, "rotation");
+}
+
+void ED_object_add_unit_props_size(wmOperatorType *ot)
+{
+  RNA_def_float_distance(
+      ot->srna, "size", 2.0f, 0.0, OBJECT_ADD_SIZE_MAXF, "Size", "", 0.001, 100.00);
+}
+
+void ED_object_add_unit_props_radius_ex(wmOperatorType *ot, float default_value)
+{
+  RNA_def_float_distance(
+      ot->srna, "radius", default_value, 0.0, OBJECT_ADD_SIZE_MAXF, "Radius", "", 0.001, 100.00);
+}
+
+void ED_object_add_unit_props_radius(wmOperatorType *ot)
+{
+  ED_object_add_unit_props_radius_ex(ot, 1.0f);
+}
+
+void ED_object_add_generic_props(wmOperatorType *ot, bool do_editmode)
+{
+  PropertyRNA *prop;
+
+  if (do_editmode) {
+    prop = RNA_def_boolean(ot->srna,
+                           "enter_editmode",
+                           false,
+                           "Enter Edit Mode",
+                           "Enter edit mode when adding this object");
+    RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  }
+  /* NOTE: this property gets hidden for add-camera operator. */
+  prop = RNA_def_enum(
+      ot->srna, "align", align_options, ALIGN_WORLD, "Align", "The alignment of the new object");
+  RNA_def_property_update_runtime(prop, (void *)view_align_update);
+
+  prop = RNA_def_float_vector_xyz(ot->srna,
+                                  "location",
+                                  3,
+                                  nullptr,
+                                  -OBJECT_ADD_SIZE_MAXF,
+                                  OBJECT_ADD_SIZE_MAXF,
+                                  "Location",
+                                  "Location for the newly added object",
+                                  -1000.0f,
+                                  1000.0f);
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+  prop = RNA_def_float_rotation(ot->srna,
+                                "rotation",
+                                3,
+                                nullptr,
+                                -OBJECT_ADD_SIZE_MAXF,
+                                OBJECT_ADD_SIZE_MAXF,
+                                "Rotation",
+                                "Rotation for the newly added object",
+                                DEG2RADF(-360.0f),
+                                DEG2RADF(360.0f));
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+  prop = RNA_def_float_vector_xyz(ot->srna,
+                                  "scale",
+                                  3,
+                                  nullptr,
+                                  -OBJECT_ADD_SIZE_MAXF,
+                                  OBJECT_ADD_SIZE_MAXF,
+                                  "Scale",
+                                  "Scale for the newly added object",
+                                  -1000.0f,
+                                  1000.0f);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+}
+
+void ED_object_add_mesh_props(wmOperatorType *ot)
+{
+  RNA_def_boolean(ot->srna, "calc_uvs", true, "Generate UVs", "Generate a default UV map");
+}
+
+bool ED_object_add_generic_get_opts(bContext *C,
+                                    wmOperator *op,
+                                    const char view_align_axis,
+                                    float r_loc[3],
+                                    float r_rot[3],
+                                    float r_scale[3],
+                                    bool *r_enter_editmode,
+                                    ushort *r_local_view_bits,
+                                    bool *r_is_view_aligned)
+{
+  /* Edit Mode! (optional) */
+  {
+    bool _enter_editmode;
+    if (!r_enter_editmode) {
+      r_enter_editmode = &_enter_editmode;
+    }
+    /* Only to ensure the value is _always_ set.
+     * Typically the property will exist when the argument is non-nullptr. */
+    *r_enter_editmode = false;
+
+    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "enter_editmode");
+    if (prop != nullptr) {
+      if (RNA_property_is_set(op->ptr, prop) && r_enter_editmode) {
+        *r_enter_editmode = RNA_property_boolean_get(op->ptr, prop);
+      }
+      else {
+        *r_enter_editmode = (U.flag & USER_ADD_EDITMODE) != 0;
+        RNA_property_boolean_set(op->ptr, prop, *r_enter_editmode);
+      }
+    }
+  }
+
+  if (r_local_view_bits) {
+    View3D *v3d = CTX_wm_view3d(C);
+    *r_local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
+  }
+
+  /* Location! */
+  {
+    float _loc[3];
+    if (!r_loc) {
+      r_loc = _loc;
+    }
+
+    if (RNA_struct_property_is_set(op->ptr, "location")) {
+      RNA_float_get_array(op->ptr, "location", r_loc);
+    }
+    else {
+      ED_object_location_from_view(C, r_loc);
+      RNA_float_set_array(op->ptr, "location", r_loc);
+    }
+  }
+
+  /* Rotation! */
+  {
+    bool _is_view_aligned;
+    float _rot[3];
+    if (!r_is_view_aligned) {
+      r_is_view_aligned = &_is_view_aligned;
+    }
+    if (!r_rot) {
+      r_rot = _rot;
+    }
+
+    if (RNA_struct_property_is_set(op->ptr, "rotation")) {
+      /* If rotation is set, always use it. Alignment (and corresponding user preference)
+       * can be ignored since this is in world space anyways.
+       * To not confuse (e.g. on redo), don't set it to #ALIGN_WORLD in the op UI though. */
+      *r_is_view_aligned = false;
+      RNA_float_get_array(op->ptr, "rotation", r_rot);
+    }
+    else {
+      int alignment = ALIGN_WORLD;
+      PropertyRNA *prop = RNA_struct_find_property(op->ptr, "align");
+
+      if (RNA_property_is_set(op->ptr, prop)) {
+        /* If alignment is set, always use it. */
+        *r_is_view_aligned = alignment == ALIGN_VIEW;
+        alignment = RNA_property_enum_get(op->ptr, prop);
+      }
+      else {
+        /* If alignment is not set, use User Preferences. */
+        *r_is_view_aligned = (U.flag & USER_ADD_VIEWALIGNED) != 0;
+        if (*r_is_view_aligned) {
+          RNA_property_enum_set(op->ptr, prop, ALIGN_VIEW);
+          alignment = ALIGN_VIEW;
+        }
+        else if ((U.flag & USER_ADD_CURSORALIGNED) != 0) {
+          RNA_property_enum_set(op->ptr, prop, ALIGN_CURSOR);
+          alignment = ALIGN_CURSOR;
+        }
+        else {
+          RNA_property_enum_set(op->ptr, prop, ALIGN_WORLD);
+          alignment = ALIGN_WORLD;
+        }
+      }
+      switch (alignment) {
+        case ALIGN_WORLD:
+          RNA_float_get_array(op->ptr, "rotation", r_rot);
+          break;
+        case ALIGN_VIEW:
+          ED_object_rotation_from_view(C, r_rot, view_align_axis);
+          RNA_float_set_array(op->ptr, "rotation", r_rot);
+          break;
+        case ALIGN_CURSOR: {
+          const Scene *scene = CTX_data_scene(C);
+          float tmat[3][3];
+          BKE_scene_cursor_rot_to_mat3(&scene->cursor, tmat);
+          mat3_normalized_to_eul(r_rot, tmat);
+          RNA_float_set_array(op->ptr, "rotation", r_rot);
+          break;
+        }
+      }
+    }
+  }
+
+  /* Scale! */
+  {
+    float _scale[3];
+    if (!r_scale) {
+      r_scale = _scale;
+    }
+
+    /* For now this is optional, we can make it always use. */
+    copy_v3_fl(r_scale, 1.0f);
+
+    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "scale");
+    if (prop != nullptr) {
+      if (RNA_property_is_set(op->ptr, prop)) {
+        RNA_property_float_get_array(op->ptr, prop, r_scale);
+      }
+      else {
+        copy_v3_fl(r_scale, 1.0f);
+        RNA_property_float_set_array(op->ptr, prop, r_scale);
+      }
+    }
+  }
+
+  return true;
+}
+
+Object *ED_object_add_type_with_obdata(bContext *C,
+                                       const int type,
+                                       const char *name,
+                                       const float loc[3],
+                                       const float rot[3],
+                                       const bool enter_editmode,
+                                       const ushort local_view_bits,
+                                       ID *obdata)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+
+  {
+    Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
+    if (obedit != nullptr) {
+      ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
+    }
+  }
+
+  /* deselects all, sets active object */
+  Object *ob;
+  if (obdata != nullptr) {
+    BLI_assert(type == BKE_object_obdata_to_type(obdata));
+    ob = BKE_object_add_for_data(bmain, view_layer, type, name, obdata, true);
+    const short *materials_len_p = BKE_id_material_len_p(obdata);
+    if (materials_len_p && *materials_len_p > 0) {
+      BKE_object_materials_test(bmain, ob, static_cast(ob->data));
+    }
+  }
+  else {
+    ob = BKE_object_add(bmain, view_layer, type, name);
+  }
+  BASACT(view_layer)->local_view_bits = local_view_bits;
+  /* editor level activate, notifiers */
+  ED_object_base_activate(C, view_layer->basact);
+
+  /* more editor stuff */
+  ED_object_base_init_transform_on_add(ob, loc, rot);
+
+  /* TODO(sergey): This is weird to manually tag objects for update, better to
+   * use DEG_id_tag_update here perhaps.
+   */
+  DEG_id_type_tag(bmain, ID_OB);
+  DEG_relations_tag_update(bmain);
+  if (ob->data != nullptr) {
+    DEG_id_tag_update_ex(bmain, (ID *)ob->data, ID_RECALC_EDITORS);
+  }
+
+  if (enter_editmode) {
+    ED_object_editmode_enter_ex(bmain, scene, ob, 0);
+  }
+
+  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+
+  /* TODO(sergey): Use proper flag for tagging here. */
+  DEG_id_tag_update(&scene->id, 0);
+
+  ED_outliner_select_sync_from_object_tag(C);
+
+  return ob;
+}
+
+Object *ED_object_add_type(bContext *C,
+                           const int type,
+                           const char *name,
+                           const float loc[3],
+                           const float rot[3],
+                           const bool enter_editmode,
+                           const ushort local_view_bits)
+{
+  return ED_object_add_type_with_obdata(
+      C, type, name, loc, rot, enter_editmode, local_view_bits, nullptr);
+}
+
+/* for object add operator */
+static int object_add_exec(bContext *C, wmOperator *op)
+{
+  ushort local_view_bits;
+  bool enter_editmode;
+  float loc[3], rot[3], radius;
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  radius = RNA_float_get(op->ptr, "radius");
+  Object *ob = ED_object_add_type(
+      C, RNA_enum_get(op->ptr, "type"), nullptr, loc, rot, enter_editmode, local_view_bits);
+
+  if (ob->type == OB_LATTICE) {
+    /* lattice is a special case!
+     * we never want to scale the obdata since that is the rest-state */
+    copy_v3_fl(ob->scale, radius);
+  }
+  else {
+    BKE_object_obdata_size_init(ob, radius);
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Object";
+  ot->description = "Add an object to the scene";
+  ot->idname = "OBJECT_OT_add";
+
+  /* api callbacks */
+  ot->exec = object_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ED_object_add_unit_props_radius(ot);
+  PropertyRNA *prop = RNA_def_enum(ot->srna, "type", rna_enum_object_type_items, 0, "Type", "");
+  RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
+
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Probe Operator
+ * \{ */
+
+/* for object add operator */
+static const char *get_lightprobe_defname(int type)
+{
+  switch (type) {
+    case LIGHTPROBE_TYPE_GRID:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "IrradianceVolume");
+    case LIGHTPROBE_TYPE_PLANAR:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionPlane");
+    case LIGHTPROBE_TYPE_CUBE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionCubemap");
+    default:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "LightProbe");
+  }
+}
+
+static int lightprobe_add_exec(bContext *C, wmOperator *op)
+{
+  bool enter_editmode;
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  int type = RNA_enum_get(op->ptr, "type");
+  float radius = RNA_float_get(op->ptr, "radius");
+
+  Object *ob = ED_object_add_type(
+      C, OB_LIGHTPROBE, get_lightprobe_defname(type), loc, rot, false, local_view_bits);
+  copy_v3_fl(ob->scale, radius);
+
+  LightProbe *probe = (LightProbe *)ob->data;
+
+  BKE_lightprobe_type_set(probe, type);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_lightprobe_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Light Probe";
+  ot->description = "Add a light probe object";
+  ot->idname = "OBJECT_OT_lightprobe_add";
+
+  /* api callbacks */
+  ot->exec = lightprobe_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ot->prop = RNA_def_enum(ot->srna, "type", lightprobe_type_items, 0, "Type", "");
+
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Effector Operator
+ * \{ */
+
+/* for object add operator */
+
+static const char *get_effector_defname(ePFieldType type)
+{
+  switch (type) {
+    case PFIELD_FORCE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Force");
+    case PFIELD_VORTEX:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Vortex");
+    case PFIELD_MAGNET:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Magnet");
+    case PFIELD_WIND:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Wind");
+    case PFIELD_GUIDE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "CurveGuide");
+    case PFIELD_TEXTURE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "TextureField");
+    case PFIELD_HARMONIC:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Harmonic");
+    case PFIELD_CHARGE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Charge");
+    case PFIELD_LENNARDJ:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Lennard-Jones");
+    case PFIELD_BOID:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Boid");
+    case PFIELD_TURBULENCE:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Turbulence");
+    case PFIELD_DRAG:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Drag");
+    case PFIELD_FLUIDFLOW:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "FluidField");
+    case PFIELD_NULL:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Field");
+    case NUM_PFIELD_TYPES:
+      break;
+  }
+
+  BLI_assert(false);
+  return CTX_DATA_(BLT_I18NCONTEXT_ID_OBJECT, "Field");
+}
+
+static int effector_add_exec(bContext *C, wmOperator *op)
+{
+  bool enter_editmode;
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  const ePFieldType type = static_cast(RNA_enum_get(op->ptr, "type"));
+  float dia = RNA_float_get(op->ptr, "radius");
+
+  Object *ob;
+  if (type == PFIELD_GUIDE) {
+    Main *bmain = CTX_data_main(C);
+    Scene *scene = CTX_data_scene(C);
+    ob = ED_object_add_type(
+        C, OB_CURVES_LEGACY, get_effector_defname(type), loc, rot, false, local_view_bits);
+
+    Curve *cu = static_cast(ob->data);
+    cu->flag |= CU_PATH | CU_3D;
+    ED_object_editmode_enter_ex(bmain, scene, ob, 0);
+
+    float mat[4][4];
+    ED_object_new_primitive_matrix(C, ob, loc, rot, nullptr, 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));
+    if (!enter_editmode) {
+      ED_object_editmode_exit_ex(bmain, scene, ob, EM_FREEDATA);
+    }
+  }
+  else {
+    ob = ED_object_add_type(
+        C, OB_EMPTY, get_effector_defname(type), loc, rot, false, local_view_bits);
+    BKE_object_obdata_size_init(ob, dia);
+    if (ELEM(type, PFIELD_WIND, PFIELD_VORTEX)) {
+      ob->empty_drawtype = OB_SINGLE_ARROW;
+    }
+  }
+
+  ob->pd = BKE_partdeflect_new(type);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_effector_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Effector";
+  ot->description = "Add an empty object with a physics effector to the scene";
+  ot->idname = "OBJECT_OT_effector_add";
+
+  /* api callbacks */
+  ot->exec = effector_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ot->prop = RNA_def_enum(ot->srna, "type", field_type_items, 0, "Type", "");
+
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Camera Operator
+ * \{ */
+
+static int object_camera_add_exec(bContext *C, wmOperator *op)
+{
+  View3D *v3d = CTX_wm_view3d(C);
+  Scene *scene = CTX_data_scene(C);
+
+  /* force view align for cameras */
+  RNA_enum_set(op->ptr, "align", ALIGN_VIEW);
+
+  ushort local_view_bits;
+  bool enter_editmode;
+  float loc[3], rot[3];
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  Object *ob = ED_object_add_type(C, OB_CAMERA, nullptr, loc, rot, false, local_view_bits);
+
+  if (v3d) {
+    if (v3d->camera == nullptr) {
+      v3d->camera = ob;
+    }
+    if (v3d->scenelock && scene->camera == nullptr) {
+      scene->camera = ob;
+    }
+  }
+
+  Camera *cam = static_cast(ob->data);
+  cam->drawsize = v3d ? ED_view3d_grid_scale(scene, v3d, nullptr) :
+                        ED_scene_grid_scale(scene, nullptr);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_camera_add(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  /* identifiers */
+  ot->name = "Add Camera";
+  ot->description = "Add a camera object to the scene";
+  ot->idname = "OBJECT_OT_camera_add";
+
+  /* api callbacks */
+  ot->exec = object_camera_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  ED_object_add_generic_props(ot, true);
+
+  /* hide this for cameras, default */
+  prop = RNA_struct_type_find_property(ot->srna, "align");
+  RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Metaball Operator
+ * \{ */
+
+static int object_metaball_add_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+
+  ushort local_view_bits;
+  bool enter_editmode;
+  float loc[3], rot[3];
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  bool newob = false;
+  Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
+  if (obedit == nullptr || obedit->type != OB_MBALL) {
+    obedit = ED_object_add_type(C, OB_MBALL, nullptr, loc, rot, true, local_view_bits);
+    newob = true;
+  }
+  else {
+    DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY);
+  }
+
+  float mat[4][4];
+  ED_object_new_primitive_matrix(C, obedit, loc, rot, nullptr, 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. */
+  float dia = RNA_float_get(op->ptr, "radius") / 2;
+
+  ED_mball_add_primitive(C, obedit, newob, mat, dia, RNA_enum_get(op->ptr, "type"));
+
+  /* userdef */
+  if (newob && !enter_editmode) {
+    ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
+  }
+  else {
+    /* Only needed in edit-mode (#ED_object_add_type normally handles this). */
+    WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obedit);
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_metaball_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Metaball";
+  ot->description = "Add an metaball object to the scene";
+  ot->idname = "OBJECT_OT_metaball_add";
+
+  /* api callbacks */
+  ot->invoke = WM_menu_invoke;
+  ot->exec = object_metaball_add_exec;
+  ot->poll = ED_operator_scene_editable;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_metaelem_type_items, 0, "Primitive", "");
+
+  ED_object_add_unit_props_radius_ex(ot, 2.0f);
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Text Operator
+ * \{ */
+
+static int object_add_text_exec(bContext *C, wmOperator *op)
+{
+  Object *obedit = CTX_data_edit_object(C);
+  bool enter_editmode;
+  ushort local_view_bits;
+  float loc[3], rot[3];
+
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  if (obedit && obedit->type == OB_FONT) {
+    return OPERATOR_CANCELLED;
+  }
+
+  obedit = ED_object_add_type(C, OB_FONT, nullptr, loc, rot, enter_editmode, local_view_bits);
+  BKE_object_obdata_size_init(obedit, RNA_float_get(op->ptr, "radius"));
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_text_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Text";
+  ot->description = "Add a text object to the scene";
+  ot->idname = "OBJECT_OT_text_add";
+
+  /* api callbacks */
+  ot->exec = object_add_text_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Armature Operator
+ * \{ */
+
+static int object_armature_add_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
+
+  RegionView3D *rv3d = CTX_wm_region_view3d(C);
+  bool newob = false;
+  bool enter_editmode;
+  ushort local_view_bits;
+  float loc[3], rot[3], dia;
+  bool view_aligned = rv3d && (U.flag & USER_ADD_VIEWALIGNED);
+
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, &enter_editmode, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  if ((obedit == nullptr) || (obedit->type != OB_ARMATURE)) {
+    obedit = ED_object_add_type(C, OB_ARMATURE, nullptr, loc, rot, true, local_view_bits);
+    ED_object_editmode_enter_ex(bmain, scene, obedit, 0);
+    newob = true;
+  }
+  else {
+    DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY);
+  }
+
+  if (obedit == nullptr) {
+    BKE_report(op->reports, RPT_ERROR, "Cannot create editmode armature");
+    return OPERATOR_CANCELLED;
+  }
+
+  dia = RNA_float_get(op->ptr, "radius");
+  ED_armature_ebone_add_primitive(obedit, dia, view_aligned);
+
+  /* userdef */
+  if (newob && !enter_editmode) {
+    ED_object_editmode_exit_ex(bmain, scene, obedit, EM_FREEDATA);
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_armature_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Armature";
+  ot->description = "Add an armature object to the scene";
+  ot->idname = "OBJECT_OT_armature_add";
+
+  /* api callbacks */
+  ot->exec = object_armature_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Empty Operator
+ * \{ */
+
+static int object_empty_add_exec(bContext *C, wmOperator *op)
+{
+  Object *ob;
+  int type = RNA_enum_get(op->ptr, "type");
+  ushort local_view_bits;
+  float loc[3], rot[3];
+
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  ob = ED_object_add_type(C, OB_EMPTY, nullptr, loc, rot, false, local_view_bits);
+
+  BKE_object_empty_draw_type_set(ob, type);
+  BKE_object_obdata_size_init(ob, RNA_float_get(op->ptr, "radius"));
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_empty_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Empty";
+  ot->description = "Add an empty object to the scene";
+  ot->idname = "OBJECT_OT_empty_add";
+
+  /* api callbacks */
+  ot->invoke = WM_menu_invoke;
+  ot->exec = object_empty_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_empty_drawtype_items, 0, "Type", "");
+
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, false);
+}
+
+static int empty_drop_named_image_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  Scene *scene = CTX_data_scene(C);
+
+  Image *ima = nullptr;
+
+  ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM);
+  if (!ima) {
+    return OPERATOR_CANCELLED;
+  }
+  /* handled below */
+  id_us_min(&ima->id);
+
+  Object *ob = nullptr;
+  Object *ob_cursor = ED_view3d_give_object_under_cursor(C, event->mval);
+
+  /* either change empty under cursor or create a new empty */
+  if (ob_cursor && ob_cursor->type == OB_EMPTY) {
+    WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
+    DEG_id_tag_update((ID *)ob_cursor, ID_RECALC_TRANSFORM);
+    ob = ob_cursor;
+  }
+  else {
+    /* add new empty */
+    ushort local_view_bits;
+    float rot[3];
+
+    if (!ED_object_add_generic_get_opts(
+            C, op, 'Z', nullptr, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+      return OPERATOR_CANCELLED;
+    }
+    ob = ED_object_add_type(C, OB_EMPTY, nullptr, nullptr, rot, false, local_view_bits);
+
+    ED_object_location_from_view(C, ob->loc);
+    ED_view3d_cursor3d_position(C, event->mval, false, ob->loc);
+    ED_object_rotation_from_view(C, ob->rot, 'Z');
+    ob->empty_drawsize = 5.0f;
+  }
+
+  BKE_object_empty_draw_type_set(ob, OB_EMPTY_IMAGE);
+
+  id_us_min(static_cast(ob->data));
+  ob->data = ima;
+  id_us_plus(static_cast(ob->data));
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_drop_named_image(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  /* identifiers */
+  ot->name = "Add Empty Image/Drop Image to Empty";
+  ot->description = "Add an empty image type to scene with data";
+  ot->idname = "OBJECT_OT_drop_named_image";
+
+  /* api callbacks */
+  ot->invoke = empty_drop_named_image_invoke;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  prop = RNA_def_string(ot->srna, "filepath", nullptr, FILE_MAX, "Filepath", "Path to image file");
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  RNA_def_boolean(ot->srna,
+                  "relative_path",
+                  true,
+                  "Relative Path",
+                  "Select the file relative to the blend file");
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  prop = RNA_def_string(
+      ot->srna, "name", nullptr, MAX_ID_NAME - 2, "Name", "Image name to assign");
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  ED_object_add_generic_props(ot, false);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Gpencil Operator
+ * \{ */
+
+static bool object_gpencil_add_poll(bContext *C)
+{
+  Scene *scene = CTX_data_scene(C);
+  Object *obact = CTX_data_active_object(C);
+
+  if ((scene == nullptr) || (ID_IS_LINKED(scene))) {
+    return false;
+  }
+
+  if (obact && obact->type == OB_GPENCIL) {
+    if (obact->mode != OB_MODE_OBJECT) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int object_gpencil_add_exec(bContext *C, wmOperator *op)
+{
+  Object *ob = CTX_data_active_object(C), *ob_orig = ob;
+  bGPdata *gpd = (ob && (ob->type == OB_GPENCIL)) ? static_cast(ob->data) : nullptr;
+
+  const int type = RNA_enum_get(op->ptr, "type");
+  const bool use_in_front = RNA_boolean_get(op->ptr, "use_in_front");
+  const bool use_lights = RNA_boolean_get(op->ptr, "use_lights");
+  const int stroke_depth_order = RNA_enum_get(op->ptr, "stroke_depth_order");
+  const float stroke_depth_offset = RNA_float_get(op->ptr, "stroke_depth_offset");
+
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  bool newob = false;
+
+  /* NOTE: We use 'Y' here (not 'Z'), as. */
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Y', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  /* Add new object if not currently editing a GP object. */
+  if ((gpd == nullptr) || (GPENCIL_ANY_MODE(gpd) == false)) {
+    const char *ob_name = nullptr;
+    switch (type) {
+      case GP_EMPTY: {
+        ob_name = "GPencil";
+        break;
+      }
+      case GP_MONKEY: {
+        ob_name = "Suzanne";
+        break;
+      }
+      case GP_STROKE: {
+        ob_name = "Stroke";
+        break;
+      }
+      case GP_LRT_OBJECT:
+      case GP_LRT_SCENE:
+      case GP_LRT_COLLECTION: {
+        ob_name = "Line Art";
+        break;
+      }
+      default: {
+        break;
+      }
+    }
+
+    ob = ED_object_add_type(C, OB_GPENCIL, ob_name, loc, rot, true, local_view_bits);
+    gpd = static_cast(ob->data);
+    newob = true;
+  }
+  else {
+    DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+    WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_ADDED, nullptr);
+  }
+
+  /* create relevant geometry */
+  switch (type) {
+    case GP_EMPTY: {
+      float mat[4][4];
+
+      ED_object_new_primitive_matrix(C, ob, loc, rot, nullptr, 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, 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, scale, mat);
+
+      ED_gpencil_create_monkey(C, ob, mat);
+      break;
+    }
+    case GP_LRT_SCENE:
+    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, scale, mat);
+
+      ED_gpencil_create_lineart(C, ob);
+
+      gpd = static_cast(ob->data);
+
+      /* Add Line Art modifier */
+      LineartGpencilModifierData *md = (LineartGpencilModifierData *)BKE_gpencil_modifier_new(
+          eGpencilModifierType_Lineart);
+      BLI_addtail(&ob->greasepencil_modifiers, md);
+      BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, (GpencilModifierData *)md);
+
+      if (type == GP_LRT_COLLECTION) {
+        md->source_type = LRT_SOURCE_COLLECTION;
+        md->source_collection = CTX_data_collection(C);
+      }
+      else if (type == GP_LRT_OBJECT) {
+        md->source_type = LRT_SOURCE_OBJECT;
+        md->source_object = ob_orig;
+      }
+      else {
+        /* Whole scene. */
+        md->source_type = LRT_SOURCE_SCENE;
+      }
+      /* Only created one layer and one material. */
+      strcpy(md->target_layer, ((bGPDlayer *)gpd->layers.first)->info);
+      md->target_material = BKE_gpencil_material(ob, 1);
+      if (md->target_material) {
+        id_us_plus(&md->target_material->id);
+      }
+
+      if (use_lights) {
+        ob->dtx |= OB_USE_GPENCIL_LIGHTS;
+      }
+      else {
+        ob->dtx &= ~OB_USE_GPENCIL_LIGHTS;
+      }
+
+      /* Stroke object is drawn in front of meshes by default. */
+      if (use_in_front) {
+        ob->dtx |= OB_DRAW_IN_FRONT;
+      }
+      else {
+        if (stroke_depth_order == GP_DRAWMODE_3D) {
+          gpd->draw_mode = GP_DRAWMODE_3D;
+        }
+        md->stroke_depth_offset = stroke_depth_offset;
+      }
+
+      break;
+    }
+    default:
+      BKE_report(op->reports, RPT_WARNING, "Not implemented");
+      break;
+  }
+
+  /* If this is a new object, initialize default stuff (colors, etc.) */
+  if (newob) {
+    /* set default viewport color to black */
+    copy_v3_fl(ob->color, 0.0f);
+
+    ED_gpencil_add_defaults(C, ob);
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+static void object_add_ui(bContext *UNUSED(C), wmOperator *op)
+{
+  uiLayout *layout = op->layout;
+
+  uiLayoutSetPropSep(layout, true);
+
+  uiItemR(layout, op->ptr, "radius", 0, nullptr, ICON_NONE);
+  uiItemR(layout, op->ptr, "align", 0, nullptr, ICON_NONE);
+  uiItemR(layout, op->ptr, "location", 0, nullptr, ICON_NONE);
+  uiItemR(layout, op->ptr, "rotation", 0, nullptr, ICON_NONE);
+  uiItemR(layout, op->ptr, "type", 0, nullptr, ICON_NONE);
+
+  int type = RNA_enum_get(op->ptr, "type");
+  if (ELEM(type, GP_LRT_COLLECTION, GP_LRT_OBJECT, GP_LRT_SCENE)) {
+    uiItemR(layout, op->ptr, "use_lights", 0, nullptr, ICON_NONE);
+    uiItemR(layout, op->ptr, "use_in_front", 0, nullptr, ICON_NONE);
+    bool in_front = RNA_boolean_get(op->ptr, "use_in_front");
+    uiLayout *col = uiLayoutColumn(layout, false);
+    uiLayoutSetActive(col, !in_front);
+    uiItemR(col, op->ptr, "stroke_depth_offset", 0, nullptr, ICON_NONE);
+    uiItemR(col, op->ptr, "stroke_depth_order", 0, nullptr, ICON_NONE);
+  }
+}
+
+static EnumPropertyItem rna_enum_gpencil_add_stroke_depth_order_items[] = {
+    {GP_DRAWMODE_2D,
+     "2D",
+     0,
+     "2D Layers",
+     "Display strokes using grease pencil layers to define order"},
+    {GP_DRAWMODE_3D, "3D", 0, "3D Location", "Display strokes using real 3D position in 3D space"},
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+void OBJECT_OT_gpencil_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Grease Pencil";
+  ot->description = "Add a Grease Pencil object to the scene";
+  ot->idname = "OBJECT_OT_gpencil_add";
+
+  /* api callbacks */
+  ot->invoke = WM_menu_invoke;
+  ot->exec = object_gpencil_add_exec;
+  ot->poll = object_gpencil_add_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* ui */
+  ot->ui = object_add_ui;
+
+  /* properties */
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, false);
+
+  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", "");
+  RNA_def_boolean(ot->srna,
+                  "use_in_front",
+                  true,
+                  "Show In Front",
+                  "Show line art grease pencil in front of everything");
+  RNA_def_float(ot->srna,
+                "stroke_depth_offset",
+                0.05f,
+                0.0f,
+                FLT_MAX,
+                "Stroke Offset",
+                "Stroke offset for the line art modifier",
+                0.0f,
+                0.5f);
+  RNA_def_boolean(
+      ot->srna, "use_lights", false, "Use Lights", "Use lights for this grease pencil object");
+  RNA_def_enum(
+      ot->srna,
+      "stroke_depth_order",
+      rna_enum_gpencil_add_stroke_depth_order_items,
+      GP_DRAWMODE_3D,
+      "Stroke Depth Order",
+      "Defines how the strokes are ordered in 3D space for objects not displayed 'In Front')");
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Light Operator
+ * \{ */
+
+static const char *get_light_defname(int type)
+{
+  switch (type) {
+    case LA_LOCAL:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Point");
+    case LA_SUN:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Sun");
+    case LA_SPOT:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Spot");
+    case LA_AREA:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Area");
+    default:
+      return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Light");
+  }
+}
+
+static int object_light_add_exec(bContext *C, wmOperator *op)
+{
+  Object *ob;
+  Light *la;
+  int type = RNA_enum_get(op->ptr, "type");
+  ushort local_view_bits;
+  float loc[3], rot[3];
+
+  WM_operator_view3d_unit_defaults(C, op);
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  ob = ED_object_add_type(C, OB_LAMP, get_light_defname(type), loc, rot, false, local_view_bits);
+
+  float size = RNA_float_get(op->ptr, "radius");
+  /* Better defaults for light size. */
+  switch (type) {
+    case LA_LOCAL:
+    case LA_SPOT:
+      break;
+    case LA_AREA:
+      size *= 4.0f;
+      break;
+    default:
+      size *= 0.5f;
+      break;
+  }
+  BKE_object_obdata_size_init(ob, size);
+
+  la = (Light *)ob->data;
+  la->type = type;
+
+  if (type == LA_SUN) {
+    la->energy = 1.0f;
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_light_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Light";
+  ot->description = "Add a light object to the scene";
+  ot->idname = "OBJECT_OT_light_add";
+
+  /* api callbacks */
+  ot->invoke = WM_menu_invoke;
+  ot->exec = object_light_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_light_type_items, 0, "Type", "");
+  RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_LIGHT);
+
+  ED_object_add_unit_props_radius(ot);
+  ED_object_add_generic_props(ot, false);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Collection Instance Operator
+ * \{ */
+
+static int collection_instance_add_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Collection *collection;
+  ushort local_view_bits;
+  float loc[3], rot[3];
+
+  PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
+  PropertyRNA *prop_location = RNA_struct_find_property(op->ptr, "location");
+  PropertyRNA *prop_session_uuid = RNA_struct_find_property(op->ptr, "session_uuid");
+
+  bool update_location_if_necessary = false;
+  if (RNA_property_is_set(op->ptr, prop_name)) {
+    char name[MAX_ID_NAME - 2];
+    RNA_property_string_get(op->ptr, prop_name, name);
+    collection = (Collection *)BKE_libblock_find_name(bmain, ID_GR, name);
+    update_location_if_necessary = true;
+  }
+  else if (RNA_property_is_set(op->ptr, prop_session_uuid)) {
+    const uint32_t session_uuid = (uint32_t)RNA_property_int_get(op->ptr, prop_session_uuid);
+    collection = (Collection *)BKE_libblock_find_session_uuid(bmain, ID_GR, session_uuid);
+    update_location_if_necessary = true;
+  }
+  else {
+    collection = static_cast(
+        BLI_findlink(&bmain->collections, RNA_enum_get(op->ptr, "collection")));
+  }
+
+  if (update_location_if_necessary) {
+    int mval[2];
+    if (!RNA_property_is_set(op->ptr, prop_location) && object_add_drop_xy_get(C, op, &mval)) {
+      ED_object_location_from_view(C, loc);
+      ED_view3d_cursor3d_position(C, mval, false, loc);
+      RNA_property_float_set_array(op->ptr, prop_location, loc);
+    }
+  }
+
+  if (collection == nullptr) {
+    return OPERATOR_CANCELLED;
+  }
+
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+
+  /* Avoid dependency cycles. */
+  LayerCollection *active_lc = BKE_layer_collection_get_active(view_layer);
+  while (BKE_collection_cycle_find(active_lc->collection, collection)) {
+    active_lc = BKE_layer_collection_activate_parent(view_layer, active_lc);
+  }
+
+  Object *ob = ED_object_add_type(
+      C, OB_EMPTY, collection->id.name + 2, loc, rot, false, local_view_bits);
+  ob->instance_collection = collection;
+  ob->empty_drawsize = U.collection_instance_empty_size;
+  ob->transflag |= OB_DUPLICOLLECTION;
+  id_us_plus(&collection->id);
+
+  return OPERATOR_FINISHED;
+}
+
+static int object_instance_add_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  if (!object_add_drop_xy_is_set(op)) {
+    RNA_int_set(op->ptr, "drop_x", event->xy[0]);
+    RNA_int_set(op->ptr, "drop_y", event->xy[1]);
+  }
+
+  if (!RNA_struct_property_is_set(op->ptr, "name") &&
+      !RNA_struct_property_is_set(op->ptr, "session_uuid")) {
+    return WM_enum_search_invoke(C, op, event);
+  }
+  return op->type->exec(C, op);
+}
+
+void OBJECT_OT_collection_instance_add(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  /* identifiers */
+  ot->name = "Add Collection Instance";
+  ot->description = "Add a collection instance";
+  ot->idname = "OBJECT_OT_collection_instance_add";
+
+  /* api callbacks */
+  ot->invoke = object_instance_add_invoke;
+  ot->exec = collection_instance_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  RNA_def_string(
+      ot->srna, "name", "Collection", MAX_ID_NAME - 2, "Name", "Collection name to add");
+  prop = RNA_def_enum(ot->srna, "collection", DummyRNA_NULL_items, 0, "Collection", "");
+  RNA_def_enum_funcs(prop, RNA_collection_itemf);
+  RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
+  ot->prop = prop;
+  ED_object_add_generic_props(ot, false);
+
+  prop = RNA_def_int(ot->srna,
+                     "session_uuid",
+                     0,
+                     INT32_MIN,
+                     INT32_MAX,
+                     "Session UUID",
+                     "Session UUID of the collection to add",
+                     INT32_MIN,
+                     INT32_MAX);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_SKIP_SAVE | PROP_HIDDEN));
+
+  object_add_drop_xy_props(ot);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Data Instance Operator
+ *
+ * Use for dropping ID's from the outliner.
+ * \{ */
+
+static int object_data_instance_add_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  ID *id = nullptr;
+  ushort local_view_bits;
+  float loc[3], rot[3];
+
+  PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
+  PropertyRNA *prop_type = RNA_struct_find_property(op->ptr, "type");
+  PropertyRNA *prop_location = RNA_struct_find_property(op->ptr, "location");
+
+  /* These shouldn't fail when created by outliner dropping as it checks the ID is valid. */
+  if (!RNA_property_is_set(op->ptr, prop_name) || !RNA_property_is_set(op->ptr, prop_type)) {
+    return OPERATOR_CANCELLED;
+  }
+  const short id_type = RNA_property_enum_get(op->ptr, prop_type);
+  char name[MAX_ID_NAME - 2];
+  RNA_property_string_get(op->ptr, prop_name, name);
+  id = BKE_libblock_find_name(bmain, id_type, name);
+  if (id == nullptr) {
+    return OPERATOR_CANCELLED;
+  }
+  const int object_type = BKE_object_obdata_to_type(id);
+  if (object_type == -1) {
+    return OPERATOR_CANCELLED;
+  }
+
+  int mval[2];
+  if (!RNA_property_is_set(op->ptr, prop_location) && object_add_drop_xy_get(C, op, &mval)) {
+    ED_object_location_from_view(C, loc);
+    ED_view3d_cursor3d_position(C, mval, false, loc);
+    RNA_property_float_set_array(op->ptr, prop_location, loc);
+  }
+
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  ED_object_add_type_with_obdata(
+      C, object_type, id->name + 2, loc, rot, false, local_view_bits, id);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_data_instance_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Object Data Instance";
+  ot->description = "Add an object data instance";
+  ot->idname = "OBJECT_OT_data_instance_add";
+
+  /* api callbacks */
+  ot->invoke = object_add_drop_xy_generic_invoke;
+  ot->exec = object_data_instance_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  RNA_def_string(ot->srna, "name", "Name", MAX_ID_NAME - 2, "Name", "ID name to add");
+  PropertyRNA *prop = RNA_def_enum(ot->srna, "type", rna_enum_id_type_items, 0, "Type", "");
+  RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
+  ED_object_add_generic_props(ot, false);
+
+  object_add_drop_xy_props(ot);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Speaker Operator
+ * \{ */
+
+static int object_speaker_add_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+  Object *ob = ED_object_add_type(C, OB_SPEAKER, nullptr, loc, rot, false, local_view_bits);
+  const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ob);
+
+  /* To make it easier to start using this immediately in NLA, a default sound clip is created
+   * ready to be moved around to re-time the sound and/or make new sound clips. */
+  {
+    /* create new data for NLA hierarchy */
+    AnimData *adt = BKE_animdata_ensure_id(&ob->id);
+    NlaTrack *nlt = BKE_nlatrack_add(adt, nullptr, is_liboverride);
+    NlaStrip *strip = BKE_nla_add_soundstrip(bmain, scene, static_cast(ob->data));
+    strip->start = CFRA;
+    strip->end += strip->start;
+
+    /* hook them up */
+    BKE_nlatrack_add_strip(nlt, strip, is_liboverride);
+
+    /* Auto-name the strip, and give the track an interesting name. */
+    BLI_strncpy(nlt->name, DATA_("SoundTrack"), sizeof(nlt->name));
+    BKE_nlastrip_validate_name(adt, strip);
+
+    WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, nullptr);
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_speaker_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Speaker";
+  ot->description = "Add a speaker object to the scene";
+  ot->idname = "OBJECT_OT_speaker_add";
+
+  /* api callbacks */
+  ot->exec = object_speaker_add_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  ED_object_add_generic_props(ot, true);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Hair Curves Operator
+ * \{ */
+
+static bool object_hair_curves_add_poll(bContext *C)
+{
+  if (!U.experimental.use_new_curves_type) {
+    return false;
+  }
+  return ED_operator_objectmode(C);
+}
+
+static int object_hair_curves_add_exec(bContext *C, wmOperator *op)
+{
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits);
+  object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_hair_curves_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Hair Curves";
+  ot->description = "Add a hair curves object to the scene";
+  ot->idname = "OBJECT_OT_hair_curves_add";
+
+  /* api callbacks */
+  ot->exec = object_hair_curves_add_exec;
+  ot->poll = object_hair_curves_add_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  ED_object_add_generic_props(ot, false);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Point Cloud Operator
+ * \{ */
+
+static bool object_pointcloud_add_poll(bContext *C)
+{
+  if (!U.experimental.use_new_point_cloud_type) {
+    return false;
+  }
+  return ED_operator_objectmode(C);
+}
+
+static int object_pointcloud_add_exec(bContext *C, wmOperator *op)
+{
+  ushort local_view_bits;
+  float loc[3], rot[3];
+  if (!ED_object_add_generic_get_opts(
+          C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  Object *object = ED_object_add_type(C, OB_POINTCLOUD, nullptr, loc, rot, false, local_view_bits);
+  object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_pointcloud_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Point Cloud";
+  ot->description = "Add a point cloud object to the scene";
+  ot->idname = "OBJECT_OT_pointcloud_add";
+
+  /* api callbacks */
+  ot->exec = object_pointcloud_add_exec;
+  ot->poll = object_pointcloud_add_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  ED_object_add_generic_props(ot, false);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Delete Object Operator
+ * \{ */
+
+void ED_object_base_free_and_unlink(Main *bmain, Scene *scene, Object *ob)
+{
+  if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 &&
+      BKE_library_ID_is_indirectly_used(bmain, ob)) {
+    /* We cannot delete indirectly used object... */
+    printf(
+        "WARNING, undeletable object '%s', should have been caught before reaching this "
+        "function!",
+        ob->id.name + 2);
+    return;
+  }
+  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
+    /* Do not delete objects used by overrides of collections. */
+    return;
+  }
+
+  DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_BASE_FLAGS);
+
+  BKE_scene_collections_object_remove(bmain, scene, ob, true);
+}
+
+void ED_object_base_free_and_unlink_no_indirect_check(Main *bmain, Scene *scene, Object *ob)
+{
+  BLI_assert(!BKE_library_ID_is_indirectly_used(bmain, ob));
+  DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_BASE_FLAGS);
+  BKE_scene_collections_object_remove(bmain, scene, ob, true);
+}
+
+static int object_delete_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  wmWindowManager *wm = CTX_wm_manager(C);
+  const bool use_global = RNA_boolean_get(op->ptr, "use_global");
+  const bool confirm = op->flag & OP_IS_INVOKE;
+  uint changed_count = 0;
+  uint tagged_count = 0;
+
+  if (CTX_data_edit_object(C)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
+
+  CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
+    if (ob->id.tag & LIB_TAG_INDIRECT) {
+      /* Can this case ever happen? */
+      BKE_reportf(op->reports,
+                  RPT_WARNING,
+                  "Cannot delete indirectly linked object '%s'",
+                  ob->id.name + 2);
+      continue;
+    }
+
+    if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
+      BKE_reportf(op->reports,
+                  RPT_WARNING,
+                  "Cannot delete object '%s' as it is used by override collections",
+                  ob->id.name + 2);
+      continue;
+    }
+
+    if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 &&
+        BKE_library_ID_is_indirectly_used(bmain, ob)) {
+      BKE_reportf(op->reports,
+                  RPT_WARNING,
+                  "Cannot delete object '%s' from scene '%s', indirectly used objects need at "
+                  "least one user",
+                  ob->id.name + 2,
+                  scene->id.name + 2);
+      continue;
+    }
+
+    /* if grease pencil object, set cache as dirty */
+    if (ob->type == OB_GPENCIL) {
+      bGPdata *gpd = (bGPdata *)ob->data;
+      DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+    }
+
+    /* Use multi tagged delete if `use_global=True`, or the object is used only in one scene. */
+    if (use_global || ID_REAL_USERS(ob) <= 1) {
+      ob->id.tag |= LIB_TAG_DOIT;
+      tagged_count += 1;
+    }
+    else {
+      /* Object is used in multiple scenes. Delete the object from the current scene only. */
+      ED_object_base_free_and_unlink_no_indirect_check(bmain, scene, ob);
+      changed_count += 1;
+
+      /* FIXME: this will also remove parent from grease pencil from other scenes. */
+      /* Remove from Grease Pencil parent */
+      LISTBASE_FOREACH (bGPdata *, gpd, &bmain->gpencils) {
+        LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+          if (gpl->parent != nullptr) {
+            if (gpl->parent == ob) {
+              gpl->parent = nullptr;
+            }
+          }
+        }
+      }
+    }
+  }
+  CTX_DATA_END;
+
+  if ((changed_count + tagged_count) == 0) {
+    return OPERATOR_CANCELLED;
+  }
+
+  if (tagged_count > 0) {
+    BKE_id_multi_tagged_delete(bmain);
+  }
+
+  if (confirm) {
+    BKE_reportf(op->reports, RPT_INFO, "Deleted %u object(s)", (changed_count + tagged_count));
+  }
+
+  /* delete has to handle all open scenes */
+  BKE_main_id_tag_listbase(&bmain->scenes, LIB_TAG_DOIT, true);
+  LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
+    scene = WM_window_get_active_scene(win);
+
+    if (scene->id.tag & LIB_TAG_DOIT) {
+      scene->id.tag &= ~LIB_TAG_DOIT;
+
+      DEG_relations_tag_update(bmain);
+
+      DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
+      WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
+      WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+    }
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_delete(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Delete";
+  ot->description = "Delete selected objects";
+  ot->idname = "OBJECT_OT_delete";
+
+  /* api callbacks */
+  ot->invoke = WM_operator_confirm_or_exec;
+  ot->exec = object_delete_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  PropertyRNA *prop;
+  prop = RNA_def_boolean(
+      ot->srna, "use_global", false, "Delete Globally", "Remove object from all scenes");
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+  WM_operator_properties_confirm_or_exec(ot);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Copy Object Utilities
+ * \{ */
+
+/* after copying objects, copied data should get new pointers */
+static void copy_object_set_idnew(bContext *C)
+{
+  Main *bmain = CTX_data_main(C);
+
+  CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
+    BKE_libblock_relink_to_newid(bmain, &ob->id, 0);
+  }
+  CTX_DATA_END;
+
+#ifndef NDEBUG
+  /* Call to `BKE_libblock_relink_to_newid` above is supposed to have cleared all those flags. */
+  ID *id_iter;
+  FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
+    if (GS(id_iter->name) == ID_OB) {
+      /* Not all duplicated objects would be used by other newly duplicated data, so their flag
+       * will not always be cleared. */
+      continue;
+    }
+    BLI_assert((id_iter->tag & LIB_TAG_NEW) == 0);
+  }
+  FOREACH_MAIN_ID_END;
+#endif
+
+  BKE_main_id_newptr_and_tag_clear(bmain);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Make Instanced Objects Real Operator
+ * \{ */
+
+/* XXX TODO: That whole hierarchy handling based on persistent_id tricks is
+ * very confusing and convoluted, and it will fail in many cases besides basic ones.
+ * Think this should be replaced by a proper tree-like representation of the instantiations,
+ * should help a lot in both readability, and precise consistent rebuilding of hierarchy.
+ */
+
+/**
+ * \note regarding hashing dupli-objects which come from OB_DUPLICOLLECTION,
+ * skip the first member of #DupliObject.persistent_id
+ * since its a unique index and we only want to know if the group objects are from the same
+ * dupli-group instance.
+ *
+ * \note regarding hashing dupli-objects which come from non-OB_DUPLICOLLECTION,
+ * include the first member of #DupliObject.persistent_id
+ * since its the index of the vertex/face the object is instantiated on and we want to identify
+ * objects on the same vertex/face.
+ * In other words, we consider each group of objects from a same item as being
+ * the 'local group' where to check for parents.
+ */
+static uint dupliobject_hash(const void *ptr)
+{
+  const DupliObject *dob = static_cast(ptr);
+  uint hash = BLI_ghashutil_ptrhash(dob->ob);
+
+  if (dob->type == OB_DUPLICOLLECTION) {
+    for (int i = 1; (i < MAX_DUPLI_RECUR) && dob->persistent_id[i] != INT_MAX; i++) {
+      hash ^= (dob->persistent_id[i] ^ i);
+    }
+  }
+  else {
+    hash ^= (dob->persistent_id[0] ^ 0);
+  }
+  return hash;
+}
+
+/**
+ * \note regarding hashing dupli-objects when using OB_DUPLICOLLECTION,
+ * skip the first member of #DupliObject.persistent_id
+ * since its a unique index and we only want to know if the group objects are from the same
+ * dupli-group instance.
+ */
+static uint dupliobject_instancer_hash(const void *ptr)
+{
+  const DupliObject *dob = static_cast(ptr);
+  uint hash = BLI_ghashutil_inthash(dob->persistent_id[0]);
+  for (int i = 1; (i < MAX_DUPLI_RECUR) && dob->persistent_id[i] != INT_MAX; i++) {
+    hash ^= (dob->persistent_id[i] ^ i);
+  }
+  return hash;
+}
+
+/* Compare function that matches dupliobject_hash */
+static bool dupliobject_cmp(const void *a_, const void *b_)
+{
+  const DupliObject *a = static_cast(a_);
+  const DupliObject *b = static_cast(b_);
+
+  if (a->ob != b->ob) {
+    return true;
+  }
+
+  if (a->type != b->type) {
+    return true;
+  }
+
+  if (a->type == OB_DUPLICOLLECTION) {
+    for (int i = 1; (i < MAX_DUPLI_RECUR); i++) {
+      if (a->persistent_id[i] != b->persistent_id[i]) {
+        return true;
+      }
+      if (a->persistent_id[i] == INT_MAX) {
+        break;
+      }
+    }
+  }
+  else {
+    if (a->persistent_id[0] != b->persistent_id[0]) {
+      return true;
+    }
+  }
+
+  /* matching */
+  return false;
+}
+
+/* Compare function that matches dupliobject_instancer_hash. */
+static bool dupliobject_instancer_cmp(const void *a_, const void *b_)
+{
+  const DupliObject *a = static_cast(a_);
+  const DupliObject *b = static_cast(b_);
+
+  for (int i = 0; (i < MAX_DUPLI_RECUR); i++) {
+    if (a->persistent_id[i] != b->persistent_id[i]) {
+      return true;
+    }
+    if (a->persistent_id[i] == INT_MAX) {
+      break;
+    }
+  }
+
+  /* matching */
+  return false;
+}
+
+static void make_object_duplilist_real(bContext *C,
+                                       Depsgraph *depsgraph,
+                                       Scene *scene,
+                                       Base *base,
+                                       const bool use_base_parent,
+                                       const bool use_hierarchy)
+{
+  Main *bmain = CTX_data_main(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  GHash *parent_gh = nullptr, *instancer_gh = nullptr;
+
+  Object *object_eval = DEG_get_evaluated_object(depsgraph, base->object);
+
+  if (!(base->object->transflag & OB_DUPLI) &&
+      !BKE_object_has_geometry_set_instances(object_eval)) {
+    return;
+  }
+
+  ListBase *lb_duplis = object_duplilist(depsgraph, scene, object_eval);
+
+  if (BLI_listbase_is_empty(lb_duplis)) {
+    free_object_duplilist(lb_duplis);
+    return;
+  }
+
+  GHash *dupli_gh = BLI_ghash_ptr_new(__func__);
+  if (use_hierarchy) {
+    parent_gh = BLI_ghash_new(dupliobject_hash, dupliobject_cmp, __func__);
+
+    if (use_base_parent) {
+      instancer_gh = BLI_ghash_new(
+          dupliobject_instancer_hash, dupliobject_instancer_cmp, __func__);
+    }
+  }
+
+  LISTBASE_FOREACH (DupliObject *, dob, lb_duplis) {
+    Object *ob_src = DEG_get_original_object(dob->ob);
+    Object *ob_dst = static_cast(ID_NEW_SET(ob_src, BKE_id_copy(bmain, &ob_src->id)));
+    id_us_min(&ob_dst->id);
+
+    /* font duplis can have a totcol without material, we get them from parent
+     * should be implemented better...
+     */
+    if (ob_dst->mat == nullptr) {
+      ob_dst->totcol = 0;
+    }
+
+    BKE_collection_object_add_from(bmain, scene, base->object, ob_dst);
+    Base *base_dst = BKE_view_layer_base_find(view_layer, ob_dst);
+    BLI_assert(base_dst != nullptr);
+
+    ED_object_base_select(base_dst, BA_SELECT);
+    DEG_id_tag_update(&ob_dst->id, ID_RECALC_SELECT);
+
+    BKE_scene_object_base_flag_sync_from_base(base_dst);
+
+    /* make sure apply works */
+    BKE_animdata_free(&ob_dst->id, true);
+    ob_dst->adt = nullptr;
+
+    ob_dst->parent = nullptr;
+    BKE_constraints_free(&ob_dst->constraints);
+    ob_dst->runtime.curve_cache = nullptr;
+    const bool is_dupli_instancer = (ob_dst->transflag & OB_DUPLI) != 0;
+    ob_dst->transflag &= ~OB_DUPLI;
+    /* Remove instantiated collection, it's annoying to keep it here
+     * (and get potentially a lot of usages of it then...). */
+    id_us_min((ID *)ob_dst->instance_collection);
+    ob_dst->instance_collection = nullptr;
+
+    copy_m4_m4(ob_dst->obmat, dob->mat);
+    BKE_object_apply_mat4(ob_dst, ob_dst->obmat, false, false);
+
+    BLI_ghash_insert(dupli_gh, dob, ob_dst);
+    if (parent_gh) {
+      void **val;
+      /* Due to nature of hash/comparison of this ghash, a lot of duplis may be considered as
+       * 'the same', this avoids trying to insert same key several time and
+       * raise asserts in debug builds... */
+      if (!BLI_ghash_ensure_p(parent_gh, dob, &val)) {
+        *val = ob_dst;
+      }
+
+      if (is_dupli_instancer && instancer_gh) {
+        /* Same as above, we may have several 'hits'. */
+        if (!BLI_ghash_ensure_p(instancer_gh, dob, &val)) {
+          *val = ob_dst;
+        }
+      }
+    }
+  }
+
+  LISTBASE_FOREACH (DupliObject *, dob, lb_duplis) {
+    Object *ob_src = dob->ob;
+    Object *ob_dst = static_cast(BLI_ghash_lookup(dupli_gh, dob));
+
+    /* Remap new object to itself, and clear again newid pointer of orig object. */
+    BKE_libblock_relink_to_newid(bmain, &ob_dst->id, 0);
+
+    DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY);
+
+    if (use_hierarchy) {
+      /* original parents */
+      Object *ob_src_par = ob_src->parent;
+      Object *ob_dst_par = nullptr;
+
+      /* find parent that was also made real */
+      if (ob_src_par) {
+        /* OK to keep most of the members uninitialized,
+         * they won't be read, this is simply for a hash lookup. */
+        DupliObject dob_key;
+        dob_key.ob = ob_src_par;
+        dob_key.type = dob->type;
+        if (dob->type == OB_DUPLICOLLECTION) {
+          memcpy(&dob_key.persistent_id[1],
+                 &dob->persistent_id[1],
+                 sizeof(dob->persistent_id[1]) * (MAX_DUPLI_RECUR - 1));
+        }
+        else {
+          dob_key.persistent_id[0] = dob->persistent_id[0];
+        }
+        ob_dst_par = static_cast(BLI_ghash_lookup(parent_gh, &dob_key));
+      }
+
+      if (ob_dst_par) {
+        /* allow for all possible parent types */
+        ob_dst->partype = ob_src->partype;
+        BLI_strncpy(ob_dst->parsubstr, ob_src->parsubstr, sizeof(ob_dst->parsubstr));
+        ob_dst->par1 = ob_src->par1;
+        ob_dst->par2 = ob_src->par2;
+        ob_dst->par3 = ob_src->par3;
+
+        copy_m4_m4(ob_dst->parentinv, ob_src->parentinv);
+
+        ob_dst->parent = ob_dst_par;
+      }
+    }
+    if (use_base_parent && ob_dst->parent == nullptr) {
+      Object *ob_dst_par = nullptr;
+
+      if (instancer_gh != nullptr) {
+        /* OK to keep most of the members uninitialized,
+         * they won't be read, this is simply for a hash lookup. */
+        DupliObject dob_key;
+        /* We are looking one step upper in hierarchy, so we need to 'shift' the `persistent_id`,
+         * ignoring the first item.
+         * We only check on persistent_id here, since we have no idea what object it might be. */
+        memcpy(&dob_key.persistent_id[0],
+               &dob->persistent_id[1],
+               sizeof(dob_key.persistent_id[0]) * (MAX_DUPLI_RECUR - 1));
+        ob_dst_par = static_cast(BLI_ghash_lookup(instancer_gh, &dob_key));
+      }
+
+      if (ob_dst_par == nullptr) {
+        /* Default to parenting to root object...
+         * Always the case when use_hierarchy is false. */
+        ob_dst_par = base->object;
+      }
+
+      ob_dst->parent = ob_dst_par;
+      ob_dst->partype = PAROBJECT;
+    }
+
+    if (ob_dst->parent) {
+      /* NOTE: this may be the parent of other objects, but it should
+       * still work out ok */
+      BKE_object_apply_mat4(ob_dst, dob->mat, false, true);
+
+      /* to set ob_dst->orig and in case there's any other discrepancies */
+      DEG_id_tag_update(&ob_dst->id, ID_RECALC_TRANSFORM);
+    }
+  }
+
+  if (base->object->transflag & OB_DUPLICOLLECTION && base->object->instance_collection) {
+    base->object->instance_collection = nullptr;
+  }
+
+  ED_object_base_select(base, BA_DESELECT);
+  DEG_id_tag_update(&base->object->id, ID_RECALC_SELECT);
+
+  BLI_ghash_free(dupli_gh, nullptr, nullptr);
+  if (parent_gh) {
+    BLI_ghash_free(parent_gh, nullptr, nullptr);
+  }
+  if (instancer_gh) {
+    BLI_ghash_free(instancer_gh, nullptr, nullptr);
+  }
+
+  free_object_duplilist(lb_duplis);
+
+  BKE_main_id_newptr_and_tag_clear(bmain);
+
+  base->object->transflag &= ~OB_DUPLI;
+  DEG_id_tag_update(&base->object->id, ID_RECALC_COPY_ON_WRITE);
+}
+
+static int object_duplicates_make_real_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+  Scene *scene = CTX_data_scene(C);
+
+  const bool use_base_parent = RNA_boolean_get(op->ptr, "use_base_parent");
+  const bool use_hierarchy = RNA_boolean_get(op->ptr, "use_hierarchy");
+
+  BKE_main_id_newptr_and_tag_clear(bmain);
+
+  CTX_DATA_BEGIN (C, Base *, base, selected_editable_bases) {
+    make_object_duplilist_real(C, depsgraph, scene, base, use_base_parent, use_hierarchy);
+
+    /* dependencies were changed */
+    WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, base->object);
+  }
+  CTX_DATA_END;
+
+  DEG_relations_tag_update(bmain);
+  WM_event_add_notifier(C, NC_SCENE, scene);
+  WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr);
+  ED_outliner_select_sync_from_object_tag(C);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_duplicates_make_real(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Make Instances Real";
+  ot->description = "Make instanced objects attached to this object real";
+  ot->idname = "OBJECT_OT_duplicates_make_real";
+
+  /* api callbacks */
+  ot->exec = object_duplicates_make_real_exec;
+
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  RNA_def_boolean(ot->srna,
+                  "use_base_parent",
+                  false,
+                  "Parent",
+                  "Parent newly created objects to the original instancer");
+  RNA_def_boolean(
+      ot->srna, "use_hierarchy", false, "Keep Hierarchy", "Maintain parent child relationships");
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Data Convert Operator
+ * \{ */
+
+static const EnumPropertyItem convert_target_items[] = {
+    {OB_CURVES_LEGACY,
+     "CURVE",
+     ICON_OUTLINER_OB_CURVE,
+     "Curve",
+     "Curve from Mesh or Text objects"},
+    {OB_MESH,
+     "MESH",
+     ICON_OUTLINER_OB_MESH,
+     "Mesh",
+#ifdef WITH_POINT_CLOUD
+     "Mesh from Curve, Surface, Metaball, Text, or Point Cloud objects"},
+#else
+     "Mesh from Curve, Surface, Metaball, or Text objects"},
+#endif
+    {OB_GPENCIL,
+     "GPENCIL",
+     ICON_OUTLINER_OB_GREASEPENCIL,
+     "Grease Pencil",
+     "Grease Pencil from Curve or Mesh objects"},
+#ifdef WITH_POINT_CLOUD
+    {OB_POINTCLOUD,
+     "POINTCLOUD",
+     ICON_OUTLINER_OB_POINTCLOUD,
+     "Point Cloud",
+     "Point Cloud from Mesh objects"},
+#endif
+    {0, nullptr, 0, nullptr, nullptr},
+};
+
+static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene *scene, Object *ob)
+{
+  if (ob->runtime.curve_cache == nullptr) {
+    /* Force creation. This is normally not needed but on operator
+     * redo we might end up with an object which isn't evaluated yet.
+     * Also happens in case we are working on a copy of the object
+     * (all its caches have been nuked then).
+     */
+    if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)) {
+      /* We need 'for render' ON here, to enable computing bevel dipslist if needed.
+       * Also makes sense anyway, we would not want e.g. to lose hidden parts etc. */
+      BKE_displist_make_curveTypes(depsgraph, scene, ob, true);
+    }
+    else if (ob->type == OB_MBALL) {
+      BKE_displist_make_mball(depsgraph, scene, ob);
+    }
+  }
+}
+
+static void object_data_convert_curve_to_mesh(Main *bmain, Depsgraph *depsgraph, Object *ob)
+{
+  Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
+  Curve *curve = static_cast(ob->data);
+
+  Mesh *mesh = BKE_mesh_new_from_object_to_bmain(bmain, depsgraph, object_eval, true);
+  if (mesh == nullptr) {
+    /* Unable to convert the curve to a mesh. */
+    return;
+  }
+
+  BKE_object_free_modifiers(ob, 0);
+  /* Replace curve used by the object itself. */
+  ob->data = mesh;
+  ob->type = OB_MESH;
+  id_us_min(&curve->id);
+  id_us_plus(&mesh->id);
+  /* Change objects which are using same curve.
+   * A bit annoying, but:
+   * - It's possible to have multiple curve objects selected which are sharing the same curve
+   *   data-block. We don't want mesh to be created for every of those objects.
+   * - This is how conversion worked for a long time. */
+  LISTBASE_FOREACH (Object *, other_object, &bmain->objects) {
+    if (other_object->data == curve) {
+      other_object->type = OB_MESH;
+
+      id_us_min((ID *)other_object->data);
+      other_object->data = ob->data;
+      id_us_plus((ID *)other_object->data);
+    }
+  }
+}
+
+static bool object_convert_poll(bContext *C)
+{
+  Scene *scene = CTX_data_scene(C);
+  Base *base_act = CTX_data_active_base(C);
+  Object *obact = base_act ? base_act->object : nullptr;
+
+  if (obact == nullptr || obact->data == nullptr || ID_IS_LINKED(obact) ||
+      ID_IS_OVERRIDE_LIBRARY(obact) || ID_IS_OVERRIDE_LIBRARY(obact->data)) {
+    return false;
+  }
+
+  return (!ID_IS_LINKED(scene) && (BKE_object_is_in_editmode(obact) == false) &&
+          (base_act->flag & BASE_SELECTED));
+}
+
+/* Helper for object_convert_exec */
+static Base *duplibase_for_convert(
+    Main *bmain, Depsgraph *depsgraph, Scene *scene, ViewLayer *view_layer, Base *base, Object *ob)
+{
+  if (ob == nullptr) {
+    ob = base->object;
+  }
+
+  Object *obn = (Object *)BKE_id_copy(bmain, &ob->id);
+  id_us_min(&obn->id);
+  DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
+  BKE_collection_object_add_from(bmain, scene, ob, obn);
+
+  Base *basen = BKE_view_layer_base_find(view_layer, obn);
+  ED_object_base_select(basen, BA_SELECT);
+  ED_object_base_select(base, BA_DESELECT);
+
+  /* XXX: An ugly hack needed because if we re-run depsgraph with some new meta-ball objects
+   * having same 'family name' as orig ones, they will affect end result of meta-ball computation.
+   * For until we get rid of that name-based thingy in MBalls, that should do the trick
+   * (this is weak, but other solution (to change name of `obn`) is even worse imho).
+   * See T65996. */
+  const bool is_meta_ball = (obn->type == OB_MBALL);
+  void *obdata = obn->data;
+  if (is_meta_ball) {
+    obn->type = OB_EMPTY;
+    obn->data = nullptr;
+  }
+
+  /* XXX Doing that here is stupid, it means we update and re-evaluate the whole depsgraph every
+   * time we need to duplicate an object to convert it. Even worse, this is not 100% correct, since
+   * we do not yet have duplicated obdata.
+   * However, that is a safe solution for now. Proper, longer-term solution is to refactor
+   * object_convert_exec to:
+   *  - duplicate all data it needs to in a first loop.
+   *  - do a single update.
+   *  - convert data in a second loop. */
+  DEG_graph_tag_relations_update(depsgraph);
+  CustomData_MeshMasks customdata_mask_prev = scene->customdata_mask;
+  CustomData_MeshMasks_update(&scene->customdata_mask, &CD_MASK_MESH);
+  BKE_scene_graph_update_tagged(depsgraph, bmain);
+  scene->customdata_mask = customdata_mask_prev;
+
+  if (is_meta_ball) {
+    obn->type = OB_MBALL;
+    obn->data = obdata;
+  }
+
+  return basen;
+}
+
+static int object_convert_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  View3D *v3d = CTX_wm_view3d(C);
+  Base *basen = nullptr, *basact = nullptr;
+  Object *ob1, *obact = CTX_data_active_object(C);
+  const short target = RNA_enum_get(op->ptr, "target");
+  bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
+
+  const float angle = RNA_float_get(op->ptr, "angle");
+  const int thickness = RNA_int_get(op->ptr, "thickness");
+  const bool use_seams = RNA_boolean_get(op->ptr, "seams");
+  const bool use_faces = RNA_boolean_get(op->ptr, "faces");
+  const float offset = RNA_float_get(op->ptr, "offset");
+
+  int a, mballConverted = 0;
+  bool gpencilConverted = false;
+  bool gpencilCurveConverted = false;
+
+  /* don't forget multiple users! */
+
+  {
+    FOREACH_SCENE_OBJECT_BEGIN (scene, ob) {
+      ob->flag &= ~OB_DONE;
+
+      /* flag data that's not been edited (only needed for !keep_original) */
+      if (ob->data) {
+        ((ID *)ob->data)->tag |= LIB_TAG_DOIT;
+      }
+
+      /* possible metaball basis is not in this scene */
+      if (ob->type == OB_MBALL && target == OB_MESH) {
+        if (BKE_mball_is_basis(ob) == false) {
+          Object *ob_basis;
+          ob_basis = BKE_mball_basis_find(scene, ob);
+          if (ob_basis) {
+            ob_basis->flag &= ~OB_DONE;
+          }
+        }
+      }
+    }
+    FOREACH_SCENE_OBJECT_END;
+  }
+
+  ListBase selected_editable_bases;
+  CTX_data_selected_editable_bases(C, &selected_editable_bases);
+
+  /* Ensure we get all meshes calculated with a sufficient data-mask,
+   * needed since re-evaluating single modifiers causes bugs if they depend
+   * on other objects data masks too, see: T50950. */
+  {
+    LISTBASE_FOREACH (CollectionPointerLink *, link, &selected_editable_bases) {
+      Base *base = static_cast(link->ptr.data);
+      Object *ob = base->object;
+
+      /* The way object type conversion works currently (enforcing conversion of *all* objects
+       * using converted object-data, even some un-selected/hidden/another scene ones,
+       * sounds totally bad to me.
+       * However, changing this is more design than bug-fix, not to mention convoluted code below,
+       * so that will be for later.
+       * But at the very least, do not do that with linked IDs! */
+      if ((ID_IS_LINKED(ob) || (ob->data && ID_IS_LINKED(ob->data))) && !keep_original) {
+        keep_original = true;
+        BKE_report(
+            op->reports,
+            RPT_INFO,
+            "Converting some linked object/object data, enforcing 'Keep Original' option to True");
+      }
+
+      DEG_id_tag_update(&base->object->id, ID_RECALC_GEOMETRY);
+    }
+
+    CustomData_MeshMasks customdata_mask_prev = scene->customdata_mask;
+    CustomData_MeshMasks_update(&scene->customdata_mask, &CD_MASK_MESH);
+    BKE_scene_graph_update_tagged(depsgraph, bmain);
+    scene->customdata_mask = customdata_mask_prev;
+  }
+
+  LISTBASE_FOREACH (CollectionPointerLink *, link, &selected_editable_bases) {
+    Object *newob = nullptr;
+    Base *base = static_cast(link->ptr.data);
+    Object *ob = base->object;
+
+    if (ob->flag & OB_DONE || !IS_TAGGED(ob->data)) {
+      if (ob->type != target) {
+        base->flag &= ~SELECT;
+        ob->flag &= ~SELECT;
+      }
+
+      /* obdata already modified */
+      if (!IS_TAGGED(ob->data)) {
+        /* When 2 objects with linked data are selected, converting both
+         * would keep modifiers on all but the converted object T26003. */
+        if (ob->type == OB_MESH) {
+          BKE_object_free_modifiers(ob, 0); /* after derivedmesh calls! */
+        }
+        if (ob->type == OB_GPENCIL) {
+          BKE_object_free_modifiers(ob, 0); /* after derivedmesh calls! */
+          BKE_object_free_shaderfx(ob, 0);
+        }
+      }
+    }
+    else if (ob->type == OB_MESH && target == OB_CURVES_LEGACY) {
+      ob->flag |= OB_DONE;
+
+      if (keep_original) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+        newob = basen->object;
+
+        /* Decrement original mesh's usage count. */
+        Mesh *me = static_cast(newob->data);
+        id_us_min(&me->id);
+
+        /* Make a new copy of the mesh. */
+        newob->data = BKE_id_copy(bmain, &me->id);
+      }
+      else {
+        newob = ob;
+      }
+
+      BKE_mesh_to_curve(bmain, depsgraph, scene, newob);
+
+      if (newob->type == OB_CURVES_LEGACY) {
+        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
+        if (newob->rigidbody_object != nullptr) {
+          ED_rigidbody_object_remove(bmain, scene, newob);
+        }
+      }
+    }
+    else if (ob->type == OB_MESH && target == OB_GPENCIL) {
+      ob->flag |= OB_DONE;
+
+      /* Create a new grease pencil object and copy transformations. */
+      ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
+      float loc[3], size[3], rot[3][3], eul[3];
+      float matrix[4][4];
+      mat4_to_loc_rot_size(loc, rot, size, ob->obmat);
+      mat3_to_eul(eul, rot);
+
+      Object *ob_gpencil = ED_gpencil_add_object(C, loc, local_view_bits);
+      copy_v3_v3(ob_gpencil->loc, loc);
+      copy_v3_v3(ob_gpencil->rot, eul);
+      copy_v3_v3(ob_gpencil->scale, size);
+      unit_m4(matrix);
+      /* Set object in 3D mode. */
+      bGPdata *gpd = (bGPdata *)ob_gpencil->data;
+      gpd->draw_mode = GP_DRAWMODE_3D;
+
+      gpencilConverted |= BKE_gpencil_convert_mesh(bmain,
+                                                   depsgraph,
+                                                   scene,
+                                                   ob_gpencil,
+                                                   ob,
+                                                   angle,
+                                                   thickness,
+                                                   offset,
+                                                   matrix,
+                                                   0,
+                                                   use_seams,
+                                                   use_faces,
+                                                   true);
+
+      /* Remove unused materials. */
+      int actcol = ob_gpencil->actcol;
+      for (int slot = 1; slot <= ob_gpencil->totcol; slot++) {
+        while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) {
+          ob_gpencil->actcol = slot;
+          BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil);
+
+          if (actcol >= slot) {
+            actcol--;
+          }
+        }
+      }
+      ob_gpencil->actcol = actcol;
+    }
+    else if (ob->type == OB_MESH && target == OB_POINTCLOUD) {
+      ob->flag |= OB_DONE;
+
+      if (keep_original) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+        newob = basen->object;
+
+        /* Decrement original mesh's usage count. */
+        Mesh *me = static_cast(newob->data);
+        id_us_min(&me->id);
+
+        /* Make a new copy of the mesh. */
+        newob->data = BKE_id_copy(bmain, &me->id);
+      }
+      else {
+        newob = ob;
+      }
+
+      BKE_mesh_to_pointcloud(bmain, depsgraph, scene, newob);
+
+      if (newob->type == OB_POINTCLOUD) {
+        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
+        ED_rigidbody_object_remove(bmain, scene, newob);
+      }
+    }
+    else if (ob->type == OB_MESH) {
+      ob->flag |= OB_DONE;
+
+      if (keep_original) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+        newob = basen->object;
+
+        /* Decrement original mesh's usage count. */
+        Mesh *me = static_cast(newob->data);
+        id_us_min(&me->id);
+
+        /* Make a new copy of the mesh. */
+        newob->data = BKE_id_copy(bmain, &me->id);
+      }
+      else {
+        newob = ob;
+        DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
+      }
+
+      /* make new mesh data from the original copy */
+      /* NOTE: get the mesh from the original, not from the copy in some
+       * cases this doesn't give correct results (when MDEF is used for eg)
+       */
+      Scene *scene_eval = (Scene *)DEG_get_evaluated_id(depsgraph, &scene->id);
+      Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
+      Mesh *me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
+      me_eval = BKE_mesh_copy_for_eval(me_eval, false);
+      /* Full (edge-angle based) draw calculation should ideally be performed. */
+      BKE_mesh_edges_set_draw_render(me_eval);
+      BKE_object_material_from_eval_data(bmain, newob, &me_eval->id);
+      Mesh *new_mesh = (Mesh *)newob->data;
+      BKE_mesh_nomain_to_mesh(me_eval, new_mesh, newob, &CD_MASK_MESH, true);
+      /* Anonymous attributes shouldn't be available on the applied geometry. */
+      BKE_mesh_anonymous_attributes_remove(new_mesh);
+      BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
+    }
+    else if (ob->type == OB_FONT) {
+      ob->flag |= OB_DONE;
+
+      if (keep_original) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+        newob = basen->object;
+
+        /* Decrement original curve's usage count. */
+        id_us_min(&((Curve *)newob->data)->id);
+
+        /* Make a new copy of the curve. */
+        newob->data = BKE_id_copy(bmain, static_cast(ob->data));
+      }
+      else {
+        newob = ob;
+      }
+
+      Curve *cu = static_cast(newob->data);
+
+      Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
+      BKE_vfont_to_curve_ex(ob_eval,
+                            static_cast(ob_eval->data),
+                            FO_EDIT,
+                            &cu->nurb,
+                            nullptr,
+                            nullptr,
+                            nullptr,
+                            nullptr);
+
+      newob->type = OB_CURVES_LEGACY;
+      cu->type = OB_CURVES_LEGACY;
+
+      if (cu->vfont) {
+        id_us_min(&cu->vfont->id);
+        cu->vfont = nullptr;
+      }
+      if (cu->vfontb) {
+        id_us_min(&cu->vfontb->id);
+        cu->vfontb = nullptr;
+      }
+      if (cu->vfonti) {
+        id_us_min(&cu->vfonti->id);
+        cu->vfonti = nullptr;
+      }
+      if (cu->vfontbi) {
+        id_us_min(&cu->vfontbi->id);
+        cu->vfontbi = nullptr;
+      }
+
+      if (!keep_original) {
+        /* other users */
+        if (ID_REAL_USERS(&cu->id) > 1) {
+          for (ob1 = static_cast(bmain->objects.first); ob1;
+               ob1 = static_cast(ob1->id.next)) {
+            if (ob1->data == ob->data) {
+              ob1->type = OB_CURVES_LEGACY;
+              DEG_id_tag_update(&ob1->id,
+                                ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
+            }
+          }
+        }
+      }
+
+      LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
+        nu->charidx = 0;
+      }
+
+      cu->flag &= ~CU_3D;
+      BKE_curve_dimension_update(cu);
+
+      if (target == OB_MESH) {
+        /* No assumption should be made that the resulting objects is a mesh, as conversion can
+         * fail. */
+        object_data_convert_curve_to_mesh(bmain, depsgraph, newob);
+        /* meshes doesn't use displist */
+        BKE_object_free_curve_cache(newob);
+      }
+      else if (target == OB_GPENCIL) {
+        ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
+        Object *ob_gpencil = ED_gpencil_add_object(C, newob->loc, local_view_bits);
+        copy_v3_v3(ob_gpencil->rot, newob->rot);
+        copy_v3_v3(ob_gpencil->scale, newob->scale);
+        BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, newob, false, 1.0f, 0.0f);
+        gpencilConverted = true;
+        gpencilCurveConverted = true;
+        basen = nullptr;
+      }
+    }
+    else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) {
+      ob->flag |= OB_DONE;
+
+      if (target == OB_MESH) {
+        if (keep_original) {
+          basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+          newob = basen->object;
+
+          /* Decrement original curve's usage count. */
+          id_us_min(&((Curve *)newob->data)->id);
+
+          /* make a new copy of the curve */
+          newob->data = BKE_id_copy(bmain, static_cast(ob->data));
+        }
+        else {
+          newob = ob;
+        }
+
+        /* No assumption should be made that the resulting objects is a mesh, as conversion can
+         * fail. */
+        object_data_convert_curve_to_mesh(bmain, depsgraph, newob);
+        /* meshes doesn't use displist */
+        BKE_object_free_curve_cache(newob);
+      }
+      else if (target == OB_GPENCIL) {
+        if (ob->type != OB_CURVES_LEGACY) {
+          ob->flag &= ~OB_DONE;
+          BKE_report(op->reports, RPT_ERROR, "Convert Surfaces to Grease Pencil is not supported");
+        }
+        else {
+          /* Create a new grease pencil object and copy transformations.
+           * Nurbs Surface are not supported.
+           */
+          ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
+          Object *ob_gpencil = ED_gpencil_add_object(C, ob->loc, local_view_bits);
+          copy_v3_v3(ob_gpencil->rot, ob->rot);
+          copy_v3_v3(ob_gpencil->scale, ob->scale);
+          BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, ob, false, 1.0f, 0.0f);
+          gpencilConverted = true;
+        }
+      }
+    }
+    else if (ob->type == OB_MBALL && target == OB_MESH) {
+      Object *baseob;
+
+      base->flag &= ~BASE_SELECTED;
+      ob->base_flag &= ~BASE_SELECTED;
+
+      baseob = BKE_mball_basis_find(scene, ob);
+
+      if (ob != baseob) {
+        /* if motherball is converting it would be marked as done later */
+        ob->flag |= OB_DONE;
+      }
+
+      if (!(baseob->flag & OB_DONE)) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, baseob);
+        newob = basen->object;
+
+        MetaBall *mb = static_cast(newob->data);
+        id_us_min(&mb->id);
+
+        newob->data = BKE_mesh_add(bmain, "Mesh");
+        newob->type = OB_MESH;
+
+        Mesh *me = static_cast(newob->data);
+        me->totcol = mb->totcol;
+        if (newob->totcol) {
+          me->mat = static_cast(MEM_dupallocN(mb->mat));
+          for (a = 0; a < newob->totcol; a++) {
+            id_us_plus((ID *)me->mat[a]);
+          }
+        }
+
+        object_data_convert_ensure_curve_cache(depsgraph, scene, baseob);
+        BKE_mesh_from_metaball(&baseob->runtime.curve_cache->disp,
+                               static_cast(newob->data));
+
+        if (obact->type == OB_MBALL) {
+          basact = basen;
+        }
+
+        baseob->flag |= OB_DONE;
+        mballConverted = 1;
+      }
+    }
+    else if (ob->type == OB_POINTCLOUD && target == OB_MESH) {
+      ob->flag |= OB_DONE;
+
+      if (keep_original) {
+        basen = duplibase_for_convert(bmain, depsgraph, scene, view_layer, base, nullptr);
+        newob = basen->object;
+
+        /* Decrement original point cloud's usage count. */
+        PointCloud *pointcloud = static_cast(newob->data);
+        id_us_min(&pointcloud->id);
+
+        /* Make a new copy of the point cloud. */
+        newob->data = BKE_id_copy(bmain, &pointcloud->id);
+      }
+      else {
+        newob = ob;
+      }
+
+      BKE_pointcloud_to_mesh(bmain, depsgraph, scene, newob);
+
+      if (newob->type == OB_MESH) {
+        BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */
+        ED_rigidbody_object_remove(bmain, scene, newob);
+      }
+    }
+    else {
+      continue;
+    }
+
+    /* Ensure new object has consistent material data with its new obdata. */
+    if (newob) {
+      BKE_object_materials_test(bmain, newob, static_cast(newob->data));
+    }
+
+    /* tag obdata if it was been changed */
+
+    /* If the original object is active then make this object active */
+    if (basen) {
+      if (ob == obact) {
+        /* store new active base to update BASACT */
+        basact = basen;
+      }
+
+      basen = nullptr;
+    }
+
+    if (!keep_original && (ob->flag & OB_DONE)) {
+      /* NOTE: Tag transform for update because object parenting to curve with path is handled
+       * differently from all other cases. Converting curve to mesh and mesh to curve will likely
+       * affect the way children are evaluated.
+       * It is not enough to tag only geometry and rely on the curve parenting relations because
+       * this relation is lost when curve is converted to mesh. */
+      DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_TRANSFORM);
+      ((ID *)ob->data)->tag &= ~LIB_TAG_DOIT; /* flag not to convert this datablock again */
+    }
+  }
+  BLI_freelistN(&selected_editable_bases);
+
+  if (!keep_original) {
+    if (mballConverted) {
+      /* We need to remove non-basis MBalls first, otherwise we won't be able to detect them if
+       * their basis happens to be removed first. */
+      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_mball) {
+        if (ob_mball->type == OB_MBALL) {
+          Object *ob_basis = nullptr;
+          if (!BKE_mball_is_basis(ob_mball) &&
+              ((ob_basis = BKE_mball_basis_find(scene, ob_mball)) && (ob_basis->flag & OB_DONE))) {
+            ED_object_base_free_and_unlink(bmain, scene, ob_mball);
+          }
+        }
+      }
+      FOREACH_SCENE_OBJECT_END;
+      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_mball) {
+        if (ob_mball->type == OB_MBALL) {
+          if (ob_mball->flag & OB_DONE) {
+            if (BKE_mball_is_basis(ob_mball)) {
+              ED_object_base_free_and_unlink(bmain, scene, ob_mball);
+            }
+          }
+        }
+      }
+      FOREACH_SCENE_OBJECT_END;
+    }
+    /* Remove curves and meshes converted to Grease Pencil object. */
+    if (gpencilConverted) {
+      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) {
+        if (ELEM(ob_delete->type, OB_CURVES_LEGACY, OB_MESH)) {
+          if (ob_delete->flag & OB_DONE) {
+            ED_object_base_free_and_unlink(bmain, scene, ob_delete);
+          }
+        }
+      }
+      FOREACH_SCENE_OBJECT_END;
+    }
+  }
+  else {
+    /* Remove Text curves converted to Grease Pencil object to avoid duplicated curves. */
+    if (gpencilCurveConverted) {
+      FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) {
+        if (ELEM(ob_delete->type, OB_CURVES_LEGACY) && (ob_delete->flag & OB_DONE)) {
+          ED_object_base_free_and_unlink(bmain, scene, ob_delete);
+        }
+      }
+      FOREACH_SCENE_OBJECT_END;
+    }
+  }
+
+  // XXX  ED_object_editmode_enter(C, 0);
+  // XXX  exit_editmode(C, EM_FREEDATA|); /* freedata, but no undo */
+
+  if (basact) {
+    /* active base was changed */
+    ED_object_base_activate(C, basact);
+    BASACT(view_layer) = basact;
+  }
+  else if (BASACT(view_layer)->object->flag & OB_DONE) {
+    WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, BASACT(view_layer)->object);
+    WM_event_add_notifier(C, NC_OBJECT | ND_DATA, BASACT(view_layer)->object);
+  }
+
+  DEG_relations_tag_update(bmain);
+  DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
+  WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, scene);
+  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+
+  return OPERATOR_FINISHED;
+}
+
+static void object_convert_ui(bContext *UNUSED(C), wmOperator *op)
+{
+  uiLayout *layout = op->layout;
+
+  uiLayoutSetPropSep(layout, true);
+
+  uiItemR(layout, op->ptr, "target", 0, nullptr, ICON_NONE);
+  uiItemR(layout, op->ptr, "keep_original", 0, nullptr, ICON_NONE);
+
+  if (RNA_enum_get(op->ptr, "target") == OB_GPENCIL) {
+    uiItemR(layout, op->ptr, "thickness", 0, nullptr, ICON_NONE);
+    uiItemR(layout, op->ptr, "angle", 0, nullptr, ICON_NONE);
+    uiItemR(layout, op->ptr, "offset", 0, nullptr, ICON_NONE);
+    uiItemR(layout, op->ptr, "seams", 0, nullptr, ICON_NONE);
+    uiItemR(layout, op->ptr, "faces", 0, nullptr, ICON_NONE);
+  }
+}
+
+void OBJECT_OT_convert(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  /* identifiers */
+  ot->name = "Convert To";
+  ot->description = "Convert selected objects to another type";
+  ot->idname = "OBJECT_OT_convert";
+
+  /* api callbacks */
+  ot->invoke = WM_menu_invoke;
+  ot->exec = object_convert_exec;
+  ot->poll = object_convert_poll;
+  ot->ui = object_convert_ui;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* properties */
+  ot->prop = RNA_def_enum(
+      ot->srna, "target", convert_target_items, OB_MESH, "Target", "Type of object to convert to");
+  RNA_def_boolean(ot->srna,
+                  "keep_original",
+                  false,
+                  "Keep Original",
+                  "Keep original objects instead of replacing them");
+
+  prop = RNA_def_float_rotation(ot->srna,
+                                "angle",
+                                0,
+                                nullptr,
+                                DEG2RADF(0.0f),
+                                DEG2RADF(180.0f),
+                                "Threshold Angle",
+                                "Threshold to determine ends of the strokes",
+                                DEG2RADF(0.0f),
+                                DEG2RADF(180.0f));
+  RNA_def_property_float_default(prop, DEG2RADF(70.0f));
+
+  RNA_def_int(ot->srna, "thickness", 5, 1, 100, "Thickness", "", 1, 100);
+  RNA_def_boolean(ot->srna, "seams", false, "Only Seam Edges", "Convert only seam edges");
+  RNA_def_boolean(ot->srna, "faces", true, "Export Faces", "Export faces as filled strokes");
+  RNA_def_float_distance(ot->srna,
+                         "offset",
+                         0.01f,
+                         0.0,
+                         OBJECT_ADD_SIZE_MAXF,
+                         "Stroke Offset",
+                         "Offset strokes from fill",
+                         0.0,
+                         100.00);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Duplicate Object Operator
+ * \{ */
+
+/*
+ * dupflag: a flag made from constants declared in DNA_userdef_types.h
+ * The flag tells adduplicate() whether to copy data linked to the object,
+ * or to reference the existing data.
+ * U.dupflag for default operations or you can construct a flag as python does
+ * if the dupflag is 0 then no data will be copied (linked duplicate). */
+
+/* used below, assumes id.new is correct */
+/* leaves selection of base/object unaltered */
+/* Does set ID->newid pointers. */
+static Base *object_add_duplicate_internal(Main *bmain,
+                                           Scene *scene,
+                                           ViewLayer *view_layer,
+                                           Object *ob,
+                                           const eDupli_ID_Flags dupflag,
+                                           const eLibIDDuplicateFlags duplicate_options)
+{
+  Base *base, *basen = nullptr;
+  Object *obn;
+
+  if (ob->mode & OB_MODE_POSE) {
+    /* nothing? */
+  }
+  else {
+    obn = static_cast(
+        ID_NEW_SET(ob, BKE_object_duplicate(bmain, ob, dupflag, duplicate_options)));
+    DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+
+    base = BKE_view_layer_base_find(view_layer, ob);
+    if ((base != nullptr) && (base->flag & BASE_VISIBLE_DEPSGRAPH)) {
+      BKE_collection_object_add_from(bmain, scene, ob, obn);
+    }
+    else {
+      LayerCollection *layer_collection = BKE_layer_collection_get_active(view_layer);
+      BKE_collection_object_add(bmain, layer_collection->collection, obn);
+    }
+
+    basen = BKE_view_layer_base_find(view_layer, obn);
+    if (base != nullptr) {
+      basen->local_view_bits = base->local_view_bits;
+    }
+
+    /* 1) duplis should end up in same collection as the original
+     * 2) Rigid Body sim participants MUST always be part of a collection...
+     */
+    /* XXX: is 2) really a good measure here? */
+    if (ob->rigidbody_object || ob->rigidbody_constraint) {
+      LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
+        if (BKE_collection_has_object(collection, ob)) {
+          BKE_collection_object_add(bmain, collection, obn);
+        }
+      }
+    }
+  }
+  return basen;
+}
+
+Base *ED_object_add_duplicate(
+    Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base, const eDupli_ID_Flags dupflag)
+{
+  Base *basen;
+  Object *ob;
+
+  basen = object_add_duplicate_internal(bmain,
+                                        scene,
+                                        view_layer,
+                                        base->object,
+                                        dupflag,
+                                        LIB_ID_DUPLICATE_IS_SUBPROCESS |
+                                            LIB_ID_DUPLICATE_IS_ROOT_ID);
+  if (basen == nullptr) {
+    return nullptr;
+  }
+
+  ob = basen->object;
+
+  /* Link own references to the newly duplicated data T26816.
+   * Note that this function can be called from edit-mode code, in which case we may have to
+   * enforce remapping obdata (by default this is forbidden in edit mode). */
+  const int remap_flag = BKE_object_is_in_editmode(ob) ? ID_REMAP_FORCE_OBDATA_IN_EDITMODE : 0;
+  BKE_libblock_relink_to_newid(bmain, &ob->id, remap_flag);
+
+  /* DAG_relations_tag_update(bmain); */ /* caller must do */
+
+  if (ob->data != nullptr) {
+    DEG_id_tag_update_ex(bmain, (ID *)ob->data, ID_RECALC_EDITORS);
+  }
+
+  BKE_main_id_newptr_and_tag_clear(bmain);
+
+  return basen;
+}
+
+/* contextual operator dupli */
+static int duplicate_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  const bool linked = RNA_boolean_get(op->ptr, "linked");
+  const eDupli_ID_Flags dupflag = (linked) ? (eDupli_ID_Flags)0 : (eDupli_ID_Flags)U.dupflag;
+
+  /* We need to handle that here ourselves, because we may duplicate several objects, in which case
+   * we also want to remap pointers between those... */
+  BKE_main_id_newptr_and_tag_clear(bmain);
+
+  CTX_DATA_BEGIN (C, Base *, base, selected_bases) {
+    Base *basen = object_add_duplicate_internal(bmain,
+                                                scene,
+                                                view_layer,
+                                                base->object,
+                                                dupflag,
+                                                LIB_ID_DUPLICATE_IS_SUBPROCESS |
+                                                    LIB_ID_DUPLICATE_IS_ROOT_ID);
+
+    /* note that this is safe to do with this context iterator,
+     * the list is made in advance */
+    ED_object_base_select(base, BA_DESELECT);
+    ED_object_base_select(basen, BA_SELECT);
+
+    if (basen == nullptr) {
+      continue;
+    }
+
+    /* new object becomes active */
+    if (BASACT(view_layer) == base) {
+      ED_object_base_activate(C, basen);
+    }
+
+    if (basen->object->data) {
+      DEG_id_tag_update(static_cast(basen->object->data), 0);
+    }
+  }
+  CTX_DATA_END;
+
+  /* Note that this will also clear newid pointers and tags. */
+  copy_object_set_idnew(C);
+
+  ED_outliner_select_sync_from_object_tag(C);
+
+  DEG_relations_tag_update(bmain);
+  DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT);
+
+  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_duplicate(wmOperatorType *ot)
+{
+  PropertyRNA *prop;
+
+  /* identifiers */
+  ot->name = "Duplicate Objects";
+  ot->description = "Duplicate selected objects";
+  ot->idname = "OBJECT_OT_duplicate";
+
+  /* api callbacks */
+  ot->exec = duplicate_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  /* to give to transform */
+  prop = RNA_def_boolean(ot->srna,
+                         "linked",
+                         false,
+                         "Linked",
+                         "Duplicate object but not object data, linking to the original data");
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+  prop = RNA_def_enum(
+      ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", "");
+  RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Add Named Object Operator
+ *
+ * Use for drag & drop.
+ * \{ */
+
+static int object_add_named_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  Base *basen;
+  Object *ob;
+  const bool linked = RNA_boolean_get(op->ptr, "linked");
+  const eDupli_ID_Flags dupflag = (linked) ? (eDupli_ID_Flags)0 : (eDupli_ID_Flags)U.dupflag;
+  char name[MAX_ID_NAME - 2];
+
+  /* find object, create fake base */
+  RNA_string_get(op->ptr, "name", name);
+  ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name);
+
+  if (ob == nullptr) {
+    BKE_report(op->reports, RPT_ERROR, "Object not found");
+    return OPERATOR_CANCELLED;
+  }
+
+  /* prepare dupli */
+  basen = object_add_duplicate_internal(
+      bmain,
+      scene,
+      view_layer,
+      ob,
+      dupflag,
+      /* Sub-process flag because the new-ID remapping (#BKE_libblock_relink_to_newid()) in this
+       * function will only work if the object is already linked in the view layer, which is not
+       * the case here. So we have to do the new-ID relinking ourselves
+       * (#copy_object_set_idnew()).
+       */
+      LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID);
+
+  if (basen == nullptr) {
+    BKE_report(op->reports, RPT_ERROR, "Object could not be duplicated");
+    return OPERATOR_CANCELLED;
+  }
+
+  basen->object->visibility_flag &= ~OB_HIDE_VIEWPORT;
+  /* Do immediately, as #copy_object_set_idnew() below operates on visible objects. */
+  BKE_base_eval_flags(basen);
+
+  /* object_add_duplicate_internal() doesn't deselect other objects, unlike object_add_common() or
+   * BKE_view_layer_base_deselect_all(). */
+  ED_object_base_deselect_all(view_layer, nullptr, SEL_DESELECT);
+  ED_object_base_select(basen, BA_SELECT);
+  ED_object_base_activate(C, basen);
+
+  copy_object_set_idnew(C);
+
+  /* TODO(sergey): Only update relations for the current scene. */
+  DEG_relations_tag_update(bmain);
+
+  DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
+  WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+  WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
+  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+  ED_outliner_select_sync_from_object_tag(C);
+
+  PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
+  if (RNA_property_is_set(op->ptr, prop_matrix)) {
+    Object *ob_add = basen->object;
+    RNA_property_float_get_array(op->ptr, prop_matrix, &ob_add->obmat[0][0]);
+    BKE_object_apply_mat4(ob_add, ob_add->obmat, true, true);
+
+    DEG_id_tag_update(&ob_add->id, ID_RECALC_TRANSFORM);
+  }
+  else {
+    int mval[2];
+    if (object_add_drop_xy_get(C, op, &mval)) {
+      ED_object_location_from_view(C, basen->object->loc);
+      ED_view3d_cursor3d_position(C, mval, false, basen->object->loc);
+    }
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_add_named(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add Object";
+  ot->description = "Add named object";
+  ot->idname = "OBJECT_OT_add_named";
+
+  /* api callbacks */
+  ot->invoke = object_add_drop_xy_generic_invoke;
+  ot->exec = object_add_named_exec;
+  ot->poll = ED_operator_objectmode_poll_msg;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  PropertyRNA *prop;
+  RNA_def_boolean(ot->srna,
+                  "linked",
+                  false,
+                  "Linked",
+                  "Duplicate object but not object data, linking to the original data");
+
+  RNA_def_string(ot->srna, "name", nullptr, MAX_ID_NAME - 2, "Name", "Object name to add");
+
+  prop = RNA_def_float_matrix(
+      ot->srna, "matrix", 4, 4, nullptr, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+
+  object_add_drop_xy_props(ot);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Transform Object to Mouse Operator
+ * \{ */
+
+/**
+ * Alternate behavior for dropping an asset that positions the appended object(s).
+ */
+static int object_transform_to_mouse_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  Object *ob;
+
+  if (RNA_struct_property_is_set(op->ptr, "name")) {
+    char name[MAX_ID_NAME - 2];
+    RNA_string_get(op->ptr, "name", name);
+    ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name);
+  }
+  else {
+    ob = OBACT(view_layer);
+  }
+
+  if (ob == nullptr) {
+    BKE_report(op->reports, RPT_ERROR, "Object not found");
+    return OPERATOR_CANCELLED;
+  }
+
+  /* Don't transform a linked object. There's just nothing to do here in this case, so return
+   * #OPERATOR_FINISHED. */
+  if (ID_IS_LINKED(ob)) {
+    return OPERATOR_FINISHED;
+  }
+
+  /* Ensure the locations are updated so snap reads the evaluated active location. */
+  CTX_data_ensure_evaluated_depsgraph(C);
+
+  PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
+  if (RNA_property_is_set(op->ptr, prop_matrix)) {
+    ObjectsInViewLayerParams params = {0};
+    uint objects_len;
+    Object **objects = BKE_view_layer_array_selected_objects_params(
+        view_layer, nullptr, &objects_len, ¶ms);
+
+    float matrix[4][4];
+    RNA_property_float_get_array(op->ptr, prop_matrix, &matrix[0][0]);
+
+    float mat_src_unit[4][4];
+    float mat_dst_unit[4][4];
+    float final_delta[4][4];
+
+    normalize_m4_m4(mat_src_unit, ob->obmat);
+    normalize_m4_m4(mat_dst_unit, matrix);
+    invert_m4(mat_src_unit);
+    mul_m4_m4m4(final_delta, mat_dst_unit, mat_src_unit);
+
+    ED_object_xform_array_m4(objects, objects_len, final_delta);
+
+    MEM_freeN(objects);
+  }
+  else {
+    int mval[2];
+    if (object_add_drop_xy_get(C, op, &mval)) {
+      float cursor[3];
+      ED_object_location_from_view(C, cursor);
+      ED_view3d_cursor3d_position(C, mval, false, cursor);
+
+      /* Use the active objects location since this is the ID which the user selected to drop.
+       *
+       * This transforms all selected objects, so that dropping a single object which links in
+       * other objects will have their relative transformation preserved.
+       * For example a child/parent relationship or other objects used with a boolean modifier.
+       *
+       * The caller is responsible for ensuring the selection state gives useful results.
+       * Link/append does this using #FILE_AUTOSELECT. */
+      ED_view3d_snap_selected_to_location(C, cursor, V3D_AROUND_ACTIVE);
+    }
+  }
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_transform_to_mouse(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Place Object Under Mouse";
+  ot->description = "Snap selected item(s) to the mouse location";
+  ot->idname = "OBJECT_OT_transform_to_mouse";
+
+  /* api callbacks */
+  ot->invoke = object_add_drop_xy_generic_invoke;
+  ot->exec = object_transform_to_mouse_exec;
+  ot->poll = ED_operator_objectmode;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  PropertyRNA *prop;
+  RNA_def_string(ot->srna,
+                 "name",
+                 nullptr,
+                 MAX_ID_NAME - 2,
+                 "Name",
+                 "Object name to place (when unset use the active object)");
+
+  prop = RNA_def_float_matrix(
+      ot->srna, "matrix", 4, 4, nullptr, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
+  RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+
+  object_add_drop_xy_props(ot);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Join Object Operator
+ * \{ */
+
+static bool object_join_poll(bContext *C)
+{
+  Object *ob = CTX_data_active_object(C);
+
+  if (ob == nullptr || ob->data == nullptr || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob) ||
+      ID_IS_OVERRIDE_LIBRARY(ob->data)) {
+    return false;
+  }
+
+  if (ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, OB_SURF, OB_ARMATURE, OB_GPENCIL)) {
+    return ED_operator_screenactive(C);
+  }
+  return false;
+}
+
+static int object_join_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Object *ob = CTX_data_active_object(C);
+
+  if (ob->mode & OB_MODE_EDIT) {
+    BKE_report(op->reports, RPT_ERROR, "This data does not support joining in edit mode");
+    return OPERATOR_CANCELLED;
+  }
+  if (BKE_object_obdata_is_libdata(ob)) {
+    BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
+    return OPERATOR_CANCELLED;
+  }
+  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
+    BKE_reportf(op->reports,
+                RPT_WARNING,
+                "Cannot edit object '%s' as it is used by override collections",
+                ob->id.name + 2);
+    return OPERATOR_CANCELLED;
+  }
+
+  if (ob->type == OB_GPENCIL) {
+    bGPdata *gpd = (bGPdata *)ob->data;
+    if ((!gpd) || GPENCIL_ANY_MODE(gpd)) {
+      BKE_report(op->reports, RPT_ERROR, "This data does not support joining in this mode");
+      return OPERATOR_CANCELLED;
+    }
+  }
+
+  int ret = OPERATOR_CANCELLED;
+  if (ob->type == OB_MESH) {
+    ret = ED_mesh_join_objects_exec(C, op);
+  }
+  else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) {
+    ret = ED_curve_join_objects_exec(C, op);
+  }
+  else if (ob->type == OB_ARMATURE) {
+    ret = ED_armature_join_objects_exec(C, op);
+  }
+  else if (ob->type == OB_GPENCIL) {
+    ret = ED_gpencil_join_objects_exec(C, op);
+  }
+
+  if (ret & OPERATOR_FINISHED) {
+    /* Even though internally failure to invert is accounted for with a fallback,
+     * show a warning since the result may not be what the user expects. See T80077.
+     *
+     * Failure to invert the matrix is typically caused by zero scaled axes
+     * (which can be caused by constraints, even if the input scale isn't zero).
+     *
+     * Internally the join functions use #invert_m4_m4_safe_ortho which creates
+     * an inevitable matrix from one that has one or more degenerate axes.
+     *
+     * In most cases we don't worry about special handling for non-inevitable matrices however for
+     * joining objects there may be flat 2D objects where it's not obvious the scale is zero.
+     * In this case, using #invert_m4_m4_safe_ortho works as well as we can expect,
+     * joining the contents, flattening on the axis that's zero scaled.
+     * If the zero scale is removed, the data on this axis remains un-scaled
+     * (something that wouldn't work for #invert_m4_m4_safe). */
+    float imat_test[4][4];
+    if (!invert_m4_m4(imat_test, ob->obmat)) {
+      BKE_report(op->reports,
+                 RPT_WARNING,
+                 "Active object final transform has one or more zero scaled axes");
+    }
+  }
+
+  return ret;
+}
+
+void OBJECT_OT_join(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Join";
+  ot->description = "Join selected objects into active object";
+  ot->idname = "OBJECT_OT_join";
+
+  /* api callbacks */
+  ot->exec = object_join_exec;
+  ot->poll = object_join_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Join as Shape Key Operator
+ * \{ */
+
+static bool join_shapes_poll(bContext *C)
+{
+  Object *ob = CTX_data_active_object(C);
+
+  if (ob == nullptr || ob->data == nullptr || ID_IS_LINKED(ob) || ID_IS_OVERRIDE_LIBRARY(ob) ||
+      ID_IS_OVERRIDE_LIBRARY(ob->data)) {
+    return false;
+  }
+
+  /* only meshes supported at the moment */
+  if (ob->type == OB_MESH) {
+    return ED_operator_screenactive(C);
+  }
+  return false;
+}
+
+static int join_shapes_exec(bContext *C, wmOperator *op)
+{
+  Main *bmain = CTX_data_main(C);
+  Object *ob = CTX_data_active_object(C);
+
+  if (ob->mode & OB_MODE_EDIT) {
+    BKE_report(op->reports, RPT_ERROR, "This data does not support joining in edit mode");
+    return OPERATOR_CANCELLED;
+  }
+  if (BKE_object_obdata_is_libdata(ob)) {
+    BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
+    return OPERATOR_CANCELLED;
+  }
+  if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
+    BKE_reportf(op->reports,
+                RPT_WARNING,
+                "Cannot edit object '%s' as it is used by override collections",
+                ob->id.name + 2);
+    return OPERATOR_CANCELLED;
+  }
+
+  if (ob->type == OB_MESH) {
+    return ED_mesh_shapes_join_objects_exec(C, op);
+  }
+
+  return OPERATOR_CANCELLED;
+}
+
+void OBJECT_OT_join_shapes(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Join as Shapes";
+  ot->description = "Copy the current resulting shape of another selected object to this one";
+  ot->idname = "OBJECT_OT_join_shapes";
+
+  /* api callbacks */
+  ot->exec = join_shapes_exec;
+  ot->poll = join_shapes_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/** \} */
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index d88dc98318a..135c76140c1 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -96,7 +96,7 @@ void OBJECT_OT_select_more(struct wmOperatorType *ot);
 void OBJECT_OT_select_less(struct wmOperatorType *ot);
 void OBJECT_OT_select_same_collection(struct wmOperatorType *ot);
 
-/* object_add.c */
+/* object_add.cc */
 
 void OBJECT_OT_add(struct wmOperatorType *ot);
 void OBJECT_OT_add_named(struct wmOperatorType *ot);
-- 
cgit v1.2.3


From 6594e802ab94ff1124d9157deb0ca760981e3f34 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 17:20:37 -0500
Subject: Curves: Add method to access cyclic attribute

Avoids the need to use the attribute API to access this commonly
used builtin attribute.
---
 source/blender/blenkernel/BKE_curves.hh             |  3 +++
 source/blender/blenkernel/intern/curves_geometry.cc | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 209f892c651..6fa7de49eb0 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -119,6 +119,9 @@ class CurvesGeometry : public ::CurvesGeometry {
   Span offsets() const;
   MutableSpan offsets();
 
+  VArray cyclic() const;
+  MutableSpan cyclic();
+
   /* --------------------------------------------------------------------
    * Operations.
    */
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index 68797942b56..3eea579230a 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -18,6 +18,7 @@ namespace blender::bke {
 static const std::string ATTR_POSITION = "position";
 static const std::string ATTR_RADIUS = "radius";
 static const std::string ATTR_CURVE_TYPE = "curve_type";
+static const std::string ATTR_CYCLIC = "cyclic";
 
 /* -------------------------------------------------------------------- */
 /** \name Constructors/Destructor
@@ -168,6 +169,23 @@ Span CurvesGeometry::offsets() const
   return {this->curve_offsets, this->curve_size + 1};
 }
 
+VArray CurvesGeometry::cyclic() const
+{
+  const bool *data = (const bool *)CustomData_get_layer_named(
+      &this->curve_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str());
+  if (data != nullptr) {
+    return VArray::ForSpan(Span(data, this->curve_size));
+  }
+  return VArray::ForSingle(false, this->curve_size);
+}
+
+MutableSpan CurvesGeometry::cyclic()
+{
+  bool *data = (bool *)CustomData_add_layer_named(
+      &this->curve_data, CD_PROP_BOOL, CD_CALLOC, nullptr, this->curve_size, ATTR_CYCLIC.c_str());
+  return {data, this->curve_size};
+}
+
 void CurvesGeometry::resize(const int point_size, const int curve_size)
 {
   if (point_size != this->point_size) {
-- 
cgit v1.2.3


From 9ebb65323e57eda953e8a6bd03775318ab7ccb05 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 17:27:38 -0500
Subject: Fix: Use correct default in Curves to CurveEval conversion

Currently the code expects the radius attribuet to always exist on the
input Curves. This won't be true in the future though, so the correct
default value of one should be used when creating the data on CurveEval,
where the data is not optional.
---
 source/blender/blenkernel/intern/curve_eval.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 78dafe34b4f..d6525a11cff 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -432,7 +432,7 @@ std::unique_ptr curves_to_curve_eval(const Curves &curves)
     }
     spline->positions().fill(float3(0));
     spline->tilts().fill(0.0f);
-    spline->radii().fill(0.0f);
+    spline->radii().fill(1.0f);
     curve_eval->add_spline(std::move(spline));
   }
 
-- 
cgit v1.2.3


From 94a4dddb1b64ec0912b7b2ac5b2264e86cd3d955 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 28 Feb 2022 17:28:54 -0500
Subject: Fix: Don't tag curves component cache dirty for radius

Changing the radius does not invalidate a cache on
`CurvesGeometry`.
---
 source/blender/blenkernel/intern/geometry_component_curves.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc
index e32dd852e0e..5723d110aa0 100644
--- a/source/blender/blenkernel/intern/geometry_component_curves.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curves.cc
@@ -357,7 +357,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
                                                point_access,
                                                make_array_read_attribute,
                                                make_array_write_attribute,
-                                               tag_component_normals_changed);
+                                               nullptr);
 
   static BuiltinCustomDataLayerProvider id("id",
                                            ATTR_DOMAIN_POINT,
-- 
cgit v1.2.3


From 5777bd719b01f56e52d618a3260bd4cdf223d5cd Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 09:09:24 +1100
Subject: Cleanup: use doxygen comments, correct spelling

Also move eDupli_ID_Flags doc-string to it's declaration.
---
 intern/ghost/intern/GHOST_SystemWin32.cpp          |  2 +-
 .../blenkernel/intern/blendfile_link_append.c      |  8 ++--
 source/blender/blenkernel/intern/mesh_validate.cc  |  2 +-
 source/blender/blenkernel/intern/subdiv_modifier.c |  2 +-
 source/blender/editors/include/ED_anim_api.h       |  8 ++--
 .../blender/editors/include/ED_keyframes_keylist.h | 10 ++--
 source/blender/editors/interface/interface.c       |  6 +--
 .../blender/editors/interface/interface_widgets.c  |  2 +-
 source/blender/editors/mesh/editmesh_tools.c       | 10 ++--
 source/blender/editors/object/object_add.cc        | 23 ++++-----
 source/blender/editors/object/object_edit.c        |  2 -
 source/blender/editors/space_file/filelist.c       |  2 +-
 source/blender/makesdna/DNA_userdef_enums.h        |  9 +++-
 .../makesrna/intern/rna_access_compare_override.c  |  2 +-
 .../geometry/nodes/node_geo_duplicate_elements.cc  | 56 ++++++++++++++--------
 15 files changed, 81 insertions(+), 63 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index fa93324e0b3..e588c7485b4 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -654,7 +654,7 @@ GHOST_TKey GHOST_SystemWin32::processSpecialKey(short vKey, short scanCode) cons
       }
       else if (vKey == VK_OEM_8) {
         if (PRIMARYLANGID(m_langId) == LANG_FRENCH) {
-          /* oem key; used purely for shortcuts. */
+          /* OEM key; used purely for shortcuts. */
           key = GHOST_kKeyF13;
         }
       }
diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c
index 862266b696c..ce36bfe81be 100644
--- a/source/blender/blenkernel/intern/blendfile_link_append.c
+++ b/source/blender/blenkernel/intern/blendfile_link_append.c
@@ -930,9 +930,9 @@ static int foreach_libblock_link_append_callback(LibraryIDLinkCallbackData *cb_d
      * shape-key referencing the shape-key itself).
      * NOTE: in case both IDs (owner and 'used' ones) are non-linkable, we can assume we can break
      * the dependency here. Indeed, either they are both linked in another way (through their own
-     * meshes for shape keys e.g.), or this is an unsupported case (two shapekeys depending on
-     * each-other need to be also 'linked' in by their respective meshes, independant shapekeys are
-     * not allowed). ref T96048. */
+     * meshes for shape keys e.g.), or this is an unsupported case (two shape-keys depending on
+     * each-other need to be also 'linked' in by their respective meshes, independent shape-keys
+     * are not allowed). ref T96048. */
     if (id != cb_data->id_self && BKE_idtype_idcode_is_linkable(GS(cb_data->id_self->name))) {
       BKE_library_foreach_ID_link(
           cb_data->bmain, id, foreach_libblock_link_append_callback, data, IDWALK_NOP);
@@ -1454,7 +1454,7 @@ void BKE_blendfile_library_relocate(BlendfileLinkAppendContext *lapp_context,
         BlendfileLinkAppendContextItem *item;
 
         /* We remove it from current Main, and add it to items to link... */
-        /* Note that non-linkable IDs (like e.g. shapekeys) are also explicitly linked here... */
+        /* Note that non-linkable IDs (like e.g. shape-keys) are also explicitly linked here... */
         BLI_remlink(lbarray[lba_idx], id);
         /* Usual special code for ShapeKeys snowflakes... */
         Key *old_key = BKE_key_from_id(id);
diff --git a/source/blender/blenkernel/intern/mesh_validate.cc b/source/blender/blenkernel/intern/mesh_validate.cc
index cc7404e2a11..fb526354305 100644
--- a/source/blender/blenkernel/intern/mesh_validate.cc
+++ b/source/blender/blenkernel/intern/mesh_validate.cc
@@ -1437,7 +1437,7 @@ static void mesh_calc_edges_mdata(MVert *UNUSED(allvert),
       med++;
     }
     else {
-      /* equal edge, we merge the drawflag */
+      /* Equal edge, merge the draw-flag. */
       (ed + 1)->is_draw |= ed->is_draw;
     }
   }
diff --git a/source/blender/blenkernel/intern/subdiv_modifier.c b/source/blender/blenkernel/intern/subdiv_modifier.c
index 95a5946d7d3..34dfdaf7595 100644
--- a/source/blender/blenkernel/intern/subdiv_modifier.c
+++ b/source/blender/blenkernel/intern/subdiv_modifier.c
@@ -89,7 +89,7 @@ bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const SubsurfMod
                                                                 const Mesh *mesh)
 {
   if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) {
-    /* GPU subdivision is explicitely disabled, so we don't force it. */
+    /* GPU subdivision is explicitly disabled, so we don't force it. */
     return false;
   }
 
diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h
index 3e045c9c43a..4b6f5e4cac6 100644
--- a/source/blender/editors/include/ED_anim_api.h
+++ b/source/blender/editors/include/ED_anim_api.h
@@ -1090,11 +1090,11 @@ void animviz_calc_motionpaths(struct Depsgraph *depsgraph,
                               bool restore);
 
 /**
- * Update motion path computation range (in ob.avs or armature.avs) from user choice in
- *  ob.avs.path_range or arm.avs.path_range, depending on active user mode.
+ * Update motion path computation range (in `ob.avs` or `armature.avs`) from user choice in
+ *  `ob.avs.path_range` or `arm.avs.path_range`, depending on active user mode.
  *
- * @param ob: Object to compute range for (must be provided)
- * @param scene: Used when scene range is choosen
+ * \param ob: Object to compute range for (must be provided).
+ * \param scene: Used when scene range is chosen.
  */
 void animviz_motionpath_compute_range(struct Object *ob, struct Scene *scene);
 
diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h
index 39f050c9739..fd3d35e1df7 100644
--- a/source/blender/editors/include/ED_keyframes_keylist.h
+++ b/source/blender/editors/include/ED_keyframes_keylist.h
@@ -132,8 +132,10 @@ const struct ActKeyColumn *ED_keylist_find_any_between(const struct AnimKeylist
 bool ED_keylist_is_empty(const struct AnimKeylist *keylist);
 const struct ListBase /* ActKeyColumn */ *ED_keylist_listbase(const struct AnimKeylist *keylist);
 bool ED_keylist_all_keys_frame_range(const struct AnimKeylist *keylist, Range2f *r_frame_range);
-/* Return the selected keyframe's range. If none are selected, return False and does not affect
- * the frame range. */
+/**
+ * Return the selected key-frame's range.
+ * \return False If none are selected and does not affect the frame range.
+ */
 bool ED_keylist_selected_keys_frame_range(const struct AnimKeylist *keylist,
                                           Range2f *r_frame_range);
 const ActKeyColumn *ED_keylist_array(const struct AnimKeylist *keylist);
@@ -187,10 +189,10 @@ void mask_to_keylist(struct bDopeSheet *ads,
 
 /* ActKeyColumn API ---------------- */
 
-/* Checks if ActKeyColumn has any block data */
+/** Checks if #ActKeyColumn has any block data. */
 bool actkeyblock_is_valid(const ActKeyColumn *ac);
 
-/* Checks if ActKeyColumn can be used as a block (i.e. drawn/used to detect "holds") */
+/** Checks if #ActKeyColumn can be used as a block (i.e. drawn/used to detect "holds"). */
 int actkeyblock_get_valid_hold(const ActKeyColumn *ac);
 
 #ifdef __cplusplus
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index a9a68be2d48..bbd2a64bab4 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -3087,11 +3087,11 @@ bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
         PointerRNA rptr;
 
         /* This is kind of hackish, in theory think we could only ever use the second member of
-         * this if/else, since ui_searchbox_apply() is supposed to always set that pointer when
+         * this if/else, since #ui_searchbox_apply() is supposed to always set that pointer when
          * we are storing pointers... But keeping str search first for now,
          * to try to break as little as possible existing code. All this is band-aids anyway.
-         * Fact remains, using editstr as main 'reference' over whole search button thingy
-         * is utterly weak and should be redesigned imho, but that's not a simple task. */
+         * Fact remains, using `editstr` as main 'reference' over whole search button thingy
+         * is utterly weak and should be redesigned IMHO, but that's not a simple task. */
         if (search_but && search_but->rnasearchprop &&
             RNA_property_collection_lookup_string(
                 &search_but->rnasearchpoin, search_but->rnasearchprop, str, &rptr)) {
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index 5638a98b33b..d1f3843c643 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -4293,7 +4293,7 @@ static void widget_tab(
   const bool is_active = (state & UI_SELECT);
 
   /* Draw shaded outline - Disabled for now,
-   * seems incorrect and also looks nicer without it imho ;) */
+   * seems incorrect and also looks nicer without it IMHO ;). */
   // #define USE_TAB_SHADED_HIGHLIGHT
 
   uchar theme_col_tab_highlight[3];
diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c
index 9757dd1367f..2577218d6a9 100644
--- a/source/blender/editors/mesh/editmesh_tools.c
+++ b/source/blender/editors/mesh/editmesh_tools.c
@@ -9641,13 +9641,13 @@ static int edbm_smooth_normals_exec(bContext *C, wmOperator *op)
     float(*smooth_normal)[3] = MEM_callocN(sizeof(*smooth_normal) * lnors_ed_arr->totloop,
                                            __func__);
 
-    /* This is weird choice of operation, taking all loops of faces of current vertex.
-     * Could lead to some rather far away loops weighting as much as very close ones
+    /* NOTE(@mont29): This is weird choice of operation, taking all loops of faces of current
+     * vertex. Could lead to some rather far away loops weighting as much as very close ones
      * (topologically speaking), with complex polygons.
      * Using topological distance here (rather than geometrical one)
-     * makes sense imho, but would rather go with a more consistent and flexible code,
-     * we could even add max topological distance to take into account, * and a weighting curve.
-     * Would do that later though, think for now we can live with that choice. --mont29. */
+     * makes sense IMHO, but would rather go with a more consistent and flexible code,
+     * we could even add max topological distance to take into account, and a weighting curve.
+     * Would do that later though, think for now we can live with that choice. */
     BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
     for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
       l = lnor_ed->loop;
diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc
index ef11337254c..19db09961e8 100644
--- a/source/blender/editors/object/object_add.cc
+++ b/source/blender/editors/object/object_add.cc
@@ -2573,7 +2573,7 @@ static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene *
      * (all its caches have been nuked then).
      */
     if (ELEM(ob->type, OB_SURF, OB_CURVES_LEGACY, OB_FONT)) {
-      /* We need 'for render' ON here, to enable computing bevel dipslist if needed.
+      /* We need 'for render' ON here, to enable computing bevel #DispList if needed.
        * Also makes sense anyway, we would not want e.g. to lose hidden parts etc. */
       BKE_displist_make_curveTypes(depsgraph, scene, ob, true);
     }
@@ -2650,8 +2650,8 @@ static Base *duplibase_for_convert(
 
   /* XXX: An ugly hack needed because if we re-run depsgraph with some new meta-ball objects
    * having same 'family name' as orig ones, they will affect end result of meta-ball computation.
-   * For until we get rid of that name-based thingy in MBalls, that should do the trick
-   * (this is weak, but other solution (to change name of `obn`) is even worse imho).
+   * For until we get rid of that name-based thingy in meta-balls, that should do the trick
+   * (this is weak, but other solution (to change name of `obn`) is even worse IMHO).
    * See T65996. */
   const bool is_meta_ball = (obn->type == OB_MBALL);
   void *obdata = obn->data;
@@ -3204,7 +3204,7 @@ static int object_convert_exec(bContext *C, wmOperator *op)
   }
 
   // XXX  ED_object_editmode_enter(C, 0);
-  // XXX  exit_editmode(C, EM_FREEDATA|); /* freedata, but no undo */
+  // XXX  exit_editmode(C, EM_FREEDATA|); /* free data, but no undo */
 
   if (basact) {
     /* active base was changed */
@@ -3302,16 +3302,11 @@ void OBJECT_OT_convert(wmOperatorType *ot)
 /** \name Duplicate Object Operator
  * \{ */
 
-/*
- * dupflag: a flag made from constants declared in DNA_userdef_types.h
- * The flag tells adduplicate() whether to copy data linked to the object,
- * or to reference the existing data.
- * U.dupflag for default operations or you can construct a flag as python does
- * if the dupflag is 0 then no data will be copied (linked duplicate). */
-
-/* used below, assumes id.new is correct */
-/* leaves selection of base/object unaltered */
-/* Does set ID->newid pointers. */
+/**
+ * - Assumes `id.new` is correct.
+ * - Leaves selection of base/object unaltered.
+ * - Sets #ID.newid pointers.
+ */
 static Base *object_add_duplicate_internal(Main *bmain,
                                            Scene *scene,
                                            ViewLayer *view_layer,
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 96970c3e208..82b14787d9b 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -1460,8 +1460,6 @@ void OBJECT_OT_paths_clear(wmOperatorType *ot)
 
 /** \} */
 
-/** \} */
-
 /* -------------------------------------------------------------------- */
 /** \name Object Shade Smooth/Flat Operator
  * \{ */
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c
index 3b3d968aed6..daa4b53803f 100644
--- a/source/blender/editors/space_file/filelist.c
+++ b/source/blender/editors/space_file/filelist.c
@@ -2245,7 +2245,7 @@ FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const
   cache->misc_entries_indices[cache->misc_cursor] = index;
   cache->misc_cursor = (cache->misc_cursor + 1) % cache_size;
 
-#if 0 /* Actually no, only block cached entries should have preview imho. */
+#if 0 /* Actually no, only block cached entries should have preview IMHO. */
   if (cache->previews_pool) {
     filelist_cache_previews_push(filelist, ret, index);
   }
diff --git a/source/blender/makesdna/DNA_userdef_enums.h b/source/blender/makesdna/DNA_userdef_enums.h
index bb061e73c9c..e90aa0e0f07 100644
--- a/source/blender/makesdna/DNA_userdef_enums.h
+++ b/source/blender/makesdna/DNA_userdef_enums.h
@@ -10,7 +10,14 @@
 extern "C" {
 #endif
 
-/** #UserDef.dupflag */
+/**
+ * #UserDef.dupflag
+ *
+ * The flag tells #BKE_object_duplicate() whether to copy data linked to the object,
+ * or to reference the existing data.
+ * #U.dupflag should be used for default operations or you can construct a flag as Python does.
+ * If #eDupli_ID_Flags is 0 then no data will be copied (linked duplicate).
+ */
 typedef enum eDupli_ID_Flags {
   USER_DUP_MESH = (1 << 0),
   USER_DUP_CURVE = (1 << 1),
diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c
index 1aa51c93ee8..fd65cc464f2 100644
--- a/source/blender/makesrna/intern/rna_access_compare_override.c
+++ b/source/blender/makesrna/intern/rna_access_compare_override.c
@@ -218,7 +218,7 @@ bool RNA_property_copy(
 
   /* IDprops: destination may not exist, if source does and is set, try to create it. */
   /* NOTE: this is sort of quick hack/bandage to fix the issue,
-   * we need to rethink how IDProps are handled in 'diff' RNA code completely, imho... */
+   * we need to rethink how IDProps are handled in 'diff' RNA code completely, IMHO. */
   if (prop_src != NULL && prop_dst == NULL && RNA_property_is_set(fromptr, prop)) {
     BLI_assert(prop_src->magic != RNA_MAGIC);
     IDProperty *idp_dst = RNA_struct_idprops(ptr, true);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index c58bab239e6..1ceab18c01b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -57,9 +57,9 @@ struct IndexAttributes {
   StrongAnonymousAttributeID duplicate_index;
 };
 
-/* --------------------------------------------------------------------
- * Attribute Copy/Creation Functions.
- */
+/* -------------------------------------------------------------------- */
+/** \name Attribute Copy/Creation Functions
+ * \{ */
 
 static void gather_attributes_without_id(const GeometrySet &geometry_set,
                                          const GeometryComponentType component_type,
@@ -131,7 +131,7 @@ static void threaded_id_offset_copy(const Span offsets,
   });
 }
 
-/* Create the copy indices for the duplication domain. */
+/** Create the copy indices for the duplication domain. */
 static void create_duplicate_index_attribute(GeometryComponent &component,
                                              const AttributeDomain output_domain,
                                              const IndexMask selection,
@@ -151,8 +151,10 @@ static void create_duplicate_index_attribute(GeometryComponent &component,
   copy_attribute.save();
 }
 
-/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
- * and the duplicate number. This function is used for the point domain elements. */
+/**
+ * Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for the point domain elements.
+ */
 static void copy_stable_id_point(const Span offsets,
                                  const GeometryComponent &src_component,
                                  GeometryComponent &dst_component)
@@ -173,7 +175,8 @@ static void copy_stable_id_point(const Span offsets,
   dst_attribute.save();
 }
 
-/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+/**
+ * Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
  * and the duplicate number. This function is used for points when duplicating the edge domain.
  */
 static void copy_stable_id_edges(const Mesh &mesh,
@@ -216,11 +219,12 @@ static void copy_stable_id_edges(const Mesh &mesh,
   dst_attribute.save();
 }
 
-/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+/**
+ * Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
  * and the duplicate number. This function is used for points when duplicating the face domain.
  *
  * This function could be threaded in the future, but since it is only 1 attribute and the
- * face->edge->vert mapping would mean creating a 1/1 mapping to allow for it, is it worth it?
+ * `face->edge->vert` mapping would mean creating a 1/1 mapping to allow for it, is it worth it?
  */
 static void copy_stable_id_faces(const Mesh &mesh,
                                  const IndexMask selection,
@@ -266,10 +270,12 @@ static void copy_stable_id_faces(const Mesh &mesh,
   dst_attribute.save();
 }
 
-/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+/**
+ * Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
  * and the duplicate number. In the spline case, copy the entire spline's points to the
  * destination,
- * then loop over the remaining ones point by point, hashing their ids to the new ids. */
+ * then loop over the remaining ones point by point, hashing their ids to the new ids.
+ */
 static void copy_stable_id_splines(const CurveEval &curve,
                                    const IndexMask selection,
                                    const Span curve_offsets,
@@ -359,8 +365,10 @@ static void copy_point_attributes_without_id(GeometrySet &geometry_set,
   }
 }
 
-/* Copies the attributes for spline duplciates. If copying the spline domain, the attributes are
- * copied with an offset fill, otherwise a mapping is used. */
+/**
+ * Copies the attributes for spline duplicates. If copying the spline domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used.
+ */
 static void copy_spline_attributes_without_id(const GeometrySet &geometry_set,
                                               const Span point_mapping,
                                               const Span offsets,
@@ -409,8 +417,10 @@ static void copy_spline_attributes_without_id(const GeometrySet &geometry_set,
   }
 }
 
-/* Copies the attributes for edge duplciates. If copying the edge domain, the attributes are
- * copied with an offset fill, for point domain a mapping is used. */
+/**
+ * Copies the attributes for edge duplicates. If copying the edge domain, the attributes are
+ * copied with an offset fill, for point domain a mapping is used.
+ */
 static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
                                             const Span point_mapping,
                                             const Span offsets,
@@ -456,8 +466,10 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
   }
 }
 
-/* Copies the attributes for face duplciates. If copying the face domain, the attributes are
- * copied with an offset fill, otherwise a mapping is used. */
+/**
+ * Copies the attributes for face duplicates. If copying the face domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used.
+ */
 static void copy_face_attributes_without_id(GeometrySet &geometry_set,
                                             const Span edge_mapping,
                                             const Span vert_mapping,
@@ -512,9 +524,11 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set,
   }
 }
 
-/* --------------------------------------------------------------------
- * Duplication Functions.
- */
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Duplication Functions
+ * \{ */
 
 static void duplicate_splines(GeometrySet &geometry_set,
                               const Field &count_field,
@@ -1101,3 +1115,5 @@ void register_node_type_geo_duplicate_elements()
   ntype.declare = file_ns::node_declare;
   nodeRegisterType(&ntype);
 }
+
+/** \} */
-- 
cgit v1.2.3


From ae6400cfb4cae798667eab903d9c463fdec31298 Mon Sep 17 00:00:00 2001
From: Leon Schittek 
Date: Mon, 28 Feb 2022 18:05:12 -0500
Subject: UI: Fix multi input socket outline and highlight

Small fixes to the drawing of multi input sockets:
 - Make the outline thickness consistent with normal node sockets,
   independent from the screen DPI.
 - Only highlight multi input sockets when they are actually selected.
 - Skip selected multi inputs when drawing normal selected sockets.

Differential Revision: https://developer.blender.org/D14192
---
 source/blender/editors/space_node/node_draw.cc   | 31 +++++++++++++++++-------
 source/blender/editors/space_node/node_edit.cc   |  2 +-
 source/blender/editors/space_node/node_intern.hh |  2 ++
 3 files changed, 25 insertions(+), 10 deletions(-)

diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 7b4578e6c05..1286f6a818c 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -718,7 +718,12 @@ static void node_socket_draw_multi_input(const float color[4],
                                          const int locx,
                                          const int locy)
 {
-  const float outline_width = 1.0f;
+  /* The other sockets are drawn with the keyframe shader. There, the outline has a base thickness
+   * that can be varied but always scales with the size the socket is drawn at. Using `U.dpi_fac`
+   * has the the same effect here. It scales the outline correctly across different screen DPIs
+   * and UI scales without being affected by the 'line-width'. */
+  const float outline_width = NODE_SOCK_OUTLINE_SCALE * U.dpi_fac;
+
   /* UI_draw_roundbox draws the outline on the outer side, so compensate for the outline width. */
   const rctf rect = {
       locx - width + outline_width * 0.5f,
@@ -1060,7 +1065,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[
 {
   using namespace blender::ed::space_node;
 
-  const float size = 2.25f * NODE_SOCKSIZE * scale;
+  const float size = NODE_SOCKSIZE_DRAW_MULIPLIER * NODE_SOCKSIZE * scale;
   rcti draw_rect = *rect;
   float outline_color[4] = {0};
 
@@ -1081,7 +1086,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[
   GPU_program_point_size(true);
 
   immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
-  immUniform1f("outline_scale", 1.0f);
+  immUniform1f("outline_scale", NODE_SOCK_OUTLINE_SCALE);
   immUniform2f("ViewportSize", -1.0f, -1.0f);
 
   /* Single point. */
@@ -1232,13 +1237,14 @@ static void node_draw_sockets(const View2D &v2d,
   GPU_blend(GPU_BLEND_ALPHA);
   GPU_program_point_size(true);
   immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
-  immUniform1f("outline_scale", 1.0f);
+  immUniform1f("outline_scale", NODE_SOCK_OUTLINE_SCALE);
   immUniform2f("ViewportSize", -1.0f, -1.0f);
 
   /* Set handle size. */
+  const float socket_draw_size = NODE_SOCKSIZE * NODE_SOCKSIZE_DRAW_MULIPLIER;
   float scale;
   UI_view2d_scale_get(&v2d, &scale, nullptr);
-  scale *= 2.25f * NODE_SOCKSIZE;
+  scale *= socket_draw_size;
 
   if (!select_all) {
     immBeginAtMost(GPU_PRIM_POINTS, total_input_len + total_output_len);
@@ -1251,7 +1257,10 @@ static void node_draw_sockets(const View2D &v2d,
       continue;
     }
     if (select_all || (sock->flag & SELECT)) {
-      selected_input_len++;
+      if (!(sock->flag & SOCK_MULTI_INPUT)) {
+        /* Don't add multi-input sockets here since they are drawn in a different batch. */
+        selected_input_len++;
+      }
       continue;
     }
     /* Don't draw multi-input sockets here since they are drawn in a different batch. */
@@ -1318,6 +1327,10 @@ static void node_draw_sockets(const View2D &v2d,
         if (nodeSocketIsHidden(sock)) {
           continue;
         }
+        /* Don't draw multi-input sockets here since they are drawn in a different batch. */
+        if (sock->flag & SOCK_MULTI_INPUT) {
+          continue;
+        }
         if (select_all || (sock->flag & SELECT)) {
           node_socket_draw_nested(C,
                                   ntree,
@@ -1383,13 +1396,13 @@ static void node_draw_sockets(const View2D &v2d,
     }
 
     const bool is_node_hidden = (node.flag & NODE_HIDDEN);
-    const float width = NODE_SOCKSIZE;
+    const float width = 0.5f * socket_draw_size;
     float height = is_node_hidden ? width : node_socket_calculate_height(*socket) - width;
 
     float color[4];
     float outline_color[4];
     node_socket_color_get(C, ntree, node_ptr, *socket, color);
-    node_socket_outline_color_get(selected, socket->type, outline_color);
+    node_socket_outline_color_get(socket->flag & SELECT, socket->type, outline_color);
 
     node_socket_draw_multi_input(color, outline_color, width, height, socket->locx, socket->locy);
   }
@@ -2661,7 +2674,7 @@ static void node_draw_nodetree(const bContext &C,
       node_draw_link(C, region.v2d, snode, *link, true);
     }
   }
-  
+
   nodelink_batch_end(snode);
   GPU_blend(GPU_BLEND_NONE);
 
diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc
index 64f6e8bdf18..b30be6ae0af 100644
--- a/source/blender/editors/space_node/node_edit.cc
+++ b/source/blender/editors/space_node/node_edit.cc
@@ -89,7 +89,7 @@ struct CompoJob {
 
 float node_socket_calculate_height(const bNodeSocket &socket)
 {
-  float sock_height = NODE_SOCKSIZE * 2.0f;
+  float sock_height = NODE_SOCKSIZE * NODE_SOCKSIZE_DRAW_MULIPLIER;
   if (socket.flag & SOCK_MULTI_INPUT) {
     sock_height += max_ii(NODE_MULTI_INPUT_LINK_GAP * 0.5f * socket.total_inputs, NODE_SOCKSIZE);
   }
diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh
index 6b5beb6c0d6..319f97e57f5 100644
--- a/source/blender/editors/space_node/node_intern.hh
+++ b/source/blender/editors/space_node/node_intern.hh
@@ -104,6 +104,8 @@ ENUM_OPERATORS(NodeResizeDirection, NODE_RESIZE_LEFT);
 #define NODE_HEIGHT(node) (node.height * UI_DPI_FAC)
 #define NODE_MARGIN_X (1.2f * U.widget_unit)
 #define NODE_SOCKSIZE (0.25f * U.widget_unit)
+#define NODE_SOCKSIZE_DRAW_MULIPLIER 2.25f
+#define NODE_SOCK_OUTLINE_SCALE 1.0f
 #define NODE_MULTI_INPUT_LINK_GAP (0.25f * U.widget_unit)
 #define NODE_RESIZE_MARGIN (0.20f * U.widget_unit)
 #define NODE_LINK_RESOL 12
-- 
cgit v1.2.3


From eb0f8317e231c4a02940d0269125a96a47e94c7e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 10:56:28 +1100
Subject: Cleanup: ED_view3d_win_to_delta & ED_view3d_calc_zfac usage

- Rename ED_view3d_win_to_delta `mval` argument to `xy_delta` as it
  as it was misleading since this is an screen-space offset not a region
  relative cursor position (typical use of the name `mval`).
  Also rename the variable passed to this function which also used the
  term `mval` in many places.
- Re-order the output argument of ED_view3d_win_to_delta last.
  use an r_ prefix for return arguments.
- Document how the `zfac` argument is intended to be used.
- Split ED_view3d_calc_zfac into two functions as the `r_flip` argument
  was only used in some special cases.
---
 .../gizmo_library/gizmo_types/move3d_gizmo.c       |  6 +--
 source/blender/editors/gpencil/annotate_paint.c    | 12 ++---
 source/blender/editors/gpencil/gpencil_paint.c     | 12 ++---
 .../blender/editors/gpencil/gpencil_sculpt_paint.c | 19 ++++---
 source/blender/editors/gpencil/gpencil_utils.c     | 37 +++++++-------
 source/blender/editors/include/ED_view3d.h         | 43 +++++++++++-----
 source/blender/editors/mesh/editmesh_bisect.c      |  4 +-
 source/blender/editors/physics/particle_edit.c     |  6 +--
 source/blender/editors/sculpt_paint/paint_utils.c  |  7 ++-
 source/blender/editors/sculpt_paint/sculpt.c       |  8 +--
 source/blender/editors/space_view3d/view3d_edit.c  |  4 +-
 .../blender/editors/space_view3d/view3d_navigate.c |  4 +-
 .../editors/space_view3d/view3d_navigate_ndof.c    |  2 +-
 .../editors/space_view3d/view3d_navigate_zoom.c    | 10 ++--
 .../space_view3d/view3d_navigate_zoom_border.c     | 10 ++--
 .../blender/editors/space_view3d/view3d_project.c  | 59 ++++++++++++----------
 source/blender/editors/transform/transform.c       |  4 +-
 .../editors/transform/transform_constraints.c      |  3 +-
 .../blender/editors/transform/transform_generics.c | 17 +++----
 .../editors/transform/transform_mode_vert_slide.c  | 14 ++---
 .../blender/io/gpencil/intern/gpencil_io_base.cc   |  2 +-
 21 files changed, 150 insertions(+), 133 deletions(-)

diff --git a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c
index aea6d41202e..447fe1005a1 100644
--- a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c
+++ b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c
@@ -147,7 +147,7 @@ static void move3d_get_translate(const wmGizmo *gz,
                                  float co_delta[3])
 {
   MoveInteraction *inter = gz->interaction_data;
-  const float mval_delta[2] = {
+  const float xy_delta[2] = {
       event->mval[0] - inter->init.mval[0],
       event->mval[1] - inter->init.mval[1],
   };
@@ -155,9 +155,9 @@ static void move3d_get_translate(const wmGizmo *gz,
   RegionView3D *rv3d = region->regiondata;
   float co_ref[3];
   mul_v3_mat3_m4v3(co_ref, gz->matrix_space, inter->init.prop_co);
-  const float zfac = ED_view3d_calc_zfac(rv3d, co_ref, NULL);
+  const float zfac = ED_view3d_calc_zfac(rv3d, co_ref);
 
-  ED_view3d_win_to_delta(region, mval_delta, co_delta, zfac);
+  ED_view3d_win_to_delta(region, xy_delta, zfac, co_delta);
 
   float matrix_space_inv[3][3];
   copy_m3_m4(matrix_space_inv, gz->matrix_space);
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index b40dda146dc..5ab4a663efe 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -326,8 +326,7 @@ static void annotation_stroke_convertcoords(tGPsdata *p,
     }
     else {
       float mval_prj[2];
-      float rvec[3], dvec[3];
-      float zfac;
+      float rvec[3];
 
       /* Current method just converts each point in screen-coordinates to
        * 3D-coordinates using the 3D-cursor as reference. In general, this
@@ -339,13 +338,14 @@ static void annotation_stroke_convertcoords(tGPsdata *p,
        */
 
       annotation_get_3d_reference(p, rvec);
-      zfac = ED_view3d_calc_zfac(p->region->regiondata, rvec, NULL);
+      const float zfac = ED_view3d_calc_zfac(p->region->regiondata, rvec);
 
       if (ED_view3d_project_float_global(p->region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
           V3D_PROJ_RET_OK) {
-        float mval_f[2];
-        sub_v2_v2v2(mval_f, mval_prj, mval);
-        ED_view3d_win_to_delta(p->region, mval_f, dvec, zfac);
+        float dvec[3];
+        float xy_delta[2];
+        sub_v2_v2v2(xy_delta, mval_prj, mval);
+        ED_view3d_win_to_delta(p->region, xy_delta, zfac, dvec);
         sub_v3_v3v3(out, rvec, dvec);
       }
       else {
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 9442b2edc13..5409cea2a2a 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -447,21 +447,21 @@ static void gpencil_stroke_convertcoords(tGPsdata *p,
     }
 
     float mval_prj[2];
-    float rvec[3], dvec[3];
-    float mval_f[2];
-    float zfac;
+    float rvec[3];
 
     /* Current method just converts each point in screen-coordinates to
      * 3D-coordinates using the 3D-cursor as reference. In general, this
      * works OK, but it could of course be improved. */
 
     gpencil_get_3d_reference(p, rvec);
-    zfac = ED_view3d_calc_zfac(p->region->regiondata, rvec, NULL);
+    const float zfac = ED_view3d_calc_zfac(p->region->regiondata, rvec);
 
     if (ED_view3d_project_float_global(p->region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
         V3D_PROJ_RET_OK) {
-      sub_v2_v2v2(mval_f, mval_prj, mval);
-      ED_view3d_win_to_delta(p->region, mval_f, dvec, zfac);
+      float dvec[3];
+      float xy_delta[2];
+      sub_v2_v2v2(xy_delta, mval_prj, mval);
+      ED_view3d_win_to_delta(p->region, xy_delta, zfac, dvec);
       sub_v3_v3v3(out, rvec, dvec);
     }
     else {
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index ebe879959c9..67325e8a3d1 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -507,7 +507,7 @@ static void gpencil_brush_grab_calc_dvec(tGP_BrushEditData *gso)
   /* Convert mouse-movements to movement vector */
   RegionView3D *rv3d = gso->region->regiondata;
   float *rvec = gso->object->loc;
-  float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+  const float zfac = ED_view3d_calc_zfac(rv3d, rvec);
 
   float mval_f[2];
 
@@ -525,7 +525,7 @@ static void gpencil_brush_grab_calc_dvec(tGP_BrushEditData *gso)
     copy_v2_v2(mval_f, r);
   }
 
-  ED_view3d_win_to_delta(gso->region, mval_f, gso->dvec, zfac);
+  ED_view3d_win_to_delta(gso->region, mval_f, zfac, gso->dvec);
 }
 
 /* Apply grab transform to all relevant points of the affected strokes */
@@ -624,17 +624,16 @@ static void gpencil_brush_calc_midpoint(tGP_BrushEditData *gso)
    */
   RegionView3D *rv3d = gso->region->regiondata;
   const float *rvec = gso->object->loc;
-  float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+  const float zfac = ED_view3d_calc_zfac(rv3d, rvec);
 
-  float mval_f[2];
-  copy_v2_v2(mval_f, gso->mval);
   float mval_prj[2];
-  float dvec[3];
 
   if (ED_view3d_project_float_global(gso->region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
       V3D_PROJ_RET_OK) {
-    sub_v2_v2v2(mval_f, mval_prj, mval_f);
-    ED_view3d_win_to_delta(gso->region, mval_f, dvec, zfac);
+    float dvec[3];
+    float xy_delta[2];
+    sub_v2_v2v2(xy_delta, mval_prj, gso->mval);
+    ED_view3d_win_to_delta(gso->region, xy_delta, zfac, dvec);
     sub_v3_v3v3(gso->dvec, rvec, dvec);
   }
   else {
@@ -830,10 +829,10 @@ static bool gpencil_brush_randomize_apply(tGP_BrushEditData *gso,
       /* 3D: Project to 3D space */
       bool flip;
       RegionView3D *rv3d = gso->region->regiondata;
-      float zfac = ED_view3d_calc_zfac(rv3d, &pt->x, &flip);
+      const float zfac = ED_view3d_calc_zfac_ex(rv3d, &pt->x, &flip);
       if (flip == false) {
         float dvec[3];
-        ED_view3d_win_to_delta(gso->gsc.region, svec, dvec, zfac);
+        ED_view3d_win_to_delta(gso->gsc.region, svec, zfac, dvec);
         add_v3_v3(&pt->x, dvec);
         /* compute lock axis */
         gpencil_sculpt_compute_lock_axis(gso, pt, save_pt);
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index c0777ac3105..9a658b68f21 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -822,17 +822,16 @@ bool gpencil_point_xy_to_3d(const GP_SpaceConversion *gsc,
 
   ED_gpencil_drawing_reference_get(scene, gsc->ob, scene->toolsettings->gpencil_v3d_align, rvec);
 
-  float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+  float zfac = ED_view3d_calc_zfac(rv3d, rvec);
 
-  float mval_f[2], mval_prj[2];
-  float dvec[3];
-
-  copy_v2_v2(mval_f, screen_co);
+  float mval_prj[2];
 
   if (ED_view3d_project_float_global(gsc->region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
       V3D_PROJ_RET_OK) {
-    sub_v2_v2v2(mval_f, mval_prj, mval_f);
-    ED_view3d_win_to_delta(gsc->region, mval_f, dvec, zfac);
+    float dvec[3];
+    float xy_delta[2];
+    sub_v2_v2v2(xy_delta, mval_prj, screen_co);
+    ED_view3d_win_to_delta(gsc->region, xy_delta, zfac, dvec);
     sub_v3_v3v3(r_out, rvec, dvec);
 
     return true;
@@ -863,21 +862,21 @@ void gpencil_stroke_convertcoords_tpoint(Scene *scene,
      */
   }
   else {
-    float mval_f[2] = {UNPACK2(point2D->m_xy)};
     float mval_prj[2];
-    float rvec[3], dvec[3];
-    float zfac;
+    float rvec[3];
 
     /* Current method just converts each point in screen-coordinates to
      * 3D-coordinates using the 3D-cursor as reference.
      */
     ED_gpencil_drawing_reference_get(scene, ob, ts->gpencil_v3d_align, rvec);
-    zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL);
+    const float zfac = ED_view3d_calc_zfac(region->regiondata, rvec);
 
     if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
         V3D_PROJ_RET_OK) {
-      sub_v2_v2v2(mval_f, mval_prj, mval_f);
-      ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
+      float dvec[3];
+      float xy_delta[2];
+      sub_v2_v2v2(xy_delta, mval_prj, point2D->m_xy);
+      ED_view3d_win_to_delta(region, xy_delta, zfac, dvec);
       sub_v3_v3v3(r_out, rvec, dvec);
     }
     else {
@@ -2005,19 +2004,19 @@ static void gpencil_stroke_convertcoords(ARegion *region,
                                          const float origin[3],
                                          float out[3])
 {
-  float mval_f[2] = {UNPACK2(point2D->m_xy)};
   float mval_prj[2];
-  float rvec[3], dvec[3];
-  float zfac;
+  float rvec[3];
 
   copy_v3_v3(rvec, origin);
 
-  zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL);
+  const float zfac = ED_view3d_calc_zfac(region->regiondata, rvec);
 
   if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
       V3D_PROJ_RET_OK) {
-    sub_v2_v2v2(mval_f, mval_prj, mval_f);
-    ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
+    float dvec[3];
+    float xy_delta[2];
+    sub_v2_v2v2(xy_delta, mval_prj, point2D->m_xy);
+    ED_view3d_win_to_delta(region, xy_delta, zfac, dvec);
     sub_v3_v3v3(out, rvec, dvec);
   }
   else {
diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h
index 18b63403fcb..b1435e76eb2 100644
--- a/source/blender/editors/include/ED_view3d.h
+++ b/source/blender/editors/include/ED_view3d.h
@@ -507,9 +507,18 @@ float ED_view3d_pixel_size(const struct RegionView3D *rv3d, const float co[3]);
 float ED_view3d_pixel_size_no_ui_scale(const struct RegionView3D *rv3d, const float co[3]);
 
 /**
- * Calculate a depth value from \a co, use with #ED_view3d_win_to_delta
+ * Calculate a depth value from \a co, use with #ED_view3d_win_to_delta.
+ *
+ * \param r_flip: Set to `zfac < 0.0` before the value is made signed.
+ * Since it's important in some cases to know if the value was flipped.
+ *
+ * \return The unsigned depth component of `co` multiplied by `rv3d->persmat` matrix,
+ * with additional sanitation to ensure the result is never negative
+ * as this isn't useful for tool-code.
  */
-float ED_view3d_calc_zfac(const struct RegionView3D *rv3d, const float co[3], bool *r_flip);
+float ED_view3d_calc_zfac_ex(const struct RegionView3D *rv3d, const float co[3], bool *r_flip);
+/** See #ED_view3d_calc_zfac_ex doc-string. */
+float ED_view3d_calc_zfac(const struct RegionView3D *rv3d, const float co[3]);
 /**
  * Calculate a depth value from `co` (result should only be used for comparison).
  */
@@ -627,16 +636,24 @@ bool ED_view3d_win_to_3d_on_plane_int(const struct ARegion *region,
                                       float r_out[3]);
 /**
  * Calculate a 3d difference vector from 2d window offset.
- * note that #ED_view3d_calc_zfac() must be called first to determine
+ *
+ * \note that #ED_view3d_calc_zfac() must be called first to determine
  * the depth used to calculate the delta.
+ *
+ * When the `zfac` is calculated based on a world-space location directly under the cursor,
+ * the value of `r_out` can be subtracted from #RegionView3D.ofs to pan the view
+ * with the contents following the cursor perfectly (without sliding).
+ *
  * \param region: The region (used for the window width and height).
- * \param mval: The area relative 2d difference (such as `event->mval[0] - other_x`).
- * \param out: The resulting world-space delta.
+ * \param xy_delta: 2D difference (in pixels) such as `event->mval[0] - other_x`.
+ * \param zfac: The depth result typically calculated by by #ED_view3d_calc_zfac
+ * (see it's doc-string for details).
+ * \param r_out: The resulting world-space delta.
  */
 void ED_view3d_win_to_delta(const struct ARegion *region,
-                            const float mval[2],
-                            float out[3],
-                            float zfac);
+                            const float xy_delta[2],
+                            float zfac,
+                            float r_out[3]);
 /**
  * Calculate a 3d origin from 2d window coordinates.
  * \note Orthographic views have a less obvious origin,
@@ -645,23 +662,23 @@ void ED_view3d_win_to_delta(const struct ARegion *region,
  *
  * \param region: The region (used for the window width and height).
  * \param mval: The area relative 2d location (such as event->mval converted to floats).
- * \param out: The resulting normalized world-space direction vector.
+ * \param r_out: The resulting normalized world-space direction vector.
  */
-void ED_view3d_win_to_origin(const struct ARegion *region, const float mval[2], float out[3]);
+void ED_view3d_win_to_origin(const struct ARegion *region, const float mval[2], float r_out[3]);
 /**
  * Calculate a 3d direction vector from 2d window coordinates.
  * This direction vector starts and the view in the direction of the 2d window coordinates.
  * In orthographic view all window coordinates yield the same vector.
  *
- * \note doesn't rely on ED_view3d_calc_zfac
+ * \note doesn't rely on #ED_view3d_calc_zfac
  * for perspective view, get the vector direction to
  * the mouse cursor as a normalized vector.
  *
  * \param region: The region (used for the window width and height).
  * \param mval: The area relative 2d location (such as event->mval converted to floats).
- * \param out: The resulting normalized world-space direction vector.
+ * \param r_out: The resulting normalized world-space direction vector.
  */
-void ED_view3d_win_to_vector(const struct ARegion *region, const float mval[2], float out[3]);
+void ED_view3d_win_to_vector(const struct ARegion *region, const float mval[2], float r_out[3]);
 /**
  * Calculate a 3d segment from 2d window coordinates.
  * This ray_start is located at the viewpoint, ray_end is a far point.
diff --git a/source/blender/editors/mesh/editmesh_bisect.c b/source/blender/editors/mesh/editmesh_bisect.c
index 58bd906101c..7b251b77750 100644
--- a/source/blender/editors/mesh/editmesh_bisect.c
+++ b/source/blender/editors/mesh/editmesh_bisect.c
@@ -77,14 +77,14 @@ static void mesh_bisect_interactive_calc(bContext *C,
   const float *co_ref = rv3d->ofs;
   float co_a_ss[2] = {x_start, y_start}, co_b_ss[2] = {x_end, y_end}, co_delta_ss[2];
   float co_a[3], co_b[3];
-  const float zfac = ED_view3d_calc_zfac(rv3d, co_ref, NULL);
+  const float zfac = ED_view3d_calc_zfac(rv3d, co_ref);
 
   /* view vector */
   ED_view3d_win_to_vector(region, co_a_ss, co_a);
 
   /* view delta */
   sub_v2_v2v2(co_delta_ss, co_a_ss, co_b_ss);
-  ED_view3d_win_to_delta(region, co_delta_ss, co_b, zfac);
+  ED_view3d_win_to_delta(region, co_delta_ss, zfac, co_b);
 
   /* cross both to get a normal */
   cross_v3_v3v3(plane_no, co_a, co_b);
diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c
index 6307f6dc37f..fc815ebe682 100644
--- a/source/blender/editors/physics/particle_edit.c
+++ b/source/blender/editors/physics/particle_edit.c
@@ -4684,7 +4684,7 @@ static int brush_edit_init(bContext *C, wmOperator *op)
   bedit->ob = ob;
   bedit->edit = edit;
 
-  bedit->zfac = ED_view3d_calc_zfac(region->regiondata, min, NULL);
+  bedit->zfac = ED_view3d_calc_zfac(region->regiondata, min);
 
   /* cache view depths and settings for re-use */
   PE_set_view3d_data(C, &bedit->data);
@@ -4757,7 +4757,7 @@ static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
 
       switch (pset->brushtype) {
         case PE_BRUSH_COMB: {
-          const float mval_f[2] = {dx, dy};
+          const float xy_delta[2] = {dx, dy};
           data.mval = mval;
           data.rad = pe_brush_size_get(scene, brush);
 
@@ -4771,7 +4771,7 @@ static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
 
           invert_m4_m4(ob->imat, ob->obmat);
 
-          ED_view3d_win_to_delta(region, mval_f, vec, bedit->zfac);
+          ED_view3d_win_to_delta(region, xy_delta, bedit->zfac, vec);
           data.dvec = vec;
 
           foreach_mouse_hit_key(&data, brush_comb, selected);
diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c
index d470e6a3050..31b965c6a92 100644
--- a/source/blender/editors/sculpt_paint/paint_utils.c
+++ b/source/blender/editors/sculpt_paint/paint_utils.c
@@ -130,13 +130,12 @@ float paint_calc_object_space_radius(ViewContext *vc, const float center[3], flo
 {
   Object *ob = vc->obact;
   float delta[3], scale, loc[3];
-  const float mval_f[2] = {pixel_radius, 0.0f};
-  float zfac;
+  const float xy_delta[2] = {pixel_radius, 0.0f};
 
   mul_v3_m4v3(loc, ob->obmat, center);
 
-  zfac = ED_view3d_calc_zfac(vc->rv3d, loc, NULL);
-  ED_view3d_win_to_delta(vc->region, mval_f, delta, zfac);
+  const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc);
+  ED_view3d_win_to_delta(vc->region, xy_delta, zfac, delta);
 
   scale = fabsf(mat4_to_scale(ob->obmat));
   scale = (scale == 0.0f) ? 1.0f : scale;
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 919385f82e7..70ff7596d6d 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -2648,13 +2648,13 @@ static void update_sculpt_normal(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
 static void calc_local_y(ViewContext *vc, const float center[3], float y[3])
 {
   Object *ob = vc->obact;
-  float loc[3], mval_f[2] = {0.0f, 1.0f};
-  float zfac;
+  float loc[3];
+  const float xy_delta[2] = {0.0f, 1.0f};
 
   mul_v3_m4v3(loc, ob->imat, center);
-  zfac = ED_view3d_calc_zfac(vc->rv3d, loc, NULL);
+  const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc);
 
-  ED_view3d_win_to_delta(vc->region, mval_f, y, zfac);
+  ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y);
   normalize_v3(y);
 
   add_v3_v3(y, ob->loc);
diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c
index d6bc7ded92e..5adce170e06 100644
--- a/source/blender/editors/space_view3d/view3d_edit.c
+++ b/source/blender/editors/space_view3d/view3d_edit.c
@@ -832,13 +832,13 @@ void ED_view3d_cursor3d_position(bContext *C,
     return;
   }
 
-  ED_view3d_calc_zfac(rv3d, cursor_co, &flip);
+  ED_view3d_calc_zfac_ex(rv3d, cursor_co, &flip);
 
   /* Reset the depth based on the view offset (we _know_ the offset is in front of us). */
   if (flip) {
     negate_v3_v3(cursor_co, rv3d->ofs);
     /* re initialize, no need to check flip again */
-    ED_view3d_calc_zfac(rv3d, cursor_co, NULL /* &flip */);
+    ED_view3d_calc_zfac(rv3d, cursor_co);
   }
 
   if (use_depth) { /* maybe this should be accessed some other way */
diff --git a/source/blender/editors/space_view3d/view3d_navigate.c b/source/blender/editors/space_view3d/view3d_navigate.c
index 21e0459f346..d1e7f6ffb12 100644
--- a/source/blender/editors/space_view3d/view3d_navigate.c
+++ b/source/blender/editors/space_view3d/view3d_navigate.c
@@ -396,7 +396,7 @@ ViewOpsData *viewops_data_create(bContext *C, const wmEvent *event, enum eViewOp
   {
     float tvec[3];
     negate_v3_v3(tvec, rv3d->ofs);
-    vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
+    vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec);
   }
 
   vod->reverse = 1.0f;
@@ -559,7 +559,7 @@ void viewmove_apply(ViewOpsData *vod, int x, int y)
   else {
     float dvec[3];
 
-    ED_view3d_win_to_delta(vod->region, event_ofs, dvec, vod->init.zfac);
+    ED_view3d_win_to_delta(vod->region, event_ofs, vod->init.zfac, dvec);
 
     sub_v3_v3(vod->rv3d->ofs, dvec);
 
diff --git a/source/blender/editors/space_view3d/view3d_navigate_ndof.c b/source/blender/editors/space_view3d/view3d_navigate_ndof.c
index 6b77f464773..1ce9bdcb211 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_ndof.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_ndof.c
@@ -48,7 +48,7 @@ static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth
   float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
 
   if (rv3d->is_persp) {
-    speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
+    speed *= ED_view3d_calc_zfac(rv3d, depth_pt);
   }
 
   return speed;
diff --git a/source/blender/editors/space_view3d/view3d_navigate_zoom.c b/source/blender/editors/space_view3d/view3d_navigate_zoom.c
index 8eb8fffaa31..5f6f9fde324 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_zoom.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_zoom.c
@@ -125,18 +125,18 @@ static void view_zoom_to_window_xy_3d(ARegion *region, float dfac, const int zoo
     float dvec[3];
     float tvec[3];
     float tpos[3];
-    float mval_f[2];
+    float xy_delta[2];
 
     float zfac;
 
     negate_v3_v3(tpos, rv3d->ofs);
 
-    mval_f[0] = (float)(((zoom_xy[0] - region->winrct.xmin) * 2) - region->winx) / 2.0f;
-    mval_f[1] = (float)(((zoom_xy[1] - region->winrct.ymin) * 2) - region->winy) / 2.0f;
+    xy_delta[0] = (float)(((zoom_xy[0] - region->winrct.xmin) * 2) - region->winx) / 2.0f;
+    xy_delta[1] = (float)(((zoom_xy[1] - region->winrct.ymin) * 2) - region->winy) / 2.0f;
 
     /* Project cursor position into 3D space */
-    zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
-    ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
+    zfac = ED_view3d_calc_zfac(rv3d, tpos);
+    ED_view3d_win_to_delta(region, xy_delta, zfac, dvec);
 
     /* Calculate view target position for dolly */
     add_v3_v3v3(tvec, tpos, dvec);
diff --git a/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c b/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c
index 4e909151ce4..f834efe4a7b 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_zoom_border.c
@@ -125,7 +125,7 @@ static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
       negate_v3_v3(new_ofs, p);
     }
     else {
-      float mval_f[2];
+      float xy_delta[2];
       float zfac;
 
       /* We can't use the depth, fallback to the old way that doesn't set the center depth */
@@ -134,12 +134,12 @@ static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
       {
         float tvec[3];
         negate_v3_v3(tvec, new_ofs);
-        zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
+        zfac = ED_view3d_calc_zfac(rv3d, tvec);
       }
 
-      mval_f[0] = (rect.xmin + rect.xmax - vb[0]) / 2.0f;
-      mval_f[1] = (rect.ymin + rect.ymax - vb[1]) / 2.0f;
-      ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
+      xy_delta[0] = (rect.xmin + rect.xmax - vb[0]) / 2.0f;
+      xy_delta[1] = (rect.ymin + rect.ymax - vb[1]) / 2.0f;
+      ED_view3d_win_to_delta(region, xy_delta, zfac, dvec);
       /* center the view to the center of the rectangle */
       sub_v3_v3(new_ofs, dvec);
     }
diff --git a/source/blender/editors/space_view3d/view3d_project.c b/source/blender/editors/space_view3d/view3d_project.c
index 2cf9ac0a52e..85d239507ce 100644
--- a/source/blender/editors/space_view3d/view3d_project.c
+++ b/source/blender/editors/space_view3d/view3d_project.c
@@ -276,7 +276,7 @@ float ED_view3d_pixel_size_no_ui_scale(const RegionView3D *rv3d, const float co[
   return mul_project_m4_v3_zfac(rv3d->persmat, co) * rv3d->pixsize;
 }
 
-float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3], bool *r_flip)
+float ED_view3d_calc_zfac_ex(const RegionView3D *rv3d, const float co[3], bool *r_flip)
 {
   float zfac = mul_project_m4_v3_zfac(rv3d->persmat, co);
 
@@ -299,10 +299,15 @@ float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3], bool *r_f
   return zfac;
 }
 
+float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3])
+{
+  return ED_view3d_calc_zfac_ex(rv3d, co, NULL);
+}
+
 float ED_view3d_calc_depth_for_comparison(const RegionView3D *rv3d, const float co[3])
 {
   if (rv3d->is_persp) {
-    return ED_view3d_calc_zfac(rv3d, co, NULL);
+    return ED_view3d_calc_zfac(rv3d, co);
   }
   return -dot_v3v3(rv3d->viewinv[2], co);
 }
@@ -436,8 +441,8 @@ bool view3d_get_view_aligned_coordinate(ARegion *region,
 
   if (ret == V3D_PROJ_RET_OK) {
     const float mval_f[2] = {(float)(mval_cpy[0] - mval[0]), (float)(mval_cpy[1] - mval[1])};
-    const float zfac = ED_view3d_calc_zfac(rv3d, fp, NULL);
-    ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
+    const float zfac = ED_view3d_calc_zfac(rv3d, fp);
+    ED_view3d_win_to_delta(region, mval_f, zfac, dvec);
     sub_v3_v3(fp, dvec);
 
     return true;
@@ -584,57 +589,57 @@ bool ED_view3d_win_to_3d_on_plane_with_fallback(const ARegion *region,
 }
 
 void ED_view3d_win_to_delta(const ARegion *region,
-                            const float mval[2],
-                            float out[3],
-                            const float zfac)
+                            const float xy_delta[2],
+                            const float zfac,
+                            float r_out[3])
 {
   RegionView3D *rv3d = region->regiondata;
   float dx, dy;
 
-  dx = 2.0f * mval[0] * zfac / region->winx;
-  dy = 2.0f * mval[1] * zfac / region->winy;
+  dx = 2.0f * xy_delta[0] * zfac / region->winx;
+  dy = 2.0f * xy_delta[1] * zfac / region->winy;
 
-  out[0] = (rv3d->persinv[0][0] * dx + rv3d->persinv[1][0] * dy);
-  out[1] = (rv3d->persinv[0][1] * dx + rv3d->persinv[1][1] * dy);
-  out[2] = (rv3d->persinv[0][2] * dx + rv3d->persinv[1][2] * dy);
+  r_out[0] = (rv3d->persinv[0][0] * dx + rv3d->persinv[1][0] * dy);
+  r_out[1] = (rv3d->persinv[0][1] * dx + rv3d->persinv[1][1] * dy);
+  r_out[2] = (rv3d->persinv[0][2] * dx + rv3d->persinv[1][2] * dy);
 }
 
-void ED_view3d_win_to_origin(const ARegion *region, const float mval[2], float out[3])
+void ED_view3d_win_to_origin(const ARegion *region, const float mval[2], float r_out[3])
 {
   RegionView3D *rv3d = region->regiondata;
   if (rv3d->is_persp) {
-    copy_v3_v3(out, rv3d->viewinv[3]);
+    copy_v3_v3(r_out, rv3d->viewinv[3]);
   }
   else {
-    out[0] = 2.0f * mval[0] / region->winx - 1.0f;
-    out[1] = 2.0f * mval[1] / region->winy - 1.0f;
+    r_out[0] = 2.0f * mval[0] / region->winx - 1.0f;
+    r_out[1] = 2.0f * mval[1] / region->winy - 1.0f;
 
     if (rv3d->persp == RV3D_CAMOB) {
-      out[2] = -1.0f;
+      r_out[2] = -1.0f;
     }
     else {
-      out[2] = 0.0f;
+      r_out[2] = 0.0f;
     }
 
-    mul_project_m4_v3(rv3d->persinv, out);
+    mul_project_m4_v3(rv3d->persinv, r_out);
   }
 }
 
-void ED_view3d_win_to_vector(const ARegion *region, const float mval[2], float out[3])
+void ED_view3d_win_to_vector(const ARegion *region, const float mval[2], float r_out[3])
 {
   RegionView3D *rv3d = region->regiondata;
 
   if (rv3d->is_persp) {
-    out[0] = 2.0f * (mval[0] / region->winx) - 1.0f;
-    out[1] = 2.0f * (mval[1] / region->winy) - 1.0f;
-    out[2] = -0.5f;
-    mul_project_m4_v3(rv3d->persinv, out);
-    sub_v3_v3(out, rv3d->viewinv[3]);
+    r_out[0] = 2.0f * (mval[0] / region->winx) - 1.0f;
+    r_out[1] = 2.0f * (mval[1] / region->winy) - 1.0f;
+    r_out[2] = -0.5f;
+    mul_project_m4_v3(rv3d->persinv, r_out);
+    sub_v3_v3(r_out, rv3d->viewinv[3]);
   }
   else {
-    negate_v3_v3(out, rv3d->viewinv[2]);
+    negate_v3_v3(r_out, rv3d->viewinv[2]);
   }
-  normalize_v3(out);
+  normalize_v3(r_out);
 }
 
 bool ED_view3d_win_to_segment_clipped(struct Depsgraph *depsgraph,
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index b006b13d217..67b0d5c85ac 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -177,8 +177,8 @@ void convertViewVec(TransInfo *t, float r_vec[3], double dx, double dy)
       r_vec[1] = dy;
     }
     else {
-      const float mval_f[2] = {(float)dx, (float)dy};
-      ED_view3d_win_to_delta(t->region, mval_f, r_vec, t->zfac);
+      const float xy_delta[2] = {(float)dx, (float)dy};
+      ED_view3d_win_to_delta(t->region, xy_delta, t->zfac, r_vec);
     }
   }
   else if (t->spacetype == SPACE_IMAGE) {
diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c
index 64ef170a13f..81b35e4539b 100644
--- a/source/blender/editors/transform/transform_constraints.c
+++ b/source/blender/editors/transform/transform_constraints.c
@@ -1041,8 +1041,7 @@ static void setNearestAxis3d(TransInfo *t)
    * and to overflow the short integers.
    * The formula used is a bit stupid, just a simplification of the subtraction
    * of two 2D points 30 pixels apart (that's the last factor in the formula) after
-   * projecting them with ED_view3d_win_to_delta and then get the length of that vector.
-   */
+   * projecting them with #ED_view3d_win_to_delta and then get the length of that vector. */
   zfac = mul_project_m4_v3_zfac(t->persmat, t->center_global);
   zfac = len_v3(t->persinv[0]) * 2.0f / t->region->winx * zfac * 30.0f;
 
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index 2b523461800..8987325145c 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -1146,7 +1146,7 @@ void calculateCenter(TransInfo *t)
 
         projectFloatView(t, axis, t->center2d);
 
-        /* rotate only needs correct 2d center, grab needs ED_view3d_calc_zfac() value */
+        /* Rotate only needs correct 2d center, grab needs #ED_view3d_calc_zfac() value. */
         if (t->mode == TFM_TRANSLATION) {
           copy_v3_v3(t->center_global, axis);
         }
@@ -1155,17 +1155,16 @@ void calculateCenter(TransInfo *t)
   }
 
   if (t->spacetype == SPACE_VIEW3D) {
-    /* ED_view3d_calc_zfac() defines a factor for perspective depth correction,
-     * used in ED_view3d_win_to_delta() */
+    /* #ED_view3d_calc_zfac() defines a factor for perspective depth correction,
+     * used in #ED_view3d_win_to_delta(). */
 
-    /* zfac is only used convertViewVec only in cases operator was invoked in RGN_TYPE_WINDOW
-     * and never used in other cases.
+    /* NOTE: `t->zfac` is only used #convertViewVec only in cases operator was invoked in
+     * #RGN_TYPE_WINDOW and never used in other cases.
      *
-     * We need special case here as well, since ED_view3d_calc_zfac will crash when called
-     * for a region different from RGN_TYPE_WINDOW.
-     */
+     * We need special case here as well, since #ED_view3d_calc_zfac will crash when called
+     * for a region different from #RGN_TYPE_WINDOW. */
     if (t->region->regiontype == RGN_TYPE_WINDOW) {
-      t->zfac = ED_view3d_calc_zfac(t->region->regiondata, t->center_global, NULL);
+      t->zfac = ED_view3d_calc_zfac(t->region->regiondata, t->center_global);
     }
     else {
       t->zfac = 0.0f;
diff --git a/source/blender/editors/transform/transform_mode_vert_slide.c b/source/blender/editors/transform/transform_mode_vert_slide.c
index 0afc527d4a8..77c5707d814 100644
--- a/source/blender/editors/transform/transform_mode_vert_slide.c
+++ b/source/blender/editors/transform/transform_mode_vert_slide.c
@@ -146,9 +146,9 @@ static void calcVertSlideMouseActiveEdges(struct TransInfo *t, const int mval[2]
    * by finding the closest edge in local-space.
    * However this skews the outcome with non-uniform-scale. */
 
-  /* first get the direction of the original mouse position */
+  /* First get the direction of the original mouse position. */
   sub_v2_v2v2(dir, imval_fl, mval_fl);
-  ED_view3d_win_to_delta(t->region, dir, dir, t->zfac);
+  ED_view3d_win_to_delta(t->region, dir, t->zfac, dir);
   normalize_v3(dir);
 
   for (i = 0, sv = sld->sv; i < sld->totsv; i++, sv++) {
@@ -425,18 +425,18 @@ void drawVertSlide(TransInfo *t)
       /* direction from active vertex! */
       if ((t->mval[0] != t->mouse.imval[0]) || (t->mval[1] != t->mouse.imval[1])) {
         float zfac;
-        float mval_ofs[2];
+        float xy_delta[2];
         float co_orig_3d[3];
         float co_dest_3d[3];
 
-        mval_ofs[0] = t->mval[0] - t->mouse.imval[0];
-        mval_ofs[1] = t->mval[1] - t->mouse.imval[1];
+        xy_delta[0] = t->mval[0] - t->mouse.imval[0];
+        xy_delta[1] = t->mval[1] - t->mouse.imval[1];
 
         mul_v3_m4v3(
             co_orig_3d, TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->obmat, curr_sv->co_orig_3d);
-        zfac = ED_view3d_calc_zfac(t->region->regiondata, co_orig_3d, NULL);
+        zfac = ED_view3d_calc_zfac(t->region->regiondata, co_orig_3d);
 
-        ED_view3d_win_to_delta(t->region, mval_ofs, co_dest_3d, zfac);
+        ED_view3d_win_to_delta(t->region, xy_delta, zfac, co_dest_3d);
 
         invert_m4_m4(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->imat,
                      TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->obmat);
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc
index 9d4311bd693..9379e72bdd9 100644
--- a/source/blender/io/gpencil/intern/gpencil_io_base.cc
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc
@@ -158,7 +158,7 @@ void GpencilIO::create_object_list()
       float zdepth = 0;
       if (rv3d_) {
         if (rv3d_->is_persp) {
-          zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3], nullptr);
+          zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3]);
         }
         else {
           zdepth = -dot_v3v3(rv3d_->viewinv[2], object->obmat[3]);
-- 
cgit v1.2.3


From 8a8424021c6bd7666375b5d93804c2693fab4a0d Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 11:59:21 +1100
Subject: Cleanup: move Event.is_repeat & is_direction_inverted to flags

Use a flag for events to avoid adding struct members every time a new
kind of tag is needed - so events remain small.

This also simplifies copying settings as flags can be copied at once
with a mask.
---
 source/blender/editors/interface/interface.c       |  2 +-
 .../blender/editors/interface/interface_handlers.c |  8 ++---
 source/blender/editors/screen/screen_ops.c         |  2 +-
 .../editors/space_view3d/view3d_navigate_rotate.c  |  2 +-
 source/blender/editors/transform/transform.c       |  6 ++--
 source/blender/makesrna/intern/rna_wm.c            |  2 +-
 source/blender/makesrna/intern/rna_wm_api.c        |  2 +-
 source/blender/windowmanager/WM_types.h            | 34 ++++++++++++----------
 .../blender/windowmanager/intern/wm_event_query.c  |  6 ++--
 .../blender/windowmanager/intern/wm_event_system.c | 32 +++++++++++---------
 .../blender/windowmanager/intern/wm_gesture_ops.c  |  2 +-
 source/blender/windowmanager/intern/wm_window.c    |  6 ++--
 12 files changed, 56 insertions(+), 48 deletions(-)

diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index bbd2a64bab4..3619a7ce317 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -6529,7 +6529,7 @@ void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but)
 
   event.type = EVT_BUT_OPEN;
   event.val = KM_PRESS;
-  event.is_repeat = false;
+  event.flag = 0;
   event.customdata = but;
   event.customdata_free = false;
 
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 3d92cd6c397..c277ca2e36b 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -8974,7 +8974,7 @@ void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but)
   wm_event_init_from_window(win, &event);
   event.type = EVT_BUT_OPEN;
   event.val = KM_PRESS;
-  event.is_repeat = false;
+  event.flag = 0;
   event.customdata = but;
   event.customdata_free = false;
 
@@ -9538,7 +9538,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
     ui_pan_to_scroll(event, &type, &val);
 
     /* 'ui_pan_to_scroll' gives the absolute direction. */
-    if (event->is_direction_inverted) {
+    if (event->flag & WM_EVENT_SCROLL_INVERT) {
       scroll_dir = -1;
     }
 
@@ -10459,7 +10459,7 @@ static int ui_handle_menu_event(bContext *C,
 
             /* Only respond to explicit press to avoid the event that opened the menu
              * activating an item when the key is held. */
-            if (event->is_repeat) {
+            if (event->flag & WM_EVENT_IS_REPEAT) {
               break;
             }
 
@@ -10546,7 +10546,7 @@ static int ui_handle_menu_event(bContext *C,
               ((event->modifier & (KM_SHIFT | KM_CTRL | KM_OSKEY)) == 0) &&
               /* Only respond to explicit press to avoid the event that opened the menu
                * activating an item when the key is held. */
-              !event->is_repeat) {
+              (event->flag & WM_EVENT_IS_REPEAT) == 0) {
             if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
               break;
             }
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index b912b02852f..ee3bc3cba76 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -1023,7 +1023,7 @@ static void actionzone_apply(bContext *C, wmOperator *op, int type)
   }
 
   event.val = KM_NOTHING;
-  event.is_repeat = false;
+  event.flag = 0;
   event.customdata = op->customdata;
   event.customdata_free = true;
   op->customdata = NULL;
diff --git a/source/blender/editors/space_view3d/view3d_navigate_rotate.c b/source/blender/editors/space_view3d/view3d_navigate_rotate.c
index 774a8983c67..11de5463cdb 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_rotate.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_rotate.c
@@ -383,7 +383,7 @@ static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
     int event_xy[2];
 
     if (event->type == MOUSEPAN) {
-      if (event->is_direction_inverted) {
+      if (event->flag & WM_EVENT_SCROLL_INVERT) {
         event_xy[0] = 2 * event->xy[0] - event->prev_xy[0];
         event_xy[1] = 2 * event->xy[1] - event->prev_xy[1];
       }
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index 67b0d5c85ac..d7a71350934 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -1150,7 +1150,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
   else if (event->val == KM_PRESS) {
     switch (event->type) {
       case EVT_CKEY:
-        if (event->is_repeat) {
+        if (event->flag & WM_EVENT_IS_REPEAT) {
           break;
         }
         if (event->modifier & KM_ALT) {
@@ -1164,7 +1164,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         }
         break;
       case EVT_OKEY:
-        if (event->is_repeat) {
+        if (event->flag & WM_EVENT_IS_REPEAT) {
           break;
         }
         if ((t->flag & T_PROP_EDIT) && (event->modifier & KM_SHIFT)) {
@@ -1202,7 +1202,7 @@ int transformEvent(TransInfo *t, const wmEvent *event)
         }
         break;
       case EVT_NKEY:
-        if (event->is_repeat) {
+        if (event->flag & WM_EVENT_IS_REPEAT) {
           break;
         }
         if (ELEM(t->mode, TFM_ROTATION)) {
diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c
index 16d1dc0e93f..ae688846b95 100644
--- a/source/blender/makesrna/intern/rna_wm.c
+++ b/source/blender/makesrna/intern/rna_wm.c
@@ -668,7 +668,7 @@ static int rna_Event_unicode_length(PointerRNA *ptr)
 static bool rna_Event_is_repeat_get(PointerRNA *ptr)
 {
   const wmEvent *event = ptr->data;
-  return event->is_repeat;
+  return (event->flag & WM_EVENT_IS_REPEAT) != 0;
 }
 
 static float rna_Event_pressure_get(PointerRNA *ptr)
diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c
index dc53b7a5e12..1eb51b6ec80 100644
--- a/source/blender/makesrna/intern/rna_wm_api.c
+++ b/source/blender/makesrna/intern/rna_wm_api.c
@@ -635,7 +635,7 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win,
   wmEvent e = *win->eventstate;
   e.type = type;
   e.val = value;
-  e.is_repeat = false;
+  e.flag = 0;
   e.xy[0] = x;
   e.xy[1] = y;
 
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index e9f12287d29..cdbaa2739ba 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -560,6 +560,22 @@ typedef struct wmGesture {
 
 /* ************** wmEvent ************************ */
 
+typedef enum eWM_EventFlag {
+  /**
+   * True if the operating system inverted the delta x/y values and resulting
+   * `prev_xy` values, for natural scroll direction.
+   * For absolute scroll direction, the delta must be negated again.
+   */
+  WM_EVENT_SCROLL_INVERT = (1 << 0),
+  /**
+   * Generated by auto-repeat, note that this must only ever be set for keyboard events
+   * where `ISKEYBOARD(event->type) == true`.
+   *
+   * See #KMI_REPEAT_IGNORE for details on how key-map handling uses this.
+   */
+  WM_EVENT_IS_REPEAT = (1 << 1),
+} eWM_EventFlag;
+
 typedef struct wmTabletData {
   /** 0=EVT_TABLET_NONE, 1=EVT_TABLET_STYLUS, 2=EVT_TABLET_ERASER. */
   int active;
@@ -616,14 +632,6 @@ typedef struct wmEvent {
   /** From ghost, fallback if utf8 isn't set. */
   char ascii;
 
-  /**
-   * Generated by auto-repeat, note that this must only ever be set for keyboard events
-   * where `ISKEYBOARD(event->type) == true`.
-   *
-   * See #KMI_REPEAT_IGNORE for details on how key-map handling uses this.
-   */
-  char is_repeat;
-
   /** The previous value of `type`. */
   short prev_type;
   /** The previous value of `val`. */
@@ -656,20 +664,14 @@ typedef struct wmEvent {
   /** Tablet info, available for mouse move and button events. */
   wmTabletData tablet;
 
+  eWM_EventFlag flag;
+
   /* Custom data. */
   /** Custom data type, stylus, 6dof, see wm_event_types.h */
   short custom;
   short customdata_free;
-  int pad2;
   /** Ascii, unicode, mouse-coords, angles, vectors, NDOF data, drag-drop info. */
   void *customdata;
-
-  /**
-   * True if the operating system inverted the delta x/y values and resulting
-   * `prev_xy` values, for natural scroll direction.
-   * For absolute scroll direction, the delta must be negated again.
-   */
-  char is_direction_inverted;
 } wmEvent;
 
 /**
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index 751dcc61fa9..0d42d0a44c7 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -85,7 +85,7 @@ void WM_event_print(const wmEvent *event)
         (event->modifier & KM_ALT) != 0,
         (event->modifier & KM_OSKEY) != 0,
         event->keymodifier,
-        event->is_repeat,
+        (event->flag & WM_EVENT_IS_REPEAT) != 0,
         event->xy[0],
         event->xy[1],
         event->ascii,
@@ -440,7 +440,7 @@ int WM_event_absolute_delta_x(const struct wmEvent *event)
 {
   int dx = event->xy[0] - event->prev_xy[0];
 
-  if (!event->is_direction_inverted) {
+  if ((event->flag & WM_EVENT_SCROLL_INVERT) == 0) {
     dx = -dx;
   }
 
@@ -451,7 +451,7 @@ int WM_event_absolute_delta_y(const struct wmEvent *event)
 {
   int dy = event->xy[1] - event->prev_xy[1];
 
-  if (!event->is_direction_inverted) {
+  if ((event->flag & WM_EVENT_SCROLL_INVERT) == 0) {
     dy = -dy;
   }
 
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 6753978eece..4e9a430b380 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -153,7 +153,7 @@ wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
     win->eventstate->type = event->type;
 
     if (event->val == KM_PRESS) {
-      if (event->is_repeat == false) {
+      if ((event->flag & WM_EVENT_IS_REPEAT) == 0) {
         copy_v2_v2_int(win->eventstate->prev_click_xy, event->xy);
       }
     }
@@ -166,7 +166,7 @@ void wm_event_free(wmEvent *event)
 #ifndef NDEBUG
   /* Don't use assert here because it's fairly harmless in most cases,
    * more an issue of correctness, something we should avoid in general. */
-  if (event->is_repeat && !ISKEYBOARD(event->type)) {
+  if ((event->flag & WM_EVENT_IS_REPEAT) && !ISKEYBOARD(event->type)) {
     printf("%s: 'is_repeat=true' for non-keyboard event, this should not happen.\n", __func__);
     WM_event_print(event);
   }
@@ -739,7 +739,7 @@ void wm_event_handler_ui_cancel_ex(bContext *C,
       wm_event_init_from_window(win, &event);
       event.type = EVT_BUT_CANCEL;
       event.val = reactivate_button ? 0 : 1;
-      event.is_repeat = false;
+      event.flag = 0;
       handler->handle_fn(C, &event, handler->user_data);
     }
   }
@@ -1982,7 +1982,7 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi)
     return false;
   }
 
-  if (winevent->is_repeat) {
+  if (winevent->flag & WM_EVENT_IS_REPEAT) {
     if (kmi->flag & KMI_REPEAT_IGNORE) {
       return false;
     }
@@ -3204,7 +3204,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
        * wasn't handled, the KM_RELEASE will become a KM_CLICK */
 
       if (event->val == KM_PRESS) {
-        if (event->is_repeat == false) {
+        if ((event->flag & WM_EVENT_IS_REPEAT) == 0) {
           win->event_queue_check_click = true;
           win->event_queue_check_drag = true;
           win->event_queue_check_drag_handled = false;
@@ -3814,7 +3814,7 @@ void wm_event_do_handlers(bContext *C)
       tevent.type = MOUSEMOVE;
       tevent.prev_xy[0] = tevent.xy[0];
       tevent.prev_xy[1] = tevent.xy[1];
-      tevent.is_repeat = false;
+      tevent.flag = 0;
       wm_event_add(win, &tevent);
       win->addmousemove = 0;
     }
@@ -4720,7 +4720,7 @@ static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event)
    * them for better performance. */
   if (event_last && event_last->type == MOUSEMOVE) {
     event_last->type = INBETWEEN_MOUSEMOVE;
-    event_last->is_repeat = false;
+    event_last->flag = 0;
   }
 
   wmEvent *event_new = wm_event_add(win, event);
@@ -4772,7 +4772,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
 
   /* Initialize and copy state (only mouse x y and modifiers). */
   event = *event_state;
-  event.is_repeat = false;
+  event.flag = 0;
 
   /**
    * Always support accessing the last key press/release. This is set from `win->eventstate`,
@@ -4870,7 +4870,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
       event.val = KM_NOTHING;
 
       /* The direction is inverted from the device due to system preferences. */
-      event.is_direction_inverted = pd->isDirectionInverted;
+      if (pd->isDirectionInverted) {
+        event.flag |= WM_EVENT_SCROLL_INVERT;
+      }
 
       wm_event_add_trackpad(win, &event, pd->deltaX, -pd->deltaY);
       break;
@@ -4951,12 +4953,16 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
     case GHOST_kEventKeyDown:
     case GHOST_kEventKeyUp: {
       GHOST_TEventKeyData *kd = customdata;
+      /* Only copy these flags into the `event_state`. */
+      const eWM_EventFlag event_state_flag_mask = WM_EVENT_IS_REPEAT;
       bool keymodifier = 0;
       event.type = convert_key(kd->key);
       event.ascii = kd->ascii;
       /* Might be not NULL terminated. */
       memcpy(event.utf8_buf, kd->utf8_buf, sizeof(event.utf8_buf));
-      event.is_repeat = kd->is_repeat;
+      if (kd->is_repeat) {
+        event.flag |= WM_EVENT_IS_REPEAT;
+      }
       event.val = (type == GHOST_kEventKeyDown) ? KM_PRESS : KM_RELEASE;
 
       wm_eventemulation(&event, false);
@@ -4965,7 +4971,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
       /* Copy to event state. */
       event_state->val = event.val;
       event_state->type = event.type;
-      event_state->is_repeat = event.is_repeat;
+      event_state->flag = (event.flag & event_state_flag_mask);
 
       /* Exclude arrow keys, esc, etc from text input. */
       if (type == GHOST_kEventKeyUp) {
@@ -5096,7 +5102,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
       /* Double click test - only for press. */
       if (event.val == KM_PRESS) {
         /* Don't reset timer & location when holding the key generates repeat events. */
-        if (event.is_repeat == false) {
+        if ((event.flag & WM_EVENT_IS_REPEAT) == 0) {
           wm_event_prev_click_set(&event, event_state);
         }
       }
@@ -5217,7 +5223,7 @@ void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val)
   wmEvent event = {
       .type = EVT_XR_ACTION,
       .val = val,
-      .is_repeat = false,
+      .flag = 0,
       .custom = EVT_DATA_XR,
       .customdata = actiondata,
       .customdata_free = true,
diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c
index d7e62d549d0..cd41cffe1f0 100644
--- a/source/blender/windowmanager/intern/wm_gesture_ops.c
+++ b/source/blender/windowmanager/intern/wm_gesture_ops.c
@@ -509,7 +509,7 @@ static void gesture_tweak_modal(bContext *C, const wmEvent *event)
         tevent.val = val;
         tevent.modifier = gesture->event_modifier;
         tevent.keymodifier = gesture->event_keymodifier;
-        tevent.is_repeat = false;
+        tevent.flag = 0;
         /* mouse coords! */
 
         /* important we add immediately after this event, so future mouse releases
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index cdfb6a81596..242b8dc2968 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -1213,7 +1213,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
         wm_event_init_from_window(win, &event);
         event.type = MOUSEMOVE;
         copy_v2_v2_int(event.prev_xy, event.xy);
-        event.is_repeat = false;
+        event.flag = 0;
 
         wm_event_add(win, &event);
 
@@ -1344,7 +1344,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
         /* activate region */
         event.type = MOUSEMOVE;
         copy_v2_v2_int(event.prev_xy, event.xy);
-        event.is_repeat = false;
+        event.flag = 0;
 
         /* No context change! C->wm->windrawable is drawable, or for area queues. */
         wm->winactive = win;
@@ -1485,7 +1485,7 @@ static bool wm_window_timer(const bContext *C)
         event.type = wt->event_type;
         event.val = KM_NOTHING;
         event.keymodifier = 0;
-        event.is_repeat = false;
+        event.flag = 0;
         event.custom = EVT_DATA_TIMER;
         event.customdata = wt;
         wm_event_add(win, &event);
-- 
cgit v1.2.3


From a4e8b3a9a9fb0554184462d772c88d918bd374b0 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 14:09:03 +1100
Subject: Cleanup: remove check_deprecated.py

This was temporarily added back as the build-bot was running it.
---
 tests/check_deprecated.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 tests/check_deprecated.py

diff --git a/tests/check_deprecated.py b/tests/check_deprecated.py
deleted file mode 100644
index e69de29bb2d..00000000000
-- 
cgit v1.2.3


From 91de337dc5704d6a74a7aed76e2487187e559e8d Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Mon, 28 Feb 2022 23:45:59 -0500
Subject: Cmake: Re-enable tissue add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 0297fe63365..22903cfae64 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -393,7 +393,6 @@ if(WITH_PYTHON)
 	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
 	  PATTERN "addons/amaranth" EXCLUDE
 	  PATTERN "addons/mesh_tiny_cad" EXCLUDE
-	  PATTERN "addons/mesh_tissue" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From f0bfceb96d86062245c673938a8272276c1fcacf Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 17:22:35 +1100
Subject: Cleanup: remove unused code

---
 source/blender/makesdna/DNA_curve_types.h            |  8 --------
 source/blender/windowmanager/WM_keymap.h             |  9 ---------
 .../blender/windowmanager/intern/wm_keymap_utils.c   | 20 --------------------
 3 files changed, 37 deletions(-)

diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h
index 14a06f197bf..66206bcfddd 100644
--- a/source/blender/makesdna/DNA_curve_types.h
+++ b/source/blender/makesdna/DNA_curve_types.h
@@ -306,14 +306,6 @@ enum {
   CU_AUTOSPACE_EVALUATED = 2,
 };
 
-#if 0 /* Moved to overlay options in 2.8 */
-/* Curve.drawflag */
-enum {
-  CU_HIDE_HANDLES = 1 << 0,
-  CU_HIDE_NORMALS = 1 << 1,
-};
-#endif
-
 /* Curve.flag */
 enum {
   CU_3D = 1 << 0,
diff --git a/source/blender/windowmanager/WM_keymap.h b/source/blender/windowmanager/WM_keymap.h
index 32315d72ba7..6e07fd7fb32 100644
--- a/source/blender/windowmanager/WM_keymap.h
+++ b/source/blender/windowmanager/WM_keymap.h
@@ -98,15 +98,6 @@ wmKeyMapItem *WM_keymap_add_panel(
 wmKeyMapItem *WM_keymap_add_tool(
     struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
 
-/** Useful for mapping numbers to an enum. */
-void WM_keymap_add_context_enum_set_items(wmKeyMap *keymap,
-                                          const struct EnumPropertyItem *items,
-                                          const char *data_path,
-                                          int type_start,
-                                          int val,
-                                          int modifier,
-                                          int keymodifier);
-
 wmKeyMap *WM_keymap_guess_from_context(const struct bContext *C);
 
 /**
diff --git a/source/blender/windowmanager/intern/wm_keymap_utils.c b/source/blender/windowmanager/intern/wm_keymap_utils.c
index eac8a1f5211..4bd5fea91e6 100644
--- a/source/blender/windowmanager/intern/wm_keymap_utils.c
+++ b/source/blender/windowmanager/intern/wm_keymap_utils.c
@@ -67,26 +67,6 @@ wmKeyMapItem *WM_keymap_add_tool(
   return kmi;
 }
 
-void WM_keymap_add_context_enum_set_items(wmKeyMap *keymap,
-                                          const EnumPropertyItem *items,
-                                          const char *data_path,
-                                          int type_start,
-                                          int val,
-                                          int modifier,
-                                          int keymodifier)
-{
-  for (int i = 0, type_offset = 0; items[i].identifier; i++) {
-    if (items[i].identifier[0] == '\0') {
-      continue;
-    }
-    wmKeyMapItem *kmi = WM_keymap_add_item(
-        keymap, "WM_OT_context_set_enum", type_start + type_offset, val, modifier, keymodifier);
-    RNA_string_set(kmi->ptr, "data_path", data_path);
-    RNA_string_set(kmi->ptr, "value", items[i].identifier);
-    type_offset += 1;
-  }
-}
-
 /** \} */
 
 /* -------------------------------------------------------------------- */
-- 
cgit v1.2.3


From 5e9c1feb8aff0ca40eff6689f9bf6a9678711a9e Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 08:40:08 +0100
Subject: Image Engine: Performance 8 byte images.

Previously we used to cache a float image representation of the image in
rect_float. This adds some incorrect behavior as many areas only expect
one of these buffers to be used.

This patch stores float buffers inside the image engine. This is done per
instance. In the future we should consider making a global cache.
---
 .../draw/engines/image/image_buffer_cache.hh       | 131 +++++++++++++++++++++
 .../draw/engines/image/image_drawing_mode.hh       |  64 ++++++----
 .../draw/engines/image/image_instance_data.hh      |   9 ++
 source/blender/draw/engines/image/image_usage.hh   |   3 +
 source/blender/imbuf/IMB_imbuf.h                   |   3 +
 source/blender/imbuf/intern/divers.c               |  83 +++++++++----
 6 files changed, 250 insertions(+), 43 deletions(-)
 create mode 100644 source/blender/draw/engines/image/image_buffer_cache.hh

diff --git a/source/blender/draw/engines/image/image_buffer_cache.hh b/source/blender/draw/engines/image/image_buffer_cache.hh
new file mode 100644
index 00000000000..ef11551c879
--- /dev/null
+++ b/source/blender/draw/engines/image/image_buffer_cache.hh
@@ -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.
+ *
+ * Copyright 2022, Blender Foundation.
+ */
+
+/** \file
+ * \ingroup draw_engine
+ */
+
+#pragma once
+
+#include "BLI_vector.hh"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+struct FloatImageBuffer {
+  ImBuf *source_buffer = nullptr;
+  ImBuf *float_buffer = nullptr;
+  bool is_used = true;
+
+  FloatImageBuffer(ImBuf *source_buffer, ImBuf *float_buffer)
+      : source_buffer(source_buffer), float_buffer(float_buffer)
+  {
+  }
+
+  FloatImageBuffer(FloatImageBuffer &&other) noexcept
+  {
+    source_buffer = other.source_buffer;
+    float_buffer = other.float_buffer;
+    is_used = other.is_used;
+    other.source_buffer = nullptr;
+    other.float_buffer = nullptr;
+  }
+
+  virtual ~FloatImageBuffer()
+  {
+    IMB_freeImBuf(float_buffer);
+    float_buffer = nullptr;
+    source_buffer = nullptr;
+  }
+
+  FloatImageBuffer &operator=(FloatImageBuffer &&other) noexcept
+  {
+    this->source_buffer = other.source_buffer;
+    this->float_buffer = other.float_buffer;
+    is_used = other.is_used;
+    other.source_buffer = nullptr;
+    other.float_buffer = nullptr;
+    return *this;
+  }
+};
+
+struct FloatBufferCache {
+ private:
+  blender::Vector cache_;
+
+ public:
+  ImBuf *ensure_float_buffer(ImBuf *image_buffer)
+  {
+    /* Check if we can use the float buffer of the given image_buffer. */
+    if (image_buffer->rect_float != nullptr) {
+      return image_buffer;
+    }
+
+    /* Do we have a cached float buffer. */
+    for (FloatImageBuffer &item : cache_) {
+      if (item.source_buffer == image_buffer) {
+        item.is_used = true;
+        return item.float_buffer;
+      }
+    }
+
+    /* Generate a new float buffer. */
+    IMB_float_from_rect(image_buffer);
+    ImBuf *new_imbuf = IMB_allocImBuf(image_buffer->x, image_buffer->y, image_buffer->planes, 0);
+    new_imbuf->rect_float = image_buffer->rect_float;
+    new_imbuf->flags |= IB_rectfloat;
+    new_imbuf->mall |= IB_rectfloat;
+    image_buffer->rect_float = nullptr;
+    image_buffer->flags &= ~IB_rectfloat;
+    image_buffer->mall &= ~IB_rectfloat;
+
+    cache_.append(FloatImageBuffer(image_buffer, new_imbuf));
+    return new_imbuf;
+  }
+
+  void reset_usage_flags()
+  {
+    for (FloatImageBuffer &buffer : cache_) {
+      buffer.is_used = false;
+    }
+  }
+
+  void mark_used(const ImBuf *image_buffer)
+  {
+    for (FloatImageBuffer &item : cache_) {
+      if (item.source_buffer == image_buffer) {
+        item.is_used = true;
+        return;
+      }
+    }
+  }
+
+  void remove_unused_buffers()
+  {
+    for (int64_t i = cache_.size() - 1; i >= 0; i--) {
+      if (!cache_[i].is_used) {
+        cache_.remove_and_reorder(i);
+      }
+    }
+  }
+
+  void clear()
+  {
+    cache_.clear();
+  }
+};
diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh
index ccb0f3e963a..f0f7f221069 100644
--- a/source/blender/draw/engines/image/image_drawing_mode.hh
+++ b/source/blender/draw/engines/image/image_drawing_mode.hh
@@ -172,6 +172,7 @@ template class ScreenSpaceDrawingMode : public AbstractD
         if (tile_buffer == nullptr) {
           continue;
         }
+        instance_data.float_buffers.mark_used(tile_buffer);
         BKE_image_release_ibuf(image, tile_buffer, lock);
 
         DRWShadingGroup *shsub = DRW_shgroup_create_sub(shgrp);
@@ -199,12 +200,14 @@ template class ScreenSpaceDrawingMode : public AbstractD
     switch (changes.get_result_code()) {
       case ePartialUpdateCollectResult::FullUpdateNeeded:
         instance_data.mark_all_texture_slots_dirty();
+        instance_data.float_buffers.clear();
         break;
       case ePartialUpdateCollectResult::NoChangesDetected:
         break;
       case ePartialUpdateCollectResult::PartialChangesDetected:
         /* Partial update when wrap repeat is enabled is not supported. */
         if (instance_data.flags.do_tile_drawing) {
+          instance_data.float_buffers.clear();
           instance_data.mark_all_texture_slots_dirty();
         }
         else {
@@ -215,6 +218,33 @@ template class ScreenSpaceDrawingMode : public AbstractD
     do_full_update_for_dirty_textures(instance_data, image_user);
   }
 
+  /**
+   * Update the float buffer.
+   *
+   * TODO(jbakker): This is a very expensive operation and should be optimized to perform the
+   * color space conversion + alpha premultiplication on a part of the buffer.
+   * Basically perform a float_from_rect on a given rectangle.
+   */
+  void do_partial_update_float_buffer(
+      ImBuf *float_buffer, PartialUpdateChecker::CollectResult &iterator) const
+  {
+#if 0
+    ImBuf *src = iterator.tile_data.tile_buffer;
+    src->rect_float = float_buffer->rect_float;
+    IMB_float_from_rect(src);
+
+    src->rect_float = nullptr;
+#else
+    ImBuf *src = iterator.tile_data.tile_buffer;
+    BLI_assert(float_buffer->float_rect != nullptr);
+    BLI_assert(float_buffer->rect == nullptr);
+    BLI_assert(src->float_rect == nullptr);
+    BLI_assert(src->rect != nullptr);
+    IMB_float_from_rect_ex(float_buffer, src, &iterator.changed_region.region);
+
+#endif
+  }
+
   void do_partial_update(PartialUpdateChecker::CollectResult &iterator,
                          IMAGE_InstanceData &instance_data) const
   {
@@ -223,7 +253,11 @@ template class ScreenSpaceDrawingMode : public AbstractD
       if (iterator.tile_data.tile_buffer == nullptr) {
         continue;
       }
-      const bool do_free_float_buffer = ensure_float_buffer(*iterator.tile_data.tile_buffer);
+      ImBuf *tile_buffer = ensure_float_buffer(instance_data, iterator.tile_data.tile_buffer);
+      if (tile_buffer != iterator.tile_data.tile_buffer) {
+        do_partial_update_float_buffer(tile_buffer, iterator);
+      }
+
       const float tile_width = static_cast(iterator.tile_data.tile_buffer->x);
       const float tile_height = static_cast(iterator.tile_data.tile_buffer->y);
 
@@ -299,7 +333,6 @@ template class ScreenSpaceDrawingMode : public AbstractD
             &extracted_buffer, texture_region_width, texture_region_height, 32, IB_rectfloat);
 
         int offset = 0;
-        ImBuf *tile_buffer = iterator.tile_data.tile_buffer;
         for (int y = gpu_texture_region_to_update.ymin; y < gpu_texture_region_to_update.ymax;
              y++) {
           float yf = y / (float)texture_height;
@@ -330,10 +363,6 @@ template class ScreenSpaceDrawingMode : public AbstractD
                                0);
         imb_freerectImbuf_all(&extracted_buffer);
       }
-
-      if (do_free_float_buffer) {
-        imb_freerectfloatImBuf(iterator.tile_data.tile_buffer);
-      }
     }
   }
 
@@ -392,16 +421,12 @@ template class ScreenSpaceDrawingMode : public AbstractD
    * rect_float as the refcounter isn't 0. To work around this we destruct any created local
    * buffers ourself.
    */
-  bool ensure_float_buffer(ImBuf &image_buffer) const
+  ImBuf *ensure_float_buffer(IMAGE_InstanceData &instance_data, ImBuf *image_buffer) const
   {
-    if (image_buffer.rect_float == nullptr) {
-      IMB_float_from_rect(&image_buffer);
-      return true;
-    }
-    return false;
+    return instance_data.float_buffers.ensure_float_buffer(image_buffer);
   }
 
-  void do_full_update_texture_slot(const IMAGE_InstanceData &instance_data,
+  void do_full_update_texture_slot(IMAGE_InstanceData &instance_data,
                                    const TextureInfo &texture_info,
                                    ImBuf &texture_buffer,
                                    ImBuf &tile_buffer,
@@ -409,7 +434,7 @@ template class ScreenSpaceDrawingMode : public AbstractD
   {
     const int texture_width = texture_buffer.x;
     const int texture_height = texture_buffer.y;
-    const bool do_free_float_buffer = ensure_float_buffer(tile_buffer);
+    ImBuf *float_tile_buffer = ensure_float_buffer(instance_data, &tile_buffer);
 
     /* IMB_transform works in a non-consistent space. This should be documented or fixed!.
      * Construct a variant of the info_uv_to_texture that adds the texel space
@@ -440,16 +465,12 @@ template class ScreenSpaceDrawingMode : public AbstractD
       transform_mode = IMB_TRANSFORM_MODE_CROP_SRC;
     }
 
-    IMB_transform(&tile_buffer,
+    IMB_transform(float_tile_buffer,
                   &texture_buffer,
                   transform_mode,
                   IMB_FILTER_NEAREST,
                   uv_to_texel,
                   crop_rect_ptr);
-
-    if (do_free_float_buffer) {
-      imb_freerectfloatImBuf(&tile_buffer);
-    }
   }
 
  public:
@@ -468,6 +489,7 @@ template class ScreenSpaceDrawingMode : public AbstractD
 
     instance_data->partial_update.ensure_image(image);
     instance_data->clear_dirty_flag();
+    instance_data->float_buffers.reset_usage_flags();
 
     /* Step: Find out which screen space textures are needed to draw on the screen. Remove the
      * screen space textures that aren't needed. */
@@ -488,8 +510,10 @@ template class ScreenSpaceDrawingMode : public AbstractD
     add_shgroups(instance_data);
   }
 
-  void draw_finish(IMAGE_Data *UNUSED(vedata)) const override
+  void draw_finish(IMAGE_Data *vedata) const override
   {
+    IMAGE_InstanceData *instance_data = vedata->instance_data;
+    instance_data->float_buffers.remove_unused_buffers();
   }
 
   void draw_scene(IMAGE_Data *vedata) const override
diff --git a/source/blender/draw/engines/image/image_instance_data.hh b/source/blender/draw/engines/image/image_instance_data.hh
index 420f9396f3f..f8f20fbe178 100644
--- a/source/blender/draw/engines/image/image_instance_data.hh
+++ b/source/blender/draw/engines/image/image_instance_data.hh
@@ -23,6 +23,7 @@
 #pragma once
 
 #include "image_batches.hh"
+#include "image_buffer_cache.hh"
 #include "image_partial_updater.hh"
 #include "image_private.hh"
 #include "image_shader_params.hh"
@@ -63,11 +64,18 @@ struct IMAGE_InstanceData {
     DRWPass *depth_pass;
   } passes;
 
+  /**
+   * Cache containing the float buffers when drawing byte images.
+   */
+  FloatBufferCache float_buffers;
+
   /** \brief Transform matrix to convert a normalized screen space coordinates to texture space. */
   float ss_to_texture[4][4];
   TextureInfo texture_infos[SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN];
 
  public:
+  virtual ~IMAGE_InstanceData() = default;
+
   void clear_dirty_flag()
   {
     reset_dirty_flag(false);
@@ -117,6 +125,7 @@ struct IMAGE_InstanceData {
     if (last_usage != usage) {
       last_usage = usage;
       reset_dirty_flag(true);
+      float_buffers.clear();
     }
   }
 
diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh
index 1eadee4481d..bbd56e3db7a 100644
--- a/source/blender/draw/engines/image/image_usage.hh
+++ b/source/blender/draw/engines/image/image_usage.hh
@@ -38,6 +38,8 @@ struct ImageUsage {
   /** IMA_ALPHA_* */
   char alpha_mode;
 
+  const void *last_image = nullptr;
+
   ImageUsage() = default;
   ImageUsage(const struct Image *image, const struct ImageUser *image_user)
   {
@@ -46,6 +48,7 @@ struct ImageUsage {
     view = image_user ? image_user->multi_index : 0;
     colorspace_settings = image->colorspace_settings;
     alpha_mode = image->alpha_mode;
+    last_image = static_cast(image);
   }
 
   bool operator==(const ImageUsage &other) const
diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h
index a557d7dc6d1..2f0dc6345e5 100644
--- a/source/blender/imbuf/IMB_imbuf.h
+++ b/source/blender/imbuf/IMB_imbuf.h
@@ -576,6 +576,9 @@ bool IMB_alpha_affects_rgb(const struct ImBuf *ibuf);
  * Create char buffer, color corrected if necessary, for ImBufs that lack one.
  */
 void IMB_rect_from_float(struct ImBuf *ibuf);
+void IMB_float_from_rect_ex(struct ImBuf *dst,
+                            const struct ImBuf *src,
+                            const struct rcti *region_to_update);
 void IMB_float_from_rect(struct ImBuf *ibuf);
 /**
  * No profile conversion.
diff --git a/source/blender/imbuf/intern/divers.c b/source/blender/imbuf/intern/divers.c
index f23748e59a2..c4de30660e2 100644
--- a/source/blender/imbuf/intern/divers.c
+++ b/source/blender/imbuf/intern/divers.c
@@ -23,6 +23,7 @@
  */
 
 #include "BLI_math.h"
+#include "BLI_rect.h"
 #include "BLI_utildefines.h"
 
 #include "IMB_filter.h"
@@ -769,6 +770,61 @@ void IMB_rect_from_float(ImBuf *ibuf)
   ibuf->userflags &= ~IB_RECT_INVALID;
 }
 
+void IMB_float_from_rect_ex(struct ImBuf *dst,
+                            const struct ImBuf *src,
+                            const rcti *region_to_update)
+{
+  BLI_assert_msg(dst->rect_float != NULL,
+                 "Destination buffer should have a float buffer assigned.");
+  BLI_assert_msg(src->rect != NULL, "Source buffer should have a byte buffer assigned.");
+  BLI_assert_msg(dst->x == src->x, "Source and destination buffer should have the same dimension");
+  BLI_assert_msg(dst->y == src->y, "Source and destination buffer should have the same dimension");
+  BLI_assert_msg(dst->channels = 4, "Destination buffer should have 4 channels.");
+  BLI_assert_msg(region_to_update->xmin >= 0,
+                 "Region to update should be clipped to the given buffers.");
+  BLI_assert_msg(region_to_update->ymin >= 0,
+                 "Region to update should be clipped to the given buffers.");
+  BLI_assert_msg(region_to_update->xmax < dst->x,
+                 "Region to update should be clipped to the given buffers.");
+  BLI_assert_msg(region_to_update->ymax < dst->y,
+                 "Region to update should be clipped to the given buffers.");
+
+  float *rect_float = dst->rect_float;
+  rect_float += (region_to_update->xmin + region_to_update->ymin * dst->x) * 4;
+  unsigned char *rect = (unsigned char *)src->rect;
+  rect += (region_to_update->xmin + region_to_update->ymin * dst->x) * 4;
+  const int region_width = BLI_rcti_size_x(region_to_update);
+  const int region_height = BLI_rcti_size_y(region_to_update);
+
+  /* Convert byte buffer to float buffer without color or alpha conversion. */
+  IMB_buffer_float_from_byte(rect_float,
+                             rect,
+                             IB_PROFILE_SRGB,
+                             IB_PROFILE_SRGB,
+                             false,
+                             region_width,
+                             region_height,
+                             src->x,
+                             dst->x);
+
+  /* Perform color space conversion from rect color space to linear. */
+  float *float_ptr = rect_float;
+  for (int i = 0; i < region_height; i++) {
+    IMB_colormanagement_colorspace_to_scene_linear(
+        float_ptr, region_width, 1, dst->channels, src->rect_colorspace, false);
+    float_ptr += 4 * dst->x;
+  }
+
+  /* Perform alpha conversion. */
+  if (IMB_alpha_affects_rgb(src)) {
+    float_ptr = rect_float;
+    for (int i = 0; i < region_height; i++) {
+      IMB_premultiply_rect_float(float_ptr, dst->channels, region_width, 1);
+      float_ptr += 4 * dst->x;
+    }
+  }
+}
+
 void IMB_float_from_rect(ImBuf *ibuf)
 {
   float *rect_float;
@@ -792,33 +848,14 @@ void IMB_float_from_rect(ImBuf *ibuf)
     }
 
     ibuf->channels = 4;
-  }
-
-  /* first, create float buffer in non-linear space */
-  IMB_buffer_float_from_byte(rect_float,
-                             (unsigned char *)ibuf->rect,
-                             IB_PROFILE_SRGB,
-                             IB_PROFILE_SRGB,
-                             false,
-                             ibuf->x,
-                             ibuf->y,
-                             ibuf->x,
-                             ibuf->x);
-
-  /* then make float be in linear space */
-  IMB_colormanagement_colorspace_to_scene_linear(
-      rect_float, ibuf->x, ibuf->y, ibuf->channels, ibuf->rect_colorspace, false);
-
-  /* byte buffer is straight alpha, float should always be premul */
-  if (IMB_alpha_affects_rgb(ibuf)) {
-    IMB_premultiply_rect_float(rect_float, ibuf->channels, ibuf->x, ibuf->y);
-  }
-
-  if (ibuf->rect_float == NULL) {
     ibuf->rect_float = rect_float;
     ibuf->mall |= IB_rectfloat;
     ibuf->flags |= IB_rectfloat;
   }
+
+  rcti region_to_update;
+  BLI_rcti_init(®ion_to_update, 0, ibuf->x, 0, ibuf->y);
+  IMB_float_from_rect_ex(ibuf, ibuf, ®ion_to_update);
 }
 
 /** \} */
-- 
cgit v1.2.3


From ead84d2ba6895dd0d9e62711eef4ef46ab9b3fc2 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 08:53:23 +0100
Subject: Fix building error in previous commit.

---
 .../blender/draw/engines/image/image_drawing_mode.hh  | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh
index f0f7f221069..4564ef87025 100644
--- a/source/blender/draw/engines/image/image_drawing_mode.hh
+++ b/source/blender/draw/engines/image/image_drawing_mode.hh
@@ -219,30 +219,17 @@ template class ScreenSpaceDrawingMode : public AbstractD
   }
 
   /**
-   * Update the float buffer.
-   *
-   * TODO(jbakker): This is a very expensive operation and should be optimized to perform the
-   * color space conversion + alpha premultiplication on a part of the buffer.
-   * Basically perform a float_from_rect on a given rectangle.
+   * Update the float buffer in the region given by the partial update checker.
    */
   void do_partial_update_float_buffer(
       ImBuf *float_buffer, PartialUpdateChecker::CollectResult &iterator) const
   {
-#if 0
-    ImBuf *src = iterator.tile_data.tile_buffer;
-    src->rect_float = float_buffer->rect_float;
-    IMB_float_from_rect(src);
-
-    src->rect_float = nullptr;
-#else
     ImBuf *src = iterator.tile_data.tile_buffer;
-    BLI_assert(float_buffer->float_rect != nullptr);
+    BLI_assert(float_buffer->rect_float != nullptr);
     BLI_assert(float_buffer->rect == nullptr);
-    BLI_assert(src->float_rect == nullptr);
+    BLI_assert(src->rect_float == nullptr);
     BLI_assert(src->rect != nullptr);
     IMB_float_from_rect_ex(float_buffer, src, &iterator.changed_region.region);
-
-#endif
   }
 
   void do_partial_update(PartialUpdateChecker::CollectResult &iterator,
-- 
cgit v1.2.3


From 34f6a9943333f4b6c9727efb5db7bca1ffc7c531 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 08:56:58 +0100
Subject: Fix crash triggered by an introduced assert.

---
 source/blender/imbuf/intern/divers.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/imbuf/intern/divers.c b/source/blender/imbuf/intern/divers.c
index c4de30660e2..5b4b731027d 100644
--- a/source/blender/imbuf/intern/divers.c
+++ b/source/blender/imbuf/intern/divers.c
@@ -784,9 +784,9 @@ void IMB_float_from_rect_ex(struct ImBuf *dst,
                  "Region to update should be clipped to the given buffers.");
   BLI_assert_msg(region_to_update->ymin >= 0,
                  "Region to update should be clipped to the given buffers.");
-  BLI_assert_msg(region_to_update->xmax < dst->x,
+  BLI_assert_msg(region_to_update->xmax <= dst->x,
                  "Region to update should be clipped to the given buffers.");
-  BLI_assert_msg(region_to_update->ymax < dst->y,
+  BLI_assert_msg(region_to_update->ymax <= dst->y,
                  "Region to update should be clipped to the given buffers.");
 
   float *rect_float = dst->rect_float;
-- 
cgit v1.2.3


From 4a4701b43c53d1a4946e224821ba398548b22043 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 09:32:58 +0100
Subject: Fix painting on none 256 aligned images.

Internally the update tiles are 256x256. Due to some miscalculations
tiles were not generated correctly if the dimension of the image wasn't
a multifold of 256.
---
 source/blender/blenkernel/intern/image_partial_update.cc |  4 ++--
 source/blender/draw/engines/image/image_drawing_mode.hh  | 16 +++++++++++++++-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/source/blender/blenkernel/intern/image_partial_update.cc b/source/blender/blenkernel/intern/image_partial_update.cc
index 7e187c2014e..bec3c193af5 100644
--- a/source/blender/blenkernel/intern/image_partial_update.cc
+++ b/source/blender/blenkernel/intern/image_partial_update.cc
@@ -213,8 +213,8 @@ struct TileChangeset {
     tile_width = image_buffer->x;
     tile_height = image_buffer->y;
 
-    int chunk_x_len = tile_width / CHUNK_SIZE;
-    int chunk_y_len = tile_height / CHUNK_SIZE;
+    int chunk_x_len = (tile_width + CHUNK_SIZE - 1) / CHUNK_SIZE;
+    int chunk_y_len = (tile_height + CHUNK_SIZE - 1) / CHUNK_SIZE;
     init_chunks(chunk_x_len, chunk_y_len);
     return true;
   }
diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh
index 4564ef87025..267b0477a29 100644
--- a/source/blender/draw/engines/image/image_drawing_mode.hh
+++ b/source/blender/draw/engines/image/image_drawing_mode.hh
@@ -229,7 +229,21 @@ template class ScreenSpaceDrawingMode : public AbstractD
     BLI_assert(float_buffer->rect == nullptr);
     BLI_assert(src->rect_float == nullptr);
     BLI_assert(src->rect != nullptr);
-    IMB_float_from_rect_ex(float_buffer, src, &iterator.changed_region.region);
+
+    /* Calculate the overlap between the updated region and the buffer size. Partial Update Checker
+     * always returns a tile (256x256). Which could lay partially outside the buffer when using
+     * different resolutions.
+     */
+    rcti buffer_rect;
+    BLI_rcti_init(&buffer_rect, 0, float_buffer->x, 0, float_buffer->y);
+    rcti clipped_update_region;
+    const bool has_overlap = BLI_rcti_isect(
+        &buffer_rect, &iterator.changed_region.region, &clipped_update_region);
+    if (!has_overlap) {
+      return;
+    }
+
+    IMB_float_from_rect_ex(float_buffer, src, &clipped_update_region);
   }
 
   void do_partial_update(PartialUpdateChecker::CollectResult &iterator,
-- 
cgit v1.2.3


From d743ef91df743941a859a98fb6a94e3982c234b5 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 10:45:19 +0100
Subject: Fix 3d texture painting artifacts.

When dimension of images aren't a multifold of 256 parts of the gpu
textures are not updated. This patch will calculate the correct part of
the image that needs to be reuploaded.
---
 source/blender/blenkernel/intern/image_gpu.cc | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc
index 42dcfcd7aa9..d854c043a3e 100644
--- a/source/blender/blenkernel/intern/image_gpu.cc
+++ b/source/blender/blenkernel/intern/image_gpu.cc
@@ -338,19 +338,25 @@ static void image_gpu_texture_partial_update_changes_available(
     Image *image, PartialUpdateChecker::CollectResult &changes)
 {
   while (changes.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
-    const int tile_offset_x = changes.changed_region.region.xmin;
-    const int tile_offset_y = changes.changed_region.region.ymin;
-    const int tile_width = min_ii(changes.tile_data.tile_buffer->x,
-                                  BLI_rcti_size_x(&changes.changed_region.region));
-    const int tile_height = min_ii(changes.tile_data.tile_buffer->y,
-                                   BLI_rcti_size_y(&changes.changed_region.region));
+    /* Calculate the clipping region with the tile buffer.
+     * TODO(jbakker): should become part of ImageTileData to deduplicate with image engine. */
+    rcti buffer_rect;
+    BLI_rcti_init(
+        &buffer_rect, 0, changes.tile_data.tile_buffer->x, 0, changes.tile_data.tile_buffer->y);
+    rcti clipped_update_region;
+    const bool has_overlap = BLI_rcti_isect(
+        &buffer_rect, &changes.changed_region.region, &clipped_update_region);
+    if (!has_overlap) {
+      continue;
+    }
+
     image_update_gputexture_ex(image,
                                changes.tile_data.tile,
                                changes.tile_data.tile_buffer,
-                               tile_offset_x,
-                               tile_offset_y,
-                               tile_width,
-                               tile_height);
+                               clipped_update_region.xmin,
+                               clipped_update_region.ymin,
+                               BLI_rcti_size_x(&clipped_update_region),
+                               BLI_rcti_size_y(&clipped_update_region));
   }
 }
 
-- 
cgit v1.2.3


From a5214212cffba14aa99c3eac49809709c94125f1 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 10:51:07 +0100
Subject: Fix T96030: ImageEditor not updated for generated images.

---
 source/blender/makesrna/intern/rna_image.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c
index a533ea88436..c586ae319ee 100644
--- a/source/blender/makesrna/intern/rna_image.c
+++ b/source/blender/makesrna/intern/rna_image.c
@@ -117,6 +117,7 @@ static void rna_Image_generated_update(Main *bmain, Scene *UNUSED(scene), Pointe
 {
   Image *ima = (Image *)ptr->owner_id;
   BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_FREE);
+  BKE_image_partial_update_mark_full_update(ima);
 }
 
 static void rna_Image_colormanage_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
@@ -155,6 +156,7 @@ static void rna_Image_views_format_update(Main *bmain, Scene *scene, PointerRNA
   }
 
   BKE_image_release_ibuf(ima, ibuf, lock);
+  BKE_image_partial_update_mark_full_update(ima);
 }
 
 static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr)
-- 
cgit v1.2.3


From eec1c162000546d64232ad63e89cb0b54ac04881 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 11:04:09 +0100
Subject: Fix T96030: Update Image editor after reload.

---
 source/blender/blenkernel/intern/image.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c
index bf6ede77a79..487a018b9b7 100644
--- a/source/blender/blenkernel/intern/image.c
+++ b/source/blender/blenkernel/intern/image.c
@@ -3585,6 +3585,7 @@ static void image_tag_reload(Image *ima, ID *iuser_id, ImageUser *iuser, void *c
       /* Must copy image user changes to CoW data-block. */
       DEG_id_tag_update(iuser_id, ID_RECALC_COPY_ON_WRITE);
     }
+    BKE_image_partial_update_mark_full_update(ima);
   }
 }
 
-- 
cgit v1.2.3


From 476fe7d164225add7d91b2d619e2764c2c5a1a04 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 11:20:54 +0100
Subject: Fix T95979: Bake doesn't update Image editor.

---
 source/blender/editors/object/object_bake_api.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c
index cef99017b9c..0acaa8924cb 100644
--- a/source/blender/editors/object/object_bake_api.c
+++ b/source/blender/editors/object/object_bake_api.c
@@ -317,6 +317,7 @@ static void bake_targets_refresh(BakeTargets *targets)
     Image *ima = targets->images[i].image;
 
     if (ima) {
+      BKE_image_partial_update_mark_full_update(ima);
       LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
         BKE_image_free_gputextures(ima);
         DEG_id_tag_update(&ima->id, 0);
-- 
cgit v1.2.3


From 9216cf9cb589f4d8ec0e79f0f0a160ce2c1dfb07 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 1 Mar 2022 11:34:22 +0100
Subject: Fix: dangling internal links after removing sockets

This is a follow up for rBd5e73fa13dd275fb9c76b1e41142ab086dd2e6be.
The issue was found with the file in T95997.
---
 source/blender/blenkernel/intern/node.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 40d0c24c9af..fbf5677e867 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -1973,6 +1973,8 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node)
     }
   }
 
+  BLI_freelistN(&node->internal_links);
+
   LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) {
     node_socket_free(sock, true);
     MEM_freeN(sock);
-- 
cgit v1.2.3


From cde5e12c0bcc55b7c19b625ebf7a6f29dbd833d7 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Tue, 1 Mar 2022 11:35:11 +0100
Subject: Fix T96097: Image editor tile drawing not working.

The image engine is depth aware when using tile drawing the depth is
only updated for the central image what lead to showing the background
on top of other areas.

Also makes sure that switching the tile drawing would lead to an update
of the texture slots.
---
 source/blender/draw/engines/image/image_drawing_mode.hh  | 8 ++++++--
 source/blender/draw/engines/image/image_instance_data.hh | 2 +-
 source/blender/draw/engines/image/image_usage.hh         | 6 ++++--
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh
index 267b0477a29..12d8607a8bd 100644
--- a/source/blender/draw/engines/image/image_drawing_mode.hh
+++ b/source/blender/draw/engines/image/image_drawing_mode.hh
@@ -507,7 +507,9 @@ template class ScreenSpaceDrawingMode : public AbstractD
 
     /* Step: Add the GPU textures to the shgroup. */
     instance_data->update_batches();
-    add_depth_shgroups(*instance_data, image, iuser);
+    if (!instance_data->flags.do_tile_drawing) {
+      add_depth_shgroups(*instance_data, image, iuser);
+    }
     add_shgroups(instance_data);
   }
 
@@ -523,8 +525,10 @@ template class ScreenSpaceDrawingMode : public AbstractD
 
     DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
     GPU_framebuffer_bind(dfbl->default_fb);
+
     static float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
-    GPU_framebuffer_clear_color_depth(dfbl->default_fb, clear_col, 1.0);
+    float clear_depth = instance_data->flags.do_tile_drawing ? 0.75 : 1.0f;
+    GPU_framebuffer_clear_color_depth(dfbl->default_fb, clear_col, clear_depth);
 
     DRW_view_set_active(instance_data->view);
     DRW_draw_pass(instance_data->passes.depth_pass);
diff --git a/source/blender/draw/engines/image/image_instance_data.hh b/source/blender/draw/engines/image/image_instance_data.hh
index f8f20fbe178..7aeb423071e 100644
--- a/source/blender/draw/engines/image/image_instance_data.hh
+++ b/source/blender/draw/engines/image/image_instance_data.hh
@@ -121,7 +121,7 @@ struct IMAGE_InstanceData {
 
   void update_image_usage(const ImageUser *image_user)
   {
-    ImageUsage usage(image, image_user);
+    ImageUsage usage(image, image_user, flags.do_tile_drawing);
     if (last_usage != usage) {
       last_usage = usage;
       reset_dirty_flag(true);
diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh
index bbd56e3db7a..a581731036e 100644
--- a/source/blender/draw/engines/image/image_usage.hh
+++ b/source/blender/draw/engines/image/image_usage.hh
@@ -24,7 +24,7 @@
 
 /**
  * ImageUsage contains data of the image and image user to identify changes that require a rebuild
- * of the texture slots.
+ * the texture slots.
  */
 struct ImageUsage {
   /** Render pass of the image that is used. */
@@ -37,11 +37,12 @@ struct ImageUsage {
   ColorManagedColorspaceSettings colorspace_settings;
   /** IMA_ALPHA_* */
   char alpha_mode;
+  bool last_tile_drawing;
 
   const void *last_image = nullptr;
 
   ImageUsage() = default;
-  ImageUsage(const struct Image *image, const struct ImageUser *image_user)
+  ImageUsage(const struct Image *image, const struct ImageUser *image_user, bool do_tile_drawing)
   {
     pass = image_user ? image_user->pass : 0;
     layer = image_user ? image_user->layer : 0;
@@ -49,6 +50,7 @@ struct ImageUsage {
     colorspace_settings = image->colorspace_settings;
     alpha_mode = image->alpha_mode;
     last_image = static_cast(image);
+    last_tile_drawing = do_tile_drawing;
   }
 
   bool operator==(const ImageUsage &other) const
-- 
cgit v1.2.3


From 90ec634135a0a79534fc34146c4860805279a3a9 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 21:33:16 +1100
Subject: Fix incorrect drag-event threshold when releasing modifiers early

db4313610cab18933c1b1b1348720ea241b9d91e added support for modifier
keys to be released while dragging.

The key release events set wmEvent.prev_type which is used select the
drag threshold, causing the wrong threshold to be used.

Add wmEvent.prev_click_type which is only set when the drag begins.
---
 source/blender/windowmanager/WM_types.h               | 16 +++++++++-------
 source/blender/windowmanager/intern/wm_event_query.c  |  4 ++--
 source/blender/windowmanager/intern/wm_event_system.c |  5 +++--
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index cdbaa2739ba..26462402e3d 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -636,6 +636,15 @@ typedef struct wmEvent {
   short prev_type;
   /** The previous value of `val`. */
   short prev_val;
+  /**
+   * The previous value of #wmEvent.xy,
+   * Unlike other previous state variables, this is set on any mouse motion.
+   * Use `prev_click` for the value at time of pressing.
+   */
+  int prev_xy[2];
+
+  /** The `type` at the point of the click action. */
+  short prev_click_type;
   /** The time when the key is pressed, see #PIL_check_seconds_timer. */
   double prev_click_time;
   /** The location when the key is pressed (used to enforce drag thresholds). */
@@ -645,13 +654,6 @@ typedef struct wmEvent {
   /** The `keymodifier` at the point of the click action. */
   short prev_click_keymodifier;
 
-  /**
-   * The previous value of #wmEvent.xy,
-   * Unlike other previous state variables, this is set on any mouse motion.
-   * Use `prev_click` for the value at time of pressing.
-   */
-  int prev_xy[2];
-
   /**
    * Modifier states.
    * #KM_SHIFT, #KM_CTRL, #KM_ALT & #KM_OSKEY is apple or windows-key.
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index 0d42d0a44c7..5e8b9b945c9 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -260,8 +260,8 @@ bool WM_cursor_test_motion_and_update(const int mval[2])
 int WM_event_drag_threshold(const struct wmEvent *event)
 {
   int drag_threshold;
-  if (ISMOUSE(event->prev_type)) {
-    BLI_assert(event->prev_type != MOUSEMOVE);
+  if (ISMOUSE(event->prev_click_type)) {
+    BLI_assert(event->prev_click_type != MOUSEMOVE);
     /* Using the previous type is important is we want to check the last pressed/released button,
      * The `event->type` would include #MOUSEMOVE which is always the case when dragging
      * and does not help us know which threshold to use. */
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 4e9a430b380..ee07e90f4de 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -4705,10 +4705,11 @@ static void wm_event_prev_values_set(wmEvent *event, wmEvent *event_state)
 static void wm_event_prev_click_set(wmEvent *event, wmEvent *event_state)
 {
   event->prev_click_time = event_state->prev_click_time = PIL_check_seconds_timer();
-  event->prev_click_xy[0] = event_state->prev_click_xy[0] = event_state->xy[0];
-  event->prev_click_xy[1] = event_state->prev_click_xy[1] = event_state->xy[1];
+  event->prev_click_type = event_state->prev_click_type = event_state->type;
   event->prev_click_modifier = event_state->prev_click_modifier = event_state->modifier;
   event->prev_click_keymodifier = event_state->prev_click_keymodifier = event_state->keymodifier;
+  event->prev_click_xy[0] = event_state->prev_click_xy[0] = event_state->xy[0];
+  event->prev_click_xy[1] = event_state->prev_click_xy[1] = event_state->xy[1];
 }
 
 static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event)
-- 
cgit v1.2.3


From 629f22f161abfbe492d71e08cc89651da683aded Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Thu, 24 Feb 2022 14:15:38 +0100
Subject: Fix T95997: Crash when entering edit mode

The issue was uncovered by the 0f89bcdbebf5, but the root cause goes
into a much earlier design violation happened in the code: the modifier
evaluation function is modifying input mesh, which is not something
what is ever expected.

Bring code closer to the older state where such modification is only
done for the object in edit mode.

---

From own tests works seems to work fine, but extra eyes and testing
is needed.

Differential Revision: https://developer.blender.org/D14191
---
 source/blender/blenkernel/intern/DerivedMesh.cc | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc
index 1fcf1bf1839..00a6fa6d178 100644
--- a/source/blender/blenkernel/intern/DerivedMesh.cc
+++ b/source/blender/blenkernel/intern/DerivedMesh.cc
@@ -1667,7 +1667,9 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph,
       }
       else {
         Mesh *me_orig = mesh_input;
-        if (me_orig->id.tag & LIB_TAG_COPIED_ON_WRITE) {
+        /* Modifying the input mesh is weak, however as there can only be one object in edit mode
+         * even if multiple are sharing the same mesh this should be thread safe. */
+        if ((me_orig->id.tag & LIB_TAG_COPIED_ON_WRITE) && (ob->mode & OB_MODE_EDIT)) {
           if (!BKE_mesh_runtime_ensure_edit_data(me_orig)) {
             BKE_mesh_runtime_reset_edit_data(me_orig);
           }
-- 
cgit v1.2.3


From d681d82d41c176951eb294a2d1dd413eb2fe8af2 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 1 Mar 2022 23:06:01 +1100
Subject: Fix T96035: Some tool settings do not work

Oversight in 74611e3555684a22e9a07bd0992a444b571b8083 missed updating
property access to use the tool identifier to lookup the property group.
---
 source/blender/windowmanager/WM_toolsystem.h        |  1 +
 source/blender/windowmanager/intern/wm_toolsystem.c | 16 +++++++++++++---
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/source/blender/windowmanager/WM_toolsystem.h b/source/blender/windowmanager/WM_toolsystem.h
index 32b9cf1d7ba..5cbe1e2da14 100644
--- a/source/blender/windowmanager/WM_toolsystem.h
+++ b/source/blender/windowmanager/WM_toolsystem.h
@@ -117,6 +117,7 @@ void WM_toolsystem_do_msg_notify_tag_refresh(struct bContext *C,
                                              struct wmMsgSubscribeKey *msg_key,
                                              struct wmMsgSubscribeValue *msg_val);
 
+struct IDProperty *WM_toolsystem_ref_properties_get_idprops(struct bToolRef *tref);
 struct IDProperty *WM_toolsystem_ref_properties_ensure_idprops(struct bToolRef *tref);
 void WM_toolsystem_ref_properties_ensure_ex(struct bToolRef *tref,
                                             const char *idname,
diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c
index fce11853030..df6f0a9e1ff 100644
--- a/source/blender/windowmanager/intern/wm_toolsystem.c
+++ b/source/blender/windowmanager/intern/wm_toolsystem.c
@@ -830,6 +830,15 @@ static IDProperty *idprops_ensure_named_group(IDProperty *group, const char *idn
   return prop;
 }
 
+IDProperty *WM_toolsystem_ref_properties_get_idprops(bToolRef *tref)
+{
+  IDProperty *group = tref->properties;
+  if (group == NULL) {
+    return NULL;
+  }
+  return IDP_GetPropertyFromGroup(group, tref->idname);
+}
+
 IDProperty *WM_toolsystem_ref_properties_ensure_idprops(bToolRef *tref)
 {
   if (tref->properties == NULL) {
@@ -844,7 +853,7 @@ bool WM_toolsystem_ref_properties_get_ex(bToolRef *tref,
                                          StructRNA *type,
                                          PointerRNA *r_ptr)
 {
-  IDProperty *group = tref->properties;
+  IDProperty *group = WM_toolsystem_ref_properties_get_idprops(tref);
   IDProperty *prop = group ? IDP_GetPropertyFromGroup(group, idname) : NULL;
   RNA_pointer_create(NULL, type, prop, r_ptr);
   return (prop != NULL);
@@ -873,8 +882,9 @@ void WM_toolsystem_ref_properties_init_for_keymap(bToolRef *tref,
     IDPropertyTemplate val = {0};
     dst_ptr->data = IDP_New(IDP_GROUP, &val, "wmOpItemProp");
   }
-  if (tref->properties != NULL) {
-    IDProperty *prop = IDP_GetPropertyFromGroup(tref->properties, ot->idname);
+  IDProperty *group = WM_toolsystem_ref_properties_get_idprops(tref);
+  if (group != NULL) {
+    IDProperty *prop = IDP_GetPropertyFromGroup(group, ot->idname);
     if (prop) {
       /* Important key-map items properties don't get overwritten by the tools.
        * - When a key-map item doesn't set a property, the tool-systems is used.
-- 
cgit v1.2.3


From 952a613d3843a7ab47bd8063da71c277ee0a013f Mon Sep 17 00:00:00 2001
From: Michael Jones 
Date: Tue, 22 Feb 2022 17:09:28 +0000
Subject: Cycles: Hide MetalRT checkbox for AMD GPUs

This patch hides the MetalRT checkbox for AMD GPUs, pending fixes for MetalRT argument encoding on AMD.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D14175
---
 intern/cycles/blender/addon/properties.py | 9 ++++++---
 intern/cycles/device/metal/device_impl.mm | 2 +-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py
index ef686fc0c70..84d0e95acd8 100644
--- a/intern/cycles/blender/addon/properties.py
+++ b/intern/cycles/blender/addon/properties.py
@@ -1527,9 +1527,12 @@ class CyclesPreferences(bpy.types.AddonPreferences):
             row.prop(self, "peer_memory")
 
         if compute_device_type == 'METAL':
-            row = layout.row()
-            row.use_property_split = True
-            row.prop(self, "use_metalrt")
+            import platform
+            # MetalRT only works on Apple Silicon at present, pending argument encoding fixes on AMD
+            if platform.machine() == 'arm64':
+                row = layout.row()
+                row.use_property_split = True
+                row.prop(self, "use_metalrt")
 
 
     def draw(self, context):
diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm
index 8ced0210e30..7291dd880ca 100644
--- a/intern/cycles/device/metal/device_impl.mm
+++ b/intern/cycles/device/metal/device_impl.mm
@@ -90,11 +90,11 @@ MetalDevice::MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profile
     }
     case METAL_GPU_APPLE: {
       max_threads_per_threadgroup = 512;
+      use_metalrt = info.use_metalrt;
       break;
     }
   }
 
-  use_metalrt = info.use_metalrt;
   if (auto metalrt = getenv("CYCLES_METALRT")) {
     use_metalrt = (atoi(metalrt) != 0);
   }
-- 
cgit v1.2.3


From f98d74c80de7b1cae1e5a963f33c51c49f478ba1 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 1 Mar 2022 11:40:25 -0500
Subject: Curves: Move curves primitive to object add code

Currently, any time a Curves data-block is created, the `curves_random`
function runs, filling it with 500 random curves, also adding a radius
attribute. This is just left over from the prototype in the initial
commit that added the type.

This commit moves the code that creates the random data to the curve
editors module, like the other primitives are organized.

Differential Revision: https://developer.blender.org/D14211
---
 source/blender/blenkernel/intern/curves.cc         | 46 -----------------
 source/blender/editors/curves/CMakeLists.txt       |  1 +
 source/blender/editors/curves/intern/curves_add.cc | 57 ++++++++++++++++++++++
 source/blender/editors/include/ED_curves.h         | 11 +++++
 source/blender/editors/object/CMakeLists.txt       |  1 +
 source/blender/editors/object/object_add.cc        |  6 +++
 6 files changed, 76 insertions(+), 46 deletions(-)
 create mode 100644 source/blender/editors/curves/intern/curves_add.cc

diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc
index d7783c76f65..c7aaf4718fe 100644
--- a/source/blender/blenkernel/intern/curves.cc
+++ b/source/blender/blenkernel/intern/curves.cc
@@ -50,7 +50,6 @@ using blender::Span;
 
 static const char *ATTR_POSITION = "position";
 
-static void curves_random(Curves *curves);
 static void update_custom_data_pointers(Curves &curves);
 
 static void curves_init_data(ID *id)
@@ -61,8 +60,6 @@ static void curves_init_data(ID *id)
   MEMCPY_STRUCT_AFTER(curves, DNA_struct_default_get(Curves), id);
 
   new (&curves->geometry) blender::bke::CurvesGeometry();
-
-  curves_random(curves);
 }
 
 static void curves_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag)
@@ -234,49 +231,6 @@ static void update_custom_data_pointers(Curves &curves)
   blender::bke::CurvesGeometry::wrap(curves.geometry).update_customdata_pointers();
 }
 
-static void curves_random(Curves *curves)
-{
-  const int numpoints = 8;
-
-  blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry);
-  geometry = blender::bke::CurvesGeometry(500 * numpoints, 500);
-
-  MutableSpan offsets = geometry.offsets();
-  MutableSpan positions = geometry.positions();
-
-  float *radius_data = (float *)CustomData_add_layer_named(
-      &geometry.point_data, CD_PROP_FLOAT, CD_DEFAULT, nullptr, geometry.point_size, "radius");
-  MutableSpan radii{radius_data, geometry.points_size()};
-
-  for (const int i : offsets.index_range()) {
-    offsets[i] = numpoints * i;
-  }
-
-  RandomNumberGenerator rng;
-
-  for (int i = 0; i < geometry.curve_size; i++) {
-    const IndexRange curve_range = geometry.range_for_curve(i);
-    MutableSpan curve_positions = positions.slice(curve_range);
-    MutableSpan curve_radii = radii.slice(curve_range);
-
-    const float theta = 2.0f * M_PI * rng.get_float();
-    const float phi = saacosf(2.0f * rng.get_float() - 1.0f);
-
-    float3 no = {std::sin(theta) * std::sin(phi), std::cos(theta) * std::sin(phi), std::cos(phi)};
-    no = blender::math::normalize(no);
-
-    float3 co = no;
-    for (int key = 0; key < numpoints; key++) {
-      float t = key / (float)(numpoints - 1);
-      curve_positions[key] = co;
-      curve_radii[key] = 0.02f * (1.0f - t);
-
-      float3 offset = float3(rng.get_float(), rng.get_float(), rng.get_float()) * 2.0f - 1.0f;
-      co += (offset + no) / numpoints;
-    }
-  }
-}
-
 void *BKE_curves_add(Main *bmain, const char *name)
 {
   Curves *curves = static_cast(BKE_id_new(bmain, ID_CV, name));
diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt
index 93b21a46916..1731d224b3e 100644
--- a/source/blender/editors/curves/CMakeLists.txt
+++ b/source/blender/editors/curves/CMakeLists.txt
@@ -14,6 +14,7 @@ set(INC
 )
 
 set(SRC
+  intern/curves_add.cc
   intern/curves_ops.cc
 )
 
diff --git a/source/blender/editors/curves/intern/curves_add.cc b/source/blender/editors/curves/intern/curves_add.cc
new file mode 100644
index 00000000000..9cde23451dc
--- /dev/null
+++ b/source/blender/editors/curves/intern/curves_add.cc
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup edcurves
+ */
+
+#include "BLI_rand.hh"
+
+#include "BKE_curves.hh"
+
+#include "ED_curves.h"
+
+namespace blender::ed::curves {
+
+bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int points_per_curve)
+{
+  bke::CurvesGeometry curves(points_per_curve * curves_size, curves_size);
+
+  MutableSpan offsets = curves.offsets();
+  MutableSpan positions = curves.positions();
+
+  float *radius_data = (float *)CustomData_add_layer_named(
+      &curves.point_data, CD_PROP_FLOAT, CD_DEFAULT, nullptr, curves.point_size, "radius");
+  MutableSpan radii{radius_data, curves.points_size()};
+
+  for (const int i : offsets.index_range()) {
+    offsets[i] = points_per_curve * i;
+  }
+
+  RandomNumberGenerator rng;
+
+  for (const int i : curves.curves_range()) {
+    const IndexRange curve_range = curves.range_for_curve(i);
+    MutableSpan curve_positions = positions.slice(curve_range);
+    MutableSpan curve_radii = radii.slice(curve_range);
+
+    const float theta = 2.0f * M_PI * rng.get_float();
+    const float phi = saacosf(2.0f * rng.get_float() - 1.0f);
+
+    float3 no = {std::sin(theta) * std::sin(phi), std::cos(theta) * std::sin(phi), std::cos(phi)};
+    no = math::normalize(no);
+
+    float3 co = no;
+    for (int key = 0; key < points_per_curve; key++) {
+      float t = key / (float)(points_per_curve - 1);
+      curve_positions[key] = co;
+      curve_radii[key] = 0.02f * (1.0f - t);
+
+      float3 offset = float3(rng.get_float(), rng.get_float(), rng.get_float()) * 2.0f - 1.0f;
+      co += (offset + no) / points_per_curve;
+    }
+  }
+
+  return curves;
+}
+
+}  // namespace blender::ed::curves
diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h
index 7316b045646..706563061d4 100644
--- a/source/blender/editors/include/ED_curves.h
+++ b/source/blender/editors/include/ED_curves.h
@@ -15,3 +15,14 @@ void ED_operatortypes_curves(void);
 #ifdef __cplusplus
 }
 #endif
+
+#ifdef __cplusplus
+
+#  include "BKE_curves.hh"
+
+namespace blender::ed::curves {
+
+bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve);
+
+}
+#endif
\ No newline at end of file
diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt
index a94387961ee..39ccadd1445 100644
--- a/source/blender/editors/object/CMakeLists.txt
+++ b/source/blender/editors/object/CMakeLists.txt
@@ -8,6 +8,7 @@ set(INC
   ../../blentranslation
   ../../bmesh
   ../../depsgraph
+  ../../functions
   ../../gpencil_modifiers
   ../../gpu
   ../../ikplugin
diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc
index 19db09961e8..7befad3b8d7 100644
--- a/source/blender/editors/object/object_add.cc
+++ b/source/blender/editors/object/object_add.cc
@@ -93,6 +93,7 @@
 
 #include "ED_armature.h"
 #include "ED_curve.h"
+#include "ED_curves.h"
 #include "ED_gpencil.h"
 #include "ED_mball.h"
 #include "ED_mesh.h"
@@ -1899,6 +1900,8 @@ static bool object_hair_curves_add_poll(bContext *C)
 
 static int object_hair_curves_add_exec(bContext *C, wmOperator *op)
 {
+  using namespace blender;
+
   ushort local_view_bits;
   float loc[3], rot[3];
   if (!ED_object_add_generic_get_opts(
@@ -1909,6 +1912,9 @@ static int object_hair_curves_add_exec(bContext *C, wmOperator *op)
   Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits);
   object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
 
+  Curves *curves_id = static_cast(object->data);
+  bke::CurvesGeometry::wrap(curves_id->geometry) = ed::curves::primitive_random_sphere(500, 8);
+
   return OPERATOR_FINISHED;
 }
 
-- 
cgit v1.2.3


From 89bf5d8ba98f4236f97777d14d3a6029f0471e42 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Tue, 1 Mar 2022 11:56:42 -0500
Subject: Cmake: Re-enable tiny cad add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 042e49c402b..01f3e6f52c6 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -382,7 +382,6 @@ if(WITH_PYTHON)
 
 	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
 	  PATTERN "addons/amaranth" EXCLUDE
-	  PATTERN "addons/mesh_tiny_cad" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From f085c2bab5b62a7ec188d7e5be93dfdd4b5418aa Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Tue, 1 Mar 2022 11:58:19 -0500
Subject: Cmake: Re-enable tiny cad add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 22903cfae64..2e626428826 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -392,7 +392,6 @@ if(WITH_PYTHON)
 
 	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
 	  PATTERN "addons/amaranth" EXCLUDE
-	  PATTERN "addons/mesh_tiny_cad" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From 444d57d440459304a248ba75e1936b69be4d09dd Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 1 Mar 2022 12:06:11 -0500
Subject: Geometry Nodes: Port most curve primitives to new data-block

Create `Curves` directly, instead of using the conversion from
`CurveEval`. This means that the `tilt` and `radius` attributes
don't need to be allocated. The old behavior is kept by using the
right defaults in the conversion to `CurveEval` later on.

The Bezier segment primitive isn't ported yet, because functions
to provide easy access to built-in attributes used for Bezier curves
haven't been added yet.

Differential Revision: https://developer.blender.org/D14212
---
 source/blender/blenkernel/BKE_curves.hh            |   5 +
 source/blender/blenkernel/intern/curves.cc         |   9 ++
 source/blender/editors/include/ED_curves.h         |   2 +-
 .../geometry/nodes/node_geo_curve_primitive_arc.cc | 126 +++++++++------------
 .../nodes/node_geo_curve_primitive_circle.cc       |  59 ++++------
 .../nodes/node_geo_curve_primitive_line.cc         |  63 +++++------
 .../node_geo_curve_primitive_quadratic_bezier.cc   |  27 ++---
 .../node_geo_curve_primitive_quadrilateral.cc      |  18 ++-
 .../nodes/node_geo_curve_primitive_spiral.cc       |  44 ++++---
 .../nodes/node_geo_curve_primitive_star.cc         |  37 +++---
 10 files changed, 172 insertions(+), 218 deletions(-)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 6fa7de49eb0..f3d9090d16b 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -158,4 +158,9 @@ class CurvesGeometry : public ::CurvesGeometry {
 
 Curves *curves_new_nomain(int point_size, int curves_size);
 
+/**
+ * Create a new curves data-block containing a single curve with the given length and type.
+ */
+Curves *curves_new_nomain_single(int point_size, CurveType type);
+
 }  // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc
index c7aaf4718fe..838f7f28e93 100644
--- a/source/blender/blenkernel/intern/curves.cc
+++ b/source/blender/blenkernel/intern/curves.cc
@@ -374,4 +374,13 @@ Curves *curves_new_nomain(const int point_size, const int curves_size)
   return curves;
 }
 
+Curves *curves_new_nomain_single(const int point_size, const CurveType type)
+{
+  Curves *curves = curves_new_nomain(point_size, 1);
+  CurvesGeometry &geometry = CurvesGeometry::wrap(curves->geometry);
+  geometry.offsets().last() = point_size;
+  geometry.curve_types().first() = type;
+  return curves;
+}
+
 }  // namespace blender::bke
diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h
index 706563061d4..9233b65b2ce 100644
--- a/source/blender/editors/include/ED_curves.h
+++ b/source/blender/editors/include/ED_curves.h
@@ -25,4 +25,4 @@ namespace blender::ed::curves {
 bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve);
 
 }
-#endif
\ No newline at end of file
+#endif
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
index 339e65321b1..6c7d7ed375b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
@@ -1,11 +1,15 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include 
+
 #include "BLI_math_base_safe.h"
+
+#include "BKE_curves.hh"
+
 #include "UI_interface.h"
 #include "UI_resources.h"
+
 #include "node_geometry_util.hh"
-#include 
 
 namespace blender::nodes::node_geo_curve_primitive_arc_cc {
 
@@ -139,32 +143,24 @@ static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3)
   return (ELEM(a, b, b * -1.0f));
 }
 
-static std::unique_ptr create_arc_curve_from_points(const int resolution,
-                                                               const float3 a,
-                                                               const float3 b,
-                                                               const float3 c,
-                                                               float angle_offset,
-                                                               const bool connect_center,
-                                                               const bool invert_arc,
-                                                               float3 &r_center,
-                                                               float3 &r_normal,
-                                                               float &r_radius)
+static Curves *create_arc_curve_from_points(const int resolution,
+                                            const float3 a,
+                                            const float3 b,
+                                            const float3 c,
+                                            float angle_offset,
+                                            const bool connect_center,
+                                            const bool invert_arc,
+                                            float3 &r_center,
+                                            float3 &r_normal,
+                                            float &r_radius)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-
-  if (connect_center) {
-    spline->resize(resolution + 1);
-  }
-  else {
-    spline->resize(resolution);
-  }
+  const int size = connect_center ? resolution + 1 : resolution;
+  Curves *curves_id = bke::curves_new_nomain_single(size, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
 
   const int stepcount = resolution - 1;
   const int centerpoint = resolution;
-  MutableSpan positions = spline->positions();
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
+  MutableSpan positions = curves.positions();
 
   const bool is_colinear = colinear_f3_f3_f3(a, b, c);
 
@@ -254,7 +250,7 @@ static std::unique_ptr create_arc_curve_from_points(const int resolut
   }
 
   if (connect_center) {
-    spline->set_cyclic(true);
+    curves.cyclic().first() = true;
     positions[centerpoint] = center;
   }
 
@@ -263,36 +259,26 @@ static std::unique_ptr create_arc_curve_from_points(const int resolut
     normal = -normal;
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
   r_center = center;
   r_radius = radius;
   r_normal = normal;
-  return curve;
+  return curves_id;
 }
 
-static std::unique_ptr create_arc_curve_from_radius(const int resolution,
-                                                               const float radius,
-                                                               const float start_angle,
-                                                               const float sweep_angle,
-                                                               const bool connect_center,
-                                                               const bool invert_arc)
+static Curves *create_arc_curve_from_radius(const int resolution,
+                                            const float radius,
+                                            const float start_angle,
+                                            const float sweep_angle,
+                                            const bool connect_center,
+                                            const bool invert_arc)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-
-  if (connect_center) {
-    spline->resize(resolution + 1);
-  }
-  else {
-    spline->resize(resolution);
-  }
+  const int size = connect_center ? resolution + 1 : resolution;
+  Curves *curves_id = bke::curves_new_nomain_single(size, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
 
   const int stepcount = resolution - 1;
   const int centerpoint = resolution;
-  MutableSpan positions = spline->positions();
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
+  MutableSpan positions = curves.positions();
 
   const float sweep = (invert_arc) ? -(2.0f * M_PI - sweep_angle) : sweep_angle;
 
@@ -305,13 +291,11 @@ static std::unique_ptr create_arc_curve_from_radius(const int resolut
   }
 
   if (connect_center) {
-    spline->set_cyclic(true);
+    curves.cyclic().first() = true;
     positions[centerpoint] = float3(0.0f, 0.0f, 0.0f);
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+  return curves_id;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -322,35 +306,35 @@ static void node_geo_exec(GeoNodeExecParams params)
 
   switch (mode) {
     case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS: {
-      std::unique_ptr curve;
       float3 r_center, r_normal;
       float r_radius;
-      curve = create_arc_curve_from_points(std::max(params.extract_input("Resolution"), 2),
-                                           params.extract_input("Start"),
-                                           params.extract_input("Middle"),
-                                           params.extract_input("End"),
-                                           params.extract_input("Offset Angle"),
-                                           params.extract_input("Connect Center"),
-                                           params.extract_input("Invert Arc"),
-                                           r_center,
-                                           r_normal,
-                                           r_radius);
-      params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+      Curves *curves = create_arc_curve_from_points(
+          std::max(params.extract_input("Resolution"), 2),
+          params.extract_input("Start"),
+          params.extract_input("Middle"),
+          params.extract_input("End"),
+          params.extract_input("Offset Angle"),
+          params.extract_input("Connect Center"),
+          params.extract_input("Invert Arc"),
+          r_center,
+          r_normal,
+          r_radius);
+      params.set_output("Curve", GeometrySet::create_with_curves(curves));
       params.set_output("Center", r_center);
       params.set_output("Normal", r_normal);
       params.set_output("Radius", r_radius);
       break;
     }
     case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS: {
-      std::unique_ptr curve;
-      curve = create_arc_curve_from_radius(std::max(params.extract_input("Resolution"), 2),
-                                           params.extract_input("Radius"),
-                                           params.extract_input("Start Angle"),
-                                           params.extract_input("Sweep Angle"),
-                                           params.extract_input("Connect Center"),
-                                           params.extract_input("Invert Arc"));
-
-      params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+      Curves *curves = create_arc_curve_from_radius(
+          std::max(params.extract_input("Resolution"), 2),
+          params.extract_input("Radius"),
+          params.extract_input("Start Angle"),
+          params.extract_input("Sweep Angle"),
+          params.extract_input("Connect Center"),
+          params.extract_input("Invert Arc"));
+
+      params.set_output("Curve", GeometrySet::create_with_curves(curves));
       break;
     }
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
index 54d7c488fb7..874e29dda86 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 
 #include "UI_interface.h"
 #include "UI_resources.h"
@@ -92,7 +92,7 @@ static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3)
   return (ELEM(a, b, b * -1.0f));
 }
 
-static std::unique_ptr create_point_circle_curve(
+static Curves *create_point_circle_curve(
     const float3 p1, const float3 p2, const float3 p3, const int resolution, float3 &r_center)
 {
   if (colinear_f3_f3_f3(p1, p2, p3)) {
@@ -100,11 +100,11 @@ static std::unique_ptr create_point_circle_curve(
     return nullptr;
   }
 
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
+  Curves *curves_id = bke::curves_new_nomain_single(resolution, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+  curves.cyclic().first() = true;
 
-  spline->resize(resolution);
-  MutableSpan positions = spline->positions();
+  MutableSpan positions = curves.positions();
 
   float3 center;
   /* Midpoints of `P1->P2` and `P2->P3`. */
@@ -147,24 +147,17 @@ static std::unique_ptr create_point_circle_curve(
     positions[i] = center + r * sin(theta) * v1 + r * cos(theta) * v4;
   }
 
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
-  spline->set_cyclic(true);
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-
   r_center = center;
-  return curve;
+  return curves_id;
 }
 
-static std::unique_ptr create_radius_circle_curve(const int resolution,
-                                                             const float radius)
+static Curves *create_radius_circle_curve(const int resolution, const float radius)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
+  Curves *curves_id = bke::curves_new_nomain_single(resolution, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+  curves.cyclic().first() = true;
 
-  spline->resize(resolution);
-  MutableSpan positions = spline->positions();
+  MutableSpan positions = curves.positions();
 
   const float theta_step = (2.0f * M_PI) / float(resolution);
   for (int i : IndexRange(resolution)) {
@@ -173,12 +166,8 @@ static std::unique_ptr create_radius_circle_curve(const int resolutio
     const float y = radius * sin(theta);
     positions[i] = float3(x, y, 0.0f);
   }
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
-  spline->set_cyclic(true);
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+
+  return curves_id;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -187,23 +176,23 @@ static void node_geo_exec(GeoNodeExecParams params)
   const GeometryNodeCurvePrimitiveCircleMode mode = (GeometryNodeCurvePrimitiveCircleMode)
                                                         storage.mode;
 
-  std::unique_ptr curve;
+  Curves *curves;
   if (mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS) {
     float3 center_point;
-    curve = create_point_circle_curve(params.extract_input("Point 1"),
-                                      params.extract_input("Point 2"),
-                                      params.extract_input("Point 3"),
-                                      std::max(params.extract_input("Resolution"), 3),
-                                      center_point);
+    curves = create_point_circle_curve(params.extract_input("Point 1"),
+                                       params.extract_input("Point 2"),
+                                       params.extract_input("Point 3"),
+                                       std::max(params.extract_input("Resolution"), 3),
+                                       center_point);
     params.set_output("Center", center_point);
   }
   else if (mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS) {
-    curve = create_radius_circle_curve(std::max(params.extract_input("Resolution"), 3),
-                                       params.extract_input("Radius"));
+    curves = create_radius_circle_curve(std::max(params.extract_input("Resolution"), 3),
+                                        params.extract_input("Radius"));
   }
 
-  if (curve) {
-    params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+  if (curves) {
+    params.set_output("Curve", GeometrySet::create_with_curves(curves));
   }
   else {
     params.set_default_remaining_outputs();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
index ece7e44cc35..2e2f4254752 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 
 #include "UI_interface.h"
 #include "UI_resources.h"
@@ -60,39 +60,28 @@ static void node_update(bNodeTree *ntree, bNode *node)
       ntree, length_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION);
 }
 
-static std::unique_ptr create_point_line_curve(const float3 start, const float3 end)
+static Curves *create_point_line_curve(const float3 start, const float3 end)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-
-  spline->resize(2);
-  MutableSpan positions = spline->positions();
-  positions[0] = start;
-  positions[1] = end;
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+  Curves *curves_id = bke::curves_new_nomain_single(2, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+
+  curves.positions().first() = start;
+  curves.positions().last() = end;
+
+  return curves_id;
 }
 
-static std::unique_ptr create_direction_line_curve(const float3 start,
-                                                              const float3 direction,
-                                                              const float length)
+static Curves *create_direction_line_curve(const float3 start,
+                                           const float3 direction,
+                                           const float length)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-
-  spline->resize(2);
-  MutableSpan positions = spline->positions();
-  positions[0] = start;
-  positions[1] = math::normalize(direction) * length + start;
-
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+  Curves *curves_id = bke::curves_new_nomain_single(2, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+
+  curves.positions().first() = start;
+  curves.positions().last() = math::normalize(direction) * length + start;
+
+  return curves_id;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -100,18 +89,18 @@ static void node_geo_exec(GeoNodeExecParams params)
   const NodeGeometryCurvePrimitiveLine &storage = node_storage(params.node());
   const GeometryNodeCurvePrimitiveLineMode mode = (GeometryNodeCurvePrimitiveLineMode)storage.mode;
 
-  std::unique_ptr curve;
+  Curves *curves = nullptr;
   if (mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS) {
-    curve = create_point_line_curve(params.extract_input("Start"),
-                                    params.extract_input("End"));
+    curves = create_point_line_curve(params.extract_input("Start"),
+                                     params.extract_input("End"));
   }
   else if (mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION) {
-    curve = create_direction_line_curve(params.extract_input("Start"),
-                                        params.extract_input("Direction"),
-                                        params.extract_input("Length"));
+    curves = create_direction_line_curve(params.extract_input("Start"),
+                                         params.extract_input("Direction"),
+                                         params.extract_input("Length"));
   }
 
-  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curves));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_line_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
index c6c33cffa54..37810ccaff5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 #include "node_geometry_util.hh"
 
 namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc {
@@ -28,18 +28,15 @@ static void node_declare(NodeDeclarationBuilder &b)
   b.add_output(N_("Curve"));
 }
 
-static std::unique_ptr create_quadratic_bezier_curve(const float3 p1,
-                                                                const float3 p2,
-                                                                const float3 p3,
-                                                                const int resolution)
+static Curves *create_quadratic_bezier_curve(const float3 p1,
+                                             const float3 p2,
+                                             const float3 p3,
+                                             const int resolution)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
+  Curves *curves_id = bke::curves_new_nomain_single(resolution + 1, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
 
-  spline->resize(resolution + 1);
-  MutableSpan positions = spline->positions();
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
+  MutableSpan positions = curves.positions();
 
   const float step = 1.0f / resolution;
   for (const int i : IndexRange(resolution + 1)) {
@@ -49,19 +46,17 @@ static std::unique_ptr create_quadratic_bezier_curve(const float3 p1,
     positions[i] = math::interpolate(q1, q2, factor);
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+  return curves_id;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
 {
-  std::unique_ptr curve = create_quadratic_bezier_curve(
+  Curves *curves = create_quadratic_bezier_curve(
       params.extract_input("Start"),
       params.extract_input("Middle"),
       params.extract_input("End"),
       std::max(params.extract_input("Resolution"), 3));
-  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curves));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc
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 cc2b9e4bc4e..ad3123a6a4a 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
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 #include "UI_interface.h"
 #include "UI_resources.h"
 
@@ -216,13 +216,11 @@ static void node_geo_exec(GeoNodeExecParams params)
   const NodeGeometryCurvePrimitiveQuad &storage = node_storage(params.node());
   const GeometryNodeCurvePrimitiveQuadMode mode = (GeometryNodeCurvePrimitiveQuadMode)storage.mode;
 
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-  spline->resize(4);
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
-  spline->set_cyclic(true);
-  MutableSpan positions = spline->positions();
+  Curves *curves_id = bke::curves_new_nomain_single(4, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+  curves.cyclic().first() = true;
+
+  MutableSpan positions = curves.positions();
 
   switch (mode) {
     case GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE:
@@ -262,9 +260,7 @@ static void node_geo_exec(GeoNodeExecParams params)
       return;
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+  params.set_output("Curve", GeometrySet::create_with_curves(curves_id));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc
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 d4f7c4ed5f1..22619577d04 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
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 
 #include "node_geometry_util.hh"
 
@@ -35,26 +35,23 @@ static void node_declare(NodeDeclarationBuilder &b)
   b.add_output(N_("Curve"));
 }
 
-static std::unique_ptr create_spiral_curve(const float rotations,
-                                                      const int resolution,
-                                                      const float start_radius,
-                                                      const float end_radius,
-                                                      const float height,
-                                                      const bool direction)
+static Curves *create_spiral_curve(const float rotations,
+                                   const int resolution,
+                                   const float start_radius,
+                                   const float end_radius,
+                                   const float height,
+                                   const bool direction)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-
   const int totalpoints = std::max(int(resolution * rotations), 1);
   const float delta_radius = (end_radius - start_radius) / (float)totalpoints;
   const float delta_height = height / (float)totalpoints;
   const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints *
                             (direction ? 1.0f : -1.0f);
 
-  spline->resize(totalpoints + 1);
-  MutableSpan positions = spline->positions();
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
+  Curves *curves_id = bke::curves_new_nomain_single(totalpoints + 1, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+
+  MutableSpan positions = curves.positions();
 
   for (const int i : IndexRange(totalpoints + 1)) {
     const float theta = i * delta_theta;
@@ -66,9 +63,7 @@ static std::unique_ptr create_spiral_curve(const float rotations,
     positions[i] = {x, y, z};
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-  return curve;
+  return curves_id;
 }
 
 static void node_geo_exec(GeoNodeExecParams params)
@@ -79,14 +74,13 @@ static void node_geo_exec(GeoNodeExecParams params)
     return;
   }
 
-  std::unique_ptr curve = create_spiral_curve(
-      rotations,
-      std::max(params.extract_input("Resolution"), 1),
-      params.extract_input("Start Radius"),
-      params.extract_input("End Radius"),
-      params.extract_input("Height"),
-      params.extract_input("Reverse"));
-  params.set_output("Curve", GeometrySet::create_with_curves(curve_eval_to_curves(*curve)));
+  Curves *curves = create_spiral_curve(rotations,
+                                       std::max(params.extract_input("Resolution"), 1),
+                                       params.extract_input("Start Radius"),
+                                       params.extract_input("End Radius"),
+                                       params.extract_input("Height"),
+                                       params.extract_input("Reverse"));
+  params.set_output("Curve", GeometrySet::create_with_curves(curves));
 }
 
 }  // namespace blender::nodes::node_geo_curve_primitive_spiral_cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
index 5b6852abfc2..e7e899881cf 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
 
 #include "node_geometry_util.hh"
 
@@ -33,19 +33,16 @@ static void node_declare(NodeDeclarationBuilder &b)
       .description(N_("An attribute field with a selection of the outer points"));
 }
 
-static std::unique_ptr create_star_curve(const float inner_radius,
-                                                    const float outer_radius,
-                                                    const float twist,
-                                                    const int points)
+static Curves *create_star_curve(const float inner_radius,
+                                 const float outer_radius,
+                                 const float twist,
+                                 const int points)
 {
-  std::unique_ptr curve = std::make_unique();
-  std::unique_ptr spline = std::make_unique();
-  spline->set_cyclic(true);
+  Curves *curves_id = bke::curves_new_nomain_single(points * 2, CURVE_TYPE_POLY);
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
+  curves.cyclic().first() = true;
 
-  spline->resize(points * 2);
-  MutableSpan positions = spline->positions();
-  spline->radii().fill(1.0f);
-  spline->tilts().fill(0.0f);
+  MutableSpan positions = curves.positions();
 
   const float theta_step = (2.0f * M_PI) / float(points);
   for (const int i : IndexRange(points)) {
@@ -58,10 +55,7 @@ static std::unique_ptr create_star_curve(const float inner_radius,
     positions[i * 2 + 1] = {inner_x, inner_y, 0.0f};
   }
 
-  curve->add_spline(std::move(spline));
-  curve->attributes.reallocate(curve->splines().size());
-
-  return curve;
+  return curves_id;
 }
 
 static void create_selection_output(CurveComponent &component,
@@ -78,12 +72,11 @@ static void create_selection_output(CurveComponent &component,
 
 static void node_geo_exec(GeoNodeExecParams params)
 {
-  std::unique_ptr curve = create_star_curve(
-      std::max(params.extract_input("Inner Radius"), 0.0f),
-      std::max(params.extract_input("Outer Radius"), 0.0f),
-      params.extract_input("Twist"),
-      std::max(params.extract_input("Points"), 3));
-  GeometrySet output = GeometrySet::create_with_curves(curve_eval_to_curves(*curve));
+  Curves *curves = create_star_curve(std::max(params.extract_input("Inner Radius"), 0.0f),
+                                     std::max(params.extract_input("Outer Radius"), 0.0f),
+                                     params.extract_input("Twist"),
+                                     std::max(params.extract_input("Points"), 3));
+  GeometrySet output = GeometrySet::create_with_curves(curves);
 
   if (params.output_is_required("Outer Points")) {
     StrongAnonymousAttributeID attribute_output("Outer Points");
-- 
cgit v1.2.3


From 6498688e274836a66d9075edfe79fd3572334427 Mon Sep 17 00:00:00 2001
From: Henrik Dick 
Date: Tue, 1 Mar 2022 19:32:02 +0100
Subject: GPencil: Improve subdivision modifier

The subdivision modifier for Grease Pencil handles closed strokes
correctly now and does converge to the same shape as the mesh
subdivision surface.

Differential Revision: http://developer.blender.org/D14218
---
 source/blender/blenkernel/intern/gpencil_geom.cc   | 59 ++++++++++------------
 .../gpencil_modifiers/intern/MOD_gpencilsubdiv.c   |  5 --
 2 files changed, 28 insertions(+), 36 deletions(-)

diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index 1823d84d48d..865bcebee25 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -2098,27 +2098,30 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
   MDeformVert *dvert_final = nullptr;
   MDeformVert *dvert_next = nullptr;
   int totnewpoints, oldtotpoints;
-  int i2;
+
+  bool cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
 
   for (int s = 0; s < level; s++) {
-    totnewpoints = gps->totpoints - 1;
+    totnewpoints = gps->totpoints;
+    if (!cyclic) {
+      totnewpoints--;
+    }
     /* duplicate points in a temp area */
-    temp_points = (bGPDspoint *)MEM_dupallocN(gps->points);
+    temp_points = gps->points;
     oldtotpoints = gps->totpoints;
 
     /* resize the points arrays */
     gps->totpoints += totnewpoints;
-    gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+    gps->points = (bGPDspoint *)MEM_malloc_arrayN(gps->totpoints, sizeof(*gps->points), __func__);
     if (gps->dvert != nullptr) {
-      temp_dverts = (MDeformVert *)MEM_dupallocN(gps->dvert);
-      gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+      temp_dverts = gps->dvert;
+      gps->dvert = (MDeformVert *)MEM_malloc_arrayN(gps->totpoints, sizeof(*gps->dvert), __func__);
     }
 
     /* move points from last to first to new place */
-    i2 = gps->totpoints - 1;
-    for (int i = oldtotpoints - 1; i > 0; i--) {
+    for (int i = 0; i < oldtotpoints; i++) {
       bGPDspoint *pt = &temp_points[i];
-      bGPDspoint *pt_final = &gps->points[i2];
+      bGPDspoint *pt_final = &gps->points[i * 2];
 
       copy_v3_v3(&pt_final->x, &pt->x);
       pt_final->pressure = pt->pressure;
@@ -2131,18 +2134,16 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
 
       if (gps->dvert != nullptr) {
         dvert = &temp_dverts[i];
-        dvert_final = &gps->dvert[i2];
+        dvert_final = &gps->dvert[i * 2];
         dvert_final->totweight = dvert->totweight;
         dvert_final->dw = dvert->dw;
       }
-      i2 -= 2;
     }
     /* interpolate mid points */
-    i2 = 1;
-    for (int i = 0; i < oldtotpoints - 1; i++) {
-      bGPDspoint *pt = &temp_points[i];
-      bGPDspoint *next = &temp_points[i + 1];
-      bGPDspoint *pt_final = &gps->points[i2];
+    for (int i = cyclic ? 0 : 1, j = cyclic ? oldtotpoints - 1 : 0; i < oldtotpoints; j = i, i++) {
+      bGPDspoint *pt = &temp_points[j];
+      bGPDspoint *next = &temp_points[i];
+      bGPDspoint *pt_final = &gps->points[j * 2 + 1];
 
       /* add a half way point */
       interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
@@ -2155,9 +2156,9 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
       interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f);
 
       if (gps->dvert != nullptr) {
-        dvert = &temp_dverts[i];
-        dvert_next = &temp_dverts[i + 1];
-        dvert_final = &gps->dvert[i2];
+        dvert = &temp_dverts[j];
+        dvert_next = &temp_dverts[i];
+        dvert_final = &gps->dvert[j * 2 + 1];
 
         dvert_final->totweight = dvert->totweight;
         dvert_final->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
@@ -2172,8 +2173,6 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
           }
         }
       }
-
-      i2 += 2;
     }
 
     MEM_SAFE_FREE(temp_points);
@@ -2181,20 +2180,18 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int
 
     /* Move points to smooth stroke (not simple type). */
     if (type != GP_SUBDIV_SIMPLE) {
-      /* duplicate points in a temp area with the new subdivide data */
-      temp_points = (bGPDspoint *)MEM_dupallocN(gps->points);
-
+      float mid[3];
       /* extreme points are not changed */
-      for (int i = 0; i < gps->totpoints - 2; i++) {
-        bGPDspoint *pt = &temp_points[i];
-        bGPDspoint *next = &temp_points[i + 1];
-        bGPDspoint *pt_final = &gps->points[i + 1];
+      for (int i = cyclic ? 0 : 2, j = cyclic ? gps->totpoints - 2 : 0; i < gps->totpoints - 2;
+           j = i, i += 2) {
+        bGPDspoint *prev = &gps->points[j + 1];
+        bGPDspoint *pt = &gps->points[i];
+        bGPDspoint *next = &gps->points[i + 1];
 
         /* move point */
-        interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
+        interp_v3_v3v3(mid, &prev->x, &next->x, 0.5f);
+        interp_v3_v3v3(&pt->x, mid, &pt->x, 0.5f);
       }
-      /* free temp memory */
-      MEM_SAFE_FREE(temp_points);
     }
   }
 
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
index 5e916a13f2c..dcec2865f29 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c
@@ -79,11 +79,6 @@ static void deformStroke(GpencilModifierData *md,
   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) {
-    BKE_gpencil_stroke_close(gps);
-  }
 }
 
 static void bakeModifier(struct Main *UNUSED(bmain),
-- 
cgit v1.2.3


From 8322a4f6b76960cb4983f8aaf5a74e694aed1384 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Tue, 1 Mar 2022 19:45:36 +0100
Subject: Fix T92980: missing Cycles video texture update with persistent data

---
 intern/cycles/blender/shader.cpp | 13 ++++---------
 intern/cycles/blender/sync.cpp   |  9 +++++++--
 intern/cycles/blender/sync.h     |  2 +-
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp
index 418393c2be7..65866525894 100644
--- a/intern/cycles/blender/shader.cpp
+++ b/intern/cycles/blender/shader.cpp
@@ -1587,18 +1587,13 @@ void BlenderSync::sync_lights(BL::Depsgraph &b_depsgraph, bool update_all)
   }
 }
 
-void BlenderSync::sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d)
+void BlenderSync::sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all)
 {
-  /* for auto refresh images */
-  ImageManager *image_manager = scene->image_manager;
-  const int frame = b_scene.frame_current();
-  const bool auto_refresh_update = image_manager->set_animation_frame_update(frame);
-
   shader_map.pre_sync();
 
-  sync_world(b_depsgraph, b_v3d, auto_refresh_update);
-  sync_lights(b_depsgraph, auto_refresh_update);
-  sync_materials(b_depsgraph, auto_refresh_update);
+  sync_world(b_depsgraph, b_v3d, update_all);
+  sync_lights(b_depsgraph, update_all);
+  sync_materials(b_depsgraph, update_all);
 }
 
 CCL_NAMESPACE_END
diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp
index 7e6f1535d66..851e4751501 100644
--- a/intern/cycles/blender/sync.cpp
+++ b/intern/cycles/blender/sync.cpp
@@ -259,7 +259,12 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render,
                             int height,
                             void **python_thread_state)
 {
-  if (!has_updates_) {
+  /* For auto refresh images. */
+  ImageManager *image_manager = scene->image_manager;
+  const int frame = b_scene.frame_current();
+  const bool auto_refresh_update = image_manager->set_animation_frame_update(frame);
+
+  if (!has_updates_ && !auto_refresh_update) {
     return;
   }
 
@@ -274,7 +279,7 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render,
   sync_view_layer(b_view_layer);
   sync_integrator(b_view_layer, background);
   sync_film(b_view_layer, b_v3d);
-  sync_shaders(b_depsgraph, b_v3d);
+  sync_shaders(b_depsgraph, b_v3d, auto_refresh_update);
   sync_images();
 
   geometry_synced.clear(); /* use for objects and motion sync */
diff --git a/intern/cycles/blender/sync.h b/intern/cycles/blender/sync.h
index 3722b938863..01bb92331f6 100644
--- a/intern/cycles/blender/sync.h
+++ b/intern/cycles/blender/sync.h
@@ -127,7 +127,7 @@ class BlenderSync {
   /* Shader */
   array find_used_shaders(BL::Object &b_ob);
   void sync_world(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all);
-  void sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d);
+  void sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all);
   void sync_nodes(Shader *shader, BL::ShaderNodeTree &b_ntree);
 
   /* Object */
-- 
cgit v1.2.3


From 8e88af99348590e9879dcbfe97bbbc180fc5ec67 Mon Sep 17 00:00:00 2001
From: Germano Cavalcante 
Date: Tue, 1 Mar 2022 16:11:38 -0300
Subject: Fix wrong object mode checking in snap code

The value of `OB_MODE_OBJECT` is 0, this makes it unsuitable as a bitflag.

Issue pointed out at https://pvs-studio.com/en/blog/posts/cpp/0922/

Thanks to Andrey Karpov
---
 source/blender/editors/transform/transform_snap_object.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c
index dc9315b6d5a..515a4360bb5 100644
--- a/source/blender/editors/transform/transform_snap_object.c
+++ b/source/blender/editors/transform/transform_snap_object.c
@@ -491,7 +491,7 @@ static void iter_snap_objects(SnapObjectContext *sctx,
       }
     }
     else if (snap_select == SNAP_NOT_SELECTED) {
-      if (is_object_active && !(base->object->mode & OB_MODE_OBJECT)) {
+      if (is_object_active && base->object->mode != OB_MODE_OBJECT) {
         /* Pass. Consider the selection of elements being edited. */
       }
       else if ((base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL)) {
-- 
cgit v1.2.3


From 9bd586a01e6813a615eab05871803730603e2152 Mon Sep 17 00:00:00 2001
From: Germano Cavalcante 
Date: Tue, 1 Mar 2022 18:09:28 -0300
Subject: Fix T95608: Mac issues with drag drop on multi-monitor

Mousemove events are sent to windows.

In Windows OS, almost all mousemove events are sent to the window whose
mouse cursor is over.

On MacOS, the window with mousemove events is always the active window.
It doesn't matter if the mouse cursor is inside or outside the window.

So, in order for non-active windows to also have events,
`WM_window_find_under_cursor` is called to find those windows and send
the same events.

The problem is that to find the window, `WM_window_find_under_cursor`
only has the mouse coordinates available, it doesn't differentiate
which monitor these coordinates came from.

So the mouse on one monitor may incorrectly send events to a window on
another monitor.

The solution used is to use a native API on Mac to detect the window
under the cursor.

For Windows and Linux nothing has changed.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D14197
---
 intern/ghost/GHOST_C-api.h                         | 10 ++++
 intern/ghost/GHOST_ISystem.h                       |  8 +++
 intern/ghost/intern/GHOST_C-api.cpp                | 10 ++++
 intern/ghost/intern/GHOST_System.cpp               | 19 ++++++++
 intern/ghost/intern/GHOST_System.h                 |  8 +++
 intern/ghost/intern/GHOST_SystemCocoa.h            |  8 +++
 intern/ghost/intern/GHOST_SystemCocoa.mm           | 14 ++++++
 intern/ghost/intern/GHOST_SystemNULL.h             |  5 ++
 .../editors/interface/interface_eyedropper.c       |  3 +-
 source/blender/windowmanager/WM_api.h              |  6 +--
 .../blender/windowmanager/intern/wm_event_system.c |  4 +-
 source/blender/windowmanager/intern/wm_window.c    | 57 +++++-----------------
 12 files changed, 98 insertions(+), 54 deletions(-)

diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index 98094cc0669..a2eef91c704 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -265,6 +265,16 @@ extern GHOST_TSuccess GHOST_EndFullScreen(GHOST_SystemHandle systemhandle);
  */
 extern int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle);
 
+/**
+ * Get the Window under the cursor.
+ * \param x: The x-coordinate of the cursor.
+ * \param y: The y-coordinate of the cursor.
+ * @return The window under the cursor or nullptr in none.
+ */
+extern GHOST_WindowHandle GHOST_GetWindowUnderCursor(GHOST_SystemHandle systemhandle,
+                                                     int32_t x,
+                                                     int32_t y);
+
 /***************************************************************************************
  * Event management functionality
  ***************************************************************************************/
diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h
index 05c6c9d907f..4c568a0cc02 100644
--- a/intern/ghost/GHOST_ISystem.h
+++ b/intern/ghost/GHOST_ISystem.h
@@ -325,6 +325,14 @@ class GHOST_ISystem {
    */
   virtual void useWindowFocus(const bool use_focus) = 0;
 
+  /**
+   * Get the Window under the cursor.
+   * \param x: The x-coordinate of the cursor.
+   * \param y: The y-coordinate of the cursor.
+   * @return The window under the cursor or nullptr if none.
+   */
+  virtual GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y) = 0;
+
   /***************************************************************************************
    * Event management functionality
    ***************************************************************************************/
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp
index a21c3a90c06..3d8411a8268 100644
--- a/intern/ghost/intern/GHOST_C-api.cpp
+++ b/intern/ghost/intern/GHOST_C-api.cpp
@@ -249,6 +249,16 @@ int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle)
   return (int)system->getFullScreen();
 }
 
+GHOST_WindowHandle GHOST_GetWindowUnderCursor(GHOST_SystemHandle systemhandle,
+                                              int32_t x,
+                                              int32_t y)
+{
+  GHOST_ISystem *system = (GHOST_ISystem *)systemhandle;
+  GHOST_IWindow *window = system->getWindowUnderCursor(x, y);
+
+  return (GHOST_WindowHandle)window;
+}
+
 bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent)
 {
   GHOST_ISystem *system = (GHOST_ISystem *)systemhandle;
diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp
index d09c167cb95..17f74003805 100644
--- a/intern/ghost/intern/GHOST_System.cpp
+++ b/intern/ghost/intern/GHOST_System.cpp
@@ -205,6 +205,25 @@ bool GHOST_System::getFullScreen(void)
   return fullScreen;
 }
 
+GHOST_IWindow *GHOST_System::getWindowUnderCursor(int32_t x, int32_t y)
+{
+  /* TODO: This solution should follow the order of the activated windows (Z-order).
+   * It is imperfect but usable in most cases. */
+  for (GHOST_IWindow *iwindow : m_windowManager->getWindows()) {
+    if (iwindow->getState() == GHOST_kWindowStateMinimized) {
+      continue;
+    }
+
+    GHOST_Rect bounds;
+    iwindow->getClientBounds(bounds);
+    if (bounds.isInside(x, y)) {
+      return iwindow;
+    }
+  }
+
+  return NULL;
+}
+
 void GHOST_System::dispatchEvents()
 {
 #ifdef WITH_INPUT_NDOF
diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h
index 16c34ff1a6d..692e483be2a 100644
--- a/intern/ghost/intern/GHOST_System.h
+++ b/intern/ghost/intern/GHOST_System.h
@@ -173,6 +173,14 @@ class GHOST_System : public GHOST_ISystem {
   void useWindowFocus(const bool use_focus);
   bool m_windowFocus;
 
+  /**
+   * Get the Window under the cursor.
+   * \param x: The x-coordinate of the cursor.
+   * \param y: The y-coordinate of the cursor.
+   * @return The window under the cursor or nullptr if none.
+   */
+  GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y);
+
   /***************************************************************************************
    * Event management functionality
    ***************************************************************************************/
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.h b/intern/ghost/intern/GHOST_SystemCocoa.h
index 5950da6813d..37cba8b8559 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.h
+++ b/intern/ghost/intern/GHOST_SystemCocoa.h
@@ -125,6 +125,14 @@ class GHOST_SystemCocoa : public GHOST_System {
    */
   GHOST_TSuccess disposeContext(GHOST_IContext *context);
 
+  /**
+   * Get the Window under the cursor.
+   * \param x: The x-coordinate of the cursor.
+   * \param y: The y-coordinate of the cursor.
+   * @return The window under the cursor or nullptr if none.
+   */
+  GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y);
+
   /***************************************************************************************
    * Event management functionality
    ***************************************************************************************/
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm
index a53c3d8f2ab..b54bfab5547 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemCocoa.mm
@@ -788,6 +788,20 @@ GHOST_TSuccess GHOST_SystemCocoa::disposeContext(GHOST_IContext *context)
   return GHOST_kSuccess;
 }
 
+GHOST_IWindow *GHOST_SystemCocoa::getWindowUnderCursor(int32_t x, int32_t y)
+{
+  NSPoint scr_co = NSMakePoint(x, y);
+
+  int windowNumberAtPoint = [NSWindow windowNumberAtPoint:scr_co belowWindowWithWindowNumber:0];
+  NSWindow *nswindow = [NSApp windowWithWindowNumber:windowNumberAtPoint];
+
+  if (nswindow == nil) {
+    return nil;
+  }
+
+  return m_windowManager->getWindowAssociatedWithOSWindow((void *)nswindow);
+}
+
 /**
  * \note : returns coordinates in Cocoa screen coordinates
  */
diff --git a/intern/ghost/intern/GHOST_SystemNULL.h b/intern/ghost/intern/GHOST_SystemNULL.h
index 5dbc42b53a2..43bbc788113 100644
--- a/intern/ghost/intern/GHOST_SystemNULL.h
+++ b/intern/ghost/intern/GHOST_SystemNULL.h
@@ -128,4 +128,9 @@ class GHOST_SystemNULL : public GHOST_System {
                                 type,
                                 ((glSettings.flags & GHOST_glStereoVisual) != 0));
   }
+
+  GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y)
+  {
+    return NULL;
+  }
 };
diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c
index fd03cc5e12c..c475e143df0 100644
--- a/source/blender/editors/interface/interface_eyedropper.c
+++ b/source/blender/editors/interface/interface_eyedropper.c
@@ -165,8 +165,7 @@ void datadropper_win_area_find(
   *r_win = CTX_wm_window(C);
   *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval);
   if (*r_area == NULL) {
-    wmWindowManager *wm = CTX_wm_manager(C);
-    *r_win = WM_window_find_under_cursor(wm, NULL, *r_win, mval, r_mval);
+    *r_win = WM_window_find_under_cursor(*r_win, mval, r_mval);
     if (*r_win) {
       screen = WM_window_get_active_screen(*r_win);
       *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval);
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index ff3e1b7474c..5f8a831706b 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -143,11 +143,7 @@ void WM_reinit_gizmomap_all(struct Main *bmain);
  */
 void WM_script_tag_reload(void);
 
-wmWindow *WM_window_find_under_cursor(const wmWindowManager *wm,
-                                      const wmWindow *win_ignore,
-                                      const wmWindow *win,
-                                      const int mval[2],
-                                      int r_mval[2]);
+wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mval[2]);
 void WM_window_pixel_sample_read(const wmWindowManager *wm,
                                  const wmWindow *win,
                                  const int pos[2],
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 0cc2db661ec..d9ef7dc63ff 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -4663,8 +4663,8 @@ static wmWindow *wm_event_cursor_other_windows(wmWindowManager *wm, wmWindow *wi
       }
     }
 
-    wmWindow *win_other = WM_window_find_under_cursor(wm, win, win, mval, mval);
-    if (win_other) {
+    wmWindow *win_other = WM_window_find_under_cursor(win, mval, mval);
+    if (win_other && win_other != win) {
       copy_v2_v2_int(event->xy, mval);
       return win_other;
     }
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index a1854a8ed86..0a229b2ea56 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -1855,56 +1855,23 @@ bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
 /** \name Find Window Utility
  * \{ */
 
-static void wm_window_desktop_pos_get(const wmWindow *win,
-                                      const int screen_pos[2],
-                                      int r_desk_pos[2])
+wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mval[2])
 {
-  /* To desktop space. */
-  r_desk_pos[0] = screen_pos[0] + (int)(U.pixelsize * win->posx);
-  r_desk_pos[1] = screen_pos[1] + (int)(U.pixelsize * win->posy);
-}
-
-static void wm_window_screen_pos_get(const wmWindow *win,
-                                     const int desktop_pos[2],
-                                     int r_scr_pos[2])
-{
-  /* To window space. */
-  r_scr_pos[0] = desktop_pos[0] - (int)(U.pixelsize * win->posx);
-  r_scr_pos[1] = desktop_pos[1] - (int)(U.pixelsize * win->posy);
-}
-
-wmWindow *WM_window_find_under_cursor(const wmWindowManager *wm,
-                                      const wmWindow *win_ignore,
-                                      const wmWindow *win,
-                                      const int mval[2],
-                                      int r_mval[2])
-{
-  int desk_pos[2];
-  wm_window_desktop_pos_get(win, mval, desk_pos);
-
-  /* TODO: This should follow the order of the activated windows.
-   * The current solution is imperfect but usable in most cases. */
-  LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
-    if (win_iter == win_ignore) {
-      continue;
-    }
-
-    if (win_iter->windowstate == GHOST_kWindowStateMinimized) {
-      continue;
-    }
-
-    int scr_pos[2];
-    wm_window_screen_pos_get(win_iter, desk_pos, scr_pos);
+  int tmp[2];
+  copy_v2_v2_int(tmp, mval);
+  wm_cursor_position_to_ghost(win, &tmp[0], &tmp[1]);
 
-    if (scr_pos[0] >= 0 && scr_pos[1] >= 0 && scr_pos[0] <= WM_window_pixels_x(win_iter) &&
-        scr_pos[1] <= WM_window_pixels_y(win_iter)) {
+  GHOST_WindowHandle ghostwin = GHOST_GetWindowUnderCursor(g_system, tmp[0], tmp[1]);
 
-      copy_v2_v2_int(r_mval, scr_pos);
-      return win_iter;
-    }
+  if (!ghostwin) {
+    return NULL;
   }
 
-  return NULL;
+  wmWindow *r_win = GHOST_GetWindowUserData(ghostwin);
+  wm_cursor_position_from_ghost(r_win, &tmp[0], &tmp[1]);
+  copy_v2_v2_int(r_mval, tmp);
+
+  return r_win;
 }
 
 void WM_window_pixel_sample_read(const wmWindowManager *wm,
-- 
cgit v1.2.3


From 4491c6226051799f4415ba50abbc4861e09af862 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Tue, 1 Mar 2022 23:35:21 +0100
Subject: Fix potential crash during proxy building

Last step of proxy building caused crash due to thread race contition
when acessing ed->seqbase.

Looking at code, it seems that calling IMB_close_anim_proxies on
original strips is unnecessary so this code was removed to resolve this
issue.

Locking seqbase data may be possible, but it's not very practical as
many functions access this data on demand which can easily cause
program to freeze.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D14210
---
 source/blender/sequencer/intern/proxy.c | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/source/blender/sequencer/intern/proxy.c b/source/blender/sequencer/intern/proxy.c
index d87da1557e0..91b69bfe01f 100644
--- a/source/blender/sequencer/intern/proxy.c
+++ b/source/blender/sequencer/intern/proxy.c
@@ -549,18 +549,6 @@ void SEQ_proxy_rebuild(SeqIndexBuildContext *context,
   }
 }
 
-static bool seq_orig_free_anims(Sequence *seq_iter, void *data)
-{
-  SessionUUID orig_seq_uuid = ((SeqIndexBuildContext *)data)->orig_seq_uuid;
-
-  if (BLI_session_uuid_is_equal(&seq_iter->runtime.session_uuid, &orig_seq_uuid)) {
-    for (StripAnim *sanim = seq_iter->anims.first; sanim; sanim = sanim->next) {
-      IMB_close_anim_proxies(sanim->anim);
-    }
-  }
-  return true;
-}
-
 void SEQ_proxy_rebuild_finish(SeqIndexBuildContext *context, bool stop)
 {
   if (context->index_context) {
@@ -570,9 +558,6 @@ void SEQ_proxy_rebuild_finish(SeqIndexBuildContext *context, bool stop)
       IMB_close_anim_proxies(sanim->anim);
     }
 
-    /* `context->seq_orig` may have been removed during building. */
-    SEQ_for_each_callback(&context->scene->ed->seqbase, seq_orig_free_anims, context);
-
     IMB_anim_index_rebuild_finish(context->index_context, stop);
   }
 
-- 
cgit v1.2.3


From 10405b15ecc3f79e9279ac2eb1f711f4bb1c1cc5 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Tue, 1 Mar 2022 23:41:00 +0100
Subject: Cleanup: Refactor seeking code

Improve readability and reduce indentation levels. No functional changes.

Reviewed By: zeddb

Differential Revision: https://developer.blender.org/D14075
---
 source/blender/imbuf/intern/anim_movie.c | 143 ++++++++++++++++++-------------
 1 file changed, 83 insertions(+), 60 deletions(-)

diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c
index 469141cb996..f97a50ecf47 100644
--- a/source/blender/imbuf/intern/anim_movie.c
+++ b/source/blender/imbuf/intern/anim_movie.c
@@ -867,6 +867,17 @@ static void ffmpeg_decode_store_frame_pts(struct anim *anim)
          (int64_t)anim->cur_pts);
 }
 
+static int ffmpeg_read_video_frame(struct anim *anim, AVPacket *packet)
+{
+  int ret = 0;
+  while (ret = av_read_frame(anim->pFormatCtx, packet) >= 0) {
+    if (packet->stream_index == anim->videoStream) {
+      break;
+    }
+  }
+  return ret;
+}
+
 /* decode one video frame also considering the packet read into cur_packet */
 static int ffmpeg_decode_video_frame(struct anim *anim)
 {
@@ -887,7 +898,7 @@ static int ffmpeg_decode_video_frame(struct anim *anim)
     anim->cur_packet->stream_index = -1;
   }
 
-  while ((rval = av_read_frame(anim->pFormatCtx, anim->cur_packet)) >= 0) {
+  while ((rval = ffmpeg_read_video_frame(anim, anim->cur_packet)) >= 0) {
     av_log(anim->pFormatCtx,
            AV_LOG_DEBUG,
            "%sREAD: strID=%d (VID: %d) dts=%" PRId64 " pts=%" PRId64 " %s\n",
@@ -897,14 +908,13 @@ static int ffmpeg_decode_video_frame(struct anim *anim)
            (anim->cur_packet->dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->cur_packet->dts,
            (anim->cur_packet->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->cur_packet->pts,
            (anim->cur_packet->flags & AV_PKT_FLAG_KEY) ? " KEY" : "");
-    if (anim->cur_packet->stream_index == anim->videoStream) {
-      avcodec_send_packet(anim->pCodecCtx, anim->cur_packet);
-      anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0;
 
-      if (anim->pFrameComplete) {
-        ffmpeg_decode_store_frame_pts(anim);
-        break;
-      }
+    avcodec_send_packet(anim->pCodecCtx, anim->cur_packet);
+    anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0;
+
+    if (anim->pFrameComplete) {
+      ffmpeg_decode_store_frame_pts(anim);
+      break;
     }
     av_packet_unref(anim->cur_packet);
     anim->cur_packet->stream_index = -1;
@@ -1159,13 +1169,59 @@ static int ffmpeg_generic_seek_workaround(struct anim *anim,
   return av_seek_frame(anim->pFormatCtx, anim->videoStream, current_pts, AVSEEK_FLAG_BACKWARD);
 }
 
+/* Read packet until timestamp matches `anim->cur_packet`, thus recovering internal `anim` stream
+ * position state. */
+static void ffmpeg_seek_recover_stream_position(struct anim *anim)
+{
+  AVPacket *temp_packet = av_packet_alloc();
+  while (ffmpeg_read_video_frame(anim, temp_packet)) {
+    int64_t current_pts = timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts);
+    int64_t temp_pts = timestamp_from_pts_or_dts(temp_packet->pts, temp_packet->dts);
+    av_packet_unref(temp_packet);
+
+    if (current_pts == temp_pts) {
+      break;
+    }
+  }
+  av_packet_free(&temp_packet);
+}
+
+/* Check if seeking and mainly flushing codec buffers is needed. */
+static bool ffmpeg_seek_buffers_need_flushing(struct anim *anim, int position, int64_t seek_pos)
+{
+  /* Get timestamp of packet read after seeking. */
+  AVPacket *temp_packet = av_packet_alloc();
+  ffmpeg_read_video_frame(anim, temp_packet);
+  int64_t gop_pts = timestamp_from_pts_or_dts(temp_packet->pts, temp_packet->dts);
+  av_packet_unref(temp_packet);
+  av_packet_free(&temp_packet);
+
+  /* Seeking gives packet, that is currently read. No seeking was necessary, so buffers don't have
+   * to be flushed. */
+  if (gop_pts == timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts)) {
+    return false;
+  }
+
+  /* Packet after seeking is same key frame as current, and further in time. No seeking was
+   * necessary, so buffers don't have to be flushed. But stream position has to be recovered. */
+  if (gop_pts == anim->cur_key_frame_pts && position > anim->cur_position) {
+    ffmpeg_seek_recover_stream_position(anim);
+    return false;
+  }
+
+  /* Seeking was necessary, but we have read packets. Therefore we must seek again. */
+  av_seek_frame(anim->pFormatCtx, anim->videoStream, seek_pos, AVSEEK_FLAG_BACKWARD);
+  anim->cur_key_frame_pts = gop_pts;
+  return true;
+}
+
 /* Seek to last necessary key frame. */
 static int ffmpeg_seek_to_key_frame(struct anim *anim,
                                     int position,
                                     struct anim_index *tc_index,
                                     int64_t pts_to_search)
 {
-  int64_t pos;
+  int64_t seek_pos;
   int ret;
 
   if (tc_index) {
@@ -1180,23 +1236,23 @@ static int ffmpeg_seek_to_key_frame(struct anim *anim,
     uint64_t pts;
     uint64_t dts;
 
-    pos = IMB_indexer_get_seek_pos(tc_index, new_frame_index);
+    seek_pos = IMB_indexer_get_seek_pos(tc_index, new_frame_index);
     pts = IMB_indexer_get_seek_pos_pts(tc_index, new_frame_index);
     dts = IMB_indexer_get_seek_pos_dts(tc_index, new_frame_index);
 
     anim->cur_key_frame_pts = timestamp_from_pts_or_dts(pts, dts);
 
-    av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek pos = %" PRId64 "\n", pos);
+    av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek seek_pos = %" PRId64 "\n", seek_pos);
     av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek pts = %" PRIu64 "\n", pts);
     av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek dts = %" PRIu64 "\n", dts);
 
     if (ffmpeg_seek_by_byte(anim->pFormatCtx)) {
-      av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using BYTE pos\n");
+      av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using BYTE seek_pos\n");
 
-      ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BYTE);
+      ret = av_seek_frame(anim->pFormatCtx, -1, seek_pos, AVSEEK_FLAG_BYTE);
     }
     else {
-      av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using PTS pos\n");
+      av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using PTS seek_pos\n");
       ret = av_seek_frame(
           anim->pFormatCtx, anim->videoStream, anim->cur_key_frame_pts, AVSEEK_FLAG_BACKWARD);
     }
@@ -1204,58 +1260,25 @@ static int ffmpeg_seek_to_key_frame(struct anim *anim,
   else {
     /* We have to manually seek with ffmpeg to get to the key frame we want to start decoding from.
      */
-    pos = ffmpeg_get_seek_pts(anim, pts_to_search);
-    av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %" PRId64 "\n", pos);
+    seek_pos = ffmpeg_get_seek_pts(anim, pts_to_search);
+    av_log(
+        anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek seek_pos = %" PRId64 "\n", seek_pos);
 
     AVFormatContext *format_ctx = anim->pFormatCtx;
 
     if (format_ctx->iformat->read_seek2 || format_ctx->iformat->read_seek) {
-      ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, pos, AVSEEK_FLAG_BACKWARD);
+      ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, seek_pos, AVSEEK_FLAG_BACKWARD);
     }
     else {
-      ret = ffmpeg_generic_seek_workaround(anim, &pos, pts_to_search);
-      av_log(anim->pFormatCtx, AV_LOG_DEBUG, "Adjusted final seek pos = %" PRId64 "\n", pos);
+      ret = ffmpeg_generic_seek_workaround(anim, &seek_pos, pts_to_search);
+      av_log(anim->pFormatCtx,
+             AV_LOG_DEBUG,
+             "Adjusted final seek seek_pos = %" PRId64 "\n",
+             seek_pos);
     }
 
-    if (ret >= 0) {
-      /* Double check if we need to seek and decode all packets. */
-      AVPacket *current_gop_start_packet = av_packet_alloc();
-      while (av_read_frame(anim->pFormatCtx, current_gop_start_packet) >= 0) {
-        if (current_gop_start_packet->stream_index == anim->videoStream) {
-          break;
-        }
-        av_packet_unref(current_gop_start_packet);
-      }
-      int64_t gop_pts = timestamp_from_pts_or_dts(current_gop_start_packet->pts,
-                                                  current_gop_start_packet->dts);
-
-      av_packet_free(¤t_gop_start_packet);
-      bool same_gop = gop_pts == anim->cur_key_frame_pts;
-
-      if (same_gop && position > anim->cur_position) {
-        /* Change back to our old frame position so we can simply continue decoding from there. */
-        int64_t cur_pts = timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts);
-
-        if (cur_pts == gop_pts) {
-          /* We are already at the correct position. */
-          return 0;
-        }
-        AVPacket *temp = av_packet_alloc();
-
-        while (av_read_frame(anim->pFormatCtx, temp) >= 0) {
-          int64_t temp_pts = timestamp_from_pts_or_dts(temp->pts, temp->dts);
-          if (temp->stream_index == anim->videoStream && temp_pts == cur_pts) {
-            break;
-          }
-          av_packet_unref(temp);
-        }
-        av_packet_free(&temp);
-        return 0;
-      }
-
-      anim->cur_key_frame_pts = gop_pts;
-      /* Seek back so we are at the correct position after we decoded a frame. */
-      av_seek_frame(anim->pFormatCtx, anim->videoStream, pos, AVSEEK_FLAG_BACKWARD);
+    if (ret <= 0 && !ffmpeg_seek_buffers_need_flushing(anim, position, seek_pos)) {
+      return 0;
     }
   }
 
@@ -1265,7 +1288,7 @@ static int ffmpeg_seek_to_key_frame(struct anim *anim,
            "FETCH: "
            "error while seeking to DTS = %" PRId64 " (frameno = %d, PTS = %" PRId64
            "): errcode = %d\n",
-           pos,
+           seek_pos,
            position,
            pts_to_search,
            ret);
@@ -1290,7 +1313,7 @@ static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Typ
     return NULL;
   }
 
-  av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position);
+  av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: seek_pos=%d\n", position);
 
   struct anim_index *tc_index = IMB_anim_open_index(anim, tc);
   int64_t pts_to_search = ffmpeg_get_pts_to_search(anim, tc_index, position);
-- 
cgit v1.2.3


From a75e9863fef9a3f1f9495d66a21c85a7cc73367c Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Tue, 1 Mar 2022 17:45:13 -0500
Subject: Cmake: Re-enable Amaranth add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 2e626428826..bc4d912405c 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -389,9 +389,6 @@ if(WITH_PYTHON)
     PATTERN "__pycache__" EXCLUDE
     PATTERN "${ADDON_EXCLUDE_CONDITIONAL}" EXCLUDE
     PATTERN "${FREESTYLE_EXCLUDE_CONDITIONAL}" EXCLUDE
-
-	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
-	  PATTERN "addons/amaranth" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From e03b9f555c177161ac3ed66e2f6071d479a563f7 Mon Sep 17 00:00:00 2001
From: Aaron Carlisle 
Date: Tue, 1 Mar 2022 17:45:13 -0500
Subject: Cmake: Re-enable Amaranth add-on

This add-on now conforms to the distribution requirements, see: T95442.
---
 source/creator/CMakeLists.txt | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 01f3e6f52c6..d17afad0918 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -379,9 +379,6 @@ if(WITH_PYTHON)
     PATTERN "__pycache__" EXCLUDE
     PATTERN "${ADDON_EXCLUDE_CONDITIONAL}" EXCLUDE
     PATTERN "${FREESTYLE_EXCLUDE_CONDITIONAL}" EXCLUDE
-
-	  # Disable add-ons that don't conform to distribution requirements, see: T95442.
-	  PATTERN "addons/amaranth" EXCLUDE
   )
 
   unset(ADDON_EXCLUDE_CONDITIONAL)
-- 
cgit v1.2.3


From 4932269ec3fafb011f9f6973f4d9d61e3558369f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= 
Date: Wed, 2 Mar 2022 00:58:40 +0100
Subject: Fix T94952: normals maps don't render correctly with GPU subdivision

A simple case of missing the tangent VBO. The tangents are computed from
the coarse mesh, and interpolated on the GPU for the final mesh. Code for
initializing the tangents, and the vertex format for the VBO was
factored out of the coarse extraction routine, to be shared with the
subdivision routine.
---
 .../blender/draw/intern/draw_cache_extract_mesh.cc |   2 +
 .../draw/intern/draw_cache_impl_subdivision.cc     |  18 ++-
 source/blender/draw/intern/draw_subdivision.h      |   3 +-
 .../mesh_extractors/extract_mesh_vbo_attributes.cc |   2 +-
 .../mesh_extractors/extract_mesh_vbo_pos_nor.cc    |   3 +-
 .../extract_mesh_vbo_sculpt_data.cc                |   2 +-
 .../intern/mesh_extractors/extract_mesh_vbo_tan.cc | 166 +++++++++++++++++----
 .../mesh_extractors/extract_mesh_vbo_vcol.cc       |   2 +-
 .../mesh_extractors/extract_mesh_vbo_weights.cc    |   2 +-
 9 files changed, 163 insertions(+), 37 deletions(-)

diff --git a/source/blender/draw/intern/draw_cache_extract_mesh.cc b/source/blender/draw/intern/draw_cache_extract_mesh.cc
index 4056f31bd5e..8b4962f24b0 100644
--- a/source/blender/draw/intern/draw_cache_extract_mesh.cc
+++ b/source/blender/draw/intern/draw_cache_extract_mesh.cc
@@ -835,6 +835,7 @@ static void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache *cache,
   EXTRACT_ADD_REQUESTED(vbo, edituv_data);
   /* Make sure UVs are computed before edituv stuffs. */
   EXTRACT_ADD_REQUESTED(vbo, uv);
+  EXTRACT_ADD_REQUESTED(vbo, tan);
   EXTRACT_ADD_REQUESTED(vbo, edituv_stretch_area);
   EXTRACT_ADD_REQUESTED(vbo, edituv_stretch_angle);
   EXTRACT_ADD_REQUESTED(ibo, lines_adjacency);
@@ -848,6 +849,7 @@ static void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache *cache,
     return;
   }
 
+  mesh_render_data_update_looptris(mr, MR_ITER_LOOPTRI, MR_DATA_LOOPTRI);
   mesh_render_data_update_loose_geom(mr, mbc, MR_ITER_LEDGE | MR_ITER_LVERT, MR_DATA_LOOSE_GEOM);
 
   void *data_stack = MEM_mallocN(extractors.data_size_total(), __func__);
diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
index 8c42eea1689..f88c1a0e945 100644
--- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc
+++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
@@ -1317,7 +1317,8 @@ void draw_subdiv_interp_custom_data(const DRWSubdivCache *cache,
                                     GPUVertBuf *src_data,
                                     GPUVertBuf *dst_data,
                                     int dimensions,
-                                    int dst_offset)
+                                    int dst_offset,
+                                    bool compress_to_u16)
 {
   GPUShader *shader = nullptr;
 
@@ -1337,10 +1338,17 @@ void draw_subdiv_interp_custom_data(const DRWSubdivCache *cache,
                                "#define DIMENSIONS 3\n");
   }
   else if (dimensions == 4) {
-    shader = get_subdiv_shader(SHADER_COMP_CUSTOM_DATA_INTERP_4D,
-                               "#define SUBDIV_POLYGON_OFFSET\n"
-                               "#define DIMENSIONS 4\n"
-                               "#define GPU_FETCH_U16_TO_FLOAT\n");
+    if (compress_to_u16) {
+      shader = get_subdiv_shader(SHADER_COMP_CUSTOM_DATA_INTERP_4D,
+                                 "#define SUBDIV_POLYGON_OFFSET\n"
+                                 "#define DIMENSIONS 4\n"
+                                 "#define GPU_FETCH_U16_TO_FLOAT\n");
+    }
+    else {
+      shader = get_subdiv_shader(SHADER_COMP_CUSTOM_DATA_INTERP_4D,
+                                 "#define SUBDIV_POLYGON_OFFSET\n"
+                                 "#define DIMENSIONS 4\n");
+    }
   }
   else {
     /* Crash if dimensions are not supported. */
diff --git a/source/blender/draw/intern/draw_subdivision.h b/source/blender/draw/intern/draw_subdivision.h
index 2f339cf18f8..5942797ef8f 100644
--- a/source/blender/draw/intern/draw_subdivision.h
+++ b/source/blender/draw/intern/draw_subdivision.h
@@ -197,7 +197,8 @@ void draw_subdiv_interp_custom_data(const DRWSubdivCache *cache,
                                     struct GPUVertBuf *src_data,
                                     struct GPUVertBuf *dst_data,
                                     int dimensions,
-                                    int dst_offset);
+                                    int dst_offset,
+                                    bool compress_to_u16);
 
 void draw_subdiv_extract_uvs(const DRWSubdivCache *cache,
                              struct GPUVertBuf *uvs,
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc
index b846da3f016..8568b87ba69 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc
@@ -409,7 +409,7 @@ static void extract_attr_init_subdiv(const DRWSubdivCache *subdiv_cache,
   /* Ensure data is uploaded properly. */
   GPU_vertbuf_tag_dirty(src_data);
   draw_subdiv_interp_custom_data(
-      subdiv_cache, src_data, dst_buffer, static_cast(dimensions), 0);
+      subdiv_cache, src_data, dst_buffer, static_cast(dimensions), 0, false);
 
   GPU_vertbuf_discard(src_data);
 }
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
index 0645683d58b..a844a116a87 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc
@@ -257,7 +257,8 @@ static void extract_pos_nor_init_subdiv(const DRWSubdivCache *subdiv_cache,
     GPU_vertbuf_init_build_on_device(
         dst_custom_normals, get_custom_normals_format(), subdiv_cache->num_subdiv_loops);
 
-    draw_subdiv_interp_custom_data(subdiv_cache, src_custom_normals, dst_custom_normals, 3, 0);
+    draw_subdiv_interp_custom_data(
+        subdiv_cache, src_custom_normals, dst_custom_normals, 3, 0, false);
 
     draw_subdiv_finalize_custom_normals(subdiv_cache, dst_custom_normals, vbo);
 
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_sculpt_data.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_sculpt_data.cc
index 753fbe7e0e2..4eef69411ca 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_sculpt_data.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_sculpt_data.cc
@@ -167,7 +167,7 @@ static void extract_sculpt_data_init_subdiv(const DRWSubdivCache *subdiv_cache,
     GPU_vertbuf_init_build_on_device(
         subdiv_mask_vbo, &mask_format, subdiv_cache->num_subdiv_loops);
 
-    draw_subdiv_interp_custom_data(subdiv_cache, mask_vbo, subdiv_mask_vbo, 1, 0);
+    draw_subdiv_interp_custom_data(subdiv_cache, mask_vbo, subdiv_mask_vbo, 1, 0, false);
   }
 
   /* Then, gather face sets. */
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_tan.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_tan.cc
index 03d1b327689..cce559e07f0 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_tan.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_tan.cc
@@ -32,22 +32,26 @@
 
 #include "extract_mesh.h"
 
+#include "draw_subdivision.h"
+
 namespace blender::draw {
 
 /* ---------------------------------------------------------------------- */
 /** \name Extract Tangent layers
  * \{ */
 
-static void extract_tan_ex_init(const MeshRenderData *mr,
-                                struct MeshBatchCache *cache,
-                                GPUVertBuf *vbo,
-                                const bool do_hq)
+static void extract_tan_init_common(const MeshRenderData *mr,
+                                    struct MeshBatchCache *cache,
+                                    GPUVertFormat *format,
+                                    GPUVertCompType comp_type,
+                                    GPUVertFetchMode fetch_mode,
+                                    CustomData *r_loop_data,
+                                    int *r_v_len,
+                                    int *r_tan_len,
+                                    char r_tangent_names[MAX_MTFACE][MAX_CUSTOMDATA_LAYER_NAME],
+                                    bool *r_use_orco_tan)
 {
-  GPUVertCompType comp_type = do_hq ? GPU_COMP_I16 : GPU_COMP_I10;
-  GPUVertFetchMode fetch_mode = GPU_FETCH_INT_TO_FLOAT_UNIT;
-
-  GPUVertFormat format = {0};
-  GPU_vertformat_deinterleave(&format);
+  GPU_vertformat_deinterleave(format);
 
   CustomData *cd_ldata = (mr->extract_type == MR_EXTRACT_BMESH) ? &mr->bm->ldata : &mr->me->ldata;
   CustomData *cd_vdata = (mr->extract_type == MR_EXTRACT_BMESH) ? &mr->bm->vdata : &mr->me->vdata;
@@ -57,7 +61,6 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
   bool use_orco_tan = cache->cd_used.tan_orco != 0;
 
   int tan_len = 0;
-  char tangent_names[MAX_MTFACE][MAX_CUSTOMDATA_LAYER_NAME];
 
   /* FIXME(T91838): This is to avoid a crash when orco tangent was requested but there are valid
    * uv layers. It would be better to fix the root cause. */
@@ -73,17 +76,17 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
       GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
       /* Tangent layer name. */
       BLI_snprintf(attr_name, sizeof(attr_name), "t%s", attr_safe_name);
-      GPU_vertformat_attr_add(&format, attr_name, comp_type, 4, fetch_mode);
+      GPU_vertformat_attr_add(format, attr_name, comp_type, 4, fetch_mode);
       /* Active render layer name. */
       if (i == CustomData_get_render_layer(cd_ldata, CD_MLOOPUV)) {
-        GPU_vertformat_alias_add(&format, "t");
+        GPU_vertformat_alias_add(format, "t");
       }
       /* Active display layer name. */
       if (i == CustomData_get_active_layer(cd_ldata, CD_MLOOPUV)) {
-        GPU_vertformat_alias_add(&format, "at");
+        GPU_vertformat_alias_add(format, "at");
       }
 
-      BLI_strncpy(tangent_names[tan_len++], layer_name, MAX_CUSTOMDATA_LAYER_NAME);
+      BLI_strncpy(r_tangent_names[tan_len++], layer_name, MAX_CUSTOMDATA_LAYER_NAME);
     }
   }
   if (use_orco_tan && orco == nullptr) {
@@ -110,20 +113,19 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
   }
 
   /* Start Fresh */
-  CustomData loop_data;
-  CustomData_reset(&loop_data);
+  CustomData_reset(r_loop_data);
   if (tan_len != 0 || use_orco_tan) {
     short tangent_mask = 0;
     bool calc_active_tangent = false;
     if (mr->extract_type == MR_EXTRACT_BMESH) {
       BKE_editmesh_loop_tangent_calc(mr->edit_bmesh,
                                      calc_active_tangent,
-                                     tangent_names,
+                                     r_tangent_names,
                                      tan_len,
                                      mr->poly_normals,
                                      mr->loop_normals,
                                      orco,
-                                     &loop_data,
+                                     r_loop_data,
                                      mr->loop_len,
                                      &tangent_mask);
     }
@@ -136,13 +138,13 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
                                     mr->tri_len,
                                     cd_ldata,
                                     calc_active_tangent,
-                                    tangent_names,
+                                    r_tangent_names,
                                     tan_len,
                                     mr->vert_normals,
                                     mr->poly_normals,
                                     mr->loop_normals,
                                     orco,
-                                    &loop_data,
+                                    r_loop_data,
                                     mr->loop_len,
                                     &tangent_mask);
     }
@@ -150,12 +152,12 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
 
   if (use_orco_tan) {
     char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
-    const char *layer_name = CustomData_get_layer_name(&loop_data, CD_TANGENT, 0);
+    const char *layer_name = CustomData_get_layer_name(r_loop_data, CD_TANGENT, 0);
     GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
     BLI_snprintf(attr_name, sizeof(*attr_name), "t%s", attr_safe_name);
-    GPU_vertformat_attr_add(&format, attr_name, comp_type, 4, fetch_mode);
-    GPU_vertformat_alias_add(&format, "t");
-    GPU_vertformat_alias_add(&format, "at");
+    GPU_vertformat_attr_add(format, attr_name, comp_type, 4, fetch_mode);
+    GPU_vertformat_alias_add(format, "t");
+    GPU_vertformat_alias_add(format, "at");
   }
 
   if (orco_allocated) {
@@ -163,12 +165,42 @@ static void extract_tan_ex_init(const MeshRenderData *mr,
   }
 
   int v_len = mr->loop_len;
-  if (format.attr_len == 0) {
-    GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
+  if (format->attr_len == 0) {
+    GPU_vertformat_attr_add(format, "dummy", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
     /* VBO will not be used, only allocate minimum of memory. */
     v_len = 1;
   }
 
+  *r_use_orco_tan = use_orco_tan;
+  *r_v_len = v_len;
+  *r_tan_len = tan_len;
+}
+
+static void extract_tan_ex_init(const MeshRenderData *mr,
+                                struct MeshBatchCache *cache,
+                                GPUVertBuf *vbo,
+                                const bool do_hq)
+{
+  GPUVertCompType comp_type = do_hq ? GPU_COMP_I16 : GPU_COMP_I10;
+  GPUVertFetchMode fetch_mode = GPU_FETCH_INT_TO_FLOAT_UNIT;
+
+  GPUVertFormat format = {0};
+  CustomData loop_data;
+  int v_len = 0;
+  int tan_len = 0;
+  bool use_orco_tan;
+  char tangent_names[MAX_MTFACE][MAX_CUSTOMDATA_LAYER_NAME];
+  extract_tan_init_common(mr,
+                          cache,
+                          &format,
+                          comp_type,
+                          fetch_mode,
+                          &loop_data,
+                          &v_len,
+                          &tan_len,
+                          tangent_names,
+                          &use_orco_tan);
+
   GPU_vertbuf_init_with_format(vbo, &format);
   GPU_vertbuf_data_alloc(vbo, v_len);
 
@@ -227,10 +259,92 @@ static void extract_tan_init(const MeshRenderData *mr,
   extract_tan_ex_init(mr, cache, vbo, false);
 }
 
+static GPUVertFormat *get_coarse_tan_format()
+{
+  static GPUVertFormat format = {0};
+  if (format.attr_len == 0) {
+    GPU_vertformat_attr_add(&format, "tan", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+  }
+  return &format;
+}
+
+static void extract_tan_init_subdiv(const DRWSubdivCache *subdiv_cache,
+                                    const MeshRenderData *mr,
+                                    struct MeshBatchCache *cache,
+                                    void *buffer,
+                                    void *UNUSED(data))
+{
+  GPUVertCompType comp_type = GPU_COMP_F32;
+  GPUVertFetchMode fetch_mode = GPU_FETCH_FLOAT;
+  GPUVertFormat format = {0};
+  CustomData loop_data;
+  int coarse_len = 0;
+  int tan_len = 0;
+  bool use_orco_tan;
+  char tangent_names[MAX_MTFACE][MAX_CUSTOMDATA_LAYER_NAME];
+  extract_tan_init_common(mr,
+                          cache,
+                          &format,
+                          comp_type,
+                          fetch_mode,
+                          &loop_data,
+                          &coarse_len,
+                          &tan_len,
+                          tangent_names,
+                          &use_orco_tan);
+
+  GPUVertBuf *dst_buffer = static_cast(buffer);
+  GPU_vertbuf_init_build_on_device(dst_buffer, &format, subdiv_cache->num_subdiv_loops);
+
+  GPUVertBuf *coarse_vbo = GPU_vertbuf_calloc();
+  /* Dynamic as we upload and interpolate layers one at a time. */
+  GPU_vertbuf_init_with_format_ex(coarse_vbo, get_coarse_tan_format(), GPU_USAGE_DYNAMIC);
+  GPU_vertbuf_data_alloc(coarse_vbo, coarse_len);
+
+  /* Index of the tangent layer in the compact buffer. Used layers are stored in a single buffer.
+   */
+  int pack_layer_index = 0;
+  for (int i = 0; i < tan_len; i++) {
+    float(*tan_data)[4] = (float(*)[4])GPU_vertbuf_get_data(coarse_vbo);
+    const char *name = tangent_names[i];
+    float(*layer_data)[4] = (float(*)[4])CustomData_get_layer_named(&loop_data, CD_TANGENT, name);
+    for (int ml_index = 0; ml_index < mr->loop_len; ml_index++) {
+      copy_v3_v3(*tan_data, layer_data[ml_index]);
+      (*tan_data)[3] = (layer_data[ml_index][3] > 0.0f) ? 1.0f : -1.0f;
+      tan_data++;
+    }
+
+    /* Ensure data is uploaded properly. */
+    GPU_vertbuf_tag_dirty(coarse_vbo);
+    /* Include stride in offset. */
+    const int dst_offset = (int)subdiv_cache->num_subdiv_loops * 4 * pack_layer_index++;
+    draw_subdiv_interp_custom_data(subdiv_cache, coarse_vbo, dst_buffer, 4, dst_offset, false);
+  }
+  if (use_orco_tan) {
+    float(*tan_data)[4] = (float(*)[4])GPU_vertbuf_get_data(coarse_vbo);
+    float(*layer_data)[4] = (float(*)[4])CustomData_get_layer_n(&loop_data, CD_TANGENT, 0);
+    for (int ml_index = 0; ml_index < mr->loop_len; ml_index++) {
+      copy_v3_v3(*tan_data, layer_data[ml_index]);
+      (*tan_data)[3] = (layer_data[ml_index][3] > 0.0f) ? 1.0f : -1.0f;
+      tan_data++;
+    }
+
+    /* Ensure data is uploaded properly. */
+    GPU_vertbuf_tag_dirty(coarse_vbo);
+    /* Include stride in offset. */
+    const int dst_offset = (int)subdiv_cache->num_subdiv_loops * 4 * pack_layer_index++;
+    draw_subdiv_interp_custom_data(subdiv_cache, coarse_vbo, dst_buffer, 4, dst_offset, true);
+  }
+
+  CustomData_free(&loop_data, mr->loop_len);
+  GPU_vertbuf_discard(coarse_vbo);
+}
+
 constexpr MeshExtract create_extractor_tan()
 {
   MeshExtract extractor = {nullptr};
   extractor.init = extract_tan_init;
+  extractor.init_subdiv = extract_tan_init_subdiv;
   extractor.data_type = MR_DATA_POLY_NOR | MR_DATA_TAN_LOOP_NOR | MR_DATA_LOOPTRI;
   extractor.data_size = 0;
   extractor.use_threading = false;
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_vcol.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_vcol.cc
index a0307b9b2cd..324ac4bae37 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_vcol.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_vcol.cc
@@ -180,7 +180,7 @@ static void extract_vcol_init_subdiv(const DRWSubdivCache *subdiv_cache,
 
       /* Ensure data is uploaded properly. */
       GPU_vertbuf_tag_dirty(src_data);
-      draw_subdiv_interp_custom_data(subdiv_cache, src_data, dst_buffer, 4, dst_offset);
+      draw_subdiv_interp_custom_data(subdiv_cache, src_data, dst_buffer, 4, dst_offset, true);
     }
   }
 
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc
index bb608014c53..b5727367412 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_weights.cc
@@ -201,7 +201,7 @@ static void extract_weights_init_subdiv(const DRWSubdivCache *subdiv_cache,
     }
   }
 
-  draw_subdiv_interp_custom_data(subdiv_cache, coarse_weights, vbo, 1, 0);
+  draw_subdiv_interp_custom_data(subdiv_cache, coarse_weights, vbo, 1, 0, false);
 
   GPU_vertbuf_discard(coarse_weights);
 }
-- 
cgit v1.2.3


From aa500c4fcabe7dad834b91572c75bee760041ede Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 12:07:18 +1100
Subject: Cleanup: use back-slash for doxygen commands, color after parameters

---
 intern/ghost/GHOST_C-api.h                                   |  2 +-
 intern/ghost/GHOST_ISystem.h                                 |  2 +-
 intern/ghost/intern/GHOST_System.h                           |  2 +-
 intern/ghost/intern/GHOST_SystemCocoa.h                      |  2 +-
 source/blender/blenkernel/BKE_lib_remap.h                    |  2 +-
 source/blender/editors/sculpt_paint/sculpt_intern.h          |  6 +++---
 source/blender/editors/space_outliner/tree/tree_element.hh   |  4 ++--
 .../functions/FN_multi_function_procedure_optimization.hh    |  6 +++---
 source/blender/gpu/intern/gpu_shader_create_info.hh          |  2 +-
 source/blender/imbuf/IMB_imbuf.h                             | 12 ++++++------
 source/blender/io/wavefront_obj/exporter/obj_export_io.hh    |  2 +-
 source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh  |  2 +-
 12 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index 18439350238..4e48a908c00 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -253,7 +253,7 @@ extern int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle);
  * Get the Window under the cursor.
  * \param x: The x-coordinate of the cursor.
  * \param y: The y-coordinate of the cursor.
- * @return The window under the cursor or nullptr in none.
+ * \return The window under the cursor or nullptr in none.
  */
 extern GHOST_WindowHandle GHOST_GetWindowUnderCursor(GHOST_SystemHandle systemhandle,
                                                      int32_t x,
diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h
index a33879522f4..ed193ee7e5d 100644
--- a/intern/ghost/GHOST_ISystem.h
+++ b/intern/ghost/GHOST_ISystem.h
@@ -313,7 +313,7 @@ class GHOST_ISystem {
    * Get the Window under the cursor.
    * \param x: The x-coordinate of the cursor.
    * \param y: The y-coordinate of the cursor.
-   * @return The window under the cursor or nullptr if none.
+   * \return The window under the cursor or nullptr if none.
    */
   virtual GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y) = 0;
 
diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h
index 0911b35f617..0e1e3f734ae 100644
--- a/intern/ghost/intern/GHOST_System.h
+++ b/intern/ghost/intern/GHOST_System.h
@@ -161,7 +161,7 @@ class GHOST_System : public GHOST_ISystem {
    * Get the Window under the cursor.
    * \param x: The x-coordinate of the cursor.
    * \param y: The y-coordinate of the cursor.
-   * @return The window under the cursor or nullptr if none.
+   * \return The window under the cursor or nullptr if none.
    */
   GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y);
 
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.h b/intern/ghost/intern/GHOST_SystemCocoa.h
index 926d50b7942..8b6dfb4efed 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.h
+++ b/intern/ghost/intern/GHOST_SystemCocoa.h
@@ -113,7 +113,7 @@ class GHOST_SystemCocoa : public GHOST_System {
    * Get the Window under the cursor.
    * \param x: The x-coordinate of the cursor.
    * \param y: The y-coordinate of the cursor.
-   * @return The window under the cursor or nullptr if none.
+   * \return The window under the cursor or nullptr if none.
    */
   GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y);
 
diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h
index 94b94303ec9..fd7d39fc250 100644
--- a/source/blender/blenkernel/BKE_lib_remap.h
+++ b/source/blender/blenkernel/BKE_lib_remap.h
@@ -220,7 +220,7 @@ IDRemapperApplyResult BKE_id_remapper_apply(const struct IDRemapper *id_remapper
  * Use this function when `ID_REMAP_APPLY_UNMAP_WHEN_REMAPPING_TO_SELF`. In this case
  * the #id_self parameter is required. Otherwise the #BKE_id_remapper_apply can be used.
  *
- * \param id_self required for ID_REMAP_APPLY_UNMAP_WHEN_REMAPPING_TO_SELF.
+ * \param id_self: required for ID_REMAP_APPLY_UNMAP_WHEN_REMAPPING_TO_SELF.
  *     When remapping to id_self it will then be remapped to NULL.
  */
 IDRemapperApplyResult BKE_id_remapper_apply_ex(const struct IDRemapper *id_remapper,
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index a6b412b2b7e..6f9df4d8252 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -1105,10 +1105,10 @@ bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v);
 bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v);
 
 /**
- * Initialize a point-in-brush test with a given falloff shape
+ * Initialize a point-in-brush test with a given falloff shape.
  *
- * \param falloff_shape PAINT_FALLOFF_SHAPE_SPHERE or PAINT_FALLOFF_SHAPE_TUBE
- * \return The brush falloff function
+ * \param falloff_shape: #PAINT_FALLOFF_SHAPE_SPHERE or #PAINT_FALLOFF_SHAPE_TUBE.
+ * \return The brush falloff function.
  */
 SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss,
                                                             SculptBrushTest *test,
diff --git a/source/blender/editors/space_outliner/tree/tree_element.hh b/source/blender/editors/space_outliner/tree/tree_element.hh
index 996f51eee82..2fbc86705b9 100644
--- a/source/blender/editors/space_outliner/tree/tree_element.hh
+++ b/source/blender/editors/space_outliner/tree/tree_element.hh
@@ -89,8 +89,8 @@ void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner
 /**
  * Get actual warning data of a tree element, if any.
  *
- * \param r_icon The icon to display as warning.
- * \param r_message The message to display as warning.
+ * \param r_icon: The icon to display as warning.
+ * \param r_message: The message to display as warning.
  * \return true if there is a warning, false otherwise.
  */
 bool tree_element_warnings_get(struct TreeElement *te, int *r_icon, const char **r_message);
diff --git a/source/blender/functions/FN_multi_function_procedure_optimization.hh b/source/blender/functions/FN_multi_function_procedure_optimization.hh
index 6e1e804c804..f5a02d84d42 100644
--- a/source/blender/functions/FN_multi_function_procedure_optimization.hh
+++ b/source/blender/functions/FN_multi_function_procedure_optimization.hh
@@ -38,9 +38,9 @@ namespace blender::fn::procedure_optimization {
  * For simplicity, and because this is the most common use case, this optimization currently only
  * works on a single chain of instructions. Destruct instructions are not moved across branches.
  *
- * \param procedure The procedure that should be optimized.
- * \param block_end_instr The instruction that points to the last instruction within a linear chain
- *   of instructions. The algorithm moves instructions backward starting at this instruction.
+ * \param procedure: The procedure that should be optimized.
+ * \param block_end_instr: The instruction that points to the last instruction within a linear
+ * chain of instructions. The algorithm moves instructions backward starting at this instruction.
  */
 void move_destructs_up(MFProcedure &procedure, MFInstruction &block_end_instr);
 
diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh
index e05dce57674..bf74d44d9a7 100644
--- a/source/blender/gpu/intern/gpu_shader_create_info.hh
+++ b/source/blender/gpu/intern/gpu_shader_create_info.hh
@@ -255,7 +255,7 @@ struct StageInterfaceInfo {
 };
 
 /**
- * @brief Describe inputs & outputs, stage interfaces, resources and sources of a shader.
+ * \brief Describe inputs & outputs, stage interfaces, resources and sources of a shader.
  *        If all data is correctly provided, this is all that is needed to create and compile
  *        a GPUShader.
  *
diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h
index 6b35e84ca51..8929a467670 100644
--- a/source/blender/imbuf/IMB_imbuf.h
+++ b/source/blender/imbuf/IMB_imbuf.h
@@ -900,16 +900,16 @@ typedef enum eIMBTransformMode {
 /**
  * \brief Transform source image buffer onto destination image buffer using a transform matrix.
  *
- * \param src Image buffer to read from.
- * \param dst Image buffer to write to. rect or rect_float must already be initialized.
+ * \param src: Image buffer to read from.
+ * \param dst: Image buffer to write to. rect or rect_float must already be initialized.
  * - dst buffer must be a 4 channel buffers.
  * - Only one data type buffer will be used (rect_float has priority over rect)
- * \param mode Cropping/Wrap repeat effect to apply during transformation.
- * \param filter Interpolation to use during sampling.
- * \param transform_matrix Transformation matrix to use.
+ * \param mode: Cropping/Wrap repeat effect to apply during transformation.
+ * \param filter: Interpolation to use during sampling.
+ * \param transform_matrix: Transformation matrix to use.
  * The given matrix should transform between dst pixel space to src pixel space.
  * One unit is one pixel.
- * \param src_crop cropping region how to crop the source buffer. Should only be passed when mode
+ * \param src_crop: Cropping region how to crop the source buffer. Should only be passed when mode
  * is set to #IMB_TRANSFORM_MODE_CROP_SRC. For any other mode this should be empty.
  *
  * During transformation no data/color conversion will happens.
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
index 7016dff2a29..3bee93b36a5 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
@@ -310,7 +310,7 @@ class FormatHandler : NonCopyable, NonMovable {
   /**
    * Example invocation: `writer->write("foo")`.
    *
-   * \param key Must match what the instance's filetype expects; i.e., `eMTLSyntaxElement` for
+   * \param key: Must match what the instance's filetype expects; i.e., `eMTLSyntaxElement` for
    * `eFileType::MTL`.
    */
   template::SyntaxType key, typename... T>
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
index 9b9c8483464..5b11c85b7a4 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
@@ -189,7 +189,7 @@ class OBJMesh : NonCopyable {
   }
   /**
    * Calculate a polygon's polygon/loop normal indices.
-   * \param poly_index Index of the polygon to calculate indices for.
+   * \param poly_index: Index of the polygon to calculate indices for.
    * \return Vector of normal indices, aligned with vertices of polygon.
    */
   Vector calc_poly_normal_indices(int poly_index) const;
-- 
cgit v1.2.3


From cf428b2ebddabd5786f65ae8901f25a4cbf456da Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 12:15:01 +1100
Subject: Fix ignored click-drag events when operators pass-through & finished

Replacing tweak key-map items with click-drag caused selection
in the graph/sequencer/node editors to ignore drag events
(all uses of WM_generic_select_modal).

Operators that return OPERATOR_PASS_THROUGH | OPERATOR_FINISHED
result in the event loop considering the event as being handled.

This stopped WM_CLICK_DRAG events from being generated which is not the
case for tweak events.

As click-drag is intended to be compatible with tweak events,
accept drag events when WM_HANDLER_BREAK isn't set or when
wm_action_not_handled returns true.
---
 source/blender/windowmanager/intern/wm_event_system.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index fb5af920ff8..20940c59679 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3155,8 +3155,14 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
   }
 
   if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
-    /* Test for CLICK_DRAG events. */
-    if (wm_action_not_handled(action)) {
+    /* Test for #WM_CLICK_DRAG events. */
+
+    /* NOTE(@campbellbarton): Needed so drag can be used for editors that support both click
+     * selection and passing through the drag action to box select. See #WM_generic_select_modal.
+     * Unlike click, accept `action` when break isn't set.
+     * Operators can return `OPERATOR_FINISHED | OPERATOR_PASS_THROUGH` which results
+     * in `action` setting #WM_HANDLER_HANDLED, but not #WM_HANDLER_BREAK. */
+    if ((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action)) {
       if (win->event_queue_check_drag) {
         if (WM_event_drag_test(event, event->prev_click_xy)) {
           win->event_queue_check_drag_handled = true;
@@ -3184,7 +3190,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
           copy_v2_v2_int(event->xy, prev_xy);
 
           win->event_queue_check_click = false;
-          if (!wm_action_not_handled(action)) {
+          if (!((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action))) {
             /* Only disable when handled as other handlers may use this drag event. */
             win->event_queue_check_drag = false;
           }
-- 
cgit v1.2.3


From aa71414dfca7f301e101cce3e72551e7529290ea Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 14:28:06 +1100
Subject: Fix click-drag events for dragging markers

Clicking and dragging markers accumulates flags from multiple operators
in a way that can't be interpreted when combine.

Follow tweak behavior for cancelling click-drag events.
---
 .../blender/windowmanager/intern/wm_event_system.c | 69 +++++++++++-----------
 1 file changed, 34 insertions(+), 35 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 20940c59679..b3b99b0b7fc 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3157,44 +3157,40 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
   if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
     /* Test for #WM_CLICK_DRAG events. */
 
-    /* NOTE(@campbellbarton): Needed so drag can be used for editors that support both click
+    /* NOTE(@campbellbarton): Ignore `action` so drag can be used for editors that use both click
      * selection and passing through the drag action to box select. See #WM_generic_select_modal.
-     * Unlike click, accept `action` when break isn't set.
-     * Operators can return `OPERATOR_FINISHED | OPERATOR_PASS_THROUGH` which results
-     * in `action` setting #WM_HANDLER_HANDLED, but not #WM_HANDLER_BREAK. */
-    if ((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action)) {
-      if (win->event_queue_check_drag) {
-        if (WM_event_drag_test(event, event->prev_click_xy)) {
-          win->event_queue_check_drag_handled = true;
-
-          const int prev_xy[2] = {UNPACK2(event->xy)};
-          const short prev_val = event->val;
-          const short prev_type = event->type;
-          const uint8_t prev_modifier = event->modifier;
-          const short prev_keymodifier = event->keymodifier;
-
-          copy_v2_v2_int(event->xy, event->prev_click_xy);
-          event->val = KM_CLICK_DRAG;
-          event->type = event->prev_type;
-          event->modifier = event->prev_click_modifier;
-          event->keymodifier = event->prev_click_keymodifier;
-
-          CLOG_INFO(WM_LOG_HANDLERS, 1, "handling PRESS_DRAG");
+     * In the case of marker select-drag the combinations of (pass-through / finished / modal)
+     * can accumulate to have flags set that they can't be properly interpreted here.
+     * Instead `win->event_queue_check_drag` is cleared in `wm_event_do_handlers`. */
+    if (win->event_queue_check_drag) {
+      if (WM_event_drag_test(event, event->prev_click_xy)) {
+        win->event_queue_check_drag_handled = true;
+
+        const int prev_xy[2] = {UNPACK2(event->xy)};
+        const short prev_val = event->val;
+        const short prev_type = event->type;
+        const uint8_t prev_modifier = event->modifier;
+        const short prev_keymodifier = event->keymodifier;
+
+        copy_v2_v2_int(event->xy, event->prev_click_xy);
+        event->val = KM_CLICK_DRAG;
+        event->type = event->prev_type;
+        event->modifier = event->prev_click_modifier;
+        event->keymodifier = event->prev_click_keymodifier;
+
+        CLOG_INFO(WM_LOG_HANDLERS, 1, "handling PRESS_DRAG");
 
-          action |= wm_handlers_do_intern(C, win, event, handlers);
+        WM_event_print(event);
 
-          event->keymodifier = prev_keymodifier;
-          event->modifier = prev_modifier;
-          event->val = prev_val;
-          event->type = prev_type;
-          copy_v2_v2_int(event->xy, prev_xy);
+        action |= wm_handlers_do_intern(C, win, event, handlers);
 
-          win->event_queue_check_click = false;
-          if (!((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action))) {
-            /* Only disable when handled as other handlers may use this drag event. */
-            win->event_queue_check_drag = false;
-          }
-        }
+        event->keymodifier = prev_keymodifier;
+        event->modifier = prev_modifier;
+        event->val = prev_val;
+        event->type = prev_type;
+        copy_v2_v2_int(event->xy, prev_xy);
+
+        win->event_queue_check_click = false;
       }
     }
     else {
@@ -3270,7 +3266,6 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
     }
     else {
       win->event_queue_check_click = false;
-      win->event_queue_check_drag = false;
     }
   }
   else if (ISMOUSE_WHEEL(event->type) || ISMOUSE_GESTURE(event->type)) {
@@ -3720,6 +3715,10 @@ void wm_event_do_handlers(bContext *C)
       /* Builtin tweak, if action is break it removes tweak. */
       wm_tweakevent_test(C, event, action);
 
+      if (action & WM_HANDLER_BREAK) {
+        win->event_queue_check_drag = false;
+      }
+
       if ((action & WM_HANDLER_BREAK) == 0) {
         /* NOTE: setting subwin active should be done here, after modal handlers have been done. */
         if (event->type == MOUSEMOVE) {
-- 
cgit v1.2.3


From 78372d8b9ca2e923945a009166b9bc04f74ddadf Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 14:44:34 +1100
Subject: Event System: support "Release Confirms" for click-drag events

---
 source/blender/editors/animation/anim_markers.c    |  5 ++--
 source/blender/windowmanager/WM_api.h              |  6 +++--
 .../blender/windowmanager/intern/wm_event_query.c  | 27 ++++++++++++----------
 3 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c
index ab51702d628..95125516fe8 100644
--- a/source/blender/editors/animation/anim_markers.c
+++ b/source/blender/editors/animation/anim_markers.c
@@ -700,7 +700,7 @@ static void MARKER_OT_add(wmOperatorType *ot)
 typedef struct MarkerMove {
   SpaceLink *slink;
   ListBase *markers;
-  int event_type; /* store invoke-event, to verify */
+  short event_type, event_val; /* store invoke-event, to verify */
   int *oldframe, evtx, firstx;
   NumInput num;
 } MarkerMove;
@@ -844,6 +844,7 @@ static int ed_marker_move_invoke(bContext *C, wmOperator *op, const wmEvent *eve
     mm->evtx = event->xy[0];
     mm->firstx = event->xy[0];
     mm->event_type = event->type;
+    mm->event_val = event->val;
 
     /* add temp handler */
     WM_event_add_modal_handler(C, op);
@@ -941,7 +942,7 @@ static int ed_marker_move_modal(bContext *C, wmOperator *op, const wmEvent *even
       case EVT_PADENTER:
       case LEFTMOUSE:
       case MIDDLEMOUSE:
-        if (WM_event_is_modal_tweak_exit(event, mm->event_type)) {
+        if (WM_event_is_modal_drag_exit(event, mm->event_type, mm->event_val)) {
           ed_marker_move_exit(C, op);
           WM_event_add_notifier(C, NC_SCENE | ND_MARKERS, NULL);
           WM_event_add_notifier(C, NC_ANIMATION | ND_MARKERS, NULL);
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 2c2143f350a..07eaa2ab976 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -1424,9 +1424,11 @@ bool WM_window_modal_keymap_status_draw(struct bContext *C,
 void WM_event_print(const struct wmEvent *event);
 
 /**
- * For modal callbacks, check configuration for how to interpret exit with tweaks.
+ * For modal callbacks, check configuration for how to interpret exit when dragging.
  */
-bool WM_event_is_modal_tweak_exit(const struct wmEvent *event, int tweak_event);
+bool WM_event_is_modal_drag_exit(const struct wmEvent *event,
+                                 short init_event_type,
+                                 short init_event_val);
 bool WM_event_is_last_mousemove(const struct wmEvent *event);
 bool WM_event_is_mouse_drag(const struct wmEvent *event);
 bool WM_event_is_mouse_drag_or_press(const wmEvent *event);
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index 5e8b9b945c9..ddca10a8382 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -183,34 +183,37 @@ bool WM_event_type_mask_test(const int event_type, const enum eEventType_Mask ma
 /** \name Event Motion Queries
  * \{ */
 
-bool WM_event_is_modal_tweak_exit(const wmEvent *event, int tweak_event)
+bool WM_event_is_modal_drag_exit(const wmEvent *event,
+                                 const short init_event_type,
+                                 const short init_event_val)
 {
-  /* if the release-confirm userpref setting is enabled,
-   * tweak events can be canceled when mouse is released
-   */
+  /* If the release-confirm preference setting is enabled,
+   * drag events can be canceled when mouse is released. */
   if (U.flag & USER_RELEASECONFIRM) {
     /* option on, so can exit with km-release */
     if (event->val == KM_RELEASE) {
-      switch (tweak_event) {
+      switch (init_event_type) {
         case EVT_TWEAK_L:
         case EVT_TWEAK_M:
         case EVT_TWEAK_R:
           return 1;
       }
+      if ((init_event_val == KM_CLICK_DRAG) && (event->type == init_event_type)) {
+        return 1;
+      }
     }
     else {
-      /* if the initial event wasn't a tweak event then
-       * ignore USER_RELEASECONFIRM setting: see T26756. */
-      if (ELEM(tweak_event, EVT_TWEAK_L, EVT_TWEAK_M, EVT_TWEAK_R) == 0) {
+      /* If the initial event wasn't a drag event then
+       * ignore #USER_RELEASECONFIRM setting: see T26756. */
+      if ((ELEM(init_event_type, EVT_TWEAK_L, EVT_TWEAK_M, EVT_TWEAK_R) ||
+           init_event_val == KM_CLICK_DRAG) == 0) {
         return 1;
       }
     }
   }
   else {
-    /* this is fine as long as not doing km-release, otherwise
-     * some items (i.e. markers) being tweaked may end up getting
-     * dropped all over
-     */
+    /* This is fine as long as not doing km-release, otherwise some items (i.e. markers)
+     * being tweaked may end up getting dropped all over. */
     if (event->val != KM_RELEASE) {
       return 1;
     }
-- 
cgit v1.2.3


From 426ff481a789017bd5810a2064ec06a298a6f2dc Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 14:53:15 +1100
Subject: Event System: match click-drag & tweak event handling for transform

---
 source/blender/editors/transform/transform.c               | 2 +-
 source/blender/editors/transform/transform.h               | 8 +++++---
 source/blender/editors/transform/transform_convert_graph.c | 2 +-
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index d7a71350934..36ffcc8cd68 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -1697,7 +1697,7 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
 
   /* Needed to translate tweak events to mouse buttons. */
   t->launch_event = event ? WM_userdef_event_type_from_keymap_type(event->type) : -1;
-  t->is_launch_event_tweak = event ? ISTWEAK(event->type) : false;
+  t->is_launch_event_drag = event ? (ISTWEAK(event->type) || event->val == KM_CLICK_DRAG) : false;
 
   /* XXX Remove this when wm_operator_call_internal doesn't use window->eventstate
    * (which can have type = 0) */
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index 757c11f1179..3ee5868d5be 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -592,9 +592,11 @@ typedef struct TransInfo {
   /*************** NEW STUFF *********************/
   /** event type used to launch transform. */
   short launch_event;
-  /** Is the actual launch event a tweak event? (launch_event above is set to the corresponding
-   * mouse button then.) */
-  bool is_launch_event_tweak;
+  /**
+   * Is the actual launch event a drag event?
+   * (`launch_event` is set to the corresponding mouse button then.)
+   */
+  bool is_launch_event_drag;
 
   bool is_orient_default_overwrite;
 
diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c
index 608fd59c8b7..54222fbb117 100644
--- a/source/blender/editors/transform/transform_convert_graph.c
+++ b/source/blender/editors/transform/transform_convert_graph.c
@@ -161,7 +161,7 @@ static void graph_bezt_get_transform_selection(const TransInfo *t,
   bool left = use_handle ? ((bezt->f1 & SELECT) != 0) : key;
   bool right = use_handle ? ((bezt->f3 & SELECT) != 0) : key;
 
-  if (use_handle && t->is_launch_event_tweak) {
+  if (use_handle && t->is_launch_event_drag) {
     if (sipo->runtime.flag & SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT) {
       key = right = false;
     }
-- 
cgit v1.2.3


From 4986f718482b061082936f1f6aa13929741093a2 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 15:07:00 +1100
Subject: Event System: remove tweak events in favor of click-drag

Supporting two kinds of dragging is redundant, remove tweak events as
they only supported 3 mouse buttons and added complexity from using the
'value' to store directions.

Support only click-drag events (KM_CLICK_DRAG) which can be used with
any keyboard or mouse button.

Details:

- A "direction" member has been added to keymap items and events which
  can be used when the event value is set to KM_CLICK_DRAG.

- Keymap items are version patched.

- Loading older key-maps are also updated.

- Currently the key-maps stored in ./release/scripts/presets/keyconfig/
  still reference tweak events & need updating. For now they are updated
  on load.

Note that in general this wont impact add-ons as modal operators don't
receive tweak events.

Reviewed By: brecht

Ref D14214
---
 release/scripts/modules/bl_keymap_utils/io.py      |  16 +++
 .../scripts/modules/bl_keymap_utils/versioning.py  |  18 +++
 release/scripts/modules/rna_keymap_ui.py           |   4 +
 .../keyconfig/keymap_data/blender_default.py       |  26 ++---
 source/blender/blenkernel/BKE_blender_version.h    |   2 +-
 .../blender/blenloader/intern/versioning_userdef.c |  50 ++++++++-
 .../editors/interface/interface_context_menu.c     |   2 +-
 .../blender/editors/interface/interface_handlers.c |  12 +-
 source/blender/editors/interface/interface_icons.c |  12 --
 .../editors/space_view3d/view3d_navigate_dolly.c   |   9 +-
 .../editors/space_view3d/view3d_navigate_move.c    |   4 +-
 source/blender/editors/transform/transform.c       |   2 +-
 source/blender/makesdna/DNA_windowmanager_types.h  |   9 +-
 source/blender/makesrna/RNA_enum_items.h           |   5 +-
 source/blender/makesrna/intern/rna_wm.c            |  91 +++------------
 source/blender/makesrna/intern/rna_wm_api.c        |  17 ++-
 source/blender/windowmanager/WM_api.h              |   2 +
 source/blender/windowmanager/WM_keymap.h           |  63 ++++++++---
 source/blender/windowmanager/WM_types.h            |   5 +-
 .../windowmanager/gizmo/intern/wm_gizmo_group.c    |  48 ++++----
 source/blender/windowmanager/intern/wm.c           |   1 -
 .../blender/windowmanager/intern/wm_event_query.c  |  93 ++++++++++-----
 .../blender/windowmanager/intern/wm_event_system.c |  30 ++---
 source/blender/windowmanager/intern/wm_gesture.c   |  77 -------------
 .../blender/windowmanager/intern/wm_gesture_ops.c  | 125 ---------------------
 source/blender/windowmanager/intern/wm_keymap.c    |  36 ++++--
 .../blender/windowmanager/intern/wm_keymap_utils.c |  44 ++++++--
 source/blender/windowmanager/intern/wm_operators.c |   2 +-
 source/blender/windowmanager/wm.h                  |   8 --
 source/blender/windowmanager/wm_event_types.h      |  20 +---
 30 files changed, 372 insertions(+), 461 deletions(-)

diff --git a/release/scripts/modules/bl_keymap_utils/io.py b/release/scripts/modules/bl_keymap_utils/io.py
index f34002741c6..456a1fa5a83 100644
--- a/release/scripts/modules/bl_keymap_utils/io.py
+++ b/release/scripts/modules/bl_keymap_utils/io.py
@@ -52,6 +52,8 @@ def kmi_args_as_data(kmi):
                 s.append(f"\"{attr:s}\": " + ("-1" if mod == -1 else "True"))
     if (mod := kmi.key_modifier) and (mod != 'NONE'):
         s.append(f"\"key_modifier\": '{mod:s}'")
+    if (direction := kmi.direction) and (direction != 'ANY'):
+        s.append(f"\"direction\": '{direction:s}'")
 
     if kmi.repeat:
         if (
@@ -247,6 +249,20 @@ def _init_properties_from_data(base_props, base_value):
 def keymap_init_from_data(km, km_items, is_modal=False):
     new_fn = getattr(km.keymap_items, "new_modal" if is_modal else "new")
     for (kmi_idname, kmi_args, kmi_data) in km_items:
+
+        # TODO(@campbellbarton): Temporary workaround keep until our
+        # key-maps have been updated to remove tweak events.
+        if ty_new := {
+                'EVT_TWEAK_L': 'LEFTMOUSE',
+                'EVT_TWEAK_M': 'MIDDLEMOUSE',
+                'EVT_TWEAK_R': 'RIGHTMOUSE',
+        }.get(kmi_args["type"]):
+            kmi_args["type"] = ty_new
+            if (value := kmi_args["value"]) != 'ANY':
+                kmi_args["direction"] = value
+            kmi_args["value"] = 'CLICK_DRAG'
+        # End workaround.
+
         kmi = new_fn(kmi_idname, **kmi_args)
         if kmi_data is not None:
             if not kmi_data.get("active", True):
diff --git a/release/scripts/modules/bl_keymap_utils/versioning.py b/release/scripts/modules/bl_keymap_utils/versioning.py
index ee7cc5daceb..402c21f8ef1 100644
--- a/release/scripts/modules/bl_keymap_utils/versioning.py
+++ b/release/scripts/modules/bl_keymap_utils/versioning.py
@@ -30,4 +30,22 @@ def keyconfig_update(keyconfig_data, keyconfig_version):
                     # Setting repeat true on other kinds of events is harmless.
                     item_event["repeat"] = True
 
+    if keyconfig_version <= (3, 2, 5):
+        # Only copy once.
+        if not has_copy:
+            keyconfig_data = copy.deepcopy(keyconfig_data)
+            has_copy = True
+
+        for _km_name, _km_parms, km_items_data in keyconfig_data:
+            for (_item_op, item_event, _item_prop) in km_items_data["items"]:
+                if ty_new := {
+                        'EVT_TWEAK_L': 'LEFTMOUSE',
+                        'EVT_TWEAK_M': 'MIDDLEMOUSE',
+                        'EVT_TWEAK_R': 'RIGHTMOUSE',
+                }.get(item_event.get("type")):
+                    item_event["type"] = ty_new
+                    if (value := item_event["value"]) != 'ANY':
+                        item_event["direction"] = value
+                    item_event["value"] = 'CLICK_DRAG'
+
     return keyconfig_data
diff --git a/release/scripts/modules/rna_keymap_ui.py b/release/scripts/modules/rna_keymap_ui.py
index 2676c00c655..5da98cd783d 100644
--- a/release/scripts/modules/rna_keymap_ui.py
+++ b/release/scripts/modules/rna_keymap_ui.py
@@ -180,6 +180,10 @@ def draw_kmi(display_keymaps, kc, km, kmi, layout, level):
                 subrow.prop(kmi, "type", text="")
                 subrow.prop(kmi, "value", text="")
 
+            if map_type in {'KEYBOARD', 'MOUSE'} and kmi.value == 'CLICK_DRAG':
+                subrow = sub.row()
+                subrow.prop(kmi, "direction")
+
             subrow = sub.row()
             subrow.scale_x = 0.75
             subrow.prop(kmi, "any", toggle=True)
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 5fb0f052154..bf71b8aece8 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -1426,28 +1426,28 @@ def km_view3d(params):
         ("view3d.view_axis", {"type": 'NUMPAD_7', "value": 'PRESS', "shift": True, "ctrl": True},
          {"properties": [("type", 'BOTTOM'), ("align_active", True)]}),
         *((
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'NORTH', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'NORTH', "alt": True},
              {"properties": [("type", 'TOP'), ("relative", True)]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'SOUTH', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'SOUTH', "alt": True},
              {"properties": [("type", 'BOTTOM'), ("relative", True)]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'EAST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'EAST', "alt": True},
              {"properties": [("type", 'RIGHT'), ("relative", True)]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'WEST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'WEST', "alt": True},
              {"properties": [("type", 'LEFT'), ("relative", True)]}),
         ) if params.v3d_alt_mmb_drag_action == 'RELATIVE' else (
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'NORTH', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'NORTH', "alt": True},
              {"properties": [("type", 'TOP')]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'SOUTH', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'SOUTH', "alt": True},
              {"properties": [("type", 'BOTTOM')]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'EAST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'EAST', "alt": True},
              {"properties": [("type", 'RIGHT')]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'WEST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'WEST', "alt": True},
              {"properties": [("type", 'LEFT')]}),
-            # Match the pie menu.
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'NORTH_WEST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'NORTH_WEST', "alt": True},
              {"properties": [("type", 'FRONT')]}),
-            ("view3d.view_axis", {"type": 'EVT_TWEAK_M', "value": 'NORTH_EAST', "alt": True},
+            ("view3d.view_axis", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG', "direction": 'NORTH_EAST', "alt": True},
              {"properties": [("type", 'BACK')]}),
+            # Match the pie menu.
         )),
         ("view3d.view_center_pick", {"type": 'MIDDLEMOUSE', "value": 'CLICK', "alt": True}, None),
         ("view3d.ndof_orbit_zoom", {"type": 'NDOF_MOTION', "value": 'ANY'}, None),
@@ -6600,10 +6600,10 @@ def km_3d_view_tool_shear(params):
         {"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
         {"items": [
             ("transform.shear",
-             {"type": params.tool_tweak, "value": 'NORTH', **params.tool_modifier},
+             {"type": params.tool_mouse, "value": 'CLICK_DRAG', "direction": 'NORTH', **params.tool_modifier},
              {"properties": [("release_confirm", True), ("orient_axis_ortho", 'Y')]}),
             ("transform.shear",
-             {"type": params.tool_tweak, "value": 'SOUTH', **params.tool_modifier},
+             {"type": params.tool_mouse, "value": 'CLICK_DRAG', "direction": 'SOUTH', **params.tool_modifier},
              {"properties": [("release_confirm", True), ("orient_axis_ortho", 'Y')]}),
 
             # Use as fallback to catch diagonals too.
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index 6f22e45d8d5..a8a851bb228 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -25,7 +25,7 @@ extern "C" {
 
 /* Blender file format version. */
 #define BLENDER_FILE_VERSION BLENDER_VERSION
-#define BLENDER_FILE_SUBVERSION 4
+#define BLENDER_FILE_SUBVERSION 5
 
 /* Minimum Blender version that supports reading file written with the current
  * version. Older Blender versions will test this and show a warning if the file
diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index 10160e9aadc..862e8a09166 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -33,6 +33,7 @@
 
 #include "readfile.h" /* Own include. */
 
+#include "WM_types.h"
 #include "wm_event_types.h"
 
 /* Don't use translation strings in versioning!
@@ -363,10 +364,12 @@ static void do_version_select_mouse(UserDef *userdef, wmKeyMapItem *kmi)
       kmi->type = (left) ? RIGHTMOUSE : LEFTMOUSE;
       break;
     case EVT_TWEAK_S:
-      kmi->type = (left) ? EVT_TWEAK_L : EVT_TWEAK_R;
+      kmi->type = (left) ? LEFTMOUSE : RIGHTMOUSE;
+      kmi->val = KM_CLICK_DRAG;
       break;
     case EVT_TWEAK_A:
-      kmi->type = (left) ? EVT_TWEAK_R : EVT_TWEAK_L;
+      kmi->type = (left) ? RIGHTMOUSE : LEFTMOUSE;
+      kmi->val = KM_CLICK_DRAG;
       break;
     default:
       break;
@@ -385,6 +388,39 @@ static bool keymap_item_has_invalid_wm_context_data_path(wmKeyMapItem *kmi,
   return false;
 }
 
+static bool keymap_item_update_tweak_event(wmKeyMapItem *kmi, void *UNUSED(user_data))
+{
+  /* Tweak events for L M R mouse-buttons. */
+  enum {
+    EVT_TWEAK_L = 0x5002,
+    EVT_TWEAK_M = 0x5003,
+    EVT_TWEAK_R = 0x5004,
+  };
+  switch (kmi->type) {
+    case EVT_TWEAK_L:
+      kmi->type = LEFTMOUSE;
+      break;
+    case EVT_TWEAK_M:
+      kmi->type = MIDDLEMOUSE;
+      break;
+    case EVT_TWEAK_R:
+      kmi->type = RIGHTMOUSE;
+      break;
+    default:
+      kmi->direction = KM_ANY;
+      return false;
+  }
+
+  if (kmi->val >= EVT_GESTURE_N && kmi->val <= EVT_GESTURE_NW) {
+    kmi->direction = kmi->val;
+  }
+  else {
+    kmi->direction = KM_ANY;
+  }
+  kmi->val = KM_CLICK_DRAG;
+  return false;
+}
+
 void blo_do_versions_userdef(UserDef *userdef)
 {
   /* #UserDef & #Main happen to have the same struct member. */
@@ -952,6 +988,16 @@ void blo_do_versions_userdef(UserDef *userdef)
     userdef->ndof_flag |= NDOF_CAMERA_PAN_ZOOM;
   }
 
+  if (!USER_VERSION_ATLEAST(302, 5)) {
+    BKE_keyconfig_pref_filter_items(userdef,
+                                    &((struct wmKeyConfigFilterItemParams){
+                                        .check_item = true,
+                                        .check_diff_item_add = true,
+                                    }),
+                                    keymap_item_update_tweak_event,
+                                    NULL);
+  }
+
   /**
    * Versioning code until next subversion bump goes here.
    *
diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c
index 34a20b91172..07efcdcbe38 100644
--- a/source/blender/editors/interface/interface_context_menu.c
+++ b/source/blender/editors/interface/interface_context_menu.c
@@ -201,7 +201,7 @@ static uiBlock *menu_add_shortcut(bContext *C, ARegion *region, void *arg)
   /* XXX this guess_opname can potentially return a different keymap
    * than being found on adding later... */
   wmKeyMap *km = WM_keymap_guess_opname(C, idname);
-  wmKeyMapItem *kmi = WM_keymap_add_item(km, idname, EVT_AKEY, KM_PRESS, 0, 0);
+  wmKeyMapItem *kmi = WM_keymap_add_item(km, idname, EVT_AKEY, KM_PRESS, 0, 0, KM_ANY);
   const int kmi_id = kmi->id;
 
   /* This takes ownership of prop, or prop can be NULL for reset. */
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index c277ca2e36b..e0b64dcd4d6 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -9398,7 +9398,7 @@ static int ui_list_activate_hovered_row(bContext *C,
     }
   }
 
-  const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy;
+  const int *mouse_xy = (event->val == KM_CLICK_DRAG) ? event->prev_click_xy : event->xy;
   uiBut *listrow = ui_list_row_find_mouse_over(region, mouse_xy);
   if (listrow) {
     wmOperatorType *custom_activate_optype = ui_list->dyn_data->custom_activate_optype;
@@ -9425,7 +9425,7 @@ static bool ui_list_is_hovering_draggable_but(bContext *C,
                                               const wmEvent *event)
 {
   /* On a tweak event, uses the coordinates from where tweaking was started. */
-  const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy;
+  const int *mouse_xy = (event->val == KM_CLICK_DRAG) ? event->prev_click_xy : event->xy;
   const uiBut *hovered_but = ui_but_find_mouse_over_ex(region, mouse_xy, false, NULL, NULL);
 
   if (list->dyn_data->custom_drag_optype) {
@@ -9442,7 +9442,7 @@ static int ui_list_handle_click_drag(bContext *C,
                                      ARegion *region,
                                      const wmEvent *event)
 {
-  if (!ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
+  if (event->type != LEFTMOUSE) {
     return WM_HANDLER_CONTINUE;
   }
 
@@ -9452,7 +9452,7 @@ static int ui_list_handle_click_drag(bContext *C,
   bool activate = false;
   bool activate_dragging = false;
 
-  if (event->type == EVT_TWEAK_L) {
+  if (event->val == KM_CLICK_DRAG) {
     if (is_draggable) {
       activate_dragging = true;
       activate = true;
@@ -9462,7 +9462,7 @@ static int ui_list_handle_click_drag(bContext *C,
    * regular events (including mouse presses to start dragging) and this part only kicks in if it
    * hasn't handled the release event. Note that if there's no overlaid button, the row selects
    * on the press event already via regular #UI_BTYPE_LISTROW handling. */
-  else if ((event->type == LEFTMOUSE) && (event->val == KM_CLICK)) {
+  else if (event->val == KM_CLICK) {
     activate = true;
   }
 
@@ -9549,7 +9549,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
     }
   }
 
-  if (ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
+  if (event->type == LEFTMOUSE) {
     retval = ui_list_handle_click_drag(C, ui_list, region, event);
   }
   else if (val == KM_PRESS) {
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index e99f6978f4c..9dfc9be2a30 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -573,18 +573,6 @@ int UI_icon_from_event_type(short event_type, short event_value)
   else if (event_type == EVT_RIGHTALTKEY) {
     event_type = EVT_LEFTALTKEY;
   }
-  else if (event_type == EVT_TWEAK_L) {
-    event_type = LEFTMOUSE;
-    event_value = KM_CLICK_DRAG;
-  }
-  else if (event_type == EVT_TWEAK_M) {
-    event_type = MIDDLEMOUSE;
-    event_value = KM_CLICK_DRAG;
-  }
-  else if (event_type == EVT_TWEAK_R) {
-    event_type = RIGHTMOUSE;
-    event_value = KM_CLICK_DRAG;
-  }
 
   DrawInfo *di = g_di_event_list;
   do {
diff --git a/source/blender/editors/space_view3d/view3d_navigate_dolly.c b/source/blender/editors/space_view3d/view3d_navigate_dolly.c
index 06b616e71da..7b6b119294d 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_dolly.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_dolly.c
@@ -50,9 +50,12 @@ void viewdolly_modal_keymap(wmKeyConfig *keyconf)
 
   /* disabled mode switching for now, can re-implement better, later on */
 #if 0
-  WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
-  WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
-  WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
+  WM_modalkeymap_add_item(
+      keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, KM_ANY, VIEWROT_MODAL_SWITCH_ROTATE);
+  WM_modalkeymap_add_item(
+      keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, KM_ANY, VIEWROT_MODAL_SWITCH_ROTATE);
+  WM_modalkeymap_add_item(
+      keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, KM_ANY, VIEWROT_MODAL_SWITCH_MOVE);
 #endif
 
   /* assign map to operators */
diff --git a/source/blender/editors/space_view3d/view3d_navigate_move.c b/source/blender/editors/space_view3d/view3d_navigate_move.c
index d2fd505a703..071643e9314 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_move.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_move.c
@@ -43,8 +43,8 @@ void viewmove_modal_keymap(wmKeyConfig *keyconf)
   keymap = WM_modalkeymap_ensure(keyconf, "View3D Move Modal", modal_items);
 
   /* items for modal map */
-  WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
-  WM_modalkeymap_add_item(keymap, EVT_ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
+  WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, KM_ANY, VIEW_MODAL_CONFIRM);
+  WM_modalkeymap_add_item(keymap, EVT_ESCKEY, KM_PRESS, KM_ANY, 0, KM_ANY, VIEW_MODAL_CONFIRM);
 
   /* disabled mode switching for now, can re-implement better, later on */
 #if 0
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index 36ffcc8cd68..fd01f708ed2 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -1697,7 +1697,7 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
 
   /* Needed to translate tweak events to mouse buttons. */
   t->launch_event = event ? WM_userdef_event_type_from_keymap_type(event->type) : -1;
-  t->is_launch_event_drag = event ? (ISTWEAK(event->type) || event->val == KM_CLICK_DRAG) : false;
+  t->is_launch_event_drag = event ? (event->val == KM_CLICK_DRAG) : false;
 
   /* XXX Remove this when wm_operator_call_internal doesn't use window->eventstate
    * (which can have type = 0) */
diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h
index 626c2b2a81f..dabef04583b 100644
--- a/source/blender/makesdna/DNA_windowmanager_types.h
+++ b/source/blender/makesdna/DNA_windowmanager_types.h
@@ -295,9 +295,6 @@ typedef struct wmWindow {
   /** Storage for event system. */
   struct wmEvent *eventstate;
 
-  /** Internal for wm_operators.c. */
-  struct wmGesture *tweak;
-
   /* Input Method Editor data - complex character input (especially for Asian character input)
    * Currently WIN32 and APPLE, runtime-only data. */
   struct wmIMEData *ime_data;
@@ -364,7 +361,9 @@ typedef struct wmKeyMapItem {
   /** Event code itself. */
   short type;
   /** KM_ANY, KM_PRESS, KM_NOTHING etc. */
-  short val;
+  int8_t val;
+  /** Use when `val == WM_CLICK_DRAG`,  */
+  int8_t direction;
   /** `oskey` also known as apple, windows-key or super, value denotes order of pressed. */
   short shift, ctrl, alt, oskey;
   /** Raw-key modifier. */
@@ -422,7 +421,7 @@ enum {
 enum {
   KMI_TYPE_KEYBOARD = 0,
   KMI_TYPE_MOUSE = 1,
-  KMI_TYPE_TWEAK = 2,
+  /* 2 is deprecated, was tweak. */
   KMI_TYPE_TEXTINPUT = 3,
   KMI_TYPE_TIMER = 4,
   KMI_TYPE_NDOF = 5,
diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h
index 352ff8f1f42..31db8e39e6f 100644
--- a/source/blender/makesrna/RNA_enum_items.h
+++ b/source/blender/makesrna/RNA_enum_items.h
@@ -90,9 +90,8 @@ DEF_ENUM(rna_enum_motionpath_bake_location_items)
 DEF_ENUM(rna_enum_motionpath_display_type_items)
 DEF_ENUM(rna_enum_motionpath_range_items)
 
-DEF_ENUM(rna_enum_event_value_all_items)
-DEF_ENUM(rna_enum_event_value_keymouse_items)
-DEF_ENUM(rna_enum_event_value_tweak_items)
+DEF_ENUM(rna_enum_event_value_items)
+DEF_ENUM(rna_enum_event_direction_items)
 
 DEF_ENUM(rna_enum_event_type_items)
 DEF_ENUM(rna_enum_event_type_mask_items)
diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c
index ae688846b95..d88f150c1dd 100644
--- a/source/blender/makesrna/intern/rna_wm.c
+++ b/source/blender/makesrna/intern/rna_wm.c
@@ -28,13 +28,6 @@
 
 #ifdef RNA_RUNTIME
 
-static const EnumPropertyItem event_tweak_type_items[] = {
-    {EVT_TWEAK_L, "EVT_TWEAK_L", 0, "Left", ""},
-    {EVT_TWEAK_M, "EVT_TWEAK_M", 0, "Middle", ""},
-    {EVT_TWEAK_R, "EVT_TWEAK_R", 0, "Right", ""},
-    {0, NULL, 0, NULL, NULL},
-};
-
 static const EnumPropertyItem event_mouse_type_items[] = {
     {LEFTMOUSE, "LEFTMOUSE", 0, "Left", ""},
     {MIDDLEMOUSE, "MIDDLEMOUSE", 0, "Middle", ""},
@@ -156,10 +149,6 @@ const EnumPropertyItem rna_enum_event_type_items[] = {
     {WHEELINMOUSE, "WHEELINMOUSE", 0, "Wheel In", "WhIn"},
     {WHEELOUTMOUSE, "WHEELOUTMOUSE", 0, "Wheel Out", "WhOut"},
     {0, "", 0, NULL, NULL},
-    {EVT_TWEAK_L, "EVT_TWEAK_L", 0, "Tweak Left", "TwkL"},
-    {EVT_TWEAK_M, "EVT_TWEAK_M", 0, "Tweak Middle", "TwkM"},
-    {EVT_TWEAK_R, "EVT_TWEAK_R", 0, "Tweak Right", "TwkR"},
-    {0, "", 0, NULL, NULL},
     {EVT_AKEY, "A", 0, "A", ""},
     {EVT_BKEY, "B", 0, "B", ""},
     {EVT_CKEY, "C", 0, "C", ""},
@@ -362,26 +351,7 @@ const EnumPropertyItem rna_enum_event_type_items[] = {
  * This is needed for `km.keymap_items.new` value argument,
  * to accept values from different types.
  */
-const EnumPropertyItem rna_enum_event_value_all_items[] = {
-    {KM_ANY, "ANY", 0, "Any", ""},
-    {KM_PRESS, "PRESS", 0, "Press", ""},
-    {KM_RELEASE, "RELEASE", 0, "Release", ""},
-    {KM_CLICK, "CLICK", 0, "Click", ""},
-    {KM_DBL_CLICK, "DOUBLE_CLICK", 0, "Double Click", ""},
-    {KM_CLICK_DRAG, "CLICK_DRAG", 0, "Click Drag", ""},
-    {EVT_GESTURE_N, "NORTH", 0, "North", ""},
-    {EVT_GESTURE_NE, "NORTH_EAST", 0, "North-East", ""},
-    {EVT_GESTURE_E, "EAST", 0, "East", ""},
-    {EVT_GESTURE_SE, "SOUTH_EAST", 0, "South-East", ""},
-    {EVT_GESTURE_S, "SOUTH", 0, "South", ""},
-    {EVT_GESTURE_SW, "SOUTH_WEST", 0, "South-West", ""},
-    {EVT_GESTURE_W, "WEST", 0, "West", ""},
-    {EVT_GESTURE_NW, "NORTH_WEST", 0, "North-West", ""},
-    {KM_NOTHING, "NOTHING", 0, "Nothing", ""},
-    {0, NULL, 0, NULL, NULL},
-};
-
-const EnumPropertyItem rna_enum_event_value_keymouse_items[] = {
+const EnumPropertyItem rna_enum_event_value_items[] = {
     {KM_ANY, "ANY", 0, "Any", ""},
     {KM_PRESS, "PRESS", 0, "Press", ""},
     {KM_RELEASE, "RELEASE", 0, "Release", ""},
@@ -393,7 +363,7 @@ const EnumPropertyItem rna_enum_event_value_keymouse_items[] = {
     {0, NULL, 0, NULL, NULL},
 };
 
-const EnumPropertyItem rna_enum_event_value_tweak_items[] = {
+const EnumPropertyItem rna_enum_event_direction_items[] = {
     {KM_ANY, "ANY", 0, "Any", ""},
     {EVT_GESTURE_N, "NORTH", 0, "North", ""},
     {EVT_GESTURE_NE, "NORTH_EAST", 0, "North-East", ""},
@@ -420,7 +390,6 @@ const EnumPropertyItem rna_enum_event_type_mask_items[] = {
     {EVT_TYPE_MASK_MOUSE_BUTTON, "MOUSE_BUTTON", 0, "Mouse Button", ""},
     {EVT_TYPE_MASK_MOUSE, "MOUSE", 0, "Mouse", ""},
     {EVT_TYPE_MASK_NDOF, "NDOF", 0, "NDOF", ""},
-    {EVT_TYPE_MASK_TWEAK, "TWEAK", 0, "Tweak", ""},
     {EVT_TYPE_MASK_ACTIONZONE, "ACTIONZONE", 0, "Action Zone", ""},
     {0, NULL, 0, NULL, NULL},
 };
@@ -612,18 +581,6 @@ static PointerRNA rna_OperatorMacro_properties_get(PointerRNA *ptr)
   return result;
 }
 
-static const EnumPropertyItem *rna_Event_value_itemf(bContext *UNUSED(C),
-                                                     PointerRNA *ptr,
-                                                     PropertyRNA *UNUSED(prop),
-                                                     bool *UNUSED(r_free))
-{
-  const wmEvent *event = ptr->data;
-  if (ISTWEAK(event->type)) {
-    return rna_enum_event_value_tweak_items;
-  }
-  return rna_enum_event_value_all_items;
-}
-
 static void rna_Event_ascii_get(PointerRNA *ptr, char *value)
 {
   const wmEvent *event = ptr->data;
@@ -915,10 +872,6 @@ static void rna_wmKeyMapItem_map_type_set(PointerRNA *ptr, int value)
         kmi->type = EVT_AKEY;
         kmi->val = KM_PRESS;
         break;
-      case KMI_TYPE_TWEAK:
-        kmi->type = EVT_TWEAK_L;
-        kmi->val = KM_ANY;
-        break;
       case KMI_TYPE_MOUSE:
         kmi->type = LEFTMOUSE;
         kmi->val = KM_PRESS;
@@ -969,9 +922,6 @@ static const EnumPropertyItem *rna_KeyMapItem_type_itemf(bContext *UNUSED(C),
   if (map_type == KMI_TYPE_MOUSE) {
     return event_mouse_type_items;
   }
-  if (map_type == KMI_TYPE_TWEAK) {
-    return event_tweak_type_items;
-  }
   if (map_type == KMI_TYPE_TIMER) {
     return event_timer_type_items;
   }
@@ -986,24 +936,6 @@ static const EnumPropertyItem *rna_KeyMapItem_type_itemf(bContext *UNUSED(C),
   }
 }
 
-static const EnumPropertyItem *rna_KeyMapItem_value_itemf(bContext *UNUSED(C),
-                                                          PointerRNA *ptr,
-                                                          PropertyRNA *UNUSED(prop),
-                                                          bool *UNUSED(r_free))
-{
-  int map_type = rna_wmKeyMapItem_map_type_get(ptr);
-
-  if (map_type == KMI_TYPE_MOUSE || map_type == KMI_TYPE_KEYBOARD || map_type == KMI_TYPE_NDOF) {
-    return rna_enum_event_value_keymouse_items;
-  }
-  if (map_type == KMI_TYPE_TWEAK) {
-    return rna_enum_event_value_tweak_items;
-  }
-  else {
-    return rna_enum_event_value_all_items;
-  }
-}
-
 static const EnumPropertyItem *rna_KeyMapItem_propvalue_itemf(bContext *C,
                                                               PointerRNA *ptr,
                                                               PropertyRNA *UNUSED(prop),
@@ -2106,8 +2038,7 @@ static void rna_def_event(BlenderRNA *brna)
   /* enums */
   prop = RNA_def_property(srna, "value", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "val");
-  RNA_def_property_enum_items(prop, rna_enum_event_value_all_items);
-  RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_Event_value_itemf");
+  RNA_def_property_enum_items(prop, rna_enum_event_value_items);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "Value", "The type of event, only applies to some");
 
@@ -2118,6 +2049,12 @@ static void rna_def_event(BlenderRNA *brna)
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
   RNA_def_property_ui_text(prop, "Type", "");
 
+  prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "direction");
+  RNA_def_property_enum_items(prop, rna_enum_event_direction_items);
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(prop, "Direction", "The direction (only applies to drag events)");
+
   /* keyboard */
   prop = RNA_def_property(srna, "is_repeat", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
@@ -2538,7 +2475,6 @@ static void rna_def_keyconfig(BlenderRNA *brna)
 
   static const EnumPropertyItem map_type_items[] = {
       {KMI_TYPE_KEYBOARD, "KEYBOARD", 0, "Keyboard", ""},
-      {KMI_TYPE_TWEAK, "TWEAK", 0, "Tweak", ""},
       {KMI_TYPE_MOUSE, "MOUSE", 0, "Mouse", ""},
       {KMI_TYPE_NDOF, "NDOF", 0, "NDOF", ""},
       {KMI_TYPE_TEXTINPUT, "TEXTINPUT", 0, "Text Input", ""},
@@ -2679,11 +2615,16 @@ static void rna_def_keyconfig(BlenderRNA *brna)
 
   prop = RNA_def_property(srna, "value", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "val");
-  RNA_def_property_enum_items(prop, rna_enum_event_value_all_items);
-  RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_KeyMapItem_value_itemf");
+  RNA_def_property_enum_items(prop, rna_enum_event_value_items);
   RNA_def_property_ui_text(prop, "Value", "");
   RNA_def_property_update(prop, 0, "rna_KeyMapItem_update");
 
+  prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "direction");
+  RNA_def_property_enum_items(prop, rna_enum_event_direction_items);
+  RNA_def_property_ui_text(prop, "Direction", "The direction (only applies to drag events)");
+  RNA_def_property_update(prop, 0, "rna_KeyMapItem_update");
+
   prop = RNA_def_property(srna, "id", PROP_INT, PROP_NONE);
   RNA_def_property_int_sdna(prop, NULL, "id");
   RNA_def_property_clear_flag(prop, PROP_EDITABLE);
diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c
index 1eb51b6ec80..0589fa7a96e 100644
--- a/source/blender/makesrna/intern/rna_wm_api.c
+++ b/source/blender/makesrna/intern/rna_wm_api.c
@@ -258,6 +258,7 @@ static wmKeyMapItem *rna_KeyMap_item_new(wmKeyMap *km,
                                          int alt,
                                          int oskey,
                                          int keymodifier,
+                                         int direction,
                                          bool repeat,
                                          bool head)
 {
@@ -275,7 +276,7 @@ static wmKeyMapItem *rna_KeyMap_item_new(wmKeyMap *km,
   WM_operator_bl_idname(idname_bl, idname);
 
   /* create keymap item */
-  kmi = WM_keymap_add_item(km, idname_bl, type, value, modifier, keymodifier);
+  kmi = WM_keymap_add_item(km, idname_bl, type, value, modifier, keymodifier, direction);
 
   if (!repeat) {
     kmi->flag |= KMI_REPEAT_IGNORE;
@@ -324,6 +325,7 @@ static wmKeyMapItem *rna_KeyMap_item_new_modal(wmKeyMap *km,
                                                int alt,
                                                int oskey,
                                                int keymodifier,
+                                               int direction,
                                                bool repeat)
 {
   /* only modal maps */
@@ -338,13 +340,14 @@ static wmKeyMapItem *rna_KeyMap_item_new_modal(wmKeyMap *km,
 
   /* not initialized yet, do delayed lookup */
   if (!km->modal_items) {
-    kmi = WM_modalkeymap_add_item_str(km, type, value, modifier, keymodifier, propvalue_str);
+    kmi = WM_modalkeymap_add_item_str(
+        km, type, value, modifier, keymodifier, direction, propvalue_str);
   }
   else {
     if (RNA_enum_value_from_id(km->modal_items, propvalue_str, &propvalue) == 0) {
       BKE_report(reports, RPT_WARNING, "Property value not in enumeration");
     }
-    kmi = WM_modalkeymap_add_item(km, type, value, modifier, keymodifier, propvalue);
+    kmi = WM_modalkeymap_add_item(km, type, value, modifier, keymodifier, direction, propvalue);
   }
 
   if (!repeat) {
@@ -729,7 +732,7 @@ void RNA_api_window(StructRNA *srna)
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   parm = RNA_def_enum(func, "type", rna_enum_event_type_items, 0, "Type", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
-  parm = RNA_def_enum(func, "value", rna_enum_event_value_all_items, 0, "Value", "");
+  parm = RNA_def_enum(func, "value", rna_enum_event_value_items, 0, "Value", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
   parm = RNA_def_string(func, "unicode", NULL, 0, "", "");
   RNA_def_parameter_clear_flags(parm, PROP_NEVER_NULL, 0);
@@ -1134,7 +1137,7 @@ void RNA_api_keymapitems(StructRNA *srna)
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
   parm = RNA_def_enum(func, "type", rna_enum_event_type_items, 0, "Type", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
-  parm = RNA_def_enum(func, "value", rna_enum_event_value_all_items, 0, "Value", "");
+  parm = RNA_def_enum(func, "value", rna_enum_event_value_items, 0, "Value", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
   RNA_def_boolean(func, "any", 0, "Any", "");
   RNA_def_int(func, "shift", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Shift", "", KM_ANY, KM_MOD_HELD);
@@ -1142,6 +1145,7 @@ void RNA_api_keymapitems(StructRNA *srna)
   RNA_def_int(func, "alt", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Alt", "", KM_ANY, KM_MOD_HELD);
   RNA_def_int(func, "oskey", KM_NOTHING, KM_ANY, KM_MOD_HELD, "OS Key", "", KM_ANY, KM_MOD_HELD);
   RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", "");
+  RNA_def_enum(func, "direction", rna_enum_event_direction_items, KM_ANY, "Direction", "");
   RNA_def_boolean(func, "repeat", false, "Repeat", "When set, accept key-repeat events");
   RNA_def_boolean(func,
                   "head",
@@ -1158,7 +1162,7 @@ void RNA_api_keymapitems(StructRNA *srna)
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
   parm = RNA_def_enum(func, "type", rna_enum_event_type_items, 0, "Type", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
-  parm = RNA_def_enum(func, "value", rna_enum_event_value_all_items, 0, "Value", "");
+  parm = RNA_def_enum(func, "value", rna_enum_event_value_items, 0, "Value", "");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
   RNA_def_boolean(func, "any", 0, "Any", "");
   RNA_def_int(func, "shift", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Shift", "", KM_ANY, KM_MOD_HELD);
@@ -1166,6 +1170,7 @@ void RNA_api_keymapitems(StructRNA *srna)
   RNA_def_int(func, "alt", KM_NOTHING, KM_ANY, KM_MOD_HELD, "Alt", "", KM_ANY, KM_MOD_HELD);
   RNA_def_int(func, "oskey", KM_NOTHING, KM_ANY, KM_MOD_HELD, "OS Key", "", KM_ANY, KM_MOD_HELD);
   RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", "");
+  RNA_def_enum(func, "direction", rna_enum_event_direction_items, KM_ANY, "Direction", "");
   RNA_def_boolean(func, "repeat", false, "Repeat", "When set, accept key-repeat events");
   parm = RNA_def_pointer(func, "item", "KeyMapItem", "Item", "Added key map item");
   RNA_def_function_return(func, parm);
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 07eaa2ab976..21d76ce93bd 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -1432,6 +1432,8 @@ bool WM_event_is_modal_drag_exit(const struct wmEvent *event,
 bool WM_event_is_last_mousemove(const struct wmEvent *event);
 bool WM_event_is_mouse_drag(const struct wmEvent *event);
 bool WM_event_is_mouse_drag_or_press(const wmEvent *event);
+int WM_event_drag_direction(const wmEvent *event);
+
 /**
  * Detect motion between selection (callers should only use this for selection picking),
  * typically mouse press/click events.
diff --git a/source/blender/windowmanager/WM_keymap.h b/source/blender/windowmanager/WM_keymap.h
index 6e07fd7fb32..068dbb32be2 100644
--- a/source/blender/windowmanager/WM_keymap.h
+++ b/source/blender/windowmanager/WM_keymap.h
@@ -42,8 +42,13 @@ void WM_keymap_clear(struct wmKeyMap *keymap);
 /**
  * Always add item.
  */
-wmKeyMapItem *WM_keymap_add_item(
-    struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
+wmKeyMapItem *WM_keymap_add_item(struct wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction);
 wmKeyMapItem *WM_keymap_add_item_copy(struct wmKeyMap *keymap, wmKeyMapItem *kmi_src);
 
 bool WM_keymap_remove_item(struct wmKeyMap *keymap, struct wmKeyMapItem *kmi);
@@ -80,23 +85,43 @@ bool WM_keymap_item_compare(const struct wmKeyMapItem *k1, const struct wmKeyMap
 /**
  * Menu wrapper for #WM_keymap_add_item.
  */
-wmKeyMapItem *WM_keymap_add_menu(
-    struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
+wmKeyMapItem *WM_keymap_add_menu(struct wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction);
 /**
  * Pie-menu wrapper for #WM_keymap_add_item.
  */
-wmKeyMapItem *WM_keymap_add_menu_pie(
-    struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
+wmKeyMapItem *WM_keymap_add_menu_pie(struct wmKeyMap *keymap,
+                                     const char *idname,
+                                     int type,
+                                     int val,
+                                     int modifier,
+                                     int keymodifier,
+                                     int direction);
 /**
  * Panel (popover) wrapper for #WM_keymap_add_item.
  */
-wmKeyMapItem *WM_keymap_add_panel(
-    struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
+wmKeyMapItem *WM_keymap_add_panel(struct wmKeyMap *keymap,
+                                  const char *idname,
+                                  int type,
+                                  int val,
+                                  int modifier,
+                                  int keymodifier,
+                                  int direction);
 /**
  * Tool wrapper for #WM_keymap_add_item.
  */
-wmKeyMapItem *WM_keymap_add_tool(
-    struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier);
+wmKeyMapItem *WM_keymap_add_tool(struct wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction);
 
 wmKeyMap *WM_keymap_guess_from_context(const struct bContext *C);
 
@@ -128,10 +153,20 @@ wmKeyMap *WM_modalkeymap_ensure(struct wmKeyConfig *keyconf,
                                 const char *idname,
                                 const struct EnumPropertyItem *items);
 wmKeyMap *WM_modalkeymap_find(struct wmKeyConfig *keyconf, const char *idname);
-wmKeyMapItem *WM_modalkeymap_add_item(
-    struct wmKeyMap *km, int type, int val, int modifier, int keymodifier, int value);
-wmKeyMapItem *WM_modalkeymap_add_item_str(
-    struct wmKeyMap *km, int type, int val, int modifier, int keymodifier, const char *value);
+wmKeyMapItem *WM_modalkeymap_add_item(struct wmKeyMap *km,
+                                      int type,
+                                      int val,
+                                      int modifier,
+                                      int keymodifier,
+                                      int direction,
+                                      int value);
+wmKeyMapItem *WM_modalkeymap_add_item_str(struct wmKeyMap *km,
+                                          int type,
+                                          int val,
+                                          int modifier,
+                                          int keymodifier,
+                                          int direction,
+                                          const char *value);
 const wmKeyMapItem *WM_modalkeymap_find_propvalue(const wmKeyMap *km, int propvalue);
 void WM_modalkeymap_assign(struct wmKeyMap *km, const char *opname);
 
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 26462402e3d..b2e214782f0 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -494,7 +494,6 @@ typedef struct wmNotifier {
 /* ************** Gesture Manager data ************** */
 
 /* wmGesture->type */
-#define WM_GESTURE_TWEAK 0
 #define WM_GESTURE_LINES 1
 #define WM_GESTURE_RECT 2
 #define WM_GESTURE_CROSS_RECT 3
@@ -504,7 +503,6 @@ typedef struct wmNotifier {
 
 /**
  * wmGesture is registered to #wmWindow.gesture, handled by operator callbacks.
- * Tweak gesture is builtin feature.
  */
 typedef struct wmGesture {
   struct wmGesture *next, *prev;
@@ -660,6 +658,9 @@ typedef struct wmEvent {
    */
   uint8_t modifier;
 
+  /** The direction (for #KM_CLICK_DRAG events only). */
+  int8_t direction;
+
   /** Raw-key modifier (allow using any key as a modifier). */
   short keymodifier;
 
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
index c46a2b6afe5..2971cdf40c3 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
@@ -640,24 +640,29 @@ wmKeyMap *wm_gizmogroup_tweak_modal_keymap(wmKeyConfig *keyconf)
   keymap = WM_modalkeymap_ensure(keyconf, name, modal_items);
 
   /* items for modal map */
-  WM_modalkeymap_add_item(keymap, EVT_ESCKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CANCEL);
-  WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CANCEL);
+  WM_modalkeymap_add_item(keymap, EVT_ESCKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_CANCEL);
+  WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_CANCEL);
 
-  WM_modalkeymap_add_item(keymap, EVT_RETKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CONFIRM);
-  WM_modalkeymap_add_item(keymap, EVT_PADENTER, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CONFIRM);
+  WM_modalkeymap_add_item(keymap, EVT_RETKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_CONFIRM);
+  WM_modalkeymap_add_item(keymap, EVT_PADENTER, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_CONFIRM);
 
   WM_modalkeymap_add_item(
-      keymap, EVT_RIGHTSHIFTKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_PRECISION_ON);
+      keymap, EVT_RIGHTSHIFTKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_PRECISION_ON);
   WM_modalkeymap_add_item(
-      keymap, EVT_RIGHTSHIFTKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_PRECISION_OFF);
-  WM_modalkeymap_add_item(keymap, EVT_LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_PRECISION_ON);
+      keymap, EVT_RIGHTSHIFTKEY, KM_RELEASE, KM_ANY, 0, KM_ANY, TWEAK_MODAL_PRECISION_OFF);
   WM_modalkeymap_add_item(
-      keymap, EVT_LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_PRECISION_OFF);
+      keymap, EVT_LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_PRECISION_ON);
+  WM_modalkeymap_add_item(
+      keymap, EVT_LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, KM_ANY, TWEAK_MODAL_PRECISION_OFF);
 
-  WM_modalkeymap_add_item(keymap, EVT_RIGHTCTRLKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_SNAP_ON);
-  WM_modalkeymap_add_item(keymap, EVT_RIGHTCTRLKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_SNAP_OFF);
-  WM_modalkeymap_add_item(keymap, EVT_LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_SNAP_ON);
-  WM_modalkeymap_add_item(keymap, EVT_LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_SNAP_OFF);
+  WM_modalkeymap_add_item(
+      keymap, EVT_RIGHTCTRLKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_SNAP_ON);
+  WM_modalkeymap_add_item(
+      keymap, EVT_RIGHTCTRLKEY, KM_RELEASE, KM_ANY, 0, KM_ANY, TWEAK_MODAL_SNAP_OFF);
+  WM_modalkeymap_add_item(
+      keymap, EVT_LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, KM_ANY, TWEAK_MODAL_SNAP_ON);
+  WM_modalkeymap_add_item(
+      keymap, EVT_LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, KM_ANY, TWEAK_MODAL_SNAP_OFF);
 
   WM_modalkeymap_assign(keymap, "GIZMOGROUP_OT_gizmo_tweak");
 
@@ -709,24 +714,26 @@ static wmKeyMap *WM_gizmogroup_keymap_template_select_ex(
   const int select_tweak = (U.flag & USER_LMOUSESELECT) ? EVT_TWEAK_L : EVT_TWEAK_R;
   const int action_mouse = (U.flag & USER_LMOUSESELECT) ? RIGHTMOUSE : LEFTMOUSE;
 #else
-  const int select_mouse = RIGHTMOUSE;
-  const int select_tweak = EVT_TWEAK_R;
-  const int action_mouse = LEFTMOUSE;
+  const int select_mouse = RIGHTMOUSE, select_mouse_val = KM_PRESS;
+  const int select_tweak = RIGHTMOUSE, select_tweak_val = KM_CLICK_DRAG;
+  const int action_mouse = LEFTMOUSE, action_mouse_val = KM_PRESS;
 #endif
 
   if (do_init) {
-    WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_tweak", action_mouse, KM_PRESS, KM_ANY, 0);
-    WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_tweak", select_tweak, KM_ANY, 0, 0);
+    WM_keymap_add_item(
+        km, "GIZMOGROUP_OT_gizmo_tweak", action_mouse, action_mouse_val, KM_ANY, 0, KM_ANY);
+    WM_keymap_add_item(
+        km, "GIZMOGROUP_OT_gizmo_tweak", select_tweak, select_tweak_val, 0, 0, KM_ANY);
   }
 
   if (do_init) {
     wmKeyMapItem *kmi = WM_keymap_add_item(
-        km, "GIZMOGROUP_OT_gizmo_select", select_mouse, KM_PRESS, 0, 0);
+        km, "GIZMOGROUP_OT_gizmo_select", select_mouse, select_mouse_val, 0, 0, KM_ANY);
     RNA_boolean_set(kmi->ptr, "extend", false);
     RNA_boolean_set(kmi->ptr, "deselect", false);
     RNA_boolean_set(kmi->ptr, "toggle", false);
     kmi = WM_keymap_add_item(
-        km, "GIZMOGROUP_OT_gizmo_select", select_mouse, KM_PRESS, KM_SHIFT, 0);
+        km, "GIZMOGROUP_OT_gizmo_select", select_mouse, select_mouse_val, KM_SHIFT, 0, KM_ANY);
     RNA_boolean_set(kmi->ptr, "extend", false);
     RNA_boolean_set(kmi->ptr, "deselect", false);
     RNA_boolean_set(kmi->ptr, "toggle", true);
@@ -1129,7 +1136,8 @@ void WM_gizmo_group_refresh(const bContext *C, wmGizmoGroup *gzgroup)
       ARegion *region = CTX_wm_region(C);
       BLI_assert(region->gizmo_map == gzmap);
       /* Check if the tweak event originated from this region. */
-      if ((win->tweak != NULL) && BLI_rcti_compare(®ion->winrct, &win->tweak->winrct)) {
+      if ((win->eventstate != NULL) && (win->event_queue_check_drag) &&
+          BLI_rcti_isect_pt_v(®ion->winrct, win->eventstate->prev_click_xy)) {
         /* We need to run refresh again. */
         gzgroup->init_flag &= ~WM_GIZMOGROUP_INIT_REFRESH;
         WM_gizmomap_tag_refresh_drawstep(gzmap, WM_gizmomap_drawstep_from_gizmo_group(gzgroup));
diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c
index 13bf902f02c..c333d8149ed 100644
--- a/source/blender/windowmanager/intern/wm.c
+++ b/source/blender/windowmanager/intern/wm.c
@@ -156,7 +156,6 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id)
     win->gpuctx = NULL;
     win->eventstate = NULL;
     win->cursor_keymap_status = NULL;
-    win->tweak = NULL;
 #if defined(WIN32) || defined(__APPLE__)
     win->ime_data = NULL;
 #endif
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index ddca10a8382..e56d3fb3886 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -47,12 +47,7 @@ static void event_ids_from_type_and_value(const short type,
   RNA_enum_identifier(rna_enum_event_type_items, type, r_type_id);
 
   /* Value. */
-  if (ISTWEAK(type)) {
-    RNA_enum_identifier(rna_enum_event_value_tweak_items, val, r_val_id);
-  }
-  else {
-    RNA_enum_identifier(rna_enum_event_value_all_items, val, r_val_id);
-  }
+  RNA_enum_identifier(rna_enum_event_value_items, val, r_val_id);
 }
 
 void WM_event_print(const wmEvent *event)
@@ -160,13 +155,6 @@ bool WM_event_type_mask_test(const int event_type, const enum eEventType_Mask ma
     }
   }
 
-  /* Tweak. */
-  if (mask & EVT_TYPE_MASK_TWEAK) {
-    if (ISTWEAK(event_type)) {
-      return true;
-    }
-  }
-
   /* Action Zone. */
   if (mask & EVT_TYPE_MASK_ACTIONZONE) {
     if (IS_EVENT_ACTIONZONE(event_type)) {
@@ -192,12 +180,6 @@ bool WM_event_is_modal_drag_exit(const wmEvent *event,
   if (U.flag & USER_RELEASECONFIRM) {
     /* option on, so can exit with km-release */
     if (event->val == KM_RELEASE) {
-      switch (init_event_type) {
-        case EVT_TWEAK_L:
-        case EVT_TWEAK_M:
-        case EVT_TWEAK_R:
-          return 1;
-      }
       if ((init_event_val == KM_CLICK_DRAG) && (event->type == init_event_type)) {
         return 1;
       }
@@ -205,8 +187,7 @@ bool WM_event_is_modal_drag_exit(const wmEvent *event,
     else {
       /* If the initial event wasn't a drag event then
        * ignore #USER_RELEASECONFIRM setting: see T26756. */
-      if ((ELEM(init_event_type, EVT_TWEAK_L, EVT_TWEAK_M, EVT_TWEAK_R) ||
-           init_event_val == KM_CLICK_DRAG) == 0) {
+      if (init_event_val != KM_CLICK_DRAG) {
         return 1;
       }
     }
@@ -234,7 +215,7 @@ bool WM_event_is_last_mousemove(const wmEvent *event)
 
 bool WM_event_is_mouse_drag(const wmEvent *event)
 {
-  return ISTWEAK(event->type) || (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG));
+  return (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG));
 }
 
 bool WM_event_is_mouse_drag_or_press(const wmEvent *event)
@@ -243,6 +224,68 @@ bool WM_event_is_mouse_drag_or_press(const wmEvent *event)
          (ISMOUSE_BUTTON(event->type) && (event->val == KM_PRESS));
 }
 
+int WM_event_drag_direction(const wmEvent *event)
+{
+  const int delta[2] = {
+      event->xy[0] - event->prev_click_xy[0],
+      event->xy[1] - event->prev_click_xy[1],
+  };
+
+  int theta = round_fl_to_int(4.0f * atan2f((float)delta[1], (float)delta[0]) / (float)M_PI);
+  int val = EVT_GESTURE_W;
+
+  if (theta == 0) {
+    val = EVT_GESTURE_E;
+  }
+  else if (theta == 1) {
+    val = EVT_GESTURE_NE;
+  }
+  else if (theta == 2) {
+    val = EVT_GESTURE_N;
+  }
+  else if (theta == 3) {
+    val = EVT_GESTURE_NW;
+  }
+  else if (theta == -1) {
+    val = EVT_GESTURE_SE;
+  }
+  else if (theta == -2) {
+    val = EVT_GESTURE_S;
+  }
+  else if (theta == -3) {
+    val = EVT_GESTURE_SW;
+  }
+
+#if 0
+  /* debug */
+  if (val == 1) {
+    printf("tweak north\n");
+  }
+  if (val == 2) {
+    printf("tweak north-east\n");
+  }
+  if (val == 3) {
+    printf("tweak east\n");
+  }
+  if (val == 4) {
+    printf("tweak south-east\n");
+  }
+  if (val == 5) {
+    printf("tweak south\n");
+  }
+  if (val == 6) {
+    printf("tweak south-west\n");
+  }
+  if (val == 7) {
+    printf("tweak west\n");
+  }
+  if (val == 8) {
+    printf("tweak north-west\n");
+  }
+#endif
+  return val;
+}
+
 bool WM_cursor_test_motion_and_update(const int mval[2])
 {
   static int mval_prev[2] = {-1, -1};
@@ -316,12 +359,6 @@ int WM_userdef_event_map(int kmitype)
 int WM_userdef_event_type_from_keymap_type(int kmitype)
 {
   switch (kmitype) {
-    case EVT_TWEAK_L:
-      return LEFTMOUSE;
-    case EVT_TWEAK_M:
-      return MIDDLEMOUSE;
-    case EVT_TWEAK_R:
-      return RIGHTMOUSE;
     case WHEELOUTMOUSE:
       return (U.uiflag & USER_WHEELZOOMDIR) ? WHEELUPMOUSE : WHEELDOWNMOUSE;
     case WHEELINMOUSE:
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index b3b99b0b7fc..017af86e401 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -2029,6 +2029,14 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi)
     }
   }
 
+  if (kmi->val == KM_CLICK_DRAG) {
+    if (kmi->direction != KM_ANY) {
+      if (kmi->direction != winevent->direction) {
+        return false;
+      }
+    }
+  }
+
   const bool shift = (winevent->modifier & KM_SHIFT) != 0;
   const bool ctrl = (winevent->modifier & KM_CTRL) != 0;
   const bool alt = (winevent->modifier & KM_ALT) != 0;
@@ -2775,7 +2783,7 @@ static int wm_handlers_do_gizmo_handler(bContext *C,
 {
   /* Drag events use the previous click location to highlight the gizmos,
    * Get the highlight again in case the user dragged off the gizmo. */
-  const bool is_event_drag = ISTWEAK(event->type) || (event->val == KM_CLICK_DRAG);
+  const bool is_event_drag = (event->val == KM_CLICK_DRAG);
   const bool is_event_modifier = ISKEYMODIFIER(event->type);
   /* Only keep the highlight if the gizmo becomes modal as result of event handling.
    * Without this check, even un-handled drag events will set the highlight if the drag
@@ -2886,15 +2894,10 @@ static int wm_handlers_do_gizmo_handler(bContext *C,
           wmEvent event_test_click_drag = *event;
           event_test_click_drag.val = KM_CLICK_DRAG;
 
-          wmEvent event_test_tweak = *event;
-          event_test_tweak.type = EVT_TWEAK_L + (event->type - LEFTMOUSE);
-          event_test_tweak.val = KM_ANY;
-
           LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
             if ((kmi->flag & KMI_INACTIVE) == 0) {
               if (wm_eventmatch(&event_test_click, kmi) ||
-                  wm_eventmatch(&event_test_click_drag, kmi) ||
-                  wm_eventmatch(&event_test_tweak, kmi)) {
+                  wm_eventmatch(&event_test_click_drag, kmi)) {
                 wmOperatorType *ot = WM_operatortype_find(kmi->idname, 0);
                 if (WM_operator_poll_context(C, ot, WM_OP_INVOKE_DEFAULT)) {
                   is_event_handle_all = true;
@@ -3165,6 +3168,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
     if (win->event_queue_check_drag) {
       if (WM_event_drag_test(event, event->prev_click_xy)) {
         win->event_queue_check_drag_handled = true;
+        const int direction = WM_event_drag_direction(event);
 
         const int prev_xy[2] = {UNPACK2(event->xy)};
         const short prev_val = event->val;
@@ -3177,6 +3181,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
         event->type = event->prev_type;
         event->modifier = event->prev_click_modifier;
         event->keymodifier = event->prev_click_keymodifier;
+        event->direction = direction;
 
         CLOG_INFO(WM_LOG_HANDLERS, 1, "handling PRESS_DRAG");
 
@@ -3184,6 +3189,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
 
         action |= wm_handlers_do_intern(C, win, event, handlers);
 
+        event->direction = 0;
         event->keymodifier = prev_keymodifier;
         event->modifier = prev_modifier;
         event->val = prev_val;
@@ -3712,9 +3718,7 @@ void wm_event_do_handlers(bContext *C)
       /* Check dragging, creates new event or frees, adds draw tag. */
       wm_event_drag_and_drop_test(wm, win, event);
 
-      /* Builtin tweak, if action is break it removes tweak. */
-      wm_tweakevent_test(C, event, action);
-
+      /* Builtin drag: #KM_CLICK_DRAG. */
       if (action & WM_HANDLER_BREAK) {
         win->event_queue_check_drag = false;
       }
@@ -5515,15 +5519,15 @@ void WM_window_cursor_keymap_status_refresh(bContext *C, wmWindow *win)
   } event_data[] = {
       {0, 0, LEFTMOUSE, KM_PRESS},
       {0, 0, LEFTMOUSE, KM_CLICK},
-      {0, 1, EVT_TWEAK_L, KM_ANY},
+      {0, 0, LEFTMOUSE, KM_CLICK_DRAG},
 
       {1, 0, MIDDLEMOUSE, KM_PRESS},
       {1, 0, MIDDLEMOUSE, KM_CLICK},
-      {1, 1, EVT_TWEAK_M, KM_ANY},
+      {1, 0, MIDDLEMOUSE, KM_CLICK_DRAG},
 
       {2, 0, RIGHTMOUSE, KM_PRESS},
       {2, 0, RIGHTMOUSE, KM_CLICK},
-      {2, 1, EVT_TWEAK_R, KM_ANY},
+      {2, 0, RIGHTMOUSE, KM_CLICK_DRAG},
   };
 
   for (int button_index = 0; button_index < 3; button_index++) {
diff --git a/source/blender/windowmanager/intern/wm_gesture.c b/source/blender/windowmanager/intern/wm_gesture.c
index 86ada4aaf2a..a6fbad8b171 100644
--- a/source/blender/windowmanager/intern/wm_gesture.c
+++ b/source/blender/windowmanager/intern/wm_gesture.c
@@ -52,7 +52,6 @@ wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent
   if (ELEM(type,
            WM_GESTURE_RECT,
            WM_GESTURE_CROSS_RECT,
-           WM_GESTURE_TWEAK,
            WM_GESTURE_CIRCLE,
            WM_GESTURE_STRAIGHTLINE)) {
     rcti *rect = MEM_callocN(sizeof(rcti), "gesture rect new");
@@ -83,9 +82,6 @@ wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent
 
 void WM_gesture_end(wmWindow *win, wmGesture *gesture)
 {
-  if (win->tweak == gesture) {
-    win->tweak = NULL;
-  }
   BLI_remlink(&win->gesture, gesture);
   MEM_freeN(gesture->customdata);
   WM_generic_user_data_free(&gesture->user_data);
@@ -114,74 +110,6 @@ bool WM_gesture_is_modal_first(const wmGesture *gesture)
   return (gesture->is_active_prev == false);
 }
 
-int wm_gesture_evaluate(wmGesture *gesture, const wmEvent *event)
-{
-  if (gesture->type == WM_GESTURE_TWEAK) {
-    rcti *rect = gesture->customdata;
-    const int delta[2] = {
-        BLI_rcti_size_x(rect),
-        BLI_rcti_size_y(rect),
-    };
-
-    if (WM_event_drag_test_with_delta(event, delta)) {
-      int theta = round_fl_to_int(4.0f * atan2f((float)delta[1], (float)delta[0]) / (float)M_PI);
-      int val = EVT_GESTURE_W;
-
-      if (theta == 0) {
-        val = EVT_GESTURE_E;
-      }
-      else if (theta == 1) {
-        val = EVT_GESTURE_NE;
-      }
-      else if (theta == 2) {
-        val = EVT_GESTURE_N;
-      }
-      else if (theta == 3) {
-        val = EVT_GESTURE_NW;
-      }
-      else if (theta == -1) {
-        val = EVT_GESTURE_SE;
-      }
-      else if (theta == -2) {
-        val = EVT_GESTURE_S;
-      }
-      else if (theta == -3) {
-        val = EVT_GESTURE_SW;
-      }
-
-#if 0
-      /* debug */
-      if (val == 1) {
-        printf("tweak north\n");
-      }
-      if (val == 2) {
-        printf("tweak north-east\n");
-      }
-      if (val == 3) {
-        printf("tweak east\n");
-      }
-      if (val == 4) {
-        printf("tweak south-east\n");
-      }
-      if (val == 5) {
-        printf("tweak south\n");
-      }
-      if (val == 6) {
-        printf("tweak south-west\n");
-      }
-      if (val == 7) {
-        printf("tweak west\n");
-      }
-      if (val == 8) {
-        printf("tweak north-west\n");
-      }
-#endif
-      return val;
-    }
-  }
-  return 0;
-}
-
 /* ******************* gesture draw ******************* */
 
 static void wm_gesture_draw_line_active_side(rcti *rect, const bool flip)
@@ -511,11 +439,6 @@ void wm_gesture_draw(wmWindow *win)
     if (gt->type == WM_GESTURE_RECT) {
       wm_gesture_draw_rect(gt);
     }
-#if 0
-    else if (gt->type == WM_GESTURE_TWEAK) {
-      wm_gesture_draw_line(gt);
-    }
-#endif
     else if (gt->type == WM_GESTURE_CIRCLE) {
       wm_gesture_draw_circle(gt);
     }
diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c
index cd41cffe1f0..1fdc8bbe2c8 100644
--- a/source/blender/windowmanager/intern/wm_gesture_ops.c
+++ b/source/blender/windowmanager/intern/wm_gesture_ops.c
@@ -470,131 +470,6 @@ void WM_OT_circle_gesture(wmOperatorType *ot)
 
 /** \} */
 
-/* -------------------------------------------------------------------- */
-/** \name Tweak Gesture
- * \{ */
-
-static void gesture_tweak_modal(bContext *C, const wmEvent *event)
-{
-  wmWindow *window = CTX_wm_window(C);
-  wmGesture *gesture = window->tweak;
-  rcti *rect = gesture->customdata;
-  bool gesture_end = false;
-
-  switch (event->type) {
-    case MOUSEMOVE:
-    case INBETWEEN_MOUSEMOVE: {
-
-      rect->xmax = event->xy[0] - gesture->winrct.xmin;
-      rect->ymax = event->xy[1] - gesture->winrct.ymin;
-
-      const int val = wm_gesture_evaluate(gesture, event);
-      if (val != 0) {
-        wmEvent tevent;
-
-        wm_event_init_from_window(window, &tevent);
-        /* We want to get coord from start of drag,
-         * not from point where it becomes a tweak event, see T40549. */
-        tevent.xy[0] = rect->xmin + gesture->winrct.xmin;
-        tevent.xy[1] = rect->ymin + gesture->winrct.ymin;
-        if (gesture->event_type == LEFTMOUSE) {
-          tevent.type = EVT_TWEAK_L;
-        }
-        else if (gesture->event_type == RIGHTMOUSE) {
-          tevent.type = EVT_TWEAK_R;
-        }
-        else {
-          tevent.type = EVT_TWEAK_M;
-        }
-        tevent.val = val;
-        tevent.modifier = gesture->event_modifier;
-        tevent.keymodifier = gesture->event_keymodifier;
-        tevent.flag = 0;
-        /* mouse coords! */
-
-        /* important we add immediately after this event, so future mouse releases
-         * (which may be in the queue already), are handled in order, see T44740 */
-        wm_event_add_ex(window, &tevent, event);
-
-        gesture_end = true;
-      }
-
-      break;
-    }
-
-    case LEFTMOUSE:
-    case RIGHTMOUSE:
-    case MIDDLEMOUSE:
-      if (gesture->event_type == event->type) {
-        gesture_end = true;
-
-        /* when tweak fails we should give the other keymap entries a chance */
-
-        /* XXX, assigning to readonly, BAD JUJU! */
-        ((wmEvent *)event)->val = KM_RELEASE;
-      }
-      break;
-    default:
-      if (ISTIMER(event->type)) {
-        /* Ignore timers. */
-      }
-      else if (event->type == EVENT_NONE) {
-        /* Ignore none events. */
-      }
-      else if ((event->val == KM_RELEASE) &&
-               (ISKEYMODIFIER(event->type) || (event->type == event->prev_click_keymodifier))) {
-        /* Support releasing modifier keys without canceling the drag event, see T89989.
-         * NOTE: this logic is replicated for drag events. */
-      }
-      else {
-        gesture_end = true;
-      }
-      break;
-  }
-
-  if (gesture_end) {
-    /* Frees gesture itself, and unregisters from window. */
-    WM_gesture_end(window, gesture);
-
-    /* This isn't very nice but needed to redraw gizmos which are hidden while tweaking,
-     * See #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK for details. */
-    ARegion *region = CTX_wm_region(C);
-    if ((region != NULL) && (region->gizmo_map != NULL)) {
-      if (WM_gizmomap_tag_delay_refresh_for_tweak_check(region->gizmo_map)) {
-        ED_region_tag_redraw(region);
-      }
-    }
-  }
-}
-
-void wm_tweakevent_test(bContext *C, const wmEvent *event, int action)
-{
-  wmWindow *win = CTX_wm_window(C);
-
-  if (win->tweak == NULL) {
-    const ARegion *region = CTX_wm_region(C);
-
-    if (region) {
-      if (event->val == KM_PRESS) {
-        if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
-          win->tweak = WM_gesture_new(win, region, event, WM_GESTURE_TWEAK);
-        }
-      }
-    }
-  }
-  else {
-    /* no tweaks if event was handled */
-    if (action & WM_HANDLER_BREAK) {
-      WM_gesture_end(win, win->tweak);
-    }
-    else {
-      gesture_tweak_modal(C, event);
-    }
-  }
-}
-
-/** \} */
-
 /* -------------------------------------------------------------------- */
 /** \name Lasso Gesture
  * \{ */
diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c
index f7bc138f163..ffac585cde7 100644
--- a/source/blender/windowmanager/intern/wm_keymap.c
+++ b/source/blender/windowmanager/intern/wm_keymap.c
@@ -169,6 +169,7 @@ static bool wm_keymap_item_equals(wmKeyMapItem *a, wmKeyMapItem *b)
   return (wm_keymap_item_equals_result(a, b) && a->type == b->type && a->val == b->val &&
           a->shift == b->shift && a->ctrl == b->ctrl && a->alt == b->alt && a->oskey == b->oskey &&
           a->keymodifier == b->keymodifier && a->maptype == b->maptype &&
+          ((a->val != KM_CLICK_DRAG) || (a->direction == b->direction)) &&
           ((ISKEYBOARD(a->type) == 0) ||
            (a->flag & KMI_REPEAT_IGNORE) == (b->flag & KMI_REPEAT_IGNORE)));
 }
@@ -195,9 +196,6 @@ int WM_keymap_item_map_type_get(const wmKeyMapItem *kmi)
   if (ISKEYBOARD(kmi->type)) {
     return KMI_TYPE_KEYBOARD;
   }
-  if (ISTWEAK(kmi->type)) {
-    return KMI_TYPE_TWEAK;
-  }
   if (ISMOUSE(kmi->type)) {
     return KMI_TYPE_MOUSE;
   }
@@ -459,11 +457,12 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap)
 }
 
 static void keymap_event_set(
-    wmKeyMapItem *kmi, short type, short val, int modifier, short keymodifier)
+    wmKeyMapItem *kmi, short type, short val, int modifier, short keymodifier, int direction)
 {
   kmi->type = type;
   kmi->val = val;
   kmi->keymodifier = keymodifier;
+  kmi->direction = direction;
 
   if (modifier == KM_ANY) {
     kmi->shift = kmi->ctrl = kmi->alt = kmi->oskey = KM_ANY;
@@ -497,15 +496,20 @@ static void keymap_item_set_id(wmKeyMap *keymap, wmKeyMapItem *kmi)
   }
 }
 
-wmKeyMapItem *WM_keymap_add_item(
-    wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier)
+wmKeyMapItem *WM_keymap_add_item(wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction)
 {
   wmKeyMapItem *kmi = MEM_callocN(sizeof(wmKeyMapItem), "keymap entry");
 
   BLI_addtail(&keymap->items, kmi);
   BLI_strncpy(kmi->idname, idname, OP_MAX_TYPENAME);
 
-  keymap_event_set(kmi, type, val, modifier, keymodifier);
+  keymap_event_set(kmi, type, val, modifier, keymodifier, direction);
   wm_keymap_item_properties_set(kmi);
 
   keymap_item_set_id(keymap, kmi);
@@ -919,14 +923,14 @@ wmKeyMap *WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
 }
 
 wmKeyMapItem *WM_modalkeymap_add_item(
-    wmKeyMap *km, int type, int val, int modifier, int keymodifier, int value)
+    wmKeyMap *km, int type, int val, int modifier, int keymodifier, int direction, int value)
 {
   wmKeyMapItem *kmi = MEM_callocN(sizeof(wmKeyMapItem), "keymap entry");
 
   BLI_addtail(&km->items, kmi);
   kmi->propvalue = value;
 
-  keymap_event_set(kmi, type, val, modifier, keymodifier);
+  keymap_event_set(kmi, type, val, modifier, keymodifier, direction);
 
   keymap_item_set_id(km, kmi);
 
@@ -935,15 +939,20 @@ wmKeyMapItem *WM_modalkeymap_add_item(
   return kmi;
 }
 
-wmKeyMapItem *WM_modalkeymap_add_item_str(
-    wmKeyMap *km, int type, int val, int modifier, int keymodifier, const char *value)
+wmKeyMapItem *WM_modalkeymap_add_item_str(wmKeyMap *km,
+                                          int type,
+                                          int val,
+                                          int modifier,
+                                          int keymodifier,
+                                          int direction,
+                                          const char *value)
 {
   wmKeyMapItem *kmi = MEM_callocN(sizeof(wmKeyMapItem), "keymap entry");
 
   BLI_addtail(&km->items, kmi);
   BLI_strncpy(kmi->propvalue_str, value, sizeof(kmi->propvalue_str));
 
-  keymap_event_set(kmi, type, val, modifier, keymodifier);
+  keymap_event_set(kmi, type, val, modifier, keymodifier, direction);
 
   keymap_item_set_id(km, kmi);
 
@@ -1730,6 +1739,9 @@ bool WM_keymap_item_compare(const wmKeyMapItem *k1, const wmKeyMapItem *k2)
     if (k1->val != k2->val) {
       return 0;
     }
+    if (k1->val == KM_CLICK_DRAG && (k1->direction != k2->direction)) {
+      return 0;
+    }
   }
 
   if (k1->shift != KM_ANY && k2->shift != KM_ANY && k1->shift != k2->shift) {
diff --git a/source/blender/windowmanager/intern/wm_keymap_utils.c b/source/blender/windowmanager/intern/wm_keymap_utils.c
index 4bd5fea91e6..24c221221d1 100644
--- a/source/blender/windowmanager/intern/wm_keymap_utils.c
+++ b/source/blender/windowmanager/intern/wm_keymap_utils.c
@@ -29,40 +29,60 @@
 /** \name Wrappers for #WM_keymap_add_item
  * \{ */
 
-wmKeyMapItem *WM_keymap_add_menu(
-    wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier)
+wmKeyMapItem *WM_keymap_add_menu(wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction)
 {
   wmKeyMapItem *kmi = WM_keymap_add_item(
-      keymap, "WM_OT_call_menu", type, val, modifier, keymodifier);
+      keymap, "WM_OT_call_menu", type, val, modifier, keymodifier, direction);
   RNA_string_set(kmi->ptr, "name", idname);
   return kmi;
 }
 
-wmKeyMapItem *WM_keymap_add_menu_pie(
-    wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier)
+wmKeyMapItem *WM_keymap_add_menu_pie(wmKeyMap *keymap,
+                                     const char *idname,
+                                     int type,
+                                     int val,
+                                     int modifier,
+                                     int keymodifier,
+                                     int direction)
 {
   wmKeyMapItem *kmi = WM_keymap_add_item(
-      keymap, "WM_OT_call_menu_pie", type, val, modifier, keymodifier);
+      keymap, "WM_OT_call_menu_pie", type, val, modifier, keymodifier, direction);
   RNA_string_set(kmi->ptr, "name", idname);
   return kmi;
 }
 
-wmKeyMapItem *WM_keymap_add_panel(
-    wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier)
+wmKeyMapItem *WM_keymap_add_panel(wmKeyMap *keymap,
+                                  const char *idname,
+                                  int type,
+                                  int val,
+                                  int modifier,
+                                  int keymodifier,
+                                  int direction)
 {
   wmKeyMapItem *kmi = WM_keymap_add_item(
-      keymap, "WM_OT_call_panel", type, val, modifier, keymodifier);
+      keymap, "WM_OT_call_panel", type, val, modifier, keymodifier, direction);
   RNA_string_set(kmi->ptr, "name", idname);
   /* TODO: we might want to disable this. */
   RNA_boolean_set(kmi->ptr, "keep_open", false);
   return kmi;
 }
 
-wmKeyMapItem *WM_keymap_add_tool(
-    wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier)
+wmKeyMapItem *WM_keymap_add_tool(wmKeyMap *keymap,
+                                 const char *idname,
+                                 int type,
+                                 int val,
+                                 int modifier,
+                                 int keymodifier,
+                                 int direction)
 {
   wmKeyMapItem *kmi = WM_keymap_add_item(
-      keymap, "WM_OT_tool_set_by_id", type, val, modifier, keymodifier);
+      keymap, "WM_OT_tool_set_by_id", type, val, modifier, keymodifier, direction);
   RNA_string_set(kmi->ptr, "name", idname);
   return kmi;
 }
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index 6b8f7309d80..6e0bf911555 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -911,9 +911,9 @@ int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
       ret_value = op->type->exec(C, op);
       OPERATOR_RETVAL_CHECK(ret_value);
-
       op->customdata = POINTER_FROM_INT((int)event->type);
       if (ret_value & OPERATOR_RUNNING_MODAL) {
+        printf("Starting modal: %s\n", op->idname);
         WM_event_add_modal_handler(C, op);
       }
       return ret_value | OPERATOR_PASS_THROUGH;
diff --git a/source/blender/windowmanager/wm.h b/source/blender/windowmanager/wm.h
index 68b16d46746..172a879e118 100644
--- a/source/blender/windowmanager/wm.h
+++ b/source/blender/windowmanager/wm.h
@@ -74,16 +74,8 @@ void wm_gesture_draw(struct wmWindow *win);
 /**
  * Tweak and line gestures.
  */
-int wm_gesture_evaluate(wmGesture *gesture, const struct wmEvent *event);
 void wm_gesture_tag_redraw(struct wmWindow *win);
 
-/* wm_gesture_ops.c */
-
-/**
- * Standard tweak, called after window handlers passed on event.
- */
-void wm_tweakevent_test(bContext *C, const wmEvent *event, int action);
-
 /* wm_jobs.c */
 
 /**
diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h
index c5764ad7a5e..1e907578e52 100644
--- a/source/blender/windowmanager/wm_event_types.h
+++ b/source/blender/windowmanager/wm_event_types.h
@@ -325,16 +325,6 @@ enum {
 
   /* NOTE: these values are saved in key-map files, do not change them but just add new ones. */
 
-  /* Tweak events:
-   * Sent as additional event with the mouse coordinates
-   * from where the initial click was placed. */
-
-  /* Tweak events for L M R mouse-buttons. */
-  EVT_TWEAK_L = 0x5002, /* 20482 */
-  EVT_TWEAK_M = 0x5003, /* 20483 */
-  EVT_TWEAK_R = 0x5004, /* 20484 */
-  /* 0x5010 (and lower) should be left to add other tweak types in the future. */
-
   /* 0x5011 is taken, see EVT_ACTIONZONE_FULLSCREEN */
 
   /* Misc Blender internals: 0x502x */
@@ -394,9 +384,6 @@ enum {
         BUTTON6MOUSE, \
         BUTTON7MOUSE))
 
-/** Test whether the event is tweak event. */
-#define ISTWEAK(event_type) ((event_type) >= EVT_TWEAK_L && (event_type) <= EVT_TWEAK_R)
-
 /** Test whether the event is a NDOF event. */
 #define ISNDOF(event_type) ((event_type) >= _NDOF_MIN && (event_type) <= _NDOF_MAX)
 
@@ -423,14 +410,11 @@ enum eEventType_Mask {
   EVT_TYPE_MASK_MOUSE = (1 << 5),
   /** #ISNDOF */
   EVT_TYPE_MASK_NDOF = (1 << 6),
-  /** #ISTWEAK */
-  EVT_TYPE_MASK_TWEAK = (1 << 7),
   /** #IS_EVENT_ACTIONZONE */
-  EVT_TYPE_MASK_ACTIONZONE = (1 << 8),
+  EVT_TYPE_MASK_ACTIONZONE = (1 << 7),
 };
 #define EVT_TYPE_MASK_ALL \
-  (EVT_TYPE_MASK_KEYBOARD | EVT_TYPE_MASK_MOUSE | EVT_TYPE_MASK_NDOF | EVT_TYPE_MASK_TWEAK | \
-   EVT_TYPE_MASK_ACTIONZONE)
+  (EVT_TYPE_MASK_KEYBOARD | EVT_TYPE_MASK_MOUSE | EVT_TYPE_MASK_NDOF | EVT_TYPE_MASK_ACTIONZONE)
 
 #define EVT_TYPE_MASK_HOTKEY_INCLUDE \
   (EVT_TYPE_MASK_KEYBOARD | EVT_TYPE_MASK_MOUSE | EVT_TYPE_MASK_NDOF)
-- 
cgit v1.2.3


From 7e4c0313283304bd8f020eaedb94b35e75b50068 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 17:29:27 +1100
Subject: Cleanup: remove all tweak events from key-map presets

---
 release/scripts/modules/bl_keymap_utils/io.py      |  14 --
 .../keyconfig/keymap_data/blender_default.py       | 221 ++++++++++-----------
 .../keymap_data/industry_compatible_data.py        | 154 +++++++-------
 tests/python/bl_keymap_validate.py                 |  12 +-
 4 files changed, 188 insertions(+), 213 deletions(-)

diff --git a/release/scripts/modules/bl_keymap_utils/io.py b/release/scripts/modules/bl_keymap_utils/io.py
index 456a1fa5a83..6631461eaba 100644
--- a/release/scripts/modules/bl_keymap_utils/io.py
+++ b/release/scripts/modules/bl_keymap_utils/io.py
@@ -249,20 +249,6 @@ def _init_properties_from_data(base_props, base_value):
 def keymap_init_from_data(km, km_items, is_modal=False):
     new_fn = getattr(km.keymap_items, "new_modal" if is_modal else "new")
     for (kmi_idname, kmi_args, kmi_data) in km_items:
-
-        # TODO(@campbellbarton): Temporary workaround keep until our
-        # key-maps have been updated to remove tweak events.
-        if ty_new := {
-                'EVT_TWEAK_L': 'LEFTMOUSE',
-                'EVT_TWEAK_M': 'MIDDLEMOUSE',
-                'EVT_TWEAK_R': 'RIGHTMOUSE',
-        }.get(kmi_args["type"]):
-            kmi_args["type"] = ty_new
-            if (value := kmi_args["value"]) != 'ANY':
-                kmi_args["direction"] = value
-            kmi_args["value"] = 'CLICK_DRAG'
-        # End workaround.
-
         kmi = new_fn(kmi_idname, **kmi_args)
         if kmi_data is not None:
             if not kmi_data.get("active", True):
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index bf71b8aece8..6f4f862a3b8 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -20,12 +20,8 @@ class Params:
         "legacy",
         "select_mouse",
         "select_mouse_value",
-        "select_tweak",
         "action_mouse",
-        "action_tweak",
         "tool_mouse",
-        "tool_tweak",
-        "tool_maybe_tweak",
         "tool_maybe_tweak_value",
         "context_menu_event",
         "cursor_set_event",
@@ -72,13 +68,13 @@ class Params:
         "use_fallback_tool_select_mouse",
         # Shorthand for: `('CLICK' if self.use_fallback_tool_rmb else self.select_mouse_value)`.
         "select_mouse_value_fallback",
-        # Shorthand for: `{"type": params.select_tweak, "value": 'ANY'}`.
+        # Shorthand for: `{"type": params.select_mouse, "value": 'CLICK_DRAG'}`.
         "select_tweak_event",
         # Shorthand for: `('CLICK_DRAG' if params.use_pie_click_drag else 'PRESS')`
         "pie_value",
-        # Shorthand for: `{"type": params.tool_tweak, "value": 'ANY'}`.
+        # Shorthand for: `{"type": params.tool_mouse, "value": 'CLICK_DRAG'}`.
         "tool_tweak_event",
-        # Shorthand for: `{"type": params.tool_maybe_tweak, "value": params.tool_maybe_tweak_value}`.
+        # Shorthand for: `{"type": params.tool_mouse, "value": params.tool_maybe_tweak_value}`.
         #
         # NOTE: This is typically used for active tool key-map items however it should never
         # be used for selection tools (the default box-select tool for example).
@@ -122,24 +118,19 @@ class Params:
             # Right mouse select.
             self.select_mouse = 'RIGHTMOUSE'
             self.select_mouse_value = 'PRESS'
-            self.select_tweak = 'EVT_TWEAK_R'
             self.action_mouse = 'LEFTMOUSE'
-            self.action_tweak = 'EVT_TWEAK_L'
             self.tool_mouse = 'LEFTMOUSE'
-            self.tool_tweak = 'EVT_TWEAK_L'
             if use_alt_tool_or_cursor:
-                self.tool_maybe_tweak = 'LEFTMOUSE'
                 self.tool_maybe_tweak_value = 'PRESS'
             else:
-                self.tool_maybe_tweak = 'EVT_TWEAK_L'
-                self.tool_maybe_tweak_value = 'ANY'
+                self.tool_maybe_tweak_value = 'CLICK_DRAG'
 
             self.context_menu_event = {"type": 'W', "value": 'PRESS'}
 
             # Use the "cursor" functionality for RMB select.
             if use_alt_tool_or_cursor:
                 self.cursor_set_event = {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}
-                self.cursor_tweak_event = {"type": 'EVT_TWEAK_L', "value": 'ANY', "alt": True}
+                self.cursor_tweak_event = {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "alt": True}
             else:
                 self.cursor_set_event = {"type": 'LEFTMOUSE', "value": 'CLICK'}
                 self.cursor_tweak_event = None
@@ -151,13 +142,9 @@ class Params:
             # events on the same mouse buttons.
             self.select_mouse = 'LEFTMOUSE'
             self.select_mouse_value = 'CLICK'
-            self.select_tweak = 'EVT_TWEAK_L'
             self.action_mouse = 'RIGHTMOUSE'
-            self.action_tweak = 'EVT_TWEAK_R'
             self.tool_mouse = 'LEFTMOUSE'
-            self.tool_tweak = 'EVT_TWEAK_L'
-            self.tool_maybe_tweak = 'EVT_TWEAK_L'
-            self.tool_maybe_tweak_value = 'ANY'
+            self.tool_maybe_tweak_value = 'CLICK_DRAG'
 
             if self.legacy:
                 self.context_menu_event = {"type": 'W', "value": 'PRESS'}
@@ -165,7 +152,7 @@ class Params:
                 self.context_menu_event = {"type": 'RIGHTMOUSE', "value": 'PRESS'}
 
             self.cursor_set_event = {"type": 'RIGHTMOUSE', "value": 'PRESS', "shift": True}
-            self.cursor_tweak_event = {"type": 'EVT_TWEAK_R', "value": 'ANY', "shift": True}
+            self.cursor_tweak_event = {"type": 'RIGHTMOUSE', "value": 'CLICK_DRAG', "shift": True}
 
             # Use the "tool" functionality for LMB select.
             if use_alt_tool_or_cursor:
@@ -199,10 +186,10 @@ class Params:
         # Convenience variables:
         self.use_fallback_tool_select_mouse = True if (select_mouse == 'LEFT') else self.use_fallback_tool_rmb
         self.select_mouse_value_fallback = 'CLICK' if self.use_fallback_tool_rmb else self.select_mouse_value
-        self.select_tweak_event = {"type": self.select_tweak, "value": 'ANY'}
+        self.select_tweak_event = {"type": self.select_mouse, "value": 'CLICK_DRAG'}
         self.pie_value = 'CLICK_DRAG' if use_pie_click_drag else 'PRESS'
-        self.tool_tweak_event = {"type": self.tool_tweak, "value": 'ANY'}
-        self.tool_maybe_tweak_event = {"type": self.tool_maybe_tweak, "value": self.tool_maybe_tweak_value}
+        self.tool_tweak_event = {"type": self.tool_mouse, "value": 'CLICK_DRAG'}
+        self.tool_maybe_tweak_event = {"type": self.tool_mouse, "value": self.tool_maybe_tweak_value}
 
 
 # ------------------------------------------------------------------------------
@@ -355,13 +342,13 @@ def _template_items_gizmo_tweak_value_click_drag():
         ("gizmogroup.gizmo_tweak",
          {"type": 'LEFTMOUSE', "value": 'CLICK', **any_except("alt")}, None),
         ("gizmogroup.gizmo_tweak",
-         {"type": 'EVT_TWEAK_L', "value": 'ANY', **any_except("alt")}, None),
+         {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', **any_except("alt")}, None),
     ]
 
 
 def _template_items_gizmo_tweak_value_drag():
     return [
-        ("gizmogroup.gizmo_tweak", {"type": 'EVT_TWEAK_L', "value": 'ANY', **any_except("alt")}, None),
+        ("gizmogroup.gizmo_tweak", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', **any_except("alt")}, None),
     ]
 
 
@@ -447,7 +434,7 @@ def _template_items_tool_select(params, operator, cursor_operator, *, extend):
         # For right mouse, set the cursor.
         return [
             (cursor_operator, {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
-            ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+            ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
              {"properties": [("release_confirm", True), ("cursor_transform", True)]}),
         ]
 
@@ -890,9 +877,9 @@ def km_mask_editing(params):
          {"properties": [("deselect", True)]}),
         ("mask.select_box", {"type": 'B', "value": 'PRESS'}, None),
         ("mask.select_circle", {"type": 'C', "value": 'PRESS'}, None),
-        ("mask.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True},
+        ("mask.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
-        ("mask.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+        ("mask.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
         ("mask.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
         ("mask.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
@@ -912,7 +899,7 @@ def km_mask_editing(params):
         ("mask.copy_splines", {"type": 'C', "value": 'PRESS', "ctrl": True}, None),
         ("mask.paste_splines", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
         ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
         ("transform.tosphere", {"type": 'S', "value": 'PRESS', "shift": True, "alt": True}, None),
@@ -946,7 +933,7 @@ def km_markers(params):
 
     items.extend([
         ("marker.add", {"type": 'M', "value": 'PRESS'}, None),
-        ("marker.move", {"type": params.select_tweak, "value": 'ANY'},
+        ("marker.move", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True)]}),
         ("marker.duplicate", {"type": 'D', "value": 'PRESS', "shift": True}, None),
         ("marker.select", {"type": params.select_mouse, "value": 'PRESS'}, None),
@@ -956,7 +943,7 @@ def km_markers(params):
          {"properties": [("camera", True)]}),
         ("marker.select", {"type": params.select_mouse, "value": 'PRESS', "shift": True, "ctrl": True},
          {"properties": [("extend", True), ("camera", True)]}),
-        ("marker.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("marker.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True)]}),
         ("marker.select_box", {"type": 'B', "value": 'PRESS'}, None),
         *_template_items_select_actions(params, "marker.select_all"),
@@ -1073,10 +1060,10 @@ def km_outliner(params):
         ("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
          {"properties": [("extend", True), ("extend_range", True), ("deselect_all", not params.legacy)]}),
         ("outliner.select_box", {"type": 'B', "value": 'PRESS'}, None),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, {"properties": [("tweak", True)]}),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
         ("outliner.select_walk", {"type": 'UP_ARROW', "value": 'PRESS', "repeat": True},
          {"properties": [("direction", 'UP')]}),
@@ -1098,14 +1085,14 @@ def km_outliner(params):
          {"properties": [("all", False)]}),
         ("outliner.item_openclose", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
          {"properties": [("all", True)]}),
-        ("outliner.item_openclose", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("outliner.item_openclose", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("all", False)]}),
         # Fall through to generic context menu if the item(s) selected have no type specific actions.
         ("outliner.operation", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
         op_menu("OUTLINER_MT_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),
         op_menu_pie("OUTLINER_MT_view_pie", {"type": 'ACCENT_GRAVE', "value": 'PRESS'}),
-        ("outliner.item_drag_drop", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("outliner.item_drag_drop", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True}, None),
+        ("outliner.item_drag_drop", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("outliner.item_drag_drop", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True}, None),
         ("outliner.show_hierarchy", {"type": 'HOME', "value": 'PRESS'}, None),
         ("outliner.show_active", {"type": 'PERIOD', "value": 'PRESS'}, None),
         ("outliner.show_active", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
@@ -1183,9 +1170,9 @@ def km_uv_editor(params):
         op_tool_optional(
             ("uv.select_circle", {"type": 'C', "value": 'PRESS'}, None),
             (op_tool, "builtin.select_circle"), params),
-        ("uv.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("uv.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("uv.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("uv.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         ("uv.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
         ("uv.select_linked_pick", {"type": 'L', "value": 'PRESS'},
@@ -1214,7 +1201,7 @@ def km_uv_editor(params):
         ),
         *_template_items_proportional_editing(
             params, connected=False, toggle_data_path='tool_settings.use_proportional_edit'),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         op_tool_optional(
             ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
             (op_tool_cycle, "builtin.move"), params),
@@ -1487,9 +1474,9 @@ def km_view3d(params):
         op_tool_optional(
             ("view3d.select_box", {"type": 'B', "value": 'PRESS'}, None),
             (op_tool, "builtin.select_box"), params),
-        ("view3d.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("view3d.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("view3d.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("view3d.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         op_tool_optional(
             ("view3d.select_circle", {"type": 'C', "value": 'PRESS'}, None),
@@ -1506,7 +1493,7 @@ def km_view3d(params):
         ("view3d.copybuffer", {"type": 'C', "value": 'PRESS', "ctrl": True}, None),
         ("view3d.pastebuffer", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
         # Transform.
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         op_tool_optional(
             ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
             (op_tool_cycle, "builtin.move"), params),
@@ -1698,15 +1685,15 @@ def km_graph_editor(params):
         ("graph.select_box", {"type": 'B', "value": 'PRESS'}, None),
         ("graph.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("graph.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("graph.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True), ("mode", 'SET')]}),
-        ("graph.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
+        ("graph.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("graph.select_box", {"type": params.select_tweak, "value": 'ANY', "ctrl": True},
+        ("graph.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
-        ("graph.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("graph.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("graph.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("graph.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         ("graph.select_circle", {"type": 'C', "value": 'PRESS'}, None),
         ("graph.select_column", {"type": 'K', "value": 'PRESS'},
@@ -1754,7 +1741,7 @@ def km_graph_editor(params):
          {"properties": [("only_active", False)]}),
         ("anim.channels_editable_toggle", {"type": 'TAB', "value": 'PRESS'}, None),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
@@ -1982,11 +1969,11 @@ def km_node_editor(params):
         ])
 
     items.extend([
-        ("node.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("node.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True)]}),
-        ("node.select_lasso", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True, "alt": True},
+        ("node.select_lasso", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
-        ("node.select_lasso", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+        ("node.select_lasso", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
         op_tool_optional(
             ("node.select_box", {"type": 'B', "value": 'PRESS'},
@@ -1995,16 +1982,16 @@ def km_node_editor(params):
         op_tool_optional(
             ("node.select_circle", {"type": 'C', "value": 'PRESS'}, None),
             (op_tool, "builtin.select_circle"), params),
-        ("node.link", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("node.link", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("detach", False)]}),
-        ("node.link", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("node.link", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("detach", True)]}),
-        ("node.resize", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("node.resize", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("node.add_reroute",
-         {"type": 'EVT_TWEAK_L' if params.legacy else 'EVT_TWEAK_R', "value": 'ANY', "shift": True}, None),
+         {"type": 'LEFTMOUSE' if params.legacy else 'RIGHTMOUSE', "value": 'CLICK_DRAG', "shift": True}, None),
         ("node.links_cut",
-         {"type": 'EVT_TWEAK_L' if params.legacy else 'EVT_TWEAK_R', "value": 'ANY', "ctrl": True}, None),
-        ("node.links_mute", {"type": 'EVT_TWEAK_R', "value": 'ANY', "ctrl": True, "alt": True}, None),
+         {"type": 'LEFTMOUSE' if params.legacy else 'RIGHTMOUSE', "value": 'CLICK_DRAG', "ctrl": True}, None),
+        ("node.links_mute", {"type": 'RIGHTMOUSE', "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ("node.select_link_viewer", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True}, None),
         ("node.backimage_move", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "alt": True}, None),
         ("node.backimage_zoom", {"type": 'V', "value": 'PRESS', "repeat": True},
@@ -2065,19 +2052,19 @@ def km_node_editor(params):
          {"type": 'G', "value": 'PRESS'},
          {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
         ("node.translate_attach",
-         {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+         {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
         # Avoid duplicating the previous item.
-        *([] if params.select_tweak == 'EVT_TWEAK_L' else (
-            ("node.translate_attach", {"type": params.select_tweak, "value": 'ANY'},
+        *([] if params.select_mouse == 'LEFTMOUSE' else (
+            ("node.translate_attach", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
              {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
         )),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, {"properties": [("view2d_edge_pan", True)]}),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}),
         # Avoid duplicating the previous item.
-        *([] if params.select_tweak == 'EVT_TWEAK_L' else (
-            ("transform.translate", {"type": params.select_tweak, "value": 'ANY'},
+        *([] if params.select_mouse == 'LEFTMOUSE' else (
+            ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
              {"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}),
         )),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
@@ -2086,10 +2073,10 @@ def km_node_editor(params):
          {"type": 'D', "value": 'PRESS', "alt": True},
          {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
         ("node.move_detach_links_release",
-         {"type": params.action_tweak, "value": 'ANY', "alt": True},
+         {"type": params.action_mouse, "value": 'CLICK_DRAG', "alt": True},
          {"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),
         ("node.move_detach_links",
-         {"type": params.select_tweak, "value": 'ANY', "alt": True},
+         {"type": params.select_mouse, "value": 'CLICK_DRAG', "alt": True},
          {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
         ("wm.context_toggle", {"type": 'TAB', "value": 'PRESS', "shift": True},
          {"properties": [("data_path", 'tool_settings.use_snap')]}),
@@ -2118,7 +2105,7 @@ def km_info(params):
         ("info.select_pick", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None),
         ("info.select_pick", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
          {"properties": [("extend", True)]}),
-        ("info.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("info.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("wait_for_input", False)]}),
         *_template_items_select_actions(params, "info.select_all"),
         ("info.select_box", {"type": 'B', "value": 'PRESS'}, None),
@@ -2247,10 +2234,10 @@ def km_file_browser_main(params):
         ("file.next", {"type": 'BUTTON5MOUSE', "value": 'CLICK'}, None),
         *_template_items_select_actions(params, "file.select_all"),
         ("file.select_box", {"type": 'B', "value": 'PRESS'}, None),
-        ("file.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("file.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("file.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("file.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("mode", 'ADD')]}),
-        ("file.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("file.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         ("file.highlight", {"type": 'MOUSEMOVE', "value": 'ANY', "any": True}, None),
         ("file.sort_column_ui_context", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None),
@@ -2352,15 +2339,15 @@ def km_dopesheet(params):
          {"properties": [("axis_range", False)]}),
         ("action.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("action.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("action.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True), ("mode", 'SET')]}),
-        ("action.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
+        ("action.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("action.select_box", {"type": params.select_tweak, "value": 'ANY', "ctrl": True},
+        ("action.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
-        ("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("action.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("action.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         ("action.select_circle", {"type": 'C', "value": 'PRESS'}, None),
         ("action.select_column", {"type": 'K', "value": 'PRESS'},
@@ -2405,7 +2392,7 @@ def km_dopesheet(params):
         ("anim.channels_select_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
         ("transform.transform", {"type": 'G', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_TRANSLATE')]}),
-        ("transform.transform", {"type": params.select_tweak, "value": 'ANY'},
+        ("transform.transform", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TIME_TRANSLATE')]}),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
@@ -2504,11 +2491,11 @@ def km_nla_editor(params):
          {"properties": [("axis_range", False)]}),
         ("nla.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("nla.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("nla.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True), ("mode", 'SET')]}),
-        ("nla.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
+        ("nla.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("nla.select_box", {"type": params.select_tweak, "value": 'ANY', "ctrl": True},
+        ("nla.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
         ("nla.previewrange_set", {"type": 'P', "value": 'PRESS', "ctrl": True, "alt": True}, None),
         ("nla.view_all", {"type": 'HOME', "value": 'PRESS'}, None),
@@ -2543,7 +2530,7 @@ def km_nla_editor(params):
         ("nla.fmodifier_add", {"type": 'M', "value": 'PRESS', "shift": True, "ctrl": True}, None),
         ("transform.transform", {"type": 'G', "value": 'PRESS'},
          {"properties": [("mode", 'TRANSLATION')]}),
-        ("transform.transform", {"type": params.select_tweak, "value": 'ANY'},
+        ("transform.transform", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TRANSLATION')]}),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
@@ -2705,7 +2692,7 @@ def km_text(params):
         ("text.scroll_bar", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
         ("text.scroll", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
         ("text.scroll", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
-        ("text.selection_set", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("text.selection_set", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("text.cursor_set", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
         ("text.selection_set", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
         ("text.scroll", {"type": 'WHEELUPMOUSE', "value": 'PRESS'},
@@ -2836,11 +2823,11 @@ def km_sequencer(params):
         ("sequencer.select_linked_pick", {"type": 'L', "value": 'PRESS', "shift": True},
          {"properties": [("extend", True)]}),
         ("sequencer.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
-        ("sequencer.select_box", {"type": params.select_tweak, "value": 'ANY'},
+        ("sequencer.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True), ("mode", 'SET')]}),
-        ("sequencer.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
+        ("sequencer.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("sequencer.select_box", {"type": params.select_tweak, "value": 'ANY', "ctrl": True},
+        ("sequencer.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
         ("sequencer.select_box", {"type": 'B', "value": 'PRESS'}, None),
         ("sequencer.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True},
@@ -2853,7 +2840,7 @@ def km_sequencer(params):
         ("wm.context_set_int", {"type": 'O', "value": 'PRESS'},
          {"properties": [("data_path", 'scene.sequence_editor.overlay_frame'), ("value", 0)]}),
         ("transform.seq_slide", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.seq_slide", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.seq_slide", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
         ("marker.add", {"type": 'M', "value": 'PRESS'}, None),
@@ -2909,7 +2896,7 @@ def km_sequencerpreview(params):
         op_menu_pie("SEQUENCER_MT_preview_view_pie", {"type": 'ACCENT_GRAVE', "value": 'PRESS'}),
 
         # Edit.
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         op_tool_optional(
             ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
             (op_tool_cycle, "builtin.move"), params),
@@ -3125,9 +3112,9 @@ def km_clip_editor(params):
         ("clip.select_box", {"type": 'B', "value": 'PRESS'}, None),
         ("clip.select_circle", {"type": 'C', "value": 'PRESS'}, None),
         op_menu("CLIP_MT_select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}),
-        ("clip.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True},
+        ("clip.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
-        ("clip.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+        ("clip.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
         ("clip.add_marker_slide", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
         ("clip.delete_marker", {"type": 'X', "value": 'PRESS', "shift": True}, None),
@@ -3154,7 +3141,7 @@ def km_clip_editor(params):
         ("wm.context_toggle", {"type": 'M', "value": 'PRESS'},
          {"properties": [("data_path", 'space_data.use_mute_footage')]}),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
         ("clip.clear_track_path", {"type": 'T', "value": 'PRESS', "alt": True},
@@ -3223,7 +3210,7 @@ def km_clip_graph_editor(params):
         ("clip.graph_disable_markers", {"type": 'D', "value": 'PRESS', "shift": True},
          {"properties": [("action", 'TOGGLE')]}),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
     ])
@@ -3405,10 +3392,10 @@ def km_animation_channels(params):
         # Selection.
         *_template_items_select_actions(params, "anim.channels_select_all"),
         ("anim.channels_select_box", {"type": 'B', "value": 'PRESS'}, None),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("extend", True)]}),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("deselect", True)]}),
         # Delete.
         ("anim.channels_delete", {"type": 'X', "value": 'PRESS'}, None),
@@ -3498,9 +3485,9 @@ def _grease_pencil_selection(params, use_select_mouse=True):
             ("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
             (op_tool, "builtin.select_box"), params),
         # Lasso select
-        ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         # In the Node Editor, lasso select needs ALT modifier too
         # (as somehow CTRL+LMB drag gets taken for "cut" quite early).
@@ -3508,10 +3495,10 @@ def _grease_pencil_selection(params, use_select_mouse=True):
         # as part of standard GP editing keymap. This hotkey combo doesn't seem
         # to see much use under standard scenarios?
         ("gpencil.select_lasso",
-         {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True},
+         {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
         ("gpencil.select_lasso",
-         {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+         {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
         *_template_view3d_gpencil_select(
             type=params.select_mouse,
@@ -3603,7 +3590,7 @@ def km_grease_pencil_stroke_edit_mode(params):
         # Merge Layer
         ("gpencil.layer_merge", {"type": 'M', "value": 'PRESS', "shift": True, "ctrl": True}, None),
         # Transform tools
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         op_tool_optional(
             ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
             (op_tool_cycle, "builtin.move"), params),
@@ -3775,7 +3762,7 @@ def km_grease_pencil_stroke_paint_draw_brush(params):
         # Box select
         ("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
         # Lasso select
-        ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+        ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
     ])
 
     return keymap
@@ -3798,7 +3785,7 @@ def km_grease_pencil_stroke_paint_erase(params):
         # Box select (used by eraser)
         ("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
         # Lasso select
-        ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+        ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
     ])
 
     return keymap
@@ -4346,9 +4333,9 @@ def km_weight_paint_vertex_selection(params):
     items.extend([
         *_template_items_select_actions(params, "paint.vert_select_all"),
         ("view3d.select_box", {"type": 'B', "value": 'PRESS'}, None),
-        ("view3d.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
+        ("view3d.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
-        ("view3d.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
+        ("view3d.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True},
          {"properties": [("mode", 'SUB')]}),
         ("view3d.select_circle", {"type": 'C', "value": 'PRESS'}, None),
     ])
@@ -4546,7 +4533,7 @@ def km_paint_curve(params):
         ("paintcurve.draw", {"type": 'RET', "value": 'PRESS'}, None),
         ("paintcurve.draw", {"type": 'NUMPAD_ENTER', "value": 'PRESS'}, None),
         ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": params.select_tweak, "value": 'ANY'}, None),
+        ("transform.translate", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, None),
         ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
         ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
     ])
@@ -6461,8 +6448,8 @@ def km_node_editor_tool_select_circle(params, *, fallback):
                 "node.select_circle",
                 # Why circle select should be used on tweak?
                 # So that RMB or Shift-RMB is still able to set an element as active.
-                type=params.select_tweak if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
-                value='ANY' if fallback else 'PRESS',
+                type=params.select_mouse if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
+                value='CLICK_DRAG' if (fallback and params.use_fallback_tool_select_mouse) else 'PRESS',
                 properties=[("wait_for_input", False)])),
         ]},
     )
@@ -6530,8 +6517,8 @@ def km_3d_view_tool_select_circle(params, *, fallback):
                 "view3d.select_circle",
                 # Why circle select should be used on tweak?
                 # So that RMB or Shift-RMB is still able to set an element as active.
-                type=params.select_tweak if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
-                value='ANY' if fallback else 'PRESS',
+                type=params.select_mouse if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
+                value='CLICK_DRAG' if (fallback and params.use_fallback_tool_select_mouse) else 'PRESS',
                 properties=[("wait_for_input", False)])),
         ]},
     )
@@ -6608,7 +6595,7 @@ def km_3d_view_tool_shear(params):
 
             # Use as fallback to catch diagonals too.
             ("transform.shear",
-             {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier},
+             {"type": params.tool_mouse, "value": 'CLICK_DRAG', **params.tool_modifier},
              {"properties": [("release_confirm", True), ("orient_axis_ortho", 'X')]}),
         ]},
     )
@@ -7281,7 +7268,7 @@ def km_3d_view_tool_paint_gpencil_line(params):
             ("gpencil.primitive_line", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
              {"properties": [("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7296,7 +7283,7 @@ def km_3d_view_tool_paint_gpencil_polyline(params):
             ("gpencil.primitive_polyline", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
              {"properties": [("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7313,7 +7300,7 @@ def km_3d_view_tool_paint_gpencil_box(params):
             ("gpencil.primitive_box", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
              {"properties": [("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7330,7 +7317,7 @@ def km_3d_view_tool_paint_gpencil_circle(params):
             ("gpencil.primitive_circle", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
              {"properties": [("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7347,7 +7334,7 @@ def km_3d_view_tool_paint_gpencil_arc(params):
             ("gpencil.primitive_curve", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
              {"properties": [("type", 'ARC'), ("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7360,7 +7347,7 @@ def km_3d_view_tool_paint_gpencil_curve(params):
             ("gpencil.primitive_curve", params.tool_maybe_tweak_event,
              {"properties": [("type", 'CURVE'), ("wait_for_input", False)]}),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7372,7 +7359,7 @@ def km_3d_view_tool_paint_gpencil_cutter(params):
         {"items": [
             ("gpencil.stroke_cutter", {"type": params.tool_mouse, "value": 'PRESS'}, None),
             # Lasso select
-            ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+            ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ]},
     )
 
@@ -7442,8 +7429,8 @@ def km_3d_view_tool_edit_gpencil_select_circle(params, *, fallback):
                 "gpencil.select_circle",
                 # Why circle select should be used on tweak?
                 # So that RMB or Shift-RMB is still able to set an element as active.
-                type=params.select_tweak if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
-                value='ANY' if fallback else 'PRESS',
+                type=params.select_mouse if (fallback and params.use_fallback_tool_select_mouse) else params.tool_mouse,
+                value='CLICK_DRAG' if (fallback and params.use_fallback_tool_select_mouse) else 'PRESS',
                 properties=[("wait_for_input", False)])),
         ]},
     )
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 7a55ce5e5f0..e65ac32d088 100644
--- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
+++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
@@ -7,11 +7,8 @@ class Params:
     __slots__ = (
         "select_mouse",
         "select_mouse_value",
-        "select_tweak",
         "action_mouse",
-        "action_tweak",
         "tool_mouse",
-        "tool_tweak",
         "use_mouse_emulate_3_button",
 
     )
@@ -24,9 +21,7 @@ class Params:
         self.tool_mouse = 'LEFTMOUSE'
         self.select_mouse = 'LEFTMOUSE'
         self.select_mouse_value = 'CLICK'
-        self.select_tweak = 'EVT_TWEAK_L'
-        self.tool_tweak = 'EVT_TWEAK_L'
-        self.action_tweak = 'EVT_TWEAK_R'
+        self.action_mouse = 'RIGHTMOUSE'
         self.use_mouse_emulate_3_button = use_mouse_emulate_3_button
 
 
@@ -103,7 +98,7 @@ def _template_items_animation():
 
 def _template_items_gizmo_tweak_value_drag():
     return [
-        ("gizmogroup.gizmo_tweak", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("gizmogroup.gizmo_tweak", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
     ]
 
 
@@ -479,10 +474,10 @@ def km_outliner(params):
          {"properties": [("extend", False), ("extend_range", True), ("deselect_all", True)]}),
         ("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
          {"properties": [("extend", True), ("extend_range", True), ("deselect_all", True)]}),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, {"properties": [("tweak", True)]}),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("tweak", True), ("mode", 'ADD')]}),
-        ("outliner.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("tweak", True), ("mode", 'SUB')]}),
         ("outliner.select_walk", {"type": 'UP_ARROW', "value": 'PRESS', "repeat": True},
          {"properties": [("direction", 'UP')]}),
@@ -504,13 +499,13 @@ def km_outliner(params):
          {"properties": [("all", False)]}),
         ("outliner.item_openclose", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
          {"properties": [("all", True)]}),
-        ("outliner.item_openclose", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("outliner.item_openclose", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("all", False)]}),
         # Fall through to generic context menu if the item(s) selected have no type specific actions.
         ("outliner.operation", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
         op_menu("OUTLINER_MT_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),
-        ("outliner.item_drag_drop", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("outliner.item_drag_drop", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True}, None),
+        ("outliner.item_drag_drop", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("outliner.item_drag_drop", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True}, None),
         ("outliner.show_hierarchy", {"type": 'A', "value": 'PRESS'}, None),
         ("outliner.show_active", {"type": 'PERIOD', "value": 'PRESS'}, None),
         ("outliner.show_active", {"type": 'F', "value": 'PRESS'}, None),
@@ -573,7 +568,7 @@ def km_uv_editor(params):
         ("uv.select", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
          {"properties": [("extend", True), ("deselect_all", False)]}),
 
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("uv.select_loop", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "shift": True},
          {"properties": [("extend", True)]}),
         ("uv.select_loop", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'},
@@ -742,7 +737,7 @@ def km_view3d(params):
         # Menus.
         op_menu_pie("VIEW3D_MT_snap_pie", {"type": 'X', "value": 'PRESS', "shift": True}),
         # Transform.
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         op_menu_pie("VIEW3D_MT_pivot_pie", {"type": 'PERIOD', "value": 'PRESS'}),
         op_menu_pie("VIEW3D_MT_orientations_pie", {"type": 'COMMA', "value": 'PRESS'}),
         ("view3d.toggle_xray", {"type": 'X', "value": 'PRESS', "alt": True}, None),
@@ -782,9 +777,9 @@ def km_mask_editing(params):
          {"properties": [("deselect", True)]}),
         ("mask.select_box", {"type": 'Q', "value": 'PRESS'}, None),
         ("mask.select_circle", {"type": 'C', "value": 'PRESS'}, None),
-        ("mask.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True},
+        ("mask.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
-        ("mask.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+        ("mask.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
         ("mask.select_more", {"type": 'UP_ARROW', "value": 'PRESS', "repeat": True}, None),
         ("mask.select_less", {"type": 'DOWN_ARROW', "value": 'PRESS', "repeat": True}, None),
@@ -826,7 +821,7 @@ def km_markers(params):
     items.extend([
         ("wm.search_menu", {"type": 'TAB', "value": 'PRESS'}, None),
         ("marker.add", {"type": 'M', "value": 'PRESS'}, None),
-        ("marker.move", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("marker.move", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("marker.duplicate", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
         ("marker.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
         ("marker.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
@@ -906,11 +901,11 @@ def km_graph_editor(params):
          {"properties": [("axis_range", False)]}),
         ("graph.select_box", {"type": 'Q', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("graph.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties":[("tweak", True), ("axis_range", False), ("mode", 'SET')]}),
-        ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("graph.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties":[("tweak", True), ("axis_range", False), ("mode", 'ADD')]}),
-        ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("graph.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties":[("tweak", True), ("axis_range", False), ("mode", 'SUB')]}),
         ("graph.select_more", {"type": 'UP_ARROW', "value": 'PRESS', "repeat": True}, None),
         ("graph.select_less", {"type": 'DOWN_ARROW', "value": 'PRESS', "repeat": True}, None),
@@ -931,8 +926,8 @@ def km_graph_editor(params):
         ("graph.view_frame", {"type": 'A', "value": 'PRESS', "shift": True}, None),
         ("anim.channels_editable_toggle", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None),
         ("transform.translate", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_M', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("transform.translate", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.transform", {"type": 'Y', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
         ("transform.rotate", {"type": 'E', "value": 'PRESS'}, None),
@@ -1087,20 +1082,20 @@ def km_node_editor(params):
     items.extend(node_select_ops('LEFTMOUSE'))
 
     items.extend([
-        ("node.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("node.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("tweak", True)]}),
-        ("node.select_lasso", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True, "alt": True},
+        ("node.select_lasso", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True, "alt": True},
          {"properties": [("mode", 'ADD')]}),
-        ("node.select_lasso", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+        ("node.select_lasso", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True, "ctrl": True, "alt": True},
          {"properties": [("mode", 'SUB')]}),
-        ("node.link", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("node.link", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("detach", False)]}),
-        ("node.link", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("node.link", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties": [("detach", True)]}),
-        ("node.resize", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("node.add_reroute", {"type": params.action_tweak, "value": 'ANY', "shift": True}, None),
-        ("node.links_cut", {"type": params.action_tweak, "value": 'ANY', "ctrl": True}, None),
-        ("node.links_mute", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+        ("node.resize", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("node.add_reroute", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True}, None),
+        ("node.links_cut", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True}, None),
+        ("node.links_mute", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
         ("node.select_link_viewer", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True}, None),
         ("node.backimage_fit", {"type": 'A', "value": 'PRESS', "alt": True}, None),
         ("node.backimage_sample", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, None),
@@ -1147,15 +1142,15 @@ def km_node_editor(params):
         ("node.viewer_border", {"type": 'Z', "value": 'PRESS'}, None),
         ("node.clear_viewer_border", {"type": 'Z', "value": 'PRESS', "alt": True}, None),
         ("node.translate_attach", {"type": 'W', "value": 'PRESS'}, None),
-        ("node.translate_attach", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("node.translate_attach", {"type": 'EVT_TWEAK_M', "value": 'ANY'}, None),
+        ("node.translate_attach", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("node.translate_attach", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.translate", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("release_confirm", True)]}),
         ("transform.rotate", {"type": 'E', "value": 'PRESS'}, None),
         ("transform.resize", {"type": 'R', "value": 'PRESS'}, None),
-        ("node.move_detach_links_release", {"type": params.action_tweak, "value": 'ANY', "alt": True}, None),
-        ("node.move_detach_links", {"type": 'EVT_TWEAK_L', "value": 'ANY', "alt": True}, None),
+        ("node.move_detach_links_release", {"type": params.action_mouse, "value": 'CLICK_DRAG', "alt": True}, None),
+        ("node.move_detach_links", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "alt": True}, None),
         ("wm.context_toggle", {"type": 'X', "value": 'PRESS'},
          {"properties": [("data_path", 'tool_settings.use_snap')]}),
     ])
@@ -1177,7 +1172,7 @@ def km_info(params):
         ("info.select_pick", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
         ("info.select_pick", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
          {"properties": [("extend", True)]}),
-        ("info.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("info.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("wait_for_input", False)]}),
         ("info.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True},
          {"properties": [("action", 'SELECT')]}),
@@ -1300,8 +1295,8 @@ def km_file_browser_main(params):
         ("file.next", {"type": 'BUTTON5MOUSE', "value": 'CLICK'}, None),
         ("file.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, None),
         ("file.select_box", {"type": 'Q', "value": 'PRESS'}, None),
-        ("file.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("file.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("file.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("file.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties": [("mode", 'ADD')]}),
         ("file.highlight", {"type": 'MOUSEMOVE', "value": 'ANY', "any": True}, None),
         ("file.sort_column_ui_context", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None),
@@ -1391,11 +1386,11 @@ def km_dopesheet(params):
          {"properties": [("axis_range", False)]}),
         ("action.select_box", {"type": 'Q', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("action.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("action.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties":[("tweak", True), ("axis_range", False), ("wait_for_input", False), ("mode", 'SET')]}),
-        ("action.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("action.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties":[("tweak", True), ("axis_range", False), ("wait_for_input", False), ("mode", 'ADD')]}),
-        ("action.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("action.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties":[("tweak", True), ("axis_range", False), ("wait_for_input", False), ("mode", 'SUB')]}),
         ("action.select_column", {"type": 'K', "value": 'PRESS'},
          {"properties": [("mode", 'KEYS')]}),
@@ -1430,9 +1425,9 @@ def km_dopesheet(params):
         ("anim.channels_select_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
         ("transform.transform", {"type": 'W', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_TRANSLATE')]}),
-        ("transform.transform", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("transform.transform", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TIME_TRANSLATE')]}),
-        ("transform.transform", {"type": 'EVT_TWEAK_M', "value": 'ANY'},
+        ("transform.transform", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TIME_TRANSLATE')]}),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
@@ -1518,11 +1513,11 @@ def km_nla_editor(params):
          {"properties": [("axis_range", False)]}),
         ("nla.select_box", {"type": 'Q', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
-        ("nla.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("nla.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties":[("tweak", True), ("mode", 'SET')]}),
-        ("nla.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("nla.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties":[("tweak", True), ("mode", 'ADD')]}),
-        ("nla.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("nla.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties":[("tweak", True), ("mode", 'SUB')]}),
         ("nla.view_all", {"type": 'A', "value": 'PRESS'}, None),
         ("nla.view_all", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'}, None),
@@ -1542,9 +1537,9 @@ def km_nla_editor(params):
         ("nla.move_down", {"type": 'PAGE_DOWN', "value": 'PRESS'}, None),
         ("transform.transform", {"type": 'W', "value": 'PRESS'},
          {"properties": [("mode", 'TRANSLATION')]}),
-        ("transform.transform", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("transform.transform", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TRANSLATION')]}),
-        ("transform.transform", {"type": 'EVT_TWEAK_M', "value": 'ANY'},
+        ("transform.transform", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG'},
          {"properties": [("mode", 'TRANSLATION')]}),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
@@ -1701,7 +1696,7 @@ def km_text(params):
         ("text.scroll_bar", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
         ("text.scroll", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
         ("text.scroll", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
-        ("text.selection_set", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("text.selection_set", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("text.cursor_set", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
         ("text.selection_set", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
         ("text.scroll", {"type": 'WHEELUPMOUSE', "value": 'PRESS'},
@@ -1823,19 +1818,19 @@ def km_sequencer(params):
         ("sequencer.select_linked_pick", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "shift": True},
          {"properties": [("extend", True)]}),
         ("sequencer.select_linked", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "ctrl": True}, None),
-        ("sequencer.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
+        ("sequencer.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'},
          {"properties":[("tweak", True), ("mode", 'SET')]}),
-        ("sequencer.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
+        ("sequencer.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
          {"properties":[("tweak", True), ("mode", 'ADD')]}),
-        ("sequencer.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
+        ("sequencer.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True},
          {"properties":[("tweak", True), ("mode", 'SUB')]}),
         ("sequencer.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None),
         ("sequencer.slip", {"type": 'R', "value": 'PRESS'}, None),
         ("wm.context_set_int", {"type": 'O', "value": 'PRESS'},
          {"properties": [("data_path", 'scene.sequence_editor.overlay_frame'), ("value", 0)]}),
         ("transform.seq_slide", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.seq_slide", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("transform.seq_slide", {"type": 'EVT_TWEAK_M', "value": 'ANY'}, None),
+        ("transform.seq_slide", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("transform.seq_slide", {"type": 'MIDDLEMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.transform", {"type": 'E', "value": 'PRESS'},
          {"properties": [("mode", 'TIME_EXTEND')]}),
         *_template_items_context_menu("SEQUENCER_MT_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),
@@ -2059,7 +2054,7 @@ def km_clip_editor(params):
         ("wm.context_toggle", {"type": 'M', "value": 'PRESS'},
          {"properties": [("data_path", 'space_data.use_mute_footage')]}),
         ("transform.translate", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.resize", {"type": 'R', "value": 'PRESS'}, None),
         ("transform.rotate", {"type": 'E', "value": 'PRESS'}, None),
         ("clip.clear_track_path", {"type": 'T', "value": 'PRESS', "alt": True},
@@ -2113,7 +2108,7 @@ def km_clip_graph_editor(params):
         ("clip.graph_disable_markers", {"type": 'D', "value": 'PRESS', "shift": True},
          {"properties": [("action", 'TOGGLE')]}),
         ("transform.translate", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.resize", {"type": 'R', "value": 'PRESS'}, None),
         ("transform.rotate", {"type": 'E', "value": 'PRESS'}, None),
     ])
@@ -2227,10 +2222,10 @@ def km_animation_channels(params):
         ("anim.channels_select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'SELECT')]}),
         ("anim.channels_select_all", {"type": 'A', "value": 'PRESS', "ctrl": True, "shift": True}, {"properties": [("action", 'DESELECT')]}),
         ("anim.channels_select_all", {"type": 'I', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'INVERT')]}),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True,},
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True,},
          {"properties": [("extend", True)]}),
-        ("anim.channels_select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True,},
+        ("anim.channels_select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "ctrl": True,},
          {"properties": [("deselect", True)]}),
         # Delete.
         ("anim.channels_delete", {"type": 'BACK_SPACE', "value": 'PRESS'}, None),
@@ -2359,7 +2354,7 @@ def km_grease_pencil_stroke_edit_mode(params):
         # Isolate layer
         ("gpencil.layer_isolate", {"type": 'NUMPAD_ASTERIX', "value": 'PRESS'}, None),
         # Transform tools
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("wm.context_toggle", {"type": 'B', "value": 'PRESS'},
          {"properties": [("data_path", 'tool_settings.use_proportional_edit')]}),
         # Vertex group menu
@@ -2482,7 +2477,7 @@ def km_grease_pencil_stroke_paint_erase(params):
         # Box select (used by eraser)
         ("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
         # Lasso select
-        ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
+        ("gpencil.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True, "alt": True}, None),
     ])
 
     return keymap
@@ -3125,7 +3120,7 @@ def km_paint_curve(params):
         ("paintcurve.draw", {"type": 'RET', "value": 'PRESS'}, None),
         ("paintcurve.draw", {"type": 'NUMPAD_ENTER', "value": 'PRESS'}, None),
         ("transform.translate", {"type": 'W', "value": 'PRESS'}, None),
-        ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+        ("transform.translate", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
         ("transform.rotate", {"type": 'E', "value": 'PRESS'}, None),
         ("transform.resize", {"type": 'R', "value": 'PRESS'}, None),
     ])
@@ -4018,9 +4013,9 @@ def km_3d_view_tool_interactive_add(params):
         "3D View Tool: Object, Add Primitive",
         {"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
         {"items": [
-            ("view3d.interactive_add", {"type": params.tool_tweak, "value": 'ANY'},
+            ("view3d.interactive_add", {"type": params.tool_mouse, "value": 'CLICK_DRAG'},
              {"properties": [("wait_for_input", False)]}),
-            ("view3d.interactive_add", {"type": params.tool_tweak, "value": 'ANY', "ctrl": True},
+            ("view3d.interactive_add", {"type": params.tool_mouse, "value": 'CLICK_DRAG', "ctrl": True},
              {"properties": [("wait_for_input", False)]}),
         ]},
     )
@@ -4184,29 +4179,28 @@ def keymap_transform_tool_mmb(keymap):
             km_items_new = []
             for kmi in km_items:
                 ty = kmi[1]["type"]
+                value = kmi[1]["value"]
                 if km_name.endswith(" (fallback)"):
                     if ty == 'RIGHTMOUSE':
                         kmi = (kmi[0], kmi[1].copy(), kmi[2])
                         kmi[1]["type"] = 'LEFTMOUSE'
                         km_items_new.append(kmi)
-                    elif ty == 'EVT_TWEAK_R':
-                        kmi = (kmi[0], kmi[1].copy(), kmi[2])
-                        kmi[1]["type"] = 'EVT_TWEAK_L'
-                        km_items_new.append(kmi)
                 else:
                     if ty == 'LEFTMOUSE':
-                        kmi = (kmi[0], kmi[1].copy(), kmi[2])
-                        kmi[1]["type"] = 'MIDDLEMOUSE'
-                        km_items_new.append(kmi)
-                    elif ty == 'EVT_TWEAK_L':
-                        kmi = (kmi[0], kmi[1].copy(), kmi[2])
-                        if kmi[1]["value"] == 'ANY':
+                        if value == 'CLICK_DRAG':
+                            kmi = (kmi[0], kmi[1].copy(), kmi[2])
+                            if kmi[1].get("direction", 'ANY') == 'ANY':
+                                kmi[1]["type"] = 'MIDDLEMOUSE'
+                                kmi[1]["value"] = 'PRESS'
+                            else:
+                                # Directional tweaking can't be replaced by middle-mouse.
+                                kmi[1]["type"] = 'MIDDLEMOUSE'
+                            km_items_new.append(kmi)
+                        else:
+                            kmi = (kmi[0], kmi[1].copy(), kmi[2])
                             kmi[1]["type"] = 'MIDDLEMOUSE'
                             kmi[1]["value"] = 'PRESS'
-                        else:
-                            # Directional tweaking can't be replaced by middle-mouse.
-                            kmi[1]["type"] = 'EVT_TWEAK_M'
-                        km_items_new.append(kmi)
+                            km_items_new.append(kmi)
             km_items.extend(km_items_new)
 
 
diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index 7bd40ce97e7..1743893dc8a 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -63,6 +63,12 @@ PRESET_PREFS = {
     ),
 }
 
+# Don't report duplicates for these presets.
+ALLOW_DUPLICATES = {
+    # This key-map manipulates the default key-map, making it difficult to avoid duplicates entirely.
+    "Industry_Compatible"
+}
+
 # -----------------------------------------------------------------------------
 # Generic Utilities
 
@@ -271,7 +277,6 @@ def main() -> None:
     presets = keyconfig_preset_scan()
     for filepath in presets:
         name_only = os.path.splitext(os.path.basename(filepath))[0]
-
         for config in PRESET_PREFS.get(name_only, ((),)):
             name_only_with_config = name_only + keyconfig_config_as_filename_component(config)
             print("KeyMap Validate:", name_only_with_config, end=" ... ")
@@ -307,7 +312,10 @@ def main() -> None:
 
             # Perform an additional sanity check:
             # That there are no identical key-map items.
-            error_text_duplicates = keyconfig_report_duplicates(data_orig)
+            if name_only not in ALLOW_DUPLICATES:
+                error_text_duplicates = keyconfig_report_duplicates(data_orig)
+            else:
+                error_text_duplicates = ""
 
             if error_text_consistency or error_text_duplicates:
                 print("FAILED!")
-- 
cgit v1.2.3


From 46a5a15d30f951b150b66856442566d5b657ab1e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 17:54:06 +1100
Subject: Cleanup: rename direction enum as it's part of the key-map item

Also improve doc-strings for key-map item constants.
---
 .../blender/blenloader/intern/versioning_userdef.c |  2 +-
 source/blender/makesrna/intern/rna_wm.c            | 16 ++--
 source/blender/windowmanager/WM_types.h            | 87 +++++++++++++++-------
 .../blender/windowmanager/intern/wm_event_query.c  | 16 ++--
 source/blender/windowmanager/wm_event_types.h      | 12 ---
 5 files changed, 77 insertions(+), 56 deletions(-)

diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index 862e8a09166..e933964221b 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -411,7 +411,7 @@ static bool keymap_item_update_tweak_event(wmKeyMapItem *kmi, void *UNUSED(user_
       return false;
   }
 
-  if (kmi->val >= EVT_GESTURE_N && kmi->val <= EVT_GESTURE_NW) {
+  if (kmi->val >= KM_DIRECTION_N && kmi->val <= KM_DIRECTION_NW) {
     kmi->direction = kmi->val;
   }
   else {
diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c
index d88f150c1dd..8835591f303 100644
--- a/source/blender/makesrna/intern/rna_wm.c
+++ b/source/blender/makesrna/intern/rna_wm.c
@@ -365,14 +365,14 @@ const EnumPropertyItem rna_enum_event_value_items[] = {
 
 const EnumPropertyItem rna_enum_event_direction_items[] = {
     {KM_ANY, "ANY", 0, "Any", ""},
-    {EVT_GESTURE_N, "NORTH", 0, "North", ""},
-    {EVT_GESTURE_NE, "NORTH_EAST", 0, "North-East", ""},
-    {EVT_GESTURE_E, "EAST", 0, "East", ""},
-    {EVT_GESTURE_SE, "SOUTH_EAST", 0, "South-East", ""},
-    {EVT_GESTURE_S, "SOUTH", 0, "South", ""},
-    {EVT_GESTURE_SW, "SOUTH_WEST", 0, "South-West", ""},
-    {EVT_GESTURE_W, "WEST", 0, "West", ""},
-    {EVT_GESTURE_NW, "NORTH_WEST", 0, "North-West", ""},
+    {KM_DIRECTION_N, "NORTH", 0, "North", ""},
+    {KM_DIRECTION_NE, "NORTH_EAST", 0, "North-East", ""},
+    {KM_DIRECTION_E, "EAST", 0, "East", ""},
+    {KM_DIRECTION_SE, "SOUTH_EAST", 0, "South-East", ""},
+    {KM_DIRECTION_S, "SOUTH", 0, "South", ""},
+    {KM_DIRECTION_SW, "SOUTH_WEST", 0, "South-West", ""},
+    {KM_DIRECTION_W, "WEST", 0, "West", ""},
+    {KM_DIRECTION_NW, "NORTH_WEST", 0, "North-West", ""},
     {0, NULL, 0, NULL, NULL},
 };
 
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index b2e214782f0..c4858a8a1fa 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -225,35 +225,68 @@ typedef enum eOperatorPropTags {
 } eOperatorPropTags;
 #define OP_PROP_TAG_ADVANCED ((eOperatorPropTags)OP_PROP_TAG_ADVANCED)
 
-/* ************** wmKeyMap ************************ */
-
-/* modifier */
-#define KM_SHIFT 1
-#define KM_CTRL 2
-#define KM_ALT 4
-#define KM_OSKEY 8
-
-/* Used for key-map item creation function arguments (never stored in DNA). */
-#define KM_SHIFT_ANY 16
-#define KM_CTRL_ANY 32
-#define KM_ALT_ANY 64
-#define KM_OSKEY_ANY 128
-
-/* KM_MOD_ flags for `wmKeyMapItem` and `wmEvent.alt/shift/oskey/ctrl`. */
-/* note that KM_ANY and KM_NOTHING are used with these defines too */
+/* -------------------------------------------------------------------- */
+/** \name #wmKeyMapItem
+ * \{ */
+
+/**
+ * Modifier keys, not actually used for #wmKeyMapItem (never stored in DNA), used for:
+ * - #wmEvent.modifier without the `KM_*_ANY` flags.
+ * - #WM_keymap_add_item & #WM_modalkeymap_add_item
+ */
+enum {
+  KM_SHIFT = (1 << 0),
+  KM_CTRL = (1 << 1),
+  KM_ALT = (1 << 2),
+  KM_OSKEY = (1 << 3),
+
+  /* Used for key-map item creation function arguments. */
+  KM_SHIFT_ANY = (1 << 4),
+  KM_CTRL_ANY = (1 << 5),
+  KM_ALT_ANY = (1 << 6),
+  KM_OSKEY_ANY = (1 << 7),
+};
+
+/* `KM_MOD_*` flags for #wmKeyMapItem and `wmEvent.alt/shift/oskey/ctrl`. */
+/* Note that #KM_ANY and #KM_NOTHING are used with these defines too. */
 #define KM_MOD_HELD 1
 
-/* type: defined in wm_event_types.c */
-#define KM_TEXTINPUT -2
-
-/* val */
-#define KM_ANY -1
-#define KM_NOTHING 0
-#define KM_PRESS 1
-#define KM_RELEASE 2
-#define KM_CLICK 3
-#define KM_DBL_CLICK 4
-#define KM_CLICK_DRAG 5
+/**
+ * #wmKeyMapItem.type
+ * NOTE: most types are defined in `wm_event_types.h`.
+ */
+enum {
+  KM_TEXTINPUT = -2,
+};
+
+/** #wmKeyMapItem.val */
+enum {
+  KM_ANY = -1,
+  KM_NOTHING = 0,
+  KM_PRESS = 1,
+  KM_RELEASE = 2,
+  KM_CLICK = 3,
+  KM_DBL_CLICK = 4,
+  KM_CLICK_DRAG = 5,
+};
+
+/**
+ * #wmKeyMapItem.direction
+ *
+ * Value of tweaks and line gestures. #KM_ANY (-1) works for this case too.
+ */
+enum {
+  KM_DIRECTION_N = 1,
+  KM_DIRECTION_NE = 2,
+  KM_DIRECTION_E = 3,
+  KM_DIRECTION_SE = 4,
+  KM_DIRECTION_S = 5,
+  KM_DIRECTION_SW = 6,
+  KM_DIRECTION_W = 7,
+  KM_DIRECTION_NW = 8,
+};
+
+/** \} */
 
 /* ************** UI Handler ***************** */
 
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index e56d3fb3886..ee13e1832ed 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -232,28 +232,28 @@ int WM_event_drag_direction(const wmEvent *event)
   };
 
   int theta = round_fl_to_int(4.0f * atan2f((float)delta[1], (float)delta[0]) / (float)M_PI);
-  int val = EVT_GESTURE_W;
+  int val = KM_DIRECTION_W;
 
   if (theta == 0) {
-    val = EVT_GESTURE_E;
+    val = KM_DIRECTION_E;
   }
   else if (theta == 1) {
-    val = EVT_GESTURE_NE;
+    val = KM_DIRECTION_NE;
   }
   else if (theta == 2) {
-    val = EVT_GESTURE_N;
+    val = KM_DIRECTION_N;
   }
   else if (theta == 3) {
-    val = EVT_GESTURE_NW;
+    val = KM_DIRECTION_NW;
   }
   else if (theta == -1) {
-    val = EVT_GESTURE_SE;
+    val = KM_DIRECTION_SE;
   }
   else if (theta == -2) {
-    val = EVT_GESTURE_S;
+    val = KM_DIRECTION_S;
   }
   else if (theta == -3) {
-    val = EVT_GESTURE_SW;
+    val = KM_DIRECTION_SW;
   }
 
 #if 0
diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h
index 1e907578e52..d5c8c5022cc 100644
--- a/source/blender/windowmanager/wm_event_types.h
+++ b/source/blender/windowmanager/wm_event_types.h
@@ -429,18 +429,6 @@ bool WM_event_type_mask_test(int event_type, enum eEventType_Mask mask);
  * \{ */
 
 /* Gestures */
-/* NOTE: these values are saved in keymap files, do not change them but just add new ones */
-enum {
-  /* Value of tweaks and line gestures. #KM_ANY (-1) works for this case too. */
-  EVT_GESTURE_N = 1,
-  EVT_GESTURE_NE = 2,
-  EVT_GESTURE_E = 3,
-  EVT_GESTURE_SE = 4,
-  EVT_GESTURE_S = 5,
-  EVT_GESTURE_SW = 6,
-  EVT_GESTURE_W = 7,
-  EVT_GESTURE_NW = 8,
-};
 
 /* File select */
 enum {
-- 
cgit v1.2.3


From f8577db05dfc5140049d9a8b21a774cefea0bf79 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 18:28:15 +1100
Subject: Docs: document the use_axis_view argumeht to view_roll_angle

Also remove event print left in by accident.
---
 source/blender/editors/space_view3d/view3d_navigate_roll.c | 5 +++++
 source/blender/windowmanager/intern/wm_event_system.c      | 2 --
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/source/blender/editors/space_view3d/view3d_navigate_roll.c b/source/blender/editors/space_view3d/view3d_navigate_roll.c
index 8d01f6b591b..9c070fb0341 100644
--- a/source/blender/editors/space_view3d/view3d_navigate_roll.c
+++ b/source/blender/editors/space_view3d/view3d_navigate_roll.c
@@ -24,6 +24,11 @@
 /** \name View Roll Operator
  * \{ */
 
+/**
+ * \param use_axis_view: When true, keep axis-aligned orthographic views
+ * (when rotating in 90 degree increments). While this may seem obscure some NDOF
+ * devices have key shortcuts to do this (see #NDOF_BUTTON_ROLL_CW & #NDOF_BUTTON_ROLL_CCW).
+ */
 static void view_roll_angle(ARegion *region,
                             float quat[4],
                             const float orig_quat[4],
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 017af86e401..08d842e198a 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3185,8 +3185,6 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
 
         CLOG_INFO(WM_LOG_HANDLERS, 1, "handling PRESS_DRAG");
 
-        WM_event_print(event);
-
         action |= wm_handlers_do_intern(C, win, event, handlers);
 
         event->direction = 0;
-- 
cgit v1.2.3


From 5086913b28d2c0d66167e6945edf1207f540d9e2 Mon Sep 17 00:00:00 2001
From: Jeroen Bakker 
Date: Wed, 2 Mar 2022 11:38:39 +0100
Subject: Fix T96116: Image editor not updated when adding new tile.

---
 source/blender/blenkernel/intern/image.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c
index 487a018b9b7..5a9bb9495b8 100644
--- a/source/blender/blenkernel/intern/image.c
+++ b/source/blender/blenkernel/intern/image.c
@@ -3626,6 +3626,7 @@ static void image_free_tile(Image *ima, ImageTile *tile)
       }
     }
   }
+  BKE_image_partial_update_mark_full_update(ima);
 
   if (BKE_image_is_multiview(ima)) {
     const int totviews = BLI_listbase_count(&ima->views);
@@ -3966,6 +3967,7 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la
       }
     }
   }
+  BKE_image_partial_update_mark_full_update(ima);
 
   return tile;
 }
@@ -4031,6 +4033,7 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu
       }
     }
   }
+  BKE_image_partial_update_mark_full_update(ima);
 }
 
 static int tile_sort_cb(const void *a, const void *b)
-- 
cgit v1.2.3


From 383a6ee78c111e3e1fa0be464d5493f7941fe67c Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Wed, 2 Mar 2022 11:03:58 +0100
Subject: Fix T94609: geometry nodes always re-evaluated with image texture
 nodes

previously_visible_components_mask was not preserved for Image ID nodes, which
meant it was always detected as newly visible and tagged to be updated, which
in turn caused the geometry nodes using it to be always updated also.

Reviewed By: sergey, JacquesLucke

Maniphest Tasks: T94609

Differential Revision: https://developer.blender.org/D14217
---
 source/blender/depsgraph/intern/builder/deg_builder_nodes.cc | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
index 79d674e8415..9c842d2030c 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
@@ -351,16 +351,10 @@ void DepsgraphNodeBuilder::begin_build()
      * same as id_orig. Additionally, such ID might have been removed, which makes the check
      * for whether id_cow is expanded to access freed memory. In order to deal with this we
      * check whether CoW is needed based on a scalar value which does not lead to access of
-     * possibly deleted memory.
-     * Additionally, this saves some space in the map by skipping mapping for datablocks which
-     * do not need CoW, */
-    if (!deg_copy_on_write_is_needed(id_node->id_type)) {
-      id_node->id_cow = nullptr;
-      continue;
-    }
-
+     * possibly deleted memory. */
     IDInfo *id_info = (IDInfo *)MEM_mallocN(sizeof(IDInfo), "depsgraph id info");
-    if (deg_copy_on_write_is_expanded(id_node->id_cow) && id_node->id_orig != id_node->id_cow) {
+    if (deg_copy_on_write_is_needed(id_node->id_type) &&
+        deg_copy_on_write_is_expanded(id_node->id_cow) && id_node->id_orig != id_node->id_cow) {
       id_info->id_cow = id_node->id_cow;
     }
     else {
-- 
cgit v1.2.3


From 1747269ea5713f23f57b3a5dbb68f71ef05a0410 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 21:40:09 +1100
Subject: Cleanup: misleading code from aa71414dfca7f301e101cce3e72551e7529290

This branch was previously run when the action had been handled,
since action checks were removed it was running. This assignment
does nothing but shouldn't be kept.
---
 source/blender/windowmanager/intern/wm_event_system.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 08d842e198a..c6b2e81d121 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3197,9 +3197,6 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
         win->event_queue_check_click = false;
       }
     }
-    else {
-      win->event_queue_check_drag = false;
-    }
   }
   else if (ISMOUSE_BUTTON(event->type) || ISKEYBOARD(event->type)) {
     /* All events that don't set wmEvent.prev_type must be ignored. */
-- 
cgit v1.2.3


From 102644cb8cbb8b21e55643cebe2ed364885023a6 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 21:43:22 +1100
Subject: Fix releasing modifier keys suppressing drag events

Always use event.prev_click_type since this is never set on key-release,
which could still interrupt dragging (box selecting for example).
---
 source/blender/windowmanager/intern/wm_event_system.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index c6b2e81d121..375cc4e785e 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -3178,7 +3178,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
 
         copy_v2_v2_int(event->xy, event->prev_click_xy);
         event->val = KM_CLICK_DRAG;
-        event->type = event->prev_type;
+        event->type = event->prev_click_type;
         event->modifier = event->prev_click_modifier;
         event->keymodifier = event->prev_click_keymodifier;
         event->direction = direction;
@@ -3215,7 +3215,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
       }
       else if (event->val == KM_RELEASE) {
         if (win->event_queue_check_drag) {
-          if ((event->prev_type != event->type) &&
+          if ((event->prev_click_type != event->type) &&
               (ISKEYMODIFIER(event->type) || (event->type == event->prev_click_keymodifier))) {
             /* Support releasing modifier keys without canceling the drag event, see T89989.
              * NOTE: this logic is replicated for tweak gestures. */
@@ -3226,7 +3226,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
         }
       }
 
-      if (event->prev_type == event->type) {
+      if (event->prev_click_type == event->type) {
 
         if (event->val == KM_RELEASE) {
           if (event->prev_val == KM_PRESS) {
-- 
cgit v1.2.3


From ccf4001458847eb871a5d3cc5b871a62ca22ab4f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 21:59:14 +1100
Subject: Fix modal item open/close in the outliner since removing tweak events

Correct check for left-mouse event which assumed the alternative was
a tweak event.

Also remove print added by accident.
---
 source/blender/editors/space_outliner/outliner_edit.cc | 2 +-
 source/blender/windowmanager/intern/wm_operators.c     | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc
index a6ac2a5a1f3..6916f5fe502 100644
--- a/source/blender/editors/space_outliner/outliner_edit.cc
+++ b/source/blender/editors/space_outliner/outliner_edit.cc
@@ -240,7 +240,7 @@ static int outliner_item_openclose_invoke(bContext *C, wmOperator *op, const wmE
     outliner_tag_redraw_avoid_rebuild_on_open_change(space_outliner, region);
 
     /* Only toggle once for single click toggling */
-    if (event->type == LEFTMOUSE) {
+    if ((event->type == LEFTMOUSE) && (event->val != KM_CLICK_DRAG)) {
       return OPERATOR_FINISHED;
     }
 
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index 6e0bf911555..7e680af4537 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -913,7 +913,6 @@ int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
       OPERATOR_RETVAL_CHECK(ret_value);
       op->customdata = POINTER_FROM_INT((int)event->type);
       if (ret_value & OPERATOR_RUNNING_MODAL) {
-        printf("Starting modal: %s\n", op->idname);
         WM_event_add_modal_handler(C, op);
       }
       return ret_value | OPERATOR_PASS_THROUGH;
-- 
cgit v1.2.3


From c2e8e68b6517b91eb14270707d7182c1727861c4 Mon Sep 17 00:00:00 2001
From: Henrik Dick 
Date: Wed, 2 Mar 2022 12:14:22 +0100
Subject: Fix T95963: change thresholds in complex solidify

This fix contains two parts. There was one critical mistake where
order of two indices was wrong when removing constraint planes from
the array. The other changes are improvements to the used thresholds
to keep everything numerically stable.

Differential Revision: http://developer.blender.org/D14183
---
 .../modifiers/intern/MOD_solidify_nonmanifold.c    | 41 ++++++++++++----------
 1 file changed, 22 insertions(+), 19 deletions(-)

diff --git a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
index e4f7a7b5473..ff25c1afd49 100644
--- a/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
+++ b/source/blender/modifiers/intern/MOD_solidify_nonmanifold.c
@@ -1468,10 +1468,10 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                 for (uint k = 0; k < queue_index; k++) {
                   for (uint m = k + 1; m < queue_index; m++) {
                     float p = dot_v3v3(planes_queue[k], planes_queue[m]);
-                    if (p <= min_p + FLT_EPSILON) {
+                    if (p < min_p) {
                       min_p = p;
-                      min_n0 = m;
-                      min_n1 = k;
+                      min_n0 = k;
+                      min_n1 = m;
                     }
                   }
                 }
@@ -1484,18 +1484,13 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                   swap_v4_v4(planes_queue[min_n0], planes_queue[1]);
                 }
                 /* Find the third most important/different normal. */
-                min_p = 1;
-                min_n1 = 0;
-                float max_p = -1;
+                min_p = 1.0f;
+                min_n1 = 2;
+                float max_p = -1.0f;
                 for (uint k = 2; k < queue_index; k++) {
-                  max_p = -1;
-                  for (uint m = 0; m < 2; m++) {
-                    float p = dot_v3v3(planes_queue[m], planes_queue[k]);
-                    if (p > max_p + FLT_EPSILON) {
-                      max_p = p;
-                    }
-                  }
-                  if (max_p <= min_p + FLT_EPSILON) {
+                  max_p = max_ff(dot_v3v3(planes_queue[0], planes_queue[k]),
+                                 dot_v3v3(planes_queue[1], planes_queue[k]));
+                  if (max_p <= min_p) {
                     min_p = max_p;
                     min_n1 = k;
                   }
@@ -1503,7 +1498,7 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                 swap_v4_v4(planes_queue[min_n1], planes_queue[2]);
               }
               /* Remove/average duplicate normals in planes_queue. */
-              while (queue_index > 0) {
+              while (queue_index > 2) {
                 uint best_n0 = 0;
                 uint best_n1 = 0;
                 float best_p = -1.0f;
@@ -1515,12 +1510,14 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                     if (p > best_p + FLT_EPSILON || (p >= best_p && ofs_diff < best_ofs_diff)) {
                       best_p = p;
                       best_ofs_diff = ofs_diff;
-                      best_n0 = m;
-                      best_n1 = k;
+                      best_n0 = k;
+                      best_n1 = m;
                     }
                   }
                 }
-                if (best_p < 0.999f) {
+                /* Make sure there are no equal planes. This threshold is crucial for the
+                 * methods below to work without numerical issues. */
+                if (best_p < 0.98f) {
                   break;
                 }
                 add_v3_v3(planes_queue[best_n0], planes_queue[best_n1]);
@@ -1549,6 +1546,10 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                   for (int m = 0; m < size; m++) {
                     madd_v3_v3fl(mat[k], planes_queue[m], planes_queue[m][k]);
                   }
+                  /* Add a small epsilon to ensure the invert is going to work.
+                   * This addition makes the inverse more stable and the results
+                   * seem to get more precise. */
+                  mat[k][k] += 5e-5f;
                 }
                 /* NOTE: this matrix invert fails if there is less than 3 different normals. */
                 invert_m3(mat);
@@ -1597,7 +1598,9 @@ Mesh *MOD_solidify_nonmanifold_modifyMesh(ModifierData *md,
                 add_v3_v3v3(nor, planes_queue[0], planes_queue[1]);
                 if (size == 3) {
                   d = dot_v3v3(planes_queue[2], move_nor);
-                  if (LIKELY(fabsf(d) > FLT_EPSILON)) {
+                  /* The following threshold ignores the third plane if it is almost orthogonal to
+                   * the still free direction. */
+                  if (fabsf(d) > 0.02f) {
                     float tmp[3];
                     madd_v3_v3v3fl(tmp, nor, planes_queue[2], -planes_queue[2][3]);
                     mul_v3_v3fl(tmp, move_nor, dot_v3v3(planes_queue[2], tmp) / d);
-- 
cgit v1.2.3


From 721335553ccb5ce4f7a374b958b7d65befa319df Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Wed, 2 Mar 2022 21:59:14 +1100
Subject: Add Object Tool: use the drag-start location for initial placement

---
 source/blender/editors/space_view3d/view3d_placement.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/source/blender/editors/space_view3d/view3d_placement.c b/source/blender/editors/space_view3d/view3d_placement.c
index 06b848571d8..98fb914cda9 100644
--- a/source/blender/editors/space_view3d/view3d_placement.c
+++ b/source/blender/editors/space_view3d/view3d_placement.c
@@ -727,6 +727,17 @@ static void view3d_interactive_add_begin(bContext *C, wmOperator *op, const wmEv
   V3DSnapCursorState *snap_state_new = ED_view3d_cursor_snap_active();
   if (snap_state_new) {
     ipd->snap_state = snap_state = snap_state_new;
+
+    /* For drag events, update the location since it will be set from the drag-start.
+     * This is needed as cursor-drawing doesn't deal with drag events and will use
+     * the current cursor location instead of the drag-start. */
+    if (event->val == KM_CLICK_DRAG) {
+      /* Set this flag so snapping always updated. */
+      int flag_orig = snap_state_new->flag;
+      snap_state_new->flag |= V3D_SNAPCURSOR_TOGGLE_ALWAYS_TRUE;
+      ED_view3d_cursor_snap_data_get(snap_state_new, C, event->mval[0], event->mval[1]);
+      snap_state_new->flag = flag_orig;
+    }
   }
 
   snap_state->draw_point = true;
-- 
cgit v1.2.3


From c23ec04b4e30f300a670f1cb1dc882e0608d09ad Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Wed, 2 Mar 2022 12:51:21 +0100
Subject: BLI: add scoped-defer utility to add RAII-like behavior to C types

This utility is useful when using C types that own some resource in
a C++ file. It mainly helps in functions that have multiple return
statements, but also simplifies code by moving construction and
destruction closer together.

Differential Revision: https://developer.blender.org/D14215
---
 source/blender/blenlib/BLI_memory_utils.hh         | 28 ++++++++++++++++++++++
 .../blender/blenlib/tests/BLI_memory_utils_test.cc | 25 +++++++++++++++++++
 .../nodes/node_geo_distribute_points_on_faces.cc   |  3 +--
 .../nodes/geometry/nodes/node_geo_raycast.cc       |  9 ++++---
 4 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/source/blender/blenlib/BLI_memory_utils.hh b/source/blender/blenlib/BLI_memory_utils.hh
index dd6b8463d84..a7cad5461b4 100644
--- a/source/blender/blenlib/BLI_memory_utils.hh
+++ b/source/blender/blenlib/BLI_memory_utils.hh
@@ -544,3 +544,31 @@ Container &move_assign_container(Container &dst, Container &&src) noexcept(
 }
 
 }  // namespace blender
+
+namespace blender::detail {
+
+template struct ScopedDeferHelper {
+  Func func;
+
+  ~ScopedDeferHelper()
+  {
+    func();
+  }
+};
+
+}  // namespace blender::detail
+
+#define BLI_SCOPED_DEFER_NAME1(a, b) a##b
+#define BLI_SCOPED_DEFER_NAME2(a, b) BLI_SCOPED_DEFER_NAME1(a, b)
+#define BLI_SCOPED_DEFER_NAME(a) BLI_SCOPED_DEFER_NAME2(_scoped_defer_##a##_, __LINE__)
+
+/**
+ * Execute the given function when the current scope ends. This can be used to cheaply implement
+ * some RAII-like behavior for C types that don't support it. Long term, the types we want to use
+ * this with should either be converted to C++ or get a proper C++ API. Until then, this function
+ * can help avoid common resource leakages.
+ */
+#define BLI_SCOPED_DEFER(function_to_defer) \
+  auto BLI_SCOPED_DEFER_NAME(func) = (function_to_defer); \
+  blender::detail::ScopedDeferHelper \
+      BLI_SCOPED_DEFER_NAME(helper){std::move(BLI_SCOPED_DEFER_NAME(func))};
diff --git a/source/blender/blenlib/tests/BLI_memory_utils_test.cc b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
index 993434ddeba..ab716e5d011 100644
--- a/source/blender/blenlib/tests/BLI_memory_utils_test.cc
+++ b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
@@ -176,4 +176,29 @@ static_assert(!is_same_any_v);
 static_assert(!is_same_any_v);
 static_assert(!is_same_any_v);
 
+TEST(memory_utils, ScopedDefer1)
+{
+  int a = 0;
+  {
+    BLI_SCOPED_DEFER([&]() { a -= 5; });
+    {
+      BLI_SCOPED_DEFER([&]() { a *= 10; });
+      a = 5;
+    }
+  }
+  EXPECT_EQ(a, 45);
+}
+
+TEST(memory_utils, ScopedDefer2)
+{
+  std::string s;
+  {
+    BLI_SCOPED_DEFER([&]() { s += "A"; });
+    BLI_SCOPED_DEFER([&]() { s += "B"; });
+    BLI_SCOPED_DEFER([&]() { s += "C"; });
+    BLI_SCOPED_DEFER([&]() { s += "D"; });
+  }
+  EXPECT_EQ(s, "DCBA");
+}
+
 }  // namespace blender::tests
diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
index 51932d341bc..bdd4d74fe4b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
@@ -152,6 +152,7 @@ BLI_NOINLINE static void update_elimination_mask_for_close_points(
   }
 
   KDTree_3d *kdtree = build_kdtree(positions);
+  BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); });
 
   for (const int i : positions.index_range()) {
     if (elimination_mask[i]) {
@@ -176,8 +177,6 @@ BLI_NOINLINE static void update_elimination_mask_for_close_points(
         },
         &callback_data);
   }
-
-  BLI_kdtree_3d_free(kdtree);
 }
 
 BLI_NOINLINE static void update_elimination_mask_based_on_density_factors(
diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
index 1797364ad72..231ef547a8b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc
@@ -141,10 +141,13 @@ static void raycast_to_mesh(IndexMask mask,
 {
   BVHTreeFromMesh tree_data;
   BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4);
+  BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); });
+
   if (tree_data.tree == nullptr) {
-    free_bvhtree_from_mesh(&tree_data);
     return;
   }
+  /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */
+  BLI_assert(tree_data.cached);
 
   for (const int i : mask) {
     const float ray_length = ray_lengths[i];
@@ -197,10 +200,6 @@ static void raycast_to_mesh(IndexMask mask,
       }
     }
   }
-
-  /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */
-  BLI_assert(tree_data.cached);
-  free_bvhtree_from_mesh(&tree_data);
 }
 
 class RaycastFunction : public fn::MultiFunction {
-- 
cgit v1.2.3