[Bf-blender-cvs] [51f0c747bfc] gpencil-new-data-proposal: Fix T99256: Regression: Meta balls segfaulting copy-to-selected.

Bastien Montagne noreply at git.blender.org
Fri Jul 8 17:57:50 CEST 2022


Commit: 51f0c747bfcd743321df7edf81bfabb35b80a668
Author: Bastien Montagne
Date:   Thu Jul 7 16:20:27 2022 +0200
Branches: gpencil-new-data-proposal
https://developer.blender.org/rB51f0c747bfcd743321df7edf81bfabb35b80a668

Fix T99256: Regression: Meta balls segfaulting copy-to-selected.

Revealed by rB43167a2c251b, but actuall issue is the
`rna_MetaBall_update_data` function expecting a never-NULL `scene`
pointer, which is not guaranteed.

This lead to refactoring the duo
`rna_MetaBall_update_data`/`BKE_mball_properties_copy`, since it was
doing a very sub-optimal O(n^2) process, new code is O(2n) now
(n being the number of Objects).

Not sure why the objects were processed through the existing bases of
the existing scene in the first place, this could miss a lot of affected
objects (e.g. in case said objects are in an excluded collection, etc.).

Also noticed that both old and new implementation can fail to fully propagate
changes to all affected meta-balls in some specific corner cases, added
a comment about it in the code.

Reviewed By: sergey

Maniphest Tasks: T99256

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

===================================================================

M	source/blender/blenkernel/BKE_mball.h
M	source/blender/blenkernel/intern/mball.c
M	source/blender/makesrna/intern/rna_access.c
M	source/blender/makesrna/intern/rna_meta.c

===================================================================

diff --git a/source/blender/blenkernel/BKE_mball.h b/source/blender/blenkernel/BKE_mball.h
index f40d0bb3004..5a4988c7a5f 100644
--- a/source/blender/blenkernel/BKE_mball.h
+++ b/source/blender/blenkernel/BKE_mball.h
@@ -24,14 +24,25 @@ struct MetaBall *BKE_mball_add(struct Main *bmain, const char *name);
 bool BKE_mball_is_any_selected(const struct MetaBall *mb);
 bool BKE_mball_is_any_selected_multi(struct Base **bases, int bases_len);
 bool BKE_mball_is_any_unselected(const struct MetaBall *mb);
-bool BKE_mball_is_basis_for(struct Object *ob1, struct Object *ob2);
+
+/**
+ * Return `true` if `ob1` and `ob2` are part of the same metaBall group.
+ *
+ * \note Currently checks whether their two base names (without numerical suffix) is the same.
+ */
+bool BKE_mball_is_same_group(const struct Object *ob1, const struct Object *ob2);
+/**
+ * Return `true` if `ob1` and `ob2` are part of the same metaBall group, and `ob1` is its
+ * basis.
+ */
+bool BKE_mball_is_basis_for(const struct Object *ob1, const struct Object *ob2);
 /**
  * Test, if \a ob is a basis meta-ball.
  *
  * It test last character of Object ID name.
  * If last character is digit it return 0, else it return 1.
  */
-bool BKE_mball_is_basis(struct Object *ob);
+bool BKE_mball_is_basis(const struct Object *ob);
 /**
  * This function finds the basis meta-ball.
  *
@@ -58,13 +69,14 @@ struct BoundBox *BKE_mball_boundbox_get(struct Object *ob);
 float *BKE_mball_make_orco(struct Object *ob, struct ListBase *dispbase);
 
 /**
- * Copy some properties from object to other meta-ball object with same base name.
+ * Copy some properties from a meta-ball obdata to all other meta-ball obdata belonging to the same
+ * family (i.e. object sharing the same name basis).
  *
  * When some properties (wire-size, threshold, update flags) of meta-ball are changed, then this
  * properties are copied to all meta-balls in same "group" (meta-balls with same base name:
  * `MBall`, `MBall.001`, `MBall.002`, etc). The most important is to copy properties to the base
- * meta-ball, because this meta-ball influence polygonization of meta-balls. */
-void BKE_mball_properties_copy(struct Scene *scene, struct Object *active_object);
+ * meta-ball, because this meta-ball influences polygonization of meta-balls. */
+void BKE_mball_properties_copy(struct Main *bmain, struct MetaBall *active_metaball);
 
 bool BKE_mball_minmax_ex(
     const struct MetaBall *mb, float min[3], float max[3], const float obmat[4][4], short flag);
diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c
index 1340e53f06e..819bbde6556 100644
--- a/source/blender/blenkernel/intern/mball.c
+++ b/source/blender/blenkernel/intern/mball.c
@@ -347,7 +347,7 @@ float *BKE_mball_make_orco(Object *ob, ListBase *dispbase)
   return orcodata;
 }
 
-bool BKE_mball_is_basis(Object *ob)
+bool BKE_mball_is_basis(const Object *ob)
 {
   /* Meta-Ball Basis Notes from Blender-2.5x
    * =======================================
@@ -370,7 +370,7 @@ bool BKE_mball_is_basis(Object *ob)
   return (!isdigit(ob->id.name[len - 1]));
 }
 
-bool BKE_mball_is_basis_for(Object *ob1, Object *ob2)
+bool BKE_mball_is_same_group(const Object *ob1, const Object *ob2)
 {
   int basis1nr, basis2nr;
   char basis1name[MAX_ID_NAME], basis2name[MAX_ID_NAME];
@@ -383,11 +383,12 @@ bool BKE_mball_is_basis_for(Object *ob1, Object *ob2)
   BLI_split_name_num(basis1name, &basis1nr, ob1->id.name + 2, '.');
   BLI_split_name_num(basis2name, &basis2nr, ob2->id.name + 2, '.');
 
-  if (STREQ(basis1name, basis2name)) {
-    return BKE_mball_is_basis(ob1);
-  }
+  return STREQ(basis1name, basis2name);
+}
 
-  return false;
+bool BKE_mball_is_basis_for(const Object *ob1, const Object *ob2)
+{
+  return BKE_mball_is_same_group(ob1, ob2) && BKE_mball_is_basis(ob1);
 }
 
 bool BKE_mball_is_any_selected(const MetaBall *mb)
@@ -422,41 +423,82 @@ bool BKE_mball_is_any_unselected(const MetaBall *mb)
   return false;
 }
 
-void BKE_mball_properties_copy(Scene *scene, Object *active_object)
+static void mball_data_properties_copy(MetaBall *mb_dst, MetaBall *mb_src)
 {
-  Scene *sce_iter = scene;
-  Base *base;
-  Object *ob;
-  MetaBall *active_mball = (MetaBall *)active_object->data;
-  int basisnr, obnr;
-  char basisname[MAX_ID_NAME], obname[MAX_ID_NAME];
-  SceneBaseIter iter;
-
-  BLI_split_name_num(basisname, &basisnr, active_object->id.name + 2, '.');
-
-  /* Pass depsgraph as NULL, which means we will not expand into
-   * duplis unlike when we generate the meta-ball. Expanding duplis
-   * would not be compatible when editing multiple view layers. */
-  BKE_scene_base_iter_next(NULL, &iter, &sce_iter, 0, NULL, NULL);
-  while (BKE_scene_base_iter_next(NULL, &iter, &sce_iter, 1, &base, &ob)) {
-    if (ob->type == OB_MBALL) {
-      if (ob != active_object) {
-        BLI_split_name_num(obname, &obnr, ob->id.name + 2, '.');
-
-        /* Object ob has to be in same "group" ... it means, that it has to have
-         * same base of its name */
-        if (STREQ(obname, basisname)) {
-          MetaBall *mb = ob->data;
-
-          /* Copy properties from selected/edited metaball */
-          mb->wiresize = active_mball->wiresize;
-          mb->rendersize = active_mball->rendersize;
-          mb->thresh = active_mball->thresh;
-          mb->flag = active_mball->flag;
-          DEG_id_tag_update(&mb->id, 0);
-        }
+  mb_dst->wiresize = mb_src->wiresize;
+  mb_dst->rendersize = mb_src->rendersize;
+  mb_dst->thresh = mb_src->thresh;
+  mb_dst->flag = mb_src->flag;
+  DEG_id_tag_update(&mb_dst->id, 0);
+}
+
+void BKE_mball_properties_copy(Main *bmain, MetaBall *metaball_src)
+{
+  /* WARNING: This code does not cover all potential corner-cases. E.g. if:
+   *   |   Object   |   ObData   |
+   *   | ---------- | ---------- |
+   *   | Meta_A     | Meta_A     |
+   *   | Meta_A.001 | Meta_A.001 |
+   *   | Meta_B     | Meta_A     |
+   *   | Meta_B.001 | Meta_B.001 |
+   *
+   * Calling this function with `metaball_src` being `Meta_A.001` will update `Meta_A`, but NOT
+   * `Meta_B.001`. So in the 'Meta_B' family, the two metaballs will have unmatching settings now.
+   *
+   * Solving this case would drastically increase the complexity of this code though, so don't
+   * think it would be worth it.
+   */
+  for (Object *ob_src = bmain->objects.first; ob_src != NULL && !ID_IS_LINKED(ob_src);) {
+    if (ob_src->data != metaball_src) {
+      ob_src = ob_src->id.next;
+      continue;
+    }
+
+    /* In this code we take advantage of two facts:
+     *  - MetaBalls of the same family have the same basis name,
+     *  - IDs are sorted by name in their Main listbase.
+     * So, all MetaBall objects of the same family are contiguous in bmain list (potentially mixed
+     * with non-meta-ball objects with same basis names).
+     *
+     * Using this, it is possible to process the whole set of meta-balls with a single loop on the
+     * whole list of Objects, though additionally going backward on part of the list in some cases.
+     */
+    Object *ob_iter = NULL;
+    int obactive_nr, ob_nr;
+    char obactive_name[MAX_ID_NAME], ob_name[MAX_ID_NAME];
+    BLI_split_name_num(obactive_name, &obactive_nr, ob_src->id.name + 2, '.');
+
+    for (ob_iter = ob_src->id.prev; ob_iter != NULL; ob_iter = ob_iter->id.prev) {
+      if (ob_iter->id.name[2] != obactive_name[0]) {
+        break;
+      }
+      if (ob_iter->type != OB_MBALL || ob_iter->data == metaball_src) {
+        continue;
+      }
+      BLI_split_name_num(ob_name, &ob_nr, ob_iter->id.name + 2, '.');
+      if (!STREQ(obactive_name, ob_name)) {
+        break;
+      }
+
+      mball_data_properties_copy(ob_iter->data, metaball_src);
+    }
+
+    for (ob_iter = ob_src->id.next; ob_iter != NULL; ob_iter = ob_iter->id.next) {
+      if (ob_iter->id.name[2] != obactive_name[0] || ID_IS_LINKED(ob_iter)) {
+        break;
+      }
+      if (ob_iter->type != OB_MBALL || ob_iter->data == metaball_src) {
+        continue;
       }
+      BLI_split_name_num(ob_name, &ob_nr, ob_iter->id.name + 2, '.');
+      if (!STREQ(obactive_name, ob_name)) {
+        break;
+      }
+
+      mball_data_properties_copy(ob_iter->data, metaball_src);
     }
+
+    ob_src = ob_iter;
   }
 }
 
diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c
index a0b25cf60b2..cf4182243b9 100644
--- a/source/blender/makesrna/intern/rna_access.c
+++ b/source/blender/makesrna/intern/rna_access.c
@@ -2141,6 +2141,7 @@ void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
 
 void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop)
 {
+  BLI_assert(bmain != NULL);
   rna_property_update(NULL, bmain, scene, ptr, prop);
 }
 
diff --git a/source/blender/makesrna/intern/rna_meta.c b/source/blender/makesrna/intern/rna_meta.c
index e6cf743e167..898208e0ba1 100644
--- a/source/blender/makesrna/intern/rna_meta.c
+++ b/source/blender/makesrna/intern/rna_meta.c
@@ -81,18 +81,17 @@ static void rna_MetaBall_redraw_data(Main *UNUSED(bmain), Scene *UNUSED(scene),
   WM_main_add_notifier(NC_GEOM | ND_DATA, id);
 }
 
-static void rna_MetaBall_update_data(Main *bmain, Scene *scene, PointerRNA *ptr)
+static void rna_MetaBall_update_data(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
 {
   MetaBall *mb = (MetaBall *)ptr->owner_id;
   Object *ob;
 
-  /* cheating way for importers to avoid slow updates */
+  /* NOTE: The check on the number of users allows to avoid many repetitive (slow) updates in some
+   * cases, like e.g. importers. Calling `BKE_mball_properties_copy` on an obdata with no users
+   * would be meaningless anyway, as by definition it would not be used by any object, so not part
+   * of any meta-ball group. */
   if (mb->id.us > 0) {
-    for (ob = bmain->objects.first; ob; ob = ob->id.next) {
-      if (ob->data == mb) {
-        BKE_mball_properties_copy(scene, ob);
-      }
-    }
+    BKE_mball_properties_copy(bmain, mb);
 
     DEG_id_tag_update(&mb->id, 0);
     WM_main_add_notifier(NC_GEOM | ND_DATA, mb);



More information about the Bf-blender-cvs mailing list