Tweak API to support adding evaluated meshes to main database
authorSergey Sharybin <sergey.vfx@gmail.com>
Thu, 16 May 2019 11:49:21 +0000 (13:49 +0200)
committerSergey Sharybin <sergey.vfx@gmail.com>
Thu, 16 May 2019 14:42:16 +0000 (16:42 +0200)
One of the usecases is to create mesh from an object is a manner similar to
how Apply Modifiers does it, and have it in the bmain so it can be referenced
by other objects.

This usecase is something what went unnoticed in the previous API changes, so
here is a followup.

Summary of changes:

* bpy.meshes.new_from_object() behaves almost the same as before this change.
  The difference now is that it now ensures all referenced data-blocks are
original (for example, materials referenced by the mesh).

* object.to_mesh() now creates free-standing Mesh data-block which is outside
  of any bmain. The object owns it, which guarantees the memory never leaks.

  It is possible to force free memory by calling object.to_mesh_clear().

Reviewers: brecht

Reviewed By: brecht

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

15 files changed:
doc/python_api/examples/bpy.types.Depsgraph.4.py
doc/python_api/examples/bpy.types.Depsgraph.5.py
doc/python_api/examples/bpy.types.Depsgraph.6.py [new file with mode: 0644]
intern/cycles/blender/blender_util.h
source/blender/blenkernel/BKE_mesh.h
source/blender/blenkernel/BKE_object.h
source/blender/blenkernel/intern/mesh_convert.c
source/blender/blenkernel/intern/object.c
source/blender/editors/object/object_bake_api.c
source/blender/freestyle/intern/blender_interface/BlenderFileLoader.cpp
source/blender/makesdna/DNA_object_types.h
source/blender/makesrna/intern/rna_internal.h
source/blender/makesrna/intern/rna_main_api.c
source/blender/makesrna/intern/rna_object_api.c
tests/python/bl_alembic_import_test.py

index 5c7b76e..41f94a3 100644 (file)
@@ -2,8 +2,8 @@
 Dependency graph: Object.to_mesh()
 +++++++++++++++++++++++++++++++++++
 
-Object.to_mesh() (and bpy.data.meshes.new_from_object()) are closely interacting with dependency
-graph: their behavior depends on whether they are used on original or evaluated object.
+Object.to_mesh() is closely interacting with dependency graph: its behavior depends on whether it
+is used on original or evaluated object.
 
 When is used on original object, the result mesh is calculated from the object without taking
 animation or modifiers into account:
@@ -14,7 +14,7 @@ animation or modifiers into account:
 
 When is used on evaluated object all modifiers are taken into account.
 
-.. note:: The result mesh is added to the main database.
+.. note:: The result mesh is owned by the object. It can be freed by calling `object.to_mesh_clear()`.
 .. note:: If object does not have geometry (i.e. camera) the functions returns None.
 """
 import bpy
@@ -40,13 +40,13 @@ class OBJECT_OT_object_to_mesh(bpy.types.Operator):
         mesh_from_orig = object.to_mesh()
         self.report({'INFO'}, f"{len(mesh_from_orig.vertices)} in new mesh without modifiers.")
         # Remove temporary mesh.
-        bpy.data.meshes.remove(mesh_from_orig)
+        object.to_mesh_clear()
         # Invoke to_mesh() for evaluated object.
         object_eval = object.evaluated_get(depsgraph)
         mesh_from_eval = object_eval.to_mesh()
         self.report({'INFO'}, f"{len(mesh_from_eval.vertices)} in new mesh with modifiers.")
         # Remove temporary mesh.
-        bpy.data.meshes.remove(mesh_from_eval)
+        object_eval.to_mesh_clear()
         return {'FINISHED'}
 
 
index 781d020..a37d267 100644 (file)
@@ -1,60 +1,56 @@
 """
-Dependency graph: Simple exporter
-+++++++++++++++++++++++++++++++++
+Dependency graph: bpy.data.meshes.new_from_object()
++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-This example is a combination of all previous ones, and shows how to write a simple exporter
-script.
+Object.to_mesh() is closely interacting with dependency graph: its behavior depends on whether it
+is used on original or evaluated object.
+
+When is used on original object, the result mesh is calculated from the object without taking
+animation or modifiers into account:
+
+- For meshes this is similar to duplicating the source mesh.
+- For curves this disables own modifiers, and modifiers of objects used as bevel and taper.
+- For metaballs this produces an empty mesh since polygonization is done as a modifier evaluation.
+
+When is used on evaluated object all modifiers are taken into account.
+
+All the references (such as materials) are re-mapped to original. This ensures validity and
+consistency of the main database.
+
+.. note:: The result mesh is added to the main database.
+.. note:: If object does not have geometry (i.e. camera) the functions returns None.
 """
 import bpy
 
 
-class OBJECT_OT_simple_exporter(bpy.types.Operator):
-    """Simple (fake) exporter of selected objects"""
-    bl_label = "DEG Export Selected"
-    bl_idname = "object.simple_exporter"
-
-    apply_modifiers: bpy.props.BoolProperty(name="Apply Modifiers")
+class OBJECT_OT_mesh_from_object(bpy.types.Operator):
+    """Convert selected object to mesh and show number of vertices"""
+    bl_label = "DEG Mesh From Object"
+    bl_idname = "object.mesh_from_object"
 
     def execute(self, context):
+        # Access input original object.
+        object = context.object
+        if object is None:
+            self.report({'INFO'}, "No active mesh object to convert to mesh")
+            return {'CANCELLED'}
+        # Avoid annoying None checks later on.
+        if object.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
+            self.report({'INFO'}, "Object can not be converted to mesh")
+            return {'CANCELLED'}
         depsgraph = context.evaluated_depsgraph_get()
-        for object_instance in depsgraph.object_instances:
-            if not self.is_object_instance_from_selected(object_instance):
-                # We only export selected objects
-                continue
-            # NOTE: This will create a mesh for every instance, which is not ideal at all. In
-            # reality destination format will support some sort of instancing mechanism, so the
-            # code here will simply say "instance this object at object_instance.matrix_world".
-            mesh = self.create_mesh_for_object_instance(object_instance)
-            if mesh is None:
-                # Happens for non-geometry objects.
-                continue
-            print(f"Exporting mesh with {len(mesh.vertices)} vertices "
-                   f"at {object_instance.matrix_world}")
-            bpy.data.meshes.remove(mesh)
-
+        object_eval = object.evaluated_get(depsgraph)
+        mesh_from_eval = bpy.data.meshes.new_from_object(object_eval)
+        self.report({'INFO'}, f"{len(mesh_from_eval.vertices)} in new mesh, and is ready for use!")
         return {'FINISHED'}
 
-    def is_object_instance_from_selected(self, object_instance):
-        # For instanced objects we check selection of their instancer (more accurately: check
-        # selection status of the original object corresponding to the instancer).
-        if object_instance.parent:
-            return object_instance.parent.original.select_get()
-        # For non-instanced objects we check selection state of the original object.
-        return object_instance.object.original.select_get()
-
-    def create_mesh_for_object_instance(self, object_instance):
-        if self.apply_modifiers:
-            return object_instance.object.to_mesh()
-        else:
-            return object_instance.object.original.to_mesh()
-
 
 def register():
-    bpy.utils.register_class(OBJECT_OT_simple_exporter)
+    bpy.utils.register_class(OBJECT_OT_mesh_from_object)
 
 
 def unregister():
-    bpy.utils.unregister_class(OBJECT_OT_simple_exporter)
+    bpy.utils.unregister_class(OBJECT_OT_mesh_from_object)
 
 
 if __name__ == "__main__":
diff --git a/doc/python_api/examples/bpy.types.Depsgraph.6.py b/doc/python_api/examples/bpy.types.Depsgraph.6.py
new file mode 100644 (file)
index 0000000..781d020
--- /dev/null
@@ -0,0 +1,61 @@
+"""
+Dependency graph: Simple exporter
++++++++++++++++++++++++++++++++++
+
+This example is a combination of all previous ones, and shows how to write a simple exporter
+script.
+"""
+import bpy
+
+
+class OBJECT_OT_simple_exporter(bpy.types.Operator):
+    """Simple (fake) exporter of selected objects"""
+    bl_label = "DEG Export Selected"
+    bl_idname = "object.simple_exporter"
+
+    apply_modifiers: bpy.props.BoolProperty(name="Apply Modifiers")
+
+    def execute(self, context):
+        depsgraph = context.evaluated_depsgraph_get()
+        for object_instance in depsgraph.object_instances:
+            if not self.is_object_instance_from_selected(object_instance):
+                # We only export selected objects
+                continue
+            # NOTE: This will create a mesh for every instance, which is not ideal at all. In
+            # reality destination format will support some sort of instancing mechanism, so the
+            # code here will simply say "instance this object at object_instance.matrix_world".
+            mesh = self.create_mesh_for_object_instance(object_instance)
+            if mesh is None:
+                # Happens for non-geometry objects.
+                continue
+            print(f"Exporting mesh with {len(mesh.vertices)} vertices "
+                   f"at {object_instance.matrix_world}")
+            bpy.data.meshes.remove(mesh)
+
+        return {'FINISHED'}
+
+    def is_object_instance_from_selected(self, object_instance):
+        # For instanced objects we check selection of their instancer (more accurately: check
+        # selection status of the original object corresponding to the instancer).
+        if object_instance.parent:
+            return object_instance.parent.original.select_get()
+        # For non-instanced objects we check selection state of the original object.
+        return object_instance.object.original.select_get()
+
+    def create_mesh_for_object_instance(self, object_instance):
+        if self.apply_modifiers:
+            return object_instance.object.to_mesh()
+        else:
+            return object_instance.object.original.to_mesh()
+
+
+def register():
+    bpy.utils.register_class(OBJECT_OT_simple_exporter)
+
+
+def unregister():
+    bpy.utils.unregister_class(OBJECT_OT_simple_exporter)
+
+
+if __name__ == "__main__":
+    register()
index 2a964d0..972d729 100644 (file)
@@ -43,7 +43,7 @@ CCL_NAMESPACE_BEGIN
 void python_thread_state_save(void **python_thread_state);
 void python_thread_state_restore(void **python_thread_state);
 
-static inline BL::Mesh object_to_mesh(BL::BlendData &data,
+static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
                                       BL::Object &object,
                                       BL::Depsgraph & /*depsgraph*/,
                                       bool /*calc_undeformed*/,
@@ -75,11 +75,11 @@ static inline BL::Mesh object_to_mesh(BL::BlendData &data,
      * UV are not empty. */
     if (mesh.is_editmode() ||
         (mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) {
-      mesh = data.meshes.new_from_object(object);
+      mesh = object.to_mesh();
     }
   }
   else {
-    mesh = data.meshes.new_from_object(object);
+    mesh = object.to_mesh();
   }
 
 #if 0
@@ -102,11 +102,13 @@ static inline BL::Mesh object_to_mesh(BL::BlendData &data,
   return mesh;
 }
 
-static inline void free_object_to_mesh(BL::BlendData &data, BL::Object &object, BL::Mesh &mesh)
+static inline void free_object_to_mesh(BL::BlendData & /*data*/,
+                                       BL::Object &object,
+                                       BL::Mesh &mesh)
 {
   /* Free mesh if we didn't just use the existing one. */
   if (object.data().ptr.data != mesh.ptr.data) {
-    data.meshes.remove(mesh, false, true, false);
+    object.to_mesh_clear();
   }
 }
 
index 8ea5445..c410946 100644 (file)
@@ -208,7 +208,13 @@ float (*BKE_mesh_vertexCos_get(const struct Mesh *me, int *r_numVerts))[3];
 
 void BKE_mesh_split_faces(struct Mesh *mesh, bool free_loop_normals);
 
-struct Mesh *BKE_mesh_new_from_object(struct Main *bmain, struct Object *object);
+/* Create new mesh from the given object at its current state.
+ * The owner of this mesh is unknown, it is up to the caller to decide. */
+struct Mesh *BKE_mesh_new_from_object(struct Object *object);
+
+/* This is a version of BKE_mesh_new_from_object() which stores mesh in the given main database. */
+struct Mesh *BKE_mesh_new_from_object_to_bmain(struct Main *bmain, struct Object *object);
+
 struct Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph,
                                                   struct Scene *scene,
                                                   struct Object *ob_eval,
index c373fbf..aa4d969 100644 (file)
@@ -396,6 +396,15 @@ bool BKE_object_empty_image_frame_is_visible_in_view3d(const struct Object *ob,
 bool BKE_object_empty_image_data_is_visible_in_view3d(const struct Object *ob,
                                                       const struct RegionView3D *rv3d);
 
+/* This is an utility function for Python's object.to_mesh() (the naming is not very clear though).
+ * The result is owned by the object.
+ *
+ * The mesh will be freed when object is re-evaluated or is destroyed. It is possible to force to
+ * clear memory sued by this mesh by calling BKE_object_to_mesh_clear(). */
+struct Mesh *BKE_object_to_mesh(struct Object *object);
+
+void BKE_object_to_mesh_clear(struct Object *object);
+
 #ifdef __cplusplus
 }
 #endif
index a5c97cd..6d0245c 100644 (file)
@@ -38,6 +38,7 @@
 #include "BKE_main.h"
 #include "BKE_DerivedMesh.h"
 #include "BKE_key.h"
+#include "BKE_library_query.h"
 #include "BKE_mesh.h"
 #include "BKE_mesh_runtime.h"
 #include "BKE_modifier.h"
@@ -612,7 +613,13 @@ void BKE_mesh_from_nurbs_displist(Main *bmain,
     }
 
     /* make mesh */
-    me = BKE_mesh_add(bmain, obdata_name);
+    if (bmain != NULL) {
+      me = BKE_mesh_add(bmain, obdata_name);
+    }
+    else {
+      me = BKE_id_new_nomain(ID_ME, obdata_name);
+    }
+
     me->totvert = totvert;
     me->totedge = totedge;
     me->totloop = totloop;
@@ -632,7 +639,13 @@ void BKE_mesh_from_nurbs_displist(Main *bmain,
     BKE_mesh_calc_normals(me);
   }
   else {
-    me = BKE_mesh_add(bmain, obdata_name);
+    if (bmain != NULL) {
+      me = BKE_mesh_add(bmain, obdata_name);
+    }
+    else {
+      me = BKE_id_new_nomain(ID_ME, obdata_name);
+    }
+
     ob->runtime.mesh_eval = NULL;
     BKE_mesh_nomain_to_mesh(me_eval, me, ob, &CD_MASK_MESH, true);
   }
@@ -662,16 +675,18 @@ void BKE_mesh_from_nurbs_displist(Main *bmain,
   ob->type = OB_MESH;
 
   /* other users */
-  ob1 = bmain->objects.first;
-  while (ob1) {
-    if (ob1->data == cu) {
-      ob1->type = OB_MESH;
-
-      id_us_min((ID *)ob1->data);
-      ob1->data = ob->data;
-      id_us_plus((ID *)ob1->data);
+  if (bmain != NULL) {
+    ob1 = bmain->objects.first;
+    while (ob1) {
+      if (ob1->data == cu) {
+        ob1->type = OB_MESH;
+
+        id_us_min((ID *)ob1->data);
+        ob1->data = ob->data;
+        id_us_plus((ID *)ob1->data);
+      }
+      ob1 = ob1->id.next;
     }
-    ob1 = ob1->id.next;
   }
 
   if (temporary) {
@@ -995,7 +1010,7 @@ static void curve_to_mesh_eval_ensure(Object *object)
   BKE_object_free_curve_cache(&taper_object);
 }
 
-static Mesh *mesh_new_from_curve_type_object(Main *bmain, Object *object)
+static Mesh *mesh_new_from_curve_type_object(Object *object)
 {
   Curve *curve = object->data;
   const bool uv_from_orco = (curve->flag & CU_UV_ORCO) != 0;
@@ -1014,7 +1029,7 @@ static Mesh *mesh_new_from_curve_type_object(Main *bmain, Object *object)
   temp_curve->editnurb = NULL;
 
   /* Convert to mesh. */
-  BKE_mesh_from_nurbs_displist(bmain,
+  BKE_mesh_from_nurbs_displist(NULL,
                                temp_object,
                                &temp_object->runtime.curve_cache->disp,
                                uv_from_orco,
@@ -1037,7 +1052,7 @@ static Mesh *mesh_new_from_curve_type_object(Main *bmain, Object *object)
   return mesh_result;
 }
 
-static Mesh *mesh_new_from_mball_object(Main *bmain, Object *object)
+static Mesh *mesh_new_from_mball_object(Object *object)
 {
   MetaBall *mball = (MetaBall *)object->data;
 
@@ -1048,10 +1063,10 @@ static Mesh *mesh_new_from_mball_object(Main *bmain, Object *object)
    * We create empty mesh so scripters don't run into None objects. */
   if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == NULL ||
       BLI_listbase_is_empty(&object->runtime.curve_cache->disp)) {
-    return BKE_mesh_add(bmain, mball->id.name + 2);
+    return BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
   }
 
-  Mesh *mesh_result = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2);
+  Mesh *mesh_result = BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
   BKE_mesh_from_metaball(&object->runtime.curve_cache->disp, mesh_result);
 
   /* Copy materials. */
@@ -1066,29 +1081,32 @@ static Mesh *mesh_new_from_mball_object(Main *bmain, Object *object)
   return mesh_result;
 }
 
-static Mesh *mesh_new_from_mesh_object(Main *bmain, Object *object)
+static Mesh *mesh_new_from_mesh_object(Object *object)
 {
   Mesh *mesh_input = object->data;
   Mesh *mesh_result = NULL;
-  BKE_id_copy_ex(bmain, &mesh_input->id, (ID **)&mesh_result, 0);
+  BKE_id_copy_ex(NULL,
+                 &mesh_input->id,
+                 (ID **)&mesh_result,
+                 LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT);
   /* NOTE: Materials should already be copied. */
   return mesh_result;
 }
 
-Mesh *BKE_mesh_new_from_object(Main *bmain, Object *object)
+Mesh *BKE_mesh_new_from_object(Object *object)
 {
   Mesh *new_mesh = NULL;
   switch (object->type) {
     case OB_FONT:
     case OB_CURVE:
     case OB_SURF:
-      new_mesh = mesh_new_from_curve_type_object(bmain, object);
+      new_mesh = mesh_new_from_curve_type_object(object);
       break;
     case OB_MBALL:
-      new_mesh = mesh_new_from_mball_object(bmain, object);
+      new_mesh = mesh_new_from_mball_object(object);
       break;
     case OB_MESH:
-      new_mesh = mesh_new_from_mesh_object(bmain, object);
+      new_mesh = mesh_new_from_mesh_object(object);
       break;
     default:
       /* Object does not have geometry data. */
@@ -1098,15 +1116,55 @@ Mesh *BKE_mesh_new_from_object(Main *bmain, Object *object)
     /* Happens in special cases like request of mesh for non-mother meta ball. */
     return NULL;
   }
-  /* The result must have 0 users, since it's just a mesh which is free-dangling in the main
-   * database. All the copy and allocation functions to manipulate new Mesh datablock are ensuring
-   * an user.
-   * Here we control that user management went the way it's expected, and cancel out the user. */
-  BLI_assert(new_mesh->id.us == 1);
-  id_us_min(&new_mesh->id);
+  /* The result must have 0 users, since it's just a mesh which is free-dangling data-block.
+   * All the conversion functions are supposed to ensure mesh is not counted. */
+  BLI_assert(new_mesh->id.us == 0);
   return new_mesh;
 }
 
+static int foreach_libblock_make_original_and_usercount_callback(void *user_data_v,
+                                                                 ID *id_self,
+                                                                 ID **id_p,
+                                                                 int cb_flag)
+{
+  UNUSED_VARS(user_data_v, id_self, cb_flag);
+  if (*id_p == NULL) {
+    return IDWALK_RET_NOP;
+  }
+  *id_p = DEG_get_original_id(*id_p);
+  id_us_plus(*id_p);
+  return IDWALK_RET_NOP;
+}
+
+Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, Object *object)
+{
+  Mesh *mesh = BKE_mesh_new_from_object(object);
+
+  /* Make sure mesh only points original datablocks, also increase users of materials and other
+   * possibly referenced data-blocks.
+   *
+   * Going to original data-blocks is required to have bmain in a consistent state, where
+   * everything is only allowed to reference original data-blocks.
+   *
+   * user-count is required is because so far mesh was in a limbo, where library management does
+   * not perform any user management (i.e. copy of a mesh will not increase users of materials). */
+  BKE_library_foreach_ID_link(
+      NULL, &mesh->id, foreach_libblock_make_original_and_usercount_callback, NULL, IDWALK_NOP);
+
+  /* Append the mesh to bmain.
+   * We do it a bit longer way since there is no simple and clear way of adding existing datablock
+   * to the bmain. So we allocate new empty mesh in the bmain (which guarantess all the naming and
+   * orders and flags) and move the temporary mesh in place there. */
+  Mesh *mesh_in_bmain = BKE_mesh_add(bmain, mesh->id.name + 2);
+  BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, NULL, &CD_MASK_MESH, true);
+
+  /* Make sure user count from BKE_mesh_add() is the one we expect here and bring it down to 0. */
+  BLI_assert(mesh_in_bmain->id.us == 1);
+  id_us_min(&mesh_in_bmain->id);
+
+  return mesh_in_bmain;
+}
+
 static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src)
 {
   KeyBlock *kb;
index 78f2b10..95be7f8 100644 (file)
@@ -469,6 +469,7 @@ void BKE_object_free_derived_caches(Object *ob)
     ob->runtime.mesh_deform_eval = NULL;
   }
 
+  BKE_object_to_mesh_clear(ob);
   BKE_object_free_curve_cache(ob);
 
   /* clear grease pencil data */
@@ -4497,3 +4498,21 @@ void BKE_object_update_select_id(struct Main *bmain)
     ob = ob->id.next;
   }
 }
+
+Mesh *BKE_object_to_mesh(Object *object)
+{
+  BKE_object_to_mesh_clear(object);
+
+  Mesh *mesh = BKE_mesh_new_from_object(object);
+  object->runtime.object_as_temp_mesh = mesh;
+  return mesh;
+}
+
+void BKE_object_to_mesh_clear(Object *object)
+{
+  if (object->runtime.object_as_temp_mesh == NULL) {
+    return;
+  }
+  BKE_id_free(NULL, object->runtime.object_as_temp_mesh);
+  object->runtime.object_as_temp_mesh = NULL;
+}
index 4f26ae2..f87342a 100644 (file)
@@ -705,9 +705,9 @@ static size_t initialize_internal_images(BakeImages *bake_images, ReportList *re
 }
 
 /* create new mesh with edit mode changes and modifiers applied */
-static Mesh *bake_mesh_new_from_object(Main *bmain, Object *object)
+static Mesh *bake_mesh_new_from_object(Object *object)
 {
-  Mesh *me = BKE_mesh_new_from_object(bmain, object);
+  Mesh *me = BKE_object_to_mesh(object);
 
   if (me->flag & ME_AUTOSMOOTH) {
     BKE_mesh_split_faces(me, true);
@@ -903,7 +903,7 @@ static int bake(Render *re,
   ob_low_eval = DEG_get_evaluated_object(depsgraph, ob_low);
 
   /* get the mesh as it arrives in the renderer */
-  me_low = bake_mesh_new_from_object(bmain, ob_low_eval);
+  me_low = bake_mesh_new_from_object(ob_low_eval);
 
   /* populate the pixel array with the face data */
   if ((is_selected_to_active && (ob_cage == NULL) && is_cage) == false) {
@@ -917,7 +917,7 @@ static int bake(Render *re,
 
     /* prepare cage mesh */
     if (ob_cage) {
-      me_cage = bake_mesh_new_from_object(bmain, ob_cage_eval);
+      me_cage = bake_mesh_new_from_object(ob_cage_eval);
       if ((me_low->totpoly != me_cage->totpoly) || (me_low->totloop != me_cage->totloop)) {
         BKE_report(reports,
                    RPT_ERROR,
@@ -946,7 +946,7 @@ static int bake(Render *re,
         md = md_next;
       }
 
-      me_cage = bake_mesh_new_from_object(bmain, ob_low_eval);
+      me_cage = BKE_object_to_mesh(ob_low_eval);
       RE_bake_pixels_populate(me_cage, pixel_array_low, num_pixels, &bake_images, uv_layer);
     }
 
@@ -965,7 +965,7 @@ static int bake(Render *re,
       highpoly[i].ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter);
       highpoly[i].ob_eval->restrictflag &= ~OB_RESTRICT_RENDER;
       highpoly[i].ob_eval->base_flag |= (BASE_VISIBLE | BASE_ENABLED_RENDER);
-      highpoly[i].me = bake_mesh_new_from_object(bmain, highpoly[i].ob_eval);
+      highpoly[i].me = BKE_object_to_mesh(highpoly[i].ob_eval);
 
       /* lowpoly to highpoly transformation matrix */
       copy_m4_m4(highpoly[i].obmat, highpoly[i].ob->obmat);
@@ -1088,7 +1088,7 @@ static int bake(Render *re,
           }
 
           /* Evaluate modifiers again. */
-          me_nores = BKE_mesh_new_from_object(bmain, ob_low_eval);
+          me_nores = BKE_object_to_mesh(ob_low_eval);
           RE_bake_pixels_populate(me_nores, pixel_array_low, num_pixels, &bake_images, uv_layer);
 
           RE_bake_normal_world_to_tangent(pixel_array_low,
@@ -1098,7 +1098,7 @@ static int bake(Render *re,
                                           me_nores,
                                           normal_swizzle,
                                           ob_low_eval->obmat);
-          BKE_id_free(bmain, me_nores);
+          BKE_object_to_mesh_clear(ob_low_eval);
 
           if (md) {
             md->mode = mode;
@@ -1222,7 +1222,7 @@ cleanup:
     int i;
     for (i = 0; i < tot_highpoly; i++) {
       if (highpoly[i].me) {
-        BKE_id_free(bmain, highpoly[i].me);
+        BKE_object_to_mesh_clear(highpoly[i].ob_eval);
       }
     }
     MEM_freeN(highpoly);
@@ -1253,11 +1253,11 @@ cleanup:
   }
 
   if (me_low) {
-    BKE_id_free(bmain, me_low);
+    BKE_object_to_mesh_clear(ob_low_eval);
   }
 
   if (me_cage) {
-    BKE_id_free(bmain, me_cage);
+    BKE_object_to_mesh_clear(ob_cage_eval);
   }
 
   DEG_graph_free(depsgraph);
index ed553c9..ee13583 100644 (file)
@@ -23,6 +23,7 @@
 #include "BLI_utildefines.h"
 
 #include "BKE_global.h"
+#include "BKE_object.h"
 
 #include <sstream>
 
@@ -98,11 +99,11 @@ NodeGroup *BlenderFileLoader::Load()
       continue;
     }
 
-    Mesh *mesh = BKE_mesh_new_from_object(_re->main, ob);
+    Mesh *mesh = BKE_object_to_mesh(ob);
 
     if (mesh) {
       insertShapeNode(ob, mesh, ++id);
-      BKE_id_free_ex(_re->main, &mesh->id, LIB_ID_FREE_NO_UI_USER, true);
+      BKE_object_to_mesh_clear(ob);
     }
   }
   DEG_OBJECT_ITER_END;
index a73c762..0b2f3d6 100644 (file)
@@ -43,6 +43,7 @@ struct DerivedMesh;
 struct FluidsimSettings;
 struct GpencilBatchCache;
 struct Ipo;
+struct Mesh;
 struct Material;
 struct Object;
 struct PartDeflect;
@@ -160,6 +161,10 @@ typedef struct Object_Runtime {
    */
   struct Mesh *mesh_deform_eval;
 
+  /* This is a mesh representation of corresponding object.
+   * It created when Python calls `object.to_mesh()`. */
+  struct Mesh *object_as_temp_mesh;
+
   /** Runtime evaluated curve-specific data, not stored in the file. */
   struct CurveCache *curve_cache;
 
index d93f8c4..6402416 100644 (file)
@@ -548,9 +548,6 @@ PointerRNA rna_pointer_inherit_refine(struct PointerRNA *ptr, struct StructRNA *
 
 int rna_parameter_size(struct PropertyRNA *parm);
 
-struct Mesh *rna_Main_meshes_new_from_object(struct Main *bmain,
-                                             struct ReportList *reports,
-                                             struct Object *object);
 
 /* XXX, these should not need to be defined here~! */
 struct MTex *rna_mtex_texture_slots_add(struct ID *self,
index 8f48738..f6fee25 100644 (file)
@@ -317,7 +317,7 @@ static Mesh *rna_Main_meshes_new(Main *bmain, const char *name)
 }
 
 /* copied from Mesh_getFromObject and adapted to RNA interface */
-Mesh *rna_Main_meshes_new_from_object(Main *bmain, ReportList *reports, Object *object)
+static Mesh *rna_Main_meshes_new_from_object(Main *bmain, ReportList *reports, Object *object)
 {
   switch (object->type) {
     case OB_FONT:
@@ -331,7 +331,7 @@ Mesh *rna_Main_meshes_new_from_object(Main *bmain, ReportList *reports, Object *
       return NULL;
   }
 
-  return BKE_mesh_new_from_object(bmain, object);
+  return BKE_mesh_new_from_object_to_bmain(bmain, object);
 }
 
 static Light *rna_Main_lights_new(Main *bmain, const char *name, int type)
index d94abd4..da00e1f 100644 (file)
@@ -375,11 +375,28 @@ static void rna_Object_camera_fit_coords(
 }
 
 /* copied from Mesh_getFromObject and adapted to RNA interface */
-static Mesh *rna_Object_to_mesh(Object *object, bContext *C, ReportList *reports)
+static Mesh *rna_Object_to_mesh(Object *object, ReportList *reports)
 {
-  Main *bmain = CTX_data_main(C);
+  /* TODO(sergey): Make it more re-usable function, de-duplicate with
+   * rna_Main_meshes_new_from_object. */
+  switch (object->type) {
+    case OB_FONT:
+    case OB_CURVE:
+    case OB_SURF:
+    case OB_MBALL:
+    case OB_MESH:
+      break;
+    default:
+      BKE_report(reports, RPT_ERROR, "Object does not have geometry data");
+      return NULL;
+  }
+
+  return BKE_object_to_mesh(object);
+}
 
-  return rna_Main_meshes_new_from_object(bmain, reports, object);
+static void rna_Object_to_mesh_clear(Object *object)
+{
+  BKE_object_to_mesh_clear(object);
 }
 
 static PointerRNA rna_Object_shape_key_add(
@@ -875,16 +892,17 @@ void RNA_api_object(StructRNA *srna)
 
   /* mesh */
   func = RNA_def_function(srna, "to_mesh", "rna_Object_to_mesh");
-  RNA_def_function_ui_description(func,
-                                  "Create a Mesh data-block from the current state of the object");
-  RNA_def_function_flag(func, FUNC_USE_REPORTS | FUNC_USE_CONTEXT);
-  parm = RNA_def_pointer(func,
-                         "mesh",
-                         "Mesh",
-                         "",
-                         "Mesh created from object, remove it if it is only used for export");
+  RNA_def_function_ui_description(
+      func,
+      "Create a Mesh data-block from the current state of the object. The object owns the "
+      "data-block. To force free it use to_mesh_clear()");
+  RNA_def_function_flag(func, FUNC_USE_REPORTS);
+  parm = RNA_def_pointer(func, "mesh", "Mesh", "", "Mesh created from object");
   RNA_def_function_return(func, parm);
 
+  func = RNA_def_function(srna, "to_mesh_clear", "rna_Object_to_mesh_clear");
+  RNA_def_function_ui_description(func, "Clears mesh data-block created by to_mesh()");
+
   /* Armature */
   func = RNA_def_function(srna, "find_armature", "modifiers_isDeformedByArmature");
   RNA_def_function_ui_description(
index 6362427..baf76dc 100644 (file)
@@ -194,6 +194,7 @@ class SimpleImportTest(AbstractAlembicTest):
         self.assertAlmostEqual(-1, mesh.vertices[0].co.x)
         self.assertAlmostEqual(-1, mesh.vertices[0].co.y)
         self.assertAlmostEqual(0.5905638933181763, mesh.vertices[0].co.z)
+        plane_eval.to_mesh_clear()
 
         # Change path from absolute to relative. This should not break the animation.
         scene.frame_set(1)
@@ -205,6 +206,7 @@ class SimpleImportTest(AbstractAlembicTest):
         self.assertAlmostEqual(1, mesh.vertices[3].co.x)
         self.assertAlmostEqual(1, mesh.vertices[3].co.y)
         self.assertAlmostEqual(0.5905638933181763, mesh.vertices[3].co.z)
+        plane_eval.to_mesh_clear()
 
     def test_import_long_names(self):
         # This file contains very long names. The longest name is 4047 chars.