Skip to content

Commit f0fb865

Browse files
committed
make import faster
1 parent 9aaafa4 commit f0fb865

1 file changed

Lines changed: 85 additions & 67 deletions

File tree

typst_importer/typst_to_svg.py

Lines changed: 85 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -332,43 +332,71 @@ def _join_curves(collection: bpy.types.Collection, name: str) -> None:
332332

333333
def _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

349354
def _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

373401
def _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

Comments
 (0)