|
| 1 | +# Blender Turntable to Autodesk Flow Production Tracking |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +This job bundle renders an animated turntable in Blender, encodes it into a |
| 6 | +review-ready movie, extracts a poster-frame thumbnail, and publishes the |
| 7 | +result to [Autodesk Flow Production Tracking](https://www.autodesk.com/products/flow-production-tracking/overview) |
| 8 | +(formerly ShotGrid) as a new `Version` on an `Asset`'s review `Task`. |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +It is a deliberately end-to-end, representative example of a render-and-publish |
| 13 | +pipeline on AWS Deadline Cloud, and it demonstrates two core patterns: |
| 14 | + |
| 15 | +1. Post-render work modeled as job steps. Tasks like uploading to Flow, |
| 16 | + registering versions, and generating thumbnails are expressed as discrete |
| 17 | + steps in the job, not as side effects hidden inside the render. |
| 18 | +2. Chained, dependent, non-render tasks (movie encode, thumbnail, publish) |
| 19 | + that run after a render and fan out in parallel. |
| 20 | + |
| 21 | +## Why post-render work belongs in a job step |
| 22 | + |
| 23 | +The canonical way to do post-render work on Deadline Cloud — uploading to Flow, |
| 24 | +registering versions, generating thumbnails — is a discrete OpenJD step with a |
| 25 | +step dependency. Each unit of post-render work is a first-class farm task, |
| 26 | +which means it is: |
| 27 | + |
| 28 | +- Observable — it shows up in the monitor with its own logs and status. |
| 29 | +- Independently retryable — a failed Flow publish can be retried without |
| 30 | + re-rendering a single frame. |
| 31 | +- Independently schedulable — the publish step can run on a cheap CPU fleet |
| 32 | + instead of the GPU render fleet (this sample keeps them on one fleet for |
| 33 | + simplicity, but the steps are separable). |
| 34 | +- Parallelizable — `GenerateMovie` and `GenerateThumbnail` run at the same |
| 35 | + time because neither depends on the other. |
| 36 | + |
| 37 | +Modeling each piece of post-render work as its own step is what gives you all of |
| 38 | +the above, and it's the pattern to reach for when wiring a Flow publish into a |
| 39 | +render job. |
| 40 | + |
| 41 | +## The step graph |
| 42 | + |
| 43 | +``` |
| 44 | +RenderTurntable (Blender; one task per frame, parameter-space over the frame range) |
| 45 | + ├─ GenerateMovie (depends: RenderTurntable) ffmpeg frames → H.264 mp4 |
| 46 | + ├─ GenerateThumbnail (depends: RenderTurntable) mid frame → jpg |
| 47 | + └─ PublishToFlow (depends: GenerateMovie, GenerateThumbnail) |
| 48 | + create Version, upload movie + thumbnail, advance Task |
| 49 | +``` |
| 50 | + |
| 51 | +`GenerateMovie` and `GenerateThumbnail` both depend only on `RenderTurntable`, so |
| 52 | +they run in parallel — a clear visual demonstration of step fan-out in the |
| 53 | +monitor. `PublishToFlow` depends on both because it uploads both artifacts. |
| 54 | + |
| 55 | +## How this maps to the Flow entity model |
| 56 | + |
| 57 | +The sample follows idiomatic Flow conventions: |
| 58 | + |
| 59 | +- The `Version` is the hero entity — the reviewable media record. The movie is |
| 60 | + uploaded to `sg_uploaded_movie` (what plays in the review player); |
| 61 | + `sg_path_to_movie` / `sg_path_to_frames` hold the filesystem paths. |
| 62 | +- The thumbnail belongs on the `Version` (uploaded via `upload_thumbnail`), |
| 63 | + which populates the Version's `image` field — that's what shows in the review |
| 64 | + grid. It is not attached to the Asset. |
| 65 | +- Status is advanced on the `Task`, not the Asset. In Flow, an Asset isn't |
| 66 | + marked "rendered"; instead the review Task (for example a "Model" or |
| 67 | + "Turntable" task) is advanced. Many studios use a `rev` ("pending review") |
| 68 | + status for this, but `rev` is not a stock status code — it varies per site. |
| 69 | + The `FlowTaskStatus` parameter defaults to `fin` because the demo site only |
| 70 | + defines the stock codes `wtg` / `ip` / `fin`; set it to whatever your site |
| 71 | + uses (`rev` is the conventional choice where it exists). |
| 72 | +- The work hangs off an Asset rather than a Shot. An "Asset turntable" — build |
| 73 | + an asset, render a turntable to review the model or look — is self-contained, |
| 74 | + with no sequence/shot hierarchy required, and maps directly to "render an |
| 75 | + object and review it." |
| 76 | + |
| 77 | +## Prerequisites |
| 78 | + |
| 79 | +The job needs Blender, FFmpeg, and Python. On Deadline Cloud |
| 80 | +service-managed fleets, use a [conda queue |
| 81 | +environment](https://docs.aws.amazon.com/deadline-cloud/latest/developerguide/provide-applications.html) |
| 82 | +with: |
| 83 | + |
| 84 | +- `CondaPackages`: `blender ffmpeg python>=3.10 pip` |
| 85 | +- `CondaChannels`: `deadline-cloud conda-forge` |
| 86 | + |
| 87 | +Blender is on the `deadline-cloud` channel; FFmpeg is on the `conda-forge` |
| 88 | +community channel. |
| 89 | + |
| 90 | +> Note on `shotgun_api3`: the Flow Python client is published on PyPI |
| 91 | +> only (it is *not* on conda-forge). The `PublishToFlow` step therefore |
| 92 | +> `pip install`s `shotgun_api3` (and `boto3`) at runtime into the conda |
| 93 | +> environment. This mixed conda + pip approach is normal for Python client |
| 94 | +> libraries that have no conda package. A team that wants fully reproducible, |
| 95 | +> offline-capable environments can instead build an internal conda package or |
| 96 | +> bake the dependency into a custom worker image. |
| 97 | +
|
| 98 | +## Flow credentials: AWS Secrets Manager (do this once) |
| 99 | + |
| 100 | +Flow script credentials are stored in AWS Secrets Manager. The `PublishToFlow` |
| 101 | +step reads them at runtime using the worker's queue role, so the credentials |
| 102 | +stay out of the job bundle and out of the job's parameters. |
| 103 | + |
| 104 | +### 1. Create the secret |
| 105 | + |
| 106 | +The secret value is JSON with three keys — your Flow site URL, the script |
| 107 | +name (from Flow → Admin → Scripts), and its API key: |
| 108 | + |
| 109 | +```bash |
| 110 | +aws secretsmanager create-secret \ |
| 111 | + --region us-west-2 \ |
| 112 | + --name "deadline-cloud-samples/flow-production-tracking" \ |
| 113 | + --description "Flow Production Tracking script credentials for the turntable demo job." \ |
| 114 | + --secret-string '{ |
| 115 | + "site_url": "https://your-site.shotgrid.autodesk.com", |
| 116 | + "script_name": "your-script-name", |
| 117 | + "api_key": "your-api-key" |
| 118 | + }' |
| 119 | +``` |
| 120 | + |
| 121 | +Note the returned `ARN` — you'll pass it as the `FlowSecretArn` job parameter. |
| 122 | + |
| 123 | +To update the credentials later: |
| 124 | + |
| 125 | +```bash |
| 126 | +aws secretsmanager put-secret-value \ |
| 127 | + --region us-west-2 \ |
| 128 | + --secret-id "deadline-cloud-samples/flow-production-tracking" \ |
| 129 | + --secret-string '{ "site_url": "...", "script_name": "...", "api_key": "..." }' |
| 130 | +``` |
| 131 | + |
| 132 | +### 2. Grant the queue role permission to read the secret |
| 133 | + |
| 134 | +The job runs under your queue role (the `roleArn` on your queue). That role |
| 135 | +must be allowed `secretsmanager:GetSecretValue` on the secret ARN. Find the role |
| 136 | +name: |
| 137 | + |
| 138 | +```bash |
| 139 | +aws deadline get-queue \ |
| 140 | + --region us-west-2 \ |
| 141 | + --farm-id farm-XXXX --queue-id queue-XXXX \ |
| 142 | + --query roleArn --output text |
| 143 | +# e.g. arn:aws:iam::<account>:role/service-role/AWSDeadlineCloudQueueRole-XXXXXXXX |
| 144 | +``` |
| 145 | + |
| 146 | +Attach an inline policy granting read access to just this secret (replace the |
| 147 | +role name and ARN): |
| 148 | + |
| 149 | +```bash |
| 150 | +aws iam put-role-policy \ |
| 151 | + --role-name AWSDeadlineCloudQueueRole-XXXXXXXX \ |
| 152 | + --policy-name FlowProductionTrackingSecretRead \ |
| 153 | + --policy-document '{ |
| 154 | + "Version": "2012-10-17", |
| 155 | + "Statement": [ |
| 156 | + { |
| 157 | + "Sid": "ReadFlowProductionTrackingSecret", |
| 158 | + "Effect": "Allow", |
| 159 | + "Action": "secretsmanager:GetSecretValue", |
| 160 | + "Resource": "arn:aws:secretsmanager:us-west-2:<account>:secret:deadline-cloud-samples/flow-production-tracking-XXXXXX" |
| 161 | + } |
| 162 | + ] |
| 163 | + }' |
| 164 | +``` |
| 165 | + |
| 166 | +> Scope the `Resource` to the exact secret ARN (including the random suffix |
| 167 | +> Secrets Manager appends). Do not grant `secretsmanager:GetSecretValue` on `*`. |
| 168 | +
|
| 169 | +## Bundle layout |
| 170 | + |
| 171 | +``` |
| 172 | +blender_turntable_to_flow/ |
| 173 | +├── template.yaml the OpenJD job template |
| 174 | +├── hooks.yaml submission hook configuration (preSubmission) |
| 175 | +└── scripts/ |
| 176 | + ├── build_turntable.py builds the turntable scene in Blender and renders a frame |
| 177 | + ├── publish_to_flow.py creates the Version and uploads the movie + thumbnail |
| 178 | + └── flow_params_from_env.py preSubmission hook: fills the Flow parameters from the environment |
| 179 | +``` |
| 180 | + |
| 181 | +The two render/publish scripts are referenced by the template as `dataFlow: IN` |
| 182 | +`PATH` parameters (`BuildTurntableScript`, `PublishScript`). Deadline Cloud |
| 183 | +uploads them with the job's attachments and path-maps them onto the worker, so |
| 184 | +the steps run them as ordinary files rather than embedding the source inline. |
| 185 | + |
| 186 | +## Filling Flow parameters from the environment (submission hook) |
| 187 | + |
| 188 | +Studios usually already have environment or project-tracking tooling that sets |
| 189 | +environment variables when an artist opens a shell or launches an application |
| 190 | +(via Rez, a launcher, a "set project" script, and so on). This bundle uses a |
| 191 | +[Deadline Cloud submission hook](https://github.com/aws-deadline/deadline-cloud/blob/mainline/docs/submission-hooks.md) |
| 192 | +to read those variables and fill in the job's Flow parameters, so the |
| 193 | +submitting artist doesn't re-enter the project id, asset name, secret ARN, etc. |
| 194 | +by hand. |
| 195 | + |
| 196 | +The Flow parameters in the template are `HIDDEN` and carry only placeholder |
| 197 | +defaults. `hooks.yaml` registers a `preSubmission` hook |
| 198 | +(`scripts/flow_params_from_env.py`) that runs as part of every submission — CLI |
| 199 | +or GUI — and rewrites those parameter defaults from the environment before the |
| 200 | +job is created. |
| 201 | + |
| 202 | +| Environment variable | Job parameter | Required | |
| 203 | +|----------------------|---------------|----------| |
| 204 | +| `FLOW_PROJECT_ID` | `FlowProjectId` | yes | |
| 205 | +| `FLOW_ASSET_NAME` | `FlowAssetName` | yes | |
| 206 | +| `FLOW_SECRET_ARN` | `FlowSecretArn` | yes | |
| 207 | +| `FLOW_ASSET_TYPE` | `FlowAssetType` | no | |
| 208 | +| `FLOW_STEP_SHORT_NAME` | `FlowStepShortName` | no | |
| 209 | +| `FLOW_TASK_NAME` | `FlowTaskName` | no | |
| 210 | +| `FLOW_TASK_STATUS` | `FlowTaskStatus` | no | |
| 211 | +| `FLOW_PUBLISH` | `EnableFlowPublish` | no | |
| 212 | + |
| 213 | +If a required variable is missing, the hook exits non-zero and the submission is |
| 214 | +aborted with a message telling you which variables to set. To submit without |
| 215 | +publishing (render → movie → thumbnail only), set `FLOW_PUBLISH=FALSE`; the |
| 216 | +required-variable check is then skipped. |
| 217 | + |
| 218 | +Bundle hooks are disabled by default. Enable them once: |
| 219 | + |
| 220 | +```bash |
| 221 | +deadline config set settings.allow_bundle_hooks true |
| 222 | +``` |
| 223 | + |
| 224 | +With the hook in place, a submission only needs the render parameters — the Flow |
| 225 | +parameters come from the environment: |
| 226 | + |
| 227 | +```bash |
| 228 | +# A studio "set project" step would normally export these for you: |
| 229 | +export FLOW_PROJECT_ID=1234 |
| 230 | +export FLOW_ASSET_NAME="Hero Vehicle" |
| 231 | +export FLOW_SECRET_ARN=arn:aws:secretsmanager:us-west-2:<account>:secret:deadline-cloud-samples/flow-production-tracking-XXXXXX |
| 232 | + |
| 233 | +deadline bundle submit blender_turntable_to_flow/ -p ObjectShape=monkey -p FrameRange=1-48 |
| 234 | +``` |
| 235 | + |
| 236 | +The hook reads and writes the template with PyYAML, so the Python that runs it |
| 237 | +(the Deadline Cloud CLI's Python) needs `pyyaml` available. |
| 238 | + |
| 239 | +## Example submission |
| 240 | + |
| 241 | +These assume the `FLOW_*` environment variables are set (see the previous |
| 242 | +section) and bundle hooks are enabled. |
| 243 | + |
| 244 | +### GUI submission |
| 245 | + |
| 246 | +```bash |
| 247 | +deadline bundle gui-submit blender_turntable_to_flow/ |
| 248 | +``` |
| 249 | + |
| 250 | +### CLI submission |
| 251 | + |
| 252 | +```bash |
| 253 | +deadline bundle submit blender_turntable_to_flow/ \ |
| 254 | + -p ObjectShape=monkey \ |
| 255 | + -p FrameRange=1-48 |
| 256 | +``` |
| 257 | + |
| 258 | +### Render-only (no Flow) |
| 259 | + |
| 260 | +To exercise just the render → movie → thumbnail pipeline without Flow |
| 261 | +credentials, set `FLOW_PUBLISH=FALSE` so the hook skips the required-variable |
| 262 | +check and the publish step is skipped: |
| 263 | + |
| 264 | +```bash |
| 265 | +FLOW_PUBLISH=FALSE deadline bundle submit blender_turntable_to_flow/ \ |
| 266 | + -p ObjectShape=torus |
| 267 | +``` |
| 268 | + |
| 269 | +## Parameters |
| 270 | + |
| 271 | +| Parameter | Default | Description | |
| 272 | +|-----------|---------|-------------| |
| 273 | +| ObjectShape | `monkey` | Procedural subject: monkey, cube, torus, ico_sphere, cylinder, cone | |
| 274 | +| FrameRange | `1-48` | Frame range; a full 360° rotation is spread across it | |
| 275 | +| ResolutionX / ResolutionY | `960` / `540` | Render resolution | |
| 276 | +| Samples | `48` | Cycles render samples | |
| 277 | +| OutputDir | `output` | Working/output dir; frames in `OutputDir/frames`, movie+thumbnail in `OutputDir` | |
| 278 | +| FrameRate | `24` | Output movie fps | |
| 279 | +The Flow parameters below are `HIDDEN` and supplied by the preSubmission hook |
| 280 | +from the `FLOW_*` environment variables; the "default" column is only the |
| 281 | +template placeholder. |
| 282 | + |
| 283 | +| Parameter | Default | Description | |
| 284 | +|-----------|---------|-------------| |
| 285 | +| EnableFlowPublish | `TRUE` | `FALSE` (via `FLOW_PUBLISH`) skips the Flow publish step | |
| 286 | +| FlowSecretArn | *(empty)* | Secrets Manager ARN holding `{site_url, script_name, api_key}` | |
| 287 | +| FlowProjectId | `0` | Numeric Flow Project id to publish into | |
| 288 | +| FlowAssetName | *(empty)* | Asset code (found-or-created) | |
| 289 | +| FlowAssetType | `Prop` | `sg_asset_type` used if the Asset is created | |
| 290 | +| FlowStepShortName | `MDL` | Pipeline Step short name for the review Task (if created) | |
| 291 | +| FlowTaskName | `Turntable` | Task content/name on the Asset (found-or-created) | |
| 292 | +| FlowTaskStatus | `fin` | Status set on the Task after publishing (`rev` where defined) | |
| 293 | +| BuildTurntableScript | `scripts/build_turntable.py` | Blender scene/render script shipped in the bundle (hidden) | |
| 294 | +| PublishScript | `scripts/publish_to_flow.py` | Flow publish script shipped in the bundle (hidden) | |
0 commit comments