Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ venv/
# Temp folder
tmp/

# Dev helper scripts
force_reload.ms
test_job_bundle/
scene_settings.json

# Kiro specs
.kiro/specs/
111 changes: 111 additions & 0 deletions docs/design/vray-standalone-file-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# V-Ray Standalone Submitter — File Map

Overview of all files added for the V-Ray Standalone workflow.

```
src/deadline/max_submitter/
├── vray_standalone_submitter.py — Main submitter logic: job bundle creation for
│ local export and farm export modes, path mapping
│ script loading, vrscene path fix script loading,
│ tile rendering integration
├── vrscene_settings.py — Settings dataclass with sticky persistence (export
│ mode, region grid, output format, movie settings)
├── ui/
│ └── vray_standalone_tab.py — Qt UI tab: export mode radios, job options, region
│ grid spinners, output format dropdown, browse button
├── utilities/
│ ├── vrscene_job_submission.py — Job template loading and parameter builders:
│ │ tile coordinate calculation, script injection,
│ │ parameter value builders for all job types
│ ├── vrscene_utils.py — V-Ray helpers: renderer detection, vrscene export
│ │ (local), settings validation, output format detection
│ └── vray_executable_utils.py — Executable path resolution for vray.exe and
│ 3dsmaxcmd.exe (env vars + install path fallback)
├── scripts/
│ ├── fix_vrscene_paths.py — Farm script: runs 3dsmaxcmd export then reverses
│ │ session paths so render -remapPath works correctly
│ ├── path_mapping_render.py — Farm script: reads path mapping rules and calls
│ │ vray.exe with -remapPath arguments
│ ├── tile_render.py — Farm script: renders a single tile region with vray.exe
│ ├── tile_merge.py — Farm script: merges tile images into complete frames
│ │ using Pillow (PNG/JPEG/TIFF) or OpenEXR
│ ├── create_movie.py — Farm script: creates MP4 from frames (future release)
│ └── export_vrscene_farm.ms — MAXScript: exports vrscene on the farm worker via
│ 3dsmaxcmd, called by fix_vrscene_paths.py
└── job_templates/
├── vray_render_job_template.yaml — OpenJD template: single-step vray.exe render
├── vray_export_job_template.yaml — OpenJD template: vrscene export step (farm mode)
├── vray_tile_render_job_template.yaml — OpenJD template: RenderRegions + MergeRegions steps
└── vray_combined_job_template.yaml — OpenJD template: export + render combined (farm mode)

install_files/
└── AWSDeadline-SubmitToDeadlineCloud-VRayStandalone.mcr — 3ds Max macro for menu entry
(installer bundling: follow-up PR)

force_reload.ms — Dev helper: reloads Python modules in 3ds Max
```

## Architecture Integration

How the V-Ray Standalone feature relates to the existing submitter/adaptor architecture.

### Existing Architecture Flow

```
run_ui.py
→ show_job_bundle_submitter()
→ SubmitMaxJobToDeadlineDialog(
job_setup_widget_type = SceneSettingsWidget,
on_create_job_bundle_callback = on_create_job_bundle_callback
)
```

The existing callback builds an OpenJD job template that uses the `3dsmax-openjd` adaptor.
The farm worker launches 3ds Max and renders inside it.

### V-Ray Hook Points

The V-Ray feature hooks into the existing architecture at exactly two points:

1. **`submit_dialog.py`** — `SubmitMaxJobToDeadlineDialog.__init__()` intercepts the original
callback, wraps it in `_on_create_job_bundle_wrapper()`, and calls
`_add_vray_export_tab_if_available()`. When V-Ray is the active renderer, a "V-Ray Export"
tab appears in the dialog.

2. **Submit-time routing** — The wrapper checks `vray_export_widget.is_vray_export_enabled()`:
- Enabled → routes to `on_create_vrscene_job_bundle_callback` with `VRSceneRenderSubmitterUISettings`
- Disabled → passes through to the original `on_create_job_bundle_callback` with `RenderSubmitterUISettings`

No other existing files are modified (except `data_const.py` for the settings file extension constant).

### Adaptor vs Adaptor-Free

| | Existing 3ds Max Workflow | V-Ray Standalone Workflow |
|---|---|---|
| Rendering | `3dsmax-openjd` adaptor launches 3ds Max on the farm | Farm scripts call `vray.exe` directly |
| Job template | Loaded from `default_max_job_template.yaml` | Loaded from YAML files in `job_templates/` |
| Path mapping | Handled by the adaptor | `path_mapping_render.py` reads `Session.PathMappingRulesFile` |
| Settings class | `RenderSubmitterUISettings` | `VRSceneRenderSubmitterUISettings` |
| UI tab | `SceneSettingsWidget` | `VRayStandaloneSettingsWidget` |
| Sticky settings ext | `.deadline_render_settings.json` | `.deadline_vrscene_settings.json` |

### Executable Path Resolution

Farm workers need `vray.exe` (Windows) on the `PATH` or via environment variable.
Set `VRAY_EXECUTABLE` to the full path of `vray.exe` on each worker — this is the recommended
approach and should be configured in the fleet host configuration or worker environment.
The fallback derives the path from `VRAY_FOR_3DSMAX{version}_MAIN` (set by the V-Ray installer).

Similarly, farm export mode requires `3dsmaxcmd.exe`. Set `MAXCMD_EXECUTABLE` to its full path,
or the submitter will derive it from the current 3ds Max installation at submit time.

### Coupling Summary

The V-Ray feature is a parallel workflow that shares the dialog shell but has its own:
- Settings dataclass (`vrscene_settings.py`)
- UI tab (`vray_standalone_tab.py`)
- Submit callback (`vray_standalone_submitter.py`)
- Job templates (`job_templates/`)
- Farm scripts (`scripts/`)

Everything is additive. The only modified existing files are `submit_dialog.py` (callback wrapper + tab injection) and `data_const.py` (one constant).
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- AWS Deadline Cloud - V-Ray Standalone Submitter Macro
-- Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

macroScript SubmitToDeadlineCloudVRayStandalone
category:"AWS Deadline Cloud"
buttonText:"Submit V-Ray Standalone"
toolTip:"Submit V-Ray Standalone Render Job To Deadline Cloud"
icon:#("Deadline",1)
(
-- Check if V-Ray is the current renderer
local isVRay = false
try
(
local rendererName = (classof renderers.current) as string
isVRay = (findString rendererName "VRay" != undefined) or (findString rendererName "V_Ray" != undefined)
)
catch
(
isVRay = false
)

if not isVRay then
(
messageBox "V-Ray Standalone workflow requires V-Ray as the active renderer.\n\nPlease set V-Ray as your current renderer to use this workflow." title:"V-Ray Not Active"
return false
)

-- Import the Python module and run the UI
python.Execute "import sys"
python.Execute "import os"

-- Try development path first, then installed path
local devPath = "C:\\Users\\Administrator\\gitrepos\\deadline-cloud-for-3ds-max\\src\\deadline\\max_submitter"
local installedPath = (getDir #scripts) + "\\deadline\\max_submitter"

local submitterPath = installedPath
if (doesFileExist (devPath + "\\run_vray_standalone_ui.py")) then (
submitterPath = devPath
format "Using development path: %\n" submitterPath
) else (
format "Using installed path: %\n" submitterPath
)

python.Execute ("sys.path.insert(0, r'" + submitterPath + "')")

-- Launch the V-Ray Standalone submitter
python.Execute "from run_vray_standalone_ui import main"
python.Execute "main()"
)
104 changes: 104 additions & 0 deletions scripts/MAXScript_VRayStandaloneExport.ms
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
-- V-Ray Standalone Export MAXScript Job
-- Runs on Deadline workers to export vrscene files
-- Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

try
(
local du = DeadlineUtil -- Interface provided by Deadline 3ds Max plugin
if du == undefined do
(
-- Stand-in struct for testing outside Deadline
struct DeadlineUtilStruct
(
fn SetTitle title = ( format "Title: %\n" title ),
fn SetProgress percent = (true),
fn FailRender msg = ( throw msg ),
fn GetJobInfoEntry key = ( undefined ),
fn LogMessage msg = ( format "INFO: %\n" msg ),
fn WarnMessage msg = ( format "WARNING: %\n" msg ),
CurrentFrame = ((sliderTime as string) as integer),
CurrentTask = ( -1 ),
SceneFileName = ( maxFilePath + maxFileName ),
SceneFilePath = ( maxFilePath )
)
du = DeadlineUtilStruct()
)

fn ExportToVRayStd exportPath startFrame endFrame incrBaseFrame: =
(
-- Create output directory
makeDir (getFileNamePath exportPath) all:true
sysinfo.currentdir = getFileNamePath exportPath

-- Ensure VRayRT is the active shade renderer
if not (iskindof renderers.activeShade VRayRT) do
renderers.activeShade = VRayRT()

-- Export based on frame range
if startFrame == endFrame then
-- Single frame export
vrayExportRTScene exportPath separateFiles:false
else if incrBaseFrame != unsupplied and incrBaseFrame != startFrame then
-- Incremental export (not first frame)
vrayExportRTScene exportPath separateFiles:false startFrame:startFrame endFrame:endFrame incrBaseFrame:incrBaseFrame
else
-- Full animation or first frame of incremental
vrayExportRTScene exportPath separateFiles:false startFrame:startFrame endFrame:endFrame
)

local st = timestamp()

du.SetTitle "VRSCENE Export MAXScript Job"
du.LogMessage ">Starting VRSCENE Export MAXScript Job..."

-- Get job parameters
local startFrame = (du.GetJobInfoEntry "Render_StartFrame") as integer
local endFrame = (du.GetJobInfoEntry "Render_EndFrame") as integer
local vrsceneName = du.GetJobInfoEntry "Render_InputFilename"
local exportAnimationMode = du.GetJobInfoEntry "Render_ExportAnimationMode"

-- Default to Single File mode
if exportAnimationMode == "" do
exportAnimationMode = "1"
exportAnimationMode = exportAnimationMode as integer

du.LogMessage ">Exporting VRSCENE File..."

case exportAnimationMode of
(
1: (
-- Single File: Export entire animation to one vrscene
ExportToVRayStd vrsceneName startFrame endFrame
)
2: (
-- File Per Frame: Export current frame to separate vrscene
local frameVrsceneName = (getFilenamePath vrsceneName) + (getFilenameFile vrsceneName) + "." + (formattedPrint du.CurrentFrame format:"04i") + (getFilenameType vrsceneName)
ExportToVRayStd frameVrsceneName du.CurrentFrame (du.CurrentFrame + 1)
)
3: (
-- File Per Frame (Incremental): Export with reference to first frame
local frameVrsceneName = (getFilenamePath vrsceneName) + (getFilenameFile vrsceneName) + "." + (formattedPrint du.CurrentFrame format:"04i") + (getFilenameType vrsceneName)
ExportToVRayStd frameVrsceneName du.CurrentFrame (du.CurrentFrame + 1) incrBaseFrame:startFrame
)
)

du.LogMessage ("+Finished VRSCENE Export MAXScript Job in "+ ((timestamp() - st)/1000.0) as string + " sec.")
true
)
catch
(
-- Error handling with stack trace (Max 2017+)
if ((maxVersion())[1]/1000 as integer) >= 19 then
(
if hasCurrentExceptionStackTrace() then
(
local errorMessage = getCurrentException()
DeadlineUtil.WarnMessage("MaxScript Error: " + errorMessage)
local stackTrace = getCurrentExceptionStackTrace()
stackTrace = filterString stackTrace "\n"
for line in stackTrace do
DeadlineUtil.WarnMessage(line)
)
)
throw()
)
14 changes: 14 additions & 0 deletions src/deadline/max_submitter/data_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,17 @@
["Left, Right and Center", "All"],
["Disable Stereo Camera Submission", "None"],
]

# V-Ray Standalone Workflow Constants
VRSCENE_EXPORT_MODES = [
["Export VRSCENE On This Workstation", 1],
["Export VRSCENE On Deadline", 2],
]

VRSCENE_EXPORT_ANIMATION_MODES = [
["Single File", 1],
["File Per Frame", 2],
["File Per Frame (Incremental)", 3],
]

VRSCENE_SUBMITTER_SETTINGS_FILE_EXT = ".deadline_vrscene_settings.json"
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
specificationVersion: 'jobtemplate-2023-09'
name: VRay Export and Render
parameterDefinitions:
- name: MaxCmdExecutable
type: STRING
default: 3dsmaxcmd
description: 3dsmaxcmd.exe executable for MAXScript execution
- name: VRayExecutable
type: STRING
default: vray
description: V-Ray Standalone executable
- name: SceneFile
type: PATH
objectType: FILE
dataFlow: IN
description: The 3ds Max scene file to export
- name: VRSceneOutputPath
type: PATH
objectType: FILE
dataFlow: INOUT
description: Output path for the vrscene file(s)
- name: OutputDir
type: PATH
objectType: DIRECTORY
dataFlow: OUT
description: Output directory for rendered images
- name: Frames
type: STRING
description: Frame range
- name: ExportAnimationMode
type: INT
default: '1'
allowedValues:
- '1'
- '2'
- '3'
description: 1=Single File, 2=File Per Frame, 3=File Per Frame (Incremental)
- name: OutputFileName
type: STRING
description: Output filename with extension
- name: RegionColumns
type: INT
description: Number of region columns
- name: RegionRows
type: INT
description: Number of region rows
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
specificationVersion: 'jobtemplate-2023-09'
name: VRayStandaloneExport
parameterDefinitions:
- name: SceneFile
type: PATH
objectType: FILE
dataFlow: IN
description: The 3ds Max scene file to export
- name: VRSceneOutputPath
type: PATH
objectType: FILE
dataFlow: OUT
description: Output path for the vrscene file
- name: StartFrame
type: INT
default: '1'
- name: EndFrame
type: INT
default: '1'
- name: ExportAnimationMode
type: INT
default: '1'
allowedValues:
- '1'
- '2'
- '3'
description: 1=Single File, 2=File Per Frame, 3=File Per Frame (Incremental)
steps:
- name: ExportVRScene
parameterSpace:
taskParameterDefinitions:
- name: Frame
type: INT
range: '{{Param.StartFrame}}-{{Param.EndFrame}}'
stepEnvironments:
- name: 3dsMax
description: Runs 3ds Max to export vrscene
script:
actions:
onRun:
command: 3dsmaxbatch
args:
- -sceneFile
- '{{Param.SceneFile}}'
- -script
- '{{Task.File.ExportScript}}'
embeddedFiles:
- name: ExportScript
type: TEXT
filename: export_vrscene.ms
data: INJECT_EXPORT_SCRIPT
Loading
Loading