Skip to content

extract_matchmove_script_nuke

Extract project for Nuke.

Because original extractor script is intermingled with UI, we had to resort to this hacky solution. This is monkey-patching 3DEqualizer UI to silence it during the export. Advantage is that it is still using "vanilla" built-in export script, so it should be more compatible with future versions of the software.

TODO: This can be refactored even better, split to multiple methods, etc.

ExtractMatchmoveScriptNuke

Bases: Extractor, OptionalPyblishPluginMixin

Extract Nuke script for matchmove.

Unfortunately built-in export script from 3DEqualizer is bound to its UI, and it is not possible to call it directly from Python. Because of that, we are executing the script in the same way as artist would do it, but we are patching the UI to silence it and to avoid any user interaction.

TODO: Utilize attributes defined in ExtractScriptBase

Source code in client/ayon_equalizer/plugins/publish/extract_matchmove_script_nuke.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class ExtractMatchmoveScriptNuke(publish.Extractor,
                                 OptionalPyblishPluginMixin):
    """Extract Nuke script for matchmove.

    Unfortunately built-in export script from 3DEqualizer is bound to its UI,
    and it is not possible to call it directly from Python. Because of that,
    we are executing the script in the same way as artist would do it, but
    we are patching the UI to silence it and to avoid any user interaction.

    TODO: Utilize attributes defined in ExtractScriptBase
    """

    label = "Extract Nuke Script"
    families: ClassVar[list] = ["matchmove"]
    hosts: ClassVar[list] = ["equalizer"]
    optional = True

    order = pyblish.api.ExtractorOrder

    def process(self, instance: pyblish.api.Instance) -> None:
        """Extract Nuke script from 3DEqualizer."""
        if not self.is_active(instance.data):
            return

        cam = tde4.getCurrentCamera()

        staging_dir = self.staging_dir(instance)
        file_path = Path(staging_dir) / "nuke_export.nk"

        # these patched methods are used to silence 3DEqualizer UI:
        def patched_getWidgetValue(_, key: str) -> str:  # noqa: N802, ANN001
            """Return value for given key in widget."""
            if key == "file_browser":
                return file_path.as_posix()
            return tde4.getCameraFrameOffset(cam) - 1 \
                if key == "startframe_field" else ""

        # This is simulating artist clicking on "OK" button
        # in the export dialog.
        def patched_postCustomRequester(*args, **kwargs) -> int:  # noqa: N802, ANN002, ANN003, ARG001
            return 1

        # This is silencing success/error message after the script
        # is exported.
        def patched_postQuestionRequester(*args, **kwargs) -> None:  # noqa: N802, ANN002, ANN003, ARG001
            return None

        # import maya export script from 3DEqualizer
        exporter_path = instance.context.data["tde4_path"] / "sys_data" / "py_scripts" / "export_nuke.py"  # noqa: E501
        self.log.debug("Patching 3dequalizer requester objects ...")

        with patch("tde4.getWidgetValue", patched_getWidgetValue), \
                 patch("tde4.postCustomRequester", patched_postCustomRequester), \
                 patch("tde4.postQuestionRequester", patched_postQuestionRequester):  # noqa: E501
            with exporter_path.open() as f:
                script = f.read()
            self.log.debug("Importing %s", exporter_path.as_posix())
            exec(script, {"tde4": tde4})  # noqa: S102

        # create representation data
        if "representations" not in instance.data:
            instance.data["representations"] = []

        representation = {
            "name": "nk",
            "ext": "nk",
            "files": file_path.name,
            "stagingDir": staging_dir,
        }
        self.log.debug("output: %s", file_path.as_posix())
        instance.data["representations"].append(representation)

process(instance)

Extract Nuke script from 3DEqualizer.

Source code in client/ayon_equalizer/plugins/publish/extract_matchmove_script_nuke.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def process(self, instance: pyblish.api.Instance) -> None:
    """Extract Nuke script from 3DEqualizer."""
    if not self.is_active(instance.data):
        return

    cam = tde4.getCurrentCamera()

    staging_dir = self.staging_dir(instance)
    file_path = Path(staging_dir) / "nuke_export.nk"

    # these patched methods are used to silence 3DEqualizer UI:
    def patched_getWidgetValue(_, key: str) -> str:  # noqa: N802, ANN001
        """Return value for given key in widget."""
        if key == "file_browser":
            return file_path.as_posix()
        return tde4.getCameraFrameOffset(cam) - 1 \
            if key == "startframe_field" else ""

    # This is simulating artist clicking on "OK" button
    # in the export dialog.
    def patched_postCustomRequester(*args, **kwargs) -> int:  # noqa: N802, ANN002, ANN003, ARG001
        return 1

    # This is silencing success/error message after the script
    # is exported.
    def patched_postQuestionRequester(*args, **kwargs) -> None:  # noqa: N802, ANN002, ANN003, ARG001
        return None

    # import maya export script from 3DEqualizer
    exporter_path = instance.context.data["tde4_path"] / "sys_data" / "py_scripts" / "export_nuke.py"  # noqa: E501
    self.log.debug("Patching 3dequalizer requester objects ...")

    with patch("tde4.getWidgetValue", patched_getWidgetValue), \
             patch("tde4.postCustomRequester", patched_postCustomRequester), \
             patch("tde4.postQuestionRequester", patched_postQuestionRequester):  # noqa: E501
        with exporter_path.open() as f:
            script = f.read()
        self.log.debug("Importing %s", exporter_path.as_posix())
        exec(script, {"tde4": tde4})  # noqa: S102

    # create representation data
    if "representations" not in instance.data:
        instance.data["representations"] = []

    representation = {
        "name": "nk",
        "ext": "nk",
        "files": file_path.name,
        "stagingDir": staging_dir,
    }
    self.log.debug("output: %s", file_path.as_posix())
    instance.data["representations"].append(representation)