@@ -1281,6 +1281,20 @@ def remove_empty_collection(context, collection_name):
12811281 if collection is not None and len (collection .objects ) == 0 :
12821282 bpy .data .collections .remove (collection )
12831283
1284+ def _clear_modifier_bake_cache (self ):
1285+ """Free the mesh datablocks cached by convert_to_mesh() /
1286+ apply_all_modifiers() (see self._modifier_bake_cache). They're kept
1287+ alive only by this dict - never linked to any object - so they must
1288+ be removed explicitly on confirm/cancel or they'd leak as orphan
1289+ mesh data."""
1290+ cache = getattr (self , '_modifier_bake_cache' , None )
1291+ if not cache :
1292+ return
1293+ meshes = [me for me in cache .values () if me and me .name in bpy .data .meshes ]
1294+ if meshes :
1295+ bpy .data .batch_remove (meshes )
1296+ self ._modifier_bake_cache = {}
1297+
12841298 @staticmethod
12851299 def set_collections (obj , collections ):
12861300 """link an object to a collection"""
@@ -1297,8 +1311,7 @@ def set_collections(obj, collections):
12971311 col .objects .unlink (obj )
12981312
12991313 # Modifiers
1300- @staticmethod
1301- def apply_all_modifiers (context , obj ):
1314+ def apply_all_modifiers (self , context , obj , cache_key = None ):
13021315 """Replace obj's mesh data with the fully evaluated result of its
13031316 modifier stack - including any unrealized instances a modifier like
13041317 Geometry Nodes "Instance on Points" produces without a "Realize
@@ -1310,17 +1323,31 @@ def apply_all_modifiers(context, obj):
13101323 can't bake unrealized instances into real geometry. Evaluating via
13111324 the depsgraph and merging instances manually (merge_object_instances)
13121325 sidesteps that limitation.
1326+
1327+ `cache_key`, if given, reuses/populates self._modifier_bake_cache so
1328+ repeated calls for the same source object (e.g. once per MOUSEMOVE
1329+ delta while dragging) don't each pay for a fresh depsgraph
1330+ evaluation - see convert_to_mesh() for why that matters (#631).
13131331 """
13141332 context .view_layer .objects .active = obj
13151333 if not obj .modifiers :
13161334 return
13171335
1336+ cached_mesh = self ._modifier_bake_cache .get (cache_key ) if cache_key else None
1337+ if cached_mesh is not None and cached_mesh .name in bpy .data .meshes :
1338+ old_data = obj .data
1339+ obj .data = cached_mesh .copy ()
1340+ obj .modifiers .clear ()
1341+ if old_data .users == 0 :
1342+ bpy .data .meshes .remove (old_data )
1343+ return
1344+
13181345 depsgraph = context .evaluated_depsgraph_get ()
13191346 me = bpy .data .meshes .new_from_object (obj .evaluated_get (depsgraph ), depsgraph = depsgraph )
13201347
13211348 bm = bmesh .new ()
13221349 bm .from_mesh (me )
1323- OBJECT_OT_add_bounding_object .merge_object_instances (bm , obj , depsgraph )
1350+ self .merge_object_instances (bm , obj , depsgraph )
13241351 bm .to_mesh (me )
13251352 bm .free ()
13261353
@@ -1331,6 +1358,9 @@ def apply_all_modifiers(context, obj):
13311358 if old_data .users == 0 :
13321359 bpy .data .meshes .remove (old_data )
13331360
1361+ if cache_key :
1362+ self ._modifier_bake_cache [cache_key ] = me .copy ()
1363+
13341364 @staticmethod
13351365 def remove_all_modifiers (context , obj ):
13361366 """Remove all modifiers of an object"""
@@ -1387,31 +1417,44 @@ def restore_obj_mod_from_dic(modifier_dic):
13871417 modifier .show_in_editmode = mod_entry ["show_in_editmode" ]
13881418
13891419 def convert_to_mesh (self , context , object , use_modifiers = False ):
1390- mods = self .store_obj_mod_in_dic (object )
1420+ # Baking (evaluated_depsgraph_get + new_from_object) is only needed
1421+ # for use_modifiers=True - the base object's modifier stack doesn't
1422+ # change between drag deltas, so reuse the last bake instead of
1423+ # re-evaluating the whole scene's depsgraph on every MOUSEMOVE (#631).
1424+ cache_key = (object , use_modifiers ) if use_modifiers else None
1425+ cached_mesh = self ._modifier_bake_cache .get (cache_key ) if cache_key else None
1426+
1427+ if cached_mesh is not None and cached_mesh .name in bpy .data .meshes :
1428+ me = cached_mesh .copy ()
1429+ else :
1430+ mods = self .store_obj_mod_in_dic (object )
13911431
1392- for mod in object .modifiers :
1393- mod .show_viewport = use_modifiers
1394- mod .show_in_editmode = use_modifiers
1432+ for mod in object .modifiers :
1433+ mod .show_viewport = use_modifiers
1434+ mod .show_in_editmode = use_modifiers
13951435
1396- if use_modifiers :
1397- deg = context .evaluated_depsgraph_get ()
1398- me = bpy .data .meshes .new_from_object (object .evaluated_get (deg ), depsgraph = deg )
1399-
1400- bm = bmesh .new ()
1401- bm .from_mesh (me )
1402- self .merge_object_instances (bm , object , deg )
1403- bm .to_mesh (me )
1404- bm .free ()
1405- else :
1406- # Create mesh from base data without applying modifiers
1407- me = object .data .copy ()
1408- me .update ()
1436+ if use_modifiers :
1437+ deg = context .evaluated_depsgraph_get ()
1438+ me = bpy .data .meshes .new_from_object (object .evaluated_get (deg ), depsgraph = deg )
1439+
1440+ bm = bmesh .new ()
1441+ bm .from_mesh (me )
1442+ self .merge_object_instances (bm , object , deg )
1443+ bm .to_mesh (me )
1444+ bm .free ()
1445+ else :
1446+ # Create mesh from base data without applying modifiers
1447+ me = object .data .copy ()
1448+ me .update ()
1449+
1450+ self .restore_obj_mod_from_dic (mods )
1451+
1452+ if cache_key :
1453+ self ._modifier_bake_cache [cache_key ] = me .copy ()
14091454
14101455 new_obj = bpy .data .objects .new (object .name + "_mesh" , me )
14111456 col = self .add_to_collections (context , new_obj , 'tmp_mesh' , hide = False , color = self .prefs .col_tmp_collection_color )
14121457
1413- self .restore_obj_mod_from_dic (mods )
1414-
14151458 new_obj .matrix_world = object .matrix_world
14161459 context .view_layer .objects .active = new_obj
14171460 return new_obj
@@ -1515,7 +1558,7 @@ def get_pre_processed_mesh_objs(self, context, default_world_spc=True, use_local
15151558 tmp_ob = delete_non_selected_verts (tmp_ob )
15161559
15171560 if self .my_use_modifier_stack :
1518- self .apply_all_modifiers (context , tmp_ob )
1561+ self .apply_all_modifiers (context , tmp_ob , cache_key = ( base_ob , True ) )
15191562 base = tmp_ob
15201563
15211564 self .tmp_meshes .append (tmp_ob )
@@ -1663,6 +1706,7 @@ def cancel_cleanup(self, context, delete_colliders=True):
16631706 # Delete temporary objects
16641707 self .remove_objects (self .tmp_meshes )
16651708 self .remove_empty_collection (context , 'tmp_mesh' )
1709+ self ._clear_modifier_bake_cache ()
16661710
16671711 self .reset_display (context )
16681712
@@ -1744,6 +1788,18 @@ def __init__(self, *args, **kwargs):
17441788 self .collision_group_idx = 0
17451789 self ._naming_cache = {}
17461790
1791+ # Modifier-stack bake results (convert_to_mesh / apply_all_modifiers),
1792+ # keyed by (base_ob, use_modifiers). Baking involves an
1793+ # evaluated_depsgraph_get() + new_from_object(), which in scenes with
1794+ # many other objects/modifiers costs tens to hundreds of ms - and
1795+ # execute() (hence this bake) reruns on every MOUSEMOVE delta while
1796+ # dragging the collider's own parameters, even though the base
1797+ # object's own modifier stack doesn't change during that drag. Caching
1798+ # the baked mesh here turns that into a one-time cost per base object
1799+ # for the operator's lifetime (#631). Cleared in
1800+ # _clear_modifier_bake_cache() on confirm/cancel.
1801+ self ._modifier_bake_cache = {}
1802+
17471803 @classmethod
17481804 def poll (cls , context ):
17491805 count = 0
@@ -2338,6 +2394,7 @@ def modal(self, context, event):
23382394 # Delete temporary generated meshes
23392395 self .remove_objects (self .tmp_meshes )
23402396 self .remove_empty_collection (context , 'tmp_mesh' )
2397+ self ._clear_modifier_bake_cache ()
23412398
23422399 try :
23432400 bpy .types .SpaceView3D .draw_handler_remove (self ._handle , 'WINDOW' )
0 commit comments