-
Notifications
You must be signed in to change notification settings - Fork 12
Implement slapcomp workflow #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 22 commits
f24398f
7620fd3
d33e471
2533166
7d6754a
e683347
b240c41
77dab46
f12152b
cb30677
55d58ab
e53c2d4
1208f8d
5dbf143
8de8984
1e0d4b7
e7bf90e
abd6ff9
7c5c295
7af871d
4c9a6f5
025ac39
8aa9799
442eea5
8d0b0c4
f890253
18afd8a
ac60222
87bb43f
86f4c9c
71f2293
2f1b08a
b6a1e69
28a779b
b0e8d44
75859b1
09911b2
0f8f03d
d5eacb2
6d9c056
a587b42
887ec7a
a51c350
155235e
5b2708f
5a8d93f
290fa33
f36d32c
ab33232
e22f951
0201333
0408539
ff8f3f7
1a8c8d9
0125fc0
ec4c164
1044cbc
3697a1f
1a5cf73
1d88db8
0dc9454
75c08be
05af7de
e49306c
0a5ad41
fb2f1ce
d7ec1db
302cbfe
38eeea8
6176cf9
67a3fe7
44e58c2
a6b55d4
fda2aaa
9e9edb0
9d21bef
e7f6c56
ea52a9a
b4e745b
9cc99ef
bceaa25
c4d703e
3a1b1f6
6911d56
c0c3011
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| import os | ||
|
|
||
| import pyblish.api | ||
|
|
||
| from avalon import fusion | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import os | ||
| import pprint | ||
|
|
||
| from avalon import api | ||
| from avalon.vendor import requests | ||
|
|
||
| import pyblish.api | ||
|
|
||
|
|
||
| def _get_script_dir(): | ||
| """Get path to the image sequence script""" | ||
| try: | ||
| import colorbleed | ||
| scriptdir = os.path.dirname(colorbleed.__file__) | ||
| fusion_scripts = os.path.join(scriptdir, | ||
| "scripts", | ||
| "fusion") | ||
| except: | ||
| raise RuntimeError("This is a bug") | ||
|
|
||
| assert os.path.isdir(fusion_scripts), "Config is incomplete" | ||
| fusion_scripts = fusion_scripts.replace(os.sep, "/") | ||
|
|
||
| return fusion_scripts | ||
|
|
||
|
|
||
| class SubmitDependentSwitchJobDeadline(pyblish.api.ContextPlugin): | ||
| """Run Switch Shot on specified comp as depending job | ||
|
|
||
| """ | ||
|
|
||
| label = "Submit Switch Jobs to Deadline" | ||
| order = pyblish.api.IntegratorOrder + 0.2 | ||
| hosts = ["maya"] | ||
| families = ["colorbleed.renderlayer"] | ||
|
|
||
| def process(self, context): | ||
|
|
||
| # Run it as depend on the last submitted instance | ||
| instance = context[-1] | ||
|
|
||
| AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", | ||
| "http://localhost:8082") | ||
| assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" | ||
|
|
||
| job = instance.data.get("deadlineDependJob", None) | ||
| if not job: | ||
| self.log.warning("No dependent Job found") | ||
| return True | ||
|
|
||
| filepath = instance.data("flowFile", "") | ||
| if not filepath: | ||
| raise RuntimeError("No flow file (comp) chosen") | ||
|
|
||
| shot = api.Session["AVALON_ASSET"] | ||
| comment = instance.context.data["comment"] | ||
|
|
||
| scriptdir = _get_script_dir() | ||
| scriptfile = os.path.join(scriptdir, "deadline_swith_and_submit.py") | ||
|
||
|
|
||
| args = '--file_path "{}" --asset_name "{}" --render 1'.format( | ||
| filepath, shot) | ||
| payload_name = "{} SWITCH".format(os.path.basename(filepath)) | ||
|
|
||
| payload = { | ||
| "JobInfo": { | ||
| "Plugin": "Python", | ||
| "BatchName": job["Props"]["Batch"], | ||
| "Name": payload_name, | ||
| "JobType": "Normal", | ||
| "JobDependency0": job["_id"], | ||
| "UserName": job["Props"]["User"], | ||
| "Comment": comment, | ||
| "InitialStatus": "Suspended"}, | ||
| "PluginInfo": { | ||
| "Version": "3.6", | ||
| "ScriptFile": scriptfile, | ||
| "Arguments": args, | ||
| "SingleFrameOnly": "True" | ||
| }, | ||
| "AuxFiles": [] | ||
| } | ||
|
|
||
| # Update payload with machine limits | ||
| list_type = self._get_list_type(job) | ||
| payload["JobInfo"][list_type] = job["Props"]["ListedSlaves"] | ||
|
|
||
| environment = job["Props"].get("Env", {}) | ||
| payload["JobInfo"].update({ | ||
| "EnvironmentKeyValue%d" % index: "{key}={value}".format( | ||
| key=key, | ||
| value=environment[key] | ||
| ) for index, key in enumerate(environment) | ||
| }) | ||
|
|
||
| url = "{}/api/jobs".format(AVALON_DEADLINE) | ||
| response = requests.post(url, json=payload) | ||
| if not response.ok: | ||
| raise Exception(response.text) | ||
|
|
||
| # Temporary key name, deadlineSubmissionJob was already taken | ||
| if instance.data("runSlapComp", False): | ||
| instance.data["deadlineDependJob"] = response.json() | ||
|
|
||
| self.log.info("Slap comp arguments: %s" % args) | ||
|
|
||
| def _get_list_type(self, job): | ||
| return "Whitelist" if job["Props"]["White"]is True else "Blacklist" | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,3 +159,7 @@ def process(self, instance): | |
| response = requests.post(url, json=payload) | ||
| if not response.ok: | ||
| raise Exception(response.text) | ||
|
|
||
| # Temporary key name, deadlineSubmissionJob was already taken | ||
| if instance.data("runSlapComp", False): | ||
|
||
| instance.data["deadlineDependJob"] = response.json() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| """ | ||
| This module is for a standalone approach for Fusion similar to Maya. | ||
| Note that this will require FusionConsoleNode.exe and the BlackmagicFusion | ||
| module, | ||
|
|
||
| Deadline runs a python process, lets call it P | ||
|
|
||
| P will start the FusionConsoleNode in a new SUBPROCESS | ||
| This SUBPROCESS will need to have the same environment as P to ensure it can | ||
| use AVALON | ||
|
|
||
| P --> SUBPROCESS (FusionConsoleNode.EXE /listen) | ||
|
|
||
| From the SUBPROCESS comes a Fusion Console Node which will be used as the Fusion | ||
| instance to work in. In order to get the correct Fusion instance we use a | ||
| ScriptServer to get all Fusion programs which are running. | ||
| This is done by comparing the process ids with the subprocess.pid. | ||
|
|
||
| See `get_fusion_instance` function for more details | ||
|
|
||
| In `avalon.fusion.pipeline` we have create a work around to get the fusion | ||
| instance. This is done through | ||
|
|
||
| getattr(sys.module["__main__"], "fusion", None) | ||
|
|
||
| Because we do this we can also allow to set correct fusion module, this is done | ||
| by using the setattr. This will ensure that all other functions which are run | ||
| within `process()` can find `fusion`. | ||
|
|
||
| """ | ||
|
|
||
|
|
||
| import subprocess | ||
| import traceback | ||
| import site | ||
| import time | ||
| import sys | ||
| import os | ||
|
|
||
| # This script only works with Python 2.7 and 3.6 | ||
| version = "{0}{1}".format(*sys.version_info) # {major}{minor} | ||
| assert version in ["27", "36"], "Script only works in Python 2.7 or 3.6" | ||
| key = "FUSION_PYTHON{0}_HOME".format(version) | ||
|
|
||
| # Set Python 3.6 home for fusion, debug | ||
|
||
| print("Settings FUSION_PYTHON36_HOME ..") | ||
|
||
| os.environ[key] = os.path.dirname(sys.executable) | ||
|
|
||
| FUSCRIPT_EXE = r"C:/Program Files/Blackmagic Design/Fusion9/FuScript.exe" | ||
|
||
| FUSION_CONSOLE_EXE = r"C:/Program Files/Blackmagic Design/Fusion Render Node 9/FusionConsoleNode.exe" | ||
|
|
||
| import BlackmagicFusion as bmf | ||
|
|
||
|
|
||
| def _get_script_dir(): | ||
| """Get path to the image sequence script""" | ||
| try: | ||
| import colorbleed | ||
| scriptdir = os.path.dirname(colorbleed.__file__) | ||
| fusion_scripts = os.path.join(scriptdir, | ||
| "scripts", | ||
| "fusion") | ||
| except: | ||
| raise RuntimeError("This is a bug") | ||
|
|
||
| assert os.path.isdir(fusion_scripts), "Config is incomplete" | ||
| fusion_scripts = fusion_scripts.replace(os.sep, "/") | ||
|
|
||
| return fusion_scripts | ||
|
|
||
|
|
||
| def start_server(): | ||
| bmf.startserver() | ||
| return get_server() | ||
|
|
||
|
|
||
| def get_server(tries=10, timeout=0.5): | ||
|
|
||
| count = 0 | ||
| srv = None | ||
|
|
||
| while not srv: | ||
| count += 1 | ||
| print("Connecting to ScriptServer (try: %s)" % count) | ||
| srv = bmf.scriptapp("", "localhost", timeout) # Runs script server | ||
| if count > tries: | ||
| break | ||
|
|
||
| return srv | ||
|
|
||
|
|
||
| def get_fusion_instance(pid, srv, timeout=10): | ||
| """Get the fusion instance which has been launched""" | ||
|
|
||
| count = 0 | ||
| host = None | ||
| while not host: | ||
| if count > timeout: | ||
| break | ||
| fusion_hosts = srv.GetHostList().values() | ||
| host = next((i for i in fusion_hosts if int(i["ProcessID"]) == pid), | ||
| None) | ||
| if not host: | ||
| print("Find Fusion host... (%ss)" % count) | ||
| time.sleep(0.5) | ||
| count += 0.5 | ||
|
|
||
| assert host, "Fusion not found with pid: %s" % pid | ||
|
|
||
| return bmf.scriptapp(host["Name"], "localhost", 2, host["UUID"]) | ||
|
|
||
|
|
||
| def process(file_path, asset_name, deadline=False): | ||
| """Run switch in a Fusion Console Node (cmd) | ||
|
|
||
| Args: | ||
| file_path (str): File path of the comp to use | ||
| asset_name (str): Name of the asset (shot) to switch | ||
| deadline (bool, optional): If set True the new composition file will be | ||
| used to render | ||
| Returns: | ||
| None | ||
|
|
||
| """ | ||
|
|
||
| # Start a fusion console node in "listen" mode | ||
| proc = subprocess.Popen([FUSION_CONSOLE_EXE, "/listen"]) | ||
|
|
||
| srv = get_server() | ||
| if not srv: | ||
| print("No server found, starting server ..") | ||
| srv = start_server() | ||
|
|
||
| # Force fusion into main magical module so that host.ls() works | ||
| fusion = get_fusion_instance(proc.pid, srv) | ||
| assert fusion | ||
| print("Connected to: %s" % fusion) | ||
| setattr(sys.modules["__main__"], "fusion", fusion) | ||
|
|
||
| # Get fusion.pipeline | ||
| from avalon.fusion import pipeline | ||
|
|
||
| # This does not set | ||
| loaded_comp = fusion.LoadComp(file_path) | ||
| if not loaded_comp: | ||
| raise RuntimeError("Comp could not be loaded. File '%s'" % file_path) | ||
| pipeline.force_current_comp(loaded_comp) | ||
| current_comp = pipeline.get_current_comp() | ||
|
|
||
| assert loaded_comp == current_comp, "Could not find the correct comp" | ||
|
|
||
| print("Loaded comp name: %s" % current_comp.GetAttrs("COMPS_FileName")) | ||
|
|
||
| # Get switch and submit script | ||
| scriptdir = _get_script_dir() | ||
| site.addsitedir(scriptdir) | ||
| import switch_and_submit as switch | ||
|
|
||
| # Fusion host | ||
| try: | ||
| # Execute script in comp | ||
| result = switch.switch(asset_name=asset_name, deadline=deadline) | ||
| except: | ||
| tb = traceback.format_exc() | ||
| proc.terminate() # Ensure process closes when failing | ||
| raise RuntimeError(tb) | ||
|
|
||
| print("Success:", result is not None) | ||
| print("Closing all running process ..") | ||
| proc.terminate() | ||
|
|
||
|
|
||
| # Usability for deadline job submission | ||
| if __name__ == '__main__': | ||
|
|
||
| import argparse | ||
|
|
||
| parser = argparse.ArgumentParser(description="Switch to a shot within an" | ||
| "existing comp file") | ||
|
|
||
| parser.add_argument("--file_path", | ||
| type=str, | ||
| default=True, | ||
| help="File path of the comp to use") | ||
|
|
||
| parser.add_argument("--asset_name", | ||
| type=str, | ||
| default=True, | ||
| help="Name of the asset (shot) to switch") | ||
|
|
||
| parser.add_argument("--render", | ||
| default=False, | ||
| help="If set True the new composition file will be used" | ||
| "to render") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| process(file_path=args.file_path, | ||
| asset_name=args.asset_name, | ||
| deadline=args.render) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will break backwards compatibility for render global nodes that don't have the slap comp attribute yet, right?