{"id":17,"date":"2024-09-08T10:03:55","date_gmt":"2024-09-08T10:03:55","guid":{"rendered":"https:\/\/blog.rat9.co.uk\/?p=17"},"modified":"2024-09-08T10:03:55","modified_gmt":"2024-09-08T10:03:55","slug":"simple-gaffer-multishot-setup","status":"publish","type":"post","link":"https:\/\/blog.rat9.co.uk\/index.php\/2024\/09\/08\/simple-gaffer-multishot-setup\/","title":{"rendered":"Simple Gaffer Multishot Setup"},"content":{"rendered":"\n<p>Back in the old days, when pint only cost \u00a33 ( unless you wanted Guinness ) every shot was worked on separately as an individual piece of work.<\/p>\n\n\n\n<p>That meant a separate lighting scene for each shot, and something like re-rendering a project at a different resolution required every individual scene to be opened one by one, updated and sent to the farm.<\/p>\n\n\n\n<p>The future arrived long ago, and now ( along with flying cars ), multishot lighting is so normal that its barely worth writing about.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A list of shots<\/h2>\n\n\n\n<p>So the most obvious requirement is a list of shots.  <\/p>\n\n\n\n<p>If you are using Ftrack or Shotgun, then these systems should already contain this data, and it&#8217;s just a case of working out the api call to get it out&#8230;.  <\/p>\n\n\n\n<p>But be warned:   These databases are typically crazy slow, like wait several seconds &#8211; not conducive to a responsive interface.<\/p>\n\n\n\n<p>Having your own mariadb\/postgres\/whatever database allows you instant data access &#8211; but at the cost of setting it up, and needing to constantly sync data from the &#8220;real&#8221; production tracker.  <\/p>\n\n\n\n<p>This has been my approach in the past, but a simpler option is to just have a list of the data.  <\/p>\n\n\n\n<p>You can store this list in a spreadsheet node, and the gaffer developers seem to be encouraging this approach ( have a look at the example scene in Help&gt;Examples&gt;Rendering&gt;Multishot ). <\/p>\n\n\n\n<p>Or you can just hard code it into a startup file, which you are going to need anyway for the menu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">ShotPicker Menu<\/h2>\n\n\n\n<p>A way to conveniently switch shots is clearly needed, and one approach is to have the shots in a dropdown menu:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"500\" src=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPicker-1024x500.png\" alt=\"\" class=\"wp-image-26\" srcset=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPicker-1024x500.png 1024w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPicker-300x146.png 300w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPicker-768x375.png 768w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPicker.png 1145w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In this case we have multiple episodes, sequences and shots, but in many cases the whole project might only be a few shots and we can go simpler.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Startup script<\/h2>\n\n\n\n<p>I will cut to the chase.<\/p>\n\n\n\n<p>Copy this code into a file called <code>whateverYouWant.py<\/code> and place it in \/startup\/gui\/<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import Gaffer\nimport IECore\nimport GafferUI\nimport functools\n\nSHOT_LIST = {}\nSHOT_LIST&#91;\"SH010\"] ={\"fstart\": 1001, \"fend\":1248}\nSHOT_LIST&#91;\"SH020\"] ={\"fstart\": 1001, \"fend\":1050}\nSHOT_LIST&#91;\"SH030\"] ={\"fstart\": 1001, \"fend\":1100}\nSHOT_LIST&#91;\"SH040\"] ={\"fstart\": 1001, \"fend\":1048}\nSHOT_LIST&#91;\"SH050\"] ={\"fstart\": 1001, \"fend\":1215}\n\n# ensure any global variables exist:\ndef __add_globals(container, script):\n    custom_vars = &#91;\"shot\"]\n    for var in custom_vars:\n        if var not in script&#91;\"variables\"]:\n            script&#91;\"variables\"].addChild(\n                Gaffer.NameValuePlug(var, Gaffer.StringPlug(\"value\", defaultValue=\"none\"), True, var, Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic)\n            )\n            Gaffer.Metadata.registerValue(script&#91;\"variables\"]&#91;var], 'readOnly', True)\n\napplication.root()&#91;\"scripts\"].childAddedSignal().connect(__add_globals, scoped=False)\n\ndef set_framerange(root, fstart, fend):\n    root&#91;'frameRange']&#91;\"start\"].setValue(int(fstart))\n    root&#91;'frameRange']&#91;\"end\"].setValue(int(fend))\n    playback = GafferUI.Playback.acquire( root.context() )\n    playback.setFrameRange(int(fstart), int(fend))\n\ndef shot_selection(menu, shot):\n    scriptWindow = menu.ancestor( GafferUI.ScriptWindow )\n    script = scriptWindow.scriptNode()\n    script&#91;'variables']&#91;'shot']&#91;'value'].setValue(shot)\n    set_framerange(script, SHOT_LIST&#91;shot]&#91;\"fstart\"], SHOT_LIST&#91;shot]&#91;\"fend\"])\n\ndef shotMenu(menu):\n    subMenuDefinition = IECore.MenuDefinition()\n    for shot in SHOT_LIST.keys():\n        menuString = \"\/\" + shot\n        subMenuDefinition.append(menuString, {\"command\": functools.partial(shot_selection, shot=shot)})\n    return subMenuDefinition\n\n# create menu\nm = GafferUI.ScriptWindow.menuDefinition(application)\nm.append(\"\/ShotPicker\", {\"subMenu\": shotMenu})<\/code><\/pre>\n\n\n\n<p>I won&#8217;t explain the code, I think it&#8217;s mostly easy to follow what it&#8217;s doing.<\/p>\n\n\n\n<p>You should end up with a simple shot picker, and when you pick a shot, the frame range gets updated to whatever has been written into the hard coded list.<\/p>\n\n\n\n<p>The other thing that happens is the &#8220;shot&#8221; variable gets updated to match the selected shot.   <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"764\" height=\"376\" src=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPickerSimple.png\" alt=\"\" class=\"wp-image-38\" srcset=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPickerSimple.png 764w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/shotPickerSimple-300x148.png 300w\" sizes=\"(max-width: 764px) 100vw, 764px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Context backdrop<\/h2>\n\n\n\n<p>The current selected shot is essentially invisible, and a quick way to solve that is to have a backdrop node which tells you what it is:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"910\" height=\"349\" src=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/backdrop.png\" alt=\"\" class=\"wp-image-40\" srcset=\"https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/backdrop.png 910w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/backdrop-300x115.png 300w, https:\/\/blog.rat9.co.uk\/wp-content\/uploads\/2024\/08\/backdrop-768x295.png 768w\" sizes=\"(max-width: 910px) 100vw, 910px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Loaders and output paths<\/h2>\n\n\n\n<p>So you ultimately you might want a system by which your approved camera\/animation\/whatever version gets loaded automatically by a relevant loader node.<\/p>\n\n\n\n<p>This can be achieved via a sceneReader node with a python expression driving the path.<\/p>\n\n\n\n<p>A much simpler approach is to just have a pre-defined location on disk for each asset type, and then use the shot variable in a scene reader path to load the asset for the current shot.<\/p>\n\n\n\n<p>for example you could have your shot cameras on disk:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>disk\/project\/SH010\/cameras\/approved_camera.abc\ndisk\/project\/SH020\/cameras\/approved_camera.abc\ndisk\/project\/SH030\/cameras\/approved_camera.abc\ndisk\/project\/SH040\/cameras\/approved_camera.abc<\/code><\/pre>\n\n\n\n<p>now your &#8220;camera loader&#8221; can simply be a scene reader with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>disk\/project\/${shot}\/cameras\/approved_camera.abc<\/code><\/pre>\n\n\n\n<p>in the path.  simple.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Back in the old days, when pint only cost \u00a33 ( unless you wanted Guinness ) every shot was worked on separately as an individual piece of work. That meant a separate lighting scene for each shot, and something like re-rendering a project at a different resolution required every individual scene to be opened one [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":19,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"_links":{"self":[{"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/17"}],"collection":[{"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/comments?post=17"}],"version-history":[{"count":15,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/17\/revisions"}],"predecessor-version":[{"id":46,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/17\/revisions\/46"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/media\/19"}],"wp:attachment":[{"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/media?parent=17"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/categories?post=17"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rat9.co.uk\/index.php\/wp-json\/wp\/v2\/tags?post=17"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}