From b52aeaadfb43273b8c4cee895499207f31c5e040 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 13 Sep 2020 15:00:52 +1000 Subject: Cleanup: spelling, update function name in comments --- source/blender/blenloader/intern/versioning_290.c | 2 +- source/blender/bmesh/intern/bmesh_polygon_edgenet.c | 6 +++--- source/blender/draw/engines/image/image_private.h | 2 +- source/blender/gpu/intern/gpu_state.cc | 2 +- source/blender/makesdna/intern/makesdna.c | 6 +++--- source/blender/modifiers/intern/MOD_meshcache_pc2.c | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index cf07e9acad3..b573fb28474 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -482,7 +482,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - /* Initialise additional velocity parameter for CacheFiles. */ + /* Initialize additional velocity parameter for #CacheFile's. */ if (!DNA_struct_elem_find( fd->filesdna, "MeshSeqCacheModifierData", "float", "velocity_scale")) { for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) { diff --git a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c index 39395cb9222..b92e431c86c 100644 --- a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c +++ b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c @@ -494,7 +494,7 @@ bool BM_face_split_edgenet(BMesh *bm, } /* These arrays used to be stack memory, however they can be - * large for single faces with complex edgenets, see: T65980. */ + * large for single faces with complex edge-nets, see: T65980. */ /* over-alloc (probably 2-4 is only used in most cases), for the biggest-fan */ edge_order = MEM_mallocN(sizeof(*edge_order) * edge_order_len, __func__); @@ -1650,8 +1650,8 @@ finally: { struct TempVertPair *tvp = temp_vert_pairs.list; do { - /* we must _never_ create connections here - * (inface the islands can't have a connection at all) */ + /* We must _never_ create connections here + * (in case the islands can't have a connection at all). */ BLI_assert(BM_edge_exists(tvp->v_orig, tvp->v_temp) == NULL); } while ((tvp = tvp->next)); } diff --git a/source/blender/draw/engines/image/image_private.h b/source/blender/draw/engines/image/image_private.h index d11d868d4d2..defe5fd5bbb 100644 --- a/source/blender/draw/engines/image/image_private.h +++ b/source/blender/draw/engines/image/image_private.h @@ -34,7 +34,7 @@ struct GPUTexture; /* *********** LISTS *********** */ /* GPUViewport.storage - * Is freed everytime the viewport engine changes */ + * Is freed every time the viewport engine changes. */ typedef struct IMAGE_PassList { DRWPass *image_pass; } IMAGE_PassList; diff --git a/source/blender/gpu/intern/gpu_state.cc b/source/blender/gpu/intern/gpu_state.cc index b63abb3d57f..9ab056e868d 100644 --- a/source/blender/gpu/intern/gpu_state.cc +++ b/source/blender/gpu/intern/gpu_state.cc @@ -319,7 +319,7 @@ void GPU_force_state(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Synchronisation Utils +/** \name Synchronization Utils * \{ */ void GPU_memory_barrier(eGPUBarrier barrier) diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c index 29e29961028..f5a35783dca 100644 --- a/source/blender/makesdna/intern/makesdna.c +++ b/source/blender/makesdna/intern/makesdna.c @@ -420,10 +420,10 @@ static int add_name(const char *str) } if (str[0] == '(' && str[1] == '*') { - /* we handle function pointer and special array cases here, e.g. - * void (*function)(...) and float (*array)[..]. the array case + /* We handle function pointer and special array cases here, e.g. + * `void (*function)(...)` and `float (*array)[..]`. the array case * name is still converted to (array *)() though because it is that - * way in old dna too, and works correct with elementsize() */ + * way in old DNA too, and works correct with #DNA_elem_size_nr. */ int isfuncptr = (strchr(str + 1, '(')) != NULL; DEBUG_PRINTF(3, "\t\t\t\t*** Function pointer or multidim array pointer found\n"); diff --git a/source/blender/modifiers/intern/MOD_meshcache_pc2.c b/source/blender/modifiers/intern/MOD_meshcache_pc2.c index 120d1d6d71a..0ef9f26f1d7 100644 --- a/source/blender/modifiers/intern/MOD_meshcache_pc2.c +++ b/source/blender/modifiers/intern/MOD_meshcache_pc2.c @@ -81,7 +81,7 @@ static bool meshcache_read_pc2_head(FILE *fp, } /** - * Gets the index frange and factor + * Gets the index range and factor * * currently same as for MDD */ -- cgit v1.2.3 From ca393258262625a82a3389bf3ad6a8dc6cd22dff Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 13 Sep 2020 15:28:41 +1000 Subject: Fix T62504: Crash accessing depsgraph from evaluated view layer Use correct owner_id types for depsgraph view_layer properties instead of inheriting from the Depsgraph which is set to NULL. --- source/blender/makesrna/intern/rna_depsgraph.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/source/blender/makesrna/intern/rna_depsgraph.c b/source/blender/makesrna/intern/rna_depsgraph.c index da1ed166eb2..ed0fe3f7765 100644 --- a/source/blender/makesrna/intern/rna_depsgraph.c +++ b/source/blender/makesrna/intern/rna_depsgraph.c @@ -43,6 +43,8 @@ # include "BLI_iterator.h" # include "BLI_math.h" +# include "RNA_access.h" + # include "BKE_duplilist.h" # include "BKE_object.h" # include "BKE_scene.h" @@ -461,14 +463,19 @@ static PointerRNA rna_Depsgraph_scene_get(PointerRNA *ptr) { Depsgraph *depsgraph = (Depsgraph *)ptr->data; Scene *scene = DEG_get_input_scene(depsgraph); - return rna_pointer_inherit_refine(ptr, &RNA_Scene, scene); + PointerRNA newptr; + RNA_pointer_create(&scene->id, &RNA_Scene, scene, &newptr); + return newptr; } static PointerRNA rna_Depsgraph_view_layer_get(PointerRNA *ptr) { Depsgraph *depsgraph = (Depsgraph *)ptr->data; + Scene *scene = DEG_get_input_scene(depsgraph); ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); - return rna_pointer_inherit_refine(ptr, &RNA_ViewLayer, view_layer); + PointerRNA newptr; + RNA_pointer_create(&scene->id, &RNA_ViewLayer, view_layer, &newptr); + return newptr; } static PointerRNA rna_Depsgraph_scene_eval_get(PointerRNA *ptr) @@ -476,13 +483,19 @@ static PointerRNA rna_Depsgraph_scene_eval_get(PointerRNA *ptr) Depsgraph *depsgraph = (Depsgraph *)ptr->data; Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); return rna_pointer_inherit_refine(ptr, &RNA_Scene, scene_eval); + PointerRNA newptr; + RNA_pointer_create(&scene_eval->id, &RNA_Scene, scene_eval, &newptr); + return newptr; } static PointerRNA rna_Depsgraph_view_layer_eval_get(PointerRNA *ptr) { Depsgraph *depsgraph = (Depsgraph *)ptr->data; + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph); - return rna_pointer_inherit_refine(ptr, &RNA_ViewLayer, view_layer_eval); + PointerRNA newptr; + RNA_pointer_create(&scene_eval->id, &RNA_ViewLayer, view_layer_eval, &newptr); + return newptr; } #else -- cgit v1.2.3 From 952212ac69a5d1d16f9fac7ab19456c256382d97 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 13 Sep 2020 15:37:01 +1000 Subject: Fix printing data from an evaluated depsgraph in Python Printing an evaluated view layer would show: Evaluated Scene 'Scene' - Now __repr__ uses the __str__ fallback for evaluated data, as done in other situations where we can't create a string that would evaluate to the data. - __str__ now shows when the data is evaluated. - __str__ always includes the memory address (which was previously only shown for structs without a name). --- source/blender/python/intern/bpy_rna.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 0980d9df762..2a773519cb6 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -890,22 +890,36 @@ static PyObject *pyrna_struct_str(BPy_StructRNA *self) { PyObject *ret; const char *name; + const char *extra_info = ""; if (!PYRNA_STRUCT_IS_VALID(self)) { return PyUnicode_FromFormat("", Py_TYPE(self)->tp_name); } - /* Print name if available. */ + ID *id = self->ptr.owner_id; + if (id && id != DEG_get_original_id(id)) { + extra_info = ", evaluated"; + } + + /* Print name if available. + * + * Always include the pointer address since it can help identify unique data, + * or when data is re-allocated internally. */ name = RNA_struct_name_get_alloc(&self->ptr, NULL, 0, NULL); if (name) { - ret = PyUnicode_FromFormat( - "", RNA_struct_identifier(self->ptr.type), name); + ret = PyUnicode_FromFormat("", + RNA_struct_identifier(self->ptr.type), + name, + self->ptr.data, + extra_info); MEM_freeN((void *)name); return ret; } - return PyUnicode_FromFormat( - "", RNA_struct_identifier(self->ptr.type), self->ptr.data); + return PyUnicode_FromFormat("", + RNA_struct_identifier(self->ptr.type), + self->ptr.data, + extra_info); } static PyObject *pyrna_struct_repr(BPy_StructRNA *self) @@ -914,18 +928,14 @@ static PyObject *pyrna_struct_repr(BPy_StructRNA *self) PyObject *tmp_str; PyObject *ret; - if (id == NULL || !PYRNA_STRUCT_IS_VALID(self)) { + if (id == NULL || !PYRNA_STRUCT_IS_VALID(self) || (DEG_get_original_id(id) != id)) { /* fallback */ return pyrna_struct_str(self); } tmp_str = PyUnicode_FromString(id->name + 2); - if (DEG_get_original_id(id) != id) { - ret = PyUnicode_FromFormat( - "Evaluated %s %R", BKE_idtype_idcode_to_name(GS(id->name)), tmp_str); - } - else if (RNA_struct_is_ID(self->ptr.type) && (id->flag & LIB_EMBEDDED_DATA) == 0) { + if (RNA_struct_is_ID(self->ptr.type) && (id->flag & LIB_EMBEDDED_DATA) == 0) { ret = PyUnicode_FromFormat( "bpy.data.%s[%R]", BKE_idtype_idcode_to_name_plural(GS(id->name)), tmp_str); } -- cgit v1.2.3 From e4811cf0c308601918c3662b2b995b457140d847 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 13 Sep 2020 18:04:46 +1000 Subject: Cleanup: unused variable --- source/blender/editors/space_file/file_ops.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index b3587fc7f97..b3f9e8b6ef6 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -1593,7 +1593,6 @@ void file_draw_check_ex(bContext *C, ScrArea *area) void file_draw_check(bContext *C) { - SpaceFile *sfile = CTX_wm_space_file(C); ScrArea *area = CTX_wm_area(C); file_draw_check_ex(C, area); } -- cgit v1.2.3 From 1e186cea7c2b1409d50070f040a571ac17290bbf Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 13 Sep 2020 18:11:38 +1000 Subject: Fix T77584: Edit Mode crash with shape keys created on blank mesh Entering edit-mode after creating shape keys on a blank mesh would crash. Regression in 9b9f84b317fef which prevented initializing empty shape keys when there is no shape key offset data available. --- source/blender/bmesh/intern/bmesh_mesh_convert.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.c b/source/blender/bmesh/intern/bmesh_mesh_convert.c index 4671df90d53..2269837cc8e 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.c +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.c @@ -893,19 +893,14 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh } for (currkey = me->key->block.first; currkey; currkey = currkey->next) { - const bool apply_offset = (ofs && (currkey != actkey) && - (bm->shapenr - 1 == currkey->relative)); - int cd_shape_offset; int keyi; const float(*ofs_pt)[3] = ofs; float *newkey, (*oldkey)[3], *fp; j = bm_to_mesh_shape_layer_index_from_kb(bm, currkey); - cd_shape_offset = CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, j); - if (cd_shape_offset < 0) { - /* The target Mesh has more shapekeys than the BMesh. */ - continue; - } + const int cd_shape_offset = CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, j); + const bool apply_offset = (cd_shape_offset != -1) && (ofs != NULL) && (currkey != actkey) && + (bm->shapenr - 1 == currkey->relative); fp = newkey = MEM_callocN(me->key->elemsize * bm->totvert, "currkey->data"); oldkey = currkey->data; @@ -927,7 +922,7 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh } } } - else if (j != -1) { + else if (cd_shape_offset != -1) { /* In most cases this runs. */ copy_v3_v3(fp, BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset)); } -- cgit v1.2.3 From f5ccf8727f301f80338c2b31bf7a48c3ce52cb85 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Sun, 13 Sep 2020 11:15:37 +0200 Subject: CleanUp: Code folding Incorrect code folding in recent commit --- source/blender/draw/engines/eevee/eevee_shaders.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source/blender/draw/engines/eevee/eevee_shaders.c b/source/blender/draw/engines/eevee/eevee_shaders.c index d4b1d421603..58897272425 100644 --- a/source/blender/draw/engines/eevee/eevee_shaders.c +++ b/source/blender/draw/engines/eevee/eevee_shaders.c @@ -647,7 +647,8 @@ GPUShader *EEVEE_shaders_effect_motion_blur_velocity_tiles_expand_sh_get(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Ambient Occlusion */ +/** \name Ambient Occlusion + * \{ */ GPUShader *EEVEE_shaders_effect_ambient_occlusion_sh_get(void) { @@ -679,7 +680,8 @@ GPUShader *EEVEE_shaders_effect_ambient_occlusion_debug_sh_get(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Render Passes */ +/** \name Render Passes + * \{ */ GPUShader *EEVEE_shaders_renderpasses_post_process_sh_get(void) { @@ -693,7 +695,8 @@ GPUShader *EEVEE_shaders_renderpasses_post_process_sh_get(void) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Screen Raytrace */ +/** \name Screen Raytrace + * \{ */ struct GPUShader *EEVEE_shaders_effect_screen_raytrace_sh_get(EEVEE_SSRShaderOptions options) { @@ -730,7 +733,8 @@ struct GPUShader *EEVEE_shaders_effect_screen_raytrace_sh_get(EEVEE_SSRShaderOpt /** \} */ /* -------------------------------------------------------------------- */ -/** \name Shadows */ +/** \name Shadows + * \{ */ struct GPUShader *EEVEE_shaders_shadow_sh_get() { @@ -753,7 +757,8 @@ struct GPUShader *EEVEE_shaders_shadow_accum_sh_get() /** \} */ /* -------------------------------------------------------------------- */ -/** \name Subsurface */ +/** \name Subsurface + * \{ */ struct GPUShader *EEVEE_shaders_subsurface_first_pass_sh_get() { @@ -786,7 +791,8 @@ struct GPUShader *EEVEE_shaders_subsurface_translucency_sh_get() /** \} */ /* -------------------------------------------------------------------- */ -/** \name Volumes */ +/** \name Volumes + * \{ */ struct GPUShader *EEVEE_shaders_volumes_clear_sh_get() { -- cgit v1.2.3 From 3ee2ca0d3cd3961e5e68c58383e2ac23c3f8e0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 13 Sep 2020 15:51:46 +0200 Subject: Fix T80023 Invisible objects or glitches with object 'in front' + 'X-ray' Rendering only to the depth buffer seems to need a valid fragment shader with a color output on some platform. --- .../draw/engines/workbench/shaders/workbench_merge_infront_frag.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/draw/engines/workbench/shaders/workbench_merge_infront_frag.glsl b/source/blender/draw/engines/workbench/shaders/workbench_merge_infront_frag.glsl index b77e168889f..856654549ca 100644 --- a/source/blender/draw/engines/workbench/shaders/workbench_merge_infront_frag.glsl +++ b/source/blender/draw/engines/workbench/shaders/workbench_merge_infront_frag.glsl @@ -8,6 +8,8 @@ out vec4 fragColor; void main() { float depth = texture(depthBuffer, uvcoordsvar.st).r; + /* Fix issues with Intel drivers (see T80023). */ + fragColor = vec4(0.0); /* Discard background pixels. */ if (depth == 1.0) { discard; -- cgit v1.2.3 From 9d674708ea7e0a5d7ac698784a7649d2ee73e4f8 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Sun, 13 Sep 2020 19:50:08 +0200 Subject: Fix T80589: Translations in python scripts are missing. Python 3.8 changed handling of constant values in its AST tool. This code should work on both officialy supported 3.7, and newer 3.8, for now. --- .../modules/bl_i18n_utils/bl_extract_messages.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py index cee8f89abd3..b4abf572dbc 100644 --- a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py +++ b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py @@ -456,9 +456,11 @@ def dump_py_messages_from_files(msgs, reports, files, settings): Recursively get strings, needed in case we have "Blah" + "Blah", passed as an argument in that case it won't evaluate to a string. However, break on some kind of stopper nodes, like e.g. Subscript. """ - if type(node) == ast.Str: + # New in py 3.8: all constants are of type 'ast.Constant'. + # 'ast.Str' will have to be removed when we officially switch to this version. + if type(node) in {ast.Str, getattr(ast, "Constant", None)}: eval_str = ast.literal_eval(node) - if eval_str: + if eval_str and type(eval_str) == str: yield (is_split, eval_str, (node,)) else: is_split = (type(node) in separate_nodes) @@ -624,6 +626,7 @@ def dump_py_messages_from_files(msgs, reports, files, settings): } for fp in files: + # ~ print("Checking File ", fp) with open(fp, 'r', encoding="utf8") as filedata: root_node = ast.parse(filedata.read(), fp, 'exec') @@ -631,8 +634,8 @@ def dump_py_messages_from_files(msgs, reports, files, settings): for node in ast.walk(root_node): if type(node) == ast.Call: - # print("found function at") - # print("%s:%d" % (fp, node.lineno)) + # ~ print("found function at") + # ~ print("%s:%d" % (fp, node.lineno)) # We can't skip such situations! from blah import foo\nfoo("bar") would also be an ast.Name func! if type(node.func) == ast.Name: @@ -657,31 +660,31 @@ def dump_py_messages_from_files(msgs, reports, files, settings): if kw.arg == arg_kw: context_elements[arg_kw] = kw.value break - # print(context_elements) + # ~ print(context_elements) for kws, proc in translate_kw[msgid]: if set(kws) <= context_elements.keys(): args = tuple(context_elements[k] for k in kws) - #print("running ", proc, " with ", args) + # ~ print("running ", proc, " with ", args) ctxt = proc(*args) if ctxt: msgctxts[msgid] = ctxt break - # print(translate_args) + # ~ print(func_args) # do nothing if not found for arg_kw, (arg_pos, _) in func_args.items(): msgctxt = msgctxts[arg_kw] estr_lst = [(None, ())] if arg_pos < len(node.args): estr_lst = extract_strings_split(node.args[arg_pos]) - #print(estr, nds) else: for kw in node.keywords: if kw.arg == arg_kw: + # ~ print(kw.arg, kw.value) estr_lst = extract_strings_split(kw.value) break - #print(estr, nds) for estr, nds in estr_lst: + # ~ print(estr, nds) if estr: if nds: msgsrc = "{}:{}".format(fp_rel, sorted({nd.lineno for nd in nds})[0]) -- cgit v1.2.3 From ab7608af1bd40548cb79a0312f318a32d2fe8596 Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Sun, 13 Sep 2020 16:57:27 -0400 Subject: Apply patch D8816, from Zachary(AFWS) for collection boolean operand. Also added code so that exact solver does the whole collection at once. This patch allows users to use a collection (as an alternative to Object) for the boolean modifier operand, and therefore get rid of a long modifier stack. --- source/blender/blenlib/intern/mesh_boolean.cc | 82 ++- source/blender/blenloader/intern/versioning_290.c | 27 +- source/blender/bmesh/tools/bmesh_boolean.cc | 19 +- source/blender/bmesh/tools/bmesh_boolean.h | 2 + source/blender/editors/mesh/editmesh_intersect.c | 5 +- source/blender/editors/sculpt_paint/paint_mask.c | 2 +- source/blender/makesdna/DNA_modifier_types.h | 8 +- source/blender/makesrna/intern/rna_modifier.c | 34 +- source/blender/modifiers/intern/MOD_boolean.c | 660 ++++++++++++++++------ 9 files changed, 602 insertions(+), 237 deletions(-) diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index e92751efe72..a6ab30a3b26 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -2068,11 +2068,11 @@ static bool apply_bool_op(BoolOpType bool_optype, const Array &winding) return true; } for (int i = 1; i < nw; ++i) { - if (winding[i] == 0) { - return true; + if (winding[i] == 1) { + return false; } } - return false; + return true; } default: return false; @@ -2398,8 +2398,7 @@ static IMesh gwn_boolean(const IMesh &tm, IMesh ans; Vector out_faces; out_faces.reserve(tm.face_size()); - BLI_assert(nshapes == 2); /* TODO: generalize. */ - UNUSED_VARS_NDEBUG(nshapes); + Array winding(nshapes, 0); for (int p : pinfo.index_range()) { const Patch &patch = pinfo.patch(p); /* For test triangle, choose one in the middle of patch list @@ -2421,40 +2420,41 @@ static IMesh gwn_boolean(const IMesh &tm, if (dbg_level > 0) { std::cout << "test point = " << test_point_db << "\n"; } - int other_shape = 1 - shape; - /* The point_is_inside_shape function has to approximate if the other - * shape is not PWN. For most operations, even a hint of being inside - * gives good results, but when shape is the cutter in a Difference - * operation, we want to be pretty sure that the point is inside other_shape. - * E.g., T75827. - */ - bool need_high_confidence = (op == BoolOpType::Difference) && (shape == 1); - bool inside = point_is_inside_shape( - tm, shape_fn, test_point_db, other_shape, need_high_confidence); - if (dbg_level > 0) { - std::cout << "test point is " << (inside ? "inside\n" : "outside\n"); - } - bool do_remove; - bool do_flip; - switch (op) { - case BoolOpType::Intersect: - do_remove = !inside; - do_flip = false; - break; - case BoolOpType::Union: - do_remove = inside; - do_flip = false; - break; - case BoolOpType::Difference: - do_remove = (shape == 0) ? inside : !inside; - do_flip = (shape == 1); - break; - default: - do_remove = false; - do_flip = false; - BLI_assert(false); + for (int other_shape = 0; other_shape < nshapes; ++other_shape) { + if (other_shape == shape) { + continue; + } + /* The point_is_inside_shape function has to approximate if the other + * shape is not PWN. For most operations, even a hint of being inside + * gives good results, but when shape is a cutter in a Difference + * operation, we want to be pretty sure that the point is inside other_shape. + * E.g., T75827. + */ + bool need_high_confidence = (op == BoolOpType::Difference) && (shape != 0); + bool inside = point_is_inside_shape( + tm, shape_fn, test_point_db, other_shape, need_high_confidence); + if (dbg_level > 0) { + std::cout << "test point is " << (inside ? "inside" : "outside") << " other_shape " + << other_shape << "\n"; + } + winding[other_shape] = inside; } + /* Find out the "in the output volume" flag for each of the cases of winding[shape] == 0 + * and winding[shape] == 1. If the flags are different, this patch should be in the output. + * Also, if this is a Difference and the shape isn't the first one, need to flip the normals. + */ + winding[shape] = 0; + bool in_output_volume_0 = apply_bool_op(op, winding); + winding[shape] = 1; + bool in_output_volume_1 = apply_bool_op(op, winding); + bool do_remove = in_output_volume_0 == in_output_volume_1; + bool do_flip = !do_remove && op == BoolOpType::Difference && shape != 0; if (dbg_level > 0) { + std::cout << "winding = "; + for (int i = 0; i < nshapes; ++i) { + std::cout << winding[i] << " "; + } + std::cout << "\niv0=" << in_output_volume_0 << ", iv1=" << in_output_volume_1 << "\n"; std::cout << "result for patch " << p << ": remove=" << do_remove << ", flip=" << do_flip << "\n"; } @@ -3270,13 +3270,7 @@ IMesh boolean_trimesh(IMesh &tm_in, if (tm_in.face_size() == 0) { return IMesh(tm_in); } - IMesh tm_si; - if (use_self) { - tm_si = trimesh_self_intersect(tm_in, arena); - } - else { - tm_si = trimesh_nary_intersect(tm_in, nshapes, shape_fn, use_self, arena); - } + IMesh tm_si = trimesh_nary_intersect(tm_in, nshapes, shape_fn, use_self, arena); if (dbg_level > 1) { write_obj_mesh(tm_si, "boolean_tm_si"); std::cout << "\nboolean_tm_input after intersection:\n" << tm_si; diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index b573fb28474..7805dbdcefa 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -526,19 +526,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } - - /* Initialize solver for Boolean. */ - if (!DNA_struct_elem_find(fd->filesdna, "BooleanModifierData", "enum", "solver")) { - for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) { - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Boolean) { - BooleanModifierData *bmd = (BooleanModifierData *)md; - bmd->solver = eBooleanModifierSolver_Fast; - bmd->flag = 0; - } - } - } - } } for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) { @@ -597,6 +584,20 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + /* Solver and Collections for Boolean. */ + if (!DNA_struct_elem_find(fd->filesdna, "BooleanModifierData", "char", "solver")) { + for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Boolean) { + BooleanModifierData *bmd = (BooleanModifierData *)md; + bmd->solver = eBooleanModifierSolver_Fast; + bmd->flag = eBooleanModifierFlag_Object; + } + } + } + } + /* Keep this block, even when empty. */ } } diff --git a/source/blender/bmesh/tools/bmesh_boolean.cc b/source/blender/bmesh/tools/bmesh_boolean.cc index d2f73dd63ec..56585cb722e 100644 --- a/source/blender/bmesh/tools/bmesh_boolean.cc +++ b/source/blender/bmesh/tools/bmesh_boolean.cc @@ -194,6 +194,7 @@ static bool apply_mesh_output_to_bmesh(BMesh *bm, IMesh &m_out) /* Initially mark all existing faces as "don't keep", except hidden faces. * Also, save current #BMFace pointers as creating faces will disturb the table. */ Array old_bmfs(bm->totface); + BM_mesh_elem_index_ensure(bm, BM_FACE); for (int f = 0; f < bm->totface; ++f) { BMFace *bmf = BM_face_at_index(bm, f); old_bmfs[f] = bmf; @@ -331,6 +332,7 @@ static bool bmesh_boolean(BMesh *bm, const int looptris_tot, int (*test_fn)(BMFace *f, void *user_data), void *user_data, + int nshapes, const bool use_self, const bool use_separate_all, const BoolOpType boolean_mode) @@ -339,10 +341,9 @@ static bool bmesh_boolean(BMesh *bm, IMesh m_triangulated; IMesh m_in = mesh_from_bm(bm, looptris, looptris_tot, &m_triangulated, &arena); std::function shape_fn; - int nshapes; if (use_self && boolean_mode == BoolOpType::None) { /* Unary knife operation. Want every face where test_fn doesn't return -1. */ - nshapes = 1; + BLI_assert(nshapes == 1); shape_fn = [bm, test_fn, user_data](int f) { BMFace *bmf = BM_face_at_index(bm, f); if (test_fn(bmf, user_data) != -1) { @@ -352,15 +353,11 @@ static bool bmesh_boolean(BMesh *bm, }; } else { - nshapes = 2; shape_fn = [bm, test_fn, user_data](int f) { BMFace *bmf = BM_face_at_index(bm, f); int test_val = test_fn(bmf, user_data); - if (test_val == 0) { - return 0; - } - if (test_val == 1) { - return 1; + if (test_val >= 0) { + return test_val; } return -1; }; @@ -403,6 +400,7 @@ bool BM_mesh_boolean(BMesh *bm, const int looptris_tot, int (*test_fn)(BMFace *f, void *user_data), void *user_data, + const int nshapes, const bool use_self, const int boolean_mode) { @@ -412,6 +410,7 @@ bool BM_mesh_boolean(BMesh *bm, looptris_tot, test_fn, user_data, + nshapes, use_self, false, static_cast(boolean_mode)); @@ -430,6 +429,7 @@ bool BM_mesh_boolean_knife(BMesh *bm, const int looptris_tot, int (*test_fn)(BMFace *f, void *user_data), void *user_data, + const int nshapes, const bool use_self, const bool use_separate_all) { @@ -438,6 +438,7 @@ bool BM_mesh_boolean_knife(BMesh *bm, looptris_tot, test_fn, user_data, + nshapes, use_self, use_separate_all, blender::meshintersect::BoolOpType::None); @@ -448,6 +449,7 @@ bool BM_mesh_boolean(BMesh *UNUSED(bm), const int UNUSED(looptris_tot), int (*test_fn)(BMFace *, void *), void *UNUSED(user_data), + const int UNUSED(nshapes), const bool UNUSED(use_self), const int UNUSED(boolean_mode)) { @@ -468,6 +470,7 @@ bool BM_mesh_boolean_knife(BMesh *UNUSED(bm), const int UNUSED(looptris_tot), int (*test_fn)(BMFace *, void *), void *UNUSED(user_data), + const int UNUSED(nshapes), const bool UNUSED(use_self), const bool UNUSED(use_separate_all)) { diff --git a/source/blender/bmesh/tools/bmesh_boolean.h b/source/blender/bmesh/tools/bmesh_boolean.h index d1b4fb2509c..04b5205ec84 100644 --- a/source/blender/bmesh/tools/bmesh_boolean.h +++ b/source/blender/bmesh/tools/bmesh_boolean.h @@ -29,6 +29,7 @@ bool BM_mesh_boolean(BMesh *bm, const int looptris_tot, int (*test_fn)(BMFace *f, void *user_data), void *user_data, + const int nshapes, const bool use_self, const int boolean_mode); @@ -37,6 +38,7 @@ bool BM_mesh_boolean_knife(BMesh *bm, const int looptris_tot, int (*test_fn)(BMFace *f, void *user_data), void *user_data, + const int nshapes, const bool use_self, const bool use_separate_all); diff --git a/source/blender/editors/mesh/editmesh_intersect.c b/source/blender/editors/mesh/editmesh_intersect.c index 1b5e374b2a7..528ad57b9bf 100644 --- a/source/blender/editors/mesh/editmesh_intersect.c +++ b/source/blender/editors/mesh/editmesh_intersect.c @@ -204,8 +204,9 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op) } if (exact) { + int nshapes = use_self ? 1 : 2; has_isect = BM_mesh_boolean_knife( - em->bm, em->looptris, em->tottri, test_fn, NULL, use_self, use_separate_all); + em->bm, em->looptris, em->tottri, test_fn, NULL, nshapes, use_self, use_separate_all); } else { has_isect = BM_mesh_intersect(em->bm, @@ -373,7 +374,7 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op) if (use_exact) { has_isect = BM_mesh_boolean( - em->bm, em->looptris, em->tottri, test_fn, NULL, use_self, boolean_operation); + em->bm, em->looptris, em->tottri, test_fn, NULL, 2, use_self, boolean_operation); } else { has_isect = BM_mesh_intersect(em->bm, diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index 70f8ef89e9a..c9fdf3c238d 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -1022,7 +1022,7 @@ static void sculpt_gesture_apply_trim(SculptGestureContext *sgcontext) break; } - BM_mesh_boolean(bm, looptris, tottri, bm_face_isect_pair, NULL, false, boolean_mode); + BM_mesh_boolean(bm, looptris, tottri, bm_face_isect_pair, NULL, 2, false, boolean_mode); Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, sculpt_mesh); BM_mesh_free(bm); diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index a9f1d5bcfc4..9a5b5b8c2ca 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -861,26 +861,32 @@ typedef struct BooleanModifierData { ModifierData modifier; struct Object *object; + struct Collection *collection; + float double_threshold; char operation; char solver; char flag; char bm_flag; - float double_threshold; } BooleanModifierData; +/* BooleanModifierData->operation */ typedef enum { eBooleanModifierOp_Intersect = 0, eBooleanModifierOp_Union = 1, eBooleanModifierOp_Difference = 2, } BooleanModifierOp; +/* BooleanModifierData->solver */ typedef enum { eBooleanModifierSolver_Fast = 0, eBooleanModifierSolver_Exact = 1, } BooleanModifierSolver; +/* BooleanModifierData->flag */ enum { eBooleanModifierFlag_Self = (1 << 0), + eBooleanModifierFlag_Object = (1 << 1), + eBooleanModifierFlag_Collection = (1 << 2), }; /* bm_flag only used when G_DEBUG. */ diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 1bf14f86189..2463f3c409f 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -2804,18 +2804,32 @@ static void rna_def_modifier_boolean(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static const EnumPropertyItem prop_operand_items[] = { + {eBooleanModifierFlag_Object, + "OBJECT", + 0, + "Object", + "Use a mesh object as the operand for the Boolean operation"}, + {eBooleanModifierFlag_Collection, + "COLLECTION", + 0, + "Collection", + "Use a collection of mesh objects as the operand for the Boolean operation"}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem prop_operation_items[] = { {eBooleanModifierOp_Intersect, "INTERSECT", 0, "Intersect", - "Keep the part of the mesh that intersects with the other selected object"}, - {eBooleanModifierOp_Union, "UNION", 0, "Union", "Combine two meshes in an additive way"}, + "Keep the part of the mesh that is common between all operands"}, + {eBooleanModifierOp_Union, "UNION", 0, "Union", "Combine meshes in an additive way"}, {eBooleanModifierOp_Difference, "DIFFERENCE", 0, "Difference", - "Combine two meshes in a subtractive way"}, + "Combine meshes in a subtractive way"}, {0, NULL, 0, NULL, NULL}, }; @@ -2843,12 +2857,26 @@ static void rna_def_modifier_boolean(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + prop = RNA_def_property(srna, "collection", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "collection"); + RNA_def_property_struct_type(prop, "Collection"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT); + RNA_def_property_ui_text( + prop, "Collection", "Use mesh objects in this collection for Boolean operation"); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, prop_operation_items); RNA_def_property_enum_default(prop, eBooleanModifierOp_Difference); RNA_def_property_ui_text(prop, "Operation", ""); RNA_def_property_update(prop, 0, "rna_Modifier_update"); + prop = RNA_def_property(srna, "operand_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag"); + RNA_def_property_enum_items(prop, prop_operand_items); + RNA_def_property_ui_text(prop, "Operand Type", ""); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + prop = RNA_def_property(srna, "double_threshold", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "double_threshold"); RNA_def_property_range(prop, 0, 1.0f); diff --git a/source/blender/modifiers/intern/MOD_boolean.c b/source/blender/modifiers/intern/MOD_boolean.c index bef7d5d8e4f..cd552d4e1ad 100644 --- a/source/blender/modifiers/intern/MOD_boolean.c +++ b/source/blender/modifiers/intern/MOD_boolean.c @@ -28,16 +28,20 @@ #include "BLI_utildefines.h" #include "BLI_alloca.h" +#include "BLI_array.h" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLT_translation.h" +#include "DNA_collection_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" +#include "DNA_scene_types.h" #include "DNA_screen_types.h" +#include "BKE_collection.h" #include "BKE_context.h" #include "BKE_global.h" /* only to check G.debug */ #include "BKE_lib_id.h" @@ -65,6 +69,7 @@ #include "tools/bmesh_boolean.h" #include "tools/bmesh_intersect.h" +// #define DEBUG_TIME #ifdef DEBUG_TIME # include "PIL_time.h" # include "PIL_time_utildefines.h" @@ -77,7 +82,7 @@ static void initData(ModifierData *md) bmd->double_threshold = 1e-6f; bmd->operation = eBooleanModifierOp_Difference; bmd->solver = eBooleanModifierSolver_Exact; - bmd->flag = 0; + bmd->flag = eBooleanModifierFlag_Object; } static bool isDisabled(const struct Scene *UNUSED(scene), @@ -85,13 +90,15 @@ static bool isDisabled(const struct Scene *UNUSED(scene), bool UNUSED(useRenderParams)) { BooleanModifierData *bmd = (BooleanModifierData *)md; + Collection *col = bmd->collection; - /* The object type check is only needed here in case we have a placeholder - * object assigned (because the library containing the mesh is missing). - * - * In other cases it should be impossible to have a type mismatch. - */ - return !bmd->object || bmd->object->type != OB_MESH; + if (bmd->flag & eBooleanModifierFlag_Object) { + return !bmd->object || bmd->object->type != OB_MESH; + } + if (bmd->flag & eBooleanModifierFlag_Collection) { + return !col; + } + return false; } static void foreachObjectLink(ModifierData *md, Object *ob, ObjectWalkFunc walk, void *userData) @@ -101,23 +108,45 @@ static void foreachObjectLink(ModifierData *md, Object *ob, ObjectWalkFunc walk, walk(userData, ob, &bmd->object, IDWALK_CB_NOP); } +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + + walk(userData, ob, (ID **)&bmd->collection, IDWALK_CB_NOP); + + /* Needed for the object operand to work. */ + foreachObjectLink(md, ob, (ObjectWalkFunc)walk, userData); +} + static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) { BooleanModifierData *bmd = (BooleanModifierData *)md; - if (bmd->object != NULL) { + if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object != NULL) { DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_TRANSFORM, "Boolean Modifier"); DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_GEOMETRY, "Boolean Modifier"); } + + Collection *col = bmd->collection; + + if ((bmd->flag & eBooleanModifierFlag_Collection) && col != NULL) { + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { + if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { + DEG_add_object_relation(ctx->node, operand_ob, DEG_OB_COMP_TRANSFORM, "Boolean Modifier"); + DEG_add_object_relation(ctx->node, operand_ob, DEG_OB_COMP_GEOMETRY, "Boolean Modifier"); + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } /* We need own transformation as well. */ DEG_add_modifier_to_transform_relation(ctx->node, "Boolean Modifier"); } static Mesh *get_quick_mesh( - Object *ob_self, Mesh *mesh_self, Object *ob_other, Mesh *mesh_other, int operation) + Object *ob_self, Mesh *mesh_self, Object *ob_operand_ob, Mesh *mesh_operand_ob, int operation) { Mesh *result = NULL; - if (mesh_self->totpoly == 0 || mesh_other->totpoly == 0) { + if (mesh_self->totpoly == 0 || mesh_operand_ob->totpoly == 0) { switch (operation) { case eBooleanModifierOp_Intersect: result = BKE_mesh_new_nomain(0, 0, 0, 0, 0); @@ -128,13 +157,13 @@ static Mesh *get_quick_mesh( result = mesh_self; } else { - BKE_id_copy_ex(NULL, &mesh_other->id, (ID **)&result, LIB_ID_COPY_LOCALIZE); + BKE_id_copy_ex(NULL, &mesh_operand_ob->id, (ID **)&result, LIB_ID_COPY_LOCALIZE); float imat[4][4]; float omat[4][4]; invert_m4_m4(imat, ob_self->obmat); - mul_m4_m4m4(omat, imat, ob_other->obmat); + mul_m4_m4m4(omat, imat, ob_operand_ob->obmat); const int mverts_len = result->totvert; MVert *mv = result->mvert; @@ -168,208 +197,496 @@ static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) return BM_elem_flag_test(f, BM_FACE_TAG) ? 1 : 0; } -static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) +static bool BMD_error_messages(ModifierData *md, Collection *col) { BooleanModifierData *bmd = (BooleanModifierData *)md; - Mesh *result = mesh; - Mesh *mesh_other; + bool error_returns_result = false; + + const bool operand_collection = (bmd->flag & eBooleanModifierFlag_Collection) != 0; + const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; + const bool operation_intersect = bmd->operation == eBooleanModifierOp_Intersect; - if (bmd->object == NULL) { - return result; +#ifndef WITH_GMP + /* If compiled without GMP, return a error. */ + if (use_exact) { + BKE_modifier_set_error(md, "Compiled without GMP, using fast solver"); + error_returns_result = false; } +#endif - Object *other = bmd->object; - mesh_other = BKE_modifier_get_evaluated_mesh_from_evaluated_object(other, false); - if (mesh_other) { - Object *object = ctx->object; + /* If intersect is selected using fast solver, return a error. */ + if (operand_collection && operation_intersect && !use_exact) { + BKE_modifier_set_error(md, "Cannot execute, intersect only available using exact solver"); + error_returns_result = true; + } - /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! - * But for 2.90 better not try to be smart here. */ - BKE_mesh_wrapper_ensure_mdata(mesh_other); + /* If the selected collection is empty and using fast solver, return a error. */ + if (operand_collection) { + if (!use_exact && BKE_collection_is_empty(col)) { + BKE_modifier_set_error(md, "Cannot execute, fast solver and empty collection"); + error_returns_result = true; + } - /* when one of objects is empty (has got no faces) we could speed up - * calculation a bit returning one of objects' derived meshes (or empty one) - * Returning mesh is depended on modifiers operation (sergey) */ - result = get_quick_mesh(object, mesh, other, mesh_other, bmd->operation); + /* If the selected collection contain non mesh objects, return a error. */ + if (col) { + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { + if (operand_ob->type != OB_MESH) { + BKE_modifier_set_error( + md, "Cannot execute, the selected collection contains non mesh objects"); + error_returns_result = true; + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + } - if (result == NULL) { - const bool is_flip = (is_negative_m4(object->obmat) != is_negative_m4(other->obmat)); + return error_returns_result; +} - BMesh *bm; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh, mesh_other); +static BMesh *BMD_mesh_bm_create( + Mesh *mesh, Object *object, Mesh *mesh_operand_ob, Object *operand_ob, bool *r_is_flip) +{ + BMesh *bm; -#ifdef DEBUG_TIME - TIMEIT_START(boolean_bmesh); -#endif - bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = false, - })); + *r_is_flip = (is_negative_m4(object->obmat) != is_negative_m4(operand_ob->obmat)); - BM_mesh_bm_from_me(bm, - mesh_other, - &((struct BMeshFromMeshParams){ - .calc_face_normal = true, - })); + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh, mesh_operand_ob); - if (UNLIKELY(is_flip)) { - const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); - BMIter iter; - BMFace *efa; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); - } - } + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = false, + })); - BM_mesh_bm_from_me(bm, - mesh, - &((struct BMeshFromMeshParams){ - .calc_face_normal = true, - })); + BM_mesh_bm_from_me(bm, + mesh_operand_ob, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); - /* main bmesh intersection setup */ - { - /* create tessface & intersect */ - const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); - int tottri; - BMLoop *(*looptris)[3]; + if (UNLIKELY(*r_is_flip)) { + const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); + BMIter iter; + BMFace *efa; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); + } + } - looptris = MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); + BM_mesh_bm_from_me(bm, + mesh, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); - BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); + return bm; +} - /* postpone this until after tessellating - * so we can use the original normals before the vertex are moved */ - { - BMIter iter; - int i; - const int i_verts_end = mesh_other->totvert; - const int i_faces_end = mesh_other->totpoly; +static void BMD_mesh_intersection(BMesh *bm, + ModifierData *md, + const ModifierEvalContext *ctx, + Mesh *mesh_operand_ob, + Object *object, + Object *operand_ob, + bool is_flip) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; - float imat[4][4]; - float omat[4][4]; + /* main bmesh intersection setup */ + /* create tessface & intersect */ + const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); + int tottri; + BMLoop *(*looptris)[3]; - invert_m4_m4(imat, object->obmat); - mul_m4_m4m4(omat, imat, other->obmat); + looptris = MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); - BMVert *eve; - i = 0; - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - mul_m4_v3(omat, eve->co); - if (++i == i_verts_end) { - break; - } - } + BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); - /* we need face normals because of 'BM_face_split_edgenet' - * we could calculate on the fly too (before calling split). */ - { - float nmat[3][3]; - copy_m3_m4(nmat, omat); - invert_m3(nmat); + /* postpone this until after tessellating + * so we can use the original normals before the vertex are moved */ + { + BMIter iter; + int i; + const int i_verts_end = mesh_operand_ob->totvert; + const int i_faces_end = mesh_operand_ob->totpoly; - if (UNLIKELY(is_flip)) { - negate_m3(nmat); - } + float imat[4][4]; + float omat[4][4]; - const short ob_src_totcol = other->totcol; - short *material_remap = BLI_array_alloca(material_remap, - ob_src_totcol ? ob_src_totcol : 1); + invert_m4_m4(imat, object->obmat); + mul_m4_m4m4(omat, imat, operand_ob->obmat); - /* Using original (not evaluated) object here since we are writing to it. */ - /* XXX Pretty sure comment above is fully wrong now with CoW & co ? */ - BKE_object_material_remap_calc(ctx->object, other, material_remap); + BMVert *eve; + i = 0; + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + mul_m4_v3(omat, eve->co); + if (++i == i_verts_end) { + break; + } + } - BMFace *efa; - i = 0; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - mul_transposed_m3_v3(nmat, efa->no); - normalize_v3(efa->no); + /* we need face normals because of 'BM_face_split_edgenet' + * we could calculate on the fly too (before calling split). */ + { + float nmat[3][3]; + copy_m3_m4(nmat, omat); + invert_m3(nmat); - /* Temp tag to test which side split faces are from. */ - BM_elem_flag_enable(efa, BM_FACE_TAG); + if (UNLIKELY(is_flip)) { + negate_m3(nmat); + } - /* remap material */ - if (LIKELY(efa->mat_nr < ob_src_totcol)) { - efa->mat_nr = material_remap[efa->mat_nr]; - } + const short ob_src_totcol = operand_ob->totcol; + short *material_remap = BLI_array_alloca(material_remap, ob_src_totcol ? ob_src_totcol : 1); - if (++i == i_faces_end) { - break; - } - } - } - } + /* Using original (not evaluated) object here since we are writing to it. */ + /* XXX Pretty sure comment above is fully wrong now with CoW & co ? */ + BKE_object_material_remap_calc(ctx->object, operand_ob, material_remap); - /* not needed, but normals for 'dm' will be invalid, - * currently this is ok for 'BM_mesh_intersect' */ - // BM_mesh_normals_update(bm); + BMFace *efa; + i = 0; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + mul_transposed_m3_v3(nmat, efa->no); + normalize_v3(efa->no); - bool use_separate = false; - bool use_dissolve = true; - bool use_island_connect = true; + /* Temp tag to test which side split faces are from. */ + BM_elem_flag_enable(efa, BM_FACE_TAG); - /* change for testing */ - if (G.debug & G_DEBUG) { - use_separate = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_Separate) != 0; - use_dissolve = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoDissolve) == 0; - use_island_connect = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoConnectRegions) == - 0; + /* remap material */ + if (LIKELY(efa->mat_nr < ob_src_totcol)) { + efa->mat_nr = material_remap[efa->mat_nr]; } + if (++i == i_faces_end) { + break; + } + } + } + } + + /* not needed, but normals for 'dm' will be invalid, + * currently this is ok for 'BM_mesh_intersect' */ + // BM_mesh_normals_update(bm); + + bool use_separate = false; + bool use_dissolve = true; + bool use_island_connect = true; + + /* change for testing */ + if (G.debug & G_DEBUG) { + use_separate = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_Separate) != 0; + use_dissolve = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoDissolve) == 0; + use_island_connect = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoConnectRegions) == 0; + } + #ifdef WITH_GMP - const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; - const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; + const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; + const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; #else - if (bmd->solver == eBooleanModifierSolver_Exact) { - BKE_modifier_set_error(md, "Compiled without GMP, using fast solver"); - } - const bool use_exact = false; - const bool use_self = false; + const bool use_exact = false; + const bool use_self = false; #endif - if (use_exact) { - BM_mesh_boolean( - bm, looptris, tottri, bm_face_isect_pair, NULL, use_self, bmd->operation); - } - else { - BM_mesh_intersect(bm, - looptris, - tottri, - bm_face_isect_pair, - NULL, - false, - use_separate, - use_dissolve, - use_island_connect, - false, - false, - bmd->operation, - bmd->double_threshold); + if (use_exact) { + BM_mesh_boolean(bm, looptris, tottri, bm_face_isect_pair, NULL, 2, use_self, bmd->operation); + } + else { + BM_mesh_intersect(bm, + looptris, + tottri, + bm_face_isect_pair, + NULL, + false, + use_separate, + use_dissolve, + use_island_connect, + false, + false, + bmd->operation, + bmd->double_threshold); + } + MEM_freeN(looptris); +} + +static int bm_face_isect_nary(BMFace *f, void *user_data) +{ + int *shape = (int *)user_data; + return shape[BM_elem_index_get(f)]; +} + +/* The Exact solver can do all operands of a collection at once. */ +static Mesh *collection_boolean_exact(BooleanModifierData *bmd, + const ModifierEvalContext *ctx, + Mesh *mesh) +{ + int i; + Mesh *result = mesh; + Collection *col = bmd->collection; + int num_shapes = 1; + Mesh **meshes = NULL; + Object **objects = NULL; + BLI_array_declare(meshes); + BLI_array_declare(objects); + BMAllocTemplate bat; + bat.totvert = mesh->totvert; + bat.totedge = mesh->totedge; + bat.totloop = mesh->totloop; + bat.totface = mesh->totpoly; + BLI_array_append(meshes, mesh); + BLI_array_append(objects, ctx->object); + Mesh *col_mesh; + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, ob) { + if (ob->type == OB_MESH && ob != ctx->object) { + col_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob, false); + /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! + * But for 2.90 better not try to be smart here. */ + BKE_mesh_wrapper_ensure_mdata(col_mesh); + BLI_array_append(meshes, col_mesh); + BLI_array_append(objects, ob); + bat.totvert += col_mesh->totvert; + bat.totedge += col_mesh->totedge; + bat.totloop += col_mesh->totloop; + bat.totface += col_mesh->totpoly; + ++num_shapes; + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + int *shape_face_end = MEM_mallocN(num_shapes * sizeof(int), __func__); + int *shape_vert_end = MEM_mallocN(num_shapes * sizeof(int), __func__); + bool is_neg_mat0 = is_negative_m4(ctx->object->obmat); + BMesh *bm = BM_mesh_create(&bat, + &((struct BMeshCreateParams){ + .use_toolflags = false, + })); + for (i = 0; i < num_shapes; i++) { + Mesh *me = meshes[i]; + Object *ob = objects[i]; + /* Need normals for triangulation. */ + BM_mesh_bm_from_me(bm, + me, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + shape_face_end[i] = me->totpoly + (i == 0 ? 0 : shape_face_end[i - 1]); + shape_vert_end[i] = me->totvert + (i == 0 ? 0 : shape_vert_end[i - 1]); + if (i > 0) { + bool is_flip = (is_neg_mat0 != is_negative_m4(ob->obmat)); + if (UNLIKELY(is_flip)) { + const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); + BMIter iter; + BMFace *efa; + BM_mesh_elem_index_ensure(bm, BM_FACE); + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_index_get(efa) >= shape_face_end[i - 1]) { + BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); + } } + } + } + } - MEM_freeN(looptris); + /* Triangulate the mesh. */ + const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); + int tottri; + BMLoop *(*looptris)[3]; + looptris = MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); + BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); + + /* Move the vertices of all but the first shape into transformation space of first mesh. + * Do this after tesselation so don't need to recalculate normals. + * The Exact solver doesn't need normals on the input faces. */ + float imat[4][4]; + float omat[4][4]; + invert_m4_m4(imat, ctx->object->obmat); + int curshape = 0; + int curshape_vert_end = shape_vert_end[0]; + BMVert *eve; + BMIter iter; + i = 0; + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (i == curshape_vert_end) { + curshape++; + curshape_vert_end = shape_vert_end[curshape]; + mul_m4_m4m4(omat, imat, objects[curshape]->obmat); + } + if (curshape > 0) { + mul_m4_v3(omat, eve->co); + } + i++; + } + + /* Remap the materials. Fill a shape array for test function. Calculate normals. */ + int *shape = MEM_mallocN(bm->totface * sizeof(int), __func__); + curshape = 0; + int curshape_face_end = shape_face_end[0]; + int curshape_ncol = ctx->object->totcol; + short *material_remap = NULL; + BMFace *efa; + i = 0; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (i == curshape_face_end) { + curshape++; + curshape_face_end = shape_face_end[curshape]; + if (material_remap != NULL) { + MEM_freeN(material_remap); } + curshape_ncol = objects[curshape]->totcol; + material_remap = MEM_mallocN(curshape_ncol ? curshape_ncol : 1, __func__); + BKE_object_material_remap_calc(ctx->object, objects[curshape], material_remap); + } + shape[i] = curshape; + if (curshape > 0) { + /* Normals for other shapes changed because vertex positions changed. + * Boolean doesn't need these, but post-boolean code (interpolation) does. */ + BM_face_normal_update(efa); + if (LIKELY(efa->mat_nr < curshape_ncol)) { + efa->mat_nr = material_remap[efa->mat_nr]; + } + } + i++; + } - result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_elem_index_ensure(bm, BM_FACE); + BM_mesh_boolean( + bm, looptris, tottri, bm_face_isect_nary, shape, num_shapes, true, bmd->operation); - BM_mesh_free(bm); + result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + MEM_freeN(shape); + MEM_freeN(shape_face_end); + MEM_freeN(shape_vert_end); + MEM_freeN(looptris); + if (material_remap != NULL) { + MEM_freeN(material_remap); + } + BLI_array_free(meshes); + BLI_array_free(objects); + return result; +} + +static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + Object *object = ctx->object; + Mesh *result = mesh; + Mesh *mesh_operand_ob; + BMesh *bm; + Collection *col = bmd->collection; + + bool is_flip = false; + const bool confirm_return = true; +#ifdef WITH_GMP + const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; +#else + const bool use_exact = false; +#endif #ifdef DEBUG_TIME - TIMEIT_END(boolean_bmesh); + TIMEIT_START(boolean_bmesh); #endif + + if (bmd->flag & eBooleanModifierFlag_Object) { + if (bmd->object == NULL) { + return result; } - /* if new mesh returned, return it; otherwise there was - * an error, so delete the modifier object */ - if (result == NULL) { - BKE_modifier_set_error(md, "Cannot execute boolean operation"); + BMD_error_messages(md, NULL); + + Object *operand_ob = bmd->object; + + mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, false); + + if (mesh_operand_ob) { + /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! + * But for 2.90 better not try to be smart here. */ + BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); + /* when one of objects is empty (has got no faces) we could speed up + * calculation a bit returning one of objects' derived meshes (or empty one) + * Returning mesh is depended on modifiers operation (sergey) */ + result = get_quick_mesh(object, mesh, operand_ob, mesh_operand_ob, bmd->operation); + + if (result == NULL) { + bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); + + BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); + + result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + + /* if new mesh returned, return it; otherwise there was + * an error, so delete the modifier object */ + if (result == NULL) { + BKE_modifier_set_error(md, "Cannot execute boolean operation"); + } } } + else { + if (col == NULL && !use_exact) { + return result; + } + + /* Return result for certain errors. */ + if (BMD_error_messages(md, col) == confirm_return) { + return result; + } + + if (use_exact) { + result = collection_boolean_exact(bmd, ctx, mesh); + } + else { + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { + if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { + + mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, + false); + + if (mesh_operand_ob) { + /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! + * But for 2.90 better not try to be smart here. */ + BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); + /* when one of objects is empty (has got no faces) we could speed up + * calculation a bit returning one of objects' derived meshes (or empty one) + * Returning mesh is depended on modifiers operation (sergey) */ + result = get_quick_mesh(object, mesh, operand_ob, mesh_operand_ob, bmd->operation); + + if (result == NULL) { + bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); + + BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); + + /* Needed for multiple objects to work. */ + BM_mesh_bm_to_me(NULL, + bm, + mesh, + (&(struct BMeshToMeshParams){ + .calc_object_remap = false, + })); + + result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + /* if new mesh returned, return it; otherwise there was + * an error, so delete the modifier object */ + if (result == NULL) { + BKE_modifier_set_error(md, "Cannot execute boolean operation"); + } + } + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + } + +#ifdef DEBUG_TIME + TIMEIT_END(boolean_bmesh); +#endif + return result; } @@ -392,13 +709,26 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetPropSep(layout, true); + uiItemR(layout, ptr, "operand_type", 0, NULL, ICON_NONE); + + const bool operand_object = RNA_enum_get(ptr, "operand_type") == eBooleanModifierFlag_Object; + + if (operand_object) { + uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); + } + else { + uiItemR(layout, ptr, "collection", 0, NULL, ICON_NONE); + } + const bool use_exact = RNA_enum_get(ptr, "solver") == eBooleanModifierSolver_Exact; - uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); if (use_exact) { - uiItemR(layout, ptr, "use_self", 0, NULL, ICON_NONE); + /* When operand is collection, we always use_self. */ + if (operand_object) { + uiItemR(layout, ptr, "use_self", 0, NULL, ICON_NONE); + } } else { uiItemR(layout, ptr, "double_threshold", 0, NULL, ICON_NONE); @@ -443,7 +773,7 @@ ModifierTypeInfo modifierType_Boolean = { /* dependsOnTime */ NULL, /* dependsOnNormals */ NULL, /* foreachObjectLink */ foreachObjectLink, - /* foreachIDLink */ NULL, + /* foreachIDLink */ foreachIDLink, /* foreachTexLink */ NULL, /* freeRuntimeData */ NULL, /* panelRegister */ panelRegister, -- cgit v1.2.3 From ecfbc5fb55d5035a5ac5f100801559d57ef156e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 14 Sep 2020 01:06:49 +0200 Subject: Fix T80603 Workbench: Inverted alpha when rendering This was caused by a left over DRWPass->state modification that made the subsequent samples redraw without Blending enabled. This led to incorrect blending. The fix is to use the new API for pass instancing. --- .../draw/engines/workbench/workbench_private.h | 3 +++ .../draw/engines/workbench/workbench_transparent.c | 23 +++++++++++----------- source/blender/draw/intern/draw_manager_data.c | 6 ++++++ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/source/blender/draw/engines/workbench/workbench_private.h b/source/blender/draw/engines/workbench/workbench_private.h index 8983826f16f..d377f09ac73 100644 --- a/source/blender/draw/engines/workbench/workbench_private.h +++ b/source/blender/draw/engines/workbench/workbench_private.h @@ -131,6 +131,9 @@ typedef struct WORKBENCH_PassList { struct DRWPass *transp_accum_ps; struct DRWPass *transp_accum_infront_ps; + struct DRWPass *transp_depth_infront_ps; + struct DRWPass *transp_depth_ps; + struct DRWPass *shadow_ps[2]; struct DRWPass *merge_infront_ps; diff --git a/source/blender/draw/engines/workbench/workbench_transparent.c b/source/blender/draw/engines/workbench/workbench_transparent.c index 1c8575ddc12..94fcd8b5a9d 100644 --- a/source/blender/draw/engines/workbench/workbench_transparent.c +++ b/source/blender/draw/engines/workbench/workbench_transparent.c @@ -91,16 +91,18 @@ void workbench_transparent_cache_init(WORKBENCH_Data *vedata) { int transp = 1; for (int infront = 0; infront < 2; infront++) { - DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_OIT; + DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_OIT | + wpd->cull_state | wpd->clip_state; DRWPass *pass; if (infront) { - DRW_PASS_CREATE(psl->transp_accum_infront_ps, state | wpd->cull_state | wpd->clip_state); - pass = psl->transp_accum_infront_ps; + psl->transp_accum_infront_ps = pass = DRW_pass_create("transp_accum_infront", state); + DRW_PASS_INSTANCE_CREATE( + psl->transp_depth_infront_ps, pass, state | DRW_STATE_WRITE_DEPTH); } else { - DRW_PASS_CREATE(psl->transp_accum_ps, state | wpd->cull_state | wpd->clip_state); - pass = psl->transp_accum_ps; + psl->transp_accum_ps = pass = DRW_pass_create("transp_accum", state); + DRW_PASS_INSTANCE_CREATE(psl->transp_depth_ps, pass, state | DRW_STATE_WRITE_DEPTH); } for (eWORKBENCH_DataType data = 0; data < WORKBENCH_DATATYPE_MAX; data++) { @@ -159,20 +161,17 @@ void workbench_transparent_draw_depth_pass(WORKBENCH_Data *data) const bool do_transparent_depth_pass = psl->outline_ps || wpd->dof_enabled || do_xray_depth_pass; if (do_transparent_depth_pass) { - DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL; - if (!DRW_pass_is_empty(psl->transp_accum_ps)) { + if (!DRW_pass_is_empty(psl->transp_depth_ps)) { GPU_framebuffer_bind(fbl->opaque_fb); /* TODO(fclem) Disable writing to first two buffers. Unnecessary waste of bandwidth. */ - DRW_pass_state_set(psl->transp_accum_ps, state | wpd->cull_state | wpd->clip_state); - DRW_draw_pass(psl->transp_accum_ps); + DRW_draw_pass(psl->transp_depth_ps); } - if (!DRW_pass_is_empty(psl->transp_accum_infront_ps)) { + if (!DRW_pass_is_empty(psl->transp_depth_infront_ps)) { GPU_framebuffer_bind(fbl->opaque_infront_fb); /* TODO(fclem) Disable writing to first two buffers. Unnecessary waste of bandwidth. */ - DRW_pass_state_set(psl->transp_accum_infront_ps, state | wpd->cull_state | wpd->clip_state); - DRW_draw_pass(psl->transp_accum_infront_ps); + DRW_draw_pass(psl->transp_depth_infront_ps); } } } diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 81842f5d2ec..ca5d5170262 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -1962,6 +1962,8 @@ DRWPass *DRW_pass_create(const char *name, DRWState state) return pass; } +/* Create an instance of the original pass that will execute the same drawcalls but with its own + * DRWState. */ DRWPass *DRW_pass_create_instance(const char *name, DRWPass *original, DRWState state) { DRWPass *pass = DRW_pass_create(name, state); @@ -1980,6 +1982,10 @@ void DRW_pass_link(DRWPass *first, DRWPass *second) bool DRW_pass_is_empty(DRWPass *pass) { + if (pass->original) { + return DRW_pass_is_empty(pass->original); + } + LISTBASE_FOREACH (DRWShadingGroup *, shgroup, &pass->shgroups) { if (!DRW_shgroup_is_empty(shgroup)) { return false; -- cgit v1.2.3 From c1c53d3ae31ad1b2c0b16077e53574d20390ae84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 14 Sep 2020 01:07:05 +0200 Subject: Cleanup: DRWManager: Remove deprecated pass_state functions And also enable pass names when using `--debug-gpu` option. --- source/blender/draw/intern/DRW_render.h | 4 ---- source/blender/draw/intern/draw_manager_data.c | 17 +---------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index 30c6f0ad4dc..2744c55a231 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -581,10 +581,6 @@ bool DRW_shgroup_is_empty(DRWShadingGroup *shgroup); DRWPass *DRW_pass_create(const char *name, DRWState state); DRWPass *DRW_pass_create_instance(const char *name, DRWPass *original, DRWState state); void DRW_pass_link(DRWPass *first, DRWPass *second); -/* TODO Replace with passes inheritance. */ -void DRW_pass_state_set(DRWPass *pass, DRWState state); -void DRW_pass_state_add(DRWPass *pass, DRWState state); -void DRW_pass_state_remove(DRWPass *pass, DRWState state); void DRW_pass_foreach_shgroup(DRWPass *pass, void (*callback)(void *userData, DRWShadingGroup *shgroup), void *userData); diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index ca5d5170262..7fe3bc0f071 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -1947,7 +1947,7 @@ DRWPass *DRW_pass_create(const char *name, DRWState state) { DRWPass *pass = BLI_memblock_alloc(DST.vmempool->passes); pass->state = state | DRW_STATE_PROGRAM_POINT_SIZE; - if (((G.debug_value > 20) && (G.debug_value < 30)) || (G.debug & G_DEBUG)) { + if (G.debug & G_DEBUG_GPU) { BLI_strncpy(pass->name, name, MAX_PASS_NAME); } @@ -1994,21 +1994,6 @@ bool DRW_pass_is_empty(DRWPass *pass) return true; } -void DRW_pass_state_set(DRWPass *pass, DRWState state) -{ - pass->state = state | DRW_STATE_PROGRAM_POINT_SIZE; -} - -void DRW_pass_state_add(DRWPass *pass, DRWState state) -{ - pass->state |= state; -} - -void DRW_pass_state_remove(DRWPass *pass, DRWState state) -{ - pass->state &= ~state; -} - void DRW_pass_foreach_shgroup(DRWPass *pass, void (*callback)(void *userData, DRWShadingGroup *shgrp), void *userData) -- cgit v1.2.3 From 0f61b27e93f1eedff4217b2dc8d11c774d4d0f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 14 Sep 2020 01:08:35 +0200 Subject: DRW: Fix wrong use of GPU_blend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the enum instead of a boolean. Exibit n°5512 where typecast warning would have find the error. --- source/blender/draw/intern/draw_view.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/intern/draw_view.c b/source/blender/draw/intern/draw_view.c index 3033cf70b29..86f19c6cfd7 100644 --- a/source/blender/draw/intern/draw_view.c +++ b/source/blender/draw/intern/draw_view.c @@ -231,7 +231,7 @@ void DRW_draw_cursor_2d(void) /* Draw nice Anti Aliased cursor. */ GPU_line_width(1.0f); - GPU_blend(true); + GPU_blend(GPU_BLEND_ALPHA); GPU_line_smooth(true); /* Draw lines */ @@ -248,7 +248,7 @@ void DRW_draw_cursor_2d(void) GPU_batch_draw(cursor_batch); - GPU_blend(false); + GPU_blend(GPU_BLEND_NONE); GPU_line_smooth(false); GPU_matrix_pop(); GPU_matrix_projection_set(original_proj); -- cgit v1.2.3 From 9bc1d7a6deea8f401d727e80f42a04482c29bcef Mon Sep 17 00:00:00 2001 From: Manuel Castilla Date: Mon, 14 Sep 2020 09:13:29 +0200 Subject: Fix T72584: Hiding a collection don't hide a child object in viewport when in Local View Hiding a collection should hide all children objects even when we are in Local view with one of them. Note from reviewer: We are doing this already for local collections. So may as well do it when hiding the collections for the entire view layer. Developer details: In function "BKE_object_is_visible_in_viewport" object flag BASE_VISIBLE_VIEWLAYER wasn't being checked when we were in Local view, It's now changed so that it's checked even if we are in Local view. And this function was called by some viewport draw functions to check if it should draw an object or not. Maniphest Tasks: T72584 Differential Revision: https://developer.blender.org/D7894 --- source/blender/blenkernel/intern/layer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c index 1ad34fde0fa..dd8a22e10da 100644 --- a/source/blender/blenkernel/intern/layer.c +++ b/source/blender/blenkernel/intern/layer.c @@ -1111,8 +1111,8 @@ bool BKE_object_is_visible_in_viewport(const View3D *v3d, const struct Object *o return false; } - /* If not using local view or local collection the object may still be in a hidden collection. */ - if (((v3d->localvd) == NULL) && ((v3d->flag & V3D_LOCAL_COLLECTIONS) == 0)) { + /* If not using local collection the object may still be in a hidden collection. */ + if ((v3d->flag & V3D_LOCAL_COLLECTIONS) == 0) { return (ob->base_flag & BASE_VISIBLE_VIEWLAYER) != 0; } -- cgit v1.2.3 From c6210f9bacdb3d8b7ba05f29136ac6f9c8f643d7 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 14 Sep 2020 09:46:30 +0200 Subject: DrawManager: Resolve Assert in Image Engine Tiled texture uses different texture structure than normal textures. Normally we add dummy textures and use them, but I found it cleaner to have 2 shaders and use the correct shader. --- source/blender/draw/engines/image/image_engine.c | 8 +++--- source/blender/draw/engines/image/image_private.h | 2 +- source/blender/draw/engines/image/image_shader.c | 17 +++++++----- .../engines/image/shaders/engine_image_frag.glsl | 31 ++++++++++++---------- source/blender/draw/tests/shaders_test.cc | 3 ++- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/source/blender/draw/engines/image/image_engine.c b/source/blender/draw/engines/image/image_engine.c index 9f1278b473b..90bfb38dadf 100644 --- a/source/blender/draw/engines/image/image_engine.c +++ b/source/blender/draw/engines/image/image_engine.c @@ -42,8 +42,7 @@ #define SIMA_DRAW_FLAG_APPLY_ALPHA (1 << 1) #define SIMA_DRAW_FLAG_SHUFFLING (1 << 2) #define SIMA_DRAW_FLAG_DEPTH (1 << 3) -#define SIMA_DRAW_FLAG_TILED (1 << 4) -#define SIMA_DRAW_FLAG_DO_REPEAT (1 << 5) +#define SIMA_DRAW_FLAG_DO_REPEAT (1 << 4) static void image_cache_image_add(DRWShadingGroup *grp, Image *image) { @@ -179,10 +178,9 @@ static void image_cache_image(IMAGE_Data *vedata, Image *image, ImageUser *iuser draw_flags |= SIMA_DRAW_FLAG_APPLY_ALPHA; } - GPUShader *shader = IMAGE_shader_image_get(); + GPUShader *shader = IMAGE_shader_image_get(is_tiled_texture); DRWShadingGroup *shgrp = DRW_shgroup_create(shader, psl->image_pass); - if (tex_tile_data != NULL) { - draw_flags |= SIMA_DRAW_FLAG_TILED; + if (is_tiled_texture) { DRW_shgroup_uniform_texture_ex(shgrp, "imageTileArray", pd->texture, state); DRW_shgroup_uniform_texture(shgrp, "imageTileData", tex_tile_data); } diff --git a/source/blender/draw/engines/image/image_private.h b/source/blender/draw/engines/image/image_private.h index defe5fd5bbb..312a05e0b3b 100644 --- a/source/blender/draw/engines/image/image_private.h +++ b/source/blender/draw/engines/image/image_private.h @@ -60,7 +60,7 @@ typedef struct IMAGE_Data { } IMAGE_Data; /* image_shader.c */ -GPUShader *IMAGE_shader_image_get(void); +GPUShader *IMAGE_shader_image_get(bool is_tiled_image); void IMAGE_shader_library_ensure(void); void IMAGE_shader_free(void); diff --git a/source/blender/draw/engines/image/image_shader.c b/source/blender/draw/engines/image/image_shader.c index 433c79e20cf..0e0c432c32f 100644 --- a/source/blender/draw/engines/image/image_shader.c +++ b/source/blender/draw/engines/image/image_shader.c @@ -37,7 +37,7 @@ extern char datatoc_engine_image_frag_glsl[]; extern char datatoc_engine_image_vert_glsl[]; typedef struct IMAGE_Shaders { - GPUShader *image_sh; + GPUShader *image_sh[2]; } IMAGE_Shaders; static struct { @@ -56,14 +56,19 @@ void IMAGE_shader_library_ensure(void) } } -GPUShader *IMAGE_shader_image_get(void) +GPUShader *IMAGE_shader_image_get(bool is_tiled_image) { + const int index = is_tiled_image ? 1 : 0; IMAGE_Shaders *sh_data = &e_data.shaders; - if (!sh_data->image_sh) { - sh_data->image_sh = DRW_shader_create_with_shaderlib( - datatoc_engine_image_vert_glsl, NULL, datatoc_engine_image_frag_glsl, e_data.lib, NULL); + if (!sh_data->image_sh[index]) { + sh_data->image_sh[index] = DRW_shader_create_with_shaderlib( + datatoc_engine_image_vert_glsl, + NULL, + datatoc_engine_image_frag_glsl, + e_data.lib, + is_tiled_image ? "#define TILED_IMAGE\n" : NULL); } - return sh_data->image_sh; + return sh_data->image_sh[index]; } void IMAGE_shader_free(void) diff --git a/source/blender/draw/engines/image/shaders/engine_image_frag.glsl b/source/blender/draw/engines/image/shaders/engine_image_frag.glsl index 5c5d9362dfc..a79f4915c4d 100644 --- a/source/blender/draw/engines/image/shaders/engine_image_frag.glsl +++ b/source/blender/draw/engines/image/shaders/engine_image_frag.glsl @@ -5,12 +5,14 @@ #define SIMA_DRAW_FLAG_APPLY_ALPHA (1 << 1) #define SIMA_DRAW_FLAG_SHUFFLING (1 << 2) #define SIMA_DRAW_FLAG_DEPTH (1 << 3) -#define SIMA_DRAW_FLAG_TILED (1 << 4) -#define SIMA_DRAW_FLAG_DO_REPEAT (1 << 5) +#define SIMA_DRAW_FLAG_DO_REPEAT (1 << 4) +#ifdef TILED_IMAGE uniform sampler2DArray imageTileArray; uniform sampler1DArray imageTileData; +#else uniform sampler2D imageTexture; +#endif uniform bool imgPremultiplied; uniform int drawFlags; @@ -25,6 +27,7 @@ in vec2 uvs; out vec4 fragColor; +#ifdef TILED_IMAGE /* TODO(fclem) deduplicate code. */ bool node_tex_tile_lookup(inout vec3 co, sampler2DArray ima, sampler1DArray map) { @@ -50,26 +53,26 @@ bool node_tex_tile_lookup(inout vec3 co, sampler2DArray ima, sampler1DArray map) co = vec3(((co.xy - tile_pos) * tile_info.zw) + tile_info.xy, tile_layer); return true; } +#endif void main() { vec4 tex_color; /* Read texture */ - if ((drawFlags & SIMA_DRAW_FLAG_TILED) != 0) { - vec3 co = vec3(uvs, 0.0); - if (node_tex_tile_lookup(co, imageTileArray, imageTileData)) { - tex_color = texture(imageTileArray, co); - } - else { - tex_color = vec4(1.0, 0.0, 1.0, 1.0); - } +#ifdef TILED_IMAGE + vec3 co = vec3(uvs, 0.0); + if (node_tex_tile_lookup(co, imageTileArray, imageTileData)) { + tex_color = texture(imageTileArray, co); } else { - vec2 uvs_clamped = ((drawFlags & SIMA_DRAW_FLAG_DO_REPEAT) != 0) ? - fract(uvs) : - clamp(uvs, vec2(0.0), vec2(1.0)); - tex_color = texture(imageTexture, uvs_clamped); + tex_color = vec4(1.0, 0.0, 1.0, 1.0); } +#else + vec2 uvs_clamped = ((drawFlags & SIMA_DRAW_FLAG_DO_REPEAT) != 0) ? + fract(uvs) : + clamp(uvs, vec2(0.0), vec2(1.0)); + tex_color = texture(imageTexture, uvs_clamped); +#endif if ((drawFlags & SIMA_DRAW_FLAG_APPLY_ALPHA) != 0) { if (!imgPremultiplied && tex_color.a != 0.0 && tex_color.a != 1.0) { diff --git a/source/blender/draw/tests/shaders_test.cc b/source/blender/draw/tests/shaders_test.cc index 8feccc9588e..f99fa04ce75 100644 --- a/source/blender/draw/tests/shaders_test.cc +++ b/source/blender/draw/tests/shaders_test.cc @@ -156,7 +156,8 @@ TEST_F(DrawTest, image_glsl_shaders) { IMAGE_shader_library_ensure(); - EXPECT_NE(IMAGE_shader_image_get(), nullptr); + EXPECT_NE(IMAGE_shader_image_get(false), nullptr); + EXPECT_NE(IMAGE_shader_image_get(true), nullptr); IMAGE_shader_free(); } -- cgit v1.2.3 From c207f7c22e1439e0b285fba5d2c072bdae23f981 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 14 Sep 2020 16:11:13 +1000 Subject: Cleanup: use doxy sections for imbuf --- source/blender/imbuf/intern/cache.c | 36 ++++++++-- source/blender/imbuf/intern/colormanagement.c | 99 ++++++++++++++++++++++----- source/blender/imbuf/intern/divers.c | 30 ++++++-- source/blender/imbuf/intern/imageprocess.c | 37 ++++++++-- source/blender/imbuf/intern/stereoimbuf.c | 36 ++++++++-- source/blender/imbuf/intern/tiff.c | 62 +++++++++++------ 6 files changed, 242 insertions(+), 58 deletions(-) diff --git a/source/blender/imbuf/intern/cache.c b/source/blender/imbuf/intern/cache.c index 23ce9bd7818..02d1fe3710a 100644 --- a/source/blender/imbuf/intern/cache.c +++ b/source/blender/imbuf/intern/cache.c @@ -32,6 +32,10 @@ #include "imbuf.h" +/* -------------------------------------------------------------------- */ +/** \name Local Structs + * \{ */ + /* We use a two level cache here. A per-thread cache with limited number of * tiles. This can be accessed without locking and so is hoped to lead to most * tile access being lock-free. The global cache is shared between all threads @@ -85,7 +89,11 @@ typedef struct ImGlobalTileCache { static ImGlobalTileCache GLOBAL_CACHE; -/***************************** Hash Functions ********************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Hash Functions + * \{ */ static unsigned int imb_global_tile_hash(const void *gtile_p) { @@ -117,7 +125,11 @@ static bool imb_thread_tile_cmp(const void *a_p, const void *b_p) return ((a->ibuf != b->ibuf) || (a->tx != b->tx) || (a->ty != b->ty)); } -/******************************** Load/Unload ********************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Load/Unload + * \{ */ static void imb_global_cache_tile_load(ImGlobalTile *gtile) { @@ -167,7 +179,11 @@ void imb_tile_cache_tile_free(ImBuf *ibuf, int tx, int ty) BLI_mutex_unlock(&GLOBAL_CACHE.mutex); } -/******************************* Init/Exit ***********************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Init/Exit + * \{ */ static void imb_thread_cache_init(ImThreadTileCache *cache) { @@ -265,7 +281,11 @@ void IMB_tile_cache_params(int totthread, int maxmem) BLI_mutex_init(&GLOBAL_CACHE.mutex); } -/***************************** Global Cache **********************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Global Cache + * \{ */ static ImGlobalTile *imb_global_cache_get_tile(ImBuf *ibuf, int tx, @@ -353,7 +373,11 @@ static ImGlobalTile *imb_global_cache_get_tile(ImBuf *ibuf, return gtile; } -/***************************** Per-Thread Cache ******************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Per-Thread Cache + * \{ */ static unsigned int *imb_thread_cache_get_tile(ImThreadTileCache *cache, ImBuf *ibuf, @@ -465,3 +489,5 @@ void IMB_tiles_to_rect(ImBuf *ibuf) } } } + +/** \} */ diff --git a/source/blender/imbuf/intern/colormanagement.c b/source/blender/imbuf/intern/colormanagement.c index 6dd4d14cbc7..00dbd539410 100644 --- a/source/blender/imbuf/intern/colormanagement.c +++ b/source/blender/imbuf/intern/colormanagement.c @@ -60,7 +60,9 @@ #include -/*********************** Global declarations *************************/ +/* -------------------------------------------------------------------- */ +/** \name Global declarations + * \{ */ #define DISPLAY_BUFFER_CHANNELS 4 @@ -135,9 +137,14 @@ static struct global_color_picking_state { bool failed; } global_color_picking_state = {NULL}; -/*********************** Color managed cache *************************/ +/** \} */ -/* Cache Implementation Notes +/* -------------------------------------------------------------------- */ +/** \name Color Managed Cache + * \{ */ + +/** + * Cache Implementation Notes * ========================== * * All color management cache stuff is stored in two properties of @@ -459,7 +466,11 @@ static void colormanage_cache_handle_release(void *cache_handle) IMB_freeImBuf(cache_ibuf); } -/*********************** Initialization / De-initialization *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Initialization / De-initialization + * \{ */ static void colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config, char *colorspace_name, @@ -749,7 +760,11 @@ void colormanagement_exit(void) colormanage_free_config(); } -/*********************** Internal functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal functions + * \{ */ static bool colormanage_compatible_look(ColorManagedLook *look, const char *view_name) { @@ -1119,7 +1134,11 @@ void colormanage_imbuf_make_linear(ImBuf *ibuf, const char *from_colorspace) } } -/*********************** Generic functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Functions + * \{ */ static void colormanage_check_display_settings(ColorManagedDisplaySettings *display_settings, const char *what, @@ -1459,7 +1478,11 @@ const float *IMB_colormangement_get_xyz_to_rgb() return &imbuf_xyz_to_rgb[0][0]; } -/*********************** Threaded display buffer transform routines *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Threaded Display Buffer Transform Routines + * \{ */ typedef struct DisplayBufferThread { ColormanageProcessor *cm_processor; @@ -1827,7 +1850,11 @@ static void colormanage_display_buffer_process(ImBuf *ibuf, ibuf, NULL, display_buffer, view_settings, display_settings); } -/*********************** Threaded processor transform routines *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Threaded Processor Transform Routines + * \{ */ typedef struct ProcessorTransformThread { ColormanageProcessor *cm_processor; @@ -1955,7 +1982,11 @@ static void processor_transform_apply_threaded(unsigned char *byte_buffer, do_processor_transform_thread); } -/*********************** Color space transformation functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Color Space Transformation Functions + * \{ */ /* Convert the whole buffer from specified by name color space to another - * internal implementation. */ @@ -2667,7 +2698,11 @@ void IMB_colormanagement_buffer_make_display_space( IMB_colormanagement_processor_free(cm_processor); } -/*********************** Public display buffers interfaces *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Display Buffers Interfaces + * \{ */ /* acquire display buffer for given image buffer using specified view and display settings */ unsigned char *IMB_display_buffer_acquire(ImBuf *ibuf, @@ -2825,7 +2860,11 @@ void IMB_display_buffer_release(void *cache_handle) } } -/*********************** Display functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Display Functions + * \{ */ const char *colormanage_display_get_default_name(void) { @@ -2945,7 +2984,11 @@ const char *IMB_colormanagement_display_get_default_view_transform_name( return colormanage_view_get_default_name(display); } -/*********************** View functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Functions + * \{ */ const char *colormanage_view_get_default_name(const ColorManagedDisplay *display) { @@ -3059,7 +3102,11 @@ const char *IMB_colormanagement_view_get_default_name(const char *display_name) return NULL; } -/*********************** Color space functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Color Space Functions + * \{ */ static void colormanage_description_strip(char *description) { @@ -3203,7 +3250,11 @@ void IMB_colormanagement_colorspace_from_ibuf_ftype( } } -/*********************** Looks functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Looks Functions + * \{ */ ColorManagedLook *colormanage_look_add(const char *name, const char *process_space, bool is_noop) { @@ -3276,7 +3327,11 @@ const char *IMB_colormanagement_look_get_indexed_name(int index) return NULL; } -/*********************** RNA helper functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name RNA Helper Functions + * \{ */ void IMB_colormanagement_display_items_add(EnumPropertyItem **items, int *totitem) { @@ -3372,7 +3427,11 @@ void IMB_colormanagement_colorspace_items_add(EnumPropertyItem **items, int *tot } } -/*********************** Partial display buffer update *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Partial Display Buffer Update + * \{ */ /* * Partial display update is supposed to be used by such areas as @@ -3750,7 +3809,11 @@ void IMB_partial_display_buffer_update_delayed(ImBuf *ibuf, int xmin, int ymin, } } -/*********************** Pixel processor functions *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pixel Processor Functions + * \{ */ ColormanageProcessor *IMB_colormanagement_display_processor_new( const ColorManagedViewSettings *view_settings, @@ -4172,3 +4235,5 @@ void IMB_colormanagement_finish_glsl_draw(void) OCIO_finishGLSLDraw(global_glsl_state.ocio_glsl_state); } } + +/** \} */ diff --git a/source/blender/imbuf/intern/divers.c b/source/blender/imbuf/intern/divers.c index 798849f2dd4..5f580449e12 100644 --- a/source/blender/imbuf/intern/divers.c +++ b/source/blender/imbuf/intern/divers.c @@ -35,7 +35,9 @@ #include "MEM_guardedalloc.h" -/************************* Floyd-Steinberg dithering *************************/ +/* -------------------------------------------------------------------- */ +/** \name Floyd-Steinberg dithering + * \{ */ typedef struct DitherContext { float dither; @@ -56,7 +58,11 @@ static void clear_dither_context(DitherContext *di) MEM_freeN(di); } -/************************* Generic Buffer Conversion *************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Buffer Conversion + * \{ */ MINLINE void ushort_to_byte_v4(uchar b[4], const unsigned short us[4]) { @@ -705,7 +711,11 @@ void IMB_buffer_byte_from_byte(uchar *rect_to, } } -/****************************** ImBuf Conversion *****************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ImBuf Conversion + * \{ */ void IMB_rect_from_float(ImBuf *ibuf) { @@ -822,7 +832,11 @@ void IMB_float_from_rect(ImBuf *ibuf) } } -/**************************** Color to Grayscale *****************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Color to Grayscale + * \{ */ /* no profile conversion */ void IMB_color_to_bw(ImBuf *ibuf) @@ -864,7 +878,11 @@ void IMB_buffer_float_premultiply(float *buf, int width, int height) } } -/**************************** alter saturation *****************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Alter Saturation + * \{ */ void IMB_saturation(ImBuf *ibuf, float sat) { @@ -890,3 +908,5 @@ void IMB_saturation(ImBuf *ibuf, float sat) } } } + +/** \} */ diff --git a/source/blender/imbuf/intern/imageprocess.c b/source/blender/imbuf/intern/imageprocess.c index bcac140f036..6ad69e72b4f 100644 --- a/source/blender/imbuf/intern/imageprocess.c +++ b/source/blender/imbuf/intern/imageprocess.c @@ -76,6 +76,7 @@ void IMB_convert_rgba_to_abgr(struct ImBuf *ibuf) } } } + static void pixel_from_buffer(struct ImBuf *ibuf, unsigned char **outI, float **outF, int x, int y) { @@ -90,7 +91,9 @@ static void pixel_from_buffer(struct ImBuf *ibuf, unsigned char **outI, float ** } } -/* BICUBIC Interpolation */ +/* -------------------------------------------------------------------- */ +/** \name Bi-Cubic Interpolation + * \{ */ void bicubic_interpolation_color( struct ImBuf *in, unsigned char outI[4], float outF[4], float u, float v) @@ -118,7 +121,12 @@ void bicubic_interpolation(ImBuf *in, ImBuf *out, float u, float v, int xout, in bicubic_interpolation_color(in, outI, outF, u, v); } -/* BILINEAR INTERPOLATION */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Bi-Linear Interpolation + * \{ */ + void bilinear_interpolation_color( struct ImBuf *in, unsigned char outI[4], float outF[4], float u, float v) { @@ -224,8 +232,13 @@ void bilinear_interpolation(ImBuf *in, ImBuf *out, float u, float v, int xout, i bilinear_interpolation_color(in, outI, outF, u, v); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Nearest Interpolation + * \{ */ + /* function assumes out to be zero'ed, only does RGBA */ -/* NEAREST INTERPOLATION */ void nearest_interpolation_color( struct ImBuf *in, unsigned char outI[4], float outF[4], float u, float v) { @@ -336,7 +349,9 @@ void nearest_interpolation(ImBuf *in, ImBuf *out, float u, float v, int xout, in nearest_interpolation_color(in, outI, outF, u, v); } -/*********************** Threaded image processing *************************/ +/* -------------------------------------------------------------------- */ +/** \name Threaded Image Processing + * \{ */ static void processor_apply_func(TaskPool *__restrict pool, void *taskdata) { @@ -431,7 +446,11 @@ void IMB_processor_apply_threaded_scanlines(int total_scanlines, BLI_task_pool_free(task_pool); } -/* Alpha-under */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Alpha-under + * \{ */ void IMB_alpha_under_color_float(float *rect_float, int x, int y, float backcol[3]) { @@ -485,6 +504,12 @@ void IMB_alpha_under_color_byte(unsigned char *rect, int x, int y, const float b } } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sample Pixel + * \{ */ + /* Sample pixel of image using NEAREST method. */ void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]) { @@ -500,3 +525,5 @@ void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_r } } } + +/** \} */ diff --git a/source/blender/imbuf/intern/stereoimbuf.c b/source/blender/imbuf/intern/stereoimbuf.c index 247122065de..d3c91b55f22 100644 --- a/source/blender/imbuf/intern/stereoimbuf.c +++ b/source/blender/imbuf/intern/stereoimbuf.c @@ -58,6 +58,10 @@ typedef struct Stereo3DData { bool is_float; } Stereo3DData; +/* -------------------------------------------------------------------- */ +/** \name Local Functions + * \{ */ + static void imb_stereo3d_write_anaglyph(Stereo3DData *s3d, enum eStereo3dAnaglyphType mode) { int x, y; @@ -508,7 +512,11 @@ static void imb_stereo3d_write_topbottom(Stereo3DData *s3d) } } -/**************************** dimension utils ****************************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dimension Utils + * \{ */ void IMB_stereo3d_write_dimensions(const char mode, const bool is_squeezed, @@ -566,7 +574,11 @@ void IMB_stereo3d_read_dimensions(const char mode, } } -/**************************** un/squeeze frame ****************************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Un/Squeeze Frame + * \{ */ static void imb_stereo3d_squeeze_ImBuf(ImBuf *ibuf, Stereo3dFormat *s3d, @@ -667,7 +679,11 @@ static void imb_stereo3d_squeeze_rect( IMB_freeImBuf(ibuf); } -/*************************** preparing to call the write functions **************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Preparing To Call The Write Functions + * \{ */ static void imb_stereo3d_data_init(Stereo3DData *s3d_data, const bool is_float, @@ -798,7 +814,11 @@ static void imb_stereo3d_write_doit(Stereo3DData *s3d_data, Stereo3dFormat *s3d) } } -/******************************** reading stereo imbufs **********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reading Stereo ImBuf's + * \{ */ static void imb_stereo3d_read_anaglyph(Stereo3DData *s3d, enum eStereo3dAnaglyphType mode) { @@ -1249,7 +1269,11 @@ static void imb_stereo3d_read_topbottom(Stereo3DData *s3d) } } -/*************************** preparing to call the read functions **************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Preparing To Call The Read Functions + * \{ */ /* reading a stereo encoded ibuf (*left) and generating two ibufs from it (*left and *right) */ void IMB_ImBufFromStereo3d(Stereo3dFormat *s3d, @@ -1351,3 +1375,5 @@ static void imb_stereo3d_read_doit(Stereo3DData *s3d_data, Stereo3dFormat *s3d) break; } } + +/** \} */ diff --git a/source/blender/imbuf/intern/tiff.c b/source/blender/imbuf/intern/tiff.c index 9d5d7f58335..0c235445920 100644 --- a/source/blender/imbuf/intern/tiff.c +++ b/source/blender/imbuf/intern/tiff.c @@ -57,9 +57,10 @@ # include "utfconv.h" #endif -/*********************** - * Local declarations. * - ***********************/ +/* -------------------------------------------------------------------- */ +/** \name Local Declarations + * \{ */ + /* Reading and writing of an in-memory TIFF file. */ static tsize_t imb_tiff_ReadProc(thandle_t handle, tdata_t data, tsize_t n); static tsize_t imb_tiff_WriteProc(thandle_t handle, tdata_t data, tsize_t n); @@ -69,17 +70,22 @@ static toff_t imb_tiff_SizeProc(thandle_t handle); static int imb_tiff_DummyMapProc(thandle_t fd, tdata_t *pbase, toff_t *psize); static void imb_tiff_DummyUnmapProc(thandle_t fd, tdata_t base, toff_t size); -/* Structure for in-memory TIFF file. */ +/** Structure for in-memory TIFF file. */ typedef struct ImbTIFFMemFile { - const unsigned char *mem; /* Location of first byte of TIFF file. */ - toff_t offset; /* Current offset within the file. */ - tsize_t size; /* Size of the TIFF file. */ + /** Location of first byte of TIFF file. */ + const unsigned char *mem; + /** Current offset within the file. */ + toff_t offset; + /** Size of the TIFF file. */ + tsize_t size; } ImbTIFFMemFile; #define IMB_TIFF_GET_MEMFILE(x) ((ImbTIFFMemFile *)(x)) -/***************************** - * Function implementations. * - *****************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Function Implementations + * \{ */ static void imb_tiff_DummyUnmapProc( thandle_t fd, @@ -111,8 +117,8 @@ static int imb_tiff_DummyMapProc( * Reads data from an in-memory TIFF file. * * \param handle: Handle of the TIFF file (pointer to #ImbTIFFMemFile). - * \param data: Buffer to contain data (treat as (void *)). - * \param n: Number of bytes to read. + * \param data: Buffer to contain data (treat as (void *)). + * \param n: Number of bytes to read. * * \return: Number of bytes actually read. * 0 = EOF. @@ -174,8 +180,8 @@ static tsize_t imb_tiff_WriteProc(thandle_t handle, tdata_t data, tsize_t n) /** * Seeks to a new location in an in-memory TIFF file. * - * \param handle: Handle of the TIFF file (pointer to ImbTIFFMemFile). - * \param ofs: Offset value (interpreted according to whence below). + * \param handle: Handle of the TIFF file (pointer to #ImbTIFFMemFile). + * \param ofs: Offset value (interpreted according to whence below). * \param whence: This can be one of three values: * SEEK_SET - The offset is set to ofs bytes. * SEEK_CUR - The offset is set to its current location plus ofs bytes. @@ -226,7 +232,7 @@ static toff_t imb_tiff_SeekProc(thandle_t handle, toff_t ofs, int whence) * are made to access the file after that point. However, no such * attempts should ever be made (in theory). * - * \param handle: Handle of the TIFF file (pointer to ImbTIFFMemFile). + * \param handle: Handle of the TIFF file (pointer to #ImbTIFFMemFile). * * \return: 0 */ @@ -288,6 +294,12 @@ static TIFF *imb_tiff_client_open(ImbTIFFMemFile *memFile, const unsigned char * imb_tiff_DummyUnmapProc); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Load TIFF + * \{ */ + /** * Checks whether a given memory buffer contains a TIFF file. * @@ -539,10 +551,10 @@ void imb_inittiff(void) /** * Loads a TIFF file. - * \param mem: Memory containing the TIFF file. - * \param size: Size of the mem buffer. + * \param mem: Memory containing the TIFF file. + * \param size: Size of the mem buffer. * \param flags: If flags has IB_test set then the file is not actually loaded, - * but all other operations take place. + * but all other operations take place. * * \return: A newly allocated ImBuf structure if successful, otherwise NULL. */ @@ -726,17 +738,23 @@ void imb_loadtiletiff( TIFFClose(image); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Save TIFF + * \{ */ + /** * Saves a TIFF file. * - * ImBuf structures with 1, 3 or 4 bytes per pixel (GRAY, RGB, RGBA + * #ImBuf structures with 1, 3 or 4 bytes per pixel (GRAY, RGB, RGBA * respectively) are accepted, and interpreted correctly. Note that the TIFF * convention is to use pre-multiplied alpha, which can be achieved within * Blender by setting "Premul" alpha handling. Other alpha conventions are * not strictly correct, but are permitted anyhow. * - * \param ibuf: Image buffer. - * \param name: Name of the TIFF file to create. + * \param ibuf: Image buffer. + * \param name: Name of the TIFF file to create. * \param flags: Currently largely ignored. * * \return: 1 if the function is successful, 0 on failure. @@ -958,3 +976,5 @@ int imb_savetiff(ImBuf *ibuf, const char *filepath, int flags) } return 1; } + +/** \} */ -- cgit v1.2.3 From 14b0f203744027451533f563c5d7373b73e070e0 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 14 Sep 2020 17:50:01 +1000 Subject: Fix T80694: Crash reloading scripts from the Python console Running `bpy.ops.script.reload()` from Python was crashing since the operator being called was it's self freed. Change the reload operator to defer execution - as supporting re-registration during execution is quite involved for a corner-case. --- release/scripts/modules/bpy/utils/__init__.py | 2 ++ source/blender/editors/space_script/script_edit.c | 35 +++++++++++++++++------ source/blender/makesrna/intern/rna_wm_api.c | 12 ++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index 8a67a598ccd..4e0220ee1cc 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -283,6 +283,8 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): del _initialize if reload_scripts: + _bpy.context.window_manager.tag_script_reload() + import gc print("gc.collect() -> %d" % gc.collect()) diff --git a/source/blender/editors/space_script/script_edit.c b/source/blender/editors/space_script/script_edit.c index f739bfe367e..56f99d31cf1 100644 --- a/source/blender/editors/space_script/script_edit.c +++ b/source/blender/editors/space_script/script_edit.c @@ -115,15 +115,32 @@ static int script_reload_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - WM_script_tag_reload(); - - /* TODO, this crashes on netrender and keying sets, need to look into why - * disable for now unless running in debug mode */ - WM_cursor_wait(1); - BPY_run_string_eval( - C, (const char *[]){"bpy", NULL}, "bpy.utils.load_scripts(reload_scripts=True)"); - WM_cursor_wait(0); - WM_event_add_notifier(C, NC_WINDOW, NULL); + /* TODO(campbell): this crashes on netrender and keying sets, need to look into why + * disable for now unless running in debug mode. */ + + /* It would be nice if we could detect when this is called from the Python + * only postponing in that case, for now always do it. */ + if (true) { + /* Postpone when called from Python so this can be called from an operator + * that might be re-registered, crashing Blender when we try to read from the + * freed operator type which, see T80694. */ + BPY_run_string_exec(C, + (const char *[]){"bpy", NULL}, + "def fn():\n" + " bpy.utils.load_scripts(reload_scripts=True)\n" + " return None\n" + "bpy.app.timers.register(fn)"); + } + else { + WM_cursor_wait(true); + BPY_run_string_eval( + C, (const char *[]){"bpy", NULL}, "bpy.utils.load_scripts(reload_scripts=True)"); + WM_cursor_wait(false); + } + + /* Note that #WM_script_tag_reload is called from `bpy.utils.load_scripts`, + * any additional updates required by this operator should go there. */ + return OPERATOR_FINISHED; #else UNUSED_VARS(C, op); diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c index ee7ff472c12..8d7a3cddbc5 100644 --- a/source/blender/makesrna/intern/rna_wm_api.c +++ b/source/blender/makesrna/intern/rna_wm_api.c @@ -558,6 +558,12 @@ static void rna_WindowManager_print_undo_steps(wmWindowManager *wm) BKE_undosys_print(wm->undo_stack); } +static void rna_WindowManager_tag_script_reload(void) +{ + WM_script_tag_reload(); + WM_main_add_notifier(NC_WINDOW, NULL); +} + static PointerRNA rna_WindoManager_operator_properties_last(const char *idname) { wmOperatorType *ot = WM_operatortype_find(idname, true); @@ -913,6 +919,12 @@ void RNA_api_wm(StructRNA *srna) RNA_def_function(srna, "print_undo_steps", "rna_WindowManager_print_undo_steps"); + /* Used by (#SCRIPT_OT_reload). */ + func = RNA_def_function(srna, "tag_script_reload", "rna_WindowManager_tag_script_reload"); + RNA_def_function_ui_description( + func, "Tag for refreshing the interface after scripts have been reloaded"); + RNA_def_function_flag(func, FUNC_NO_SELF); + parm = RNA_def_property(srna, "is_interface_locked", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( parm, -- cgit v1.2.3 From f28adaef520c85ab1ea8bc1fb149045a38967431 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 14 Sep 2020 18:25:54 +1000 Subject: Fix T80747: New vgroups on unsupported object types fails silently Raise an exceptions when adding vertex groups to object types that don't support it. --- source/blender/makesrna/intern/rna_object.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index c7f625e2fa5..77200ce7eda 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -1828,8 +1828,19 @@ static void rna_Object_boundbox_get(PointerRNA *ptr, float *values) } } -static bDeformGroup *rna_Object_vgroup_new(Object *ob, Main *bmain, const char *name) -{ +static bDeformGroup *rna_Object_vgroup_new(Object *ob, + Main *bmain, + ReportList *reports, + const char *name) +{ + if (!OB_TYPE_SUPPORT_VGROUP(ob->type)) { + const char *ob_type_name = "Unknown"; + RNA_enum_name_from_value(rna_enum_object_type_items, ob->type, &ob_type_name); + BKE_reportf( + reports, RPT_ERROR, "VertexGroups.new(): is not supported for '%s' objects", ob_type_name); + return NULL; + } + bDeformGroup *defgroup = BKE_object_defgroup_add_name(ob, name); DEG_relations_tag_update(bmain); @@ -2489,7 +2500,7 @@ static void rna_def_object_vertex_groups(BlenderRNA *brna, PropertyRNA *cprop) /* vertex groups */ /* add_vertex_group */ func = RNA_def_function(srna, "new", "rna_Object_vgroup_new"); - RNA_def_function_flag(func, FUNC_USE_MAIN); + RNA_def_function_flag(func, FUNC_USE_MAIN | FUNC_USE_REPORTS); RNA_def_function_ui_description(func, "Add vertex group to object"); RNA_def_string(func, "name", "Group", 0, "", "Vertex group name"); /* optional */ parm = RNA_def_pointer(func, "group", "VertexGroup", "", "New vertex group"); -- cgit v1.2.3 From ec42daf63020010f98ef2ff1faed2fa586a68df9 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 14 Sep 2020 10:28:31 +0200 Subject: Fix T80457: Library Override - Custom Property to Drive Child Particles results in Crash. RNA diffing code was not dealing properly valid NULL PointerRNA (like the empty texture slots of a ParticleSettings e.g., which were cause of crash in that report). To be backported to 2.90 and 2.83. --- source/blender/makesrna/intern/rna_rna.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/blender/makesrna/intern/rna_rna.c b/source/blender/makesrna/intern/rna_rna.c index 7134b085fe7..9aef0b5a1cb 100644 --- a/source/blender/makesrna/intern/rna_rna.c +++ b/source/blender/makesrna/intern/rna_rna.c @@ -1188,9 +1188,12 @@ static bool rna_property_override_diff_propptr_validate_diffing(PointerRNA *prop * This helps a lot in library override case, especially to detect inserted items in collections. */ if (!no_prop_name && (is_valid_for_diffing || do_force_name)) { - PropertyRNA *nameprop_a = RNA_struct_name_property(propptr_a->type); - PropertyRNA *nameprop_b = (propptr_b != NULL) ? RNA_struct_name_property(propptr_b->type) : - NULL; + PropertyRNA *nameprop_a = (propptr_a->type != NULL) ? + RNA_struct_name_property(propptr_a->type) : + NULL; + PropertyRNA *nameprop_b = (propptr_b != NULL && propptr_b->type != NULL) ? + RNA_struct_name_property(propptr_b->type) : + NULL; int propname_a_len = 0, propname_b_len = 0; char *propname_a = NULL; -- cgit v1.2.3 From 211d213160534cd62de19743e984ca481f0e2c3e Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 14 Sep 2020 11:02:52 +0200 Subject: API doc: Gotcha's: Add section about abusing RNA properties callbacks. Especially with new undo/redo it is even less recommended to perform complex operations in those callbacks, they should remain as fast and localized as possible. Also updated the section about undo/redo a bit. --- doc/python_api/rst/info_gotcha.rst | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/doc/python_api/rst/info_gotcha.rst b/doc/python_api/rst/info_gotcha.rst index 0713ffa54a1..eb5cc143a2c 100644 --- a/doc/python_api/rst/info_gotcha.rst +++ b/doc/python_api/rst/info_gotcha.rst @@ -677,7 +677,8 @@ Here are some general hints to avoid running into these problems: Undo/Redo --------- -Undo invalidates all :class:`bpy.types.ID` instances (Object, Scene, Mesh, Light, etc.). +For safety, you should assume that undo and redo always invalidates all :class:`bpy.types.ID` +instances (Object, Scene, Mesh, Light, etc.), as weel obviously as all of their sub-data. This example shows how you can tell undo changes the memory locations: @@ -686,7 +687,7 @@ This example shows how you can tell undo changes the memory locations: >>> hash(bpy.context.object) -9223372036849950810 -Move the active object, then undo: +Delete the active object, then undo: >>> hash(bpy.context.object) -9223372036849951740 @@ -695,6 +696,16 @@ As suggested above, simply not holding references to data when Blender is used interactively by the user is the only way to make sure that the script doesn't become unstable. +.. note:: + + Modern undo/redo system does not systematically invalidate all pointers anymore. + Some data (in fact, most data, in typical cases), which were detected as unchanged for a + particular history step, may remain unchanged and hence their pointers may remain valid. + + Be aware that if you want to take advantage of this behavior for some reason, there is no + guarantee of any kind that it will be safe and consistent. Use it at your own risk. + + Undo & Library Data ^^^^^^^^^^^^^^^^^^^ @@ -712,6 +723,17 @@ So it's best to consider modifying library data an advanced usage of the API and only to use it when you know what you're doing. +Abusing RNA property callbacks +------------------------------ + +Python-defined RNA properties can have custom callbacks. Trying to perform complex operations +from there, like calling an operator, may work, but is not officialy recommended nor supported. + +Main reason is that those callback should be very fast, but additionally, it may for example +create issues with undo/redo system (most operators store an history step, and editing an RNA +property does so as well), trigger infinite update loops, and so on. + + Edit-Mode / Memory Access ------------------------- -- cgit v1.2.3 From 28c203257984ecee1c2517a0985b6c073403743d Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 14 Sep 2020 10:45:20 +0200 Subject: Fix T80705: Single Image Texture Painting Crash Regression introduced by {D8234}; GPU textures can be requested without an image user. --- source/blender/blenkernel/intern/image_gpu.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c index f37e038e69e..8e2e3fd621c 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.c @@ -272,10 +272,14 @@ static GPUTexture *image_get_gpu_texture(Image *ima, * context and might as well ensure we have as much space free as possible. */ gpu_free_unused_buffers(); - /* Free GPU textures when requesting a different render pass/layer. */ - if (ima->gpu_pass != iuser->pass || ima->gpu_layer != iuser->layer) { - ima->gpu_pass = iuser->pass; - ima->gpu_layer = iuser->layer; + /* Free GPU textures when requesting a different render pass/layer. + * When `iuser` isn't set (texture painting single image mode) we assume that + * the current `pass` and `layer` should be 0. */ + short requested_pass = iuser ? iuser->pass : 0; + short requested_layer = iuser ? iuser->layer : 0; + if (ima->gpu_pass != requested_pass || ima->gpu_layer != requested_layer) { + ima->gpu_pass = requested_pass; + ima->gpu_layer = requested_layer; ima->gpuflag |= IMA_GPU_REFRESH; } -- cgit v1.2.3 From 3ce06a8011eb276dd3447ff1101bb3eecfe9e4d7 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 14 Sep 2020 11:32:45 +0200 Subject: Fix T80643: Library Override: Can't change Armature Layer enabled-ness. Armature properties still had to be made overridable. --- source/blender/makesrna/intern/rna_armature.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/blender/makesrna/intern/rna_armature.c b/source/blender/makesrna/intern/rna_armature.c index 155943b3b8d..c0a46c65969 100644 --- a/source/blender/makesrna/intern/rna_armature.c +++ b/source/blender/makesrna/intern/rna_armature.c @@ -886,6 +886,8 @@ static void rna_def_bone_common(StructRNA *srna, int editbone) } RNA_def_property_update(prop, 0, "rna_Bone_update_renamed"); + RNA_define_lib_overridable(true); + /* flags */ prop = RNA_def_property(srna, "layers", PROP_BOOLEAN, PROP_LAYER_MEMBER); RNA_def_property_boolean_sdna(prop, NULL, "layer", 1); @@ -1116,6 +1118,8 @@ static void rna_def_bone_common(StructRNA *srna, int editbone) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_PTR_NO_OWNERSHIP); RNA_def_property_ui_text( prop, "B-Bone End Handle", "Bone that serves as the end handle for the B-Bone curve"); + + RNA_define_lib_overridable(false); } /* err... bones should not be directly edited (only editbones should be...) */ @@ -1149,6 +1153,8 @@ static void rna_def_bone(BlenderRNA *brna) rna_def_bone_common(srna, 0); rna_def_bone_curved_common(srna, false, false); + RNA_define_lib_overridable(true); + /* XXX should we define this in PoseChannel wrapping code instead? * But PoseChannels directly get some of their flags from here... */ prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE); @@ -1231,6 +1237,8 @@ static void rna_def_bone(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Length", "Length of the bone"); + RNA_define_lib_overridable(false); + RNA_api_bone(srna); } @@ -1461,6 +1469,8 @@ static void rna_def_armature(BlenderRNA *brna) /* Animation Data */ rna_def_animdata_common(srna); + RNA_define_lib_overridable(true); + /* Collections */ prop = RNA_def_property(srna, "bones", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "bonebase", NULL); @@ -1554,6 +1564,8 @@ static void rna_def_armature(BlenderRNA *brna) RNA_def_property_boolean_funcs(prop, "rna_Armature_is_editmode_get", NULL); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Is Editmode", "True when used in editmode"); + + RNA_define_lib_overridable(false); } void RNA_def_armature(BlenderRNA *brna) -- cgit v1.2.3 From 61abd776487eeb383833f95d602e318e59f42395 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 14 Sep 2020 12:01:44 +0200 Subject: Fix T80564: flow particle size is too limiting Reviewers: sebbas Differential Revision: https://developer.blender.org/D8888 --- source/blender/makesrna/intern/rna_fluid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c index 0a58f8af593..2d26317c00b 100644 --- a/source/blender/makesrna/intern/rna_fluid.c +++ b/source/blender/makesrna/intern/rna_fluid.c @@ -2582,7 +2582,7 @@ static void rna_def_fluid_flow_settings(BlenderRNA *brna) RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, "rna_Fluid_flow_reset"); prop = RNA_def_property(srna, "particle_size", PROP_FLOAT, PROP_NONE); - RNA_def_property_range(prop, 0.1, 20.0); + RNA_def_property_range(prop, 0.1, FLT_MAX); RNA_def_property_ui_range(prop, 0.5, 5.0, 0.05, 5); RNA_def_property_ui_text(prop, "Size", "Particle size in simulation cells"); RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, "rna_Fluid_flow_reset"); -- cgit v1.2.3 From b8a25bbd8a400b52de150a08c041e2f6f2a9dce1 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 14 Sep 2020 12:24:13 +0200 Subject: Cleanup: remove unused function The last usage was removed in {rB4eda60c2d82de0d7f7ded8ddf1036aea040e9c0d}. --- .../blender/editors/transform/transform_constraints.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c index 0eaae7f17cd..68bde912fe5 100644 --- a/source/blender/editors/transform/transform_constraints.c +++ b/source/blender/editors/transform/transform_constraints.c @@ -100,24 +100,6 @@ static void constraint_plane_calc(TransInfo *t, float r_plane[4]) r_plane[3] = -dot_v3v3(r_plane, t->center_global); } -static void constraintValuesFinal(TransInfo *t, float vec[3]) -{ - int mode = t->con.mode; - if (mode & CON_APPLY) { - float nval = (t->flag & T_NULL_ONE) ? 1.0f : 0.0f; - - if ((mode & CON_AXIS0) == 0) { - vec[0] = nval; - } - if ((mode & CON_AXIS1) == 0) { - vec[1] = nval; - } - if ((mode & CON_AXIS2) == 0) { - vec[2] = nval; - } - } -} - void constraintNumInput(TransInfo *t, float vec[3]) { int mode = t->con.mode; -- cgit v1.2.3 From ee97add4c404a414addab5cbb6107fac002da77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 11 Sep 2020 14:06:13 +0200 Subject: Alembic export: write custom properties Write custom properties (aka ID properties) to Alembic, to the `.userProperties` compound property. Manifest Task: https://developer.blender.org/T50725 Scalar properties (so single-value/non-array properties) are written as single-element array properties to Alembic. This is also what's done by Houdini and Maya exporters, so it seems to be the standard way of doing things. It also simplifies the implementation. Two-dimensional arrays are flattened by concatenating all the numbers into a single array. This is because ID properties have a limited type system. This means that a 3x3 "matrix" could just as well be a list of three 3D vectors. Alembic has two container properties to store custom data: - `.userProperties`, which is meant for properties that aren't necessarily understood by other software packages, and - `.arbGeomParams`, which can contain the same kind of data as `.userProperties`, but can also specify that these vary per face of a mesh. This property is mostly intended for renderers. Most industry packages write their custom data to `.arbGeomParams`. However, given their goals I feel that `.userProperties` is the more appropriate one for Blender's ID Properties. The code is a bit more involved than I would have liked. An `ABCAbstractWriter` has a `uniqueptr` to its `CustomPropertiesExporter`, but the `CustomPropertiesExporter` also has a pointer back to its owning `ABCAbstractWriter`. It's the latter pointer that I'm not too happy with, but it has a reason. Getting the aforementioned `.userProperties` from the Alembic library will automatically create it if it doesn't exist already. If it's not used to actually add custom properties to, it will crash the Alembic CLI tools (and maybe others too). This is what the pointer back to the `ABCAbstractWriter` is used for: to get the `.userProperties` at the last moment, when it's 100% sure at least one custom property will be written. Differential Revision: https://developer.blender.org/D8869 Reviewed by: sergey, dbystedt --- source/blender/editors/io/io_alembic.c | 9 + source/blender/io/alembic/ABC_alembic.h | 1 + source/blender/io/alembic/CMakeLists.txt | 2 + .../io/alembic/exporter/abc_custom_props.cc | 268 +++++++++++++++++++++ .../blender/io/alembic/exporter/abc_custom_props.h | 96 ++++++++ .../io/alembic/exporter/abc_writer_abstract.cc | 41 ++++ .../io/alembic/exporter/abc_writer_abstract.h | 41 ++++ .../io/alembic/exporter/abc_writer_camera.cc | 5 + .../io/alembic/exporter/abc_writer_camera.h | 1 + .../io/alembic/exporter/abc_writer_curves.cc | 5 + .../io/alembic/exporter/abc_writer_curves.h | 1 + .../blender/io/alembic/exporter/abc_writer_hair.cc | 5 + .../blender/io/alembic/exporter/abc_writer_hair.h | 1 + .../io/alembic/exporter/abc_writer_instance.cc | 10 + .../io/alembic/exporter/abc_writer_instance.h | 2 + .../blender/io/alembic/exporter/abc_writer_mesh.cc | 8 + .../blender/io/alembic/exporter/abc_writer_mesh.h | 1 + .../io/alembic/exporter/abc_writer_nurbs.cc | 11 + .../blender/io/alembic/exporter/abc_writer_nurbs.h | 1 + .../io/alembic/exporter/abc_writer_points.cc | 5 + .../io/alembic/exporter/abc_writer_points.h | 1 + .../io/alembic/exporter/abc_writer_transform.cc | 11 + .../io/alembic/exporter/abc_writer_transform.h | 4 + tests/python/alembic_export_tests.py | 64 +++++ 24 files changed, 594 insertions(+) create mode 100644 source/blender/io/alembic/exporter/abc_custom_props.cc create mode 100644 source/blender/io/alembic/exporter/abc_custom_props.h diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c index 0c4064b5693..292d8e6066c 100644 --- a/source/blender/editors/io/io_alembic.c +++ b/source/blender/editors/io/io_alembic.c @@ -133,6 +133,7 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op) .use_subdiv_schema = RNA_boolean_get(op->ptr, "subdiv_schema"), .export_hair = RNA_boolean_get(op->ptr, "export_hair"), .export_particles = RNA_boolean_get(op->ptr, "export_particles"), + .export_custom_properties = RNA_boolean_get(op->ptr, "export_custom_properties"), .use_instancing = RNA_boolean_get(op->ptr, "use_instancing"), .packuv = RNA_boolean_get(op->ptr, "packuv"), .triangulate = RNA_boolean_get(op->ptr, "triangulate"), @@ -191,6 +192,8 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "flatten", 0, NULL, ICON_NONE); uiItemR(sub, imfptr, "use_instancing", 0, IFACE_("Use Instancing"), ICON_NONE); + uiItemR(sub, imfptr, "export_custom_properties", 0, IFACE_("Custom Properties"), ICON_NONE); + sub = uiLayoutColumnWithHeading(col, true, IFACE_("Only")); uiItemR(sub, imfptr, "selected", 0, IFACE_("Selected Objects"), ICON_NONE); uiItemR(sub, imfptr, "renderable_only", 0, IFACE_("Renderable Objects"), ICON_NONE); @@ -449,6 +452,12 @@ void WM_OT_alembic_export(wmOperatorType *ot) RNA_def_boolean( ot->srna, "export_particles", 1, "Export Particles", "Exports non-hair particle systems"); + RNA_def_boolean(ot->srna, + "export_custom_properties", + true, + "Export Custom Properties", + "Export custom properties to Alembic .userProperties"); + RNA_def_boolean( ot->srna, "as_background_job", diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 9a2c74c64a3..9785f6d68ab 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -60,6 +60,7 @@ struct AlembicExportParams { bool triangulate; bool export_hair; bool export_particles; + bool export_custom_properties; bool use_instancing; /* See MOD_TRIANGULATE_NGON_xxx and MOD_TRIANGULATE_QUAD_xxx diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt index 2b44146e475..d55f2382a9b 100644 --- a/source/blender/io/alembic/CMakeLists.txt +++ b/source/blender/io/alembic/CMakeLists.txt @@ -56,6 +56,7 @@ set(SRC intern/alembic_capi.cc exporter/abc_archive.cc + exporter/abc_custom_props.cc exporter/abc_export_capi.cc exporter/abc_hierarchy_iterator.cc exporter/abc_subdiv_disabler.cc @@ -84,6 +85,7 @@ set(SRC intern/abc_util.h exporter/abc_archive.h + exporter/abc_custom_props.h exporter/abc_hierarchy_iterator.h exporter/abc_subdiv_disabler.h exporter/abc_writer_abstract.h diff --git a/source/blender/io/alembic/exporter/abc_custom_props.cc b/source/blender/io/alembic/exporter/abc_custom_props.cc new file mode 100644 index 00000000000..382afdc294d --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_custom_props.cc @@ -0,0 +1,268 @@ +/* + * 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 Alembic + */ + +#include "abc_custom_props.h" + +#include "abc_writer_abstract.h" + +#include +#include +#include +#include + +#include +#include + +#include "BKE_idprop.h" +#include "DNA_ID.h" + +using Alembic::Abc::ArraySample; +using Alembic::Abc::OArrayProperty; +using Alembic::Abc::OBoolArrayProperty; +using Alembic::Abc::OCompoundProperty; +using Alembic::Abc::ODoubleArrayProperty; +using Alembic::Abc::OFloatArrayProperty; +using Alembic::Abc::OInt32ArrayProperty; +using Alembic::Abc::OStringArrayProperty; + +namespace blender::io::alembic { + +CustomPropertiesExporter::CustomPropertiesExporter(ABCAbstractWriter *owner) : owner_(owner) +{ +} + +CustomPropertiesExporter::~CustomPropertiesExporter() +{ +} + +void CustomPropertiesExporter::write_all(const IDProperty *group) +{ + if (group == nullptr) { + return; + } + BLI_assert(group->type == IDP_GROUP); + + /* Loop over the properties, just like IDP_foreach_property() does, but without the recursion. */ + LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) { + if (STREQ(id_property->name, "_RNA_UI")) { + continue; + } + write(id_property); + } +} + +void CustomPropertiesExporter::write(const IDProperty *id_property) +{ + BLI_assert(id_property->name[0] != '\0'); + + switch (id_property->type) { + case IDP_STRING: { + /* The Alembic library doesn't accept NULL-terminated character arrays. */ + const std::string prop_value(IDP_String(id_property), id_property->len - 1); + set_scalar_property(id_property->name, prop_value); + break; + } + case IDP_INT: + static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit"); + set_scalar_property(id_property->name, IDP_Int(id_property)); + break; + case IDP_FLOAT: + set_scalar_property(id_property->name, IDP_Float(id_property)); + break; + case IDP_DOUBLE: + set_scalar_property(id_property->name, + IDP_Double(id_property)); + break; + case IDP_ARRAY: + write_array(id_property); + break; + case IDP_IDPARRAY: + write_idparray(id_property); + break; + } +} + +void CustomPropertiesExporter::write_array(const IDProperty *id_property) +{ + BLI_assert(id_property->type == IDP_ARRAY); + + switch (id_property->subtype) { + case IDP_INT: { + const int *array = (int *)IDP_Array(id_property); + static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit"); + set_array_property(id_property->name, array, id_property->len); + break; + } + case IDP_FLOAT: { + const float *array = (float *)IDP_Array(id_property); + set_array_property(id_property->name, array, id_property->len); + break; + } + case IDP_DOUBLE: { + const double *array = (double *)IDP_Array(id_property); + set_array_property(id_property->name, array, id_property->len); + break; + } + } +} + +void CustomPropertiesExporter::write_idparray(const IDProperty *idp_array) +{ + BLI_assert(idp_array->type == IDP_IDPARRAY); + + if (idp_array->len == 0) { + /* Don't bother writing dataless arrays. */ + return; + } + + IDProperty *idp_elements = (IDProperty *)IDP_Array(idp_array); + +#ifndef NDEBUG + /* Sanity check that all elements of the array have the same type. + * Blender should already enforce this, hence it's only used in debug mode. */ + for (int i = 1; i < idp_array->len; i++) { + if (idp_elements[i].type == idp_elements[0].type) { + continue; + } + std::cerr << "Custom property " << idp_array->name << " has elements of varying type"; + BLI_assert(!"Mixed type IDP_ARRAY custom property found"); + } +#endif + + switch (idp_elements[0].type) { + case IDP_STRING: + write_idparray_of_strings(idp_array); + break; + case IDP_ARRAY: + write_idparray_of_numbers(idp_array); + break; + } +} + +void CustomPropertiesExporter::write_idparray_of_strings(const IDProperty *idp_array) +{ + BLI_assert(idp_array->type == IDP_IDPARRAY); + BLI_assert(idp_array->len > 0); + + /* Convert to an array of std::strings, because Alembic doesn't like zero-delimited strings. */ + IDProperty *idp_elements = (IDProperty *)IDP_Array(idp_array); + std::vector strings(idp_array->len); + for (int i = 0; i < idp_array->len; i++) { + BLI_assert(idp_elements[i].type == IDP_STRING); + strings[i] = IDP_String(&idp_elements[i]); + } + + /* Alembic needs a pointer to the first value of the array. */ + const std::string *array_of_strings = &strings[0]; + set_array_property( + idp_array->name, array_of_strings, strings.size()); +} + +void CustomPropertiesExporter::write_idparray_of_numbers(const IDProperty *idp_array) +{ + BLI_assert(idp_array->type == IDP_IDPARRAY); + BLI_assert(idp_array->len > 0); + + /* This must be an array of arrays. */ + IDProperty *idp_rows = (IDProperty *)IDP_Array(idp_array); + BLI_assert(idp_rows[0].type == IDP_ARRAY); + + const int subtype = idp_rows[0].subtype; + if (!ELEM(subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE)) { + /* Non-numerical types are not supported. */ + return; + } + + switch (subtype) { + case IDP_INT: + static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit"); + write_idparray_flattened_typed(idp_array); + break; + case IDP_FLOAT: + write_idparray_flattened_typed(idp_array); + break; + case IDP_DOUBLE: + write_idparray_flattened_typed(idp_array); + break; + } +} + +template +void CustomPropertiesExporter::write_idparray_flattened_typed(const IDProperty *idp_array) +{ + BLI_assert(idp_array->type == IDP_IDPARRAY); + BLI_assert(idp_array->len > 0); + + const IDProperty *idp_rows = (IDProperty *)IDP_Array(idp_array); + BLI_assert(idp_rows[0].type == IDP_ARRAY); + BLI_assert(ELEM(idp_rows[0].subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE)); + + const uint64_t num_rows = idp_array->len; + std::vector matrix_values; + for (size_t row_idx = 0; row_idx < num_rows; ++row_idx) { + const BlenderValueType *row = (BlenderValueType *)IDP_Array(&idp_rows[row_idx]); + for (size_t col_idx = 0; col_idx < idp_rows[row_idx].len; col_idx++) { + matrix_values.push_back(row[col_idx]); + } + } + + set_array_property( + idp_array->name, &matrix_values[0], matrix_values.size()); +} + +template +void CustomPropertiesExporter::set_scalar_property(const StringRef property_name, + const BlenderValueType property_value) +{ + set_array_property(property_name, &property_value, 1); +} + +template +void CustomPropertiesExporter::set_array_property(const StringRef property_name, + const BlenderValueType *array_values, + const size_t num_array_items) +{ + auto create_callback = [this, property_name]() -> OArrayProperty { + return create_abc_property(property_name); + }; + + OArrayProperty array_prop = abc_properties_.lookup_or_add_cb(property_name, create_callback); + Alembic::Util::Dimensions array_dimensions(num_array_items); + ArraySample sample(array_values, array_prop.getDataType(), array_dimensions); + array_prop.set(sample); +} + +template +OArrayProperty CustomPropertiesExporter::create_abc_property(const StringRef property_name) +{ + /* Get the necessary info from our owner. */ + OCompoundProperty abc_prop_for_custom_props = owner_->abc_prop_for_custom_props(); + const uint32_t timesample_index = owner_->timesample_index(); + + /* Construct the Alembic property. */ + ABCPropertyType abc_property(abc_prop_for_custom_props, property_name); + abc_property.setTimeSampling(timesample_index); + return abc_property; +} + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_custom_props.h b/source/blender/io/alembic/exporter/abc_custom_props.h new file mode 100644 index 00000000000..d3f9b2fc43c --- /dev/null +++ b/source/blender/io/alembic/exporter/abc_custom_props.h @@ -0,0 +1,96 @@ +/* + * 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 Alembic + */ + +#pragma once + +#include +#include + +#include "BLI_map.hh" + +#include + +struct IDProperty; + +namespace blender::io::alembic { + +class ABCAbstractWriter; + +/* Write values of Custom Properties (a.k.a. ID Properties) to Alembic. + * + * Each Alembic Writer instance optionally has one CustomPropertiesExporter (CPE). This CPE not + * only writes the custom properties to Alembic, but also keeps references in memory so that the + * Alembic library doesn't prematurely finalize the data. */ +class CustomPropertiesExporter { + private: + /* Owner is used to get the OCompoundProperty and time sample index. The former should only be + * requested from the Alembic library when it's actually going to be used to add custom + * properties (otherwise an invalid Alembic file is written). */ + ABCAbstractWriter *owner_; + + /* The Compound Property that will contain the exported custom properties. + * + * Typically this the return value of abc_schema.getArbGeomParams() or + * abc_schema.getUserProperties(). */ + Alembic::Abc::OCompoundProperty abc_compound_prop_; + + /* Mapping from property name in Blender to property in Alembic. + * Here Blender does the same as other software (Maya, Houdini), and writes + * scalar properties as single-element arrays. */ + Map abc_properties_; + + public: + CustomPropertiesExporter(ABCAbstractWriter *owner); + virtual ~CustomPropertiesExporter(); + + void write_all(const IDProperty *group); + + private: + void write(const IDProperty *id_property); + void write_array(const IDProperty *id_property); + + /* IDProperty arrays are used to store arrays-of-arrays or arrays-of-strings. */ + void write_idparray(const IDProperty *idp_array); + void write_idparray_of_strings(const IDProperty *idp_array); + void write_idparray_of_numbers(const IDProperty *idp_array); + + /* Flatten an array-of-arrays into one long array, then write that. + * It is tempting to write an array of NxM numbers as a matrix, but there is + * no guarantee that the data actually represents a matrix. */ + template + void write_idparray_flattened_typed(const IDProperty *idp_array); + + /* Write a single scalar (i.e. non-array) property as single-value array. */ + template + void set_scalar_property(StringRef property_name, const BlenderValueType property_value); + + template + void set_array_property(StringRef property_name, + const BlenderValueType *array_values, + size_t num_array_items); + + template + Alembic::Abc::OArrayProperty create_abc_property(StringRef property_name); +}; + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc index 0b08d8c4e58..e99048cc0ee 100644 --- a/source/blender/io/alembic/exporter/abc_writer_abstract.cc +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc @@ -59,6 +59,7 @@ void ABCAbstractWriter::write(HierarchyContext &context) if (!frame_has_been_written_) { is_animated_ = (args_.export_params->frame_start != args_.export_params->frame_end) && check_is_animated(context); + ensure_custom_properties_exporter(context); } else if (!is_animated_) { /* A frame has already been written, and without animation one frame is enough. */ @@ -67,9 +68,49 @@ void ABCAbstractWriter::write(HierarchyContext &context) do_write(context); + if (custom_props_) { + custom_props_->write_all(get_id_properties(context)); + } + frame_has_been_written_ = true; } +void ABCAbstractWriter::ensure_custom_properties_exporter(const HierarchyContext &context) +{ + if (!args_.export_params->export_custom_properties) { + return; + } + + if (custom_props_) { + /* Custom properties exporter already created. */ + return; + } + + /* Avoid creating a custom properties exporter if there are no custom properties to export. */ + const IDProperty *id_properties = get_id_properties(context); + if (id_properties == nullptr || id_properties->len == 0) { + return; + } + + custom_props_ = std::make_unique(this); +} + +const IDProperty *ABCAbstractWriter::get_id_properties(const HierarchyContext &context) const +{ + Object *object = context.object; + if (object->data == nullptr) { + return nullptr; + } + + /* Most subclasses write object data, so default to the object data's ID properties. */ + return static_cast(object->data)->properties; +} + +uint32_t ABCAbstractWriter::timesample_index() const +{ + return timesample_index_; +} + const Imath::Box3d &ABCAbstractWriter::bounding_box() const { return bounding_box_; diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.h b/source/blender/io/alembic/exporter/abc_writer_abstract.h index 59c55330443..d23e69cf73e 100644 --- a/source/blender/io/alembic/exporter/abc_writer_abstract.h +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.h @@ -19,6 +19,7 @@ #pragma once #include "IO_abstract_hierarchy_iterator.h" +#include "abc_custom_props.h" #include "abc_hierarchy_iterator.h" #include @@ -27,6 +28,7 @@ #include "DEG_depsgraph_query.h" #include "DNA_material_types.h" +struct IDProperty; struct Material; struct Object; @@ -44,6 +46,9 @@ class ABCAbstractWriter : public AbstractHierarchyWriter { /* Visibility of this writer's data in Alembic. */ Alembic::Abc::OCharProperty abc_visibility_; + /* Optional writer for custom properties. */ + std::unique_ptr custom_props_; + public: explicit ABCAbstractWriter(const ABCWriterConstructorArgs &args); virtual ~ABCAbstractWriter(); @@ -59,6 +64,7 @@ class ABCAbstractWriter : public AbstractHierarchyWriter { * Empty). */ virtual bool is_supported(const HierarchyContext *context) const; + uint32_t timesample_index() const; const Imath::Box3d &bounding_box() const; /* Called by AlembicHierarchyCreator after checking that the data is supported via @@ -67,12 +73,47 @@ class ABCAbstractWriter : public AbstractHierarchyWriter { virtual Alembic::Abc::OObject get_alembic_object() const = 0; + /* Return the Alembic object's CompoundProperty that'll contain the custom properties. + * + * This function is called whenever there are custom properties to be written to Alembic. It + * should call abc_schema_prop_for_custom_props() with the writer's Alembic schema object. + * + * If custom properties are not supported by a specific subclass, it should return an empty + * OCompoundProperty() and override ensure_custom_properties_exporter() to do nothing. + */ + virtual Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() = 0; + protected: virtual void do_write(HierarchyContext &context) = 0; virtual void update_bounding_box(Object *object); + /* Return ID properties of whatever ID datablock is written by this writer. Defaults to the + * properties of the object data. Can return nullptr if no custom properties are to be written. + */ + virtual const IDProperty *get_id_properties(const HierarchyContext &context) const; + + virtual void ensure_custom_properties_exporter(const HierarchyContext &context); + void write_visibility(const HierarchyContext &context); + + /* Return the Alembic schema's compound property, which will be used for writing custom + * properties. + * + * This can return either abc_schema.getUserProperties() or abc_schema.getArbGeomParams(). The + * former only holds values similar to Blender's custom properties, whereas the latter can also + * specify that certain custom properties vary per mesh component (so per face, vertex, etc.). As + * such, .userProperties is more suitable for custom properties. However, Maya, Houdini use + * .arbGeomParams for custom data. + * + * Because of this, the code uses this templated function so that there is one place that + * determines where custom properties are exporter to. + */ + template + Alembic::Abc::OCompoundProperty abc_schema_prop_for_custom_props(T abc_schema) + { + return abc_schema.getUserProperties(); + } }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.cc b/source/blender/io/alembic/exporter/abc_writer_camera.cc index 0ce6c3dc07f..82a741699e4 100644 --- a/source/blender/io/alembic/exporter/abc_writer_camera.cc +++ b/source/blender/io/alembic/exporter/abc_writer_camera.cc @@ -65,6 +65,11 @@ Alembic::Abc::OObject ABCCameraWriter::get_alembic_object() const return abc_camera_; } +Alembic::Abc::OCompoundProperty ABCCameraWriter::abc_prop_for_custom_props() +{ + return abc_schema_prop_for_custom_props(abc_camera_schema_); +} + void ABCCameraWriter::do_write(HierarchyContext &context) { Camera *cam = static_cast(context.object->data); diff --git a/source/blender/io/alembic/exporter/abc_writer_camera.h b/source/blender/io/alembic/exporter/abc_writer_camera.h index 1b3e5898517..41885ed66d6 100644 --- a/source/blender/io/alembic/exporter/abc_writer_camera.h +++ b/source/blender/io/alembic/exporter/abc_writer_camera.h @@ -43,6 +43,7 @@ class ABCCameraWriter : public ABCAbstractWriter { protected: virtual bool is_supported(const HierarchyContext *context) const override; virtual void do_write(HierarchyContext &context) override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.cc b/source/blender/io/alembic/exporter/abc_writer_curves.cc index 6a12e4c59f3..b57af345a3c 100644 --- a/source/blender/io/alembic/exporter/abc_writer_curves.cc +++ b/source/blender/io/alembic/exporter/abc_writer_curves.cc @@ -66,6 +66,11 @@ Alembic::Abc::OObject ABCCurveWriter::get_alembic_object() const return abc_curve_; } +Alembic::Abc::OCompoundProperty ABCCurveWriter::abc_prop_for_custom_props() +{ + return abc_schema_prop_for_custom_props(abc_curve_schema_); +} + void ABCCurveWriter::do_write(HierarchyContext &context) { Curve *curve = static_cast(context.object->data); diff --git a/source/blender/io/alembic/exporter/abc_writer_curves.h b/source/blender/io/alembic/exporter/abc_writer_curves.h index d15f008f947..e210363557c 100644 --- a/source/blender/io/alembic/exporter/abc_writer_curves.h +++ b/source/blender/io/alembic/exporter/abc_writer_curves.h @@ -41,6 +41,7 @@ class ABCCurveWriter : public ABCAbstractWriter { virtual void create_alembic_objects(const HierarchyContext *context) override; virtual Alembic::Abc::OObject get_alembic_object() const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; protected: virtual void do_write(HierarchyContext &context) override; diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc index 80034245b84..072feb2a90a 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.cc +++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc @@ -62,6 +62,11 @@ Alembic::Abc::OObject ABCHairWriter::get_alembic_object() const return abc_curves_; } +Alembic::Abc::OCompoundProperty ABCHairWriter::abc_prop_for_custom_props() +{ + return abc_schema_prop_for_custom_props(abc_curves_schema_); +} + bool ABCHairWriter::check_is_animated(const HierarchyContext & /*context*/) const { /* We assume that hair particles are always animated. */ diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.h b/source/blender/io/alembic/exporter/abc_writer_hair.h index f7d988ecbc4..3759ffa4310 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.h +++ b/source/blender/io/alembic/exporter/abc_writer_hair.h @@ -44,6 +44,7 @@ class ABCHairWriter : public ABCAbstractWriter { protected: virtual void do_write(HierarchyContext &context) override; virtual bool check_is_animated(const HierarchyContext &context) const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; private: void write_hair_sample(const HierarchyContext &context, diff --git a/source/blender/io/alembic/exporter/abc_writer_instance.cc b/source/blender/io/alembic/exporter/abc_writer_instance.cc index 14c65e2a2e2..7f3b044cb8b 100644 --- a/source/blender/io/alembic/exporter/abc_writer_instance.cc +++ b/source/blender/io/alembic/exporter/abc_writer_instance.cc @@ -50,6 +50,16 @@ void ABCInstanceWriter::create_alembic_objects(const HierarchyContext *context) CLOG_INFO(&LOG, 2, "exporting instance %s", args_.abc_path.c_str()); } +void ABCInstanceWriter::ensure_custom_properties_exporter(const HierarchyContext & /*context*/) +{ + /* Intentionally do nothing. Instances should not have their own custom properties. */ +} + +Alembic::Abc::OCompoundProperty ABCInstanceWriter::abc_prop_for_custom_props() +{ + return Alembic::Abc::OCompoundProperty(); +} + OObject ABCInstanceWriter::get_alembic_object() const { /* There is no OObject for an instance. */ diff --git a/source/blender/io/alembic/exporter/abc_writer_instance.h b/source/blender/io/alembic/exporter/abc_writer_instance.h index 067c4af7aed..f7d6450055a 100644 --- a/source/blender/io/alembic/exporter/abc_writer_instance.h +++ b/source/blender/io/alembic/exporter/abc_writer_instance.h @@ -39,6 +39,8 @@ class ABCInstanceWriter : public ABCAbstractWriter { protected: virtual bool is_supported(const HierarchyContext *context) const override; virtual void do_write(HierarchyContext &context) override; + void ensure_custom_properties_exporter(const HierarchyContext &context) override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index b762ad47932..fbc662113cc 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -125,6 +125,14 @@ Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const return abc_poly_mesh_; } +Alembic::Abc::OCompoundProperty ABCGenericMeshWriter::abc_prop_for_custom_props() +{ + if (is_subd_) { + return abc_schema_prop_for_custom_props(abc_subdiv_schema_); + } + return abc_schema_prop_for_custom_props(abc_poly_mesh_schema_); +} + bool ABCGenericMeshWriter::export_as_subdivision_surface(Object *ob_eval) const { ModifierData *md = static_cast(ob_eval->modifiers.last); diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.h b/source/blender/io/alembic/exporter/abc_writer_mesh.h index 956587df7c0..fdf2d3cc1e3 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.h +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.h @@ -55,6 +55,7 @@ class ABCGenericMeshWriter : public ABCAbstractWriter { virtual void create_alembic_objects(const HierarchyContext *context) override; virtual Alembic::Abc::OObject get_alembic_object() const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; protected: virtual bool is_supported(const HierarchyContext *context) const override; diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc index de1870fefd9..7595a0eba63 100644 --- a/source/blender/io/alembic/exporter/abc_writer_nurbs.cc +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.cc @@ -78,6 +78,17 @@ OObject ABCNurbsWriter::get_alembic_object() const return abc_nurbs_[0]; } +Alembic::Abc::OCompoundProperty ABCNurbsWriter::abc_prop_for_custom_props() +{ + if (abc_nurbs_.empty()) { + return Alembic::Abc::OCompoundProperty(); + } + + /* A single NURBS object in Blender is expanded to multiple curves in Alembic. + * Just store the custom properties on the first one for simplicity. */ + return abc_schema_prop_for_custom_props(abc_nurbs_schemas_[0]); +} + bool ABCNurbsWriter::check_is_animated(const HierarchyContext &context) const { /* Check if object has shape keys. */ diff --git a/source/blender/io/alembic/exporter/abc_writer_nurbs.h b/source/blender/io/alembic/exporter/abc_writer_nurbs.h index 691390ffc9f..0f206d34682 100644 --- a/source/blender/io/alembic/exporter/abc_writer_nurbs.h +++ b/source/blender/io/alembic/exporter/abc_writer_nurbs.h @@ -40,6 +40,7 @@ class ABCNurbsWriter : public ABCAbstractWriter { virtual bool is_supported(const HierarchyContext *context) const override; virtual void do_write(HierarchyContext &context) override; virtual bool check_is_animated(const HierarchyContext &context) const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; }; class ABCNurbsMeshWriter : public ABCGenericMeshWriter { diff --git a/source/blender/io/alembic/exporter/abc_writer_points.cc b/source/blender/io/alembic/exporter/abc_writer_points.cc index 83d33577b3b..557f580e8aa 100644 --- a/source/blender/io/alembic/exporter/abc_writer_points.cc +++ b/source/blender/io/alembic/exporter/abc_writer_points.cc @@ -58,6 +58,11 @@ Alembic::Abc::OObject ABCPointsWriter::get_alembic_object() const return abc_points_; } +Alembic::Abc::OCompoundProperty ABCPointsWriter::abc_prop_for_custom_props() +{ + return abc_schema_prop_for_custom_props(abc_points_schema_); +} + bool ABCPointsWriter::is_supported(const HierarchyContext *context) const { return ELEM(context->particle_system->part->type, diff --git a/source/blender/io/alembic/exporter/abc_writer_points.h b/source/blender/io/alembic/exporter/abc_writer_points.h index fec5e84f3f2..0447c41db3e 100644 --- a/source/blender/io/alembic/exporter/abc_writer_points.h +++ b/source/blender/io/alembic/exporter/abc_writer_points.h @@ -37,6 +37,7 @@ class ABCPointsWriter : public ABCAbstractWriter { virtual void create_alembic_objects(const HierarchyContext *context) override; virtual Alembic::Abc::OObject get_alembic_object() const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; virtual bool is_supported(const HierarchyContext *context) const override; diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.cc b/source/blender/io/alembic/exporter/abc_writer_transform.cc index a72a6b47aa9..79e460e56e9 100644 --- a/source/blender/io/alembic/exporter/abc_writer_transform.cc +++ b/source/blender/io/alembic/exporter/abc_writer_transform.cc @@ -53,6 +53,17 @@ void ABCTransformWriter::create_alembic_objects(const HierarchyContext * /*conte abc_xform_schema_ = abc_xform_.getSchema(); } +Alembic::Abc::OCompoundProperty ABCTransformWriter::abc_prop_for_custom_props() +{ + return abc_schema_prop_for_custom_props(abc_xform_schema_); +} + +const IDProperty *ABCTransformWriter::get_id_properties(const HierarchyContext &context) const +{ + const Object *object = context.object; + return object->id.properties; +} + void ABCTransformWriter::do_write(HierarchyContext &context) { float parent_relative_matrix[4][4]; // The object matrix relative to the parent. diff --git a/source/blender/io/alembic/exporter/abc_writer_transform.h b/source/blender/io/alembic/exporter/abc_writer_transform.h index a334fe610ee..4542b9de506 100644 --- a/source/blender/io/alembic/exporter/abc_writer_transform.h +++ b/source/blender/io/alembic/exporter/abc_writer_transform.h @@ -21,6 +21,8 @@ #include "abc_writer_abstract.h" +#include + #include namespace blender::io::alembic { @@ -38,6 +40,8 @@ class ABCTransformWriter : public ABCAbstractWriter { virtual void do_write(HierarchyContext &context) override; virtual bool check_is_animated(const HierarchyContext &context) const override; virtual Alembic::Abc::OObject get_alembic_object() const override; + const IDProperty *get_id_properties(const HierarchyContext &context) const override; + Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override; }; } // namespace blender::io::alembic diff --git a/tests/python/alembic_export_tests.py b/tests/python/alembic_export_tests.py index b5d6bf65f3f..1d34eb3fc81 100644 --- a/tests/python/alembic_export_tests.py +++ b/tests/python/alembic_export_tests.py @@ -111,6 +111,7 @@ class AbstractAlembicTest(AbstractBlenderRunnerTest): 'uint64_t': int, 'float64_t': float, 'float32_t': float, + 'string': str, } result = {} @@ -586,6 +587,69 @@ class InvisibleObjectExportTest(AbstractAlembicTest): test('InvisibleAnimatedCube', False) +class CustomPropertiesExportTest(AbstractAlembicTest): + """Test export of custom properties.""" + + def _run_export(self, tempdir: pathlib.Path) -> pathlib.Path: + abc = tempdir / 'custom-properties.abc' + script = "import bpy; bpy.context.scene.frame_set(1); bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1)" % abc.as_posix() + self.run_blender('custom-properties.blend', script) + return abc + + @with_tempdir + def test_xform_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Cube/.xform/.userProperties') + + # Simple, single values. + self.assertEqual(abcprop['static_int'], [327]) + self.assertEqual(abcprop['static_float'], [47.01]) + self.assertEqual(abcprop['static_string'], ['Agents']) + self.assertEqual(abcprop['keyed_float'], [-1]) + self.assertEqual(abcprop['keyed_int'], [-47]) + + # Arrays. + self.assertEqual(abcprop['keyed_array_float'], [-1.000, 0.000, 1.000]) + self.assertEqual(abcprop['keyed_array_int'], [42, 47, 327]) + + # Multi-dimensional arrays. + self.assertEqual(abcprop['array_of_strings'], ['ผัดไทย', 'Pad Thai']) + self.assertEqual( + abcprop['matrix_tuple'], + [1.0, 0.0, 0.0, 3.33333, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + self.assertEqual( + abcprop['static_matrix'], + [1.0, 0.0, 0.0, 3.33333, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + self.assertEqual( + abcprop['nonuniform_array'], + [10, 20, 30, 1, 2, 47]) + + @with_tempdir + def test_mesh_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Cube/Cube/.geom/.userProperties') + self.assertEqual(abcprop['mesh_tags'], ['cube', 'box', 'low-poly-sphere']) + + @with_tempdir + def test_camera_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Camera/Hasselblad/.geom/.userProperties') + self.assertEqual(abcprop['type'], ['500c/m']) + + @with_tempdir + def test_disabled_export_option(self, tempdir: pathlib.Path) -> None: + abc = tempdir / 'custom-properties.abc' + script = ( + "import bpy; bpy.context.scene.frame_set(1); " + "bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, export_custom_properties=False)" % abc.as_posix() + ) + self.run_blender('custom-properties.blend', script) + + abcprop = self.abcprop(abc, '/Camera/Hasselblad/.geom/.userProperties') + self.assertIn('eyeSeparation', abcprop, 'Regular non-standard properties should still be written') + self.assertNotIn('type', abcprop, 'Custom properties should not be written') + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--blender', required=True) -- cgit v1.2.3 From d9e2adaaf98cd558f6de26df22928ba86f5b9f85 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 14 Sep 2020 21:03:24 +1000 Subject: Fix T80728: UV edge select splits UV's for lasso/box/circle select Oversight in 411c5238a2fef ignored sticky selection. Use 'uvedit_edge_select_set_with_sticky' to make sure sticky options are respected. Also skip checking the existing selection since that only checks the current UV, not all connected UV's which is needed for sticky selection. The extra checks to avoid updating UV's isn't such an advantage as only meshed in the selected region are tagged for updating. --- source/blender/editors/uvedit/uvedit_select.c | 70 ++++++--------------------- 1 file changed, 15 insertions(+), 55 deletions(-) diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index 2ea78ca5377..baef2eb0d4b 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -2924,9 +2924,6 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) } } else if (use_edge && !pinned) { - changed = true; - 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) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -2934,29 +2931,18 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); - bool luv_select_prev = uvedit_uv_select_test(scene, l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - const bool luv_select = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); - if ((select != luv_select) || (select != luv_select_prev)) { - if (BLI_rctf_isect_pt_v(&rectf, luv->uv) && - BLI_rctf_isect_pt_v(&rectf, luv_prev->uv)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); - uvedit_uv_select_set(scene, em, l_prev, select, false, cd_loop_uv_offset); - BM_elem_flag_enable(l->v, BM_ELEM_TAG); - BM_elem_flag_enable(l_prev->v, BM_ELEM_TAG); - } + if (BLI_rctf_isect_pt_v(&rectf, luv->uv) && BLI_rctf_isect_pt_v(&rectf, luv_prev->uv)) { + uvedit_edge_select_set_with_sticky( + sima, scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; } l_prev = l; luv_prev = luv; - luv_select_prev = luv_select; } } - - if (sima->sticky == SI_STICKY_VERTEX) { - uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); - } } else { /* other selection modes */ @@ -3155,8 +3141,6 @@ static int uv_circle_select_exec(bContext *C, wmOperator *op) } } else if (use_edge) { - 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) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -3164,29 +3148,18 @@ static int uv_circle_select_exec(bContext *C, wmOperator *op) BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); - bool luv_select_prev = uvedit_uv_select_test(scene, l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - const bool luv_select = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); - if ((select != luv_select) || (select != luv_select_prev)) { - if (uv_circle_select_is_edge_inside(luv->uv, luv_prev->uv, offset, ellipse)) { - changed = true; - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); - uvedit_uv_select_set(scene, em, l_prev, select, false, cd_loop_uv_offset); - BM_elem_flag_enable(l->v, BM_ELEM_TAG); - BM_elem_flag_enable(l_prev->v, BM_ELEM_TAG); - } + if (uv_circle_select_is_edge_inside(luv->uv, luv_prev->uv, offset, ellipse)) { + uvedit_edge_select_set_with_sticky( + sima, scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; } l_prev = l; luv_prev = luv; - luv_select_prev = luv_select; } } - - if (sima->sticky == SI_STICKY_VERTEX) { - uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); - } } else { BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); @@ -3346,8 +3319,6 @@ static bool do_lasso_select_mesh_uv(bContext *C, } } else if (use_edge) { - 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) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -3355,32 +3326,21 @@ static bool do_lasso_select_mesh_uv(bContext *C, BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); - bool luv_select_prev = uvedit_uv_select_test(scene, l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - const bool luv_select = uvedit_uv_select_test(scene, l, cd_loop_uv_offset); - if ((select != luv_select) || (select != luv_select_prev)) { - if (do_lasso_select_mesh_uv_is_point_inside( - region, &rect, mcoords, mcoords_len, luv->uv) && - do_lasso_select_mesh_uv_is_point_inside( - region, &rect, mcoords, mcoords_len, luv_prev->uv)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); - uvedit_uv_select_set(scene, em, l_prev, select, false, cd_loop_uv_offset); - changed = true; - BM_elem_flag_enable(l->v, BM_ELEM_TAG); - BM_elem_flag_enable(l_prev->v, BM_ELEM_TAG); - } + if (do_lasso_select_mesh_uv_is_point_inside( + region, &rect, mcoords, mcoords_len, luv->uv) && + do_lasso_select_mesh_uv_is_point_inside( + region, &rect, mcoords, mcoords_len, luv_prev->uv)) { + uvedit_edge_select_set_with_sticky( + sima, scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; } l_prev = l; luv_prev = luv; - luv_select_prev = luv_select; } } - - if (sima->sticky == SI_STICKY_VERTEX) { - uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); - } } else { /* Vert Sel */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); -- cgit v1.2.3 From 4b14f763da0e207bb561a40aefc5473ff0c5b184 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Mon, 14 Sep 2020 09:41:22 -0300 Subject: Fix 'Links Cut' adding undo steps without cutting anything The operator's return was ignored by the gesture ops that always returned `OPERATOR_FINISHED`. This ends by adding a undo step that brings no change. --- source/blender/windowmanager/intern/wm_gesture_ops.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 74a94e997e0..a38cbb920c4 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -618,8 +618,9 @@ int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } -static void gesture_lasso_apply(bContext *C, wmOperator *op) +static int gesture_lasso_apply(bContext *C, wmOperator *op) { + int retval = OPERATOR_FINISHED; wmGesture *gesture = op->customdata; PointerRNA itemptr; float loc[2]; @@ -639,9 +640,11 @@ static void gesture_lasso_apply(bContext *C, wmOperator *op) gesture_modal_end(C, op); if (op->type->exec) { - int retval = op->type->exec(C, op); + retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); } + + return retval; } int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event) @@ -683,8 +686,7 @@ int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event) case MIDDLEMOUSE: case RIGHTMOUSE: if (event->val == KM_RELEASE) { /* key release */ - gesture_lasso_apply(C, op); - return OPERATOR_FINISHED; + return gesture_lasso_apply(C, op); } break; case EVT_ESCKEY: -- cgit v1.2.3 From ec6d32b238da507c258a4b571e332c5cb67a6b18 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 14 Sep 2020 14:55:48 +0200 Subject: Fix T78392: [2.83.5, 2.90, 2.91] Crash on undo/ redo after changing modes. During undo/redo read code is expected to clear the `OB_MODE_EDIT` bitflag of `Object.mode`, for some reasons. This was not done anymore for re-used Objects, we need to add a special handling case for that too. Should be backported to 2.90 and 2.83 (will probably not be straight forward for the latter). --- source/blender/blenloader/intern/readfile.c | 3 +++ source/blender/blenloader/intern/writefile.c | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index b0f0a08651c..38dc63af1fe 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -6553,6 +6553,9 @@ static void read_libblock_undo_restore_identical( if (ob->proxy != NULL) { ob->proxy->proxy_from = ob; } + /* For undo we stay in object mode during undo presses, so keep editmode disabled for re-used + * data-blocks too. */ + ob->mode &= ~OB_MODE_EDIT; } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index b3e937a29b2..183ed3668b7 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -1303,10 +1303,17 @@ static void write_shaderfxs(BlendWriter *writer, ListBase *fxbase) static void write_object(BlendWriter *writer, Object *ob, const void *id_address) { - if (ob->id.us > 0 || BLO_write_is_undo(writer)) { + const bool is_undo = BLO_write_is_undo(writer); + if (ob->id.us > 0 || is_undo) { /* Clean up, important in undo case to reduce false detection of changed datablocks. */ BKE_object_runtime_reset(ob); + if (is_undo) { + /* For undo we stay in object mode during undo presses, so keep editmode disabled on save as + * well, can help reducing false detection of changed datablocks. */ + ob->mode &= ~OB_MODE_EDIT; + } + /* write LibData */ BLO_write_id_struct(writer, Object, id_address, &ob->id); BKE_id_blend_write(writer, &ob->id); -- cgit v1.2.3 From 6aeafacf8611af27de9dae122bc31aa4a7d613aa Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Mon, 14 Sep 2020 15:26:17 +0200 Subject: Fix T79651: Bounding box is wrong after duplicate object The bounding box is not updated in the original object when the function is called using evaluated object and keeps wrong while the object is not edited or the file saved. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D8565 Notes: Minor changes done in the patch following review comments. --- source/blender/blenkernel/intern/gpencil_geom.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index a9b0eede055..66a7ae757a2 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -185,6 +185,19 @@ BoundBox *BKE_gpencil_boundbox_get(Object *ob) boundbox_gpencil(ob); + Object *ob_orig = (Object *)DEG_get_original_id(&ob->id); + /* Update orig object's boundbox with re-computed evaluated values. This function can be + * called with the evaluated object and need update the original object bound box data + * to keep both values synchronized. */ + if ((ob_orig != NULL) && (ob != ob_orig)) { + if (ob_orig->runtime.bb == NULL) { + ob_orig->runtime.bb = MEM_callocN(sizeof(BoundBox), "GPencil boundbox"); + } + for (int i = 0; i < 8; i++) { + copy_v3_v3(ob_orig->runtime.bb->vec[i], ob->runtime.bb->vec[i]); + } + } + return ob->runtime.bb; } -- cgit v1.2.3 From 716ea15479895e56eb9f6d973aca88a5436d7efe Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 14 Sep 2020 15:31:26 +0200 Subject: Fix T80770: UV Image Editor: Display Texture Paint UVs Not Working When developing the image draw engine I wasn't aware of this option. But now it is back. --- source/blender/draw/engines/overlay/overlay_edit_uv.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/source/blender/draw/engines/overlay/overlay_edit_uv.c b/source/blender/draw/engines/overlay/overlay_edit_uv.c index 109db6433e0..adbf7168394 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_uv.c +++ b/source/blender/draw/engines/overlay/overlay_edit_uv.c @@ -96,16 +96,19 @@ void OVERLAY_edit_uv_init(OVERLAY_Data *vedata) (ts->uv_selectmode == UV_SELECT_FACE); const bool do_uvstretching_overlay = is_image_type && is_uv_editor && is_edit_mode && ((sima->flag & SI_DRAW_STRETCH) != 0); + const bool do_tex_paint_shadows = (sima->flag & SI_NO_DRAW_TEXPAINT) == 0; + pd->edit_uv.do_faces = do_faces && !do_uvstretching_overlay; pd->edit_uv.do_face_dots = do_faces && do_face_dots; pd->edit_uv.do_uv_overlay = do_uv_overlay; - pd->edit_uv.do_uv_shadow_overlay = - is_image_type && - ((is_paint_mode && - ((draw_ctx->object_mode & (OB_MODE_TEXTURE_PAINT | OB_MODE_EDIT)) != 0)) || - (is_view_mode && ((draw_ctx->object_mode & (OB_MODE_TEXTURE_PAINT)) != 0)) || - (do_uv_overlay && (show_modified_uvs))); + pd->edit_uv.do_uv_shadow_overlay = is_image_type && + ((is_paint_mode && do_tex_paint_shadows && + ((draw_ctx->object_mode & + (OB_MODE_TEXTURE_PAINT | OB_MODE_EDIT)) != 0)) || + (is_view_mode && do_tex_paint_shadows && + ((draw_ctx->object_mode & (OB_MODE_TEXTURE_PAINT)) != 0)) || + (do_uv_overlay && (show_modified_uvs))); pd->edit_uv.do_uv_stretching_overlay = do_uvstretching_overlay; pd->edit_uv.uv_opacity = sima->uv_opacity; pd->edit_uv.do_tiled_image_overlay = is_image_type && is_tiled_image; -- cgit v1.2.3