Skip to content

Camera presets / saved setups #161

Description

@Weisl

Save a camera's full settings bundle — resolution, exposure, world, lens/DOF, background image config (everything pie_menu.draw_camera_settings() already exposes) — as a named, reusable preset that can be applied to any camera.

This is a natural extension of the addon's existing per-camera-property model rather than a new paradigm, and directly serves the "quickly present your work" / product-design use cases called out in the README (recurring hero-shot / turntable-angle setups reused across projects).

Resolved design

Storage: Blender's native preset system (AddPresetBase + a Menu with preset_subdir / draw = Menu.draw_preset), matching the same "+" preset dropdown UX every other Blender panel already uses — not a custom JSON file. Verified AddPresetBase's actual serialization against the Blender source (bl_operators/presets.py): each preset_values entry is written as path = repr(value) and re-executed via bpy.utils.execfile() on apply.

Apply semantics: overwrite outright. Matches the existing CAM_MANAGER_OT_apply_resolution_preset / ..._focal_length_preset operators, which already unconditionally set their target fields. No merge/dirty-tracking logic — there's no reliable way to define "already customized" for fields like lens/clip/exposure that legitimately vary by default.

Field scope — background images excluded from v1: cam.background_images entries reference an Image/MovieClip datablock per entry (same ID-resolution risk as world, but compounded), and AddPresetBase's automatic collection handling calls a bare .add() which background_images doesn't support (only .new(image=..., clip=...)) — likely to error rather than degrade. Background images are also more "per-camera reference photo" than "reusable compositional setup." Revisit as a separate follow-up if it turns out to matter.

ID-pointer fields (world, dof.focus_object) ordered last in preset_values: execfile runs top-to-bottom, so if a referenced datablock doesn't exist in the target file, that line raises and aborts the rest of the script — but only the fields after it are lost, since everything before already executed as a normal sequential assignment. Putting the two risky ID-pointer fields last means a missing-datablock failure only drops those two fields instead of the whole preset.

preset_defines deviates from Blender's own convention, deliberately: native camera presets use cam = bpy.context.camera, reliable only from the Properties Editor's Camera Data tab. This addon's draw_camera_settings() is called from the 3D viewport N-panel and pie menu instead, where context.camera isn't guaranteed to match. Every call site here (ui.py, pie_menu.py) always passes context.scene.camera, so use cam = bpy.context.scene.camera.data instead.

Implementation plan

New camera_presets.py (same classes tuple + register()/unregister() shape as every other module here):

class CAM_MANAGER_OT_camera_preset_add(AddPresetBase, Operator):
    bl_idname = "cam_manager.camera_preset_add"
    preset_menu = "CAM_MANAGER_MT_camera_presets"
    preset_subdir = "simple_camera_manager/camera"
    preset_defines = ["cam = bpy.context.scene.camera.data"]
    preset_values = [
        "cam.type", "cam.resolution_overwrite", "cam.resolution",
        "cam.lens", "cam.ortho_scale", "cam.clip_start", "cam.clip_end",
        "cam.exposure", "cam.dof.use_dof", "cam.dof.focus_distance",
        "cam.dof.focus_subtarget", "cam.show_rotation_gizmo",
        "cam.dolly_zoom_link_focus",
        "cam.dof.focus_object", "cam.world",  # ID pointers last, see above
    ]

class CAM_MANAGER_MT_camera_presets(Menu):
    bl_label = "Camera Presets"
    preset_subdir = "simple_camera_manager/camera"
    preset_operator = "script.execute_preset"
    draw = Menu.draw_preset

Wire into pie_menu.py's draw_camera_settings() with the standard inline add/remove-button row (layout.menu(...), +/- operator buttons), register the new module in __init__.py.

Tests: add both classes to tests/test_addon_registration.py's _REPRESENTATIVE_CLASSES; new tests/test_camera_presets.py doing a save → mutate → apply → assert-restored round trip. Must clean up the preset file it writes in tearDown()AddPresetBase writes to the real bpy.utils.user_resource('SCRIPTS', ...) path, not a sandboxed temp dir.

Not yet implemented — this is the design, ready to build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions