Python 集成

从 Dragonframe 2027(目前正在开发中)开始,Dragonframe 支持 Python + PySide(最高支持 Qt 6.11)。.

安装完成后 2027.01 开发版本, 在 ~/Dragonframe/scripts/__init__.py 创建一个 Python 初始化文件
您可以将环境变量 DRAGONFRAME_PYTHON_INIT 设置为以冒号分隔的路径列表,这些路径包含其他 __init__.py 文件。.

您还可以设置环境变量 DRAGONFRAME_PYTHONPATH,并将其值设置为以冒号分隔的 Python 模块目录路径列表。Dragonframe 会将这些路径添加到 sys.path 中,位于其自身路径条目之后。.

目前界面还在初步成型阶段,最终发布前可能会有所改动。.
请注意,所有函数都应该只从主线程调用,但 util.executeInMainThread 和 util.executeInMainThreadWithResult 除外。.

以下是可用的模块和方法:

=== dragonframe.ui ===

--------------------------------------------------------------------------------

Members:

  FileMenu : The File menu.

  EditMenu : The Edit menu.

  ViewMenu : The View menu.

  SceneMenu : The Scene menu.

  CaptureMenu : The Capture menu.

  PlaybackMenu : The Playback menu.

  WindowMenu : The Window menu.

  HelpMenu : The Help menu.

  UserTopLevel1 : A top-level user menu.

  UserTopLevel2 : A top-level user menu.

  UserTopLevel3 : A top-level user menu.

  AudioMenu : The audio workspace menu.

  ArcMenu : The Arc workspace menu.

  DmxProgramMenu : The DMX program menu.

  MediaLayersAddMenu : The media layer + menu.

  CinematographyImageContextMenu : The right-click context menu on the cinematography image.

--------------------------------------------------------------------------------

addMenuOption(location: dragonframe.ui.Menu, label: str, callback: object, kwargs: dict = {}) -> None

Add a menu item to the specified menu.

    location -- Menu value indicating which menu to add the item to
    label    -- display text for the menu item
    callback -- callable invoked with no arguments when the item is selected
    kwargs -- dictionary with additional arguments - 'checked' = True/False, 'shortcut' = shortcut


--------------------------------------------------------------------------------

addMenuSeparator(location: dragonframe.ui.Menu) -> None

Add a separator line to the specified menu.

    location -- Menu value indicating which menu to add the separator to


--------------------------------------------------------------------------------

isMenuOptionChecked(location: dragonframe.ui.Menu, label: str) -> bool

Returns if a menu item is checked.

    location -- Menu value indicating which menu to add the item to
    label    -- display text for the menu item


--------------------------------------------------------------------------------

setMenuOptionChecked(location: dragonframe.ui.Menu, label: str, checked: bool) -> None

Sets a menu item to be checkable and sets the current value.

    location -- Menu value indicating which menu to add the item to
    label    -- display text for the menu item
    checked  -- whether the menu item is checked


--------------------------------------------------------------------------------

setTopLevelMenuName(location: dragonframe.ui.Menu, label: str) -> None

Set the name of a user-defined top level menu.

    location -- UserTopLevel1, UserTopLevel2, or UserTopLevel3
    label    -- display text for the menu



=== dragonframe.events ===

--------------------------------------------------------------------------------

Members:

  Shoot : A capture (shoot) has been initiated.

  Delete : A frame has been deleted.

  Position : The playhead position has changed.

  CaptureComplete : A single exposure capture has completed.

  FrameComplete : All exposures for the current frame have been captured.

  TestComplete : A test shot capture has completed.

  Edit : An edit operation has occurred.

  AfterAssist : An assist operation has finished.

  Exposure : The active exposure (pass) has changed.

  Bash : A bash/shell script event has fired.

  Save : The scene has been saved.

  NewTake : A new take has been created.

  TakeOpened : A take has been opened.

  CaptureFailed : A capture attempt failed.

  MocoDisconnected : The motion control rig has disconnected.

  CameraDisconnected : The camera has disconnected.

--------------------------------------------------------------------------------

registerInterest(types: list[dragonframe.events.EventType], callback: object) -> None

Register a callback to be invoked when any of the given EventType values occur.

    types    -- list of EventType values to listen for
    callback -- callable invoked with the EventType when the event fires

Example:

    def on_event(ev):
        print("event: " + str(ev.event_type))
        print("frame: " + str(ev.args["frame"]))
        print("exposure: " + str(ev.args["exposure"]))
    dragonframe.events.registerInterest(
        [dragonframe.events.EventType.FrameComplete], on_event)


--------------------------------------------------------------------------------

registerNoteSetChangeListener(set: str, callback: object) -> None

Register a callback to be invoked when the note set changes.

    set      -- the note set name that you are interested in
    callback -- callable invoked when the note set changes

Example:
    def on_note_change_event(set):
        print("notes change: " + set)
        notes = scene.getNotes("sync")
        for key, value in notes.items():
            print(f"NOTE: {key} = {value}")
    dragonframe.events.registerNoteSetChangeListener("sync", on_note_change_event)


=== dragonframe.scene ===

--------------------------------------------------------------------------------

clearNote(name: str, frame: int) -> None

Clears the note at the frame for this note set.

    name -- the name of the note set
    frame -- the frame to clear


--------------------------------------------------------------------------------

createNoteSet(name: str) -> bool

Creates a new note set with this name, if it doesn't already exist.

    name -- the name of the note set


--------------------------------------------------------------------------------

currentExposure() -> int

Return the 1-based index of the currently active exposure (pass), or 0 if no scene is open.


--------------------------------------------------------------------------------

currentFrame() -> int

Return the 1-based index of the currently selected frame, or 0 if no scene is open.


--------------------------------------------------------------------------------

exposureCount() -> int

Return the number of exposures (passes) defined in the current scene, or 0 if no scene is open.


--------------------------------------------------------------------------------

exposureFolder(exp: int) -> str

Return the absolute path to the capture folder for the given exposure.

    exposure -- 1-based exposure (pass) index
Returns an empty string if no scene is open.


--------------------------------------------------------------------------------

exposureFramePrefix(exp: int) -> str

Return the file name prefix used for captured frames of the given exposure.

    exp -- 1-based exposure index
Returns an empty string if no scene is open.


--------------------------------------------------------------------------------

exposureName(exp: int) -> str

Return the display name of the given exposure (pass).

    exp -- 1-based exposure index
Returns an empty string if no scene is open.


--------------------------------------------------------------------------------

feedFolder() -> str

Return the absolute path to the live-view feed folder, or an empty string if no scene is open.


--------------------------------------------------------------------------------

getNotes(name: str) -> dict

Returns all the notes for this note set.

    name -- the name of the note set


--------------------------------------------------------------------------------

hasNoteSet(name: str) -> bool

Returns true if the xsheet has a note set by this name.

    name -- the name of the note set


--------------------------------------------------------------------------------

highResFile(vframe: int, exposure: int, raw: bool) -> str

Return the file path of the captured image for a given frame and exposure.

    vframe   -- 1-based virtual frame number
    exposure -- 1-based exposure (pass) index
    raw      -- if True, return the path to the raw file instead of the processed image


--------------------------------------------------------------------------------

importAxisSetup(fileName: str) -> None

Import a motion control axis setup file.

    fileName -- absolute path to the .dfscene file


--------------------------------------------------------------------------------

loadMediaIntoReferences(fileNames: list[str]) -> None

Load images into the references folder.




--------------------------------------------------------------------------------

loadMediaIntoTestShots(fileNames: list[str]) -> None

Load images into the test shots folder.




--------------------------------------------------------------------------------

loadReferenceLayerImage(fileName: str, kwargs: dict = {}) -> bool

Load a still image as the active reference layer.

    fileName -- absolute path to the image file


--------------------------------------------------------------------------------

loadReferenceLayerImageSequence(fileName: str, kwargs: dict = {}) -> bool

Load an image sequence as the active reference layer.

    fileName -- absolute path to the first frame of the image sequence


--------------------------------------------------------------------------------

loadReferenceLayerMovie(fileName: str, kwargs: dict = {}) -> bool

Load a movie file as the active reference layer.

    fileName -- absolute path to the movie file


--------------------------------------------------------------------------------

loadReferenceLayerScene(fileName: str, kwargs: dict = {}) -> bool

Load another Dragonframe scene as the active reference layer.

    fileName -- absolute path to the .dfscene file


--------------------------------------------------------------------------------

name() -> str

Return the full name of the open scene, or an empty string if no scene is open.


--------------------------------------------------------------------------------

namePrefix() -> str

Return the name prefix used when constructing frame file names, or an empty string if no scene is open.


--------------------------------------------------------------------------------

openFile(fileName: str) -> bool

Load a scene or take. If you provide a .dgn folder, the Choose Take dialog will appear.

    fileName -- path to scene.dgn or take folder


--------------------------------------------------------------------------------

save() -> None

Save the current scene to disk. Has no effect if no scene is open.


--------------------------------------------------------------------------------

sceneFolder() -> str

Return the absolute path to the scene folder, or an empty string if no scene is open.


--------------------------------------------------------------------------------

sceneName() -> str

Return the scene number/identifier portion of the scene name, or an empty string if no scene is open.


--------------------------------------------------------------------------------

setCurrentExposure(exp: int) -> None

Set the currently active exposure (pass).

    exp -- 1-based exposure index
Has no effect if no scene is open.


--------------------------------------------------------------------------------

setCurrentFrame(frame: int) -> None

Set the currently selected frame.

    frame -- 1-based frame index
Has no effect if no scene is open.


--------------------------------------------------------------------------------

setNote(name: str, frame: int, message: str) -> None

Sets the note message at the frame for this note set.

    name -- the name of the note set
    frame -- the frame
    message -- the message to set



--------------------------------------------------------------------------------

takeFile() -> str

Return the absolute path to the scene data file (.dfscene), or an empty string if no scene is open.


--------------------------------------------------------------------------------

takeFolder() -> str

Return the absolute path to the current take folder, or an empty string if no scene is open.


--------------------------------------------------------------------------------

takeName() -> str

Return the take number/identifier portion of the scene name, or an empty string if no scene is open.


--------------------------------------------------------------------------------

testRefsFolder() -> str

Return the absolute path to the test reference images folder, or an empty string if no scene is open.


--------------------------------------------------------------------------------

testShotsFolder() -> str

Return the absolute path to the test shots folder, or an empty string if no scene is open.


=== dragonframe.util ===

--------------------------------------------------------------------------------

executeInMainThread(call: object, args: object = (), kwargs: dict = {}) -> None

Executes a call on the main thread.


--------------------------------------------------------------------------------

executeInMainThreadWithResult(call: object, args: object = (), kwargs: dict = {}) -> object

Executes a call on the main thread, waiting for the result.


--------------------------------------------------------------------------------

executeScript(action: str) -> None

Executes the user-provided bash/bat script (in Preferences|Advanced).

    action -- The action name that will be provided to the script

以下是一个 __init__.py 文件的示例:

print("Initializing Dragonframe python")

from dragonframe import ui
from dragonframe import events
from dragonframe import scene

def print_module_docs(mod):
    name = getattr(mod, '__name__', str(mod))
    print(f"\n=== {name} ===")
    for attr in sorted(dir(mod)):
        if attr.startswith('_'):
            continue
        obj = getattr(mod, attr)
        doc = getattr(obj, '__doc__', None)
        if doc:
            print(f"\n{attr}:\n{doc}")

print_module_docs(ui)
print_module_docs(events)
print_module_docs(scene)

from PySide6.QtWidgets import (QFileDialog)

def get_media(user_data: dict):
    print("get_media")
    print(f"Scene Name {scene.name()} {scene.sceneName()} {scene.takeName()}")
    fileNames, _ = QFileDialog.getOpenFileNames(None, "Get Media",
                                              "Test Python",
                                              "All Files (*);;Media Files (*.jpg,*.png,*.tiff,*.crw)", "")
    if fileNames:
        print(f"Chosen files {fileNames}")
        scene.loadMediaIntoReferences(fileNames);

def load_reference_image():
    print("load_reference_image")
    print(f"Scene Name {scene.name()} {scene.sceneName()} {scene.takeName()}")
    fileName, _ = QFileDialog.getOpenFileName(None, "Load Reference Image",
                                              "Test Python",
                                              "All Files (*);;Image Files (*.jpg,*.png,*.tiff,*.crw)", "")
    if fileName:
        print(f"Chosen file {fileName}")
        scene.loadReferenceLayerImage(fileName);

def launch_image(user_data: dict):
    print("Launch image! ")
    print("Exposure: " + str(user_data["exposure"]))
    frameList = user_data["frames"];
    for frame in frameList:
        fileName = scene.highResFile(frame, user_data["exposure"], False)
        rawFileName = scene.highResFile(frame, user_data["exposure"], True)
        print(f"FRAME: {frame}, File {fileName}, Raw {rawFileName}");

ui.addMenuSeparator(ui.Menu.FileMenu);
ui.addMenuOption(ui.Menu.FileMenu, "Company|Load Reference Image", load_reference_image);

ui.addMenuOption(ui.Menu.CinematographyImageContextMenu, "My Launcher", launch_image, { "shortcut": "Ctrl+G" });
ui.addMenuOption(ui.Menu.CinematographyImageContextMenu, "Import to References", get_media);

def handle_shoot(user_data: events.Event):
    print("Shooting! " + str(user_data.event_type) + " "  + str(user_data.args["frame"]) + " " + str(user_data.args["exposure"]));

def handle_position(user_data: events.Event):
    print("POSITION: " + str(user_data.event_type) + " "  + str(user_data.args["frame"]) + " " + str(user_data.args["exposure"]));

def handle_cc(user_data: events.Event):
    print("CC: " + str(user_data.event_type) + " "  + str(user_data.args["frame"]) + " " + str(user_data.args["exposure"]));


events.registerInterest([events.EventType.Shoot], handle_shoot);
events.registerInterest([events.EventType.Position], handle_position);
events.registerInterest([events.EventType.CaptureComplete], handle_cc);