@@ -332,43 +332,71 @@ def _join_curves(collection: bpy.types.Collection, name: str) -> None:
332332
333333def _set_origins_to_geometry (collection : bpy .types .Collection ) -> None :
334334 """Helper function to set object origins to geometry."""
335- bpy . ops . object . select_all ( action = "DESELECT" )
336- if not collection . objects :
335+ objects = list ( collection . objects )
336+ if not objects :
337337 return
338338
339- bpy .context .view_layer .objects .active = collection .objects [0 ]
339+ bpy .ops .object .select_all (action = "DESELECT" )
340+ bpy .context .view_layer .objects .active = objects [0 ]
340341 bpy .ops .object .mode_set (mode = "OBJECT" )
341342
342- for obj in collection .objects :
343- bpy .context .view_layer .objects .active = obj
344- obj .select_set (True )
343+ # origin_set already handles every selected editable object per-object,
344+ # so one batched call replaces a per-object operator loop.
345+ with bpy .context .temp_override (
346+ object = objects [0 ],
347+ active_object = objects [0 ],
348+ selected_objects = objects ,
349+ selected_editable_objects = objects ,
350+ ):
345351 bpy .ops .object .origin_set (type = "ORIGIN_GEOMETRY" , center = "MEDIAN" )
346- obj .select_set (False )
347352
348353
349354def _convert_to_meshes (collection : bpy .types .Collection ) -> None :
350355 """Helper function to convert curves to meshes."""
351- for obj in collection .objects :
352- if obj .type != "CURVE" :
353- continue
354-
355- curve_data = obj .data
356- original_name = obj .name .replace ("Curve" , "" )
357-
358- bpy .context .view_layer .objects .active = obj
359- obj .select_set (True )
356+ curve_objects = [obj for obj in collection .objects if obj .type == "CURVE" ]
357+ if not curve_objects :
358+ return
360359
361- bpy .ops .object .convert (target = "MESH" )
360+ source_names = [obj .name for obj in curve_objects ]
361+ source_curve_data = []
362+ for obj in curve_objects :
363+ if obj .data not in source_curve_data :
364+ source_curve_data .append (obj .data )
362365
363- new_name = f"Mesh{ original_name } "
366+ # object.convert acts on every selected editable object. Override its
367+ # selection context with exactly the imported Curves so pre-existing
368+ # objects are never included. One batched call keeps large imports from
369+ # paying the per-operator scene update once per glyph.
370+ with bpy .context .temp_override (
371+ object = curve_objects [0 ],
372+ active_object = curve_objects [0 ],
373+ selected_objects = list (curve_objects ),
374+ selected_editable_objects = list (curve_objects ),
375+ ):
376+ result = bpy .ops .object .convert (target = "MESH" )
377+
378+ failed_names = [
379+ name
380+ for obj , name in zip (curve_objects , source_names )
381+ if obj .type != "MESH"
382+ ]
383+ if result != {"FINISHED" } or failed_names :
384+ failed = failed_names [0 ] if failed_names else source_names [0 ]
385+ raise RuntimeError (f"Failed to convert { failed } to mesh" )
386+
387+ for obj , original_name in zip (curve_objects , source_names ):
388+ new_name = f"Mesh{ original_name .replace ('Curve' , '' )} "
364389 obj .name = new_name
365390 obj .data .name = new_name
366391
367- obj .select_set (False )
368- bpy .data .curves .remove (curve_data )
369-
370- # Clean up any orphaned data after conversion
371- # bpy.ops.outliner.orphans_purge(do_recursive=True) #TODO : not very tested, and might delete some materials unintended
392+ # In-place conversion leaves the source Curve datablocks behind. They are
393+ # no longer referenced by Curve objects after a successful conversion.
394+ used_curve_data = {
395+ obj .data for obj in bpy .data .objects if obj .type == "CURVE"
396+ }
397+ for curve_data in source_curve_data :
398+ if curve_data not in used_curve_data :
399+ bpy .data .curves .remove (curve_data )
372400
373401def _convert_to_unfilled_paths (collection : bpy .types .Collection ) -> None :
374402
@@ -459,44 +487,31 @@ def _convert_to_grease_pencil(
459487 )
460488
461489 # object.convert acts on every selected editable object. Override its
462- # selection context with exactly one imported Curve so pre-existing objects
463- # are never included in the conversion.
464- converted_objects = []
465- conversion_curve_data = []
466- for source_obj in curve_objects :
467- original_name = source_obj .name
468- objects_before_conversion = set (bpy .data .objects )
469- curves_before_conversion = set (bpy .data .curves )
470- with bpy .context .temp_override (
471- object = source_obj ,
472- active_object = source_obj ,
473- selected_objects = [source_obj ],
474- selected_editable_objects = [source_obj ],
475- ):
476- result = bpy .ops .object .convert (
477- target = "GREASEPENCIL" ,
478- keep_original = True ,
479- )
480- new_gp_objects = [
481- obj
482- for obj in bpy .data .objects
483- if obj not in objects_before_conversion and obj .type == "GREASEPENCIL"
484- ]
485- conversion_curve_data .extend (
486- curve
487- for curve in bpy .data .curves
488- if curve not in curves_before_conversion
489- and curve not in conversion_curve_data
490- )
491- gp_obj = new_gp_objects [0 ] if len (new_gp_objects ) == 1 else None
492- if (
493- result != {"FINISHED" }
494- or gp_obj is None
495- or gp_obj is source_obj
496- or gp_obj .type != "GREASEPENCIL"
497- ):
498- raise RuntimeError (f"Failed to convert { original_name } to Grease Pencil" )
490+ # selection context with exactly the imported Curves so pre-existing
491+ # objects are never included in the conversion. One batched call keeps
492+ # large imports (e.g. syntax-highlighted code blocks with thousands of
493+ # glyphs) from paying the per-operator scene update once per glyph.
494+ source_names = [obj .name for obj in curve_objects ]
495+ curves_before_conversion = set (bpy .data .curves )
496+ with bpy .context .temp_override (
497+ object = curve_objects [0 ],
498+ active_object = curve_objects [0 ],
499+ selected_objects = list (curve_objects ),
500+ selected_editable_objects = list (curve_objects ),
501+ ):
502+ result = bpy .ops .object .convert (target = "GREASEPENCIL" )
503+
504+ failed_names = [
505+ name
506+ for obj , name in zip (curve_objects , source_names )
507+ if obj .type != "GREASEPENCIL"
508+ ]
509+ if result != {"FINISHED" } or failed_names :
510+ failed = failed_names [0 ] if failed_names else source_names [0 ]
511+ raise RuntimeError (f"Failed to convert { failed } to Grease Pencil" )
499512
513+ converted_objects = []
514+ for gp_obj , original_name in zip (curve_objects , source_names ):
500515 gp_obj .name = f"GP_{ original_name } "
501516 gp_obj .data .name = f"GP_{ original_name } DataBlock"
502517
@@ -506,15 +521,18 @@ def _convert_to_grease_pencil(
506521 gp_obj .data .layers .active = gp_obj .data .layers [0 ]
507522
508523 converted_objects .append (gp_obj )
509- bpy .data .objects .remove (source_obj , do_unlink = True )
510524
511- # object.convert leaves the source Curve datablocks behind for undo. They
512- # are no longer referenced by Curve objects after a successful conversion.
525+ # In-place conversion leaves the source Curve datablocks behind (plus any
526+ # temporaries the converter created). They are no longer referenced by
527+ # Curve objects after a successful conversion.
528+ conversion_curve_data = [
529+ curve for curve in bpy .data .curves if curve not in curves_before_conversion
530+ ]
531+ used_curve_data = {
532+ obj .data for obj in bpy .data .objects if obj .type == "CURVE"
533+ }
513534 for curve_data in source_curve_data + conversion_curve_data :
514- is_still_used = any (
515- obj .type == "CURVE" and obj .data == curve_data for obj in bpy .data .objects
516- )
517- if not is_still_used :
535+ if curve_data not in used_curve_data :
518536 bpy .data .curves .remove (curve_data )
519537
520538 for material in source_materials :
0 commit comments