Skip to content

snippets: export presets loop transform_apply per object; selected_objects override does not narrow the operator #298

Description

@TMHSDigital

Why it matters

All three engine export presets apply transforms with a per-object loop of bpy.ops.object.transform_apply under temp_override(selected_objects=[obj]). The override is meant to scope the operator to one object, but it doesn't: transform_apply acts on selected_editable_objects, which isn't overridden, so each iteration applies to the whole selection.

The final result happens to be correct only because applying is idempotent. The loop is O(N²) operator work, and it teaches a false pattern: that overriding selected_objects narrows an operator's target. It also breaks the repo's own rule rules/prefer-data-over-ops-in-loops.mdc, which none of the listed exceptions (:92-99) cover.

Evidence

snippets/export_preset_unreal.py:18-27 (same shape in export_preset_godot.py:17-26 and export_preset_unity.py):

for obj in list(bpy.context.selected_objects):
    ...
    with bpy.context.temp_override(
        object=obj, active_object=obj, selected_objects=[obj]
    ):
        bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)

Reproduced headless on .scratch/5.2/blender-5.2.1-windows-x64/blender.exe (Blender 5.2.1 LTS): three selected cubes, each scale 2. The override above ran for cube a only:

PROBE1 after applying to only 'a': scales = [(1.0, 1.0, 1.0), (1.0, 1.0, 1.0), (1.0, 1.0, 1.0)]

All three had their scale applied, not just a.

Suggested approach

  • Call transform_apply once for the whole selection, outside any loop. That is what the loop effectively does already.
  • If a single-object scope is really needed somewhere, override selected_editable_objects=[obj]. Better, use the data API (obj.data.transform(matrix) plus resetting matrix_basis), which also satisfies the ops-in-loops rule.
  • Make the same change in the Unity, Godot and Unreal presets, and in any skill text or template (templates/ai-asset-pipeline-template/) that copies this pattern.
  • Consider adding a line to skills/operators or rules/prefer-temp-override-over-context-copy.mdc: overriding selected_objects does not scope operators that read selected_editable_objects.

Done when

  • No preset calls bpy.ops inside a per-object loop.
  • The override pattern that implies single-object scoping is gone or corrected everywhere it appears.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions