Skip to content

pre_flame_setup

FlamePrelaunch

Bases: PreLaunchHook

Flame prelaunch hook

Will make sure flame_script_dirs are copied to user's folder defined in environment var FLAME_SCRIPT_DIR.

Source code in client/ayon_flame/hooks/pre_flame_setup.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
class FlamePrelaunch(PreLaunchHook):
    """ Flame prelaunch hook

    Will make sure flame_script_dirs are copied to user's folder defined
    in environment var FLAME_SCRIPT_DIR.
    """
    app_groups = {"flame"}
    order = 1
    permissions = 0o777

    wtc_script_path = os.path.join(
        FLAME_ADDON_ROOT, "api", "scripts", "wiretap_com.py"
    )
    launch_types = {LaunchTypes.local}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.signature = "( {} )".format(self.__class__.__name__)

    def execute(self):
        _env = self.launch_context.env
        ayon_root = os.path.dirname(os.environ["AYON_EXECUTABLE"])
        _env["PATH"] = os.pathsep.join([
            path
            for path in _env["PATH"].split(os.pathsep)
            if not path.startswith(ayon_root)
        ])

        self.flame_python_exe = _env["AYON_FLAME_PYTHON_EXEC"]

        # add it to data for other hooks
        self.data["flame_python_executable"] = self.flame_python_exe

        self.flame_pythonpath = _env["AYON_FLAME_PYTHONPATH"]

        """Hook entry method."""
        project_entity = self.data["project_entity"]
        project_name = project_entity["name"]
        volume_name = _env.get("FLAME_WIRETAP_VOLUME")

        # get image io
        project_settings = self.data["project_settings"]

        imageio_flame = project_settings["flame"]["imageio"]

        # Check whether 'enabled' key from host imageio settings exists
        # so we can tell if host is using the new colormanagement framework.
        # If the 'enabled' isn't found we want 'colormanaged' set to True
        # because prior to the key existing we always did colormanagement for
        # Flame
        colormanaged = imageio_flame.get("enabled")
        # if key was not found, set to True
        # ensuring backward compatibility
        if colormanaged is None:
            colormanaged = True

        # get user name and host name
        user_name = get_ayon_username()
        user_name = user_name.replace(".", "_")

        hostname = socket.gethostname()  # not returning wiretap host name

        self.log.debug("Collected user \"{}\"".format(user_name))
        self.log.info(pformat(project_entity))
        project_attribs = project_entity["attrib"]
        width = project_attribs["resolutionWidth"]
        height = project_attribs["resolutionHeight"]
        fps = float(project_attribs["fps"])

        project_data = {
            "Name": project_entity["name"],
            "Nickname": project_entity["code"],
            "Description": "Created by AYON",
            "SetupDir": project_entity["name"],
            "FrameWidth": int(width),
            "FrameHeight": int(height),
            "AspectRatio": float(
                (width / height) * project_attribs["pixelAspect"]
            ),
            "FrameRate": self._get_flame_fps(fps)
        }

        data_to_script = {
            # from settings
            "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname,
            "volume_name": volume_name,
            "group_name": _env.get("FLAME_WIRETAP_GROUP"),

            # from project
            "project_name": project_name,
            "user_name": user_name,
            "project_data": project_data
        }

        # add color management data
        if colormanaged:
            project_data.update({
                "FrameDepth": str(imageio_flame["project"]["frameDepth"]),
                "FieldDominance": str(
                    imageio_flame["project"]["fieldDominance"])
            })
            data_to_script["color_policy"] = str(
                imageio_flame["project"]["colourPolicy"])

        self.log.info(pformat(dict(_env)))
        self.log.info(pformat(data_to_script))

        app_arguments = self._get_launch_arguments(data_to_script)

        # fix project data permission issue
        self._fix_permissions(project_name, volume_name)

        self.launch_context.launch_args.extend(app_arguments)

    def _fix_permissions(self, project_name, volume_name):
        """Work around for project data permissions

        Reported issue: when project is created locally on one machine,
        it is impossible to migrate it to other machine. Autodesk Flame
        is crating some unmanagable files which needs to be opened to 0o777.

        Args:
            project_name (str): project name
            volume_name (str): studio volume
        """
        dirs_to_modify = [
            "/usr/discreet/project/{}".format(project_name),
            "/opt/Autodesk/clip/{}/{}.prj".format(volume_name, project_name),
            "/usr/discreet/clip/{}/{}.prj".format(volume_name, project_name)
        ]

        for dirtm in dirs_to_modify:
            for root, dirs, files in os.walk(dirtm):
                try:
                    for name in set(dirs) | set(files):
                        path = os.path.join(root, name)
                        st = os.stat(path)
                        if oct(st.st_mode) != self.permissions:
                            os.chmod(path, self.permissions)

                except OSError as exc:
                    self.log.warning("Not able to open files: {}".format(exc))

    def _get_flame_fps(self, fps_num):
        fps_table = {
            float(23.976): "23.976 fps",
            int(25): "25 fps",
            int(24): "24 fps",
            float(29.97): "29.97 fps DF",
            int(30): "30 fps",
            int(50): "50 fps",
            float(59.94): "59.94 fps DF",
            int(60): "60 fps"
        }

        match_key = min(fps_table.keys(), key=lambda x: abs(x - fps_num))

        try:
            return fps_table[match_key]
        except KeyError as msg:
            raise KeyError((
                "Missing FPS key in conversion table. "
                "Following keys are available: {}".format(fps_table.keys())
            )) from msg

    def _add_pythonpath(self, env):
        pythonpath = env.get("PYTHONPATH")

        # separate it explicitly by `;` that is what we use in settings
        new_pythonpath = self.flame_pythonpath.split(os.pathsep)
        new_pythonpath += pythonpath.split(os.pathsep)

        env["PYTHONPATH"] = os.pathsep.join(new_pythonpath)

    def _get_launch_arguments(self, script_data):
        # Dump data to string
        dumped_script_data = json.dumps(script_data)

        with make_temp_file(dumped_script_data) as tmp_json_path:
            # Prepare subprocess arguments
            env = self.launch_context.env.copy()
            self._add_pythonpath(env)
            args = [
                self.flame_python_exe.format(**env),
                self.wtc_script_path,
                tmp_json_path
            ]
            self.log.info("Executing: {}".format(" ".join(args)))

            process_kwargs = {
                "logger": self.log,
                "env": env
            }

            run_subprocess(args, **process_kwargs)

            # process returned json file to pass launch args
            return_json_data = open(tmp_json_path).read()
            returned_data = json.loads(return_json_data)
            app_args = returned_data.get("app_args")
            self.log.info("____ app_args: `{}`".format(app_args))

            if not app_args:
                RuntimeError("App arguments were not solved")

        return app_args