diff options
author | Jacques Lucke <jacques@blender.org> | 2021-03-03 14:36:51 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-03-03 14:36:51 +0300 |
commit | cf6208382eb6142085e7ce3052b177344ea156cc (patch) | |
tree | 928ebef621dea175b27c9259744ec1972d91763a | |
parent | b837933b17ec1b3ec9c375eff000c1d55c438bab (diff) | |
parent | 3f716bb626fd7d5c3edeae127407e8b361f53e7d (diff) |
Merge branch 'master' into temp-spreadsheet-editor
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(®ion->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; |