Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Lucke <jacques@blender.org>2021-03-03 14:36:51 +0300
committerJacques Lucke <jacques@blender.org>2021-03-03 14:36:51 +0300
commitcf6208382eb6142085e7ce3052b177344ea156cc (patch)
tree928ebef621dea175b27c9259744ec1972d91763a
parentb837933b17ec1b3ec9c375eff000c1d55c438bab (diff)
parent3f716bb626fd7d5c3edeae127407e8b361f53e7d (diff)
Merge branch 'master' into temp-spreadsheet-editor
-rw-r--r--build_files/build_environment/cmake/python.cmake4
-rw-r--r--intern/ghost/intern/GHOST_SystemCocoa.mm2
-rw-r--r--release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py1
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py49
-rw-r--r--source/blender/blenkernel/BKE_colortools.h1
-rw-r--r--source/blender/blenkernel/BKE_node_ui_storage.hh3
-rw-r--r--source/blender/blenkernel/BKE_paint.h12
-rw-r--r--source/blender/blenkernel/intern/action.c2
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc2
-rw-r--r--source/blender/blenkernel/intern/colortools.c6
-rw-r--r--source/blender/blenkernel/intern/cryptomatte.cc26
-rw-r--r--source/blender/blenkernel/intern/cryptomatte_test.cc13
-rw-r--r--source/blender/blenkernel/intern/mesh_boolean_convert.cc71
-rw-r--r--source/blender/blenkernel/intern/node.cc6
-rw-r--r--source/blender/blenkernel/intern/node_ui_storage.cc16
-rw-r--r--source/blender/blenkernel/intern/paint.c13
-rw-r--r--source/blender/blenloader/intern/versioning_290.c37
-rw-r--r--source/blender/bmesh/intern/bmesh_marking.c2
-rw-r--r--source/blender/compositor/intern/COM_MetaData.cpp37
-rw-r--r--source/blender/compositor/intern/COM_MetaData.h16
-rw-r--r--source/blender/compositor/nodes/COM_ImageNode.cpp18
-rw-r--r--source/blender/compositor/nodes/COM_ImageNode.h5
-rw-r--r--source/blender/compositor/operations/COM_MultilayerImageOperation.cpp34
-rw-r--r--source/blender/compositor/operations/COM_MultilayerImageOperation.h19
-rw-r--r--source/blender/compositor/operations/COM_OutputFileOperation.cpp1
-rw-r--r--source/blender/compositor/operations/COM_RenderLayersProg.cpp63
-rw-r--r--source/blender/draw/engines/eevee/eevee_depth_of_field.c7
-rw-r--r--source/blender/draw/engines/eevee/eevee_lightcache.c11
-rw-r--r--source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl8
-rw-r--r--source/blender/draw/intern/draw_manager.h4
-rw-r--r--source/blender/draw/intern/draw_manager_profiling.c1
-rw-r--r--source/blender/editors/armature/armature_relations.c9
-rw-r--r--source/blender/editors/gpencil/gpencil_data.c2
-rw-r--r--source/blender/editors/include/UI_interface.h7
-rw-r--r--source/blender/editors/interface/interface.c11
-rw-r--r--source/blender/editors/interface/interface_handlers.c6
-rw-r--r--source/blender/editors/interface/interface_intern.h9
-rw-r--r--source/blender/editors/interface/interface_region_search.c17
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.c3
-rw-r--r--source/blender/editors/interface/interface_template_search_operator.c3
-rw-r--r--source/blender/editors/interface/interface_templates.c10
-rw-r--r--source/blender/editors/interface/interface_utils.c5
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt2
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c10
-rw-r--r--source/blender/editors/sculpt_paint/paint_ops.c3
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c12
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_boundary.c8
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_expand.c2258
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_geodesic.c360
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h171
-rw-r--r--source/blender/editors/space_buttons/buttons_texture.c251
-rw-r--r--source/blender/editors/space_node/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_node/drawnode.c10
-rw-r--r--source/blender/editors/space_node/node_geometry_attribute_search.cc151
-rw-r--r--source/blender/editors/space_node/node_intern.h11
-rw-r--r--source/blender/editors/space_node/node_relationships.c22
-rw-r--r--source/blender/editors/space_node/node_select.c3
-rw-r--r--source/blender/editors/space_outliner/outliner_collections.c4
-rw-r--r--source/blender/editors/space_outliner/outliner_tools.c3
-rw-r--r--source/blender/editors/space_sequencer/sequencer_add.c579
-rw-r--r--source/blender/editors/space_sequencer/sequencer_edit.c209
-rw-r--r--source/blender/editors/space_sequencer/sequencer_intern.h2
-rw-r--r--source/blender/editors/space_sequencer/sequencer_select.c25
-rw-r--r--source/blender/editors/transform/transform_constraints.c3
-rw-r--r--source/blender/editors/transform/transform_convert_sequencer.c52
-rw-r--r--source/blender/editors/uvedit/uvedit_select.c5
-rw-r--r--source/blender/imbuf/intern/anim_movie.c2
-rw-r--r--source/blender/makesdna/DNA_windowmanager_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_ID.c3
-rw-r--r--source/blender/makesrna/intern/rna_animviz.c4
-rw-r--r--source/blender/makesrna/intern/rna_armature.c9
-rw-r--r--source/blender/makesrna/intern/rna_brush.c3
-rw-r--r--source/blender/makesrna/intern/rna_color.c5
-rw-r--r--source/blender/makesrna/intern/rna_fluid.c2
-rw-r--r--source/blender/makesrna/intern/rna_gpencil.c6
-rw-r--r--source/blender/makesrna/intern/rna_lattice.c2
-rw-r--r--source/blender/makesrna/intern/rna_light.c2
-rw-r--r--source/blender/makesrna/intern/rna_material.c2
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c3
-rw-r--r--source/blender/makesrna/intern/rna_movieclip.c2
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c2
-rw-r--r--source/blender/makesrna/intern/rna_object.c5
-rw-r--r--source/blender/makesrna/intern/rna_particle.c16
-rw-r--r--source/blender/makesrna/intern/rna_pose.c21
-rw-r--r--source/blender/makesrna/intern/rna_scene.c4
-rw-r--r--source/blender/makesrna/intern/rna_sequencer_api.c198
-rw-r--r--source/blender/makesrna/intern/rna_space.c44
-rw-r--r--source/blender/makesrna/intern/rna_ui.c2
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c23
-rw-r--r--source/blender/makesrna/intern/rna_volume.c2
-rw-r--r--source/blender/makesrna/intern/rna_wm_gizmo.c8
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc7
-rw-r--r--source/blender/nodes/NOD_node_tree_ref.hh53
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_scale.cc3
-rw-r--r--source/blender/nodes/intern/node_tree_ref.cc13
-rw-r--r--source/blender/python/intern/bpy_rna.c79
-rw-r--r--source/blender/sequencer/SEQ_add.h95
-rw-r--r--source/blender/sequencer/SEQ_sequencer.h4
-rw-r--r--source/blender/sequencer/intern/render.c29
-rw-r--r--source/blender/sequencer/intern/sequencer.c52
-rw-r--r--source/blender/sequencer/intern/strip_add.c498
-rw-r--r--source/blender/sequencer/intern/strip_time.c22
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c6
103 files changed, 4692 insertions, 1268 deletions
diff --git a/build_files/build_environment/cmake/python.cmake b/build_files/build_environment/cmake/python.cmake
index fa1498dda82..7e718d22805 100644
--- a/build_files/build_environment/cmake/python.cmake
+++ b/build_files/build_environment/cmake/python.cmake
@@ -81,8 +81,8 @@ else()
# Link against zlib statically (Unix). Avoid rpath issues (macOS).
set(PYTHON_PATCH ${PATCH_CMD} --verbose -p1 -d ${BUILD_DIR}/python/src/external_python < ${PATCH_DIR}/python_unix.diff)
set(PYTHON_CONFIGURE_EXTRA_ARGS "--with-openssl=${LIBDIR}/ssl")
- set(PYTHON_CFLAGS "-I${LIBDIR}/sqlite/include -I${LIBDIR}/bzip2/include -I${LIBDIR}/lzma/include -I${LIBDIR}/zlib/include")
- set(PYTHON_LDFLAGS "-L${LIBDIR}/ffi/lib -L${LIBDIR}/sqlite/lib -L${LIBDIR}/bzip2/lib -L${LIBDIR}/lzma/lib -L${LIBDIR}/zlib/lib")
+ set(PYTHON_CFLAGS "-I${LIBDIR}/sqlite/include -I${LIBDIR}/bzip2/include -I${LIBDIR}/lzma/include -I${LIBDIR}/zlib/include ${PLATFORM_CFLAGS}")
+ set(PYTHON_LDFLAGS "-L${LIBDIR}/ffi/lib -L${LIBDIR}/sqlite/lib -L${LIBDIR}/bzip2/lib -L${LIBDIR}/lzma/lib -L${LIBDIR}/zlib/lib ${PLATFORM_LDFLAGS}")
set(PYTHON_CONFIGURE_EXTRA_ENV
export CFLAGS=${PYTHON_CFLAGS} &&
export CPPFLAGS=${PYTHON_CFLAGS} &&
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm
index f42d4af109a..3b20c95c954 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemCocoa.mm
@@ -80,7 +80,7 @@ static GHOST_TButtonMask convertButton(int button)
}
/**
- * Converts Mac rawkey codes (same for Cocoa & Carbon)
+ * Converts Mac raw-key codes (same for Cocoa & Carbon)
* into GHOST key codes
* \param rawCode: The raw physical key code
* \param recvChar: the character ignoring modifiers (except for shift)
diff --git a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
index b5127784c1e..0784a91d174 100644
--- a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
+++ b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
@@ -114,6 +114,7 @@ _km_hierarchy = [
('Custom Normals Modal Map', 'EMPTY', 'WINDOW', []),
('Bevel Modal Map', 'EMPTY', 'WINDOW', []),
('Paint Stroke Modal', 'EMPTY', 'WINDOW', []),
+ ('Sculpt Expand Modal', 'EMPTY', 'WINDOW', []),
('Paint Curve', 'EMPTY', 'WINDOW', []),
('Object Non-modal', 'EMPTY', 'WINDOW', []), # mode change
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 69582849308..949cfcae2b1 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -4470,6 +4470,15 @@ def km_sculpt(params):
{"properties": [("mode", 'INVERT')]}),
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
+ # Expand
+ ("sculpt.expand", {"type": 'A', "value": 'PRESS', "shift": True},
+ {"properties": [("target", "MASK"), ("falloff_type", "GEODESIC"), ("invert", True)]}),
+ ("sculpt.expand", {"type": 'A', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties": [("target", "MASK"), ("falloff_type", "NORMALS"), ("invert", False)]}),
+ ("sculpt.expand", {"type": 'W', "value": 'PRESS', "shift": True},
+ {"properties": [("target", "FACE_SETS"), ("falloff_type", "GEODESIC"), ("invert", False), ("use_modify_active", False)]}),
+ ("sculpt.expand", {"type": 'W', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties": [("target", "FACE_SETS"), ("falloff_type", "BOUNDARY_FACE_SET"),("invert", False), ("use_modify_active", True)]}),
# Partial Visibility Show/hide
("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS'},
{"properties": [("mode", 'TOGGLE')]}),
@@ -4477,8 +4486,6 @@ def km_sculpt(params):
{"properties": [("mode", 'HIDE_ACTIVE')]}),
("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'SHOW_ALL')]}),
- ("sculpt.mask_expand", {"type": 'W', "value": 'PRESS', "shift": True},
- {"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", False), ("smooth_iterations", 0), ("create_face_set", True)]}),
("sculpt.face_set_edit", {"type": 'W', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'GROW')]}),
("sculpt.face_set_edit", {"type": 'W', "value": 'PRESS', "ctrl": True, "alt": True},
@@ -4499,10 +4506,6 @@ def km_sculpt(params):
("paint.mask_lasso_gesture", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True}, None),
("wm.context_toggle", {"type": 'M', "value": 'PRESS', "ctrl": True},
{"properties": [("data_path", 'scene.tool_settings.sculpt.show_mask')]}),
- ("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True},
- {"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", True), ("smooth_iterations", 2), ("create_face_set", False)]}),
- ("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True, 'alt': True},
- {"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0), ("create_face_set", False)]}),
# Dynamic topology
("sculpt.dynamic_topology_toggle", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
("sculpt.dyntopo_detail_size_edit", {"type": 'D', "value": 'PRESS', "shift": True}, None),
@@ -5571,6 +5574,39 @@ def km_paint_stroke_modal(_params):
return keymap
+def km_sculpt_expand_modal(_params):
+ items = []
+ keymap = (
+ "Sculpt Expand Modal",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True},
+ {"items": items},
+ )
+
+ items.extend([
+ ("CANCEL", {"type": 'ESC', "value": 'PRESS', "any": True}, None),
+ ("CANCEL", {"type": 'RIGHTMOUSE', "value": 'PRESS', "any": True}, None),
+ ("CONFIRM", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None),
+ ("INVERT", {"type": 'F', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("PRESERVE", {"type": 'E', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("GRADIENT", {"type": 'G', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("RECURSION_STEP_GEODESIC", {"type": 'R', "value": 'PRESS', "repeat" : False}, None),
+ ("RECURSION_STEP_TOPOLOGY", {"type": 'R', "value": 'PRESS', "alt" : True ,"any": True, "repeat" : False}, None),
+ ("MOVE_TOGGLE", {"type": 'SPACE', "value": 'ANY', "any": True, "repeat" : False}, None),
+ ("FALLOFF_GEODESICS", {"type": 'ONE', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("FALLOFF_TOPOLOGY", {"type": 'TWO', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("FALLOFF_TOPOLOGY_DIAGONALS", {"type": 'THREE', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("FALLOFF_SPHERICAL", {"type": 'FOUR', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("SNAP_TOGGLE", {"type": 'LEFT_CTRL', "value": 'ANY', "repeat" : False}, None),
+ ("LOOP_COUNT_INCREASE", {"type": 'W', "value": 'PRESS', "any": True, "repeat" : True}, None),
+ ("LOOP_COUNT_DECREASE", {"type": 'Q', "value": 'PRESS', "any": True, "repeat" : True}, None),
+ ("BRUSH_GRADIENT_TOGGLE", {"type": 'B', "value": 'PRESS', "any": True, "repeat" : False}, None),
+ ("TEXTURE_DISTORTION_INCREASE", {"type": 'Y', "value": 'PRESS', "any": False, "repeat" : True}, None),
+ ("TEXTURE_DISTORTION_DECREASE", {"type": 'T', "value": 'PRESS', "any": False, "repeat" : True}, None),
+ ])
+ return keymap
+
+
+
# Fallback for gizmos that don't have custom a custom key-map.
def km_generic_gizmo(_params):
@@ -7085,6 +7121,7 @@ def generate_keymaps(params=None):
km_view3d_zoom_modal(params),
km_view3d_dolly_modal(params),
km_paint_stroke_modal(params),
+ km_sculpt_expand_modal(params),
# Gizmos.
km_generic_gizmo(params),
diff --git a/source/blender/blenkernel/BKE_colortools.h b/source/blender/blenkernel/BKE_colortools.h
index 3631feb5071..ec2262d4f60 100644
--- a/source/blender/blenkernel/BKE_colortools.h
+++ b/source/blender/blenkernel/BKE_colortools.h
@@ -59,6 +59,7 @@ enum {
CURVEMAP_SLOPE_POS_NEG = 2,
};
+void BKE_curvemapping_reset_view(struct CurveMapping *cumap);
void BKE_curvemap_reset(struct CurveMap *cuma, const struct rctf *clipr, int preset, int slope);
void BKE_curvemap_remove(struct CurveMap *cuma, const short flag);
bool BKE_curvemap_remove_point(struct CurveMap *cuma, struct CurveMapPoint *cmp);
diff --git a/source/blender/blenkernel/BKE_node_ui_storage.hh b/source/blender/blenkernel/BKE_node_ui_storage.hh
index 231eb11d473..a49ff988272 100644
--- a/source/blender/blenkernel/BKE_node_ui_storage.hh
+++ b/source/blender/blenkernel/BKE_node_ui_storage.hh
@@ -16,6 +16,8 @@
#pragma once
+#include <mutex>
+
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_session_uuid.h"
@@ -82,6 +84,7 @@ struct NodeUIStorage {
struct NodeTreeUIStorage {
blender::Map<NodeTreeEvaluationContext, blender::Map<std::string, NodeUIStorage>> context_map;
+ std::mutex context_map_mutex;
};
const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 4369f332c35..228b52123f3 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -469,10 +469,19 @@ typedef struct SculptSession {
struct MPropCol *vcol;
float *vmask;
- /* Mesh connectivity */
+ /* Mesh connectivity maps. */
+ /* Vertices to adjacent polys. */
struct MeshElemMap *pmap;
int *pmap_mem;
+ /* Edges to adjacent polys. */
+ struct MeshElemMap *epmap;
+ int *epmap_mem;
+
+ /* Vertices to adjacent edges. */
+ struct MeshElemMap *vemap;
+ int *vemap_mem;
+
/* Mesh Face Sets */
/* Total number of polys of the base mesh. */
int totfaces;
@@ -508,6 +517,7 @@ typedef struct SculptSession {
struct StrokeCache *cache;
struct FilterCache *filter_cache;
+ struct ExpandCache *expand_cache;
/* Cursor data and active vertex for tools */
int active_vertex_index;
diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c
index 06b8bd5f0f2..f9c2a4e53ad 100644
--- a/source/blender/blenkernel/intern/action.c
+++ b/source/blender/blenkernel/intern/action.c
@@ -2002,7 +2002,7 @@ void BKE_pose_blend_read_lib(BlendLibReader *reader, Object *ob, bPose *pose)
IDP_BlendReadLib(reader, pchan->prop);
- BLO_read_id_address(reader, arm->id.lib, &pchan->custom);
+ BLO_read_id_address(reader, ob->id.lib, &pchan->custom);
if (UNLIKELY(pchan->bone == NULL)) {
rebuild = true;
}
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index 8974190d0e3..aeb7fba47e8 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -1886,7 +1886,7 @@ OutputAttributePtr GeometryComponent::attribute_try_get_for_output(const StringR
if (!attribute) {
this->attribute_try_create(attribute_name, domain, data_type);
attribute = this->attribute_try_get_for_write(attribute_name);
- if (default_value != nullptr) {
+ if (attribute && default_value != nullptr) {
void *data = attribute->get_span_for_write_only().data();
cpp_type->fill_initialized(default_value, data, attribute->size());
attribute->apply_span();
diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c
index 3eb9fb6161d..44d9bd6b2d2 100644
--- a/source/blender/blenkernel/intern/colortools.c
+++ b/source/blender/blenkernel/intern/colortools.c
@@ -965,6 +965,12 @@ void BKE_curvemapping_changed_all(CurveMapping *cumap)
cumap->cur = cur;
}
+/* Reset the view for current curve. */
+void BKE_curvemapping_reset_view(CurveMapping *cumap)
+{
+ cumap->curr = cumap->clipr;
+}
+
/* table should be verified */
float BKE_curvemap_evaluateF(const CurveMapping *cumap, const CurveMap *cuma, float value)
{
diff --git a/source/blender/blenkernel/intern/cryptomatte.cc b/source/blender/blenkernel/intern/cryptomatte.cc
index a20c53ed270..9d9cace3a35 100644
--- a/source/blender/blenkernel/intern/cryptomatte.cc
+++ b/source/blender/blenkernel/intern/cryptomatte.cc
@@ -53,7 +53,7 @@ struct CryptomatteSession {
CryptomatteSession();
CryptomatteSession(const Main *bmain);
- CryptomatteSession(StampData *metadata);
+ CryptomatteSession(StampData *stamp_data);
blender::bke::cryptomatte::CryptomatteLayer &add_layer(std::string layer_name);
std::optional<std::string> operator[](float encoded_hash) const;
@@ -184,22 +184,30 @@ float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash)
char *BKE_cryptomatte_entries_to_matte_id(NodeCryptomatte *node_storage)
{
- DynStr *matte_id = BLI_dynstr_new();
+ std::stringstream ss;
+ ss.precision(9);
+
bool first = true;
LISTBASE_FOREACH (CryptomatteEntry *, entry, &node_storage->entries) {
if (!first) {
- BLI_dynstr_append(matte_id, ",");
+ ss << ',';
}
- if (BLI_strnlen(entry->name, sizeof(entry->name)) != 0) {
- BLI_dynstr_nappend(matte_id, entry->name, sizeof(entry->name));
+ blender::StringRef entry_name(entry->name, BLI_strnlen(entry->name, sizeof(entry->name)));
+ if (!entry_name.is_empty()) {
+ ss << entry_name;
}
else {
- BLI_dynstr_appendf(matte_id, "<%.9g>", entry->encoded_hash);
+ ss << '<' << std::scientific << entry->encoded_hash << '>';
}
first = false;
}
- char *result = BLI_dynstr_get_cstring(matte_id);
- BLI_dynstr_free(matte_id);
+
+ /* Convert result to C string. */
+ const std::string result_string = ss.str();
+ const char *c_str = result_string.c_str();
+ size_t result_len = result_string.size() + 1;
+ char *result = static_cast<char *>(MEM_mallocN(sizeof(char) * result_len, __func__));
+ memcpy(result, c_str, result_len);
return result;
}
@@ -492,7 +500,7 @@ std::unique_ptr<CryptomatteLayer> CryptomatteLayer::read_from_manifest(
blender::StringRefNull manifest)
{
std::unique_ptr<CryptomatteLayer> layer = std::make_unique<CryptomatteLayer>();
- blender::bke::cryptomatte::manifest::from_manifest(*layer.get(), manifest);
+ blender::bke::cryptomatte::manifest::from_manifest(*layer, manifest);
return layer;
}
diff --git a/source/blender/blenkernel/intern/cryptomatte_test.cc b/source/blender/blenkernel/intern/cryptomatte_test.cc
index d9be252d654..5481b97913c 100644
--- a/source/blender/blenkernel/intern/cryptomatte_test.cc
+++ b/source/blender/blenkernel/intern/cryptomatte_test.cc
@@ -21,6 +21,8 @@
#include "BKE_cryptomatte.hh"
#include "BKE_image.h"
+#include "DNA_node_types.h"
+
#include "RE_pipeline.h"
#include "MEM_guardedalloc.h"
@@ -176,4 +178,15 @@ TEST(cryptomatte, session_from_stamp_data)
BKE_cryptomatte_free(session);
}
+TEST(cryptomatte, T86026)
+{
+ NodeCryptomatte storage = {{0.0f}};
+ CryptomatteEntry entry = {nullptr};
+ BLI_addtail(&storage.entries, &entry);
+ entry.encoded_hash = 4.76190593e-07;
+ char *matte_id = BKE_cryptomatte_entries_to_matte_id(&storage);
+ EXPECT_STREQ("<4.761905927e-07>", matte_id);
+ MEM_freeN(matte_id);
+}
+
} // namespace blender::bke::cryptomatte::tests
diff --git a/source/blender/blenkernel/intern/mesh_boolean_convert.cc b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
index 299b1ff1c71..d9564f91a04 100644
--- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc
@@ -32,6 +32,7 @@
#include "BLI_alloca.h"
#include "BLI_float2.hh"
+#include "BLI_float4x4.hh"
#include "BLI_math.h"
#include "BLI_mesh_boolean.hh"
#include "BLI_mesh_intersect.hh"
@@ -50,12 +51,12 @@ constexpr int estimated_max_facelen = 100; /* Used for initial size of some Vect
* so this is a hack to clean up such matrices.
* Would be better to change the transformation code itself.
*/
-static void clean_obmat(float cleaned[4][4], const float mat[4][4])
+static void clean_obmat(float4x4 &cleaned, const float4x4 &mat)
{
const float fuzz = 1e-6f;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
- float f = mat[i][j];
+ float f = mat.values[i][j];
if (fabsf(f) <= fuzz) {
f = 0.0f;
}
@@ -65,17 +66,11 @@ static void clean_obmat(float cleaned[4][4], const float mat[4][4])
else if (fabsf(f + 1.0f) <= fuzz) {
f = -1.0f;
}
- cleaned[i][j] = f;
+ cleaned.values[i][j] = f;
}
}
}
-/* Need to wrap this in a class to use it in an Array. */
-class TransMat {
- public:
- float mat[4][4];
-};
-
/* `MeshesToIMeshInfo` keeps track of information used when combining a number
* of `Mesh`es into a single `IMesh` for doing boolean on.
* Mostly this means keeping track of the index offsets for various mesh elements. */
@@ -97,7 +92,7 @@ class MeshesToIMeshInfo {
Array<Face *> mesh_to_imesh_face;
/* Transformation matrix to transform a coordinate in the corresponding
* Mesh to the local space of the first Mesh. */
- Array<TransMat> to_obj0;
+ Array<float4x4> to_obj0;
/* Total number of input mesh vertices. */
int tot_meshes_verts;
/* Total number of input mesh edges. */
@@ -242,7 +237,7 @@ const MEdge *MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index,
* All allocation of memory for the IMesh comes from `arena`.
*/
static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
- const float (*obmats[])[4][4],
+ Span<const float4x4 *> obmats,
IMeshArena &arena,
MeshesToIMeshInfo *r_info)
{
@@ -271,7 +266,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
r_info->mesh_vert_offset = Array<int>(nmeshes);
r_info->mesh_edge_offset = Array<int>(nmeshes);
r_info->mesh_poly_offset = Array<int>(nmeshes);
- r_info->to_obj0 = Array<TransMat>(nmeshes);
+ r_info->to_obj0 = Array<float4x4>(nmeshes);
int v = 0;
int e = 0;
int f = 0;
@@ -286,15 +281,15 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
* of object 0, we multiply each object's `obmat` by the inverse of
* object 0's `obmat`. Exact Boolean works better if these matrices
* are 'cleaned' -- see the comment for the `clean_obmat` function, above. */
- float obj0_mat[4][4];
- float inv_obj0_mat[4][4];
+ float4x4 obj0_mat;
+ float4x4 inv_obj0_mat;
if (obmats[0] == nullptr) {
- unit_m4(obj0_mat);
- unit_m4(inv_obj0_mat);
+ unit_m4(obj0_mat.values);
+ unit_m4(inv_obj0_mat.values);
}
else {
clean_obmat(obj0_mat, *obmats[0]);
- invert_m4_m4(inv_obj0_mat, obj0_mat);
+ invert_m4_m4(inv_obj0_mat.values, obj0_mat.values);
}
/* For each input `Mesh`, make `Vert`s and `Face`s for the corresponding
@@ -303,13 +298,13 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
* When making `Face`s, we also put in the original indices for `MEdge`s that
* make up the `MPoly`s using the same scheme. */
for (int mi : meshes.index_range()) {
- float objn_to_obj0_mat[4][4];
+ float4x4 objn_to_obj0_mat;
const Mesh *me = meshes[mi];
if (mi == 0) {
r_info->mesh_vert_offset[mi] = 0;
r_info->mesh_edge_offset[mi] = 0;
r_info->mesh_poly_offset[mi] = 0;
- unit_m4(r_info->to_obj0[0].mat);
+ unit_m4(r_info->to_obj0[0].values);
}
else {
r_info->mesh_vert_offset[mi] = v;
@@ -317,23 +312,22 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
r_info->mesh_poly_offset[mi] = f;
/* Get matrix that transforms a coordinate in objects[mi]'s local space
* to object[0]'s local space.*/
- float objn_mat[4][4];
+ float4x4 objn_mat;
if (obmats[mi] == nullptr) {
- unit_m4(objn_mat);
+ unit_m4(objn_mat.values);
}
else {
clean_obmat(objn_mat, *obmats[mi]);
}
- mul_m4_m4m4(objn_to_obj0_mat, inv_obj0_mat, objn_mat);
- copy_m4_m4(r_info->to_obj0[mi].mat, objn_to_obj0_mat);
+ objn_to_obj0_mat = inv_obj0_mat * objn_mat;
+ r_info->to_obj0[mi] = objn_to_obj0_mat;
}
for (int vi = 0; vi < me->totvert; ++vi) {
- float co[3];
- copy_v3_v3(co, me->mvert[vi].co);
+ float3 co = me->mvert[vi].co;
if (mi > 0) {
- mul_m4_v3(objn_to_obj0_mat, co);
+ co = objn_to_obj0_mat * co;
}
- r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co[0], co[1], co[2]), v);
+ r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v);
++v;
}
for (const MPoly &poly : Span(me->mpoly, me->totpoly)) {
@@ -534,7 +528,7 @@ static int fill_orig_loops(const Face *f,
static void get_poly2d_cos(const Mesh *me,
const MPoly *mp,
float (*cos_2d)[2],
- const TransMat &trans_mat,
+ const float4x4 &trans_mat,
float r_axis_mat[3][3])
{
int n = mp->totloop;
@@ -546,9 +540,8 @@ static void get_poly2d_cos(const Mesh *me,
MLoop *ml = &me->mloop[mp->loopstart];
const MVert *mverts = me->mvert;
for (int i = 0; i < n; ++i) {
- float co[3];
- copy_v3_v3(co, mverts[ml->v].co);
- mul_m4_v3(trans_mat.mat, co);
+ float3 co = mverts[ml->v].co;
+ co = trans_mat * co;
mul_v2_m3v3(cos_2d[i], r_axis_mat, co);
++ml;
}
@@ -763,23 +756,23 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
* Do Exact Boolean directly, without a round trip through #BMesh.
* The Mesh operands are in `meshes`, with corresponding transforms in in `obmats`.
*/
-static Mesh *direct_mesh_boolean(const Mesh **meshes,
- const float (*obmats[])[4][4],
- const int meshes_len,
+static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
+ Span<const float4x4 *> obmats,
const bool use_self,
const BoolOpType boolean_mode)
{
const int dbg_level = 0;
+ BLI_assert(meshes.size() == obmats.size());
+ const int meshes_len = meshes.size();
if (meshes_len <= 0) {
return nullptr;
}
if (dbg_level > 0) {
std::cout << "\nDIRECT_MESH_INTERSECT, nmeshes = " << meshes_len << "\n";
}
- Span<const Mesh *> mesh_span(meshes, meshes_len);
MeshesToIMeshInfo mim;
IMeshArena arena;
- IMesh m_in = meshes_to_imesh(mesh_span, obmats, arena, &mim);
+ IMesh m_in = meshes_to_imesh(meshes, obmats, arena, &mim);
std::function<int(int)> shape_fn = [&mim](int f) {
for (int mi = 0; mi < mim.mesh_poly_offset.size() - 1; ++mi) {
if (f < mim.mesh_poly_offset[mi + 1]) {
@@ -814,10 +807,10 @@ Mesh *BKE_mesh_boolean(const Mesh **meshes,
const bool use_self,
const int boolean_mode)
{
+ const blender::float4x4 **transforms = (const blender::float4x4 **)obmats;
return blender::meshintersect::direct_mesh_boolean(
- meshes,
- obmats,
- meshes_len,
+ blender::Span(meshes, meshes_len),
+ blender::Span(transforms, meshes_len),
use_self,
static_cast<blender::meshintersect::BoolOpType>(boolean_mode));
}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 4d52a14b742..55cb0d5cce4 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -3555,7 +3555,8 @@ void nodeSetActive(bNodeTree *ntree, bNode *node)
tnode->flag &= ~NODE_ACTIVE_ID;
}
}
- if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
+ if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) ||
+ (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) {
tnode->flag &= ~NODE_ACTIVE_TEXTURE;
}
}
@@ -3564,7 +3565,8 @@ void nodeSetActive(bNodeTree *ntree, bNode *node)
if (node->id) {
node->flag |= NODE_ACTIVE_ID;
}
- if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
+ if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) ||
+ (node->typeinfo->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE)) {
node->flag |= NODE_ACTIVE_TEXTURE;
}
}
diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc
index 397f54ea7e1..6e0253eca31 100644
--- a/source/blender/blenkernel/intern/node_ui_storage.cc
+++ b/source/blender/blenkernel/intern/node_ui_storage.cc
@@ -16,6 +16,8 @@
#include "CLG_log.h"
+#include <mutex>
+
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
@@ -33,10 +35,20 @@ using blender::Map;
using blender::StringRef;
using blender::Vector;
+/* Use a global mutex because otherwise it would have to be stored directly in the
+ * bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */
+static std::mutex global_ui_storage_mutex;
+
static void ui_storage_ensure(bNodeTree &ntree)
{
+ /* As an optimization, only acquire a lock if the UI storage doesn't exist,
+ * because it only needs to be allocated once for every node tree. */
if (ntree.ui_storage == nullptr) {
- ntree.ui_storage = new NodeTreeUIStorage();
+ std::lock_guard<std::mutex> lock(global_ui_storage_mutex);
+ /* Check again-- another thread may have allocated the storage while this one waited. */
+ if (ntree.ui_storage == nullptr) {
+ ntree.ui_storage = new NodeTreeUIStorage();
+ }
}
}
@@ -74,6 +86,7 @@ void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
{
NodeTreeUIStorage *ui_storage = ntree.ui_storage;
if (ui_storage != nullptr) {
+ std::lock_guard<std::mutex> lock(ui_storage->context_map_mutex);
ui_storage->context_map.remove(context);
}
}
@@ -116,6 +129,7 @@ static NodeUIStorage &node_ui_storage_ensure(bNodeTree &ntree,
ui_storage_ensure(ntree);
NodeTreeUIStorage &ui_storage = *ntree.ui_storage;
+ std::lock_guard<std::mutex> lock(ui_storage.context_map_mutex);
Map<std::string, NodeUIStorage> &node_tree_ui_storage =
ui_storage.context_map.lookup_or_add_default(context);
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index d8fb2edb36d..08c5beedbf3 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -1422,6 +1422,12 @@ static void sculptsession_free_pbvh(Object *object)
MEM_SAFE_FREE(ss->pmap);
MEM_SAFE_FREE(ss->pmap_mem);
+ MEM_SAFE_FREE(ss->epmap);
+ MEM_SAFE_FREE(ss->epmap_mem);
+
+ MEM_SAFE_FREE(ss->vemap);
+ MEM_SAFE_FREE(ss->vemap_mem);
+
MEM_SAFE_FREE(ss->persistent_base);
MEM_SAFE_FREE(ss->preview_vert_index_list);
@@ -1471,6 +1477,13 @@ void BKE_sculptsession_free(Object *ob)
MEM_SAFE_FREE(ss->pmap);
MEM_SAFE_FREE(ss->pmap_mem);
+
+ MEM_SAFE_FREE(ss->epmap);
+ MEM_SAFE_FREE(ss->epmap_mem);
+
+ MEM_SAFE_FREE(ss->vemap);
+ MEM_SAFE_FREE(ss->vemap_mem);
+
if (ss->bm_log) {
BM_log_free(ss->bm_log);
}
diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index 5e2582a184a..fa9f00d67ec 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -69,6 +69,8 @@
#include "SEQ_proxy.h"
#include "SEQ_render.h"
#include "SEQ_sequencer.h"
+#include "SEQ_time.h"
+#include "SEQ_transform.h"
#include "BLO_readfile.h"
#include "readfile.h"
@@ -332,6 +334,37 @@ static void seq_convert_transform_crop_lb_2(const Scene *scene,
}
}
+static void seq_update_meta_disp_range(Editing *ed)
+{
+ if (ed == NULL) {
+ return;
+ }
+
+ LISTBASE_FOREACH_BACKWARD (MetaStack *, ms, &ed->metastack) {
+ /* Update ms->disp_range from meta. */
+ if (ms->disp_range[0] == ms->disp_range[1]) {
+ copy_v2_v2_int(ms->disp_range, &ms->parseq->startdisp);
+ }
+
+ /* Update meta strip endpoints. */
+ SEQ_transform_set_left_handle_frame(ms->parseq, ms->disp_range[0]);
+ SEQ_transform_set_right_handle_frame(ms->parseq, ms->disp_range[1]);
+ SEQ_transform_fix_single_image_seq_offsets(ms->parseq);
+
+ /* Recalculate effects using meta strip. */
+ LISTBASE_FOREACH (Sequence *, seq, ms->oldbasep) {
+ if (seq->seq2) {
+ seq->start = seq->startdisp = max_ii(seq->seq1->startdisp, seq->seq2->startdisp);
+ seq->enddisp = min_ii(seq->seq1->enddisp, seq->seq2->enddisp);
+ }
+ }
+
+ /* Ensure that active seqbase points to active meta strip seqbase. */
+ MetaStack *active_ms = SEQ_meta_stack_active_get(ed);
+ SEQ_seqbase_active_set(ed, &active_ms->parseq->seqbase);
+ }
+}
+
void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports))
{
if (!MAIN_VERSION_ATLEAST(bmain, 290, 1)) {
@@ -606,6 +639,10 @@ void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports))
*/
{
/* Keep this block, even when empty. */
+
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+ seq_update_meta_disp_range(SEQ_editing_get(scene, false));
+ }
}
}
diff --git a/source/blender/bmesh/intern/bmesh_marking.c b/source/blender/bmesh/intern/bmesh_marking.c
index 9cd1a2fd4ec..d479a555a58 100644
--- a/source/blender/bmesh/intern/bmesh_marking.c
+++ b/source/blender/bmesh/intern/bmesh_marking.c
@@ -46,7 +46,7 @@ static void recount_totsels(BMesh *bm)
int *tots[3];
int i;
- /* recount (tot * sel) variables */
+ /* Recount total selection variables. */
bm->totvertsel = bm->totedgesel = bm->totfacesel = 0;
tots[0] = &bm->totvertsel;
tots[1] = &bm->totedgesel;
diff --git a/source/blender/compositor/intern/COM_MetaData.cpp b/source/blender/compositor/intern/COM_MetaData.cpp
index 4bc4571face..a6306f6c657 100644
--- a/source/blender/compositor/intern/COM_MetaData.cpp
+++ b/source/blender/compositor/intern/COM_MetaData.cpp
@@ -18,7 +18,6 @@
#include "COM_MetaData.h"
-#include "BKE_cryptomatte.hh"
#include "BKE_image.h"
#include "RE_pipeline.h"
@@ -69,3 +68,39 @@ void MetaData::addToRenderResult(RenderResult *render_result) const
BKE_render_result_stamp_data(render_result, entry.key.c_str(), entry.value.c_str());
}
}
+
+void MetaDataExtractCallbackData::addMetaData(blender::StringRef key, blender::StringRefNull value)
+{
+ if (!meta_data) {
+ meta_data = std::make_unique<MetaData>();
+ }
+ meta_data->add(key, value);
+}
+
+void MetaDataExtractCallbackData::setCryptomatteKeys(blender::StringRef cryptomatte_layer_name)
+{
+ manifest_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
+ "manifest");
+ hash_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
+ "hash");
+ conversion_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
+ "conversion");
+}
+
+void MetaDataExtractCallbackData::extract_cryptomatte_meta_data(void *_data,
+ const char *propname,
+ char *propvalue,
+ int UNUSED(len))
+{
+ MetaDataExtractCallbackData *data = static_cast<MetaDataExtractCallbackData *>(_data);
+ blender::StringRefNull key(propname);
+ if (key == data->hash_key) {
+ data->addMetaData(META_DATA_KEY_CRYPTOMATTE_HASH, propvalue);
+ }
+ else if (key == data->conversion_key) {
+ data->addMetaData(META_DATA_KEY_CRYPTOMATTE_CONVERSION, propvalue);
+ }
+ else if (key == data->manifest_key) {
+ data->addMetaData(META_DATA_KEY_CRYPTOMATTE_MANIFEST, propvalue);
+ }
+} \ No newline at end of file
diff --git a/source/blender/compositor/intern/COM_MetaData.h b/source/blender/compositor/intern/COM_MetaData.h
index c1e34df2791..6fdd8d3945e 100644
--- a/source/blender/compositor/intern/COM_MetaData.h
+++ b/source/blender/compositor/intern/COM_MetaData.h
@@ -20,6 +20,7 @@
#include <string>
+#include "BKE_cryptomatte.hh"
#include "BLI_map.hh"
#include "MEM_guardedalloc.h"
@@ -54,3 +55,18 @@ class MetaData {
MEM_CXX_CLASS_ALLOC_FUNCS("COM:MetaData")
#endif
};
+
+struct MetaDataExtractCallbackData {
+ std::unique_ptr<MetaData> meta_data;
+ std::string hash_key;
+ std::string conversion_key;
+ std::string manifest_key;
+
+ void addMetaData(blender::StringRef key, blender::StringRefNull value);
+ void setCryptomatteKeys(blender::StringRef cryptomatte_layer_name);
+ /* C type callback function (StampCallback). */
+ static void extract_cryptomatte_meta_data(void *_data,
+ const char *propname,
+ char *propvalue,
+ int UNUSED(len));
+};
diff --git a/source/blender/compositor/nodes/COM_ImageNode.cpp b/source/blender/compositor/nodes/COM_ImageNode.cpp
index 596a448e6a0..69729e018d7 100644
--- a/source/blender/compositor/nodes/COM_ImageNode.cpp
+++ b/source/blender/compositor/nodes/COM_ImageNode.cpp
@@ -34,12 +34,12 @@ ImageNode::ImageNode(bNode *editorNode) : Node(editorNode)
/* pass */
}
NodeOperation *ImageNode::doMultilayerCheck(NodeConverter &converter,
- RenderLayer *rl,
+ RenderLayer *render_layer,
+ RenderPass *render_pass,
Image *image,
ImageUser *user,
int framenumber,
int outputsocketIndex,
- int passindex,
int view,
DataType datatype) const
{
@@ -47,19 +47,18 @@ NodeOperation *ImageNode::doMultilayerCheck(NodeConverter &converter,
MultilayerBaseOperation *operation = nullptr;
switch (datatype) {
case COM_DT_VALUE:
- operation = new MultilayerValueOperation(passindex, view);
+ operation = new MultilayerValueOperation(render_layer, render_pass, view);
break;
case COM_DT_VECTOR:
- operation = new MultilayerVectorOperation(passindex, view);
+ operation = new MultilayerVectorOperation(render_layer, render_pass, view);
break;
case COM_DT_COLOR:
- operation = new MultilayerColorOperation(passindex, view);
+ operation = new MultilayerColorOperation(render_layer, render_pass, view);
break;
default:
break;
}
operation->setImage(image);
- operation->setRenderLayer(rl);
operation->setImageUser(user);
operation->setFramenumber(framenumber);
@@ -128,16 +127,15 @@ void ImageNode::convertToOperations(NodeConverter &converter,
}
if (rpass) {
- int passindex = BLI_findindex(&rl->passes, rpass);
switch (rpass->channels) {
case 1:
operation = doMultilayerCheck(converter,
rl,
+ rpass,
image,
imageuser,
framenumber,
index,
- passindex,
view,
COM_DT_VALUE);
break;
@@ -146,22 +144,22 @@ void ImageNode::convertToOperations(NodeConverter &converter,
case 3:
operation = doMultilayerCheck(converter,
rl,
+ rpass,
image,
imageuser,
framenumber,
index,
- passindex,
view,
COM_DT_VECTOR);
break;
case 4:
operation = doMultilayerCheck(converter,
rl,
+ rpass,
image,
imageuser,
framenumber,
index,
- passindex,
view,
COM_DT_COLOR);
break;
diff --git a/source/blender/compositor/nodes/COM_ImageNode.h b/source/blender/compositor/nodes/COM_ImageNode.h
index 1a811fe855d..b99fc07f105 100644
--- a/source/blender/compositor/nodes/COM_ImageNode.h
+++ b/source/blender/compositor/nodes/COM_ImageNode.h
@@ -24,6 +24,7 @@
#include "DNA_node_types.h"
#include "RE_engine.h"
+#include "RE_pipeline.h"
/**
* \brief ImageNode
@@ -32,12 +33,12 @@
class ImageNode : public Node {
private:
NodeOperation *doMultilayerCheck(NodeConverter &converter,
- RenderLayer *rl,
+ RenderLayer *render_layer,
+ RenderPass *render_pass,
Image *image,
ImageUser *user,
int framenumber,
int outputsocketIndex,
- int passindex,
int view,
DataType datatype) const;
diff --git a/source/blender/compositor/operations/COM_MultilayerImageOperation.cpp b/source/blender/compositor/operations/COM_MultilayerImageOperation.cpp
index 023538ee5b1..60936ee1939 100644
--- a/source/blender/compositor/operations/COM_MultilayerImageOperation.cpp
+++ b/source/blender/compositor/operations/COM_MultilayerImageOperation.cpp
@@ -21,10 +21,14 @@
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
-MultilayerBaseOperation::MultilayerBaseOperation(int passindex, int view)
+MultilayerBaseOperation::MultilayerBaseOperation(RenderLayer *render_layer,
+ RenderPass *render_pass,
+ int view)
{
- this->m_passId = passindex;
+ this->m_passId = BLI_findindex(&render_layer->passes, render_pass);
this->m_view = view;
+ this->m_renderLayer = render_layer;
+ this->m_renderPass = render_pass;
}
ImBuf *MultilayerBaseOperation::getImBuf()
@@ -45,6 +49,32 @@ ImBuf *MultilayerBaseOperation::getImBuf()
return nullptr;
}
+std::unique_ptr<MetaData> MultilayerColorOperation::getMetaData() const
+{
+ BLI_assert(this->m_buffer);
+ MetaDataExtractCallbackData callback_data = {nullptr};
+ RenderResult *render_result = this->m_image->rr;
+ if (render_result && render_result->stamp_data) {
+ RenderLayer *render_layer = this->m_renderLayer;
+ RenderPass *render_pass = this->m_renderPass;
+ std::string full_layer_name =
+ std::string(render_layer->name,
+ BLI_strnlen(render_layer->name, sizeof(render_layer->name))) +
+ "." +
+ std::string(render_pass->name, BLI_strnlen(render_pass->name, sizeof(render_pass->name)));
+ blender::StringRef cryptomatte_layer_name =
+ blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name(full_layer_name);
+ callback_data.setCryptomatteKeys(cryptomatte_layer_name);
+
+ BKE_stamp_info_callback(&callback_data,
+ render_result->stamp_data,
+ MetaDataExtractCallbackData::extract_cryptomatte_meta_data,
+ false);
+ }
+
+ return std::move(callback_data.meta_data);
+}
+
void MultilayerColorOperation::executePixelSampled(float output[4],
float x,
float y,
diff --git a/source/blender/compositor/operations/COM_MultilayerImageOperation.h b/source/blender/compositor/operations/COM_MultilayerImageOperation.h
index adfcc975ade..f5176b0a4db 100644
--- a/source/blender/compositor/operations/COM_MultilayerImageOperation.h
+++ b/source/blender/compositor/operations/COM_MultilayerImageOperation.h
@@ -24,34 +24,34 @@ class MultilayerBaseOperation : public BaseImageOperation {
private:
int m_passId;
int m_view;
- RenderLayer *m_renderlayer;
protected:
+ RenderLayer *m_renderLayer;
+ RenderPass *m_renderPass;
ImBuf *getImBuf();
public:
/**
* Constructor
*/
- MultilayerBaseOperation(int passindex, int view);
- void setRenderLayer(RenderLayer *renderlayer)
- {
- this->m_renderlayer = renderlayer;
- }
+ MultilayerBaseOperation(RenderLayer *render_layer, RenderPass *render_pass, int view);
};
class MultilayerColorOperation : public MultilayerBaseOperation {
public:
- MultilayerColorOperation(int passindex, int view) : MultilayerBaseOperation(passindex, view)
+ MultilayerColorOperation(RenderLayer *render_layer, RenderPass *render_pass, int view)
+ : MultilayerBaseOperation(render_layer, render_pass, view)
{
this->addOutputSocket(COM_DT_COLOR);
}
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler);
+ std::unique_ptr<MetaData> getMetaData() const override;
};
class MultilayerValueOperation : public MultilayerBaseOperation {
public:
- MultilayerValueOperation(int passindex, int view) : MultilayerBaseOperation(passindex, view)
+ MultilayerValueOperation(RenderLayer *render_layer, RenderPass *render_pass, int view)
+ : MultilayerBaseOperation(render_layer, render_pass, view)
{
this->addOutputSocket(COM_DT_VALUE);
}
@@ -60,7 +60,8 @@ class MultilayerValueOperation : public MultilayerBaseOperation {
class MultilayerVectorOperation : public MultilayerBaseOperation {
public:
- MultilayerVectorOperation(int passindex, int view) : MultilayerBaseOperation(passindex, view)
+ MultilayerVectorOperation(RenderLayer *render_layer, RenderPass *render_pass, int view)
+ : MultilayerBaseOperation(render_layer, render_pass, view)
{
this->addOutputSocket(COM_DT_VECTOR);
}
diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.cpp b/source/blender/compositor/operations/COM_OutputFileOperation.cpp
index 19d49bc2ae7..bb1b312ffec 100644
--- a/source/blender/compositor/operations/COM_OutputFileOperation.cpp
+++ b/source/blender/compositor/operations/COM_OutputFileOperation.cpp
@@ -26,7 +26,6 @@
#include "BLI_path_util.h"
#include "BLI_string.h"
-#include "BKE_cryptomatte.hh"
#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_main.h"
diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cpp b/source/blender/compositor/operations/COM_RenderLayersProg.cpp
index 4f4116d6faa..73de60f4c34 100644
--- a/source/blender/compositor/operations/COM_RenderLayersProg.cpp
+++ b/source/blender/compositor/operations/COM_RenderLayersProg.cpp
@@ -20,7 +20,6 @@
#include "COM_MetaData.h"
-#include "BKE_cryptomatte.hh"
#include "BKE_image.h"
#include "BKE_scene.h"
@@ -217,74 +216,32 @@ void RenderLayersProg::determineResolution(unsigned int resolution[2],
}
}
-struct CallbackData {
- std::unique_ptr<MetaData> meta_data;
- std::string hash_key;
- std::string conversion_key;
- std::string manifest_key;
-
- void addMetaData(blender::StringRef key, blender::StringRefNull value)
- {
- if (!meta_data) {
- meta_data = std::make_unique<MetaData>();
- }
- meta_data->add(key, value);
- }
-
- void setCryptomatteKeys(blender::StringRef cryptomatte_layer_name)
- {
- manifest_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
- "manifest");
- hash_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
- "hash");
- conversion_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(
- cryptomatte_layer_name, "conversion");
- }
-};
-
-/* C type callback function (StampCallback). */
-static void extract_cryptomatte_meta_data(void *_data,
- const char *propname,
- char *propvalue,
- int UNUSED(len))
-{
- CallbackData *data = static_cast<CallbackData *>(_data);
- blender::StringRefNull key(propname);
- if (key == data->hash_key) {
- data->addMetaData(META_DATA_KEY_CRYPTOMATTE_HASH, propvalue);
- }
- else if (key == data->conversion_key) {
- data->addMetaData(META_DATA_KEY_CRYPTOMATTE_CONVERSION, propvalue);
- }
- else if (key == data->manifest_key) {
- data->addMetaData(META_DATA_KEY_CRYPTOMATTE_MANIFEST, propvalue);
- }
-}
-
std::unique_ptr<MetaData> RenderLayersProg::getMetaData() const
{
Scene *scene = this->getScene();
Render *re = (scene) ? RE_GetSceneRender(scene) : nullptr;
- RenderResult *rr = nullptr;
- CallbackData callback_data = {nullptr};
+ RenderResult *render_result = nullptr;
+ MetaDataExtractCallbackData callback_data = {nullptr};
if (re) {
- rr = RE_AcquireResultRead(re);
+ render_result = RE_AcquireResultRead(re);
}
- if (rr && rr->stamp_data) {
+ if (render_result && render_result->stamp_data) {
ViewLayer *view_layer = (ViewLayer *)BLI_findlink(&scene->view_layers, getLayerId());
if (view_layer) {
std::string full_layer_name = std::string(
view_layer->name,
BLI_strnlen(view_layer->name, sizeof(view_layer->name))) +
"." + m_passName;
- blender::StringRef cryptomatte_layer_name = blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name(
- full_layer_name);
+ blender::StringRef cryptomatte_layer_name =
+ blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name(full_layer_name);
callback_data.setCryptomatteKeys(cryptomatte_layer_name);
- BKE_stamp_info_callback(
- &callback_data, rr->stamp_data, extract_cryptomatte_meta_data, false);
+ BKE_stamp_info_callback(&callback_data,
+ render_result->stamp_data,
+ MetaDataExtractCallbackData::extract_cryptomatte_meta_data,
+ false);
}
}
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 33d45d61d42..5f8e0106337 100644
--- a/source/blender/draw/engines/eevee/eevee_depth_of_field.c
+++ b/source/blender/draw/engines/eevee/eevee_depth_of_field.c
@@ -273,9 +273,12 @@ int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata),
float minimal_overblur = 1.0f / sqrtf(sample_count);
float user_overblur = scene_eval->eevee.bokeh_overblur / 100.0f;
- effects->dof_coc_params[1] *= minimal_overblur + user_overblur;
+ minimal_overblur *= effects->dof_coc_params[1];
+ user_overblur *= effects->dof_coc_params[1];
+
+ effects->dof_coc_params[1] = minimal_overblur + user_overblur;
/* Avoid dilating the shape. Over-blur only soften. */
- effects->dof_jitter_radius -= effects->dof_coc_params[1];
+ effects->dof_jitter_radius -= minimal_overblur + user_overblur * 0.5f;
}
}
else {
diff --git a/source/blender/draw/engines/eevee/eevee_lightcache.c b/source/blender/draw/engines/eevee/eevee_lightcache.c
index 145fddf62a0..37ecdb20651 100644
--- a/source/blender/draw/engines/eevee/eevee_lightcache.c
+++ b/source/blender/draw/engines/eevee/eevee_lightcache.c
@@ -196,7 +196,7 @@ static uint eevee_lightcache_memsize_get(LightCache *lcache)
return size;
}
-static bool eevee_lightcache_version_check(LightCache *lcache)
+static bool eevee_lightcache_version_check(const LightCache *lcache)
{
switch (lcache->type) {
case LIGHTCACHE_TYPE_STATIC:
@@ -313,7 +313,14 @@ static bool EEVEE_lightcache_validate(const LightCache *light_cache,
const int grid_len,
const int irr_size[3])
{
- if (light_cache && !(light_cache->flag & LIGHTCACHE_INVALID)) {
+ if (light_cache == NULL) {
+ return false;
+ }
+ if (!eevee_lightcache_version_check(light_cache)) {
+ return false;
+ }
+
+ if (!(light_cache->flag & LIGHTCACHE_INVALID)) {
/* See if we need the same amount of texture space. */
if ((irr_size[0] == light_cache->grid_tx.tex_size[0]) &&
(irr_size[1] == light_cache->grid_tx.tex_size[1]) &&
diff --git a/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl b/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
index 9b852a57ec4..b1e3a40e8d2 100644
--- a/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
+++ b/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
@@ -98,7 +98,7 @@ vec3 light_volume(LightData ld, vec4 l_vector)
return tint * lum;
}
-#define VOLUMETRIC_SHADOW_MAX_STEP 32.0
+#define VOLUMETRIC_SHADOW_MAX_STEP 128.0
vec3 participating_media_extinction(vec3 wpos, sampler3D volume_extinction)
{
@@ -115,10 +115,10 @@ vec3 light_volume_shadow(LightData ld, vec3 ray_wpos, vec4 l_vector, sampler3D v
#if defined(VOLUME_SHADOW)
/* Heterogeneous volume shadows */
float dd = l_vector.w / volShadowSteps;
- vec3 L = l_vector.xyz * l_vector.w;
+ vec3 L = l_vector.xyz / volShadowSteps;
vec3 shadow = vec3(1.0);
- for (float s = 0.5; s < VOLUMETRIC_SHADOW_MAX_STEP && s < (volShadowSteps - 0.1); s += 1.0) {
- vec3 pos = ray_wpos + L * (s / volShadowSteps);
+ for (float s = 1.0; s < VOLUMETRIC_SHADOW_MAX_STEP && s <= volShadowSteps; s += 1.0) {
+ vec3 pos = ray_wpos + L * s;
vec3 s_extinction = participating_media_extinction(pos, volume_extinction);
shadow *= exp(-s_extinction * dd);
}
diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h
index f540ff09032..84bc0327aa2 100644
--- a/source/blender/draw/intern/draw_manager.h
+++ b/source/blender/draw/intern/draw_manager.h
@@ -46,10 +46,10 @@
struct DupliObject;
struct Object;
-/* Use draw manager to call GPU_select, see: DRW_draw_select_loop */
+/** Use draw manager to call GPU_select, see: #DRW_draw_select_loop */
#define USE_GPU_SELECT
-/* Use drawcall batching using instanced rendering. */
+/** Use draw-call batching using instanced rendering. */
#define USE_BATCHING 1
// #define DRW_DEBUG_CULLING
diff --git a/source/blender/draw/intern/draw_manager_profiling.c b/source/blender/draw/intern/draw_manager_profiling.c
index 98a6b2bff00..9bfc8d98fe4 100644
--- a/source/blender/draw/intern/draw_manager_profiling.c
+++ b/source/blender/draw/intern/draw_manager_profiling.c
@@ -151,7 +151,6 @@ void DRW_stats_group_end(void)
void DRW_stats_query_start(const char *name)
{
GPU_debug_group_begin(name);
- drw_stats_timer_start_ex(name, false);
drw_stats_timer_start_ex(name, true);
}
diff --git a/source/blender/editors/armature/armature_relations.c b/source/blender/editors/armature/armature_relations.c
index bb5bcd4083e..4dbe448c4ec 100644
--- a/source/blender/editors/armature/armature_relations.c
+++ b/source/blender/editors/armature/armature_relations.c
@@ -552,9 +552,12 @@ static void separated_armature_fix_links(Main *bmain, Object *origArm, Object *n
}
}
-/* Helper function for armature separating - remove certain bones from the given armature
- * sel: remove selected bones from the armature, otherwise the unselected bones are removed
- * (ob is not in edit-mode)
+/**
+ * Helper function for armature separating - remove certain bones from the given armature.
+ *
+ * \param ob: Armature object (must not be is not in edit-mode).
+ * \param is_select: remove selected bones from the armature,
+ * otherwise the unselected bones are removed.
*/
static void separate_armature_bones(Main *bmain, Object *ob, const bool is_select)
{
diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c
index c76c2e55d2b..039bc50fcc9 100644
--- a/source/blender/editors/gpencil/gpencil_data.c
+++ b/source/blender/editors/gpencil/gpencil_data.c
@@ -1676,7 +1676,7 @@ void GPENCIL_OT_stroke_arrange(wmOperatorType *ot)
/* identifiers */
ot->name = "Arrange Stroke";
ot->idname = "GPENCIL_OT_stroke_arrange";
- ot->description = "Arrange selected strokes up/down in the drawing order of the active layer";
+ ot->description = "Arrange selected strokes up/down in the display order of the active layer";
/* callbacks */
ot->exec = gpencil_stroke_arrange_exec;
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 81641239c6a..5620d39ab16 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -499,10 +499,14 @@ typedef int (*uiButCompleteFunc)(struct bContext *C, char *str, void *arg);
typedef struct ARegion *(*uiButSearchCreateFn)(struct bContext *C,
struct ARegion *butregion,
struct uiButSearch *search_but);
+/* `is_first` is typically used to ignore search filtering when the menu is first opened in order
+ * to display the full list of options. The value will be false after the button's text is edited
+ * (for every call except the first). */
typedef void (*uiButSearchUpdateFn)(const struct bContext *C,
void *arg,
const char *str,
- uiSearchItems *items);
+ uiSearchItems *items,
+ const bool is_first);
typedef void (*uiButSearchArgFreeFn)(void *arg);
typedef bool (*uiButSearchContextMenuFn)(struct bContext *C,
void *arg,
@@ -1602,6 +1606,7 @@ void UI_but_func_search_set(uiBut *but,
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn);
void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn);
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string);
+void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value);
/* height in pixels, it's using hardcoded values still */
int UI_searchbox_size_y(void);
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index 6e25ec9d275..c7f5385eac3 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -6661,11 +6661,20 @@ void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn)
but_search->item_tooltip_fn = tooltip_fn;
}
+void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value)
+{
+ uiButSearch *but_search = (uiButSearch *)but;
+ BLI_assert(but->type == UI_BTYPE_SEARCH_MENU);
+
+ but_search->results_are_suggestions = value;
+}
+
/* Callbacks for operator search button. */
static void operator_enum_search_update_fn(const struct bContext *C,
void *but,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
wmOperatorType *ot = ((uiBut *)but)->optype;
PropertyRNA *prop = ot->prop;
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 5de330d7136..75ee3300e63 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -3408,8 +3408,12 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
if (data->searchbox) {
if (data->cancel == false) {
+ BLI_assert(but->type == UI_BTYPE_SEARCH_MENU);
+ uiButSearch *but_search = (uiButSearch *)but;
+
if ((ui_searchbox_apply(but, data->searchbox) == false) &&
- (ui_searchbox_find_index(data->searchbox, but->editstr) == -1)) {
+ (ui_searchbox_find_index(data->searchbox, but->editstr) == -1) &&
+ !but_search->results_are_suggestions) {
data->cancel = true;
/* ensure menu (popup) too is closed! */
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 7e931eae749..c39e2b3ff8a 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -320,6 +320,12 @@ typedef struct uiButSearch {
struct PointerRNA rnasearchpoin;
struct PropertyRNA *rnasearchprop;
+
+ /**
+ * The search box only provides suggestions, it does not force
+ * the string to match one of the search items when applying.
+ */
+ bool results_are_suggestions;
} uiButSearch;
/** Derived struct for #UI_BTYPE_DECORATOR */
@@ -1197,7 +1203,8 @@ typedef struct uiRNACollectionSearch {
void ui_rna_collection_search_update_fn(const struct bContext *C,
void *arg,
const char *str,
- uiSearchItems *items);
+ uiSearchItems *items,
+ const bool is_first);
/* interface_ops.c */
bool ui_jump_to_target_button_poll(struct bContext *C);
diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c
index 2c07f5c3c03..12044863b8c 100644
--- a/source/blender/editors/interface/interface_region_search.c
+++ b/source/blender/editors/interface/interface_region_search.c
@@ -468,7 +468,8 @@ static void ui_searchbox_update_fn(bContext *C,
wmWindow *win = CTX_wm_window(C);
WM_tooltip_clear(C, win);
}
- search_but->items_update_fn(C, search_but->arg, str, items);
+ const bool is_first_search = !search_but->but.changed;
+ search_but->items_update_fn(C, search_but->arg, str, items, is_first_search);
}
/* region is the search box itself */
@@ -1052,14 +1053,16 @@ void ui_but_search_refresh(uiButSearch *search_but)
ui_searchbox_update_fn(but->block->evil_C, search_but, but->drawstr, items);
- /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */
- if (items->totitem == 0) {
- UI_but_flag_enable(but, UI_BUT_REDALERT);
- }
- else if (items->more == 0) {
- if (UI_search_items_find_index(items, but->drawstr) == -1) {
+ if (!search_but->results_are_suggestions) {
+ /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */
+ if (items->totitem == 0) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
+ else if (items->more == 0) {
+ if (UI_search_items_find_index(items, but->drawstr) == -1) {
+ UI_but_flag_enable(but, UI_BUT_REDALERT);
+ }
+ }
}
for (x1 = 0; x1 < items->maxitem; x1++) {
diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c
index 3f48c49ae17..74668b2f3a3 100644
--- a/source/blender/editors/interface/interface_template_search_menu.c
+++ b/source/blender/editors/interface/interface_template_search_menu.c
@@ -991,7 +991,8 @@ static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
static void menu_search_update_fn(const bContext *UNUSED(C),
void *arg,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
struct MenuSearch_Data *data = arg;
diff --git a/source/blender/editors/interface/interface_template_search_operator.c b/source/blender/editors/interface/interface_template_search_operator.c
index ff0f9a2e5cd..2c83f184ff0 100644
--- a/source/blender/editors/interface/interface_template_search_operator.c
+++ b/source/blender/editors/interface/interface_template_search_operator.c
@@ -59,7 +59,8 @@ static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
static void operator_search_update_fn(const bContext *C,
void *UNUSED(arg),
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
GHashIterator iter;
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 67446ca681f..c5e67e0334e 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -393,7 +393,8 @@ static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchIt
static void id_search_cb(const bContext *C,
void *arg_template,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
TemplateID *template_ui = (TemplateID *)arg_template;
ListBase *lb = template_ui->idlb;
@@ -464,7 +465,8 @@ static void id_search_cb_tagged(const bContext *C,
static void id_search_cb_objects_from_scene(const bContext *C,
void *arg_template,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
TemplateID *template_ui = (TemplateID *)arg_template;
ListBase *lb = template_ui->idlb;
@@ -518,7 +520,7 @@ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem)
static TemplateID template_ui;
PointerRNA active_item_ptr;
void (*id_search_update_fn)(
- const bContext *, void *, const char *, uiSearchItems *) = id_search_cb;
+ const bContext *, void *, const char *, uiSearchItems *, const bool) = id_search_cb;
/* arg_litem is malloced, can be freed by parent button */
template_ui = *((TemplateID *)arg_litem);
@@ -3985,7 +3987,7 @@ static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event)
BKE_curvemapping_changed(cumap, false);
break;
case UICURVE_FUNC_RESET_VIEW:
- cumap->curr = cumap->clipr;
+ BKE_curvemapping_reset_view(cumap);
break;
case UICURVE_FUNC_HANDLE_VECTOR: /* set vector */
BKE_curvemap_handle_set(cuma, HD_VECT);
diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c
index 5311bb57da9..877800c1ba2 100644
--- a/source/blender/editors/interface/interface_utils.c
+++ b/source/blender/editors/interface/interface_utils.c
@@ -405,7 +405,8 @@ static bool add_collection_search_item(CollItemSearch *cis,
void ui_rna_collection_search_update_fn(const struct bContext *C,
void *arg,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool is_first)
{
uiRNACollectionSearch *data = arg;
const int flag = RNA_property_flag(data->target_prop);
@@ -415,7 +416,7 @@ void ui_rna_collection_search_update_fn(const struct bContext *C,
* match the RNA name exactly. So only for pointer properties, the name can be modified to add
* further UI hints. */
const bool requires_exact_data_name = !is_ptr_target;
- const bool skip_filter = data->search_but && !data->search_but->changed;
+ const bool skip_filter = is_first;
char name_buf[UI_MAX_DRAW_STR];
char *name;
bool has_id_icon = false;
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index fff8d27ef5b..fff172c0707 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -61,10 +61,12 @@ set(SRC
sculpt_cloth.c
sculpt_detail.c
sculpt_dyntopo.c
+ sculpt_expand.c
sculpt_face_set.c
sculpt_filter_color.c
sculpt_filter_mask.c
sculpt_filter_mesh.c
+ sculpt_geodesic.c
sculpt_mask_expand.c
sculpt_multiplane_scrape.c
sculpt_paint_color.c
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index e9f33d2fbd3..b44f2f66d92 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1626,6 +1626,16 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
paint_cursor_pose_brush_origins_draw(pcontext);
}
+ /* Expand operation origin. */
+ if (pcontext->ss->expand_cache) {
+ cursor_draw_point_screen_space(
+ pcontext->pos,
+ pcontext->region,
+ SCULPT_vertex_co_get(pcontext->ss, pcontext->ss->expand_cache->initial_active_vertex),
+ pcontext->vc.obact->obmat,
+ 2);
+ }
+
if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
paint_cursor_preview_boundary_data_update(pcontext, update_previews);
paint_cursor_preview_boundary_data_pivot_draw(pcontext);
diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c
index f4586fa130d..e1dc8fa30b9 100644
--- a/source/blender/editors/sculpt_paint/paint_ops.c
+++ b/source/blender/editors/sculpt_paint/paint_ops.c
@@ -1393,4 +1393,7 @@ void ED_keymap_paint(wmKeyConfig *keyconf)
/* paint stroke */
keymap = paint_stroke_modal_keymap(keyconf);
WM_modalkeymap_assign(keymap, "SCULPT_OT_brush_stroke");
+
+ /* sculpt expand. */
+ sculpt_expand_modal_keymap(keyconf);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 3a18d7a10de..631327ddfe8 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1103,6 +1103,12 @@ void SCULPT_floodfill_add_initial(SculptFloodFill *flood, int index)
BLI_gsqueue_push(flood->queue, &index);
}
+void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, int index)
+{
+ BLI_gsqueue_push(flood->queue, &index);
+ BLI_BITMAP_ENABLE(flood->visited_vertices, index);
+}
+
void SCULPT_floodfill_add_initial_with_symmetry(
Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, int index, float radius)
{
@@ -9006,7 +9012,7 @@ static bool SCULPT_connected_components_floodfill_cb(
return true;
}
-static void sculpt_connected_components_ensure(Object *ob)
+void SCULPT_connected_components_ensure(Object *ob)
{
SculptSession *ss = ob->sculpt;
@@ -9081,7 +9087,7 @@ void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist)
return;
}
- sculpt_connected_components_ensure(ob);
+ SCULPT_connected_components_ensure(ob);
SCULPT_fake_neighbor_init(ss, max_dist);
for (int i = 0; i < totvert; i++) {
@@ -9790,4 +9796,6 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_color_filter);
WM_operatortype_append(SCULPT_OT_mask_by_color);
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
+
+ WM_operatortype_append(SCULPT_OT_expand);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c
index fca19c04b98..f1fb402ae41 100644
--- a/source/blender/editors/sculpt_paint/sculpt_boundary.c
+++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c
@@ -518,11 +518,13 @@ SculptBoundary *SCULPT_boundary_data_init(Object *object,
SculptBoundary *boundary = MEM_callocN(sizeof(SculptBoundary), "Boundary edit data");
- const bool init_boundary_distances = brush->boundary_falloff_type !=
- BRUSH_BOUNDARY_FALLOFF_CONSTANT;
+ const bool init_boundary_distances = brush ? brush->boundary_falloff_type !=
+ BRUSH_BOUNDARY_FALLOFF_CONSTANT :
+ false;
+
sculpt_boundary_indices_init(ss, boundary, init_boundary_distances, boundary_initial_vertex);
- const float boundary_radius = radius * (1.0f + brush->boundary_offset);
+ const float boundary_radius = brush ? radius * (1.0f + brush->boundary_offset) : radius;
sculpt_boundary_edit_data_init(ss, boundary, boundary_initial_vertex, boundary_radius);
return boundary;
diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c
new file mode 100644
index 00000000000..db6d33c2700
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_expand.c
@@ -0,0 +1,2258 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2021 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_linklist_stack.h"
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_image.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_subdiv_ccg.h"
+
+#include "DEG_depsgraph.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 "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_view3d.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "IMB_colormanagement.h"
+#include "IMB_imbuf.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+/* Sculpt Expand. */
+/* Operator for creating selections and patterns in Sculpt Mode. Expand can create masks, face sets
+ * and fill vertex colors. */
+/* The main functionality of the operator
+ * - The operator initializes a value per vertex, called "falloff". There are multiple algorithms
+ * to generate these falloff values which will create different patterns in the result when using
+ * the operator. These falloff values require algorithms that rely on mesh connectivity, so they
+ * are only valid on parts of the mesh that are in the same connected component as the given
+ * initial vertices. If needed, these falloff values are propagated from vertex or grids into the
+ * base mesh faces.
+ *
+ * - On each modal callback, the operator gets the active vertex and face and gets its falloff
+ * value from its precalculated falloff. This is now the active falloff value.
+ * - Using the active falloff value and the settings of the expand operation (which can be modified
+ * during execution using the modal key-map), the operator loops over all elements in the mesh to
+ * check if they are enabled of not.
+ * - Based on each element state after evaluating the settings, the desired mesh data (mask, face
+ * sets, colors...) is updated.
+ */
+
+/**
+ * Used for defining an invalid vertex state (for example, when the cursor is not over the mesh).
+ */
+#define SCULPT_EXPAND_VERTEX_NONE -1
+
+/** Used for defining an uninitialized active component index for an unused symmetry pass. */
+#define EXPAND_ACTIVE_COMPONENT_NONE -1
+/**
+ * Defines how much each time the texture distortion is increased/decreased
+ * when using the modal key-map.
+ */
+#define SCULPT_EXPAND_TEXTURE_DISTORTION_STEP 0.01f
+
+/**
+ * This threshold offsets the required falloff value to start a new loop. This is needed because in
+ * some situations, vertices which have the same falloff value as max_falloff will start a new
+ * loop, which is undesired.
+ */
+#define SCULPT_EXPAND_LOOP_THRESHOLD 0.00001f
+
+/**
+ * Defines how much changes in curvature in the mesh affect the falloff shape when using normal
+ * falloff. This default was found experimentally and it works well in most cases, but can be
+ * exposed for tweaking if needed.
+ */
+#define SCULPT_EXPAND_NORMALS_FALLOFF_EDGE_SENSITIVITY 300
+
+/* Expand Modal Key-map. */
+enum {
+ SCULPT_EXPAND_MODAL_CONFIRM = 1,
+ SCULPT_EXPAND_MODAL_CANCEL,
+ SCULPT_EXPAND_MODAL_INVERT,
+ SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE,
+ SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE,
+ SCULPT_EXPAND_MODAL_FALLOFF_CYCLE,
+ SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC,
+ SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY,
+ SCULPT_EXPAND_MODAL_MOVE_TOGGLE,
+ SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC,
+ SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY,
+ SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS,
+ SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL,
+ SCULPT_EXPAND_MODAL_SNAP_TOGGLE,
+ SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
+ SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE,
+ SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE,
+ SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE,
+ SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE,
+};
+
+/* Functions for getting the state of mesh elements (vertices and base mesh faces). When the main
+ * functions for getting the state of an element return true it means that data associated to that
+ * element will be modified by expand. */
+
+/**
+ * Returns true if the vertex is in a connected component with correctly initialized falloff
+ * values.
+ */
+static bool sculpt_expand_is_vert_in_active_component(SculptSession *ss,
+ ExpandCache *expand_cache,
+ const int v)
+{
+ for (int i = 0; i < EXPAND_SYMM_AREAS; i++) {
+ if (ss->vertex_info.connected_component[v] == expand_cache->active_connected_components[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns true if the face is in a connected component with correctly initialized falloff values.
+ */
+static bool sculpt_expand_is_face_in_active_component(SculptSession *ss,
+ ExpandCache *expand_cache,
+ const int f)
+{
+ const MLoop *loop = &ss->mloop[ss->mpoly[f].loopstart];
+ return sculpt_expand_is_vert_in_active_component(ss, expand_cache, loop->v);
+}
+
+/**
+ * Returns the falloff value of a vertex. This function includes texture distortion, which is not
+ * precomputed into the initial falloff values.
+ */
+static float sculpt_expand_falloff_value_vertex_get(SculptSession *ss,
+ ExpandCache *expand_cache,
+ const int v)
+{
+ if (expand_cache->texture_distortion_strength == 0.0f) {
+ return expand_cache->vert_falloff[v];
+ }
+
+ if (!expand_cache->brush->mtex.tex) {
+ return expand_cache->vert_falloff[v];
+ }
+
+ float rgba[4];
+ const float *vertex_co = SCULPT_vertex_co_get(ss, v);
+ const float avg = BKE_brush_sample_tex_3d(
+ expand_cache->scene, expand_cache->brush, vertex_co, rgba, 0, ss->tex_pool);
+
+ const float distortion = (avg - 0.5f) * expand_cache->texture_distortion_strength *
+ expand_cache->max_vert_falloff;
+ return expand_cache->vert_falloff[v] + distortion;
+}
+
+/**
+ * Returns the maximum valid falloff value stored in the falloff array, taking the maximum possible
+ * texture distortion into account.
+ */
+static float sculpt_expand_max_vertex_falloff_get(ExpandCache *expand_cache)
+{
+ if (expand_cache->texture_distortion_strength == 0.0f) {
+ return expand_cache->max_vert_falloff;
+ }
+
+ if (!expand_cache->brush->mtex.tex) {
+ return expand_cache->max_vert_falloff;
+ }
+
+ return expand_cache->max_vert_falloff +
+ (0.5f * expand_cache->texture_distortion_strength * expand_cache->max_vert_falloff);
+}
+
+/**
+ * Main function to get the state of a vertex for the current state and settings of a #ExpandCache.
+ * Returns true when the target data should be modified by expand.
+ */
+static bool sculpt_expand_state_get(SculptSession *ss, ExpandCache *expand_cache, const int v)
+{
+ if (!SCULPT_vertex_visible_get(ss, v)) {
+ return false;
+ }
+
+ if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, v)) {
+ return false;
+ }
+
+ if (expand_cache->all_enabled) {
+ return true;
+ }
+
+ bool enabled = false;
+
+ if (expand_cache->snap) {
+ /* Face Sets are not being modified when using this function, so it is ok to get this directly
+ * from the Sculpt API instead of implementing a custom function to get them from
+ * expand_cache->original_face_sets. */
+ const int face_set = SCULPT_vertex_face_set_get(ss, v);
+ enabled = BLI_gset_haskey(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
+ }
+ else {
+ const float max_falloff_factor = sculpt_expand_max_vertex_falloff_get(expand_cache);
+ const float loop_len = (max_falloff_factor / expand_cache->loop_count) +
+ SCULPT_EXPAND_LOOP_THRESHOLD;
+
+ const float vertex_falloff_factor = sculpt_expand_falloff_value_vertex_get(
+ ss, expand_cache, v);
+ const float active_factor = fmod(expand_cache->active_falloff, loop_len);
+ const float falloff_factor = fmod(vertex_falloff_factor, loop_len);
+
+ enabled = falloff_factor < active_factor;
+ }
+
+ if (expand_cache->invert) {
+ enabled = !enabled;
+ }
+ return enabled;
+}
+
+/**
+ * Main function to get the state of a face for the current state and settings of a #ExpandCache.
+ * Returns true when the target data should be modified by expand.
+ */
+static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_cache, const int f)
+{
+ if (ss->face_sets[f] <= 0) {
+ return false;
+ }
+
+ if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, f)) {
+ return false;
+ }
+
+ if (expand_cache->all_enabled) {
+ return true;
+ }
+
+ bool enabled = false;
+
+ if (expand_cache->snap_enabled_face_sets) {
+ const int face_set = expand_cache->original_face_sets[f];
+ enabled = BLI_gset_haskey(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
+ }
+ else {
+ const float loop_len = (expand_cache->max_face_falloff / expand_cache->loop_count) +
+ SCULPT_EXPAND_LOOP_THRESHOLD;
+
+ const float active_factor = fmod(expand_cache->active_falloff, loop_len);
+ const float falloff_factor = fmod(expand_cache->face_falloff[f], loop_len);
+ enabled = falloff_factor < active_factor;
+ }
+
+ if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET) {
+ if (ss->face_sets[f] == expand_cache->initial_active_face_set) {
+ enabled = false;
+ }
+ }
+
+ if (expand_cache->invert) {
+ enabled = !enabled;
+ }
+
+ return enabled;
+}
+
+/**
+ * For target modes that support gradients (such as sculpt masks or colors), this function returns
+ * the corresponding gradient value for an enabled vertex.
+ */
+static float sculpt_expand_gradient_value_get(SculptSession *ss,
+ ExpandCache *expand_cache,
+ const int v)
+{
+ if (!expand_cache->falloff_gradient) {
+ return 1.0f;
+ }
+
+ const float max_falloff_factor = sculpt_expand_max_vertex_falloff_get(expand_cache);
+ const float loop_len = (max_falloff_factor / expand_cache->loop_count) +
+ SCULPT_EXPAND_LOOP_THRESHOLD;
+
+ const float vertex_falloff_factor = sculpt_expand_falloff_value_vertex_get(ss, expand_cache, v);
+ const float active_factor = fmod(expand_cache->active_falloff, loop_len);
+ const float falloff_factor = fmod(vertex_falloff_factor, loop_len);
+
+ float linear_falloff;
+
+ if (expand_cache->invert) {
+ /* Active factor is the result of a modulus operation using loop_len, so they will never be
+ * equal and loop_len - active_factor should never be 0. */
+ BLI_assert((loop_len - active_factor) != 0.0f);
+ linear_falloff = (falloff_factor - active_factor) / (loop_len - active_factor);
+ }
+ else {
+ linear_falloff = 1.0f - (falloff_factor / active_factor);
+ }
+
+ if (!expand_cache->brush_gradient) {
+ return linear_falloff;
+ }
+
+ return BKE_brush_curve_strength(expand_cache->brush, linear_falloff, 1.0f);
+}
+
+/* Utility functions for getting all vertices state during expand. */
+
+/**
+ * Returns a bitmap indexed by vertex index which contains if the vertex was enabled or not for a
+ * give expand_cache state.
+ */
+static BLI_bitmap *sculpt_expand_bitmap_from_enabled(SculptSession *ss, ExpandCache *expand_cache)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices");
+ for (int i = 0; i < totvert; i++) {
+ const bool enabled = sculpt_expand_state_get(ss, expand_cache, i);
+ BLI_BITMAP_SET(enabled_vertices, i, enabled);
+ }
+ return enabled_vertices;
+}
+
+/**
+ * Returns a bitmap indexed by vertex index which contains if the vertex is in the boundary of the
+ * enabled vertices. This is defined as vertices that are enabled and at least have one connected
+ * vertex that is not enabled.
+ */
+static BLI_bitmap *sculpt_expand_boundary_from_enabled(SculptSession *ss,
+ const BLI_bitmap *enabled_vertices,
+ const bool use_mesh_boundary)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ BLI_bitmap *boundary_vertices = BLI_BITMAP_NEW(totvert, "boundary vertices");
+ for (int i = 0; i < totvert; i++) {
+ if (!BLI_BITMAP_TEST(enabled_vertices, i)) {
+ continue;
+ }
+
+ bool is_expand_boundary = false;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
+ if (!BLI_BITMAP_TEST(enabled_vertices, ni.index)) {
+ is_expand_boundary = true;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, i)) {
+ is_expand_boundary = true;
+ }
+
+ BLI_BITMAP_SET(boundary_vertices, i, is_expand_boundary);
+ }
+
+ return boundary_vertices;
+}
+
+/* Functions implementing different algorithms for initializing falloff values. */
+
+/**
+ * Utility function to get the closet vertex after flipping an original vertex position based on
+ * an symmetry pass iteration index.
+ */
+static int sculpt_expand_get_vertex_index_for_symmetry_pass(Object *ob,
+ const char symm_it,
+ const int original_vertex)
+{
+ SculptSession *ss = ob->sculpt;
+ int symm_vertex = SCULPT_EXPAND_VERTEX_NONE;
+ if (symm_it == 0) {
+ symm_vertex = original_vertex;
+ }
+ else {
+ float location[3];
+ flip_v3_v3(location, SCULPT_vertex_co_get(ss, original_vertex), symm_it);
+ symm_vertex = SCULPT_nearest_vertex_get(NULL, ob, location, FLT_MAX, false);
+ }
+ return symm_vertex;
+}
+
+/**
+ * Geodesic: Initializes the falloff with geodesic distances from the given active vertex, taking
+ * symmetry into account.
+ */
+static float *sculpt_expand_geodesic_falloff_create(Sculpt *sd, Object *ob, const int v)
+{
+ return SCULPT_geodesic_from_vertex_and_symm(sd, ob, v, FLT_MAX);
+}
+
+/**
+ * Topology: Initializes the falloff using a flood-fill operation,
+ * increasing the falloff value by 1 when visiting a new vertex.
+ */
+typedef struct ExpandFloodFillData {
+ float original_normal[3];
+ float edge_sensitivity;
+ float *dists;
+ float *edge_factor;
+} ExpandFloodFillData;
+
+static bool expand_topology_floodfill_cb(
+ SculptSession *UNUSED(ss), int from_v, int to_v, bool is_duplicate, void *userdata)
+{
+ ExpandFloodFillData *data = userdata;
+ if (!is_duplicate) {
+ const float to_it = data->dists[from_v] + 1.0f;
+ data->dists[to_v] = to_it;
+ }
+ else {
+ data->dists[to_v] = data->dists[from_v];
+ }
+ return true;
+}
+
+static float *sculpt_expand_topology_falloff_create(Sculpt *sd, Object *ob, const int v)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "topology dist");
+
+ SculptFloodFill flood;
+ SCULPT_floodfill_init(ss, &flood);
+ SCULPT_floodfill_add_initial_with_symmetry(sd, ob, ss, &flood, v, FLT_MAX);
+
+ ExpandFloodFillData fdata;
+ fdata.dists = dists;
+
+ SCULPT_floodfill_execute(ss, &flood, expand_topology_floodfill_cb, &fdata);
+ SCULPT_floodfill_free(&flood);
+
+ return dists;
+}
+
+/**
+ * Normals: Flood-fills the mesh and reduces the falloff depending on the normal difference between
+ * each vertex and the previous one.
+ * This creates falloff patterns that follow and snap to the hard edges of the object.
+ */
+static bool mask_expand_normal_floodfill_cb(
+ SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
+{
+ ExpandFloodFillData *data = userdata;
+ if (!is_duplicate) {
+ float current_normal[3], prev_normal[3];
+ SCULPT_vertex_normal_get(ss, to_v, current_normal);
+ SCULPT_vertex_normal_get(ss, from_v, prev_normal);
+ const float from_edge_factor = data->edge_factor[from_v];
+ data->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * from_edge_factor;
+ data->dists[to_v] = dot_v3v3(data->original_normal, current_normal) *
+ powf(from_edge_factor, data->edge_sensitivity);
+ CLAMP(data->dists[to_v], 0.0f, 1.0f);
+ }
+ else {
+ /* PBVH_GRIDS duplicate handling. */
+ data->edge_factor[to_v] = data->edge_factor[from_v];
+ data->dists[to_v] = data->dists[from_v];
+ }
+
+ return true;
+}
+
+static float *sculpt_expand_normal_falloff_create(Sculpt *sd,
+ Object *ob,
+ const int v,
+ const float edge_sensitivity)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *dists = MEM_malloc_arrayN(sizeof(float), totvert, "normal dist");
+ float *edge_factor = MEM_callocN(sizeof(float) * totvert, "mask edge factor");
+ for (int i = 0; i < totvert; i++) {
+ edge_factor[i] = 1.0f;
+ }
+
+ SculptFloodFill flood;
+ SCULPT_floodfill_init(ss, &flood);
+ SCULPT_floodfill_add_initial_with_symmetry(sd, ob, ss, &flood, v, FLT_MAX);
+
+ ExpandFloodFillData fdata;
+ fdata.dists = dists;
+ fdata.edge_factor = edge_factor;
+ fdata.edge_sensitivity = edge_sensitivity;
+ SCULPT_vertex_normal_get(ss, v, fdata.original_normal);
+
+ SCULPT_floodfill_execute(ss, &flood, mask_expand_normal_floodfill_cb, &fdata);
+ SCULPT_floodfill_free(&flood);
+
+ for (int repeat = 0; repeat < 2; repeat++) {
+ for (int i = 0; i < totvert; i++) {
+ float avg = 0.0f;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
+ avg += dists[ni.index];
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ dists[i] = avg / ni.size;
+ }
+ }
+
+ MEM_SAFE_FREE(edge_factor);
+
+ return dists;
+}
+
+/**
+ * Spherical: Initializes the falloff based on the distance from a vertex, taking symmetry into
+ * account.
+ */
+static float *sculpt_expand_spherical_falloff_create(Object *ob, const int v)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ float *dists = MEM_malloc_arrayN(sizeof(float), totvert, "spherical dist");
+ for (int i = 0; i < totvert; i++) {
+ dists[i] = FLT_MAX;
+ }
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+ const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
+ if (symm_vertex != -1) {
+ const float *co = SCULPT_vertex_co_get(ss, symm_vertex);
+ for (int i = 0; i < totvert; i++) {
+ dists[i] = min_ff(dists[i], len_v3v3(co, SCULPT_vertex_co_get(ss, i)));
+ }
+ }
+ }
+
+ return dists;
+}
+
+/**
+ * Boundary: This falloff mode uses the code from sculpt_boundary to initialize the closest mesh
+ * boundary to a falloff value of 0. Then, it propagates that falloff to the rest of the mesh so it
+ * stays parallel to the boundary, increasing the falloff value by 1 on each step.
+ */
+static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const int v)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist");
+ BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices");
+ GSQueue *queue = BLI_gsqueue_new(sizeof(int));
+
+ /* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
+
+ SculptBoundary *boundary = SCULPT_boundary_data_init(ob, NULL, symm_vertex, FLT_MAX);
+ if (!boundary) {
+ continue;
+ }
+
+ for (int i = 0; i < boundary->num_vertices; i++) {
+ BLI_gsqueue_push(queue, &boundary->vertices[i]);
+ BLI_BITMAP_ENABLE(visited_vertices, boundary->vertices[i]);
+ }
+ SCULPT_boundary_data_free(boundary);
+ }
+
+ /* If there are no boundaries, return a falloff with all values set to 0. */
+ if (BLI_gsqueue_is_empty(queue)) {
+ return dists;
+ }
+
+ /* Propagate the values from the boundaries to the rest of the mesh. */
+ while (!BLI_gsqueue_is_empty(queue)) {
+ int v_next;
+ BLI_gsqueue_pop(queue, &v_next);
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_next, ni) {
+ if (BLI_BITMAP_TEST(visited_vertices, ni.index)) {
+ continue;
+ }
+ dists[ni.index] = dists[v_next] + 1.0f;
+ BLI_BITMAP_ENABLE(visited_vertices, ni.index);
+ BLI_gsqueue_push(queue, &ni.index);
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ }
+
+ BLI_gsqueue_free(queue);
+ MEM_freeN(visited_vertices);
+ return dists;
+}
+
+/**
+ * Topology diagonals. This falloff is similar to topology, but it also considers the diagonals of
+ * the base mesh faces when checking a vertex neighbor. For this reason, this is not implement
+ * using the general flood-fill and sculpt neighbors accessors.
+ */
+static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist");
+
+ /* This algorithm uses mesh data (polys and loops), so this falloff type can't be initialized for
+ * Multires. It also does not make sense to implement it for dyntopo as the result will be the
+ * same as Topology falloff. */
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return dists;
+ }
+
+ /* Search and mask as visited the initial vertices using the enabled symmetry passes. */
+ BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices");
+ GSQueue *queue = BLI_gsqueue_new(sizeof(int));
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
+
+ BLI_gsqueue_push(queue, &symm_vertex);
+ BLI_BITMAP_ENABLE(visited_vertices, symm_vertex);
+ }
+
+ if (BLI_gsqueue_is_empty(queue)) {
+ return dists;
+ }
+
+ /* Propagate the falloff increasing the value by 1 each time a new vertex is visited. */
+ Mesh *mesh = ob->data;
+ while (!BLI_gsqueue_is_empty(queue)) {
+ int v_next;
+ BLI_gsqueue_pop(queue, &v_next);
+ for (int j = 0; j < ss->pmap[v_next].count; j++) {
+ MPoly *p = &ss->mpoly[ss->pmap[v_next].indices[j]];
+ for (int l = 0; l < p->totloop; l++) {
+ const int neighbor_v = mesh->mloop[p->loopstart + l].v;
+ if (BLI_BITMAP_TEST(visited_vertices, neighbor_v)) {
+ continue;
+ }
+ dists[neighbor_v] = dists[v_next] + 1.0f;
+ BLI_BITMAP_ENABLE(visited_vertices, neighbor_v);
+ BLI_gsqueue_push(queue, &neighbor_v);
+ }
+ }
+ }
+
+ BLI_gsqueue_free(queue);
+ MEM_freeN(visited_vertices);
+ return dists;
+}
+
+/* Functions to update the max_falloff value in the #ExpandCache. These functions are called after
+ * initializing a new falloff to make sure that this value is always updated. */
+
+/**
+ * Updates the max_falloff value for vertices in a #ExpandCache based on the current values of the
+ * falloff, skipping any invalid values initialized to FLT_MAX and not initialized components.
+ */
+static void sculpt_expand_update_max_vert_falloff_value(SculptSession *ss,
+ ExpandCache *expand_cache)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ expand_cache->max_vert_falloff = -FLT_MAX;
+ for (int i = 0; i < totvert; i++) {
+ if (expand_cache->vert_falloff[i] == FLT_MAX) {
+ continue;
+ }
+
+ if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) {
+ continue;
+ }
+
+ expand_cache->max_vert_falloff = max_ff(expand_cache->max_vert_falloff,
+ expand_cache->vert_falloff[i]);
+ }
+}
+
+/**
+ * Updates the max_falloff value for faces in a ExpandCache based on the current values of the
+ * falloff, skipping any invalid values initialized to FLT_MAX and not initialized components.
+ */
+static void sculpt_expand_update_max_face_falloff_factor(SculptSession *ss,
+ ExpandCache *expand_cache)
+{
+ const int totface = ss->totfaces;
+ expand_cache->max_face_falloff = -FLT_MAX;
+ for (int i = 0; i < totface; i++) {
+ if (expand_cache->face_falloff[i] == FLT_MAX) {
+ continue;
+ }
+
+ if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) {
+ continue;
+ }
+
+ expand_cache->max_face_falloff = max_ff(expand_cache->max_face_falloff,
+ expand_cache->face_falloff[i]);
+ }
+}
+
+/**
+ * Functions to get falloff values for faces from the values from the vertices. This is used for
+ * expanding Face Sets. Depending on the data type of the #SculptSession, this needs to get the per
+ * face falloff value from the connected vertices of each face or from the grids stored per loops
+ * for each face.
+ */
+static void sculpt_expand_grids_to_faces_falloff(SculptSession *ss,
+ Mesh *mesh,
+ ExpandCache *expand_cache)
+{
+
+ const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh);
+
+ for (int p = 0; p < mesh->totpoly; p++) {
+ MPoly *poly = &mesh->mpoly[p];
+ float accum = 0.0f;
+ for (int l = 0; l < poly->totloop; l++) {
+ const int grid_loop_index = (poly->loopstart + l) * key->grid_area;
+ for (int g = 0; g < key->grid_area; g++) {
+ accum += expand_cache->vert_falloff[grid_loop_index + g];
+ }
+ }
+ expand_cache->face_falloff[p] = accum / (poly->totloop * key->grid_area);
+ }
+}
+
+static void sculpt_expand_vertex_to_faces_falloff(Mesh *mesh, ExpandCache *expand_cache)
+{
+ for (int p = 0; p < mesh->totpoly; p++) {
+ MPoly *poly = &mesh->mpoly[p];
+ float accum = 0.0f;
+ for (int l = 0; l < poly->totloop; l++) {
+ MLoop *loop = &mesh->mloop[l + poly->loopstart];
+ accum += expand_cache->vert_falloff[loop->v];
+ }
+ expand_cache->face_falloff[p] = accum / poly->totloop;
+ }
+}
+
+/**
+ * Main function to update the faces falloff from a already calculated vertex falloff.
+ */
+static void sculpt_expand_mesh_face_falloff_from_vertex_falloff(SculptSession *ss,
+ Mesh *mesh,
+ ExpandCache *expand_cache)
+{
+ BLI_assert(expand_cache->vert_falloff != NULL);
+
+ if (!expand_cache->face_falloff) {
+ expand_cache->face_falloff = MEM_malloc_arrayN(
+ mesh->totpoly, sizeof(float), "face falloff factors");
+ }
+
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ sculpt_expand_vertex_to_faces_falloff(mesh, expand_cache);
+ }
+ else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
+ sculpt_expand_grids_to_faces_falloff(ss, mesh, expand_cache);
+ }
+ else {
+ BLI_assert(false);
+ }
+}
+
+/* Recursions. These functions will generate new falloff values based on the state of the vertices
+ * from the current ExpandCache options and falloff values. */
+
+/**
+ * Geodesic recursion: Initializes falloff values using geodesic distances from the boundary of the
+ * current vertices state.
+ */
+static void sculpt_expand_geodesics_from_state_boundary(Object *ob,
+ ExpandCache *expand_cache,
+ BLI_bitmap *enabled_vertices)
+{
+ SculptSession *ss = ob->sculpt;
+ BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES);
+
+ GSet *initial_vertices = BLI_gset_int_new("initial_vertices");
+ BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false);
+ const int totvert = SCULPT_vertex_count_get(ss);
+ for (int i = 0; i < totvert; i++) {
+ if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
+ continue;
+ }
+ BLI_gset_add(initial_vertices, POINTER_FROM_INT(i));
+ }
+ MEM_freeN(boundary_vertices);
+
+ MEM_SAFE_FREE(expand_cache->vert_falloff);
+ MEM_SAFE_FREE(expand_cache->face_falloff);
+
+ expand_cache->vert_falloff = SCULPT_geodesic_distances_create(ob, initial_vertices, FLT_MAX);
+ BLI_gset_free(initial_vertices, NULL);
+}
+
+/**
+ * Topology recursion: Initializes falloff values using topology steps from the boundary of the
+ * current vertices state, increasing the value by 1 each time a new vertex is visited.
+ */
+static void sculpt_expand_topology_from_state_boundary(Object *ob,
+ ExpandCache *expand_cache,
+ BLI_bitmap *enabled_vertices)
+{
+ MEM_SAFE_FREE(expand_cache->vert_falloff);
+ MEM_SAFE_FREE(expand_cache->face_falloff);
+
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "topology dist");
+ BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false);
+
+ SculptFloodFill flood;
+ SCULPT_floodfill_init(ss, &flood);
+ for (int i = 0; i < totvert; i++) {
+ if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
+ continue;
+ }
+ SCULPT_floodfill_add_and_skip_initial(&flood, i);
+ }
+ MEM_freeN(boundary_vertices);
+
+ ExpandFloodFillData fdata;
+ fdata.dists = dists;
+ SCULPT_floodfill_execute(ss, &flood, expand_topology_floodfill_cb, &fdata);
+ SCULPT_floodfill_free(&flood);
+
+ expand_cache->vert_falloff = dists;
+}
+
+/**
+ * Main function to create a recursion step from the current #ExpandCache state.
+ */
+static void sculpt_expand_resursion_step_add(Object *ob,
+ ExpandCache *expand_cache,
+ const eSculptExpandRecursionType recursion_type)
+{
+ SculptSession *ss = ob->sculpt;
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return;
+ }
+
+ BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
+
+ /* Each time a new recursion step is created, reset the distortion strength. This is the expected
+ * result from the recursion, as otherwise the new falloff will render with undesired distortion
+ * from the beginning. */
+ expand_cache->texture_distortion_strength = 0.0f;
+
+ switch (recursion_type) {
+ case SCULPT_EXPAND_RECURSION_GEODESICS:
+ sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
+ break;
+ case SCULPT_EXPAND_RECURSION_TOPOLOGY:
+ sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices);
+ break;
+ }
+
+ sculpt_expand_update_max_vert_falloff_value(ss, expand_cache);
+ if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
+ sculpt_expand_mesh_face_falloff_from_vertex_falloff(ss, ob->data, expand_cache);
+ sculpt_expand_update_max_face_falloff_factor(ss, expand_cache);
+ }
+
+ MEM_freeN(enabled_vertices);
+}
+
+/* Face Set Boundary falloff. */
+
+/**
+ * When internal falloff is set to true, the falloff will fill the active Face Set with a gradient,
+ * otherwise the active Face Set will be filled with a constant falloff of 0.0f.
+ */
+static void sculpt_expand_initialize_from_face_set_boundary(Object *ob,
+ ExpandCache *expand_cache,
+ const int active_face_set,
+ const bool internal_falloff)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices");
+ for (int i = 0; i < totvert; i++) {
+ if (!SCULPT_vertex_has_unique_face_set(ss, i)) {
+ continue;
+ }
+ if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) {
+ continue;
+ }
+ BLI_BITMAP_ENABLE(enabled_vertices, i);
+ }
+
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
+ }
+ else {
+ sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices);
+ }
+
+ MEM_freeN(enabled_vertices);
+
+ if (internal_falloff) {
+ for (int i = 0; i < totvert; i++) {
+ if (!(SCULPT_vertex_has_face_set(ss, i, active_face_set) &&
+ SCULPT_vertex_has_unique_face_set(ss, i))) {
+ continue;
+ }
+ expand_cache->vert_falloff[i] *= -1.0f;
+ }
+
+ float min_factor = FLT_MAX;
+ for (int i = 0; i < totvert; i++) {
+ min_factor = min_ff(expand_cache->vert_falloff[i], min_factor);
+ }
+
+ const float additional_falloff = fabsf(min_factor);
+ for (int i = 0; i < totvert; i++) {
+ expand_cache->vert_falloff[i] += additional_falloff;
+ }
+ }
+ else {
+ for (int i = 0; i < totvert; i++) {
+ if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) {
+ continue;
+ }
+ expand_cache->vert_falloff[i] = 0.0f;
+ }
+ }
+}
+
+/**
+ * Main function to initialize new falloff values in a #ExpandCache given an initial vertex and a
+ * falloff type.
+ */
+static void sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ ExpandCache *expand_cache,
+ Sculpt *sd,
+ Object *ob,
+ const int v,
+ eSculptExpandFalloffType falloff_type)
+{
+ MEM_SAFE_FREE(expand_cache->vert_falloff);
+ expand_cache->falloff_type = falloff_type;
+
+ SculptSession *ss = ob->sculpt;
+ const bool has_topology_info = BKE_pbvh_type(ss->pbvh) == PBVH_FACES;
+
+ switch (falloff_type) {
+ case SCULPT_EXPAND_FALLOFF_GEODESIC:
+ expand_cache->vert_falloff = has_topology_info ?
+ sculpt_expand_geodesic_falloff_create(sd, ob, v) :
+ sculpt_expand_spherical_falloff_create(ob, v);
+ break;
+ case SCULPT_EXPAND_FALLOFF_TOPOLOGY:
+ expand_cache->vert_falloff = sculpt_expand_topology_falloff_create(sd, ob, v);
+ break;
+ case SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS:
+ expand_cache->vert_falloff = has_topology_info ?
+ sculpt_expand_diagonals_falloff_create(ob, v) :
+ sculpt_expand_topology_falloff_create(sd, ob, v);
+ break;
+ case SCULPT_EXPAND_FALLOFF_NORMALS:
+ expand_cache->vert_falloff = sculpt_expand_normal_falloff_create(
+ sd, ob, v, SCULPT_EXPAND_NORMALS_FALLOFF_EDGE_SENSITIVITY);
+ break;
+ case SCULPT_EXPAND_FALLOFF_SPHERICAL:
+ expand_cache->vert_falloff = sculpt_expand_spherical_falloff_create(ob, v);
+ break;
+ case SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY:
+ expand_cache->vert_falloff = sculpt_expand_boundary_topology_falloff_create(ob, v);
+ break;
+ case SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET:
+ sculpt_expand_initialize_from_face_set_boundary(
+ ob, expand_cache, expand_cache->initial_active_face_set, true);
+ break;
+ case SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET:
+ sculpt_expand_initialize_from_face_set_boundary(
+ ob, expand_cache, expand_cache->initial_active_face_set, false);
+ break;
+ }
+
+ /* Update max falloff values and propagate to base mesh faces if needed. */
+ sculpt_expand_update_max_vert_falloff_value(ss, expand_cache);
+ if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
+ sculpt_expand_mesh_face_falloff_from_vertex_falloff(ss, ob->data, expand_cache);
+ sculpt_expand_update_max_face_falloff_factor(ss, expand_cache);
+ }
+}
+
+/**
+ * Adds to the snapping Face Set `gset` all Face Sets which contain all enabled vertices for the
+ * current #ExpandCache state. This improves the usability of snapping, as already enabled elements
+ * won't switch their state when toggling snapping with the modal key-map.
+ */
+static void sculpt_expand_snap_initialize_from_enabled(SculptSession *ss,
+ ExpandCache *expand_cache)
+{
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return;
+ }
+
+ /* Make sure this code runs with snapping and invert disabled. This simplifies the code and
+ * prevents using this function with snapping already enabled. */
+ const bool prev_snap_state = expand_cache->snap;
+ const bool prev_invert_state = expand_cache->invert;
+ expand_cache->snap = false;
+ expand_cache->invert = false;
+
+ BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
+
+ const int totface = ss->totfaces;
+ for (int i = 0; i < totface; i++) {
+ const int face_set = expand_cache->original_face_sets[i];
+ BLI_gset_add(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
+ }
+
+ for (int p = 0; p < totface; p++) {
+ MPoly *poly = &ss->mpoly[p];
+ bool any_disabled = false;
+ for (int l = 0; l < poly->totloop; l++) {
+ MLoop *loop = &ss->mloop[l + poly->loopstart];
+ if (!BLI_BITMAP_TEST(enabled_vertices, loop->v)) {
+ any_disabled = true;
+ break;
+ }
+ }
+ if (any_disabled) {
+ const int face_set = expand_cache->original_face_sets[p];
+ BLI_gset_remove(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set), NULL);
+ }
+ }
+
+ MEM_freeN(enabled_vertices);
+ expand_cache->snap = prev_snap_state;
+ expand_cache->invert = prev_invert_state;
+}
+
+/**
+ * Functions to free a #ExpandCache.
+ */
+static void sculpt_expand_cache_data_free(ExpandCache *expand_cache)
+{
+ if (expand_cache->snap_enabled_face_sets) {
+ BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
+ }
+ MEM_SAFE_FREE(expand_cache->nodes);
+ MEM_SAFE_FREE(expand_cache->vert_falloff);
+ MEM_SAFE_FREE(expand_cache->face_falloff);
+ MEM_SAFE_FREE(expand_cache->original_mask);
+ MEM_SAFE_FREE(expand_cache->original_face_sets);
+ MEM_SAFE_FREE(expand_cache->initial_face_sets);
+ MEM_SAFE_FREE(expand_cache->original_colors);
+ MEM_SAFE_FREE(expand_cache);
+}
+
+static void sculpt_expand_cache_free(SculptSession *ss)
+{
+ sculpt_expand_cache_data_free(ss->expand_cache);
+ /* Needs to be set to NULL as the paint cursor relies on checking this pointer detecting if an
+ * expand operation is running. */
+ ss->expand_cache = NULL;
+}
+
+/**
+ * Functions to restore the original state from the #ExpandCache when canceling the operator.
+ */
+static void sculpt_expand_restore_face_set_data(SculptSession *ss, ExpandCache *expand_cache)
+{
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ for (int n = 0; n < totnode; n++) {
+ PBVHNode *node = nodes[n];
+ BKE_pbvh_node_mark_redraw(node);
+ }
+ MEM_freeN(nodes);
+ for (int i = 0; i < ss->totfaces; i++) {
+ ss->face_sets[i] = expand_cache->original_face_sets[i];
+ }
+}
+
+static void sculpt_expand_restore_color_data(SculptSession *ss, ExpandCache *expand_cache)
+{
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ for (int n = 0; n < totnode; n++) {
+ PBVHNode *node = nodes[n];
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
+ {
+ copy_v4_v4(vd.col, expand_cache->original_colors[vd.index]);
+ }
+ BKE_pbvh_vertex_iter_end;
+ BKE_pbvh_node_mark_redraw(node);
+ }
+ MEM_freeN(nodes);
+}
+
+static void sculpt_expand_restore_mask_data(SculptSession *ss, ExpandCache *expand_cache)
+{
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ for (int n = 0; n < totnode; n++) {
+ PBVHNode *node = nodes[n];
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
+ {
+ *vd.mask = expand_cache->original_mask[vd.index];
+ }
+ BKE_pbvh_vertex_iter_end;
+ BKE_pbvh_node_mark_redraw(node);
+ }
+ MEM_freeN(nodes);
+}
+
+/* Main function to restore the original state of the data to how it was before starting the expand
+ * operation. */
+static void sculpt_expand_restore_original_state(bContext *C,
+ Object *ob,
+ ExpandCache *expand_cache)
+{
+
+ SculptSession *ss = ob->sculpt;
+ switch (expand_cache->target) {
+ case SCULPT_EXPAND_TARGET_MASK:
+ sculpt_expand_restore_mask_data(ss, expand_cache);
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ SCULPT_tag_update_overlays(C);
+ break;
+ case SCULPT_EXPAND_TARGET_FACE_SETS:
+ sculpt_expand_restore_face_set_data(ss, expand_cache);
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ SCULPT_tag_update_overlays(C);
+ break;
+ case SCULPT_EXPAND_TARGET_COLORS:
+ sculpt_expand_restore_color_data(ss, expand_cache);
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR);
+ break;
+ }
+}
+
+/**
+ * Cancel operator callback.
+ */
+static void sculpt_expand_cancel(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+
+ sculpt_expand_restore_original_state(C, ob, ss->expand_cache);
+
+ SCULPT_undo_push_end();
+ sculpt_expand_cache_free(ss);
+}
+
+/* Functions to update the sculpt mesh data. */
+
+/**
+ * Callback to update mask data per PBVH node.
+ */
+static void sculpt_expand_mask_update_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHNode *node = data->nodes[i];
+ ExpandCache *expand_cache = ss->expand_cache;
+
+ bool any_changed = false;
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL)
+ {
+ const float initial_mask = *vd.mask;
+ const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index);
+
+ float new_mask;
+
+ if (enabled) {
+ new_mask = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index);
+ }
+ else {
+ new_mask = 0.0f;
+ }
+
+ if (expand_cache->preserve) {
+ new_mask = max_ff(new_mask, expand_cache->original_mask[vd.index]);
+ }
+
+ if (new_mask == initial_mask) {
+ continue;
+ }
+
+ *vd.mask = clamp_f(new_mask, 0.0f, 1.0f);
+ any_changed = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ if (any_changed) {
+ BKE_pbvh_node_mark_update_mask(node);
+ }
+}
+
+/**
+ * Update Face Set data. Not multi-threaded per node as nodes don't contain face arrays.
+ */
+static void sculpt_expand_face_sets_update(SculptSession *ss, ExpandCache *expand_cache)
+{
+ const int totface = ss->totfaces;
+ for (int f = 0; f < totface; f++) {
+ const bool enabled = sculpt_expand_face_state_get(ss, expand_cache, f);
+ if (!enabled) {
+ continue;
+ }
+ if (expand_cache->preserve) {
+ ss->face_sets[f] += expand_cache->next_face_set;
+ }
+ else {
+ ss->face_sets[f] = expand_cache->next_face_set;
+ }
+ }
+
+ for (int i = 0; i < expand_cache->totnode; i++) {
+ BKE_pbvh_node_mark_redraw(ss->expand_cache->nodes[i]);
+ }
+}
+
+/**
+ * Callback to update vertex colors per PBVH node.
+ */
+static void sculpt_expand_colors_update_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHNode *node = data->nodes[i];
+ ExpandCache *expand_cache = ss->expand_cache;
+
+ bool any_changed = false;
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL)
+ {
+ float initial_color[4];
+ copy_v4_v4(initial_color, vd.col);
+
+ const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index);
+ float fade;
+
+ if (enabled) {
+ fade = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index);
+ }
+ else {
+ fade = 0.0f;
+ }
+
+ fade *= 1.0f - *vd.mask;
+ fade = clamp_f(fade, 0.0f, 1.0f);
+
+ float final_color[4];
+ float final_fill_color[4];
+ mul_v4_v4fl(final_fill_color, expand_cache->fill_color, fade);
+ IMB_blend_color_float(final_color,
+ expand_cache->original_colors[vd.index],
+ final_fill_color,
+ expand_cache->blend_mode);
+
+ if (equals_v4v4(initial_color, final_color)) {
+ continue;
+ }
+
+ copy_v4_v4(vd.col, final_color);
+ any_changed = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ if (any_changed) {
+ BKE_pbvh_node_mark_update_color(node);
+ }
+}
+
+static void sculpt_expand_flush_updates(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ switch (ss->expand_cache->target) {
+ case SCULPT_EXPAND_TARGET_MASK:
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
+ break;
+ case SCULPT_EXPAND_TARGET_FACE_SETS:
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
+ break;
+ case SCULPT_EXPAND_TARGET_COLORS:
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Store the original mesh data state in the expand cache. */
+static void sculpt_expand_original_state_store(Object *ob, ExpandCache *expand_cache)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ const int totface = ss->totfaces;
+
+ /* Face Sets are always stored as they are needed for snapping. */
+ expand_cache->initial_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "initial face set");
+ expand_cache->original_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "original face set");
+ for (int i = 0; i < totface; i++) {
+ expand_cache->initial_face_sets[i] = ss->face_sets[i];
+ expand_cache->original_face_sets[i] = ss->face_sets[i];
+ }
+
+ if (expand_cache->target == SCULPT_EXPAND_TARGET_MASK) {
+ expand_cache->original_mask = MEM_malloc_arrayN(totvert, sizeof(float), "initial mask");
+ for (int i = 0; i < totvert; i++) {
+ expand_cache->original_mask[i] = SCULPT_vertex_mask_get(ss, i);
+ }
+ }
+
+ if (expand_cache->target == SCULPT_EXPAND_TARGET_COLORS) {
+ expand_cache->original_colors = MEM_malloc_arrayN(totvert, sizeof(float[4]), "initial colors");
+ for (int i = 0; i < totvert; i++) {
+ copy_v4_v4(expand_cache->original_colors[i], SCULPT_vertex_color_get(ss, i));
+ }
+ }
+}
+
+/**
+ * Restore the state of the Face Sets before a new update.
+ */
+static void sculpt_expand_face_sets_restore(SculptSession *ss, ExpandCache *expand_cache)
+{
+ const int totfaces = ss->totfaces;
+ for (int i = 0; i < totfaces; i++) {
+ ss->face_sets[i] = expand_cache->initial_face_sets[i];
+ }
+}
+
+static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const int vertex)
+{
+ SculptSession *ss = ob->sculpt;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ ExpandCache *expand_cache = ss->expand_cache;
+
+ /* Update the active factor in the cache. */
+ if (vertex == SCULPT_EXPAND_VERTEX_NONE) {
+ /* This means that the cursor is not over the mesh, so a valid active falloff can't be
+ * determined. In this situations, don't evaluate enabled states and default all vertices in
+ * connected components to enabled. */
+ expand_cache->active_falloff = expand_cache->max_vert_falloff;
+ expand_cache->all_enabled = true;
+ }
+ else {
+ expand_cache->active_falloff = expand_cache->vert_falloff[vertex];
+ expand_cache->all_enabled = false;
+ }
+
+ if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
+ /* Face sets needs to be restored their initial state on each iteration as the overwrite
+ * existing data. */
+ sculpt_expand_face_sets_restore(ss, expand_cache);
+ }
+
+ /* Update the mesh sculpt data. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = expand_cache->nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, expand_cache->totnode);
+
+ switch (expand_cache->target) {
+ case SCULPT_EXPAND_TARGET_MASK:
+ BLI_task_parallel_range(
+ 0, expand_cache->totnode, &data, sculpt_expand_mask_update_task_cb, &settings);
+ break;
+ case SCULPT_EXPAND_TARGET_FACE_SETS:
+ sculpt_expand_face_sets_update(ss, expand_cache);
+ break;
+ case SCULPT_EXPAND_TARGET_COLORS:
+ BLI_task_parallel_range(
+ 0, expand_cache->totnode, &data, sculpt_expand_colors_update_task_cb, &settings);
+ break;
+ }
+
+ sculpt_expand_flush_updates(C);
+}
+
+/**
+ * Updates the #SculptSession cursor data and gets the active vertex
+ * if the cursor is over the mesh.
+ */
+static int sculpt_expand_target_vertex_update_and_get(bContext *C,
+ Object *ob,
+ const float mouse[2])
+{
+ SculptSession *ss = ob->sculpt;
+ SculptCursorGeometryInfo sgi;
+ if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) {
+ return SCULPT_active_vertex_get(ss);
+ }
+ return SCULPT_EXPAND_VERTEX_NONE;
+}
+
+/**
+ * Moves the sculpt pivot to the average point of the boundary enabled vertices of the current
+ * expand state. Take symmetry and active components into account.
+ */
+static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache *expand_cache)
+{
+ SculptSession *ss = ob->sculpt;
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ const bool initial_invert_state = expand_cache->invert;
+ expand_cache->invert = false;
+ BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
+
+ /* For boundary topology, position the pivot using only the boundary of the enabled vertices,
+ * without taking mesh boundary into account. This allows to create deformations like bending the
+ * mesh from the boundary of the mask that was just created. */
+ const float use_mesh_boundary = expand_cache->falloff_type !=
+ SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY;
+
+ BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(
+ ss, enabled_vertices, use_mesh_boundary);
+
+ /* Ignore invert state, as this is the expected behavior in most cases and mask are created in
+ * inverted state by default. */
+ expand_cache->invert = initial_invert_state;
+
+ int total = 0;
+ float avg[3] = {0.0f};
+
+ const float *expand_init_co = SCULPT_vertex_co_get(ss, expand_cache->initial_active_vertex);
+
+ for (int i = 0; i < totvert; i++) {
+ if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
+ continue;
+ }
+
+ if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) {
+ continue;
+ }
+
+ const float *vertex_co = SCULPT_vertex_co_get(ss, i);
+
+ if (!SCULPT_check_vertex_pivot_symmetry(vertex_co, expand_init_co, symm)) {
+ continue;
+ }
+
+ add_v3_v3(avg, vertex_co);
+ total++;
+ }
+
+ MEM_freeN(enabled_vertices);
+ MEM_freeN(boundary_vertices);
+
+ if (total > 0) {
+ mul_v3_v3fl(ss->pivot_pos, avg, 1.0f / total);
+ }
+
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
+}
+
+static void sculpt_expand_finish(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ SCULPT_undo_push_end();
+
+ /* Tag all nodes to redraw to avoid artifacts after the fast partial updates. */
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ for (int n = 0; n < totnode; n++) {
+ BKE_pbvh_node_mark_update_mask(nodes[n]);
+ }
+ MEM_freeN(nodes);
+
+ switch (ss->expand_cache->target) {
+ case SCULPT_EXPAND_TARGET_MASK:
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ break;
+ case SCULPT_EXPAND_TARGET_FACE_SETS:
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ break;
+ case SCULPT_EXPAND_TARGET_COLORS:
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR);
+ break;
+ }
+
+ sculpt_expand_cache_free(ss);
+ ED_workspace_status_text(C, NULL);
+}
+
+/**
+ * Finds and stores in the #ExpandCache the sculpt connected component index for each symmetry pass
+ * needed for expand.
+ */
+static void sculpt_expand_find_active_connected_components_from_vert(Object *ob,
+ ExpandCache *expand_cache,
+ const int initial_vertex)
+{
+ SculptSession *ss = ob->sculpt;
+ for (int i = 0; i < EXPAND_SYMM_AREAS; i++) {
+ expand_cache->active_connected_components[i] = EXPAND_ACTIVE_COMPONENT_NONE;
+ }
+
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(
+ ob, symm_it, initial_vertex);
+
+ expand_cache->active_connected_components[(int)symm_it] =
+ ss->vertex_info.connected_component[symm_vertex];
+ }
+}
+
+/**
+ * Stores the active vertex, Face Set and mouse coordinates in the #ExpandCache based on the
+ * current cursor position.
+ */
+static void sculpt_expand_set_initial_components_for_mouse(bContext *C,
+ Object *ob,
+ ExpandCache *expand_cache,
+ const float mouse[2])
+{
+ SculptSession *ss = ob->sculpt;
+ int initial_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mouse);
+ if (initial_vertex == SCULPT_EXPAND_VERTEX_NONE) {
+ /* Cursor not over the mesh, for creating valid initial falloffs, fallback to the last active
+ * vertex in the sculpt session. */
+ initial_vertex = SCULPT_active_vertex_get(ss);
+ }
+ copy_v2_v2(ss->expand_cache->initial_mouse, mouse);
+ expand_cache->initial_active_vertex = initial_vertex;
+ expand_cache->initial_active_face_set = SCULPT_active_face_set_get(ss);
+
+ if (expand_cache->next_face_set == SCULPT_FACE_SET_NONE) {
+ /* Only set the next face set once, otherwise this ID will constantly update to a new one each
+ * time this function is called for using a new initial vertex from a different cursor
+ * position. */
+ if (expand_cache->modify_active_face_set) {
+ expand_cache->next_face_set = SCULPT_active_face_set_get(ss);
+ }
+ else {
+ expand_cache->next_face_set = ED_sculpt_face_sets_find_next_available_id(ob->data);
+ }
+ }
+
+ /* The new mouse position can be over a different connected component, so this needs to be
+ * updated. */
+ sculpt_expand_find_active_connected_components_from_vert(ob, expand_cache, initial_vertex);
+}
+
+/**
+ * Displaces the initial mouse coordinates using the new mouse position to get a new active vertex.
+ * After that, initializes a new falloff of the same type with the new active vertex.
+ */
+static void sculpt_expand_move_propagation_origin(bContext *C,
+ Object *ob,
+ const wmEvent *event,
+ ExpandCache *expand_cache)
+{
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ const float mouse[2] = {event->mval[0], event->mval[1]};
+ float move_disp[2];
+ sub_v2_v2v2(move_disp, mouse, expand_cache->initial_mouse_move);
+
+ float new_mouse[2];
+ add_v2_v2v2(new_mouse, move_disp, expand_cache->original_mouse_move);
+
+ sculpt_expand_set_initial_components_for_mouse(C, ob, expand_cache, new_mouse);
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ expand_cache->move_preview_falloff_type);
+}
+
+/**
+ * Ensures that the #SculptSession contains the required data needed for Expand.
+ */
+static void sculpt_expand_ensure_sculptsession_data(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ SCULPT_vertex_random_access_ensure(ss);
+ SCULPT_connected_components_ensure(ob);
+ SCULPT_boundary_info_ensure(ob);
+ if (!ss->tex_pool) {
+ ss->tex_pool = BKE_image_pool_new();
+ }
+}
+
+/**
+ * Returns the active Face Sets ID from the enabled face or grid in the #SculptSession.
+ */
+static int sculpt_expand_active_face_set_id_get(SculptSession *ss, ExpandCache *expand_cache)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ return expand_cache->original_face_sets[ss->active_face_index];
+ case PBVH_GRIDS: {
+ const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg,
+ ss->active_grid_index);
+ return expand_cache->original_face_sets[face_index];
+ }
+ case PBVH_BMESH: {
+ /* Dyntopo does not support Face Set functionality. */
+ BLI_assert(false);
+ }
+ }
+ return SCULPT_FACE_SET_NONE;
+}
+
+static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ /* Skips INBETWEEN_MOUSEMOVE events and other events that may cause unnecessary updates. */
+ if (!ELEM(event->type, MOUSEMOVE, EVT_MODAL_MAP)) {
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ /* Update SculptSession data. */
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+ sculpt_expand_ensure_sculptsession_data(ob);
+
+ /* Update and get the active vertex (and face) from the cursor. */
+ const float mouse[2] = {event->mval[0], event->mval[1]};
+ const int target_expand_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mouse);
+
+ /* Handle the modal keymap state changes. */
+ ExpandCache *expand_cache = ss->expand_cache;
+ if (event->type == EVT_MODAL_MAP) {
+ switch (event->val) {
+ case SCULPT_EXPAND_MODAL_CANCEL: {
+ sculpt_expand_cancel(C, op);
+ return OPERATOR_FINISHED;
+ }
+ case SCULPT_EXPAND_MODAL_INVERT: {
+ expand_cache->invert = !expand_cache->invert;
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE: {
+ expand_cache->preserve = !expand_cache->preserve;
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE: {
+ expand_cache->falloff_gradient = !expand_cache->falloff_gradient;
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE: {
+ expand_cache->brush_gradient = !expand_cache->brush_gradient;
+ if (expand_cache->brush_gradient) {
+ expand_cache->falloff_gradient = true;
+ }
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_SNAP_TOGGLE: {
+ if (expand_cache->snap) {
+ expand_cache->snap = false;
+ if (expand_cache->snap_enabled_face_sets) {
+ BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
+ expand_cache->snap_enabled_face_sets = NULL;
+ }
+ }
+ else {
+ expand_cache->snap = true;
+ if (!expand_cache->snap_enabled_face_sets) {
+ expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets");
+ }
+ sculpt_expand_snap_initialize_from_enabled(ss, expand_cache);
+ }
+ } break;
+ case SCULPT_EXPAND_MODAL_MOVE_TOGGLE: {
+ if (expand_cache->move) {
+ expand_cache->move = false;
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ expand_cache->move_original_falloff_type);
+ break;
+ }
+ expand_cache->move = true;
+ expand_cache->move_original_falloff_type = expand_cache->falloff_type;
+ copy_v2_v2(expand_cache->initial_mouse_move, mouse);
+ copy_v2_v2(expand_cache->original_mouse_move, expand_cache->initial_mouse);
+ if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_GEODESIC &&
+ SCULPT_vertex_count_get(ss) > expand_cache->max_geodesic_move_preview) {
+ /* Set to spherical falloff for preview in high poly meshes as it is the fastest one.
+ * In most cases it should match closely the preview from geodesic. */
+ expand_cache->move_preview_falloff_type = SCULPT_EXPAND_FALLOFF_SPHERICAL;
+ }
+ else {
+ expand_cache->move_preview_falloff_type = expand_cache->falloff_type;
+ }
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC: {
+ sculpt_expand_resursion_step_add(ob, expand_cache, SCULPT_EXPAND_RECURSION_GEODESICS);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY: {
+ sculpt_expand_resursion_step_add(ob, expand_cache, SCULPT_EXPAND_RECURSION_TOPOLOGY);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_CONFIRM: {
+ sculpt_expand_update_for_vertex(C, ob, target_expand_vertex);
+
+ if (expand_cache->reposition_pivot) {
+ sculpt_expand_reposition_pivot(C, ob, expand_cache);
+ }
+
+ sculpt_expand_finish(C);
+ return OPERATOR_FINISHED;
+ }
+ case SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC: {
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ SCULPT_EXPAND_FALLOFF_GEODESIC);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY: {
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ SCULPT_EXPAND_FALLOFF_TOPOLOGY);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS: {
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL: {
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ expand_cache,
+ sd,
+ ob,
+ expand_cache->initial_active_vertex,
+ SCULPT_EXPAND_FALLOFF_SPHERICAL);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE: {
+ expand_cache->loop_count += 1;
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE: {
+ expand_cache->loop_count -= 1;
+ expand_cache->loop_count = max_ii(expand_cache->loop_count, 1);
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE: {
+ if (expand_cache->texture_distortion_strength == 0.0f) {
+ if (expand_cache->brush->mtex.tex == NULL) {
+ BKE_report(op->reports,
+ RPT_WARNING,
+ "Active brush does not contain any texture to distort the expand boundary");
+ break;
+ }
+ if (expand_cache->brush->mtex.brush_map_mode != MTEX_MAP_MODE_3D) {
+ BKE_report(op->reports,
+ RPT_WARNING,
+ "Texture mapping not set to 3D, results may be unpredictable");
+ }
+ }
+ expand_cache->texture_distortion_strength += SCULPT_EXPAND_TEXTURE_DISTORTION_STEP;
+ break;
+ }
+ case SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE: {
+ expand_cache->texture_distortion_strength -= SCULPT_EXPAND_TEXTURE_DISTORTION_STEP;
+ expand_cache->texture_distortion_strength = max_ff(
+ expand_cache->texture_distortion_strength, 0.0f);
+ break;
+ }
+ }
+ }
+
+ /* Handle expand origin movement if enabled. */
+ if (expand_cache->move) {
+ sculpt_expand_move_propagation_origin(C, ob, event, expand_cache);
+ }
+
+ /* Add new Face Sets IDs to the snapping gset if enabled. */
+ if (expand_cache->snap) {
+ const int active_face_set_id = sculpt_expand_active_face_set_id_get(ss, expand_cache);
+ if (!BLI_gset_haskey(expand_cache->snap_enabled_face_sets,
+ POINTER_FROM_INT(active_face_set_id))) {
+ BLI_gset_add(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(active_face_set_id));
+ }
+ }
+
+ /* Update the sculpt data with the current state of the #ExpandCache. */
+ sculpt_expand_update_for_vertex(C, ob, target_expand_vertex);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+/**
+ * Deletes the `delete_id` Face Set ID from the mesh Face Sets
+ * and stores the result in `r_face_set`.
+ * The faces that were using the `delete_id` Face Set are filled
+ * using the content from their neighbors.
+ */
+static void sculpt_expand_delete_face_set_id(
+ int *r_face_sets, Mesh *mesh, MeshElemMap *pmap, const int totface, const int delete_id)
+{
+ /* Check that all the face sets IDs in the mesh are not equal to `delete_id`
+ * before attempting to delete it. */
+ bool all_same_id = true;
+ for (int i = 0; i < totface; i++) {
+ if (r_face_sets[i] != delete_id) {
+ all_same_id = false;
+ break;
+ }
+ }
+ if (all_same_id) {
+ return;
+ }
+
+ BLI_LINKSTACK_DECLARE(queue, void *);
+ BLI_LINKSTACK_DECLARE(queue_next, void *);
+
+ BLI_LINKSTACK_INIT(queue);
+ BLI_LINKSTACK_INIT(queue_next);
+
+ for (int i = 0; i < totface; i++) {
+ if (r_face_sets[i] == delete_id) {
+ BLI_LINKSTACK_PUSH(queue, POINTER_FROM_INT(i));
+ }
+ }
+
+ while (BLI_LINKSTACK_SIZE(queue)) {
+ while (BLI_LINKSTACK_SIZE(queue)) {
+ const int f_index = POINTER_AS_INT(BLI_LINKSTACK_POP(queue));
+ int other_id = delete_id;
+ const MPoly *c_poly = &mesh->mpoly[f_index];
+ for (int l = 0; l < c_poly->totloop; l++) {
+ const MLoop *c_loop = &mesh->mloop[c_poly->loopstart + l];
+ const MeshElemMap *vert_map = &pmap[c_loop->v];
+ for (int i = 0; i < vert_map->count; i++) {
+
+ const int neighbor_face_index = vert_map->indices[i];
+ if (r_face_sets[neighbor_face_index] != delete_id) {
+ other_id = r_face_sets[neighbor_face_index];
+ }
+ }
+ }
+
+ if (other_id != delete_id) {
+ r_face_sets[f_index] = other_id;
+ }
+ else {
+ BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(f_index));
+ }
+ }
+
+ BLI_LINKSTACK_SWAP(queue, queue_next);
+ }
+
+ BLI_LINKSTACK_FREE(queue);
+ BLI_LINKSTACK_FREE(queue_next);
+}
+
+static void sculpt_expand_cache_initial_config_set(bContext *C,
+ wmOperator *op,
+ ExpandCache *expand_cache)
+{
+ /* RNA properties. */
+ expand_cache->invert = RNA_boolean_get(op->ptr, "invert");
+ expand_cache->preserve = RNA_boolean_get(op->ptr, "use_mask_preserve");
+ expand_cache->falloff_gradient = RNA_boolean_get(op->ptr, "use_falloff_gradient");
+ expand_cache->target = RNA_enum_get(op->ptr, "target");
+ expand_cache->modify_active_face_set = RNA_boolean_get(op->ptr, "use_modify_active");
+ expand_cache->reposition_pivot = RNA_boolean_get(op->ptr, "use_reposition_pivot");
+ expand_cache->max_geodesic_move_preview = RNA_int_get(op->ptr, "max_geodesic_move_preview");
+
+ /* These can be exposed in RNA if needed. */
+ expand_cache->loop_count = 1;
+ expand_cache->brush_gradient = false;
+
+ /* Texture and color data from the active Brush. */
+ Object *ob = CTX_data_active_object(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ SculptSession *ss = ob->sculpt;
+ expand_cache->brush = BKE_paint_brush(&sd->paint);
+ BKE_curvemapping_init(expand_cache->brush->curve);
+ copy_v4_fl(expand_cache->fill_color, 1.0f);
+ copy_v3_v3(expand_cache->fill_color, BKE_brush_color_get(ss->scene, expand_cache->brush));
+ IMB_colormanagement_srgb_to_scene_linear_v3(expand_cache->fill_color);
+
+ expand_cache->scene = CTX_data_scene(C);
+ expand_cache->mtex = &expand_cache->brush->mtex;
+ expand_cache->texture_distortion_strength = 0.0f;
+ expand_cache->blend_mode = expand_cache->brush->blend;
+}
+
+/**
+ * Does the undo sculpt push for the affected target data of the #ExpandCache.
+ */
+static void sculpt_expand_undo_push(Object *ob, ExpandCache *expand_cache)
+{
+ SculptSession *ss = ob->sculpt;
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+
+ switch (expand_cache->target) {
+ case SCULPT_EXPAND_TARGET_MASK:
+ for (int i = 0; i < totnode; i++) {
+ SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK);
+ }
+ break;
+ case SCULPT_EXPAND_TARGET_FACE_SETS:
+ SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+ break;
+ case SCULPT_EXPAND_TARGET_COLORS:
+ for (int i = 0; i < totnode; i++) {
+ SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COLOR);
+ }
+ break;
+ }
+
+ MEM_freeN(nodes);
+}
+
+static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ /* Create and configure the Expand Cache. */
+ ss->expand_cache = MEM_callocN(sizeof(ExpandCache), "expand cache");
+ sculpt_expand_cache_initial_config_set(C, op, ss->expand_cache);
+
+ /* Update object. */
+ const bool needs_colors = ss->expand_cache->target == SCULPT_EXPAND_TARGET_COLORS;
+
+ if (needs_colors) {
+ /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of
+ * earlier steps modifying the data. */
+ BKE_sculpt_color_layer_create_if_needed(ob);
+ depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ }
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, needs_colors);
+
+ /* Do nothing when the mesh has 0 vertices. */
+ const int totvert = SCULPT_vertex_count_get(ss);
+ if (totvert == 0) {
+ sculpt_expand_cache_free(ss);
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Face Set operations are not supported in dyntopo. */
+ if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS &&
+ BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
+ sculpt_expand_cache_free(ss);
+ return OPERATOR_CANCELLED;
+ }
+
+ sculpt_expand_ensure_sculptsession_data(ob);
+
+ /* Initialize undo. */
+ SCULPT_undo_push_begin(ob, "expand");
+ sculpt_expand_undo_push(ob, ss->expand_cache);
+
+ /* Set the initial element for expand from the event position. */
+ const float mouse[2] = {event->mval[0], event->mval[1]};
+ sculpt_expand_set_initial_components_for_mouse(C, ob, ss->expand_cache, mouse);
+
+ /* Cache PBVH nodes. */
+ BKE_pbvh_search_gather(
+ ss->pbvh, NULL, NULL, &ss->expand_cache->nodes, &ss->expand_cache->totnode);
+
+ /* Store initial state. */
+ sculpt_expand_original_state_store(ob, ss->expand_cache);
+
+ if (ss->expand_cache->modify_active_face_set) {
+ sculpt_expand_delete_face_set_id(ss->expand_cache->initial_face_sets,
+ ob->data,
+ ss->pmap,
+ ss->totfaces,
+ ss->expand_cache->next_face_set);
+ }
+
+ /* Initialize the falloff. */
+ eSculptExpandFalloffType falloff_type = RNA_enum_get(op->ptr, "falloff_type");
+
+ /* When starting from a boundary vertex, set the initial falloff to boundary. */
+ if (SCULPT_vertex_is_boundary(ss, ss->expand_cache->initial_active_vertex)) {
+ falloff_type = SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY;
+ }
+
+ sculpt_expand_falloff_factors_from_vertex_and_symm_create(
+ ss->expand_cache, sd, ob, ss->expand_cache->initial_active_vertex, falloff_type);
+
+ /* Initial mesh data update, resets all target data in the sculpt mesh. */
+ sculpt_expand_update_for_vertex(C, ob, ss->expand_cache->initial_active_vertex);
+
+ WM_event_add_modal_handler(C, op);
+ return OPERATOR_RUNNING_MODAL;
+}
+
+void sculpt_expand_modal_keymap(wmKeyConfig *keyconf)
+{
+ static const EnumPropertyItem modal_items[] = {
+ {SCULPT_EXPAND_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
+ {SCULPT_EXPAND_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
+ {SCULPT_EXPAND_MODAL_INVERT, "INVERT", 0, "Invert", ""},
+ {SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE, "PRESERVE", 0, "Toggle Preserve State", ""},
+ {SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE, "GRADIENT", 0, "Toggle Gradient", ""},
+ {SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC,
+ "RECURSION_STEP_GEODESIC",
+ 0,
+ "Geodesic recursion step",
+ ""},
+ {SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY,
+ "RECURSION_STEP_TOPOLOGY",
+ 0,
+ "Topology recursion Step",
+ ""},
+ {SCULPT_EXPAND_MODAL_MOVE_TOGGLE, "MOVE_TOGGLE", 0, "Move Origin", ""},
+ {SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC, "FALLOFF_GEODESICS", 0, "Geodesic Falloff", ""},
+ {SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY, "FALLOFF_TOPOLOGY", 0, "Topology Falloff", ""},
+ {SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS,
+ "FALLOFF_TOPOLOGY_DIAGONALS",
+ 0,
+ "Diagonals Falloff",
+ ""},
+ {SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL, "FALLOFF_SPHERICAL", 0, "Spherical Falloff", ""},
+ {SCULPT_EXPAND_MODAL_SNAP_TOGGLE, "SNAP_TOGGLE", 0, "Snap expand to Face Sets", ""},
+ {SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
+ "LOOP_COUNT_INCREASE",
+ 0,
+ "Loop Count Increase",
+ ""},
+ {SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE,
+ "LOOP_COUNT_DECREASE",
+ 0,
+ "Loop Count Decrease",
+ ""},
+ {SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE,
+ "BRUSH_GRADIENT_TOGGLE",
+ 0,
+ "Toggle Brush Gradient",
+ ""},
+ {SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE,
+ "TEXTURE_DISTORTION_INCREASE",
+ 0,
+ "Texture Distortion Increase",
+ ""},
+ {SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE,
+ "TEXTURE_DISTORTION_DECREASE",
+ 0,
+ "Texture Distortion Decrease",
+ ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ static const char *name = "Sculpt Expand Modal";
+ wmKeyMap *keymap = WM_modalkeymap_find(keyconf, name);
+
+ /* This function is called for each spacetype, only needs to add map once. */
+ if (keymap && keymap->modal_items) {
+ return;
+ }
+
+ keymap = WM_modalkeymap_ensure(keyconf, name, modal_items);
+ WM_modalkeymap_assign(keymap, "SCULPT_OT_expand");
+}
+
+void SCULPT_OT_expand(wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Expand";
+ ot->idname = "SCULPT_OT_expand";
+ ot->description = "Generic sculpt expand operator";
+
+ /* API callbacks. */
+ ot->invoke = sculpt_expand_invoke;
+ ot->modal = sculpt_expand_modal;
+ ot->cancel = sculpt_expand_cancel;
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ static EnumPropertyItem prop_sculpt_expand_falloff_type_items[] = {
+ {SCULPT_EXPAND_FALLOFF_GEODESIC, "GEODESIC", 0, "Geodesic", ""},
+ {SCULPT_EXPAND_FALLOFF_TOPOLOGY, "TOPOLOGY", 0, "Topology", ""},
+ {SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS,
+ "TOPOLOGY_DIAGONALS",
+ 0,
+ "Topology Diagonals",
+ ""},
+ {SCULPT_EXPAND_FALLOFF_NORMALS, "NORMALS", 0, "Normals", ""},
+ {SCULPT_EXPAND_FALLOFF_SPHERICAL, "SPHERICAL", 0, "Spherical", ""},
+ {SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY, "BOUNDARY_TOPOLOGY", 0, "Boundary Topology", ""},
+ {SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET, "BOUNDARY_FACE_SET", 0, "Boundary Face Set", ""},
+ {SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET, "ACTIVE_FACE_SET", 0, "Active Face Set", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ static EnumPropertyItem prop_sculpt_expand_target_type_items[] = {
+ {SCULPT_EXPAND_TARGET_MASK, "MASK", 0, "Mask", ""},
+ {SCULPT_EXPAND_TARGET_FACE_SETS, "FACE_SETS", 0, "Face Sets", ""},
+ {SCULPT_EXPAND_TARGET_COLORS, "COLOR", 0, "Color", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_enum(ot->srna,
+ "target",
+ prop_sculpt_expand_target_type_items,
+ SCULPT_EXPAND_TARGET_MASK,
+ "Data Target",
+ "Data that is going to be modified in the expand operation");
+
+ RNA_def_enum(ot->srna,
+ "falloff_type",
+ prop_sculpt_expand_falloff_type_items,
+ SCULPT_EXPAND_FALLOFF_GEODESIC,
+ "Falloff Type",
+ "Initial falloff of the expand operation");
+
+ ot->prop = RNA_def_boolean(
+ ot->srna, "invert", false, "Invert", "Invert the expand active elements");
+ ot->prop = RNA_def_boolean(ot->srna,
+ "use_mask_preserve",
+ false,
+ "Preserve Previous",
+ "Preserve the previous state of the target data");
+ ot->prop = RNA_def_boolean(ot->srna,
+ "use_falloff_gradient",
+ false,
+ "Falloff Gradient",
+ "Expand Using a linear falloff");
+
+ ot->prop = RNA_def_boolean(ot->srna,
+ "use_modify_active",
+ false,
+ "Modify Active",
+ "Modify the active Face Set instead of creating a new one");
+
+ ot->prop = RNA_def_boolean(
+ ot->srna,
+ "use_reposition_pivot",
+ true,
+ "Reposition Pivot",
+ "Reposition the sculpt transform pivot to the boundary of the expand active area");
+
+ ot->prop = RNA_def_int(ot->srna,
+ "max_geodesic_move_preview",
+ 10000,
+ 0,
+ INT_MAX,
+ "Max Vertex Count for Geodesic Move Preview",
+ "Maximum number of vertices in the mesh for using geodesic falloff when "
+ "moving the origin of expand. If the total number of vertices is greater "
+ "than this value, the falloff will be set to spherical when moving",
+ 0,
+ 1000000);
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_geodesic.c b/source/blender/editors/sculpt_paint/sculpt_geodesic.c
new file mode 100644
index 00000000000..d86d0938300
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_geodesic.c
@@ -0,0 +1,360 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_linklist_stack.h"
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_image.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+#include "BKE_subdiv_ccg.h"
+
+#include "DEG_depsgraph.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 "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_view3d.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "IMB_colormanagement.h"
+#include "IMB_imbuf.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+#define SCULPT_GEODESIC_VERTEX_NONE -1
+
+/* Propagate distance from v1 and v2 to v0. */
+static bool sculpt_geodesic_mesh_test_dist_add(
+ MVert *mvert, const int v0, const int v1, const int v2, float *dists, GSet *initial_vertices)
+{
+ if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(v0))) {
+ return false;
+ }
+
+ BLI_assert(dists[v1] != FLT_MAX);
+ if (dists[v0] <= dists[v1]) {
+ return false;
+ }
+
+ float dist0;
+ if (v2 != SCULPT_GEODESIC_VERTEX_NONE) {
+ BLI_assert(dists[v2] != FLT_MAX);
+ if (dists[v0] <= dists[v2]) {
+ return false;
+ }
+ dist0 = geodesic_distance_propagate_across_triangle(
+ mvert[v0].co, mvert[v1].co, mvert[v2].co, dists[v1], dists[v2]);
+ }
+ else {
+ float vec[3];
+ sub_v3_v3v3(vec, mvert[v1].co, mvert[v0].co);
+ dist0 = dists[v1] + len_v3(vec);
+ }
+
+ if (dist0 < dists[v0]) {
+ dists[v0] = dist0;
+ return true;
+ }
+
+ return false;
+}
+
+static float *SCULPT_geodesic_mesh_create(Object *ob,
+ GSet *initial_vertices,
+ const float limit_radius)
+{
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+
+ const int totvert = mesh->totvert;
+ const int totedge = mesh->totedge;
+
+ const float limit_radius_sq = limit_radius * limit_radius;
+
+ MEdge *edges = mesh->medge;
+ MVert *verts = SCULPT_mesh_deformed_mverts_get(ss);
+
+ float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances");
+ BLI_bitmap *edge_tag = BLI_BITMAP_NEW(totedge, "edge tag");
+
+ if (!ss->epmap) {
+ BKE_mesh_edge_poly_map_create(&ss->epmap,
+ &ss->epmap_mem,
+ mesh->medge,
+ mesh->totedge,
+ mesh->mpoly,
+ mesh->totpoly,
+ mesh->mloop,
+ mesh->totloop);
+ }
+ if (!ss->vemap) {
+ BKE_mesh_vert_edge_map_create(
+ &ss->vemap, &ss->vemap_mem, mesh->medge, mesh->totvert, mesh->totedge);
+ }
+
+ /* Both contain edge indices encoded as *void. */
+ BLI_LINKSTACK_DECLARE(queue, void *);
+ BLI_LINKSTACK_DECLARE(queue_next, void *);
+
+ BLI_LINKSTACK_INIT(queue);
+ BLI_LINKSTACK_INIT(queue_next);
+
+ for (int i = 0; i < totvert; i++) {
+ if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(i))) {
+ dists[i] = 0.0f;
+ }
+ else {
+ dists[i] = FLT_MAX;
+ }
+ }
+
+ /* Masks vertices that are further than limit radius from an initial vertex. As there is no need
+ * to define a distance to them the algorithm can stop earlier by skipping them. */
+ BLI_bitmap *affected_vertex = BLI_BITMAP_NEW(totvert, "affected vertex");
+ GSetIterator gs_iter;
+
+ if (limit_radius == FLT_MAX) {
+ /* In this case, no need to loop through all initial vertices to check distances as they are
+ * all going to be affected. */
+ BLI_bitmap_set_all(affected_vertex, true, totvert);
+ }
+ else {
+ /* This is an O(n^2) loop used to limit the geodesic distance calculation to a radius. When
+ * this optimization is needed, it is expected for the tool to request the distance to a low
+ * number of vertices (usually just 1 or 2). */
+ GSET_ITER (gs_iter, initial_vertices) {
+ const int v = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter));
+ float *v_co = verts[v].co;
+ for (int i = 0; i < totvert; i++) {
+ if (len_squared_v3v3(v_co, verts[i].co) <= limit_radius_sq) {
+ BLI_BITMAP_ENABLE(affected_vertex, i);
+ }
+ }
+ }
+ }
+
+ /* Add edges adjacent to an initial vertex to the queue. */
+ for (int i = 0; i < totedge; i++) {
+ const int v1 = edges[i].v1;
+ const int v2 = edges[i].v2;
+ if (!BLI_BITMAP_TEST(affected_vertex, v1) && !BLI_BITMAP_TEST(affected_vertex, v2)) {
+ continue;
+ }
+ if (dists[v1] != FLT_MAX || dists[v2] != FLT_MAX) {
+ BLI_LINKSTACK_PUSH(queue, POINTER_FROM_INT(i));
+ }
+ }
+
+ do {
+ while (BLI_LINKSTACK_SIZE(queue)) {
+ const int e = POINTER_AS_INT(BLI_LINKSTACK_POP(queue));
+ int v1 = edges[e].v1;
+ int v2 = edges[e].v2;
+
+ if (dists[v1] == FLT_MAX || dists[v2] == FLT_MAX) {
+ if (dists[v1] > dists[v2]) {
+ SWAP(int, v1, v2);
+ }
+ sculpt_geodesic_mesh_test_dist_add(
+ verts, v2, v1, SCULPT_GEODESIC_VERTEX_NONE, dists, initial_vertices);
+ }
+
+ if (ss->epmap[e].count != 0) {
+ for (int poly_map_index = 0; poly_map_index < ss->epmap[e].count; poly_map_index++) {
+ const int poly = ss->epmap[e].indices[poly_map_index];
+ if (ss->face_sets[poly] <= 0) {
+ continue;
+ }
+ const MPoly *mpoly = &mesh->mpoly[poly];
+
+ for (int loop_index = 0; loop_index < mpoly->totloop; loop_index++) {
+ const MLoop *mloop = &mesh->mloop[loop_index + mpoly->loopstart];
+ const int v_other = mloop->v;
+ if (ELEM(v_other, v1, v2)) {
+ continue;
+ }
+ if (sculpt_geodesic_mesh_test_dist_add(
+ verts, v_other, v1, v2, dists, initial_vertices)) {
+ for (int edge_map_index = 0; edge_map_index < ss->vemap[v_other].count;
+ edge_map_index++) {
+ const int e_other = ss->vemap[v_other].indices[edge_map_index];
+ int ev_other;
+ if (edges[e_other].v1 == (uint)v_other) {
+ ev_other = edges[e_other].v2;
+ }
+ else {
+ ev_other = edges[e_other].v1;
+ }
+
+ if (e_other != e && !BLI_BITMAP_TEST(edge_tag, e_other) &&
+ (ss->epmap[e_other].count == 0 || dists[ev_other] != FLT_MAX)) {
+ if (BLI_BITMAP_TEST(affected_vertex, v_other) ||
+ BLI_BITMAP_TEST(affected_vertex, ev_other)) {
+ BLI_BITMAP_ENABLE(edge_tag, e_other);
+ BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(e_other));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (LinkNode *lnk = queue_next; lnk; lnk = lnk->next) {
+ const int e = POINTER_AS_INT(lnk->link);
+ BLI_BITMAP_DISABLE(edge_tag, e);
+ }
+
+ BLI_LINKSTACK_SWAP(queue, queue_next);
+
+ } while (BLI_LINKSTACK_SIZE(queue));
+
+ BLI_LINKSTACK_FREE(queue);
+ BLI_LINKSTACK_FREE(queue_next);
+ MEM_SAFE_FREE(edge_tag);
+ MEM_SAFE_FREE(affected_vertex);
+
+ return dists;
+}
+
+/* For sculpt mesh data that does not support a geodesic distances algorithm, fallback to the
+ * distance to each vertex. In this case, only one of the initial vertices will be used to
+ * calculate the distance. */
+static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_vertices)
+{
+
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+ const int totvert = mesh->totvert;
+ float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances");
+ int first_affected = SCULPT_GEODESIC_VERTEX_NONE;
+ GSetIterator gs_iter;
+ GSET_ITER (gs_iter, initial_vertices) {
+ first_affected = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter));
+ break;
+ }
+
+ if (first_affected == SCULPT_GEODESIC_VERTEX_NONE) {
+ for (int i = 0; i < totvert; i++) {
+ dists[i] = FLT_MAX;
+ }
+ return dists;
+ }
+
+ const float *first_affected_co = SCULPT_vertex_co_get(ss, first_affected);
+ for (int i = 0; i < totvert; i++) {
+ dists[i] = len_v3v3(first_affected_co, SCULPT_vertex_co_get(ss, i));
+ }
+
+ return dists;
+}
+
+float *SCULPT_geodesic_distances_create(Object *ob,
+ GSet *initial_vertices,
+ const float limit_radius)
+{
+ SculptSession *ss = ob->sculpt;
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ return SCULPT_geodesic_mesh_create(ob, initial_vertices, limit_radius);
+ case PBVH_BMESH:
+ case PBVH_GRIDS:
+ return SCULPT_geodesic_fallback_create(ob, initial_vertices);
+ }
+ BLI_assert(false);
+ return NULL;
+}
+
+float *SCULPT_geodesic_from_vertex_and_symm(Sculpt *sd,
+ Object *ob,
+ const int vertex,
+ const float limit_radius)
+{
+ SculptSession *ss = ob->sculpt;
+ GSet *initial_vertices = BLI_gset_int_new("initial_vertices");
+
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char i = 0; i <= symm; ++i) {
+ if (SCULPT_is_symmetry_iteration_valid(i, symm)) {
+ int v = -1;
+ if (i == 0) {
+ v = vertex;
+ }
+ else {
+ float location[3];
+ flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), i);
+ v = SCULPT_nearest_vertex_get(sd, ob, location, FLT_MAX, false);
+ }
+ if (v != -1) {
+ BLI_gset_add(initial_vertices, POINTER_FROM_INT(v));
+ }
+ }
+ }
+
+ float *dists = SCULPT_geodesic_distances_create(ob, initial_vertices, limit_radius);
+ BLI_gset_free(initial_vertices, NULL);
+ return dists;
+}
+
+float *SCULPT_geodesic_from_vertex(Object *ob, const int vertex, const float limit_radius)
+{
+ GSet *initial_vertices = BLI_gset_int_new("initial_vertices");
+ BLI_gset_add(initial_vertices, POINTER_FROM_INT(vertex));
+ float *dists = SCULPT_geodesic_distances_create(ob, initial_vertices, limit_radius);
+ BLI_gset_free(initial_vertices, NULL);
+ return dists;
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index f90cf366ed9..19c4eda7593 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -187,6 +187,8 @@ void SCULPT_boundary_info_ensure(Object *object);
/* Boundary Info needs to be initialized in order to use this function. */
bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index);
+void SCULPT_connected_components_ensure(Object *ob);
+
/* Sculpt Visibility API */
void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible);
@@ -300,6 +302,7 @@ void SCULPT_floodfill_add_initial_with_symmetry(struct Sculpt *sd,
int index,
float radius);
void SCULPT_floodfill_add_initial(SculptFloodFill *flood, int index);
+void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, int index);
void SCULPT_floodfill_execute(
struct SculptSession *ss,
SculptFloodFill *flood,
@@ -361,6 +364,21 @@ float *SCULPT_boundary_automasking_init(Object *ob,
int propagation_steps,
float *automask_factor);
+/* Geodesic distances. */
+
+/* Returns an array indexed by vertex index containing the geodesic distance to the closest vertex
+in the initial vertex set. The caller is responsible for freeing the array.
+Geodesic distances will only work when used with PBVH_FACES, for other types of PBVH it will
+fallback to euclidean distances to one of the initial vertices in the set. */
+float *SCULPT_geodesic_distances_create(struct Object *ob,
+ struct GSet *initial_vertices,
+ const float limit_radius);
+float *SCULPT_geodesic_from_vertex_and_symm(struct Sculpt *sd,
+ struct Object *ob,
+ const int vertex,
+ const float limit_radius);
+float *SCULPT_geodesic_from_vertex(Object *ob, const int vertex, const float limit_radius);
+
/* Filters. */
void SCULPT_filter_cache_init(struct bContext *C, Object *ob, Sculpt *sd, const int undo_type);
void SCULPT_filter_cache_free(SculptSession *ss);
@@ -1066,6 +1084,155 @@ void SCULPT_filter_to_orientation_space(float r_v[3], struct FilterCache *filter
void SCULPT_filter_to_object_space(float r_v[3], struct FilterCache *filter_cache);
void SCULPT_filter_zero_disabled_axis_components(float r_v[3], struct FilterCache *filter_cache);
+/* Sculpt Expand. */
+typedef enum eSculptExpandFalloffType {
+ SCULPT_EXPAND_FALLOFF_GEODESIC,
+ SCULPT_EXPAND_FALLOFF_TOPOLOGY,
+ SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS,
+ SCULPT_EXPAND_FALLOFF_NORMALS,
+ SCULPT_EXPAND_FALLOFF_SPHERICAL,
+ SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY,
+ SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET,
+ SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET,
+} eSculptExpandFalloffType;
+
+typedef enum eSculptExpandTargetType {
+ SCULPT_EXPAND_TARGET_MASK,
+ SCULPT_EXPAND_TARGET_FACE_SETS,
+ SCULPT_EXPAND_TARGET_COLORS,
+} eSculptExpandTargetType;
+
+typedef enum eSculptExpandRecursionType {
+ SCULPT_EXPAND_RECURSION_TOPOLOGY,
+ SCULPT_EXPAND_RECURSION_GEODESICS,
+} eSculptExpandRecursionType;
+
+#define EXPAND_SYMM_AREAS 8
+
+typedef struct ExpandCache {
+ /* Target data elements that the expand operation will affect. */
+ eSculptExpandTargetType target;
+
+ /* Falloff data. */
+ eSculptExpandFalloffType falloff_type;
+
+ /* Indexed by vertex index, precalculated falloff value of that vertex (without any falloff
+ * editing modification applied). */
+ float *vert_falloff;
+ /* Max falloff value in *vert_falloff. */
+ float max_vert_falloff;
+
+ /* Indexed by base mesh poly index, precalculated falloff value of that face. These values are
+ * calculated from the per vertex falloff (*vert_falloff) when needed. */
+ float *face_falloff;
+ float max_face_falloff;
+
+ /* Falloff value of the active element (vertex or base mesh face) that Expand will expand to. */
+ float active_falloff;
+
+ /* When set to true, expand skips all falloff computations and considers all elements as enabled.
+ */
+ bool all_enabled;
+
+ /* Initial mouse and cursor data from where the current falloff started. This data can be changed
+ * during the execution of Expand by moving the origin. */
+ float initial_mouse_move[2];
+ float initial_mouse[2];
+ int initial_active_vertex;
+ int initial_active_face_set;
+
+ /* Maximum number of vertices allowed in the SculptSession for previewing the falloff using
+ * geodesic distances. */
+ int max_geodesic_move_preview;
+
+ /* Original falloff type before starting the move operation. */
+ eSculptExpandFalloffType move_original_falloff_type;
+ /* Falloff type using when moving the origin for preview. */
+ eSculptExpandFalloffType move_preview_falloff_type;
+
+ /* Face set ID that is going to be used when creating a new Face Set. */
+ int next_face_set;
+
+ /* Face Set ID of the Face set selected for editing. */
+ int update_face_set;
+
+ /* Mouse position since the last time the origin was moved. Used for reference when moving the
+ * initial position of Expand. */
+ float original_mouse_move[2];
+
+ /* Active components checks. */
+ /* Indexed by symmetry pass index, contains the connected component ID found in
+ * SculptSession->vertex_info.connected_component. Other connected components not found in this
+ * array will be ignored by Expand. */
+ int active_connected_components[EXPAND_SYMM_AREAS];
+
+ /* Snapping. */
+ /* GSet containing all Face Sets IDs that Expand will use to snap the new data. */
+ GSet *snap_enabled_face_sets;
+
+ /* Texture distortion data. */
+ Brush *brush;
+ struct Scene *scene;
+ struct MTex *mtex;
+
+ /* Controls how much texture distortion will be applied to the current falloff */
+ float texture_distortion_strength;
+
+ /* Cached PBVH nodes. This allows to skip gathering all nodes from the PBVH each time expand
+ * needs to update the state of the elements. */
+ PBVHNode **nodes;
+ int totnode;
+
+ /* Expand state options. */
+
+ /* Number of loops (times that the falloff is going to be repeated). */
+ int loop_count;
+
+ /* Invert the falloff result. */
+ bool invert;
+
+ /* When set to true, preserves the previous state of the data and adds the new one on top. */
+ bool preserve;
+
+ /* When set to true, the mask or colors will be applied as a gradient. */
+ bool falloff_gradient;
+
+ /* When set to true, Expand will use the Brush falloff curve data to shape the gradient. */
+ bool brush_gradient;
+
+ /* When set to true, Expand will move the origin (initial active vertex and cursor position)
+ * instead of updating the active vertex and active falloff. */
+ bool move;
+
+ /* When set to true, Expand will snap the new data to the Face Sets IDs found in
+ * *original_face_sets. */
+ bool snap;
+
+ /* When set to true, Expand will use the current Face Set ID to modify an existing Face Set
+ * instead of creating a new one. */
+ bool modify_active_face_set;
+
+ /* When set to true, Expand will reposition the sculpt pivot to the boundary of the expand result
+ * after finishing the operation. */
+ bool reposition_pivot;
+
+ /* Color target data type related data. */
+ float fill_color[4];
+ short blend_mode;
+
+ /* Face Sets at the first step of the expand operation, before starting modifying the active
+ * vertex and active falloff. These are not the original Face Sets of the sculpt before starting
+ * the operator as they could have been modified by Expand when initializing the operator and
+ * before starting changing the active vertex. These Face Sets are used for restoring and
+ * checking the Face Sets state while the Expand operation modal runs. */
+ int *initial_face_sets;
+
+ /* Original data of the sculpt as it was before running the Expand operator. */
+ float *original_mask;
+ int *original_face_sets;
+ float (*original_colors)[4];
+} ExpandCache;
+
typedef struct FilterCache {
bool enabled_axis[3];
bool enabled_force_axis[3];
@@ -1150,6 +1317,10 @@ bool SCULPT_get_redraw_rect(struct ARegion *region,
/* Operators. */
+/* Expand. */
+void SCULPT_OT_expand(struct wmOperatorType *ot);
+void sculpt_expand_modal_keymap(struct wmKeyConfig *keyconf);
+
/* Gestures. */
void SCULPT_OT_face_set_lasso_gesture(struct wmOperatorType *ot);
void SCULPT_OT_face_set_box_gesture(struct wmOperatorType *ot);
diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c
index 4847e8738df..43128ed00fa 100644
--- a/source/blender/editors/space_buttons/buttons_texture.c
+++ b/source/blender/editors/space_buttons/buttons_texture.c
@@ -64,13 +64,42 @@
#include "ED_screen.h"
#include "WM_api.h"
+#include "WM_types.h"
#include "../interface/interface_intern.h"
#include "buttons_intern.h" /* own include */
+static ScrArea *find_area_properties(const bContext *C);
+static SpaceProperties *find_space_properties(const bContext *C);
+
/************************* Texture User **************************/
+static void buttons_texture_user_node_property_add(ListBase *users,
+ ID *id,
+ PointerRNA ptr,
+ PropertyRNA *prop,
+ bNodeTree *ntree,
+ bNode *node,
+ const char *category,
+ int icon,
+ const char *name)
+{
+ ButsTextureUser *user = MEM_callocN(sizeof(ButsTextureUser), "ButsTextureUser");
+
+ user->id = id;
+ user->ptr = ptr;
+ user->prop = prop;
+ user->ntree = ntree;
+ user->node = node;
+ user->category = category;
+ user->icon = icon;
+ user->name = name;
+ user->index = BLI_listbase_count(users);
+
+ BLI_addtail(users, user);
+}
+
static void buttons_texture_user_property_add(ListBase *users,
ID *id,
PointerRNA ptr,
@@ -139,20 +168,66 @@ static void buttons_texture_users_find_nodetree(ListBase *users,
}
}
+static void buttons_texture_modifier_geonodes_users_add(Object *ob,
+ NodesModifierData *nmd,
+ bNodeTree *node_tree,
+ ListBase *users)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+
+ LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
+ if (node->type == NODE_GROUP && node->id) {
+ /* Recurse into the node group */
+ buttons_texture_modifier_geonodes_users_add(ob, nmd, (bNodeTree *)node->id, users);
+ }
+ else if (node->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) {
+ RNA_pointer_create(&node_tree->id, &RNA_Node, node, &ptr);
+ prop = RNA_struct_find_property(&ptr, "texture");
+ if (prop == NULL) {
+ continue;
+ }
+
+ PointerRNA texptr = RNA_property_pointer_get(&ptr, prop);
+ Tex *tex = (RNA_struct_is_a(texptr.type, &RNA_Texture)) ? (Tex *)texptr.data : NULL;
+ if (tex != NULL) {
+ buttons_texture_user_node_property_add(users,
+ &ob->id,
+ ptr,
+ prop,
+ node_tree,
+ node,
+ N_("Geometry Nodes"),
+ RNA_struct_ui_icon(ptr.type),
+ nmd->modifier.name);
+ }
+ }
+ }
+}
+
static void buttons_texture_modifier_foreach(void *userData,
Object *ob,
ModifierData *md,
const char *propname)
{
- PointerRNA ptr;
- PropertyRNA *prop;
ListBase *users = userData;
- RNA_pointer_create(&ob->id, &RNA_Modifier, md, &ptr);
- prop = RNA_struct_find_property(&ptr, propname);
+ if (md->type == eModifierType_Nodes) {
+ NodesModifierData *nmd = (NodesModifierData *)md;
+ if (nmd->node_group != NULL) {
+ buttons_texture_modifier_geonodes_users_add(ob, nmd, nmd->node_group, users);
+ }
+ }
+ else {
+ PointerRNA ptr;
+ PropertyRNA *prop;
- buttons_texture_user_property_add(
- users, &ob->id, ptr, prop, N_("Modifiers"), RNA_struct_ui_icon(ptr.type), md->name);
+ RNA_pointer_create(&ob->id, &RNA_Modifier, md, &ptr);
+ prop = RNA_struct_find_property(&ptr, propname);
+
+ buttons_texture_user_property_add(
+ users, &ob->id, ptr, prop, N_("Modifiers"), RNA_struct_ui_icon(ptr.type), md->name);
+ }
}
static void buttons_texture_modifier_gpencil_foreach(void *userData,
@@ -325,31 +400,32 @@ void buttons_texture_context_compute(const bContext *C, SpaceProperties *sbuts)
ct->texture = NULL;
if (ct->user) {
+ if (ct->user->node != NULL) {
+ /* Detect change of active texture node in same node tree, in that
+ * case we also automatically switch to the other node. */
+ if ((ct->user->node->flag & NODE_ACTIVE_TEXTURE) == 0) {
+ ButsTextureUser *user;
+ for (user = ct->users.first; user; user = user->next) {
+ if (user->ntree == ct->user->ntree && user->node != ct->user->node) {
+ if (user->node->flag & NODE_ACTIVE_TEXTURE) {
+ ct->user = user;
+ ct->index = BLI_findindex(&ct->users, user);
+ break;
+ }
+ }
+ }
+ }
+ }
if (ct->user->ptr.data) {
PointerRNA texptr;
Tex *tex;
- /* get texture datablock pointer if it's a property */
+ /* Get texture datablock pointer if it's a property. */
texptr = RNA_property_pointer_get(&ct->user->ptr, ct->user->prop);
tex = (RNA_struct_is_a(texptr.type, &RNA_Texture)) ? texptr.data : NULL;
ct->texture = tex;
}
- else if (ct->user->node && !(ct->user->node->flag & NODE_ACTIVE_TEXTURE)) {
- ButsTextureUser *user;
-
- /* detect change of active texture node in same node tree, in that
- * case we also automatically switch to the other node */
- for (user = ct->users.first; user; user = user->next) {
- if (user->ntree == ct->user->ntree && user->node != ct->user->node) {
- if (user->node->flag & NODE_ACTIVE_TEXTURE) {
- ct->user = user;
- ct->index = BLI_findindex(&ct->users, user);
- break;
- }
- }
- }
- }
}
}
}
@@ -357,7 +433,7 @@ void buttons_texture_context_compute(const bContext *C, SpaceProperties *sbuts)
static void template_texture_select(bContext *C, void *user_p, void *UNUSED(arg))
{
/* callback when selecting a texture user in the menu */
- SpaceProperties *sbuts = CTX_wm_space_properties(C);
+ SpaceProperties *sbuts = find_space_properties(C);
ButsContextTexture *ct = (sbuts) ? sbuts->texuser : NULL;
ButsTextureUser *user = (ButsTextureUser *)user_p;
PointerRNA texptr;
@@ -371,8 +447,15 @@ static void template_texture_select(bContext *C, void *user_p, void *UNUSED(arg)
if (user->node) {
ED_node_set_active(CTX_data_main(C), user->ntree, user->node, NULL);
ct->texture = NULL;
+
+ /* Not totally sure if we should also change selection? */
+ LISTBASE_FOREACH (bNode *, node, &user->ntree->nodes) {
+ nodeSetSelected(node, false);
+ }
+ nodeSetSelected(user->node, true);
+ WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
}
- else {
+ if (user->ptr.data) {
texptr = RNA_property_pointer_get(&user->ptr, user->prop);
tex = (RNA_struct_is_a(texptr.type, &RNA_Texture)) ? texptr.data : NULL;
@@ -511,16 +594,53 @@ void uiTemplateTextureUser(uiLayout *layout, bContext *C)
/************************* Texture Show **************************/
+static ScrArea *find_area_properties(const bContext *C)
+{
+ bScreen *screen = CTX_wm_screen(C);
+ Object *ob = CTX_data_active_object(C);
+
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ if (area->spacetype == SPACE_PROPERTIES) {
+ /* Only if unpinned, or if pinned object matches. */
+ SpaceProperties *sbuts = area->spacedata.first;
+ ID *pinid = sbuts->pinid;
+ if (pinid == NULL || ((GS(pinid->name) == ID_OB) && (Object *)pinid == ob)) {
+ return area;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static SpaceProperties *find_space_properties(const bContext *C)
+{
+ ScrArea *area = find_area_properties(C);
+ if (area != NULL) {
+ return area->spacedata.first;
+ }
+
+ return NULL;
+}
+
static void template_texture_show(bContext *C, void *data_p, void *prop_p)
{
- SpaceProperties *sbuts = CTX_wm_space_properties(C);
- ButsContextTexture *ct = (sbuts) ? sbuts->texuser : NULL;
- ButsTextureUser *user;
+ if (data_p == NULL || prop_p == NULL) {
+ return;
+ }
+
+ ScrArea *area = find_area_properties(C);
+ if (area == NULL) {
+ return;
+ }
+ SpaceProperties *sbuts = (SpaceProperties *)area->spacedata.first;
+ ButsContextTexture *ct = (sbuts) ? sbuts->texuser : NULL;
if (!ct) {
return;
}
+ ButsTextureUser *user;
for (user = ct->users.first; user; user = user->next) {
if (user->ptr.data == data_p && user->prop == prop_p) {
break;
@@ -537,48 +657,65 @@ static void template_texture_show(bContext *C, void *data_p, void *prop_p)
sbuts->preview = 1;
/* redraw editor */
- ED_area_tag_redraw(CTX_wm_area(C));
+ ED_area_tag_redraw(area);
}
}
+/* Button to quickly show texture in Properties Editor texture tab. */
void uiTemplateTextureShow(uiLayout *layout, const bContext *C, PointerRNA *ptr, PropertyRNA *prop)
{
- /* button to quickly show texture in texture tab */
- SpaceProperties *sbuts = CTX_wm_space_properties(C);
- ButsContextTexture *ct = (sbuts) ? sbuts->texuser : NULL;
- ButsTextureUser *user;
+ /* Only show the button if there is actually a texture assigned. */
+ Tex *texture = RNA_property_pointer_get(ptr, prop).data;
+ if (texture == NULL) {
+ return;
+ }
- /* only show button in other tabs in properties editor */
- if (!ct || sbuts->mainb == BCONTEXT_TEXTURE) {
+ /* Only show the button if we are not in the Properties Editor's texture tab. */
+ SpaceProperties *sbuts_context = CTX_wm_space_properties(C);
+ if (sbuts_context != NULL && sbuts_context->mainb == BCONTEXT_TEXTURE) {
return;
}
+ SpaceProperties *sbuts = find_space_properties(C);
+ ButsContextTexture *ct = (sbuts) ? sbuts->texuser : NULL;
+
/* find corresponding texture user */
- for (user = ct->users.first; user; user = user->next) {
- if (user->ptr.data == ptr->data && user->prop == prop) {
- break;
+ ButsTextureUser *user;
+ bool user_found = false;
+ if (ct != NULL) {
+ for (user = ct->users.first; user; user = user->next) {
+ if (user->ptr.data == ptr->data && user->prop == prop) {
+ user_found = true;
+ break;
+ }
}
}
- /* draw button */
- if (user) {
- uiBlock *block = uiLayoutGetBlock(layout);
- uiBut *but;
-
- but = uiDefIconBut(block,
- UI_BTYPE_BUT,
- 0,
- ICON_PROPERTIES,
- 0,
- 0,
- UI_UNIT_X,
- UI_UNIT_Y,
- NULL,
- 0.0,
- 0.0,
- 0.0,
- 0.0,
- TIP_("Show texture in texture tab"));
- UI_but_func_set(but, template_texture_show, user->ptr.data, user->prop);
+ /* Draw button (disabled if we cannot find a Properties Editor to display this in). */
+ uiBlock *block = uiLayoutGetBlock(layout);
+ uiBut *but;
+ but = uiDefIconBut(block,
+ UI_BTYPE_BUT,
+ 0,
+ ICON_PROPERTIES,
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ NULL,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ TIP_("Show texture in texture tab"));
+ UI_but_func_set(but,
+ template_texture_show,
+ user_found ? user->ptr.data : NULL,
+ user_found ? user->prop : NULL);
+ if (ct == NULL) {
+ UI_but_disable(but, TIP_("No (unpinned) Properties Editor found to display texture in"));
+ }
+ else if (!user_found) {
+ UI_but_disable(but, TIP_("No texture user found"));
}
}
diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt
index c640b076ba4..bc043a4e665 100644
--- a/source/blender/editors/space_node/CMakeLists.txt
+++ b/source/blender/editors/space_node/CMakeLists.txt
@@ -42,6 +42,7 @@ set(SRC
node_buttons.c
node_draw.cc
node_edit.c
+ node_geometry_attribute_search.cc
node_gizmo.c
node_group.c
node_ops.c
diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c
index 82a1cd818c9..977c2053187 100644
--- a/source/blender/editors/space_node/drawnode.c
+++ b/source/blender/editors/space_node/drawnode.c
@@ -3389,7 +3389,15 @@ static void std_node_socket_draw(
case SOCK_STRING: {
uiLayout *row = uiLayoutSplit(layout, 0.5f, false);
uiItemL(row, text, 0);
- uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", 0);
+
+ const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id;
+ if (node_tree->type == NTREE_GEOMETRY) {
+ node_geometry_add_attribute_search_button(node_tree, node, ptr, row);
+ }
+ else {
+ uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", 0);
+ }
+
break;
}
case SOCK_OBJECT: {
diff --git a/source/blender/editors/space_node/node_geometry_attribute_search.cc b/source/blender/editors/space_node/node_geometry_attribute_search.cc
new file mode 100644
index 00000000000..41f04dad221
--- /dev/null
+++ b/source/blender/editors/space_node/node_geometry_attribute_search.cc
@@ -0,0 +1,151 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_index_range.hh"
+#include "BLI_listbase.h"
+#include "BLI_map.hh"
+#include "BLI_set.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_string_search.h"
+
+#include "DNA_modifier_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_node_ui_storage.hh"
+#include "BKE_object.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_intern.h"
+
+using blender::IndexRange;
+using blender::Map;
+using blender::Set;
+using blender::StringRef;
+
+struct AttributeSearchData {
+ const bNodeTree &node_tree;
+ const bNode &node;
+
+ uiBut *search_button;
+
+ /* Used to keep track of a button pointer over multiple redraws. Since the UI code
+ * may reallocate the button, without this we might end up with a dangling pointer. */
+ uiButStore *button_store;
+ uiBlock *button_store_block;
+};
+
+static void attribute_search_update_fn(
+ const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
+{
+ AttributeSearchData *data = static_cast<AttributeSearchData *>(arg);
+ const NodeUIStorage *ui_storage = BKE_node_tree_ui_storage_get_from_context(
+ C, data->node_tree, data->node);
+ if (ui_storage == nullptr) {
+ return;
+ }
+
+ const Set<std::string> &attribute_name_hints = ui_storage->attribute_name_hints;
+
+ if (str[0] != '\0' && !attribute_name_hints.contains_as(StringRef(str))) {
+ /* Any string may be valid, so add the current search string with the hints. */
+ UI_search_item_add(items, str, (void *)str, ICON_ADD, 0, 0);
+ }
+
+ /* Skip the filter when the menu is first opened, so all of the items are visible. */
+ if (is_first) {
+ for (const std::string &attribute_name : attribute_name_hints) {
+ /* Just use the pointer to the name string as the search data,
+ * since it's not used anyway but we need a pointer. */
+ UI_search_item_add(items, attribute_name.c_str(), (void *)&attribute_name, ICON_NONE, 0, 0);
+ }
+ return;
+ }
+
+ StringSearch *search = BLI_string_search_new();
+ for (const std::string &attribute_name : attribute_name_hints) {
+ BLI_string_search_add(search, attribute_name.c_str(), (void *)&attribute_name);
+ }
+
+ std::string **filtered_items;
+ const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items);
+
+ for (const int i : IndexRange(filtered_amount)) {
+ std::string *item = filtered_items[i];
+ if (!UI_search_item_add(items, item->c_str(), item, ICON_NONE, 0, 0)) {
+ break;
+ }
+ }
+
+ MEM_freeN(filtered_items);
+ BLI_string_search_free(search);
+}
+
+static void attribute_search_free_fn(void *arg)
+{
+ AttributeSearchData *data = static_cast<AttributeSearchData *>(arg);
+
+ UI_butstore_free(data->button_store_block, data->button_store);
+ delete data;
+}
+
+void node_geometry_add_attribute_search_button(const bNodeTree *node_tree,
+ const bNode *node,
+ PointerRNA *socket_ptr,
+ uiLayout *layout)
+{
+ uiBlock *block = uiLayoutGetBlock(layout);
+ uiBut *but = uiDefIconTextButR(block,
+ UI_BTYPE_SEARCH_MENU,
+ 0,
+ ICON_NONE,
+ "",
+ 0,
+ 0,
+ 10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
+ UI_UNIT_Y,
+ socket_ptr,
+ "default_value",
+ 0,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ "");
+
+ AttributeSearchData *data = new AttributeSearchData{
+ *node_tree,
+ *node,
+ but,
+ UI_butstore_create(block),
+ block,
+ };
+
+ UI_butstore_register(data->button_store, &data->search_button);
+
+ UI_but_func_search_set_results_are_suggestions(but, true);
+ UI_but_func_search_set(but,
+ nullptr,
+ attribute_search_update_fn,
+ static_cast<void *>(data),
+ attribute_search_free_fn,
+ nullptr,
+ nullptr);
+}
diff --git a/source/blender/editors/space_node/node_intern.h b/source/blender/editors/space_node/node_intern.h
index 5973d59e68f..4ec8f56480e 100644
--- a/source/blender/editors/space_node/node_intern.h
+++ b/source/blender/editors/space_node/node_intern.h
@@ -38,9 +38,11 @@ struct bContext;
struct bNode;
struct bNodeLink;
struct bNodeSocket;
+struct uiBut;
struct wmGizmoGroupType;
struct wmKeyConfig;
struct wmWindow;
+struct uiBlock;
#ifdef __cplusplus
extern "C" {
@@ -57,6 +59,9 @@ typedef struct bNodeLinkDrag {
ListBase links;
bool from_multi_input_socket;
int in_out;
+
+ /** Temporarily stores the last picked link from multi input socket operator. */
+ struct bNodeLink *last_picked_multi_input_socket_link;
} bNodeLinkDrag;
typedef struct SpaceNode_Runtime {
@@ -289,6 +294,12 @@ void NODE_GGT_backdrop_corner_pin(struct wmGizmoGroupType *gzgt);
void NODE_OT_cryptomatte_layer_add(struct wmOperatorType *ot);
void NODE_OT_cryptomatte_layer_remove(struct wmOperatorType *ot);
+/* node_geometry_attribute_search.cc */
+void node_geometry_add_attribute_search_button(const struct bNodeTree *node_tree,
+ const struct bNode *node,
+ struct PointerRNA *socket_ptr,
+ struct uiLayout *layout);
+
extern const char *node_context_dir[];
/* XXXXXX */
diff --git a/source/blender/editors/space_node/node_relationships.c b/source/blender/editors/space_node/node_relationships.c
index d6edfcce8e8..35dd865047e 100644
--- a/source/blender/editors/space_node/node_relationships.c
+++ b/source/blender/editors/space_node/node_relationships.c
@@ -280,7 +280,7 @@ static void pick_input_link_by_link_intersect(const bContext *C,
float distance = dist_squared_to_line_segment_v2(cursor, l1, l2);
if (distance < cursor_link_touch_distance) {
link_to_pick = link;
- RNA_int_set(op->ptr, "last_picked_link_index", link->multi_input_socket_index);
+ nldrag->last_picked_multi_input_socket_link = link_to_pick;
}
}
}
@@ -290,13 +290,9 @@ static void pick_input_link_by_link_intersect(const bContext *C,
* Not essential for the basic behavior, but can make interaction feel a bit better if
* the mouse moves to the right and loses the "selection." */
if (!link_to_pick) {
- int last_picked_link_index = RNA_int_get(op->ptr, "last_picked_link_index");
- if (last_picked_link_index > -1) {
- LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) {
- if (link->multi_input_socket_index == last_picked_link_index) {
- link_to_pick = link;
- }
- }
+ bNodeLink *last_picked_link = nldrag->last_picked_multi_input_socket_link;
+ if (last_picked_link) {
+ link_to_pick = last_picked_link;
}
}
@@ -1032,7 +1028,6 @@ static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
float cursor[2];
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor[0], &cursor[1]);
RNA_float_set_array(op->ptr, "drag_start", cursor);
- RNA_int_set(op->ptr, "last_picked_link_index", -1);
RNA_boolean_set(op->ptr, "has_link_picked", false);
ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
@@ -1102,15 +1097,6 @@ void NODE_OT_link(wmOperatorType *ot)
-UI_PRECISION_FLOAT_MAX,
UI_PRECISION_FLOAT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
- RNA_def_int(ot->srna,
- "last_picked_link_index",
- -1,
- -1,
- 4095,
- "Last Picked Link Index",
- "The index of the last picked link on a multi-input socket",
- -1,
- 4095);
RNA_def_property_flag(prop, PROP_HIDDEN);
}
diff --git a/source/blender/editors/space_node/node_select.c b/source/blender/editors/space_node/node_select.c
index 58d22c2864f..704b7350bb9 100644
--- a/source/blender/editors/space_node/node_select.c
+++ b/source/blender/editors/space_node/node_select.c
@@ -1178,7 +1178,8 @@ static void node_find_create_label(const bNode *node, char *str, int maxlen)
static void node_find_update_fn(const struct bContext *C,
void *UNUSED(arg),
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
SpaceNode *snode = CTX_wm_space_node(C);
diff --git a/source/blender/editors/space_outliner/outliner_collections.c b/source/blender/editors/space_outliner/outliner_collections.c
index 0afc26e0d8a..ef5733fe375 100644
--- a/source/blender/editors/space_outliner/outliner_collections.c
+++ b/source/blender/editors/space_outliner/outliner_collections.c
@@ -1367,7 +1367,7 @@ void OUTLINER_OT_collection_enable(wmOperatorType *ot)
/* identifiers */
ot->name = "Enable Collection";
ot->idname = "OUTLINER_OT_collection_enable";
- ot->description = "Enable viewport drawing in the view layers";
+ ot->description = "Enable viewport display in the view layers";
/* api callbacks */
ot->exec = collection_flag_exec;
@@ -1382,7 +1382,7 @@ void OUTLINER_OT_collection_disable(wmOperatorType *ot)
/* identifiers */
ot->name = "Disable Collection";
ot->idname = "OUTLINER_OT_collection_disable";
- ot->description = "Disable viewport drawing in the view layers";
+ ot->description = "Disable viewport display in the view layers";
/* api callbacks */
ot->exec = collection_flag_exec;
diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c
index 8726fd768d4..b735064cfef 100644
--- a/source/blender/editors/space_outliner/outliner_tools.c
+++ b/source/blender/editors/space_outliner/outliner_tools.c
@@ -554,7 +554,8 @@ static void merged_element_search_fn_recursive(
static void merged_element_search_update_fn(const bContext *UNUSED(C),
void *data,
const char *str,
- uiSearchItems *items)
+ uiSearchItems *items,
+ const bool UNUSED(is_first))
{
MergedSearchData *search_data = (MergedSearchData *)data;
TreeElement *parent = search_data->parent_element;
diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c
index a9033b98708..844dbe6a0a5 100644
--- a/source/blender/editors/space_sequencer/sequencer_add.c
+++ b/source/blender/editors/space_sequencer/sequencer_add.c
@@ -36,6 +36,7 @@
#include "DNA_mask_types.h"
#include "DNA_scene_types.h"
+#include "DNA_sound_types.h"
#include "BKE_context.h"
#include "BKE_lib_id.h"
@@ -44,6 +45,8 @@
#include "BKE_movieclip.h"
#include "BKE_report.h"
+#include "IMB_imbuf.h"
+
#include "WM_api.h"
#include "WM_types.h"
@@ -89,8 +92,6 @@ typedef struct SequencerAddData {
#define SEQPROP_NOCHAN (1 << 3)
#define SEQPROP_FIT_METHOD (1 << 4)
-#define SELECT 1
-
static const EnumPropertyItem scale_fit_methods[] = {
{SEQ_SCALE_TO_FIT, "FIT", 0, "Scale to Fit", "Scale image to fit within the canvas"},
{SEQ_SCALE_TO_FILL, "FILL", 0, "Scale to Fill", "Scale image to completely fill the canvas"},
@@ -216,7 +217,7 @@ static void sequencer_generic_invoke_xy__internal(bContext *C, wmOperator *op, i
}
}
-static void seq_load_operator_info(SeqLoadInfo *seq_load, bContext *C, wmOperator *op)
+static void load_data_init_from_operator(SeqLoadData *load_data, bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
@@ -224,69 +225,56 @@ static void seq_load_operator_info(SeqLoadInfo *seq_load, bContext *C, wmOperato
const bool relative = (prop = RNA_struct_find_property(op->ptr, "relative_path")) &&
RNA_property_boolean_get(op->ptr, prop);
int is_file = -1;
- memset(seq_load, 0, sizeof(SeqLoadInfo));
+ memset(load_data, 0, sizeof(SeqLoadData));
- seq_load->start_frame = RNA_int_get(op->ptr, "frame_start");
- seq_load->end_frame = seq_load->start_frame;
- seq_load->channel = RNA_int_get(op->ptr, "channel");
- seq_load->len = 1;
- seq_load->fit_method = RNA_enum_get(op->ptr, "fit_method");
- SEQ_tool_settings_fit_method_set(CTX_data_scene(C), seq_load->fit_method);
+ load_data->start_frame = RNA_int_get(op->ptr, "frame_start");
+ load_data->channel = RNA_int_get(op->ptr, "channel");
+ load_data->image.end_frame = load_data->start_frame;
+ load_data->image.len = 1;
+ load_data->fit_method = RNA_enum_get(op->ptr, "fit_method");
+ SEQ_tool_settings_fit_method_set(CTX_data_scene(C), load_data->fit_method);
if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) {
/* Full path, file is set by the caller. */
- RNA_property_string_get(op->ptr, prop, seq_load->path);
+ RNA_property_string_get(op->ptr, prop, load_data->path);
is_file = 1;
}
else if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
/* Full path, file is set by the caller. */
- RNA_property_string_get(op->ptr, prop, seq_load->path);
+ RNA_property_string_get(op->ptr, prop, load_data->path);
is_file = 0;
}
if ((is_file != -1) && relative) {
- BLI_path_rel(seq_load->path, BKE_main_blendfile_path(bmain));
+ BLI_path_rel(load_data->path, BKE_main_blendfile_path(bmain));
}
if ((prop = RNA_struct_find_property(op->ptr, "frame_end"))) {
- seq_load->end_frame = RNA_property_int_get(op->ptr, prop);
- }
-
- if ((prop = RNA_struct_find_property(op->ptr, "replace_sel")) &&
- RNA_property_boolean_get(op->ptr, prop)) {
- seq_load->flag |= SEQ_LOAD_REPLACE_SEL;
+ load_data->image.end_frame = RNA_property_int_get(op->ptr, prop);
}
if ((prop = RNA_struct_find_property(op->ptr, "cache")) &&
RNA_property_boolean_get(op->ptr, prop)) {
- seq_load->flag |= SEQ_LOAD_SOUND_CACHE;
+ load_data->flags |= SEQ_LOAD_SOUND_CACHE;
}
if ((prop = RNA_struct_find_property(op->ptr, "mono")) &&
RNA_property_boolean_get(op->ptr, prop)) {
- seq_load->flag |= SEQ_LOAD_SOUND_MONO;
- }
-
- if ((prop = RNA_struct_find_property(op->ptr, "sound")) &&
- RNA_property_boolean_get(op->ptr, prop)) {
- seq_load->flag |= SEQ_LOAD_MOVIE_SOUND;
+ load_data->flags |= SEQ_LOAD_SOUND_MONO;
}
if ((prop = RNA_struct_find_property(op->ptr, "use_framerate")) &&
RNA_property_boolean_get(op->ptr, prop)) {
- seq_load->flag |= SEQ_LOAD_SYNC_FPS;
+ load_data->flags |= SEQ_LOAD_MOVIE_SYNC_FPS;
}
- /* Create consecutive array of strips. */
- seq_load->flag |= SEQ_LOAD_FRAME_ADVANCE;
-
if (is_file == 1) {
- BLI_strncpy(seq_load->name, BLI_path_basename(seq_load->path), sizeof(seq_load->name));
+ BLI_strncpy(load_data->name, BLI_path_basename(load_data->path), sizeof(load_data->name));
}
else if ((prop = RNA_struct_find_property(op->ptr, "files"))) {
RNA_PROP_BEGIN (op->ptr, itemptr, prop) {
char *name = RNA_string_get_alloc(&itemptr, "name", NULL, 0);
- BLI_strncpy(seq_load->name, name, sizeof(seq_load->name));
+ BLI_strncpy(load_data->name, name, sizeof(load_data->name));
MEM_freeN(name);
break;
}
@@ -299,36 +287,31 @@ static void seq_load_operator_info(SeqLoadInfo *seq_load, bContext *C, wmOperato
SequencerAddData *sad = op->customdata;
ImageFormatData *imf = &sad->im_format;
- seq_load->views_format = imf->views_format;
- seq_load->flag |= SEQ_USE_VIEWS;
- seq_load->stereo3d_format = &imf->stereo3d_format;
+ load_data->use_multiview = true;
+ load_data->views_format = imf->views_format;
+ load_data->stereo3d_format = &imf->stereo3d_format;
}
}
}
-/**
- * Apply generic operator options.
- */
-static void sequencer_add_apply_overlap(bContext *C, wmOperator *op, Sequence *seq)
+static void seq_load_apply_generic_options(bContext *C, wmOperator *op, Sequence *seq)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, false);
- if (RNA_boolean_get(op->ptr, "overlap") == false) {
- if (SEQ_transform_test_overlap(ed->seqbasep, seq)) {
- SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
- }
+ if (seq == NULL) {
+ return;
}
-}
-
-static void sequencer_add_apply_replace_sel(bContext *C, wmOperator *op, Sequence *seq)
-{
- Scene *scene = CTX_data_scene(C);
if (RNA_boolean_get(op->ptr, "replace_sel")) {
- ED_sequencer_deselect_all(scene);
- SEQ_select_active_set(scene, seq);
seq->flag |= SELECT;
+ SEQ_select_active_set(scene, seq);
+ }
+
+ if (RNA_boolean_get(op->ptr, "overlap") == false) {
+ if (SEQ_transform_test_overlap(ed->seqbasep, seq)) {
+ SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
+ }
}
}
@@ -356,34 +339,24 @@ static int sequencer_add_scene_strip_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
- Editing *ed = SEQ_editing_get(scene, true);
- Scene *sce_seq;
- Sequence *seq;
-
- int start_frame, channel;
- start_frame = RNA_int_get(op->ptr, "frame_start");
- channel = RNA_int_get(op->ptr, "channel");
- sce_seq = BLI_findlink(&bmain->scenes, RNA_enum_get(op->ptr, "scene"));
+ const Editing *ed = SEQ_editing_get(scene, true);
+ Scene *sce_seq = BLI_findlink(&bmain->scenes, RNA_enum_get(op->ptr, "scene"));
if (sce_seq == NULL) {
BKE_report(op->reports, RPT_ERROR, "Scene not found");
return OPERATOR_CANCELLED;
}
- seq = SEQ_sequence_alloc(ed->seqbasep, start_frame, channel, SEQ_TYPE_SCENE);
- seq->blend_mode = SEQ_TYPE_CROSS;
- seq->scene = sce_seq;
- seq->len = sce_seq->r.efra - sce_seq->r.sfra + 1;
-
- BLI_strncpy(seq->name + 2, sce_seq->id.name + 2, sizeof(seq->name) - 2);
- SEQ_sequence_base_unique_name_recursive(&ed->seqbase, seq);
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_sort(scene);
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+ load_data.scene = sce_seq;
- sequencer_add_apply_replace_sel(C, op, seq);
- sequencer_add_apply_overlap(C, op, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ Sequence *seq = SEQ_add_scene_strip(scene, ed->seqbasep, &load_data);
+ seq_load_apply_generic_options(C, op, seq);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
DEG_relations_tag_update(bmain);
@@ -430,36 +403,24 @@ static int sequencer_add_movieclip_strip_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
- Editing *ed = SEQ_editing_get(scene, true);
- MovieClip *clip;
- Sequence *seq;
-
- int start_frame, channel;
- start_frame = RNA_int_get(op->ptr, "frame_start");
- channel = RNA_int_get(op->ptr, "channel");
- clip = BLI_findlink(&bmain->movieclips, RNA_enum_get(op->ptr, "clip"));
+ const Editing *ed = SEQ_editing_get(scene, true);
+ MovieClip *clip = BLI_findlink(&bmain->movieclips, RNA_enum_get(op->ptr, "clip"));
if (clip == NULL) {
BKE_report(op->reports, RPT_ERROR, "Movie clip not found");
return OPERATOR_CANCELLED;
}
- seq = SEQ_sequence_alloc(ed->seqbasep, start_frame, channel, SEQ_TYPE_MOVIECLIP);
- seq->blend_mode = SEQ_TYPE_CROSS;
- seq->clip = clip;
- seq->len = BKE_movieclip_get_duration(clip);
-
- id_us_ensure_real(&seq->clip->id);
-
- BLI_strncpy(seq->name + 2, clip->id.name + 2, sizeof(seq->name) - 2);
- SEQ_sequence_base_unique_name_recursive(&ed->seqbase, seq);
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_sort(scene);
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+ load_data.clip = clip;
- sequencer_add_apply_replace_sel(C, op, seq);
- sequencer_add_apply_overlap(C, op, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ Sequence *seq = SEQ_add_movieclip_strip(scene, ed->seqbasep, &load_data);
+ seq_load_apply_generic_options(C, op, seq);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
@@ -506,36 +467,24 @@ static int sequencer_add_mask_strip_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
- Editing *ed = SEQ_editing_get(scene, true);
- Mask *mask;
- Sequence *seq;
-
- int start_frame, channel;
- start_frame = RNA_int_get(op->ptr, "frame_start");
- channel = RNA_int_get(op->ptr, "channel");
- mask = BLI_findlink(&bmain->masks, RNA_enum_get(op->ptr, "mask"));
+ const Editing *ed = SEQ_editing_get(scene, true);
+ Mask *mask = BLI_findlink(&bmain->masks, RNA_enum_get(op->ptr, "mask"));
if (mask == NULL) {
BKE_report(op->reports, RPT_ERROR, "Mask not found");
return OPERATOR_CANCELLED;
}
- seq = SEQ_sequence_alloc(ed->seqbasep, start_frame, channel, SEQ_TYPE_MASK);
- seq->blend_mode = SEQ_TYPE_CROSS;
- seq->mask = mask;
- seq->len = BKE_mask_get_duration(mask);
-
- id_us_ensure_real(&seq->mask->id);
-
- BLI_strncpy(seq->name + 2, mask->id.name + 2, sizeof(seq->name) - 2);
- SEQ_sequence_base_unique_name_recursive(&ed->seqbase, seq);
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_sort(scene);
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+ load_data.mask = mask;
- sequencer_add_apply_replace_sel(C, op, seq);
- sequencer_add_apply_overlap(C, op, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ Sequence *seq = SEQ_add_mask_strip(scene, ed->seqbasep, &load_data);
+ seq_load_apply_generic_options(C, op, seq);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
@@ -577,100 +526,120 @@ void SEQUENCER_OT_mask_strip_add(struct wmOperatorType *ot)
ot->prop = prop;
}
-static int sequencer_add_generic_strip_exec(bContext *C, wmOperator *op, SeqLoadFn seq_load_fn)
+static void sequencer_add_init(bContext *UNUSED(C), wmOperator *op)
{
- Scene *scene = CTX_data_scene(C);
- Editing *ed = SEQ_editing_get(scene, true);
- SeqLoadInfo seq_load;
- int tot_files;
-
- seq_load_operator_info(&seq_load, C, op);
+ op->customdata = MEM_callocN(sizeof(SequencerAddData), __func__);
+}
- if (seq_load.flag & SEQ_LOAD_REPLACE_SEL) {
- ED_sequencer_deselect_all(scene);
+static void sequencer_add_cancel(bContext *UNUSED(C), wmOperator *op)
+{
+ if (op->customdata) {
+ MEM_freeN(op->customdata);
}
+ op->customdata = NULL;
+}
- tot_files = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
-
- if (tot_files > 1) {
- char dir_only[FILE_MAX];
- char file_only[FILE_MAX];
-
- RNA_BEGIN (op->ptr, itemptr, "files") {
- Sequence *seq;
+static bool sequencer_add_draw_check_fn(PointerRNA *UNUSED(ptr),
+ PropertyRNA *prop,
+ void *UNUSED(user_data))
+{
+ const char *prop_id = RNA_property_identifier(prop);
- RNA_string_get(op->ptr, "directory", dir_only);
- RNA_string_get(&itemptr, "name", file_only);
- BLI_join_dirfile(seq_load.path, sizeof(seq_load.path), dir_only, file_only);
+ return !(STR_ELEM(prop_id, "filepath", "directory", "filename"));
+}
- /* Set seq_load.name, otherwise all video/audio files get the same name. */
- BLI_strncpy(seq_load.name, file_only, sizeof(seq_load.name));
+static void sequencer_add_movie_multiple_strips(bContext *C,
+ wmOperator *op,
+ SeqLoadData *load_data)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ const Editing *ed = SEQ_editing_get(scene, true);
- seq = seq_load_fn(C, ed->seqbasep, &seq_load);
- if (seq) {
- if (seq_load.seq_sound) {
- sequencer_add_apply_overlap(C, op, seq_load.seq_sound);
- }
- sequencer_add_apply_overlap(C, op, seq);
- }
+ RNA_BEGIN (op->ptr, itemptr, "files") {
+ char dir_only[FILE_MAX];
+ char file_only[FILE_MAX];
+ RNA_string_get(op->ptr, "directory", dir_only);
+ RNA_string_get(&itemptr, "name", file_only);
+ BLI_join_dirfile(load_data->path, sizeof(load_data->path), dir_only, file_only);
+ BLI_strncpy(load_data->name, file_only, sizeof(load_data->name));
+ Sequence *seq_movie = NULL;
+ Sequence *seq_sound = NULL;
+ load_data->channel++;
+ seq_movie = SEQ_add_movie_strip(bmain, scene, ed->seqbasep, load_data);
+ load_data->channel--;
+ if (seq_movie == NULL) {
+ BKE_reportf(op->reports, RPT_ERROR, "File '%s' could not be loaded", load_data->path);
}
- RNA_END;
- }
- else { /* Single file./ */
- Sequence *seq;
- seq = seq_load_fn(C, ed->seqbasep, &seq_load);
-
- if (seq) {
- if (seq_load.seq_sound) {
- sequencer_add_apply_overlap(C, op, seq_load.seq_sound);
+ else {
+ if (RNA_boolean_get(op->ptr, "sound")) {
+ seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data);
}
- sequencer_add_apply_overlap(C, op, seq);
+ load_data->start_frame += seq_movie->enddisp - seq_movie->startdisp;
+ seq_load_apply_generic_options(C, op, seq_sound);
+ seq_load_apply_generic_options(C, op, seq_movie);
}
}
+ RNA_END;
+}
- if (op->customdata) {
- MEM_freeN(op->customdata);
- }
+static bool sequencer_add_movie_single_strip(bContext *C, wmOperator *op, SeqLoadData *load_data)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ const Editing *ed = SEQ_editing_get(scene, true);
- if (seq_load.tot_success == 0) {
- BKE_reportf(op->reports, RPT_ERROR, "File '%s' could not be loaded", seq_load.path);
+ Sequence *seq_movie = NULL;
+ Sequence *seq_sound = NULL;
+ load_data->channel++;
+ seq_movie = SEQ_add_movie_strip(bmain, scene, ed->seqbasep, load_data);
+ load_data->channel--;
- return OPERATOR_CANCELLED;
+ if (seq_movie == NULL) {
+ BKE_reportf(op->reports, RPT_ERROR, "File '%s' could not be loaded", load_data->path);
+ return false;
}
+ if (RNA_boolean_get(op->ptr, "sound")) {
+ seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data);
+ }
+ seq_load_apply_generic_options(C, op, seq_sound);
+ seq_load_apply_generic_options(C, op, seq_movie);
- SEQ_sort(scene);
-
- DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
- WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
-
- return OPERATOR_FINISHED;
+ return true;
}
-static void sequencer_add_init(bContext *UNUSED(C), wmOperator *op)
+static int sequencer_add_movie_strip_exec(bContext *C, wmOperator *op)
{
- op->customdata = MEM_callocN(sizeof(SequencerAddData), __func__);
-}
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ SeqLoadData load_data;
+
+ load_data_init_from_operator(&load_data, C, op);
+
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
+
+ const int tot_files = RNA_property_collection_length(op->ptr,
+ RNA_struct_find_property(op->ptr, "files"));
+ if (tot_files > 1) {
+ sequencer_add_movie_multiple_strips(C, op, &load_data);
+ }
+ else {
+ if (!sequencer_add_movie_single_strip(C, op, &load_data)) {
+ return OPERATOR_CANCELLED;
+ }
+ }
-static void sequencer_add_cancel(bContext *UNUSED(C), wmOperator *op)
-{
if (op->customdata) {
MEM_freeN(op->customdata);
}
- op->customdata = NULL;
-}
-
-static bool sequencer_add_draw_check_fn(PointerRNA *UNUSED(ptr),
- PropertyRNA *prop,
- void *UNUSED(user_data))
-{
- const char *prop_id = RNA_property_identifier(prop);
- return !(STR_ELEM(prop_id, "filepath", "directory", "filename"));
-}
+ DEG_relations_tag_update(bmain);
+ DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
-static int sequencer_add_movie_strip_exec(bContext *C, wmOperator *op)
-{
- return sequencer_add_generic_strip_exec(C, op, SEQ_add_movie_strip);
+ return OPERATOR_FINISHED;
}
static int sequencer_add_movie_strip_invoke(bContext *C,
@@ -681,7 +650,8 @@ static int sequencer_add_movie_strip_invoke(bContext *C,
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, false);
- /* Only enable "use_framerate" if there aren't any existing strips, unless overridden by user. */
+ /* Only enable "use_framerate" if there aren't any existing strips, unless overridden by user.
+ */
if (ed && ed->seqbasep && ed->seqbasep->first) {
RNA_boolean_set(op->ptr, "use_framerate", false);
}
@@ -761,9 +731,80 @@ void SEQUENCER_OT_movie_strip_add(struct wmOperatorType *ot)
"Use framerate from the movie to keep sound and video in sync");
}
+static void sequencer_add_sound_multiple_strips(bContext *C,
+ wmOperator *op,
+ SeqLoadData *load_data)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene, true);
+
+ RNA_BEGIN (op->ptr, itemptr, "files") {
+ char dir_only[FILE_MAX];
+ char file_only[FILE_MAX];
+ RNA_string_get(op->ptr, "directory", dir_only);
+ RNA_string_get(&itemptr, "name", file_only);
+ BLI_join_dirfile(load_data->path, sizeof(load_data->path), dir_only, file_only);
+ BLI_strncpy(load_data->name, file_only, sizeof(load_data->name));
+ Sequence *seq = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data);
+ if (seq == NULL) {
+ BKE_reportf(op->reports, RPT_ERROR, "File '%s' could not be loaded", load_data->path);
+ }
+ else {
+ seq_load_apply_generic_options(C, op, seq);
+ load_data->start_frame += seq->enddisp - seq->startdisp;
+ }
+ }
+ RNA_END;
+}
+
+static bool sequencer_add_sound_single_strip(bContext *C, wmOperator *op, SeqLoadData *load_data)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene, true);
+
+ Sequence *seq = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data);
+ if (seq == NULL) {
+ BKE_reportf(op->reports, RPT_ERROR, "File '%s' could not be loaded", load_data->path);
+ return false;
+ }
+ seq_load_apply_generic_options(C, op, seq);
+
+ return true;
+}
+
static int sequencer_add_sound_strip_exec(bContext *C, wmOperator *op)
{
- return sequencer_add_generic_strip_exec(C, op, SEQ_add_sound_strip);
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
+
+ const int tot_files = RNA_property_collection_length(op->ptr,
+ RNA_struct_find_property(op->ptr, "files"));
+ if (tot_files > 1) {
+ sequencer_add_sound_multiple_strips(C, op, &load_data);
+ }
+ else {
+ if (!sequencer_add_sound_single_strip(C, op, &load_data)) {
+ return OPERATOR_CANCELLED;
+ }
+ }
+
+ if (op->customdata) {
+ MEM_freeN(op->customdata);
+ }
+
+ DEG_relations_tag_update(bmain);
+ DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+
+ return OPERATOR_FINISHED;
}
static int sequencer_add_sound_strip_invoke(bContext *C,
@@ -873,78 +914,80 @@ void sequencer_image_seq_reserve_frames(
}
}
-static int sequencer_add_image_strip_exec(bContext *C, wmOperator *op)
+static int sequencer_add_image_strip_calculate_length(wmOperator *op,
+ const int start_frame,
+ int *minframe,
+ int *numdigits)
{
- int minframe, numdigits;
- Scene *scene = CTX_data_scene(C);
- Editing *ed = SEQ_editing_get(scene, true);
- SeqLoadInfo seq_load;
- Sequence *seq;
- Strip *strip;
- StripElem *se;
const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
- seq_load_operator_info(&seq_load, C, op);
-
- /* Images are unique in how they handle this - 1 per strip elem. */
if (use_placeholders) {
- seq_load.len = sequencer_image_seq_get_minmax_frame(
- op, seq_load.start_frame, &minframe, &numdigits);
- }
- else {
- seq_load.len = RNA_property_collection_length(op->ptr,
- RNA_struct_find_property(op->ptr, "files"));
- }
-
- if (seq_load.len == 0) {
- return OPERATOR_CANCELLED;
+ return sequencer_image_seq_get_minmax_frame(op, start_frame, minframe, numdigits);
}
+ return RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
+}
- if (seq_load.flag & SEQ_LOAD_REPLACE_SEL) {
- ED_sequencer_deselect_all(scene);
- }
+static void sequencer_add_image_strip_load_files(
+ wmOperator *op, Sequence *seq, SeqLoadData *load_data, const int minframe, const int numdigits)
+{
+ const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
- /* Main adding function. */
- seq = SEQ_add_image_strip(C, ed->seqbasep, &seq_load);
- strip = seq->strip;
- se = strip->stripdata;
- seq->blend_mode = SEQ_TYPE_ALPHAOVER;
+ SEQ_add_image_set_directory(seq, load_data->path);
if (use_placeholders) {
- sequencer_image_seq_reserve_frames(op, se, seq_load.len, minframe, numdigits);
+ sequencer_image_seq_reserve_frames(
+ op, seq->strip->stripdata, load_data->image.len, minframe, numdigits);
}
else {
+ size_t strip_frame = 0;
RNA_BEGIN (op->ptr, itemptr, "files") {
char *filename = RNA_string_get_alloc(&itemptr, "name", NULL, 0);
- BLI_strncpy(se->name, filename, sizeof(se->name));
+ SEQ_add_image_load_file(seq, strip_frame, filename);
MEM_freeN(filename);
- se++;
+ strip_frame++;
}
RNA_END;
}
+}
- if (seq_load.len == 1) {
- if (seq_load.start_frame < seq_load.end_frame) {
- seq->endstill = seq_load.end_frame - seq_load.start_frame;
- }
+static int sequencer_add_image_strip_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene, true);
+
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+
+ int minframe, numdigits;
+ load_data.image.len = sequencer_add_image_strip_calculate_length(
+ op, load_data.start_frame, &minframe, &numdigits);
+ if (load_data.image.len == 0) {
+ return OPERATOR_CANCELLED;
}
- SEQ_render_init_colorspace(seq);
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_sort(scene);
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
+ }
- /* Last active name. */
- BLI_strncpy(ed->act_imagedir, strip->dir, sizeof(ed->act_imagedir));
- sequencer_add_apply_overlap(C, op, seq);
+ Sequence *seq = SEQ_add_image_strip(CTX_data_main(C), scene, ed->seqbasep, &load_data);
+ sequencer_add_image_strip_load_files(op, seq, &load_data, minframe, numdigits);
+ SEQ_add_image_init_alpha_mode(seq);
- if (op->customdata) {
- MEM_freeN(op->customdata);
+ /* Adjust length. */
+ if (load_data.image.len == 1) {
+ SEQ_transform_set_right_handle_frame(seq, load_data.image.end_frame);
+ SEQ_time_update_sequence(scene, seq);
}
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ seq_load_apply_generic_options(C, op, seq);
+
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+ if (op->customdata) {
+ MEM_freeN(op->customdata);
+ }
+
return OPERATOR_FINISHED;
}
@@ -1016,80 +1059,46 @@ static int sequencer_add_effect_strip_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, true);
- Sequence *seq;
- struct SeqEffectHandle sh;
- Sequence *seq1, *seq2, *seq3;
const char *error_msg;
- int start_frame, end_frame, channel, type;
- start_frame = RNA_int_get(op->ptr, "frame_start");
- end_frame = RNA_int_get(op->ptr, "frame_end");
- channel = RNA_int_get(op->ptr, "channel");
- type = RNA_enum_get(op->ptr, "type");
+ SeqLoadData load_data;
+ load_data_init_from_operator(&load_data, C, op);
+ load_data.effect.type = RNA_enum_get(op->ptr, "type");
- if (!seq_effect_find_selected(scene, NULL, type, &seq1, &seq2, &seq3, &error_msg)) {
+ Sequence *seq1, *seq2, *seq3;
+ if (!seq_effect_find_selected(
+ scene, NULL, load_data.effect.type, &seq1, &seq2, &seq3, &error_msg)) {
BKE_report(op->reports, RPT_ERROR, error_msg);
return OPERATOR_CANCELLED;
}
- /* Check its start and end frames are valid. */
- if (seq1 == NULL && end_frame <= start_frame) {
- end_frame = start_frame + 1;
- RNA_int_set(op->ptr, "frame_end", end_frame);
- }
-
- seq = SEQ_sequence_alloc(ed->seqbasep, start_frame, channel, type);
- BLI_strncpy(seq->name + 2, SEQ_sequence_give_name(seq), sizeof(seq->name) - 2);
- SEQ_sequence_base_unique_name_recursive(&ed->seqbase, seq);
-
- sh = SEQ_effect_handle_get(seq);
- sh.init(seq);
- seq->seq1 = seq1;
- seq->seq2 = seq2;
- seq->seq3 = seq3;
-
- if (!seq1) {
- seq->len = 1; /* Effect is generator, set non zero length. */
- SEQ_transform_set_right_handle_frame(seq, end_frame);
+ if (RNA_boolean_get(op->ptr, "replace_sel")) {
+ ED_sequencer_deselect_all(scene);
}
- seq->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
- SEQ_time_update_sequence(scene, seq);
-
- if (seq->type == SEQ_TYPE_COLOR) {
- SolidColorVars *colvars = (SolidColorVars *)seq->effectdata;
- RNA_float_get_array(op->ptr, "color", colvars->col);
- seq->blend_mode = SEQ_TYPE_CROSS;
- }
- else if (seq->type == SEQ_TYPE_ADJUSTMENT) {
- seq->blend_mode = SEQ_TYPE_CROSS;
- }
- else if (seq->type == SEQ_TYPE_TEXT) {
- seq->blend_mode = SEQ_TYPE_ALPHAOVER;
- }
- else if (SEQ_effect_get_num_inputs(seq->type) == 1) {
- seq->blend_mode = seq1->blend_mode;
- }
+ load_data.effect.seq1 = seq1;
+ load_data.effect.seq2 = seq2;
+ load_data.effect.seq3 = seq3;
/* Set channel. If unset, use lowest free one above strips. */
if (!RNA_struct_property_is_set(op->ptr, "channel")) {
- if (seq->seq1) {
- int chan = max_iii(seq->seq1 ? seq->seq1->machine : 0,
- seq->seq2 ? seq->seq2->machine : 0,
- seq->seq3 ? seq->seq3->machine : 0);
+ if (seq1 != NULL) {
+ int chan = max_iii(
+ seq1 ? seq1->machine : 0, seq2 ? seq2->machine : 0, seq3 ? seq3->machine : 0);
if (chan < MAXSEQ) {
- seq->machine = chan;
+ load_data.channel = chan;
}
}
}
- sequencer_add_apply_replace_sel(C, op, seq);
- sequencer_add_apply_overlap(C, op, seq);
+ Sequence *seq = SEQ_add_effect_strip(scene, ed->seqbasep, &load_data);
+ seq_load_apply_generic_options(C, op, seq);
- SEQ_relations_update_changed_seq_and_deps(scene, seq, 1, 1); /* Runs SEQ_time_update_sequence. */
- SEQ_sort(scene);
+ if (seq->type == SEQ_TYPE_COLOR) {
+ SolidColorVars *colvars = (SolidColorVars *)seq->effectdata;
+ RNA_float_get_array(op->ptr, "color", colvars->col);
+ }
- SEQ_relations_invalidate_cache_composite(scene, seq);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index 608e220c582..78d263dffad 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -1870,93 +1870,28 @@ void SEQUENCER_OT_images_separate(wmOperatorType *ot)
/** \name Toggle Meta Strip Operator
* \{ */
-void recurs_sel_seq(Sequence *seqm)
-{
- Sequence *seq;
-
- seq = seqm->seqbase.first;
- while (seq) {
-
- if (seqm->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) {
- seq->flag &= ~SEQ_ALLSEL;
- }
- else if (seqm->flag & SELECT) {
- seq->flag |= SELECT;
- }
- else {
- seq->flag &= ~SEQ_ALLSEL;
- }
-
- if (seq->seqbase.first) {
- recurs_sel_seq(seq);
- }
-
- seq = seq->next;
- }
-}
-
static int sequencer_meta_toggle_exec(bContext *C, wmOperator *UNUSED(op))
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, false);
- Sequence *last_seq = SEQ_select_active_get(scene);
- MetaStack *ms;
+ Sequence *active_seq = SEQ_select_active_get(scene);
- if (last_seq && last_seq->type == SEQ_TYPE_META && last_seq->flag & SELECT) {
+ if (active_seq && active_seq->type == SEQ_TYPE_META && active_seq->flag & SELECT) {
/* Enter metastrip. */
- ms = MEM_mallocN(sizeof(MetaStack), "metastack");
- BLI_addtail(&ed->metastack, ms);
- ms->parseq = last_seq;
- ms->oldbasep = ed->seqbasep;
- copy_v2_v2_int(ms->disp_range, &ms->parseq->startdisp);
-
- ed->seqbasep = &last_seq->seqbase;
-
+ SEQ_meta_stack_alloc(ed, active_seq);
+ SEQ_seqbase_active_set(ed, &active_seq->seqbase);
SEQ_select_active_set(scene, NULL);
}
else {
/* Exit metastrip if possible. */
-
- Sequence *seq;
-
if (BLI_listbase_is_empty(&ed->metastack)) {
return OPERATOR_CANCELLED;
}
- ms = ed->metastack.last;
- BLI_remlink(&ed->metastack, ms);
-
- ed->seqbasep = ms->oldbasep;
-
- /* For old files, update from meta. */
- if (ms->disp_range[0] == ms->disp_range[1]) {
- copy_v2_v2_int(ms->disp_range, &ms->parseq->startdisp);
- }
-
- /* Recalc all: the meta can have effects connected to it. */
- for (seq = ed->seqbasep->first; seq; seq = seq->next) {
- SEQ_time_update_sequence(scene, seq);
- }
-
- /* 2.73+, keeping endpoints is important!
- * Moving them around means you can't usefully use metas in a complex edit. */
-#if 1
- SEQ_transform_set_left_handle_frame(ms->parseq, ms->disp_range[0]);
- SEQ_transform_set_right_handle_frame(ms->parseq, ms->disp_range[1]);
- SEQ_transform_fix_single_image_seq_offsets(ms->parseq);
- SEQ_time_update_sequence(scene, ms->parseq);
-#else
- if (SEQ_transform_test_overlap(ed->seqbasep, ms->parseq)) {
- SEQ_transform_seqbase_shuffle(ed->seqbasep, ms->parseq, scene);
- }
-#endif
-
+ MetaStack *ms = SEQ_meta_stack_active_get(ed);
+ SEQ_seqbase_active_set(ed, ms->oldbasep);
SEQ_select_active_set(scene, ms->parseq);
-
- ms->parseq->flag |= SELECT;
- recurs_sel_seq(ms->parseq);
-
- MEM_freeN(ms);
+ SEQ_meta_stack_free(ed, ms);
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -1990,48 +1925,44 @@ static int sequencer_meta_make_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, false);
+ Sequence *active_seq = SEQ_select_active_get(scene);
+ ListBase *active_seqbase = SEQ_active_seqbase_get(ed);
- Sequence *seq, *seqm, *next, *last_seq = SEQ_select_active_get(scene);
- int channel_max = 1;
-
- if (SEQ_transform_seqbase_isolated_sel_check(ed->seqbasep) == false) {
+ if (SEQ_transform_seqbase_isolated_sel_check(active_seqbase) == false) {
BKE_report(op->reports, RPT_ERROR, "Please select all related strips");
return OPERATOR_CANCELLED;
}
SEQ_prefetch_stop(scene);
- /* Remove all selected from main list, and put in meta. */
-
- seqm = SEQ_sequence_alloc(ed->seqbasep, 1, 1, SEQ_TYPE_META); /* Channel number set later. */
- strcpy(seqm->name + 2, "MetaStrip");
- seqm->flag = SELECT;
+ int channel_max = 1, meta_start_frame = MAXFRAME, meta_end_frame = MINFRAME;
+ Sequence *seqm = SEQ_sequence_alloc(active_seqbase, 1, 1, SEQ_TYPE_META);
- seq = ed->seqbasep->first;
- while (seq) {
- next = seq->next;
- if (seq != seqm && (seq->flag & SELECT)) {
- SEQ_relations_invalidate_cache_composite(scene, seq);
- channel_max = max_ii(seq->machine, channel_max);
- /* Sequence is moved within the same edit, no need to re-generate the UUID. */
- BLI_remlink(ed->seqbasep, seq);
+ /* Remove all selected from main list, and put in meta.
+ * Sequence is moved within the same edit, no need to re-generate the UUID. */
+ LISTBASE_FOREACH_MUTABLE (Sequence *, seq, active_seqbase) {
+ if (seq != seqm && seq->flag & SELECT) {
+ BLI_remlink(active_seqbase, seq);
BLI_addtail(&seqm->seqbase, seq);
+ SEQ_relations_invalidate_cache_preprocessed(scene, seq);
+ channel_max = max_ii(seq->machine, channel_max);
+ meta_start_frame = min_ii(seq->startdisp, meta_start_frame);
+ meta_end_frame = max_ii(seq->enddisp, meta_end_frame);
}
- seq = next;
}
- seqm->machine = last_seq ? last_seq->machine : channel_max;
- SEQ_time_update_sequence(scene, seqm);
+ seqm->machine = active_seq ? active_seq->machine : channel_max;
+ strcpy(seqm->name + 2, "MetaStrip");
+ SEQ_sequence_base_unique_name_recursive(&ed->seqbase, seqm);
+ seqm->start = meta_start_frame;
+ seqm->len = meta_end_frame - meta_start_frame;
+ SEQ_time_update_sequence(scene, seqm);
SEQ_select_active_set(scene, seqm);
-
- if (SEQ_transform_test_overlap(ed->seqbasep, seqm)) {
- SEQ_transform_seqbase_shuffle(ed->seqbasep, seqm, scene);
+ if (SEQ_transform_test_overlap(active_seqbase, seqm)) {
+ SEQ_transform_seqbase_shuffle(active_seqbase, seqm, scene);
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
-
- SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seqm);
- SEQ_relations_invalidate_cache_composite(scene, seqm);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
@@ -2058,95 +1989,43 @@ void SEQUENCER_OT_meta_make(wmOperatorType *ot)
/** \name UnMeta Strip Operator
* \{ */
-static int seq_depends_on_meta(Sequence *seq, Sequence *seqm)
-{
- if (seq == seqm) {
- return 1;
- }
- if (seq->seq1 && seq_depends_on_meta(seq->seq1, seqm)) {
- return 1;
- }
- if (seq->seq2 && seq_depends_on_meta(seq->seq2, seqm)) {
- return 1;
- }
- if (seq->seq3 && seq_depends_on_meta(seq->seq3, seqm)) {
- return 1;
- }
- return 0;
-}
-
-static void recurs_del_seq_flag(Scene *scene, ListBase *lb, short flag, short deleteall)
-{
- Sequence *seq, *seqn;
- Sequence *last_seq = SEQ_select_active_get(scene);
-
- seq = lb->first;
- while (seq) {
- seqn = seq->next;
- if ((seq->flag & flag) || deleteall) {
- BLI_remlink(lb, seq);
- if (seq == last_seq) {
- SEQ_select_active_set(scene, NULL);
- }
- if (seq->type == SEQ_TYPE_META) {
- recurs_del_seq_flag(scene, &seq->seqbase, flag, 1);
- }
- SEQ_sequence_free(scene, seq, true);
- }
- seq = seqn;
- }
-}
-
static int sequencer_meta_separate_exec(bContext *C, wmOperator *UNUSED(op))
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene, false);
+ Sequence *active_seq = SEQ_select_active_get(scene);
- Sequence *seq, *last_seq = SEQ_select_active_get(scene); /* last_seq checks (ed == NULL) */
-
- if (last_seq == NULL || last_seq->type != SEQ_TYPE_META) {
+ if (active_seq == NULL || active_seq->type != SEQ_TYPE_META) {
return OPERATOR_CANCELLED;
}
SEQ_prefetch_stop(scene);
- for (seq = last_seq->seqbase.first; seq != NULL; seq = seq->next) {
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ LISTBASE_FOREACH (Sequence *, seq, &active_seq->seqbase) {
+ SEQ_relations_invalidate_cache_preprocessed(scene, seq);
}
- /* This moves strips from meta to parent, sating within same edit and no new strips are
- * allocated. If the UUID was unique already (as it should) it will stay unique.
- * No need to re-generate the UUIDs. */
- BLI_movelisttolist(ed->seqbasep, &last_seq->seqbase);
+ /* Remove all selected from meta, and put in main list.
+ * Sequence is moved within the same edit, no need to re-generate the UUID. */
+ BLI_movelisttolist(ed->seqbasep, &active_seq->seqbase);
+ BLI_listbase_clear(&active_seq->seqbase);
- BLI_listbase_clear(&last_seq->seqbase);
+ ListBase *active_seqbase = SEQ_active_seqbase_get(ed);
+ SEQ_edit_flag_for_removal(scene, active_seqbase, active_seq);
+ SEQ_edit_remove_flagged_sequences(scene, active_seqbase);
- BLI_remlink(ed->seqbasep, last_seq);
- SEQ_sequence_free(scene, last_seq, true);
-
- /* Empty meta strip, delete all effects depending on it. */
- for (seq = ed->seqbasep->first; seq; seq = seq->next) {
- if ((seq->type & SEQ_TYPE_EFFECT) && seq_depends_on_meta(seq, last_seq)) {
- seq->flag |= SEQ_FLAG_DELETE;
- }
- }
-
- recurs_del_seq_flag(scene, ed->seqbasep, SEQ_FLAG_DELETE, 0);
-
- /* Test for effects and overlap
- * don't use SEQ_CURRENT_BEGIN since that would be recursive. */
- for (seq = ed->seqbasep->first; seq; seq = seq->next) {
+ /* Test for effects and overlap. */
+ LISTBASE_FOREACH (Sequence *, seq, active_seqbase) {
if (seq->flag & SELECT) {
seq->flag &= ~SEQ_OVERLAP;
- if (SEQ_transform_test_overlap(ed->seqbasep, seq)) {
- SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
+ if (SEQ_transform_test_overlap(active_seqbase, seq)) {
+ SEQ_transform_seqbase_shuffle(active_seqbase, seq, scene);
}
}
}
SEQ_sort(scene);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
-
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h
index 4c942a83f2b..767ac76efe6 100644
--- a/source/blender/editors/space_sequencer/sequencer_intern.h
+++ b/source/blender/editors/space_sequencer/sequencer_intern.h
@@ -79,7 +79,7 @@ struct Sequence *find_neighboring_sequence(struct Scene *scene,
struct Sequence *test,
int lr,
int sel);
-void recurs_sel_seq(struct Sequence *seqm);
+void recurs_sel_seq(struct Sequence *seq_meta);
int seq_effect_find_selected(struct Scene *scene,
struct Sequence *activeseq,
int type,
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index ffcb3d35d5a..5f0a18fbd0b 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -360,6 +360,31 @@ static void select_neighbor_from_last(Scene *scene, int lr)
}
#endif
+void recurs_sel_seq(Sequence *seq_meta)
+{
+ Sequence *seq;
+
+ seq = seq_meta->seqbase.first;
+ while (seq) {
+
+ if (seq_meta->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) {
+ seq->flag &= ~SEQ_ALLSEL;
+ }
+ else if (seq_meta->flag & SELECT) {
+ seq->flag |= SELECT;
+ }
+ else {
+ seq->flag &= ~SEQ_ALLSEL;
+ }
+
+ if (seq->seqbase.first) {
+ recurs_sel_seq(seq);
+ }
+
+ seq = seq->next;
+ }
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c
index 93d5d41e121..43cbcb0aba4 100644
--- a/source/blender/editors/transform/transform_constraints.c
+++ b/source/blender/editors/transform/transform_constraints.c
@@ -572,7 +572,8 @@ static void constraints_rotation_impl(TransInfo *t,
break;
}
/* don't flip axis if asked to or if num input */
- if (r_angle && !((mode & CON_NOFLIP) || hasNumInput(&t->num) || (t->flag & T_INPUT_IS_VALUES_FINAL))) {
+ if (r_angle &&
+ !((mode & CON_NOFLIP) || hasNumInput(&t->num) || (t->flag & T_INPUT_IS_VALUES_FINAL))) {
float view_vector[3];
view_vector_calc(t, t->center_global, view_vector);
if (dot_v3v3(r_vec, view_vector) > 0.0f) {
diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c
index 4ab52b78002..30418471d6d 100644
--- a/source/blender/editors/transform/transform_convert_sequencer.c
+++ b/source/blender/editors/transform/transform_convert_sequencer.c
@@ -90,35 +90,17 @@ static void SeqTransInfo(TransInfo *t, Sequence *seq, int *r_recursive, int *r_c
Scene *scene = t->scene;
int cfra = CFRA;
- int left = SEQ_transform_get_left_handle_frame(seq, true);
- int right = SEQ_transform_get_right_handle_frame(seq, true);
+ int left = SEQ_transform_get_left_handle_frame(seq, false);
+ int right = SEQ_transform_get_right_handle_frame(seq, false);
if (seq->depth == 0 && ((seq->flag & SELECT) == 0 || (seq->flag & SEQ_LOCK))) {
*r_recursive = false;
*r_count = 0;
*r_flag = 0;
}
- else if (seq->type == SEQ_TYPE_META) {
-
- /* for meta's we only ever need to extend their children, no matter what depth
- * just check the meta's are in the bounds */
- if (t->frame_side == 'R' && right <= cfra) {
- *r_recursive = false;
- }
- else if (t->frame_side == 'L' && left >= cfra) {
- *r_recursive = false;
- }
- else {
- *r_recursive = true;
- }
-
- *r_count = 1;
- *r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
- }
else {
-
- *r_recursive = false; /* not a meta, so no thinking here */
- *r_count = 1; /* unless its set to 0, extend will never set 2 handles at once */
+ *r_recursive = false;
+ *r_count = 1; /* unless its set to 0, extend will never set 2 handles at once */
*r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
if (t->frame_side == 'R') {
@@ -183,26 +165,9 @@ static void SeqTransInfo(TransInfo *t, Sequence *seq, int *r_recursive, int *r_c
else {
/* Nested, different rules apply */
-#ifdef SEQ_TX_NESTED_METAS
*r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
*r_count = 1; /* ignore the selection for nested */
*r_recursive = (seq->type == SEQ_TYPE_META);
-#else
- if (seq->type == SEQ_TYPE_META) {
- /* Meta's can only directly be moved between channels since they
- * don't have their start and length set directly (children affect that)
- * since this Meta is nested we don't need any of its data in fact.
- * SEQ_time_update_sequence() will update its settings when run on the top-level meta. */
- *r_flag = 0;
- *r_count = 0;
- *r_recursive = true;
- }
- else {
- *r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
- *r_count = 1; /* ignore the selection for nested */
- *r_recursive = false;
- }
-#endif
}
}
}
@@ -645,8 +610,6 @@ void createTransSeqData(TransInfo *t)
/* commented _only_ because the meta may have animation data which
* needs moving too T28158. */
-#define SEQ_TX_NESTED_METAS
-
BLI_INLINE void trans_update_seq(Scene *sce, Sequence *seq, int old_start, int sel_flag)
{
if (seq->depth == 0) {
@@ -693,17 +656,10 @@ static void flushTransSeq(TransInfo *t)
switch (tdsq->sel_flag) {
case SELECT:
-#ifdef SEQ_TX_NESTED_METAS
if ((seq->depth != 0 || SEQ_transform_sequence_can_be_translated(seq))) {
/* for meta's, their children move */
seq->start = new_frame - tdsq->start_offset;
}
-#else
- if (seq->type != SEQ_TYPE_META && (seq->depth != 0 || seq_tx_test(seq))) {
- /* for meta's, their children move */
- seq->start = new_frame - tdsq->start_offset;
- }
-#endif
if (seq->depth == 0) {
seq->machine = round_fl_to_int(td2d->loc[1]);
CLAMP(seq->machine, 1, MAXSEQ);
diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c
index 88802e0d868..f46975c9378 100644
--- a/source/blender/editors/uvedit/uvedit_select.c
+++ b/source/blender/editors/uvedit/uvedit_select.c
@@ -3313,7 +3313,6 @@ static bool do_lasso_select_mesh_uv(bContext *C,
uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT);
}
- /* don't indent to avoid diff noise! */
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *obedit = objects[ob_index];
@@ -3323,7 +3322,7 @@ static bool do_lasso_select_mesh_uv(bContext *C,
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
- if (use_face_center) { /* Face Center Sel */
+ if (use_face_center) { /* Face Center Select. */
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
BM_elem_flag_disable(efa, BM_ELEM_TAG);
/* assume not touched */
@@ -3366,7 +3365,7 @@ static bool do_lasso_select_mesh_uv(bContext *C,
}
}
}
- else { /* Vert Sel */
+ else { /* Vert Selection. */
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c
index 28bf26aa343..d36c7bbe486 100644
--- a/source/blender/imbuf/intern/anim_movie.c
+++ b/source/blender/imbuf/intern/anim_movie.c
@@ -1149,7 +1149,7 @@ static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Typ
}
}
else {
- pos = (int64_t)(position - anim->preseek) * AV_TIME_BASE / frame_rate;
+ pos = (int64_t)position * AV_TIME_BASE / frame_rate;
av_log(anim->pFormatCtx,
AV_LOG_DEBUG,
diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h
index 1635aa7646b..89e547aaf22 100644
--- a/source/blender/makesdna/DNA_windowmanager_types.h
+++ b/source/blender/makesdna/DNA_windowmanager_types.h
@@ -355,7 +355,7 @@ typedef struct wmKeyMapItem {
short val;
/** Oskey is apple or windowskey, value denotes order of pressed. */
short shift, ctrl, alt, oskey;
- /** Rawkey modifier. */
+ /** Raw-key modifier. */
short keymodifier;
/* flag: inactive, expanded */
diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c
index fd19352a9a5..6e2005b7314 100644
--- a/source/blender/makesrna/intern/rna_ID.c
+++ b/source/blender/makesrna/intern/rna_ID.c
@@ -528,6 +528,9 @@ static ID *rna_ID_copy(ID *id, Main *bmain)
if (newid != NULL) {
id_us_min(newid);
}
+
+ WM_main_add_notifier(NC_ID | NA_ADDED, NULL);
+
return newid;
}
diff --git a/source/blender/makesrna/intern/rna_animviz.c b/source/blender/makesrna/intern/rna_animviz.c
index 8e95388fe2b..7be155605e6 100644
--- a/source/blender/makesrna/intern/rna_animviz.c
+++ b/source/blender/makesrna/intern/rna_animviz.c
@@ -150,7 +150,7 @@ static void rna_def_animviz_motion_path(BlenderRNA *brna)
prop = RNA_def_property(srna, "line_thickness", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "line_thickness");
RNA_def_property_range(prop, 1, 6);
- RNA_def_property_ui_text(prop, "Line Thickness", "Line thickness for drawing path");
+ RNA_def_property_ui_text(prop, "Line Thickness", "Line thickness for motion path");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
/* Settings */
@@ -176,7 +176,7 @@ static void rna_def_animviz_motion_path(BlenderRNA *brna)
/* Draw lines between keyframes */
prop = RNA_def_property(srna, "lines", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", MOTIONPATH_FLAG_LINES);
- RNA_def_property_ui_text(prop, "Lines", "Draw straight lines between keyframe points");
+ RNA_def_property_ui_text(prop, "Lines", "Use straight lines between keyframe points");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
}
diff --git a/source/blender/makesrna/intern/rna_armature.c b/source/blender/makesrna/intern/rna_armature.c
index 32162b068f6..554f04ca23c 100644
--- a/source/blender/makesrna/intern/rna_armature.c
+++ b/source/blender/makesrna/intern/rna_armature.c
@@ -966,10 +966,11 @@ static void rna_def_bone_common(StructRNA *srna, int editbone)
prop = RNA_def_property(srna, "show_wire", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", BONE_DRAWWIRE);
- RNA_def_property_ui_text(prop,
- "Display Wire",
- "Bone is always drawn as Wireframe regardless of viewport draw mode "
- "(useful for non-obstructive custom bone shapes)");
+ RNA_def_property_ui_text(
+ prop,
+ "Display Wire",
+ "Bone is always displayed in wireframe regardless of viewport shading mode "
+ "(useful for non-obstructive custom bone shapes)");
RNA_def_property_update(prop, 0, "rna_Armature_redraw_data");
/* XXX: use_cyclic_offset is deprecated in 2.5. May/may not return */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 3dcc9471906..e7daa55af6c 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -1887,7 +1887,8 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_lasso", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_BRUSH_DISSABLE_LASSO);
- RNA_def_property_ui_text(prop, "Show Lasso", "Do not draw fill color while drawing the stroke");
+ RNA_def_property_ui_text(
+ prop, "Show Lasso", "Do not display fill color while drawing the stroke");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "use_occlude_eraser", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c
index ea019c70445..206ebc2cb14 100644
--- a/source/blender/makesrna/intern/rna_color.c
+++ b/source/blender/makesrna/intern/rna_color.c
@@ -896,6 +896,9 @@ static void rna_def_curvemapping(BlenderRNA *brna)
func = RNA_def_function(srna, "update", "BKE_curvemapping_changed_all");
RNA_def_function_ui_description(func, "Update curve mapping after making changes");
+ func = RNA_def_function(srna, "reset_view", "BKE_curvemapping_reset_view");
+ RNA_def_function_ui_description(func, "Reset the curve mapping grid to its clipping size");
+
func = RNA_def_function(srna, "initialize", "rna_CurveMap_initialize");
RNA_def_function_ui_description(func, "Initialize curve");
@@ -1107,7 +1110,7 @@ static void rna_def_histogram(BlenderRNA *brna)
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "mode");
RNA_def_property_enum_items(prop, prop_mode_items);
- RNA_def_property_ui_text(prop, "Mode", "Channels to display when drawing the histogram");
+ RNA_def_property_ui_text(prop, "Mode", "Channels to display in the histogram");
prop = RNA_def_property(srna, "show_line", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", HISTO_FLAG_LINE);
diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c
index a34c3c7b536..dab76c84e6a 100644
--- a/source/blender/makesrna/intern/rna_fluid.c
+++ b/source/blender/makesrna/intern/rna_fluid.c
@@ -2553,7 +2553,7 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna)
RNA_def_property_float_sdna(prop, NULL, "display_thickness");
RNA_def_property_range(prop, 0.001, 1000.0);
RNA_def_property_ui_range(prop, 0.1, 100.0, 0.1, 3);
- RNA_def_property_ui_text(prop, "Thickness", "Thickness of smoke drawing in the viewport");
+ RNA_def_property_ui_text(prop, "Thickness", "Thickness of smoke display in the viewport");
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, NULL);
prop = RNA_def_property(srna, "display_interpolation", PROP_ENUM, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c
index f9caa746dac..1a0497b72f4 100644
--- a/source/blender/makesrna/intern/rna_gpencil.c
+++ b/source/blender/makesrna/intern/rna_gpencil.c
@@ -1632,7 +1632,7 @@ static void rna_def_gpencil_stroke(BlenderRNA *brna)
prop = RNA_def_property(srna, "display_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, stroke_display_mode_items);
- RNA_def_property_ui_text(prop, "Draw Mode", "Coordinate space that stroke is in");
+ RNA_def_property_ui_text(prop, "Display Mode", "Coordinate space that stroke is in");
RNA_def_property_update(prop, 0, "rna_GPencil_update");
prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
@@ -2227,13 +2227,13 @@ static void rna_def_gpencil_layer(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_points", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LAYER_DRAWDEBUG);
RNA_def_property_ui_text(
- prop, "Show Points", "Draw the points which make up the strokes (for debugging purposes)");
+ prop, "Show Points", "Show the points which make up the strokes (for debugging purposes)");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update");
/* In Front */
prop = RNA_def_property(srna, "show_in_front", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_LAYER_NO_XRAY);
- RNA_def_property_ui_text(prop, "In Front", "Make the layer draw in front of objects");
+ RNA_def_property_ui_text(prop, "In Front", "Make the layer display in front of objects");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update");
/* Parent object */
diff --git a/source/blender/makesrna/intern/rna_lattice.c b/source/blender/makesrna/intern/rna_lattice.c
index 319aeb69a2b..707799e5633 100644
--- a/source/blender/makesrna/intern/rna_lattice.c
+++ b/source/blender/makesrna/intern/rna_lattice.c
@@ -371,7 +371,7 @@ static void rna_def_lattice(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "flag", LT_OUTSIDE);
RNA_def_property_boolean_funcs(prop, NULL, "rna_Lattice_use_outside_set");
RNA_def_property_ui_text(
- prop, "Outside", "Only draw, and take into account, the outer vertices");
+ prop, "Outside", "Only display and take into account the outer vertices");
RNA_def_property_update(prop, 0, "rna_Lattice_update_data_editlatt");
prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_light.c b/source/blender/makesrna/intern/rna_light.c
index 433d499b4c1..52ebd5af993 100644
--- a/source/blender/makesrna/intern/rna_light.c
+++ b/source/blender/makesrna/intern/rna_light.c
@@ -504,7 +504,7 @@ static void rna_def_spot_light(BlenderRNA *brna)
RNA_def_property_ui_text(
prop,
"Show Cone",
- "Draw transparent cone in 3D view to visualize which objects are contained in it");
+ "Display transparent cone in 3D view to visualize which objects are contained in it");
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
}
diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c
index 94b56e4f4e0..84c831a178e 100644
--- a/source/blender/makesrna/intern/rna_material.c
+++ b/source/blender/makesrna/intern/rna_material.c
@@ -616,7 +616,7 @@ static void rna_def_material_greasepencil(BlenderRNA *brna)
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "mode");
RNA_def_property_enum_items(prop, gpcolordata_mode_types_items);
- RNA_def_property_ui_text(prop, "Mode Type", "Select draw mode for stroke");
+ RNA_def_property_ui_text(prop, "Line Type", "Select line type for strokes");
RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialGpencil_update");
/* stroke style */
diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c
index e2ec90ae9aa..5008240ea33 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -1743,8 +1743,7 @@ static void rna_def_modifier_subsurf(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_only_control_edges", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", eSubsurfModifierFlag_ControlEdges);
- RNA_def_property_ui_text(
- prop, "Optimal Display", "Skip drawing/rendering of interior subdivided edges");
+ RNA_def_property_ui_text(prop, "Optimal Display", "Skip displaying interior subdivided edges");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_creases", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_movieclip.c b/source/blender/makesrna/intern/rna_movieclip.c
index d32872d1682..c9520c939f4 100644
--- a/source/blender/makesrna/intern/rna_movieclip.c
+++ b/source/blender/makesrna/intern/rna_movieclip.c
@@ -290,7 +290,7 @@ static void rna_def_moviecliUser(BlenderRNA *brna)
RNA_def_property_enum_items(prop, clip_render_size_items);
RNA_def_property_ui_text(prop,
"Proxy Render Size",
- "Draw preview using full resolution or different proxy resolutions");
+ "Display preview using full resolution or different proxy resolutions");
RNA_def_property_update(
prop, NC_MOVIECLIP | ND_DISPLAY, "rna_MovieClipUser_proxy_render_settings_update");
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 2c0d7ba3d06..dfb882cde33 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -10366,7 +10366,7 @@ static void rna_def_node(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_texture", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", NODE_ACTIVE_TEXTURE);
- RNA_def_property_ui_text(prop, "Show Texture", "Draw node in viewport textured draw mode");
+ RNA_def_property_ui_text(prop, "Show Texture", "Display node in viewport textured shading mode");
RNA_def_property_update(prop, 0, "rna_Node_update");
/* generic property update function */
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index 799d1e8190c..a70b776b07a 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -3316,7 +3316,8 @@ static void rna_def_object(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_wire", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "dtx", OB_DRAWWIRE);
- RNA_def_property_ui_text(prop, "Display Wire", "Add the object's wireframe over solid drawing");
+ RNA_def_property_ui_text(
+ prop, "Display Wire", "Display the object's wireframe over solid shading");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
prop = RNA_def_property(srna, "show_all_edges", PROP_BOOLEAN, PROP_NONE);
@@ -3338,7 +3339,7 @@ static void rna_def_object(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_in_front", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "dtx", OB_DRAW_IN_FRONT);
- RNA_def_property_ui_text(prop, "In Front", "Make the object draw in front of others");
+ RNA_def_property_ui_text(prop, "In Front", "Make the object display in front of others");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_GPencil_update");
/* pose */
diff --git a/source/blender/makesrna/intern/rna_particle.c b/source/blender/makesrna/intern/rna_particle.c
index f81f965d0c8..d94e68a6808 100644
--- a/source/blender/makesrna/intern/rna_particle.c
+++ b/source/blender/makesrna/intern/rna_particle.c
@@ -2694,7 +2694,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_health", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "draw", PART_DRAW_HEALTH);
- RNA_def_property_ui_text(prop, "Health", "Draw boid health");
+ RNA_def_property_ui_text(prop, "Health", "Display boid health");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "use_absolute_path_time", PROP_BOOLEAN, PROP_NONE);
@@ -2742,7 +2742,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_render_adaptive", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "draw", PART_DRAW_REN_ADAPT);
- RNA_def_property_ui_text(prop, "Adaptive Render", "Draw steps of the particle path");
+ RNA_def_property_ui_text(prop, "Adaptive Render", "Display steps of the particle path");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "use_velocity_length", PROP_BOOLEAN, PROP_NONE);
@@ -2764,7 +2764,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
RNA_def_property_enum_sdna(prop, NULL, "draw_as");
RNA_def_property_enum_items(prop, part_draw_as_items);
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_Particle_draw_as_itemf");
- RNA_def_property_ui_text(prop, "Particle Drawing", "How particles are drawn in viewport");
+ RNA_def_property_ui_text(prop, "Particle Display", "How particles are displayed in viewport");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "render_type", PROP_ENUM, PROP_NONE);
@@ -2777,14 +2777,14 @@ static void rna_def_particle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "display_color", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "draw_col");
RNA_def_property_enum_items(prop, draw_col_items);
- RNA_def_property_ui_text(prop, "Draw Color", "Draw additional particle data as a color");
+ RNA_def_property_ui_text(prop, "Display Color", "Display additional particle data as a color");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "display_size", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "draw_size");
RNA_def_property_range(prop, 0, 1000);
RNA_def_property_ui_range(prop, 0, 100, 1, -1);
- RNA_def_property_ui_text(prop, "Draw Size", "Size of particles on viewport");
+ RNA_def_property_ui_text(prop, "Display Size", "Size of particles on viewport");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "child_type", PROP_ENUM, PROP_NONE);
@@ -2797,7 +2797,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
RNA_def_property_int_sdna(prop, NULL, "draw_step");
RNA_def_property_range(prop, 0, 10);
RNA_def_property_ui_range(prop, 0, 7, 1, -1);
- RNA_def_property_ui_text(prop, "Steps", "How many steps paths are drawn with (power of 2)");
+ RNA_def_property_ui_text(prop, "Steps", "How many steps paths are displayed with (power of 2)");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "render_step", PROP_INT, PROP_NONE);
@@ -3418,13 +3418,13 @@ static void rna_def_particle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "path_start", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "path_start");
RNA_def_property_float_funcs(prop, NULL, NULL, "rna_PartSetting_pathstartend_range");
- RNA_def_property_ui_text(prop, "Path Start", "Starting time of drawn path");
+ RNA_def_property_ui_text(prop, "Path Start", "Starting time of path");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "path_end", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "path_end");
RNA_def_property_float_funcs(prop, NULL, NULL, "rna_PartSetting_pathstartend_range");
- RNA_def_property_ui_text(prop, "Path End", "End time of drawn path");
+ RNA_def_property_ui_text(prop, "Path End", "End time of path");
RNA_def_property_update(prop, 0, "rna_Particle_redo");
prop = RNA_def_property(srna, "trail_count", PROP_INT, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_pose.c b/source/blender/makesrna/intern/rna_pose.c
index 90a860e87e2..ba65e42895c 100644
--- a/source/blender/makesrna/intern/rna_pose.c
+++ b/source/blender/makesrna/intern/rna_pose.c
@@ -425,22 +425,6 @@ static void rna_Itasc_update_rebuild(Main *bmain, Scene *scene, PointerRNA *ptr)
rna_Itasc_update(bmain, scene, ptr);
}
-static void rna_PoseChannel_bone_custom_set(PointerRNA *ptr,
- PointerRNA value,
- struct ReportList *UNUSED(reports))
-{
- bPoseChannel *pchan = (bPoseChannel *)ptr->data;
-
- if (pchan->custom) {
- id_us_min(&pchan->custom->id);
- pchan->custom = NULL;
- }
-
- pchan->custom = value.data;
-
- id_us_plus(&pchan->custom->id);
-}
-
static PointerRNA rna_PoseChannel_bone_group_get(PointerRNA *ptr)
{
Object *ob = (Object *)ptr->owner_id;
@@ -1368,11 +1352,10 @@ static void rna_def_pose_channel(BlenderRNA *brna)
prop = RNA_def_property(srna, "custom_shape", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "custom");
RNA_def_property_struct_type(prop, "Object");
- RNA_def_property_flag(prop, PROP_EDITABLE);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
- RNA_def_property_pointer_funcs(prop, NULL, "rna_PoseChannel_bone_custom_set", NULL, NULL);
RNA_def_property_ui_text(
- prop, "Custom Object", "Object that defines custom draw type for this bone");
+ prop, "Custom Object", "Object that defines custom display shape for this bone");
RNA_def_property_editable_func(prop, "rna_PoseChannel_proxy_editable");
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_dependency_update");
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 170bbc7e372..b9fc89db0d1 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -6445,7 +6445,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
RNA_def_property_enum_sdna(prop, NULL, "seq_prev_type");
RNA_def_property_enum_items(prop, rna_enum_shading_type_items);
RNA_def_property_ui_text(
- prop, "Sequencer Preview Shading", "Method to draw in the sequencer view");
+ prop, "Sequencer Preview Shading", "Display method used in the sequencer view");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SceneSequencer_update");
# if 0 /* UNUSED, see R_SEQ_GL_REND comment */
@@ -6454,7 +6454,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
RNA_def_property_enum_items(prop, rna_enum_shading_type_items);
/* XXX Label and tooltips are obviously wrong! */
RNA_def_property_ui_text(
- prop, "Sequencer Preview Shading", "Method to draw in the sequencer view");
+ prop, "Sequencer Preview Shading", "Display method used in the sequencer view");
# endif
prop = RNA_def_property(srna, "use_sequencer_override_scene_strip", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_sequencer_api.c b/source/blender/makesrna/intern/rna_sequencer_api.c
index 070fb08c3b4..00d8c43a111 100644
--- a/source/blender/makesrna/intern/rna_sequencer_api.c
+++ b/source/blender/makesrna/intern/rna_sequencer_api.c
@@ -53,6 +53,7 @@
# include "SEQ_add.h"
# include "SEQ_edit.h"
+# include "SEQ_effects.h"
# include "SEQ_relations.h"
# include "SEQ_render.h"
# include "SEQ_sequencer.h"
@@ -80,34 +81,6 @@ static void rna_Sequence_swap_internal(Sequence *seq_self,
}
}
-static Sequence *alloc_generic_sequence(
- ListBase *seqbase, const char *name, int frame_start, int channel, int type, const char *file)
-{
- Sequence *seq;
- StripElem *se;
-
- seq = SEQ_sequence_alloc(seqbase, frame_start, channel, type);
-
- BLI_strncpy(seq->name + 2, name, sizeof(seq->name) - 2);
- SEQ_sequence_base_unique_name_recursive(seqbase, seq);
-
- Strip *strip = seq->strip;
-
- /* Don't allocate StripElem for clip, mask and scene types. This struct is not handled in
- * seq_dupli() function. */
- if (file && !ELEM(type, SEQ_TYPE_MOVIECLIP, SEQ_TYPE_MASK, SEQ_TYPE_SCENE)) {
- strip->stripdata = se = MEM_callocN(sizeof(StripElem), "stripelem");
- BLI_split_dirfile(file, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name));
-
- SEQ_render_init_colorspace(seq);
- }
- else {
- strip->stripdata = NULL;
- }
-
- return seq;
-}
-
static Sequence *rna_Sequences_new_clip(ID *id,
ListBase *seqbase,
Main *bmain,
@@ -117,15 +90,10 @@ static Sequence *rna_Sequences_new_clip(ID *id,
int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
-
- seq = alloc_generic_sequence(
- seqbase, name, frame_start, channel, SEQ_TYPE_MOVIECLIP, clip->filepath);
- seq->clip = clip;
- seq->len = BKE_movieclip_get_duration(clip);
- id_us_plus((ID *)clip);
-
- SEQ_time_update_sequence_bounds(scene, seq);
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, NULL, frame_start, channel);
+ load_data.clip = clip;
+ Sequence *seq = SEQ_add_movieclip_strip(scene, seqbase, &load_data);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -165,15 +133,10 @@ static Sequence *rna_Sequences_new_mask(ID *id,
int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
-
- seq = alloc_generic_sequence(seqbase, name, frame_start, channel, SEQ_TYPE_MASK, mask->id.name);
- seq->mask = mask;
- seq->len = BKE_mask_get_duration(mask);
- id_us_plus((ID *)mask);
-
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, NULL, frame_start, channel);
+ load_data.mask = mask;
+ Sequence *seq = SEQ_add_mask_strip(scene, seqbase, &load_data);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -202,15 +165,10 @@ static Sequence *rna_Sequences_new_scene(ID *id,
int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
-
- seq = alloc_generic_sequence(seqbase, name, frame_start, channel, SEQ_TYPE_SCENE, NULL);
- seq->scene = sce_seq;
- seq->len = sce_seq->r.efra - sce_seq->r.sfra + 1;
- id_us_plus((ID *)sce_seq);
-
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, NULL, frame_start, channel);
+ load_data.scene = sce_seq;
+ Sequence *seq = SEQ_add_scene_strip(scene, seqbase, &load_data);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -244,27 +202,24 @@ static Sequence *rna_Sequences_meta_new_scene(ID *id,
static Sequence *rna_Sequences_new_image(ID *id,
ListBase *seqbase,
Main *bmain,
- ReportList *reports,
+ ReportList *UNUSED(reports),
const char *name,
const char *file,
int channel,
int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
- seq = alloc_generic_sequence(seqbase, name, frame_start, channel, SEQ_TYPE_IMAGE, file);
- seq->len = 1;
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, file, frame_start, channel);
+ load_data.image.len = 1;
+ Sequence *seq = SEQ_add_image_strip(bmain, scene, seqbase, &load_data);
- if (seq->strip->stripdata->name[0] == '\0') {
- BKE_report(reports, RPT_ERROR, "Sequences.new_image: unable to open image file");
- BLI_remlink(seqbase, seq);
- SEQ_sequence_free(scene, seq, true);
- return NULL;
- }
-
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ char dir[FILE_MAX], filename[FILE_MAX];
+ BLI_split_dirfile(file, dir, filename, sizeof(dir), sizeof(filename));
+ SEQ_add_image_set_directory(seq, dir);
+ SEQ_add_image_load_file(seq, 0, filename);
+ SEQ_add_image_init_alpha_mode(seq);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -299,48 +254,47 @@ static Sequence *rna_Sequences_meta_new_image(ID *id,
id, &seq->seqbase, bmain, reports, name, file, channel, frame_start);
}
-static Sequence *rna_Sequences_new_movie(
- ID *id, ListBase *seqbase, const char *name, const char *file, int channel, int frame_start)
+static Sequence *rna_Sequences_new_movie(ID *id,
+ ListBase *seqbase,
+ Main *bmain,
+ const char *name,
+ const char *file,
+ int channel,
+ int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
- StripAnim *sanim;
-
- seq = alloc_generic_sequence(seqbase, name, frame_start, channel, SEQ_TYPE_MOVIE, file);
-
- struct anim *an = openanim(file, IB_rect, 0, NULL);
- if (an == NULL) {
- /* Without anim, the strip gets duration 0, which makes it impossible to select in the UI. */
- seq->len = 1;
- }
- else {
- sanim = MEM_mallocN(sizeof(StripAnim), "Strip Anim");
- BLI_addtail(&seq->anims, sanim);
- sanim->anim = an;
-
- seq->anim_preseek = IMB_anim_get_preseek(an);
- seq->len = IMB_anim_get_duration(an, IMB_TC_RECORD_RUN);
- }
-
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, file, frame_start, channel);
+ load_data.allow_invalid_file = true;
+ Sequence *seq = SEQ_add_movie_strip(bmain, scene, seqbase, &load_data);
+ DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, scene);
return seq;
}
-static Sequence *rna_Sequences_editing_new_movie(
- ID *id, Editing *ed, const char *name, const char *file, int channel, int frame_start)
+static Sequence *rna_Sequences_editing_new_movie(ID *id,
+ Editing *ed,
+ Main *bmain,
+ const char *name,
+ const char *file,
+ int channel,
+ int frame_start)
{
- return rna_Sequences_new_movie(id, &ed->seqbase, name, file, channel, frame_start);
+ return rna_Sequences_new_movie(id, &ed->seqbase, bmain, name, file, channel, frame_start);
}
-static Sequence *rna_Sequences_meta_new_movie(
- ID *id, Sequence *seq, const char *name, const char *file, int channel, int frame_start)
+static Sequence *rna_Sequences_meta_new_movie(ID *id,
+ Sequence *seq,
+ Main *bmain,
+ const char *name,
+ const char *file,
+ int channel,
+ int frame_start)
{
- return rna_Sequences_new_movie(id, &seq->seqbase, name, file, channel, frame_start);
+ return rna_Sequences_new_movie(id, &seq->seqbase, bmain, name, file, channel, frame_start);
}
# ifdef WITH_AUDASPACE
@@ -354,22 +308,15 @@ static Sequence *rna_Sequences_new_sound(ID *id,
int frame_start)
{
Scene *scene = (Scene *)id;
- Sequence *seq;
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, file, frame_start, channel);
+ load_data.allow_invalid_file = true;
+ Sequence *seq = SEQ_add_sound_strip(bmain, scene, seqbase, &load_data);
- bSound *sound = BKE_sound_new_file(bmain, file);
-
- SoundInfo info;
- if (!BKE_sound_info_get(bmain, sound, &info)) {
- BKE_id_free(bmain, sound);
+ if (seq == NULL) {
BKE_report(reports, RPT_ERROR, "Sequences.new_sound: unable to open sound file");
return NULL;
}
- seq = alloc_generic_sequence(
- seqbase, name, frame_start, channel, SEQ_TYPE_SOUND_RAM, sound->filepath);
- seq->sound = sound;
- seq->len = ceil((double)info.length * FPS);
-
- SEQ_time_update_sequence_bounds(scene, seq);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -432,8 +379,7 @@ static Sequence *rna_Sequences_new_effect(ID *id,
{
Scene *scene = (Scene *)id;
Sequence *seq;
- struct SeqEffectHandle sh;
- int num_inputs = SEQ_effect_get_num_inputs(type);
+ const int num_inputs = SEQ_effect_get_num_inputs(type);
switch (num_inputs) {
case 0:
@@ -469,26 +415,14 @@ static Sequence *rna_Sequences_new_effect(ID *id,
return NULL;
}
- seq = alloc_generic_sequence(seqbase, name, frame_start, channel, type, NULL);
-
- sh = SEQ_effect_handle_get(seq);
-
- seq->seq1 = seq1;
- seq->seq2 = seq2;
- seq->seq3 = seq3;
-
- sh.init(seq);
-
- if (!seq1) { /* effect has no deps */
- seq->len = 1;
- SEQ_transform_set_right_handle_frame(seq, frame_end);
- }
-
- seq->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
-
- SEQ_time_update_sequence(scene, seq);
- SEQ_time_update_sequence_bounds(scene, seq);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ SeqLoadData load_data;
+ SEQ_add_load_data_init(&load_data, name, NULL, frame_start, channel);
+ load_data.effect.end_frame = frame_end;
+ load_data.effect.type = type;
+ load_data.effect.seq1 = seq1;
+ load_data.effect.seq2 = seq2;
+ load_data.effect.seq3 = seq3;
+ seq = SEQ_add_effect_strip(scene, seqbase, &load_data);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, scene);
@@ -865,7 +799,7 @@ void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, const bool metastri
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "new_movie", new_movie_func_name);
- RNA_def_function_flag(func, FUNC_USE_SELF_ID);
+ RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
RNA_def_function_ui_description(func, "Add a new movie sequence");
parm = RNA_def_string(func, "name", "Name", 0, "", "Name for the new sequence");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 4e793d0e133..727145f86a0 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -3193,7 +3193,7 @@ static void rna_def_space_image_uv(BlenderRNA *brna)
prop = RNA_def_property(srna, "display_stretch_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "dt_uvstretch");
RNA_def_property_enum_items(prop, dt_uvstretch_items);
- RNA_def_property_ui_text(prop, "Display Stretch Type", "Type of stretch to draw");
+ RNA_def_property_ui_text(prop, "Display Stretch Type", "Type of stretch to display");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL);
prop = RNA_def_property(srna, "show_modified_edges", PROP_BOOLEAN, PROP_NONE);
@@ -3577,7 +3577,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
prop = RNA_def_property(srna, "cavity_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, cavity_type_items);
- RNA_def_property_ui_text(prop, "Cavity Type", "Way to draw the cavity shading");
+ RNA_def_property_ui_text(prop, "Cavity Type", "Way to display the cavity shading");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
prop = RNA_def_property(srna, "curvature_ridge_factor", PROP_FLOAT, PROP_FACTOR);
@@ -3686,7 +3686,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
prop = RNA_def_property(srna, "background_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, background_type_items);
- RNA_def_property_ui_text(prop, "Background", "Way to draw the background");
+ RNA_def_property_ui_text(prop, "Background", "Way to display the background");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
prop = RNA_def_property(srna, "background_color", PROP_FLOAT, PROP_COLOR);
@@ -4043,56 +4043,56 @@ static void rna_def_space_view3d_overlay(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_edges", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_EDGES);
- RNA_def_property_ui_text(prop, "Draw Edges", "Highlight selected edges");
+ RNA_def_property_ui_text(prop, "Display Edges", "Highlight selected edges");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_faces", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_FACES);
- RNA_def_property_ui_text(prop, "Draw Faces", "Highlight selected faces");
+ RNA_def_property_ui_text(prop, "Display Faces", "Highlight selected faces");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_face_center", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_FACE_DOT);
RNA_def_property_ui_text(
prop,
- "Draw Face Center",
+ "Display Face Center",
"Display face center when face selection is enabled in solid shading modes");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_edge_crease", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_CREASES);
RNA_def_property_ui_text(
- prop, "Draw Creases", "Display creases created for Subdivision Surface modifier");
+ prop, "Display Creases", "Display creases created for Subdivision Surface modifier");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_edge_bevel_weight", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_BWEIGHTS);
RNA_def_property_ui_text(
- prop, "Draw Bevel Weights", "Display weights created for the Bevel modifier");
+ prop, "Display Bevel Weights", "Display weights created for the Bevel modifier");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_edge_seams", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_SEAMS);
- RNA_def_property_ui_text(prop, "Draw Seams", "Display UV unwrapping seams");
+ RNA_def_property_ui_text(prop, "Display Seams", "Display UV unwrapping seams");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_edge_sharp", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_SHARP);
RNA_def_property_ui_text(
- prop, "Draw Sharp", "Display sharp edges, used with the Edge Split modifier");
+ prop, "Display Sharp", "Display sharp edges, used with the Edge Split modifier");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_freestyle_edge_marks", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_FREESTYLE_EDGE);
RNA_def_property_ui_text(prop,
- "Draw Freestyle Edge Marks",
+ "Display Freestyle Edge Marks",
"Display Freestyle edge marks, used with the Freestyle renderer");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
prop = RNA_def_property(srna, "show_freestyle_face_marks", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.edit_flag", V3D_OVERLAY_EDIT_FREESTYLE_FACE);
RNA_def_property_ui_text(prop,
- "Draw Freestyle Face Marks",
+ "Display Freestyle Face Marks",
"Display Freestyle face marks, used with the Freestyle renderer");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
@@ -4578,7 +4578,7 @@ static void rna_def_space_view3d(BlenderRNA *brna)
RNA_def_property_enum_sdna(prop, NULL, "multiview_eye");
RNA_def_property_enum_items(prop, stereo3d_eye_items);
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_SpaceView3D_stereo3d_camera_itemf");
- RNA_def_property_ui_text(prop, "Stereo Eye", "Current stereo eye being drawn");
+ RNA_def_property_ui_text(prop, "Stereo Eye", "Current stereo eye being displayed");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "stereo_3d_camera", PROP_ENUM, PROP_NONE);
@@ -4992,7 +4992,7 @@ static void rna_def_space_image(BlenderRNA *brna)
"rna_SpaceImageEditor_display_channels_get",
NULL,
"rna_SpaceImageEditor_display_channels_itemf");
- RNA_def_property_ui_text(prop, "Display Channels", "Channels of the image to draw");
+ RNA_def_property_ui_text(prop, "Display Channels", "Channels of the image to display");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL);
prop = RNA_def_property(srna, "show_stereo_3d", PROP_BOOLEAN, PROP_NONE);
@@ -5140,17 +5140,17 @@ static void rna_def_space_sequencer(BlenderRNA *brna)
"NO_WAVEFORMS",
0,
"Waveforms Off",
- "No waveforms drawn for any sound strips"},
+ "Don't display waveforms for any sound strips"},
{SEQ_ALL_WAVEFORMS,
"ALL_WAVEFORMS",
0,
"Waveforms On",
- "Waveforms drawn for all sound strips"},
+ "Display waveforms for all sound strips"},
{0,
"DEFAULT_WAVEFORMS",
0,
"Use Strip Option",
- "Waveforms drawn according to strip setting"},
+ "Display waveforms depending on strip setting"},
{0, NULL, 0, NULL, NULL},
};
@@ -5241,13 +5241,13 @@ static void rna_def_space_sequencer(BlenderRNA *brna)
prop = RNA_def_property(srna, "preview_channels", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, preview_channels_items);
- RNA_def_property_ui_text(prop, "Display Channels", "Channels of the preview to draw");
+ RNA_def_property_ui_text(prop, "Display Channels", "Channels of the preview to display");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, "rna_SequenceEditor_update_cache");
prop = RNA_def_property(srna, "waveform_display_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, waveform_type_display_items);
- RNA_def_property_ui_text(prop, "Waveform Display", "How Waveforms are drawn");
+ RNA_def_property_ui_text(prop, "Waveform Display", "How Waveforms are displayed");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
prop = RNA_def_property(srna, "use_zoom_to_fit", PROP_BOOLEAN, PROP_NONE);
@@ -5283,7 +5283,7 @@ static void rna_def_space_sequencer(BlenderRNA *brna)
prop = RNA_def_property(srna, "overlay_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "overlay_type");
RNA_def_property_enum_items(prop, overlay_type_items);
- RNA_def_property_ui_text(prop, "Overlay Type", "Overlay draw type");
+ RNA_def_property_ui_text(prop, "Overlay Type", "Overlay display method");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
prop = RNA_def_property(srna, "show_backdrop", PROP_BOOLEAN, PROP_NONE);
@@ -6169,7 +6169,7 @@ static void rna_def_fileselect_params(BlenderRNA *brna)
prop = RNA_def_property(srna, "show_details_size", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "details_flags", FILE_DETAILS_SIZE);
- RNA_def_property_ui_text(prop, "File Size", "Draw a column listing the size of each file");
+ RNA_def_property_ui_text(prop, "File Size", "Show a column listing the size of each file");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
prop = RNA_def_property(srna, "show_details_datetime", PROP_BOOLEAN, PROP_NONE);
@@ -6177,7 +6177,7 @@ static void rna_def_fileselect_params(BlenderRNA *brna)
RNA_def_property_ui_text(
prop,
"File Modification Date",
- "Draw a column listing the date and time of modification for each file");
+ "Show a column listing the date and time of modification for each file");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
prop = RNA_def_property(srna, "use_filter", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_ui.c b/source/blender/makesrna/intern/rna_ui.c
index 5e5b3549986..aefb77c4077 100644
--- a/source/blender/makesrna/intern/rna_ui.c
+++ b/source/blender/makesrna/intern/rna_ui.c
@@ -1357,7 +1357,7 @@ static void rna_def_panel(BlenderRNA *brna)
0,
"Expand Header Layout",
"Allow buttons in the header to stretch and shrink to fill the entire layout width"},
- {PANEL_TYPE_DRAW_BOX, "DRAW_BOX", 0, "Box Style", "Draw panel with the box widget theme"},
+ {PANEL_TYPE_DRAW_BOX, "DRAW_BOX", 0, "Box Style", "Display panel with the box widget theme"},
{0, NULL, 0, NULL, NULL},
};
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index f3edbc61228..796b8164cd9 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -3900,8 +3900,7 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
srna = RNA_def_struct(brna, "Theme", NULL);
RNA_def_struct_sdna(srna, "bTheme");
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
- RNA_def_struct_ui_text(
- srna, "Theme", "Theme settings defining draw style and colors in the user interface");
+ RNA_def_struct_ui_text(srna, "Theme", "User interface styling and color settings");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Name", "Name of the theme");
@@ -4298,12 +4297,13 @@ static void rna_def_userdef_solidlight(BlenderRNA *brna)
srna = RNA_def_struct(brna, "UserSolidLight", NULL);
RNA_def_struct_sdna(srna, "SolidLight");
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
- RNA_def_struct_ui_text(srna, "Solid Light", "Light used for Studio lighting in solid draw mode");
+ RNA_def_struct_ui_text(
+ srna, "Solid Light", "Light used for Studio lighting in solid shading mode");
prop = RNA_def_property(srna, "use", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", 1);
RNA_def_property_boolean_default(prop, true);
- RNA_def_property_ui_text(prop, "Enabled", "Enable this light in solid draw mode");
+ RNA_def_property_ui_text(prop, "Enabled", "Enable this light in solid shading mode");
RNA_def_property_update(prop, 0, "rna_UserDef_viewport_lights_update");
prop = RNA_def_property(srna, "smooth", PROP_FLOAT, PROP_FACTOR);
@@ -4843,7 +4843,8 @@ static void rna_def_userdef_view(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_text_antialiasing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "text_render", USER_TEXT_DISABLE_AA);
- RNA_def_property_ui_text(prop, "Text Anti-Aliasing", "Draw user interface text anti-aliased");
+ RNA_def_property_ui_text(
+ prop, "Text Anti-Aliasing", "Smooth jagged edges of user interface text");
RNA_def_property_update(prop, 0, "rna_userdef_text_update");
prop = RNA_def_property(srna, "text_hinting", PROP_ENUM, PROP_NONE);
@@ -5349,12 +5350,12 @@ static void rna_def_userdef_system(BlenderRNA *brna)
"2DTEXTURE",
0,
"2D Texture",
- "Use CPU for display transform and draw image with 2D texture"},
+ "Use CPU for display transform and display image with 2D texture"},
{IMAGE_DRAW_METHOD_GLSL,
"GLSL",
0,
"GLSL",
- "Use GLSL shaders for display transform and draw image with 2D texture"},
+ "Use GLSL shaders for display transform and display image with 2D texture"},
{0, NULL, 0, NULL, NULL},
};
@@ -5391,7 +5392,7 @@ static void rna_def_userdef_system(BlenderRNA *brna)
RNA_def_property_ui_text(
prop,
"UI Scale",
- "Size multiplier to use when drawing custom user interface elements, so that "
+ "Size multiplier to use when displaying custom user interface elements, so that "
"they are scaled correctly on screens with different DPI. This value is based "
"on operating system DPI settings and Blender display scale");
@@ -5401,7 +5402,7 @@ static void rna_def_userdef_system(BlenderRNA *brna)
RNA_def_property_ui_text(
prop,
"UI Line Width",
- "Suggested line thickness and point size in pixels, for add-ons drawing custom "
+ "Suggested line thickness and point size in pixels, for add-ons displaying custom "
"user interface elements, based on operating system settings and Blender UI scale");
prop = RNA_def_property(srna, "dpi", PROP_INT, PROP_NONE);
@@ -5470,7 +5471,7 @@ static void rna_def_userdef_system(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_region_overlap", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "uiflag2", USER_REGION_OVERLAP);
RNA_def_property_ui_text(
- prop, "Region Overlap", "Draw tool/property regions over the main region");
+ prop, "Region Overlap", "Display tool/property regions over the main region");
RNA_def_property_update(prop, 0, "rna_userdef_dpi_update");
prop = RNA_def_property(srna, "viewport_aa", PROP_ENUM, PROP_NONE);
@@ -5484,7 +5485,7 @@ static void rna_def_userdef_system(BlenderRNA *brna)
RNA_def_property_collection_sdna(prop, NULL, "light_param", "");
RNA_def_property_struct_type(prop, "UserSolidLight");
RNA_def_property_ui_text(
- prop, "Solid Lights", "Lights user to display objects in solid draw mode");
+ prop, "Solid Lights", "Lights user to display objects in solid shading mode");
prop = RNA_def_property(srna, "light_ambient", PROP_FLOAT, PROP_COLOR);
RNA_def_property_float_sdna(prop, NULL, "light_ambient");
diff --git a/source/blender/makesrna/intern/rna_volume.c b/source/blender/makesrna/intern/rna_volume.c
index d71dcc0700b..1bddd9152db 100644
--- a/source/blender/makesrna/intern/rna_volume.c
+++ b/source/blender/makesrna/intern/rna_volume.c
@@ -366,7 +366,7 @@ static void rna_def_volume_display(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_range(prop, 0.00001, FLT_MAX);
RNA_def_property_ui_range(prop, 0.1, 100.0, 1, 3);
- RNA_def_property_ui_text(prop, "Density", "Thickness of volume drawing in the viewport");
+ RNA_def_property_ui_text(prop, "Density", "Thickness of volume display in the viewport");
RNA_def_property_update(prop, 0, "rna_Volume_update_display");
static const EnumPropertyItem wireframe_type_items[] = {
diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.c b/source/blender/makesrna/intern/rna_wm_gizmo.c
index e61482c91e2..f7d139dd706 100644
--- a/source/blender/makesrna/intern/rna_wm_gizmo.c
+++ b/source/blender/makesrna/intern/rna_wm_gizmo.c
@@ -1238,20 +1238,20 @@ static void rna_def_gizmo(BlenderRNA *brna, PropertyRNA *cprop)
prop = RNA_def_property(srna, "use_draw_hover", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(
prop, "rna_Gizmo_flag_use_draw_hover_get", "rna_Gizmo_flag_use_draw_hover_set");
- RNA_def_property_ui_text(prop, "Draw Hover", "");
+ RNA_def_property_ui_text(prop, "Show Hover", "");
RNA_def_property_update(prop, 0, "rna_Gizmo_update_redraw");
/* WM_GIZMO_DRAW_MODAL */
prop = RNA_def_property(srna, "use_draw_modal", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(
prop, "rna_Gizmo_flag_use_draw_modal_get", "rna_Gizmo_flag_use_draw_modal_set");
- RNA_def_property_ui_text(prop, "Draw Active", "Draw while dragging");
+ RNA_def_property_ui_text(prop, "Show Active", "Show while dragging");
RNA_def_property_update(prop, 0, "rna_Gizmo_update_redraw");
/* WM_GIZMO_DRAW_VALUE */
prop = RNA_def_property(srna, "use_draw_value", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(
prop, "rna_Gizmo_flag_use_draw_value_get", "rna_Gizmo_flag_use_draw_value_set");
RNA_def_property_ui_text(
- prop, "Draw Value", "Show an indicator for the current value while dragging");
+ prop, "Show Value", "Show an indicator for the current value while dragging");
RNA_def_property_update(prop, 0, "rna_Gizmo_update_redraw");
/* WM_GIZMO_DRAW_OFFSET_SCALE */
prop = RNA_def_property(srna, "use_draw_offset_scale", PROP_BOOLEAN, PROP_NONE);
@@ -1391,7 +1391,7 @@ static void rna_def_gizmogroup(BlenderRNA *brna)
"SCALE",
0,
"Scale",
- "Scale to respect zoom (otherwise zoom independent draw size)"},
+ "Scale to respect zoom (otherwise zoom independent display size)"},
{WM_GIZMOGROUPTYPE_DEPTH_3D,
"DEPTH_3D",
0,
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index c7731815a2a..b47f5806c9c 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -233,6 +233,11 @@ static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *u
&settings);
}
+static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void *userData)
+{
+ walk(userData, ob, md, "texture");
+}
+
static bool isDisabled(const struct Scene *UNUSED(scene),
ModifierData *md,
bool UNUSED(useRenderParams))
@@ -1348,7 +1353,7 @@ ModifierTypeInfo modifierType_Nodes = {
/* dependsOnTime */ nullptr,
/* dependsOnNormals */ nullptr,
/* foreachIDLink */ foreachIDLink,
- /* foreachTexLink */ nullptr,
+ /* foreachTexLink */ foreachTexLink,
/* freeRuntimeData */ nullptr,
/* panelRegister */ panelRegister,
/* blendWrite */ blendWrite,
diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh
index 7fcc117ba93..c1ff7bfe5e0 100644
--- a/source/blender/nodes/NOD_node_tree_ref.hh
+++ b/source/blender/nodes/NOD_node_tree_ref.hh
@@ -25,6 +25,7 @@
* The following queries are supported efficiently:
* - socket -> index of socket
* - socket -> directly linked sockets
+ * - socket -> directly linked links
* - socket -> linked sockets when skipping reroutes
* - socket -> node
* - socket/node -> rna pointer
@@ -65,6 +66,7 @@ class InputSocketRef;
class OutputSocketRef;
class NodeRef;
class NodeTreeRef;
+class LinkRef;
class SocketRef : NonCopyable, NonMovable {
protected:
@@ -76,12 +78,14 @@ class SocketRef : NonCopyable, NonMovable {
PointerRNA rna_;
Vector<SocketRef *> linked_sockets_;
Vector<SocketRef *> directly_linked_sockets_;
+ Vector<LinkRef *> directly_linked_links_;
friend NodeTreeRef;
public:
Span<const SocketRef *> linked_sockets() const;
Span<const SocketRef *> directly_linked_sockets() const;
+ Span<const LinkRef *> directly_linked_links() const;
bool is_linked() const;
const NodeRef &node() const;
@@ -156,6 +160,21 @@ class NodeRef : NonCopyable, NonMovable {
bool is_muted() const;
};
+class LinkRef : NonCopyable, NonMovable {
+ private:
+ OutputSocketRef *from_;
+ InputSocketRef *to_;
+ bNodeLink *blink_;
+
+ friend NodeTreeRef;
+
+ public:
+ const OutputSocketRef &from() const;
+ const InputSocketRef &to() const;
+
+ bNodeLink *blink() const;
+};
+
class NodeTreeRef : NonCopyable, NonMovable {
private:
LinearAllocator<> allocator_;
@@ -164,6 +183,7 @@ class NodeTreeRef : NonCopyable, NonMovable {
Vector<SocketRef *> sockets_by_id_;
Vector<InputSocketRef *> input_sockets_;
Vector<OutputSocketRef *> output_sockets_;
+ Vector<LinkRef *> links_;
MultiValueMap<const bNodeType *, NodeRef *> nodes_by_type_;
public:
@@ -178,6 +198,8 @@ class NodeTreeRef : NonCopyable, NonMovable {
Span<const InputSocketRef *> input_sockets() const;
Span<const OutputSocketRef *> output_sockets() const;
+ Span<const LinkRef *> links() const;
+
bool has_link_cycles() const;
bNodeTree *btree() const;
@@ -209,6 +231,11 @@ inline Span<const SocketRef *> SocketRef::directly_linked_sockets() const
return directly_linked_sockets_;
}
+inline Span<const LinkRef *> SocketRef::directly_linked_links() const
+{
+ return directly_linked_links_;
+}
+
inline bool SocketRef::is_linked() const
{
return linked_sockets_.size() > 0;
@@ -409,7 +436,26 @@ inline bool NodeRef::is_muted() const
}
/* --------------------------------------------------------------------
- * NodeRef inline methods.
+ * LinkRef inline methods.
+ */
+
+inline const OutputSocketRef &LinkRef::from() const
+{
+ return *from_;
+}
+
+inline const InputSocketRef &LinkRef::to() const
+{
+ return *to_;
+}
+
+inline bNodeLink *LinkRef::blink() const
+{
+ return blink_;
+}
+
+/* --------------------------------------------------------------------
+ * NodeTreeRef inline methods.
*/
inline Span<const NodeRef *> NodeTreeRef::nodes() const
@@ -443,6 +489,11 @@ inline Span<const OutputSocketRef *> NodeTreeRef::output_sockets() const
return output_sockets_;
}
+inline Span<const LinkRef *> NodeTreeRef::links() const
+{
+ return links_;
+}
+
inline bNodeTree *NodeTreeRef::btree() const
{
return btree_;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
index e28013a8bfc..9df103ff057 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
@@ -47,6 +47,9 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co
static const float3 scale_default = float3(1.0f);
OutputAttributePtr scale_attribute = component.attribute_try_get_for_output(
"scale", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, &scale_default);
+ if (!scale_attribute) {
+ return;
+ }
ReadAttributePtr attribute = params.get_input_attribute(
"Factor", component, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, nullptr);
if (!attribute) {
diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc
index 9dcd90f9f50..e174f096ff7 100644
--- a/source/blender/nodes/intern/node_tree_ref.cc
+++ b/source/blender/nodes/intern/node_tree_ref.cc
@@ -64,8 +64,16 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
InputSocketRef &to_socket = this->find_input_socket(
node_mapping, blink->tonode, blink->tosock);
+ LinkRef &link = *allocator_.construct<LinkRef>();
+ link.from_ = &from_socket;
+ link.to_ = &to_socket;
+ link.blink_ = blink;
+
+ links_.append(&link);
from_socket.directly_linked_sockets_.append(&to_socket);
to_socket.directly_linked_sockets_.append(&from_socket);
+ from_socket.directly_linked_links_.append(&link);
+ to_socket.directly_linked_links_.append(&link);
}
for (OutputSocketRef *socket : output_sockets_) {
@@ -85,6 +93,8 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
NodeTreeRef::~NodeTreeRef()
{
+ /* The destructor has to be called manually, because these types are allocated in a linear
+ * allocator. */
for (NodeRef *node : nodes_by_id_) {
node->~NodeRef();
}
@@ -94,6 +104,9 @@ NodeTreeRef::~NodeTreeRef()
for (OutputSocketRef *socket : output_sockets_) {
socket->~OutputSocketRef();
}
+ for (LinkRef *link : links_) {
+ link->~LinkRef();
+ }
}
InputSocketRef &NodeTreeRef::find_input_socket(Map<bNode *, NodeRef *> &node_mapping,
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index 189d8308c14..555dd41b228 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -2022,45 +2022,54 @@ static int pyrna_py_to_prop(
}
}
- if (!BPy_StructRNA_Check(value) && value != Py_None) {
- PyErr_Format(PyExc_TypeError,
- "%.200s %.200s.%.200s expected a %.200s type, not %.200s",
- error_prefix,
- RNA_struct_identifier(ptr->type),
- RNA_property_identifier(prop),
- RNA_struct_identifier(ptr_type),
- Py_TYPE(value)->tp_name);
- Py_XDECREF(value_new);
- return -1;
- }
- if ((flag & PROP_NEVER_NULL) && value == Py_None) {
- PyErr_Format(PyExc_TypeError,
- "%.200s %.200s.%.200s does not support a 'None' assignment %.200s type",
- error_prefix,
- RNA_struct_identifier(ptr->type),
- RNA_property_identifier(prop),
- RNA_struct_identifier(ptr_type));
- Py_XDECREF(value_new);
- return -1;
+ BPy_StructRNA *param;
+ if (value == Py_None) {
+ if (flag & PROP_NEVER_NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s %.200s.%.200s does not support a 'None' assignment %.200s type",
+ error_prefix,
+ RNA_struct_identifier(ptr->type),
+ RNA_property_identifier(prop),
+ RNA_struct_identifier(ptr_type));
+ Py_XDECREF(value_new);
+ return -1;
+ }
+ param = NULL;
}
- if ((value != Py_None) && ((flag & PROP_ID_SELF_CHECK) &&
- ptr->owner_id == ((BPy_StructRNA *)value)->ptr.owner_id)) {
- PyErr_Format(PyExc_TypeError,
- "%.200s %.200s.%.200s ID type does not support assignment to itself",
- error_prefix,
- RNA_struct_identifier(ptr->type),
- RNA_property_identifier(prop));
- Py_XDECREF(value_new);
- return -1;
+ else {
+ if (!BPy_StructRNA_Check(value)) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s %.200s.%.200s expected a %.200s type, not %.200s",
+ error_prefix,
+ RNA_struct_identifier(ptr->type),
+ RNA_property_identifier(prop),
+ RNA_struct_identifier(ptr_type),
+ Py_TYPE(value)->tp_name);
+ Py_XDECREF(value_new);
+ return -1;
+ }
+ param = (BPy_StructRNA *)value;
+
+ const ID *value_owner_id = ((BPy_StructRNA *)value)->ptr.owner_id;
+ if (value_owner_id != NULL) {
+ if ((flag & PROP_ID_SELF_CHECK) && (ptr->owner_id == value_owner_id)) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s %.200s.%.200s ID type does not support assignment to itself",
+ error_prefix,
+ RNA_struct_identifier(ptr->type),
+ RNA_property_identifier(prop));
+ Py_XDECREF(value_new);
+ return -1;
+ }
+ }
}
- BPy_StructRNA *param = (BPy_StructRNA *)value;
bool raise_error = false;
if (data) {
if (flag_parameter & PARM_RNAPTR) {
if (flag & PROP_THICK_WRAP) {
- if (value == Py_None) {
+ if (param == NULL) {
memset(data, 0, sizeof(PointerRNA));
}
else if (RNA_struct_is_a(param->ptr.type, ptr_type)) {
@@ -2075,7 +2084,7 @@ static int pyrna_py_to_prop(
* but watch out that it remains valid!
* We could possibly support this later if needed. */
BLI_assert(value_new == NULL);
- if (value == Py_None) {
+ if (param == NULL) {
*((void **)data) = NULL;
}
else if (RNA_struct_is_a(param->ptr.type, ptr_type)) {
@@ -2086,7 +2095,7 @@ static int pyrna_py_to_prop(
}
}
}
- else if (value == Py_None) {
+ else if (param == NULL) {
*((void **)data) = NULL;
}
else if (RNA_struct_is_a(param->ptr.type, ptr_type)) {
@@ -2098,11 +2107,11 @@ static int pyrna_py_to_prop(
}
else {
/* Data == NULL, assign to RNA. */
- if (value == Py_None || RNA_struct_is_a(param->ptr.type, ptr_type)) {
+ if ((param == NULL) || RNA_struct_is_a(param->ptr.type, ptr_type)) {
ReportList reports;
BKE_reports_init(&reports, RPT_STORE);
RNA_property_pointer_set(
- ptr, prop, value == Py_None ? PointerRNA_NULL : param->ptr, &reports);
+ ptr, prop, (param == NULL) ? PointerRNA_NULL : param->ptr, &reports);
const int err = (BPy_reports_to_error(&reports, PyExc_RuntimeError, true));
if (err == -1) {
Py_XDECREF(value_new);
diff --git a/source/blender/sequencer/SEQ_add.h b/source/blender/sequencer/SEQ_add.h
index b136cb35e09..e262d7ed1ef 100644
--- a/source/blender/sequencer/SEQ_add.h
+++ b/source/blender/sequencer/SEQ_add.h
@@ -32,48 +32,73 @@ struct Scene;
struct Sequence;
struct bContext;
-/* api for adding new sequence strips */
-typedef struct SeqLoadInfo {
+/* SeqLoadData.flags */
+typedef enum eSeqLoadFlags {
+ SEQ_LOAD_SOUND_CACHE = (1 << 1),
+ SEQ_LOAD_SOUND_MONO = (1 << 2),
+ SEQ_LOAD_MOVIE_SYNC_FPS = (1 << 3),
+} eSeqLoadFlags;
+
+/* Api for adding new sequence strips. */
+typedef struct SeqLoadData {
int start_frame;
- int end_frame;
int channel;
- int flag; /* use sound, replace sel */
- int type;
- int len; /* only for image strips */
+ char name[64]; /* Strip name. */
char path[1024]; /* 1024 = FILE_MAX */
+ struct {
+ int len;
+ int end_frame;
+ } image; /* Only for image strips. */
+ struct Scene *scene; /* Only for scene strips. */
+ struct MovieClip *clip; /* Only for clip strips. */
+ struct Mask *mask; /* Only for mask strips. */
+ struct {
+ int type;
+ int end_frame;
+ struct Sequence *seq1;
+ struct Sequence *seq2;
+ struct Sequence *seq3;
+ } effect; /* Only for effect strips. */
+ eSeqLoadFlags flags;
eSeqImageFitMethod fit_method;
-
- /* multiview */
+ bool use_multiview;
char views_format;
struct Stereo3dFormat *stereo3d_format;
+ bool allow_invalid_file; /* Used by RNA API to create placeholder strips. */
+} SeqLoadData;
- /* return values */
- char name[64];
- struct Sequence *seq_sound; /* for movie's */
- int tot_success;
- int tot_error;
-} SeqLoadInfo;
-
-/* SeqLoadInfo.flag */
-#define SEQ_LOAD_REPLACE_SEL (1 << 0)
-#define SEQ_LOAD_FRAME_ADVANCE (1 << 1)
-#define SEQ_LOAD_MOVIE_SOUND (1 << 2)
-#define SEQ_LOAD_SOUND_CACHE (1 << 3)
-#define SEQ_LOAD_SYNC_FPS (1 << 4)
-#define SEQ_LOAD_SOUND_MONO (1 << 5)
-
-/* use as an api function */
-typedef struct Sequence *(*SeqLoadFn)(struct bContext *, ListBase *, struct SeqLoadInfo *);
-
-struct Sequence *SEQ_add_image_strip(struct bContext *C,
- ListBase *seqbasep,
- struct SeqLoadInfo *seq_load);
-struct Sequence *SEQ_add_sound_strip(struct bContext *C,
- ListBase *seqbasep,
- struct SeqLoadInfo *seq_load);
-struct Sequence *SEQ_add_movie_strip(struct bContext *C,
- ListBase *seqbasep,
- struct SeqLoadInfo *seq_load);
+void SEQ_add_load_data_init(struct SeqLoadData *load_data,
+ const char *name,
+ const char *path,
+ const int start_frame,
+ const int channel);
+struct Sequence *SEQ_add_image_strip(struct Main *bmain,
+ struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_sound_strip(struct Main *bmain,
+ struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_movie_strip(struct Main *bmain,
+ struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_scene_strip(struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_movieclip_strip(struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_mask_strip(struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+struct Sequence *SEQ_add_effect_strip(struct Scene *scene,
+ struct ListBase *seqbase,
+ struct SeqLoadData *load_data);
+void SEQ_add_image_set_directory(struct Sequence *seq, char *path);
+void SEQ_add_image_load_file(struct Sequence *seq, size_t strip_frame, char *filename);
+void SEQ_add_image_init_alpha_mode(struct Sequence *seq);
void SEQ_add_reload_new_file(struct Main *bmain,
struct Scene *scene,
struct Sequence *seq,
diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h
index ccadfc54e1d..85513faf3e6 100644
--- a/source/blender/sequencer/SEQ_sequencer.h
+++ b/source/blender/sequencer/SEQ_sequencer.h
@@ -62,8 +62,12 @@ struct Editing *SEQ_editing_get(struct Scene *scene, bool alloc);
struct Editing *SEQ_editing_ensure(struct Scene *scene);
void SEQ_editing_free(struct Scene *scene, const bool do_id_user);
struct ListBase *SEQ_active_seqbase_get(const struct Editing *ed);
+void SEQ_seqbase_active_set(struct Editing *ed, struct ListBase *seqbase);
struct Sequence *SEQ_sequence_alloc(ListBase *lb, int timeline_frame, int machine, int type);
void SEQ_sequence_free(struct Scene *scene, struct Sequence *seq, const bool do_clean_animdata);
+struct MetaStack *SEQ_meta_stack_alloc(struct Editing *ed, struct Sequence *seq_meta);
+struct MetaStack *SEQ_meta_stack_active_get(const struct Editing *ed);
+void SEQ_meta_stack_free(struct Editing *ed, struct MetaStack *ms);
void SEQ_offset_animdata(struct Scene *scene, struct Sequence *seq, int ofs);
void SEQ_dupe_animdata(struct Scene *scene, const char *name_src, const char *name_dst);
struct Sequence *SEQ_sequence_dupli_recursive(const struct Scene *scene_src,
diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c
index e9de73bc093..cf07fc7bc19 100644
--- a/source/blender/sequencer/intern/render.c
+++ b/source/blender/sequencer/intern/render.c
@@ -203,34 +203,6 @@ void SEQ_render_pixel_from_sequencer_space_v4(struct Scene *scene, float pixel[4
}
}
-void SEQ_render_init_colorspace(Sequence *seq)
-{
- if (seq->strip && seq->strip->stripdata) {
- char name[FILE_MAX];
- ImBuf *ibuf;
-
- BLI_join_dirfile(name, sizeof(name), seq->strip->dir, seq->strip->stripdata->name);
- BLI_path_abs(name, BKE_main_blendfile_path_from_global());
-
- /* initialize input color space */
- if (seq->type == SEQ_TYPE_IMAGE) {
- ibuf = IMB_loadiffname(
- name, IB_test | IB_alphamode_detect, seq->strip->colorspace_settings.name);
-
- /* byte images are default to straight alpha, however sequencer
- * works in premul space, so mark strip to be premultiplied first
- */
- seq->alpha_mode = SEQ_ALPHA_STRAIGHT;
- if (ibuf) {
- if (ibuf->flags & IB_alphamode_premul) {
- seq->alpha_mode = IMA_ALPHA_PREMUL;
- }
-
- IMB_freeImBuf(ibuf);
- }
- }
- }
-}
/** \} */
/* -------------------------------------------------------------------- */
@@ -611,6 +583,7 @@ static bool seq_need_scale_to_render_size(const Sequence *seq, bool is_proxy_ima
return true;
}
if ((seq->type & SEQ_TYPE_EFFECT) != 0 || seq->type == SEQ_TYPE_MASK ||
+ seq->type == SEQ_TYPE_META ||
(seq->type == SEQ_TYPE_SCENE && ((seq->flag & SEQ_SCENE_STRIPS) != 0))) {
return true;
}
diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c
index 4a0e4f1d9ad..4db7350544b 100644
--- a/source/blender/sequencer/intern/sequencer.c
+++ b/source/blender/sequencer/intern/sequencer.c
@@ -344,6 +344,58 @@ ListBase *SEQ_active_seqbase_get(const Editing *ed)
return ed->seqbasep;
}
+
+/**
+ * Set seqbase that is being viewed currently. This can be main seqbase or meta strip seqbase
+ *
+ * \param ed: sequence editor data
+ * \param seqbase: ListBase with strips
+ */
+void SEQ_seqbase_active_set(Editing *ed, ListBase *seqbase)
+{
+ ed->seqbasep = seqbase;
+}
+
+/**
+ * Create and initialize #MetaStack, append it to `ed->metastack` ListBase
+ *
+ * \param ed: sequence editor data
+ * \param seq_meta: meta strip
+ * \return pointer to created meta stack
+ */
+MetaStack *SEQ_meta_stack_alloc(Editing *ed, Sequence *seq_meta)
+{
+ MetaStack *ms = MEM_mallocN(sizeof(MetaStack), "metastack");
+ BLI_addtail(&ed->metastack, ms);
+ ms->parseq = seq_meta;
+ ms->oldbasep = ed->seqbasep;
+ copy_v2_v2_int(ms->disp_range, &ms->parseq->startdisp);
+ return ms;
+}
+
+/**
+ * Free #MetaStack and remove it from `ed->metastack` ListBase.
+ *
+ * \param ed: sequence editor data
+ * \param ms: meta stack
+ */
+void SEQ_meta_stack_free(Editing *ed, MetaStack *ms)
+{
+ BLI_remlink(&ed->metastack, ms);
+ MEM_freeN(ms);
+}
+
+/**
+ * Get #MetaStack that corresponds to current level that is being viewed
+ *
+ * \param ed: sequence editor data
+ * \return pointer to meta stack
+ */
+MetaStack *SEQ_meta_stack_active_get(const Editing *ed)
+{
+ return ed->metastack.last;
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c
index ba080a07879..54e71ff0698 100644
--- a/source/blender/sequencer/intern/strip_add.c
+++ b/source/blender/sequencer/intern/strip_add.c
@@ -29,6 +29,7 @@
#include "MEM_guardedalloc.h"
+#include "DNA_mask_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "DNA_sound_types.h"
@@ -54,7 +55,9 @@
#include "IMB_metadata.h"
#include "SEQ_add.h"
+#include "SEQ_effects.h"
#include "SEQ_relations.h"
+#include "SEQ_render.h"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
@@ -65,168 +68,372 @@
#include "proxy.h"
#include "utils.h"
-static void seq_load_apply(Main *bmain, Scene *scene, Sequence *seq, SeqLoadInfo *seq_load)
+/**
+ * Initialize common SeqLoadData members
+ *
+ * \param load_data: SeqLoadData to be initialized
+ * \param name: strip name (can be NULL)
+ * \param path: path to file that is used as strip input (can be NULL)
+ * \param start_frame: timeline frame where strip will be created
+ * \param channel: timeline channel where strip will be created
+ *
+ */
+void SEQ_add_load_data_init(SeqLoadData *load_data,
+ const char *name,
+ const char *path,
+ const int start_frame,
+ const int channel)
{
- if (seq) {
- BLI_strncpy_utf8(seq->name + 2, seq_load->name, sizeof(seq->name) - 2);
- BLI_utf8_invalid_strip(seq->name + 2, strlen(seq->name + 2));
- SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq);
+ memset(load_data, 0, sizeof(SeqLoadData));
+ if (name != NULL) {
+ BLI_strncpy(load_data->name, name, sizeof(load_data->name));
+ }
+ if (path != NULL) {
+ BLI_strncpy(load_data->path, path, sizeof(load_data->path));
+ }
+ load_data->start_frame = start_frame;
+ load_data->channel = channel;
+}
- if (seq_load->flag & SEQ_LOAD_FRAME_ADVANCE) {
- seq_load->start_frame += (seq->enddisp - seq->startdisp);
- }
+static void seq_add_generic_update(Scene *scene, ListBase *seqbase, Sequence *seq)
+{
+ SEQ_sequence_base_unique_name_recursive(seqbase, seq);
+ SEQ_time_update_sequence_bounds(scene, seq);
+ SEQ_sort(scene);
+ SEQ_relations_invalidate_cache_composite(scene, seq);
+}
- if (seq_load->flag & SEQ_LOAD_REPLACE_SEL) {
- seq_load->flag |= SELECT;
- SEQ_select_active_set(scene, seq);
+static void seq_add_set_name(Sequence *seq, SeqLoadData *load_data)
+{
+ if (load_data->name != NULL) {
+ BLI_strncpy(seq->name + 2, load_data->name, sizeof(seq->name) - 2);
+ }
+ else {
+ if (seq->type == SEQ_TYPE_SCENE) {
+ BLI_strncpy(seq->name + 2, load_data->scene->id.name + 2, sizeof(seq->name) - 2);
}
-
- if (seq_load->flag & SEQ_LOAD_SOUND_MONO) {
- seq->sound->flags |= SOUND_FLAGS_MONO;
- BKE_sound_load(bmain, seq->sound);
+ else if (seq->type == SEQ_TYPE_MOVIECLIP) {
+ BLI_strncpy(seq->name + 2, load_data->clip->id.name + 2, sizeof(seq->name) - 2);
}
-
- if (seq_load->flag & SEQ_LOAD_SOUND_CACHE) {
- if (seq->sound) {
- seq->sound->flags |= SOUND_FLAGS_CACHING;
- }
+ else if (seq->type == SEQ_TYPE_MASK) {
+ BLI_strncpy(seq->name + 2, load_data->mask->id.name + 2, sizeof(seq->name) - 2);
+ }
+ else if ((seq->type & SEQ_TYPE_EFFECT) != 0) {
+ BLI_strncpy(seq->name + 2, SEQ_sequence_give_name(seq), sizeof(seq->name) - 2);
+ }
+ else { /* Image, sound and movie. */
+ BLI_strncpy_utf8(seq->name + 2, load_data->name, sizeof(seq->name) - 2);
+ BLI_utf8_invalid_strip(seq->name + 2, strlen(seq->name + 2));
}
-
- seq_load->tot_success++;
- }
- else {
- seq_load->tot_error++;
}
}
-/* NOTE: this function doesn't fill in image names */
-Sequence *SEQ_add_image_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_load)
+/**
+ * Add scene strip.
+ *
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_scene_strip(Scene *scene, ListBase *seqbase, struct SeqLoadData *load_data)
{
- Scene *scene = CTX_data_scene(C); /* only for active seq */
- Sequence *seq;
- Strip *strip;
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_SCENE);
+ seq->blend_mode = SEQ_TYPE_CROSS;
+ seq->scene = load_data->scene;
+ seq->len = load_data->scene->r.efra - load_data->scene->r.sfra + 1;
+ id_us_ensure_real((ID *)load_data->scene);
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
+ return seq;
+}
- seq = SEQ_sequence_alloc(seqbasep, seq_load->start_frame, seq_load->channel, SEQ_TYPE_IMAGE);
- seq->blend_mode = SEQ_TYPE_CROSS; /* so alpha adjustment fade to the strip below */
+/**
+ * Add movieclip strip.
+ *
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_movieclip_strip(Scene *scene, ListBase *seqbase, struct SeqLoadData *load_data)
+{
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MOVIECLIP);
+ seq->blend_mode = SEQ_TYPE_CROSS;
+ seq->clip = load_data->clip;
+ seq->len = BKE_movieclip_get_duration(load_data->clip);
+ id_us_ensure_real((ID *)load_data->clip);
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
+ return seq;
+}
- /* basic defaults */
- seq->len = seq_load->len ? seq_load->len : 1;
+/**
+ * Add mask strip.
+ *
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_mask_strip(Scene *scene, ListBase *seqbase, struct SeqLoadData *load_data)
+{
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MASK);
+ seq->blend_mode = SEQ_TYPE_CROSS;
+ seq->mask = load_data->mask;
+ seq->len = BKE_mask_get_duration(load_data->mask);
+ id_us_ensure_real((ID *)load_data->mask);
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
+ return seq;
+}
- strip = seq->strip;
- strip->stripdata = MEM_callocN(seq->len * sizeof(StripElem), "stripelem");
- BLI_strncpy(strip->dir, seq_load->path, sizeof(strip->dir));
+/**
+ * Add effect strip.
+ *
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_effect_strip(Scene *scene, ListBase *seqbase, struct SeqLoadData *load_data)
+{
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, load_data->effect.type);
+
+ seq->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
+ struct SeqEffectHandle sh = SEQ_effect_handle_get(seq);
+ sh.init(seq);
+ seq->seq1 = load_data->effect.seq1;
+ seq->seq2 = load_data->effect.seq2;
+ seq->seq3 = load_data->effect.seq3;
+
+ if (seq->type == SEQ_TYPE_COLOR) {
+ seq->blend_mode = SEQ_TYPE_CROSS;
+ }
+ else if (seq->type == SEQ_TYPE_ADJUSTMENT) {
+ seq->blend_mode = SEQ_TYPE_CROSS;
+ }
+ else if (seq->type == SEQ_TYPE_TEXT) {
+ seq->blend_mode = SEQ_TYPE_ALPHAOVER;
+ }
+ else if (SEQ_effect_get_num_inputs(seq->type) == 1) {
+ seq->blend_mode = seq->seq1->blend_mode;
+ }
- if (seq_load->stereo3d_format) {
- *seq->stereo3d_format = *seq_load->stereo3d_format;
+ if (!load_data->effect.seq1) {
+ seq->len = 1; /* Effect is generator, set non zero length. */
+ SEQ_transform_set_right_handle_frame(seq, load_data->image.end_frame);
}
+ SEQ_relations_update_changed_seq_and_deps(scene, seq, 1, 1); /* Runs SEQ_time_update_sequence. */
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
+
+ return seq;
+}
+
+/**
+ * Set directory used by image strip.
+ *
+ * \param seq: image strip to be changed
+ * \param path: directory path
+ */
+void SEQ_add_image_set_directory(Sequence *seq, char *path)
+{
+ BLI_strncpy(seq->strip->dir, path, sizeof(seq->strip->dir));
+}
- seq->views_format = seq_load->views_format;
- seq->flag |= seq_load->flag & SEQ_USE_VIEWS;
+/**
+ * Set directory used by image strip.
+ *
+ * \param seq: image strip to be changed
+ * \param strip_frame: frame index of strip to be changed
+ * \param filename: image filename (only filename, not complete path)
+ */
+void SEQ_add_image_load_file(Sequence *seq, size_t strip_frame, char *filename)
+{
+ StripElem *se = SEQ_render_give_stripelem(seq, seq->start + strip_frame);
+ BLI_strncpy(se->name, filename, sizeof(se->name));
+}
- seq_load_apply(CTX_data_main(C), scene, seq, seq_load);
+/**
+ * Set image strip alpha mode
+ *
+ * \param seq: image strip to be changed
+ */
+void SEQ_add_image_init_alpha_mode(Sequence *seq)
+{
+ if (seq->strip && seq->strip->stripdata) {
+ char name[FILE_MAX];
+ ImBuf *ibuf;
+
+ BLI_join_dirfile(name, sizeof(name), seq->strip->dir, seq->strip->stripdata->name);
+ BLI_path_abs(name, BKE_main_blendfile_path_from_global());
+
+ /* Initialize input color space. */
+ if (seq->type == SEQ_TYPE_IMAGE) {
+ ibuf = IMB_loadiffname(
+ name, IB_test | IB_alphamode_detect, seq->strip->colorspace_settings.name);
+
+ /* Byte images are default to straight alpha, however sequencer
+ * works in premul space, so mark strip to be premultiplied first.
+ */
+ seq->alpha_mode = SEQ_ALPHA_STRAIGHT;
+ if (ibuf) {
+ if (ibuf->flags & IB_alphamode_premul) {
+ seq->alpha_mode = IMA_ALPHA_PREMUL;
+ }
+ IMB_freeImBuf(ibuf);
+ }
+ }
+ }
+}
+
+/**
+ * Add image strip.
+ * NOTE: Use SEQ_add_image_set_directory() and SEQ_add_image_load_file() to load image sequences
+ *
+ * \param main: Main reference
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_image_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqLoadData *load_data)
+{
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_IMAGE);
+ seq->blend_mode = SEQ_TYPE_CROSS; /* so alpha adjustment fade to the strip below */
+ seq->len = load_data->image.len;
+ Strip *strip = seq->strip;
+ strip->stripdata = MEM_callocN(load_data->image.len * sizeof(StripElem), "stripelem");
+
+ /* Multiview settings. */
+ if (load_data->use_multiview) {
+ seq->flag |= SEQ_USE_VIEWS;
+ seq->views_format = load_data->views_format;
+ }
+ if (load_data->stereo3d_format) {
+ seq->stereo3d_format = load_data->stereo3d_format;
+ }
+
+ /* Set initial scale based on load_data->fit_method. */
char file_path[FILE_MAX];
- BLI_join_dirfile(file_path, sizeof(file_path), seq_load->path, seq_load->name);
- BLI_path_abs(file_path, BKE_main_blendfile_path(CTX_data_main(C)));
+ BLI_join_dirfile(file_path, sizeof(file_path), load_data->path, load_data->name);
+ BLI_path_abs(file_path, BKE_main_blendfile_path(bmain));
ImBuf *ibuf = IMB_loadiffname(file_path, IB_rect, seq->strip->colorspace_settings.name);
if (ibuf != NULL) {
SEQ_set_scale_to_fit(
- seq, ibuf->x, ibuf->y, scene->r.xsch, scene->r.ysch, seq_load->fit_method);
+ seq, ibuf->x, ibuf->y, scene->r.xsch, scene->r.ysch, load_data->fit_method);
IMB_freeImBuf(ibuf);
}
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ /* Set Last active directory. */
+ BLI_strncpy(scene->ed->act_imagedir, seq->strip->dir, sizeof(scene->ed->act_imagedir));
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
return seq;
}
#ifdef WITH_AUDASPACE
-Sequence *SEQ_add_sound_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_load)
-{
- Main *bmain = CTX_data_main(C);
- Scene *scene = CTX_data_scene(C); /* only for sound */
- Editing *ed = SEQ_editing_get(scene, false);
- bSound *sound;
-
- Sequence *seq; /* generic strip vars */
- Strip *strip;
- StripElem *se;
-
- sound = BKE_sound_new_file(bmain, seq_load->path); /* handles relative paths */
+/**
+ * Add sound strip.
+ * NOTE: Use SEQ_add_image_set_directory() and SEQ_add_image_load_file() to load image sequences
+ *
+ * \param main: Main reference
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_sound_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqLoadData *load_data)
+{
+ bSound *sound = BKE_sound_new_file(bmain, load_data->path); /* Handles relative paths. */
SoundInfo info;
- if (!BKE_sound_info_get(bmain, sound, &info)) {
+ bool sound_loaded = BKE_sound_info_get(bmain, sound, &info);
+
+ if (!sound_loaded && !load_data->allow_invalid_file) {
BKE_id_free(bmain, sound);
return NULL;
}
- if (info.specs.channels == SOUND_CHANNELS_INVALID) {
+ if (info.specs.channels == SOUND_CHANNELS_INVALID && !load_data->allow_invalid_file) {
BKE_id_free(bmain, sound);
return NULL;
}
- seq = SEQ_sequence_alloc(seqbasep, seq_load->start_frame, seq_load->channel, SEQ_TYPE_SOUND_RAM);
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_SOUND_RAM);
seq->sound = sound;
- BLI_strncpy(seq->name + 2, "Sound", SEQ_NAME_MAXSTR - 2);
- SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq);
+ seq->scene_sound = NULL;
- /* basic defaults */
/* We add a very small negative offset here, because
* ceil(132.0) == 133.0, not nice with videos, see T47135. */
- seq->len = (int)ceil((double)info.length * FPS - 1e-4);
- strip = seq->strip;
-
- /* we only need 1 element to store the filename */
- strip->stripdata = se = MEM_callocN(sizeof(StripElem), "stripelem");
-
- BLI_split_dirfile(seq_load->path, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name));
+ seq->len = MAX2(1, (int)ceil((double)info.length * FPS - 1e-4));
- seq->scene_sound = NULL;
-
- SEQ_time_update_sequence_bounds(scene, seq);
+ Strip *strip = seq->strip;
+ /* We only need 1 element to store the filename. */
+ StripElem *se = strip->stripdata = se = MEM_callocN(sizeof(StripElem), "stripelem");
+ BLI_split_dirfile(load_data->path, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name));
- /* last active name */
- BLI_strncpy(ed->act_sounddir, strip->dir, FILE_MAXDIR);
+ if (seq != NULL && seq->sound != NULL) {
+ if (load_data->flags & SEQ_LOAD_SOUND_MONO) {
+ seq->sound->flags |= SOUND_FLAGS_MONO;
+ }
- seq_load_apply(bmain, scene, seq, seq_load);
+ if (load_data->flags & SEQ_LOAD_SOUND_CACHE) {
+ if (seq->sound) {
+ seq->sound->flags |= SOUND_FLAGS_CACHING;
+ }
+ }
+ }
- /* TODO(sergey): Shall we tag here or in the operator? */
- DEG_relations_tag_update(bmain);
+ /* Set Last active directory. */
+ BLI_strncpy(scene->ed->act_sounddir, strip->dir, FILE_MAXDIR);
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
return seq;
}
+
#else // WITH_AUDASPACE
-Sequence *SEQ_add_sound_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_load)
+Sequence *SEQ_add_sound_strip(Main *UNUSED(bmain),
+ Scene *UNUSED(scene),
+ ListBase *UNUSED(seqbase),
+ SeqLoadData *UNUSED(load_data))
{
- (void)C;
- (void)seqbasep;
- (void)seq_load;
return NULL;
}
#endif // WITH_AUDASPACE
-Sequence *SEQ_add_movie_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_load)
+/**
+ * Add movie strip.
+ *
+ * \param main: Main reference
+ * \param scene: Scene where strips will be added
+ * \param seqbase: ListBase where strips will be added
+ * \param load_data: SeqLoadData with information necessary to create strip
+ * \return created strip
+ */
+Sequence *SEQ_add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqLoadData *load_data)
{
- Main *bmain = CTX_data_main(C);
- Scene *scene = CTX_data_scene(C); /* only for sound */
- char path[sizeof(seq_load->path)];
+ char path[sizeof(load_data->path)];
+ BLI_strncpy(path, load_data->path, sizeof(path));
+ BLI_path_abs(path, BKE_main_blendfile_path(bmain));
- Sequence *seq; /* generic strip vars */
- Strip *strip;
- StripElem *se;
char colorspace[64] = "\0"; /* MAX_COLORSPACE_NAME */
bool is_multiview_loaded = false;
- const bool is_multiview = (seq_load->flag & SEQ_USE_VIEWS) != 0;
- const int totfiles = seq_num_files(scene, seq_load->views_format, is_multiview);
- struct anim **anim_arr;
+ const int totfiles = seq_num_files(scene, load_data->views_format, load_data->use_multiview);
+ struct anim **anim_arr = MEM_callocN(sizeof(struct anim *) * totfiles, "Video files");
int i;
- BLI_strncpy(path, seq_load->path, sizeof(path));
- BLI_path_abs(path, BKE_main_blendfile_path(bmain));
-
- anim_arr = MEM_callocN(sizeof(struct anim *) * totfiles, "Video files");
-
- if (is_multiview && (seq_load->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
+ if (load_data->use_multiview && (load_data->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
char prefix[FILE_MAX];
const char *ext = NULL;
size_t j = 0;
@@ -245,38 +452,30 @@ Sequence *SEQ_add_movie_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_
j++;
}
}
-
- if (j == 0) {
- MEM_freeN(anim_arr);
- return NULL;
- }
is_multiview_loaded = true;
}
}
if (is_multiview_loaded == false) {
anim_arr[0] = openanim(path, IB_rect, 0, colorspace);
-
- if (anim_arr[0] == NULL) {
- MEM_freeN(anim_arr);
- return NULL;
- }
}
- if (seq_load->flag & SEQ_LOAD_MOVIE_SOUND) {
- seq_load->channel++;
+ if (anim_arr[0] == NULL && !load_data->allow_invalid_file) {
+ MEM_freeN(anim_arr);
+ return NULL;
}
- seq = SEQ_sequence_alloc(seqbasep, seq_load->start_frame, seq_load->channel, SEQ_TYPE_MOVIE);
- /* multiview settings */
- if (seq_load->stereo3d_format) {
- *seq->stereo3d_format = *seq_load->stereo3d_format;
- seq->views_format = seq_load->views_format;
- }
- seq->flag |= seq_load->flag & SEQ_USE_VIEWS;
+ Sequence *seq = SEQ_sequence_alloc(
+ seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MOVIE);
- seq->type = SEQ_TYPE_MOVIE;
- seq->blend_mode = SEQ_TYPE_CROSS; /* so alpha adjustment fade to the strip below */
+ /* Multiview settings. */
+ if (load_data->use_multiview) {
+ seq->flag |= SEQ_USE_VIEWS;
+ seq->views_format = load_data->views_format;
+ }
+ if (load_data->stereo3d_format) {
+ seq->stereo3d_format = load_data->stereo3d_format;
+ }
for (i = 0; i < totfiles; i++) {
if (anim_arr[i]) {
@@ -289,51 +488,38 @@ Sequence *SEQ_add_movie_strip(bContext *C, ListBase *seqbasep, SeqLoadInfo *seq_
}
}
- IMB_anim_load_metadata(anim_arr[0]);
+ seq->blend_mode = SEQ_TYPE_CROSS; /* so alpha adjustment fade to the strip below */
- seq->anim_preseek = IMB_anim_get_preseek(anim_arr[0]);
+ if (anim_arr[0] != NULL) {
+ seq->anim_preseek = IMB_anim_get_preseek(anim_arr[0]);
+ seq->len = IMB_anim_get_duration(anim_arr[0], IMB_TC_RECORD_RUN);
- const float width = IMB_anim_get_image_width(anim_arr[0]);
- const float height = IMB_anim_get_image_height(anim_arr[0]);
- SEQ_set_scale_to_fit(seq, width, height, scene->r.xsch, scene->r.ysch, seq_load->fit_method);
+ IMB_anim_load_metadata(anim_arr[0]);
- BLI_strncpy(seq->name + 2, "Movie", SEQ_NAME_MAXSTR - 2);
- SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq);
+ /* Adjust scene's frame rate settings to match. */
+ if (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS) {
+ IMB_anim_get_fps(anim_arr[0], &scene->r.frs_sec, &scene->r.frs_sec_base, true);
+ }
- /* adjust scene's frame rate settings to match */
- if (seq_load->flag & SEQ_LOAD_SYNC_FPS) {
- IMB_anim_get_fps(anim_arr[0], &scene->r.frs_sec, &scene->r.frs_sec_base, true);
+ /* Set initial scale based on load_data->fit_method. */
+ const float width = IMB_anim_get_image_width(anim_arr[0]);
+ const float height = IMB_anim_get_image_height(anim_arr[0]);
+ SEQ_set_scale_to_fit(seq, width, height, scene->r.xsch, scene->r.ysch, load_data->fit_method);
}
- /* basic defaults */
- seq->len = IMB_anim_get_duration(anim_arr[0], IMB_TC_RECORD_RUN);
- strip = seq->strip;
-
+ seq->len = MAX2(1, seq->len);
BLI_strncpy(seq->strip->colorspace_settings.name,
colorspace,
sizeof(seq->strip->colorspace_settings.name));
- /* we only need 1 element for MOVIE strips */
+ Strip *strip = seq->strip;
+ /* We only need 1 element for MOVIE strips. */
+ StripElem *se;
strip->stripdata = se = MEM_callocN(sizeof(StripElem), "stripelem");
+ BLI_split_dirfile(load_data->path, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name));
- BLI_split_dirfile(seq_load->path, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name));
-
- SEQ_time_update_sequence_bounds(scene, seq);
-
- if (seq_load->name[0] == '\0') {
- BLI_strncpy(seq_load->name, se->name, sizeof(seq_load->name));
- }
-
- if (seq_load->flag & SEQ_LOAD_MOVIE_SOUND) {
- int start_frame_back = seq_load->start_frame;
- seq_load->channel--;
- seq_load->seq_sound = SEQ_add_sound_strip(C, seqbasep, seq_load);
- seq_load->start_frame = start_frame_back;
- }
-
- /* can be NULL */
- seq_load_apply(CTX_data_main(C), scene, seq, seq_load);
- SEQ_relations_invalidate_cache_composite(scene, seq);
+ seq_add_set_name(seq, load_data);
+ seq_add_generic_update(scene, seqbase, seq);
MEM_freeN(anim_arr);
return seq;
@@ -525,9 +711,9 @@ void SEQ_add_movie_reload_if_needed(struct Main *bmain,
bool must_reload = false;
- /* The Sequence struct allows for multiple anim structs to be associated with one strip. This
- * function will return true only if there is at least one 'anim' AND all anims can produce
- * frames. */
+ /* The Sequence struct allows for multiple anim structs to be associated with one strip.
+ * This function will return true only if there is at least one 'anim' AND all anims can
+ * produce frames. */
if (BLI_listbase_is_empty(&seq->anims)) {
/* No anim present, so reloading is always necessary. */
diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c
index c495ad6d8f1..21dc9aa2cdd 100644
--- a/source/blender/sequencer/intern/strip_time.c
+++ b/source/blender/sequencer/intern/strip_time.c
@@ -164,7 +164,6 @@ void SEQ_time_update_sequence_bounds(Scene *scene, Sequence *seq)
void SEQ_time_update_sequence(Scene *scene, Sequence *seq)
{
Sequence *seqm;
- int min, max;
/* check all metas recursively */
seqm = seq->seqbase.first;
@@ -212,27 +211,6 @@ void SEQ_time_update_sequence(Scene *scene, Sequence *seq)
}
}
else {
- if (seq->type == SEQ_TYPE_META) {
- seqm = seq->seqbase.first;
- if (seqm) {
- min = MAXFRAME * 2;
- max = -MAXFRAME * 2;
- while (seqm) {
- if (seqm->startdisp < min) {
- min = seqm->startdisp;
- }
- if (seqm->enddisp > max) {
- max = seqm->enddisp;
- }
- seqm = seqm->next;
- }
- seq->start = min + seq->anim_startofs;
- seq->len = max - min;
- seq->len -= seq->anim_startofs;
- seq->len -= seq->anim_endofs;
- }
- seq_update_sound_bounds_recursive(scene, seq);
- }
SEQ_time_update_sequence_bounds(scene, seq);
}
}
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index d829b812882..fa34c561147 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -1809,7 +1809,7 @@ static void WM_OT_call_menu(wmOperatorType *ot)
{
ot->name = "Call Menu";
ot->idname = "WM_OT_call_menu";
- ot->description = "Call (draw) a predefined menu";
+ ot->description = "Open a predefined menu";
ot->exec = wm_call_menu_exec;
ot->poll = WM_operator_winactive;
@@ -1840,7 +1840,7 @@ static void WM_OT_call_menu_pie(wmOperatorType *ot)
{
ot->name = "Call Pie Menu";
ot->idname = "WM_OT_call_menu_pie";
- ot->description = "Call (draw) a predefined pie menu";
+ ot->description = "Open a predefined pie menu";
ot->invoke = wm_call_pie_menu_invoke;
ot->exec = wm_call_pie_menu_exec;
@@ -1874,7 +1874,7 @@ static void WM_OT_call_panel(wmOperatorType *ot)
{
ot->name = "Call Panel";
ot->idname = "WM_OT_call_panel";
- ot->description = "Call (draw) a predefined panel";
+ ot->description = "Open a predefined panel";
ot->exec = wm_call_panel_exec;
ot->poll = WM_operator_winactive;