├── .gitignore ├── VFXTricksToolbox_install_guide_en.pdf ├── houdini19.0 └── packages │ ├── VFXTricks │ ├── otls │ │ ├── vfxtricks.rop_husk.1.0.hdalc │ │ ├── vfxtricks.rop_cacher.1.0.hdalc │ │ ├── vfxtricks.sop_cacher.1.0.hdalc │ │ ├── vfxtricks.sop_switcher.1.0.hdalc │ │ ├── vfxtricks.rop_usd_cacher.1.0.hdalc │ │ ├── vfxtricks.sop_ocean_tiler.1.0.hdalc │ │ ├── vfxtricks.obj_camera_frustum.1.0.hdalc │ │ ├── vfxtricks.rop_farm_submitter.1.0.hdalc │ │ ├── vfxtricks.sop_camera_frustum.1.0.hdalc │ │ ├── vfxtricks.sop_cull_by_volume.1.0.hdalc │ │ ├── vfxtricks.sop_cull_geometry.1.0.hdalc │ │ ├── vfxtricks.lop_cacher_importer.1.0.hdalc │ │ └── vfxtricks.sop_ocean_mask_points.1.0.hdalc │ ├── scripts │ │ └── run_husk.py │ └── toolbar │ │ └── VFXTricks.shelf │ └── VFXTricks.json ├── DeadlineRepository10 └── custom │ └── plugins │ ├── HuskVFXTricks │ ├── HuskVFXTricks.ico │ ├── HuskVFXTricks.param │ ├── HuskVFXTricks.options │ └── HuskVFXTricks.py │ └── HoudiniVFXTricks │ ├── HoudiniVFXTricks.ico │ ├── HoudiniVFXTricks.param │ ├── HoudiniVFXTricks.options │ ├── HoudiniVFXTricks.py │ └── hrender_dlVFXTricks.py ├── LICENSE.md ├── UnrealEngine └── SequencerExporter.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/backup 2 | **/VFXTricksDev.shelf -------------------------------------------------------------------------------- /VFXTricksToolbox_install_guide_en.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/VFXTricksToolbox_install_guide_en.pdf -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_husk.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_husk.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_cacher.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_cacher.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cacher.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cacher.1.0.hdalc -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HuskVFXTricks/HuskVFXTricks.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/DeadlineRepository10/custom/plugins/HuskVFXTricks/HuskVFXTricks.ico -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_switcher.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_switcher.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_usd_cacher.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_usd_cacher.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_ocean_tiler.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_ocean_tiler.1.0.hdalc -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HoudiniVFXTricks/HoudiniVFXTricks.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/DeadlineRepository10/custom/plugins/HoudiniVFXTricks/HoudiniVFXTricks.ico -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.obj_camera_frustum.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.obj_camera_frustum.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_farm_submitter.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.rop_farm_submitter.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_camera_frustum.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_camera_frustum.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cull_by_volume.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cull_by_volume.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cull_geometry.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_cull_geometry.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.lop_cacher_importer.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.lop_cacher_importer.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_ocean_mask_points.1.0.hdalc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFX-Tricks/VFX-Tricks-Toolbox/HEAD/houdini19.0/packages/VFXTricks/otls/vfxtricks.sop_ocean_mask_points.1.0.hdalc -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | { 4 | "VFXTRICKS": "$HOUDINI_PACKAGE_PATH/VFXTricks" 5 | }, 6 | { 7 | "HOUDINI_PATH": "$VFXTRICKS" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HuskVFXTricks/HuskVFXTricks.param: -------------------------------------------------------------------------------- 1 | [About] 2 | Type=label 3 | Label=About 4 | Category=About Plugin 5 | CategoryOrder=-1 6 | Index=0 7 | Default=VFX Tricks Husk Plugin for Deadline, based on Python plugin 8 | Description=Not configurable 9 | 10 | [ConcurrentTasks] 11 | Type=label 12 | Label=ConcurrentTasks 13 | Category=About Plugin 14 | CategoryOrder=-1 15 | Index=0 16 | Default=True 17 | Description=Not configurable 18 | 19 | [Python_Executable] 20 | Type=multilinemultifilename 21 | Label=Python3 Executable 22 | Category=Python Executables 23 | CategoryOrder=0 24 | Index=0 25 | Default=C:\Python36;C:\Users\\AppData\Local\Programs\Python\Python36;/usr/bin/python3 26 | Description=The path to the Python executable. Enter alternative paths on separate lines. 27 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HoudiniVFXTricks/HoudiniVFXTricks.param: -------------------------------------------------------------------------------- 1 | [About] 2 | Type=label 3 | Label=About 4 | Category=About Plugin 5 | CategoryOrder=-1 6 | Index=0 7 | Default=VFX Tricks Houdini Plugin for Deadline 8 | Description=Not configurable 9 | 10 | [ConcurrentTasks] 11 | Type=label 12 | Label=ConcurrentTasks 13 | Category=About Plugin 14 | CategoryOrder=-1 15 | Index=0 16 | Default=True 17 | Description=Not configurable 18 | 19 | [Houdini19_0_Hython_Executable] 20 | Label=Houdini 19.0 Hython Executable 21 | Category=Render Executables 22 | CategoryOrder=0 23 | Type=multilinemultifilename 24 | Index=0 25 | Default=C:\Program Files\Side Effects Software\Houdini 19.0.000\bin\Hython.exe;/Applications/Houdini/Houdini19.0.000/Frameworks/Houdini.framework/Versions/19.0.000/Resources/bin/hython;/opt/hfs19.0/bin/hython 26 | Description=The path to the hython executable. It can be found in the Houdini bin folder. 27 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HuskVFXTricks/HuskVFXTricks.options: -------------------------------------------------------------------------------- 1 | [ScriptFile] 2 | Type=filename 3 | Label=Script File 4 | Category=Python Options 5 | CategoryOrder=0 6 | Index=0 7 | Description=The script file to be executed. 8 | Required=false 9 | DisableIfBlank=true 10 | 11 | [Arguments] 12 | Type=string 13 | Label=Arguments 14 | Category=Python Options 15 | CategoryOrder=0 16 | Index=1 17 | Description=The arguments to pass to the script. If no arguments are required, leave this blank. 18 | Required=false 19 | DisableIfBlank=true 20 | 21 | [Version] 22 | Type=enum 23 | Values=2.3;2.4;2.5;2.6;2.7;3.0;3.1;3.2;3.3;3.4;3.5;3.6;3.7 24 | Label=Version 25 | Category=Python Options 26 | CategoryOrder=0 27 | Index=2 28 | Description=The version of Python to use. 29 | Required=false 30 | DisableIfBlank=true 31 | 32 | [SingleFramesOnly] 33 | Type=boolean 34 | Label=Single Frames Only 35 | Category=Job Options 36 | CategoryOrder=1 37 | Index=0 38 | Description=If enabled, the plugin will only render one frame at a time even if a single task contains a chunk of frames. 39 | Required=true 40 | DisableIfBlank=true 41 | Default=false 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 VFX Tricks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UnrealEngine/SequencerExporter.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import os 3 | import json 4 | import shutil 5 | 6 | ### Setup paths here ### 7 | 8 | sequencer_asset_path = '/Game/Cinematics/Master.Master' 9 | output_folder = 'Y:/VFXTricks/Projects/0_Test/sequencer_export' 10 | 11 | ### End of setup ### 12 | 13 | ### Set export options ### 14 | json_file = os.path.join(output_folder, 'shots_data.json') 15 | 16 | export_options = unreal.FbxExportOption() 17 | export_options.ascii = False 18 | export_options.level_of_detail = False 19 | export_options.collision = False 20 | export_options.vertex_color = False 21 | export_options.fbx_export_compatibility = unreal.FbxExportCompatibility.FBX_2020 22 | 23 | world = unreal.EditorLevelLibrary.get_editor_world() 24 | 25 | master_sequence = unreal.load_asset(sequencer_asset_path, unreal.LevelSequence) 26 | tracks = master_sequence.get_master_tracks() 27 | sections = tracks[0].get_sections() 28 | 29 | shots_data = [] 30 | 31 | # create or clear output folder 32 | if os.path.isdir(output_folder): 33 | shutil.rmtree(output_folder) 34 | os.makedirs(output_folder) 35 | else: 36 | os.makedirs(output_folder) 37 | 38 | # write shot json data 39 | for section in sections: 40 | shot = section.get_shot_display_name() 41 | shot = shot.split('_')[0] 42 | output_file = os.path.join(output_folder, shot + '.fbx') 43 | section_start = section.get_start_frame() 44 | section_end = section.get_end_frame() 45 | section_length = section_end - section_start 46 | sequence = section.get_editor_property('sub_sequence') 47 | bindings = sequence.get_bindings() 48 | unreal.SequencerTools.export_fbx(world, sequence, bindings, export_options, output_file) 49 | start = sequence.get_playback_start() 50 | end = sequence.get_playback_end() 51 | data = { 52 | 'shot': shot, 53 | 'start': start, 54 | 'end': start + section_length, 55 | } 56 | shots_data.append(data) 57 | 58 | with open(json_file, 'w') as file: 59 | json.dump(shots_data, file) 60 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HoudiniVFXTricks/HoudiniVFXTricks.options: -------------------------------------------------------------------------------- 1 | [SceneFile] 2 | Type=filename 3 | Label=Scene Filename 4 | Category=Scene File 5 | CategoryOrder=0 6 | Index=0 7 | Description=The scene filename as it exists on the network. 8 | Required=false 9 | DisableIfBlank=true 10 | 11 | [Version] 12 | Type=label 13 | Label=Version 14 | Category=Houdini Version 15 | CategoryOrder=1 16 | Description=The version of Houdini to use for rendering. 17 | Required=false 18 | DisableIfBlank=true 19 | 20 | [Build] 21 | Type=enum 22 | Values=None;32bit;64bit 23 | Label=Houdini Build To Force 24 | Category=Houdini Version 25 | CategoryOrder=1 26 | Description=The build of Houdini to force. 27 | Required=false 28 | DisableIfBlank=true 29 | 30 | [OutputDriver] 31 | Type=string 32 | Label=Renderer Node 33 | Category=Render Options 34 | CategoryOrder=2 35 | Index=0 36 | Description=Choose which output driver to render. 37 | Required=true 38 | DisableIfBlank=true 39 | Default=mantra1 40 | 41 | [Output] 42 | Type=filenamesave 43 | Label=Output Filename 44 | Category=Render Options 45 | CategoryOrder=2 46 | Index=1 47 | Description=The output filename. 48 | Required=true 49 | DisableIfBlank=true 50 | Default= 51 | 52 | [IFD] 53 | Type=filenamesave 54 | Label=IFD Export Filename 55 | Category=Render Options 56 | CategoryOrder=2 57 | Index=2 58 | Description=The IFD export filename. 59 | Required=true 60 | DisableIfBlank=true 61 | Default= 62 | 63 | [IgnoreInputs] 64 | Type=boolean 65 | Label=Ignore Inputs 66 | Category=Render Options 67 | CategoryOrder=2 68 | Index=3 69 | Description=If enabled, only the specified ROP will be rendered (does not render any of its dependencies). 70 | Required=false 71 | DisableIfBlank=true 72 | 73 | [Width] 74 | Type=integer 75 | Minimum=0 76 | Label=Image Width 77 | Category=Output Resolution 78 | CategoryOrder=3 79 | Index=0 80 | Description=The width of the image in pixels (specify 0 to use the default width). 81 | Required=false 82 | DisableIfBlank=true 83 | 84 | [Height] 85 | Type=integer 86 | Minimum=0 87 | Label=Image Height 88 | Category=Output Resolution 89 | CategoryOrder=3 90 | Index=1 91 | Description=The height of the image in pixels (specify 0 to use the default height). 92 | Required=false 93 | DisableIfBlank=true 94 | 95 | [GPUsPerTask] 96 | Type=integer 97 | Minimum=0 98 | Maximum=16 99 | Label=GPUs Per Task 100 | Category=GPU Options 101 | CategoryOrder=4 102 | Index=0 103 | Description=The number of GPUs to use per task. If set to 0, the default number of GPUs will be used. 104 | Required=true 105 | Default=0 106 | 107 | [SelectGPUDevices] 108 | Type=string 109 | Label=Select GPU Devices 110 | Category=GPU Options 111 | CategoryOrder=4 112 | Index=1 113 | Description=A comma separated list of the GPU devices to use specified by device Id. 'GPUs Per Task' will be ignored. 114 | Required=true 115 | Default= 116 | -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/scripts/run_husk.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import math 3 | import os 4 | import subprocess 5 | 6 | args = sys.argv 7 | print("args: %s" % (args)) 8 | 9 | # handle frame ranges 10 | start_index = args.index('-f') 11 | start_frame = int(args[start_index + 1]) 12 | print("start_frame: %s" % (start_frame)) 13 | 14 | end_index = args.index('-e') 15 | end_frame = int(args[end_index + 1]) 16 | print("end_frame: %s" % (end_frame)) 17 | 18 | inc_index = args.index('-i') 19 | inc = int(args[inc_index + 1]) 20 | print("inc: %s" % (inc)) 21 | 22 | chunk_size_index = args.index('-n') 23 | chunk_size = int(args[chunk_size_index + 1]) 24 | print("chunk_size: %s" % (chunk_size)) 25 | 26 | frames = end_frame - start_frame 27 | print("frames: %s" % (frames)) 28 | 29 | if frames < (inc * (chunk_size - 1)): 30 | new_chunk_size = frames / float(inc) 31 | if new_chunk_size.is_integer(): 32 | new_chunk_size += 1 33 | new_chunk_size = math.floor(new_chunk_size) 34 | new_chunk_size = max(new_chunk_size, 1) 35 | print("new_chunk_size: %s" % (new_chunk_size)) 36 | args[chunk_size_index + 1] = str(new_chunk_size) 37 | elif frames == inc * chunk_size: 38 | new_chunk_size = chunk_size + 1 39 | print("new_chunk_size: %s" % (new_chunk_size)) 40 | args[chunk_size_index + 1] = str(new_chunk_size) 41 | 42 | 43 | # handle log path 44 | log_filepath = None 45 | try: 46 | log_folder_index = args.index('-log') 47 | log_folder = args[log_folder_index + 1] 48 | print("log_folder: %s" % (log_folder)) 49 | 50 | # check if folder exists 51 | if not os.path.isdir(log_folder): 52 | os.makedirs(log_folder) 53 | 54 | log_filepath = os.path.join(log_folder, str(start_frame) + '-' + str(end_frame) + '.txt') 55 | log_filepath = os.path.normpath(log_filepath).replace('\\', '/') 56 | 57 | # remove file if it exists 58 | if os.path.isfile(log_filepath): 59 | os.remove(log_filepath) 60 | 61 | print("log_folder: %s" % (log_filepath)) 62 | args.remove(log_folder) 63 | args.remove('-log') 64 | except Exception as e: 65 | print(e) 66 | 67 | 68 | # remove last two items, the end frame 69 | del args[end_index] 70 | del args[end_index] 71 | 72 | # remove first item, python script 73 | del args[0] 74 | 75 | 76 | command = ' '.join(args) 77 | print("command: %s" % (command)) 78 | 79 | if log_filepath: 80 | print('start logging') 81 | with open(log_filepath, 'a') as f: 82 | with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: 83 | for line in p.stdout: 84 | f.write(line) 85 | print(line) 86 | 87 | for line in p.stderr: 88 | f.write(line) 89 | print(line) 90 | else: 91 | print('no logging') 92 | with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: 93 | for line in p.stdout: 94 | print(line) 95 | 96 | for line in p.stderr: 97 | print(line) 98 | 99 | rc = p.returncode 100 | print('return code: ' + str(rc)) 101 | 102 | #if rc != 0: 103 | # raise ValueError('Husk didnt finish succesfully') 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # VFX Tricks Toolbox - [www.vfxtricks.com](http://www.vfxtricks.com) 5 | 6 | 7 | Contents: 8 | - [Houdini HDAs and Shelftools](https://github.com/VFX-Tricks/VFX-Tricks-Toolbox#1-houdini-hdas-and-shelftools) 9 | - [Deadline plugins](https://github.com/VFX-Tricks/VFX-Tricks-Toolbox#2-deadline-plugins) 10 | - [Unreal Engine Sequencer Exporter](https://github.com/VFX-Tricks/VFX-Tricks-Toolbox#3-unreal-engine-sequencer-exporter) 11 | 12 | See [Installation guide](VFXTricksToolbox_install_guide_en.pdf) 13 | 14 | Submit bugs and feature requests via [Issues](https://github.com/VFX-Tricks/VFX-Tricks-Toolbox/issues) 15 | 16 | Feedback is welcome via [Discussions](https://github.com/VFX-Tricks/VFX-Tricks-Toolbox/discussions) 17 | 18 | # 1. Houdini HDAs and Shelftools 19 | https://www.sidefx.com/ 20 | 21 | The main goal of Houdini tools is to accelerate content creation at VFX Tricks. Creating setups and tutorials is time consuming. Mundane tasks like caching, publishing and farm submission shouldn't be a slowing down factor. Writing and reading caches is at the heart of every VFX pipeline 🎥, it needs to be quick, easy and reliable. After seeing many implementations at various studios, I decided to make my own that will suit VFX Tricks creators needs. This package will include caching and setup/shot specific tools. 22 | 23 | ⚠️ HDAs are in Indie .hdalc format 24 | 25 | 26 | ## Cacher SOP 27 | 28 | 29 | - .usd, .bgeo.sc and .vdb 30 | - USD geometry purpose (render, proxy and guide) 31 | - version control and stats 📝 32 | - sections (wedging) 🍕 33 | 34 | ## Lop Cacher Import 35 | 36 | 37 | - dynamically import .usd Cachers to LOP network 38 | - well suited for Multishot workflow 39 | 40 | ## Multishot workflow 41 | 42 | 43 | At VFX Tricks we usually provide single hip file with multiple shots inside. For single artist working on mulitple shots, I find this way better than conventional approach at VFX studios(each shot/element in separate hip file). 44 | 45 | ## Shelftools 46 | 47 | 48 | - create OBJ contexts for each shot 49 | - import shots from UE Sequencer 50 | - set frame range per shot 51 | - toggle cooking mode - I recommend assigning a hotkey -> must have! 52 | - Cacher SOP and Farm Submiter ROP take advantage of this workflow 53 | 54 | # 2. Deadline plugins 55 | https://www.awsthinkbox.com/deadline
56 | https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/app-index.html 57 | 58 | VFX Tricks Toolbox extends existing Houdini plugins shipped with Deadline. 59 | 60 | - USD caching - SOP and Stage 61 | - USD rendering via command line utility - [husk](https://www.sidefx.com/docs/houdini/ref/utils/husk.html) 62 | - sections(wedges) submits hip file only once. Current section(wedge) is defined by env variable, resulting faster submission to farm ⏱ 63 | 64 | 65 | 66 | # 3. Unreal Engine Sequencer Exporter 67 | https://docs.unrealengine.com/5.0/en-US/unreal-engine-sequencer-movie-tool-overview/ 68 | 69 | A quick way of exporting animated assets (static mesh, camera, etc.) from Sequencer to FBX with supporting JSON data file.
70 | Then import to Houdini each shot as a Multishot context with corresponding assets and frame ranges. 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HuskVFXTricks/HuskVFXTricks.py: -------------------------------------------------------------------------------- 1 | from System.IO import * 2 | from System.Text.RegularExpressions import * 3 | 4 | from Deadline.Plugins import * 5 | from Deadline.Scripting import * 6 | 7 | import os 8 | import sys 9 | import re 10 | 11 | def GetDeadlinePlugin(): 12 | return PythonPlugin() 13 | 14 | def CleanupDeadlinePlugin( deadlinePlugin ): 15 | deadlinePlugin.Cleanup() 16 | 17 | class PythonPlugin (DeadlinePlugin): 18 | 19 | def __init__( self ): 20 | self.InitializeProcessCallback += self.InitializeProcess 21 | self.RenderExecutableCallback += self.RenderExecutable 22 | self.RenderArgumentCallback += self.RenderArgument 23 | 24 | def Cleanup(self): 25 | for stdoutHandler in self.StdoutHandlers: 26 | del stdoutHandler.HandleCallback 27 | 28 | del self.InitializeProcessCallback 29 | del self.RenderExecutableCallback 30 | del self.RenderArgumentCallback 31 | 32 | def InitializeProcess(self): 33 | self.PluginType = PluginType.Simple 34 | self.StdoutHandling = True 35 | 36 | self.SingleFramesOnly = self.GetBooleanPluginInfoEntryWithDefault( "SingleFramesOnly", False ) 37 | self.LogInfo( "Single Frames Only: %s" % self.SingleFramesOnly ) 38 | 39 | self.AddStdoutHandlerCallback( ".*Progress: (\d+)%.*" ).HandleCallback += self.HandleProgress 40 | self.AddStdoutHandlerCallback( ".*ALF_PROGRESS ([0-9]+)%.*" ).HandleCallback += self.HandleStdoutFrameProgress 41 | 42 | # pythonPath = self.GetEnvironmentVariable( "PYTHONPATH" ).strip() 43 | # addingPaths = self.GetConfigEntryWithDefault( "PythonSearchPaths", "" ).strip() 44 | 45 | # if addingPaths != "": 46 | # addingPaths.replace( ';', os.pathsep ) 47 | 48 | # if pythonPath != "": 49 | # pythonPath = pythonPath + os.pathsep + addingPaths 50 | # else: 51 | # pythonPath = addingPaths 52 | 53 | # self.LogInfo( "Setting PYTHONPATH to: " + pythonPath ) 54 | # self.SetEnvironmentVariable( "PYTHONPATH", pythonPath ) 55 | 56 | def RenderExecutable( self ): 57 | #version = self.GetPluginInfoEntry( "Version" ) 58 | 59 | exeList = self.GetConfigEntry( "Python_Executable" ) 60 | exe = FileUtils.SearchFileList( exeList ) 61 | if exe == "": 62 | self.FailRender( "Python executable was not found in the semicolon separated list \"" + exeList + "\". The path to the render executable can be configured from the Plugin Configuration in the Deadline Monitor." ) 63 | return exe 64 | 65 | def RenderArgument( self ): 66 | scriptFile = self.GetPluginInfoEntryWithDefault( "ScriptFile", self.GetDataFilename() ) 67 | scriptFile = RepositoryUtils.CheckPathMapping( scriptFile ) 68 | 69 | arguments = self.GetPluginInfoEntryWithDefault( "Arguments", "" ) 70 | arguments = RepositoryUtils.CheckPathMapping( arguments ) 71 | 72 | arguments = re.sub( r"<(?i)STARTFRAME>", str( self.GetStartFrame() ), arguments ) 73 | arguments = re.sub( r"<(?i)ENDFRAME>", str( self.GetEndFrame() ), arguments ) 74 | arguments = re.sub( r"<(?i)QUOTE>", "\"", arguments) 75 | 76 | arguments = self.ReplacePaddedFrame( arguments, "<(?i)STARTFRAME%([0-9]+)>", self.GetStartFrame() ) 77 | arguments = self.ReplacePaddedFrame( arguments, "<(?i)ENDFRAME%([0-9]+)>", self.GetEndFrame() ) 78 | 79 | count = 0 80 | for filename in self.GetAuxiliaryFilenames(): 81 | localAuxFile = Path.Combine( self.GetJobsDataDirectory(), filename ) 82 | arguments = re.sub( r"<(?i)AUXFILE" + str( count ) + r">", localAuxFile.replace( "\\", "/" ), arguments ) 83 | count += 1 84 | 85 | if SystemUtils.IsRunningOnWindows(): 86 | scriptFile = scriptFile.replace( "/", "\\" ) 87 | else: 88 | scriptFile = scriptFile.replace( "\\", "/" ) 89 | 90 | return "-u \"" + scriptFile + "\" " + arguments 91 | 92 | def ReplacePaddedFrame( self, arguments, pattern, frame ): 93 | frameRegex = Regex( pattern ) 94 | while True: 95 | frameMatch = frameRegex.Match( arguments ) 96 | if frameMatch.Success: 97 | paddingSize = int( frameMatch.Groups[ 1 ].Value ) 98 | if paddingSize > 0: 99 | padding = StringUtils.ToZeroPaddedString( frame, paddingSize, False ) 100 | else: 101 | padding = str(frame) 102 | arguments = arguments.replace( frameMatch.Groups[ 0 ].Value, padding ) 103 | else: 104 | break 105 | 106 | return arguments 107 | 108 | def HandleProgress( self ): 109 | progress = float( self.GetRegexMatch(1) ) 110 | self.SetProgress( progress ) 111 | 112 | def HandleStdoutFrameProgress(self): 113 | overallProgress = float(self.GetRegexMatch(1)) 114 | self.SetProgress(overallProgress) 115 | self.SetStatusMessage( "Progress: " + str(overallProgress) + " %" ) -------------------------------------------------------------------------------- /houdini19.0/packages/VFXTricks/toolbar/VFXTricks.shelf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 44 | 144 | 145 | 146 | 147 | 156 | 162 | 163 | 164 | 165 | 173 | 193 | 194 | 195 | 196 | 204 | 231 | 232 | 233 | 234 | 254 | 394 | 395 | 396 | -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HoudiniVFXTricks/HoudiniVFXTricks.py: -------------------------------------------------------------------------------- 1 | from System import * 2 | from System.Diagnostics import * 3 | from System.IO import * 4 | 5 | from Deadline.Plugins import * 6 | from Deadline.Scripting import * 7 | 8 | import io 9 | import os 10 | import re 11 | import socket 12 | import traceback 13 | 14 | def GetDeadlinePlugin(): 15 | return HoudiniPlugin() 16 | 17 | def CleanupDeadlinePlugin( deadlinePlugin ): 18 | deadlinePlugin.Cleanup() 19 | 20 | class HoudiniPlugin (DeadlinePlugin): 21 | completedFrames = 0 22 | ropType = "" 23 | 24 | def __init__( self ): 25 | self.InitializeProcessCallback += self.InitializeProcess 26 | self.RenderExecutableCallback += self.RenderExecutable 27 | self.RenderArgumentCallback += self.RenderArgument 28 | self.PreRenderTasksCallback += self.PreRenderTasks 29 | self.PostRenderTasksCallback += self.PostRenderTasks 30 | 31 | def Cleanup(self): 32 | for stdoutHandler in self.StdoutHandlers: 33 | del stdoutHandler.HandleCallback 34 | 35 | del self.InitializeProcessCallback 36 | del self.RenderExecutableCallback 37 | del self.RenderArgumentCallback 38 | del self.PreRenderTasksCallback 39 | del self.PostRenderTasksCallback 40 | 41 | def InitializeProcess( self ): 42 | self.SingleFramesOnly = False 43 | self.StdoutHandling = True 44 | self.PopupHandling = True 45 | 46 | self.AddStdoutHandlerCallback( "(Couldn't find renderer.*)" ).HandleCallback += self.HandleStdoutRenderer 47 | self.AddStdoutHandlerCallback( "(Error: Unknown option:.*)" ).HandleCallback += self.HandleStdoutUnknown 48 | self.AddStdoutHandlerCallback( "(Error: .*)" ).HandleCallback += self.HandleStdoutError 49 | self.AddStdoutHandlerCallback(r"(ERROR\s*\|.*)").HandleCallback += self.HandleStdoutError #Arnold errors 50 | self.AddStdoutHandlerCallback(r"\[Error\].*").HandleCallback += self.HandleStdoutError 51 | self.AddStdoutHandlerCallback( ".*(Redshift cannot operate with less than 128MB of free VRAM).*" ).HandleCallback += self.HandleStdoutError 52 | self.AddStdoutHandlerCallback( ".*(No licenses could be found to run this application).*" ).HandleCallback += self.HandleStdoutLicense 53 | self.AddStdoutHandlerCallback( ".*ALF_PROGRESS ([0-9]+)%.*" ).HandleCallback += self.HandleStdoutFrameProgress 54 | self.AddStdoutHandlerCallback( ".*Render Time:.*" ).HandleCallback += self.HandleStdoutFrameComplete 55 | self.AddStdoutHandlerCallback( ".*Finished Rendering.*" ).HandleCallback += self.HandleStdoutDoneRender 56 | self.AddStdoutHandlerCallback( ".*ROP type: (.*)" ).HandleCallback += self.SetRopType 57 | self.AddStdoutHandlerCallback( ".*?(\d+)% done.*" ).HandleCallback += self.HandleStdoutFrameProgress 58 | self.AddStdoutHandlerCallback( "\[render progress\] ---[ ]+(\d+) percent" ).HandleCallback += self.HandleStdoutFrameProgress 59 | self.AddStdoutHandlerCallback( "(License error: No license found)").HandleCallback += self.HandleStdoutLicense 60 | self.AddStdoutHandlerCallback( "RMAN_PROGRESS *([0-9]+)%" ).HandleCallback += self.HandleStdoutFrameProgress 61 | 62 | self.AddPopupHandler( ".*Streaming SIMD Extensions Not Enabled.*", "OK" ) 63 | 64 | def RenderExecutable( self ): 65 | version = self.GetPluginInfoEntryWithDefault( "Version", "16.0" ).replace( ".", "_" ) 66 | build = self.GetPluginInfoEntryWithDefault( "Build", "none" ).lower() 67 | houdiniExeList = self.GetConfigEntry( "Houdini" + version + "_Hython_Executable" ) 68 | 69 | if SystemUtils.IsRunningOnLinux(): 70 | houdiniExeList = houdiniExeList.replace( "hython", "hython-bin" ) 71 | 72 | houdiniExe = "" 73 | if SystemUtils.IsRunningOnWindows(): 74 | if build == "32bit": 75 | self.LogInfo( "Enforcing 32 bit build" ) 76 | houdiniExe = FileUtils.SearchFileListFor32Bit( houdiniExeList ) 77 | if houdiniExe == "": 78 | self.LogWarning( "Houdini " + version + " hython 32 bit executable was not found in the semicolon separated list \"" + houdiniExeList + "\". Checking for any executable that exists instead." ) 79 | elif build == "64bit": 80 | self.LogInfo( "Enforcing 64 bit build" ) 81 | houdiniExe = FileUtils.SearchFileListFor64Bit( houdiniExeList ) 82 | if houdiniExe == "": 83 | self.LogWarning( "Houdini " + version + " hython 64 bit executable was not found in the semicolon separated list \"" + houdiniExeList + "\". Checking for any executable that exists instead." ) 84 | 85 | if houdiniExe == "": 86 | houdiniExe = FileUtils.SearchFileList( houdiniExeList ) 87 | if houdiniExe == "": 88 | self.FailRender( "Houdini " + version + " hython executable was not found in the semicolon separated list \"" + houdiniExeList + "\". The path to the render executable can be configured from the Plugin Configuration in the Deadline Monitor." ) 89 | 90 | if SystemUtils.IsRunningOnLinux(): 91 | houdiniExe = houdiniExe.replace( "hython-bin", "hython" ) 92 | 93 | return houdiniExe 94 | 95 | def RenderArgument(self): 96 | ifdFilename = self.GetPluginInfoEntryWithDefault( "IFD", "" ) 97 | ifdFilename = RepositoryUtils.CheckPathMapping( ifdFilename ) 98 | 99 | outputFilename = self.GetPluginInfoEntryWithDefault( "Output", "" ) 100 | outputFilename = RepositoryUtils.CheckPathMapping( outputFilename ) 101 | 102 | scene = self.GetPluginInfoEntryWithDefault( "SceneFile", self.GetDataFilename() ) 103 | scene = RepositoryUtils.CheckPathMapping( scene ) 104 | 105 | regionRendering = self.GetBooleanPluginInfoEntryWithDefault( "RegionRendering", False ) 106 | singleRegionJob = self.IsTileJob() 107 | singleRegionFrame = str(self.GetStartFrame()) 108 | singleRegionIndex = self.GetCurrentTaskId() 109 | 110 | simJob = self.GetBooleanPluginInfoEntryWithDefault( "SimJob", False ) 111 | 112 | wedgeNum = -1 113 | try: 114 | wedgeNum = self.GetIntegerPluginInfoEntryWithDefault("WedgeNum", "-1") 115 | except: 116 | pass 117 | 118 | scene = scene.replace("\\","/") 119 | outputFilename = outputFilename.replace("\\", "/") 120 | ifdFilename = ifdFilename.replace("\\", "/") 121 | 122 | if SystemUtils.IsRunningOnWindows(): 123 | if scene.startswith( "/" ) and scene[0:2] != "//": 124 | scene = "/" + scene 125 | if outputFilename.startswith( "/" ) and outputFilename[0:2] != "//": 126 | outputFilename = "/" + outputFilename 127 | if ifdFilename.startswith( "/" ) and ifdFilename[0:2] != "//": 128 | ifdFilename = "/" + ifdFilename 129 | else: 130 | if scene.startswith( "/" ) and scene[0:2] == "//": 131 | scene = scene[1:len(scene)] 132 | if outputFilename.startswith( "/" ) and outputFilename[0:2] == "//": 133 | outputFilename = outputFilename[1:len(outputFilename)] 134 | if ifdFilename.startswith( "/" ) and ifdFilename[0:2] == "//": 135 | ifdFilename = ifdFilename[1:len(ifdFilename)] 136 | 137 | # Construct the command line options, to be used by hrender_dl.py and return them. 138 | hrender = Path.Combine( self.GetPluginDirectory(),"hrender_dlVFXTricks.py" ) 139 | arguments = [ "\"%s\"" % hrender ] 140 | 141 | if simJob: 142 | machineNameOrIpAddress = "" 143 | trackerPort = self.GetIntegerConfigEntry( "Houdini_SimTracker_Tracker_Port" ) 144 | if self.GetBooleanPluginInfoEntryWithDefault( "SimRequiresTracking", True ): 145 | self.LogInfo( "Sim Job: Checking which machine is running the tracking process" ) 146 | 147 | # Need to figure out which Worker is rendering the first task for this job. 148 | currentJob = self.GetJob() 149 | tasks = RepositoryUtils.GetJobTasks( currentJob, True ) 150 | 151 | if tasks.GetTask(0).TaskStatus != "Rendering": 152 | self.FailRender( "Sim Job: Cannot determine which machine is running the tracking process because the first task for this job is not in the rendering state" ) 153 | 154 | trackerMachineSlave = tasks.GetTask(0).TaskSlaveName 155 | if trackerMachineSlave == "": 156 | self.FailRender( "Sim Job: Cannot determine which machine is running the tracking process because the first task for this job is not being rendered by another Worker" ) 157 | 158 | slaveInfo = RepositoryUtils.GetSlaveInfo( trackerMachineSlave, True ) 159 | self.LogInfo( "Sim Job: Worker \"" + slaveInfo.SlaveName + "\" is running the tracking proccess" ) 160 | 161 | 162 | if not self.GetConfigEntry( "Houdini_SimTracker_Use_IP_Address" ): 163 | machineNameOrIpAddress = SlaveUtils.GetMachineNames([slaveInfo])[0] 164 | self.LogInfo( "Sim Job: Connecting to Worker machine using host name \"" + machineNameOrIpAddress + "\"" ) 165 | else: 166 | machineNameOrIpAddress = SlaveUtils.GetMachineIPAddresses([slaveInfo])[0] 167 | self.LogInfo( "Sim Job: Connecting to Worker machine using IP address \"" + machineNameOrIpAddress + "\"" ) 168 | 169 | arguments.append( "-s %s \"%s\" %s" % (singleRegionFrame, machineNameOrIpAddress, trackerPort) ) 170 | elif regionRendering and singleRegionJob: 171 | arguments.append( "-f %s %s 1" % (singleRegionFrame, singleRegionFrame) ) 172 | else: 173 | arguments.append( "-f %s %s 1" % (self.GetStartFrame(), self.GetEndFrame()) ) 174 | 175 | width = self.GetIntegerPluginInfoEntryWithDefault( "Width", 0 ) 176 | height = self.GetIntegerPluginInfoEntryWithDefault( "Height", 0 ) 177 | if( width > 0 and height > 0 ): 178 | arguments.append( "-r %s %s" % (width, height) ) 179 | 180 | if( len(outputFilename) > 0 ): 181 | arguments.append( "-o \"%s\"" % outputFilename ) 182 | if( len(ifdFilename) > 0 ): 183 | arguments.append( "-i \"%s\"" % ifdFilename ) 184 | 185 | if self.GetBooleanPluginInfoEntryWithDefault( "IgnoreInputs", False ): 186 | arguments.append( "-g" ) 187 | 188 | arguments.append( "-d %s" % self.GetPluginInfoEntry( "OutputDriver" ) ) 189 | if regionRendering: 190 | xStart = 0 191 | xEnd = 0 192 | yStart = 0 193 | yEnd = 0 194 | currTile = 0 195 | if singleRegionJob: 196 | currTile = singleRegionIndex 197 | xStart = self.GetFloatPluginInfoEntryWithDefault("RegionLeft"+str(singleRegionIndex),0) 198 | xEnd = self.GetFloatPluginInfoEntryWithDefault("RegionRight"+str(singleRegionIndex),0) 199 | yStart = self.GetFloatPluginInfoEntryWithDefault("RegionBottom"+str(singleRegionIndex),0) 200 | yEnd = self.GetFloatPluginInfoEntryWithDefault("RegionTop"+str(singleRegionIndex),0) 201 | else: 202 | currTile = self.GetIntegerPluginInfoEntryWithDefault( "CurrentTile", 1 ) 203 | xStart = self.GetFloatPluginInfoEntryWithDefault( "RegionLeft", 0 ) 204 | xEnd = self.GetFloatPluginInfoEntryWithDefault( "RegionRight", 0 ) 205 | yStart = self.GetFloatPluginInfoEntryWithDefault( "RegionBottom", 0 ) 206 | yEnd = self.GetFloatPluginInfoEntryWithDefault( "RegionTop", 0 ) 207 | 208 | arguments.append( "-t %s %s %s %s %s" % ( currTile, xStart, xEnd, yStart, yEnd ) ) 209 | 210 | if wedgeNum > -1: 211 | arguments.append( "-wedgenum %s" % wedgeNum ) 212 | 213 | gpuList = self.GetGpuOverrides() 214 | if len( gpuList ) > 0: 215 | 216 | gpus = ",".join( gpuList ) 217 | arguments.append( "-gpu %s" % gpus ) 218 | 219 | if self.GetBooleanPluginInfoEntryWithDefault( "OpenCLUseGPU", 1 ): 220 | self.SetEnvironmentVariable( "HOUDINI_OCL_DEVICETYPE", "GPU" ) 221 | self.SetEnvironmentVariable( "HOUDINI_OCL_VENDOR", "" ) 222 | self.SetEnvironmentVariable( "HOUDINI_OCL_DEVICENUMBER", gpuList[ self.GetThreadNumber() % len( gpuList ) ] ) 223 | 224 | tempDir = self.CreateTempDirectory(str( self.GetThreadNumber())) 225 | arguments.append( "-tempdir \"%s\"" % tempDir ) 226 | 227 | abortOnLicenseFail = 1 if self.GetBooleanConfigEntryWithDefault( "AbortOnArnoldLicenseFail", True ) else 0 228 | arguments.append( "-arnoldAbortOnLicenseFail %s" % abortOnLicenseFail ) 229 | 230 | # nde 231 | try: 232 | node = self.GetPluginInfoEntryWithDefault("Node", None) 233 | if node: 234 | arguments.append( "-node %s" % (node) ) 235 | except: 236 | pass 237 | 238 | # add JobType Plugin info, for some reason "jobType" arg doesnt work... 239 | try: 240 | type = self.GetPluginInfoEntryWithDefault("Type", None) 241 | if type: 242 | arguments.append( "-type %s" % (type) ) 243 | except: 244 | pass 245 | 246 | # add Section Plugin info 247 | try: 248 | section = self.GetPluginInfoEntryWithDefault("Section", None) 249 | if section: 250 | arguments.append( "-section %s" % (section) ) 251 | except: 252 | pass 253 | 254 | 255 | arguments.append( "\"%s\"" % (scene) ) 256 | 257 | return " ".join( arguments ) 258 | 259 | def GetGpuOverrides( self ): 260 | resultGPUs = [] 261 | 262 | # If the number of gpus per task is set, then need to calculate the gpus to use. 263 | gpusPerTask = self.GetIntegerPluginInfoEntryWithDefault( "GPUsPerTask", 0 ) 264 | gpusSelectDevices = self.GetPluginInfoEntryWithDefault( "GPUsSelectDevices", "" ) 265 | 266 | if self.OverrideGpuAffinity(): 267 | overrideGPUs = self.GpuAffinity() 268 | if gpusPerTask == 0 and gpusSelectDevices != "": 269 | gpus = gpusSelectDevices.split( "," ) 270 | notFoundGPUs = [] 271 | for gpu in gpus: 272 | if int( gpu ) in overrideGPUs: 273 | resultGPUs.append( gpu ) 274 | else: 275 | notFoundGPUs.append( gpu ) 276 | 277 | if len( notFoundGPUs ) > 0: 278 | self.LogWarning( "The Worker is overriding its GPU affinity and the following GPUs do not match the Workers affinity so they will not be used: " + ",".join( notFoundGPUs ) ) 279 | if len( resultGPUs ) == 0: 280 | self.FailRender( "The Worker does not have affinity for any of the GPUs specified in the job." ) 281 | elif gpusPerTask > 0: 282 | if gpusPerTask > len( overrideGPUs ): 283 | self.LogWarning( "The Worker is overriding its GPU affinity and the Worker only has affinity for " + str( len( overrideGPUs ) ) + " gpus of the " + str( gpusPerTask ) + " requested." ) 284 | resultGPUs = [ str( gpu ) for gpu in overrideGPUs ] 285 | else: 286 | resultGPUs = [ str( gpu ) for gpu in overrideGPUs if gpu < gpusPerTask ] 287 | else: 288 | resultGPUs = [ str( gpu ) for gpu in overrideGPUs ] 289 | elif gpusPerTask == 0 and gpusSelectDevices != "": 290 | resultGPUs = gpusSelectDevices.split( "," ) 291 | 292 | elif gpusPerTask > 0: 293 | gpuList = [] 294 | for i in range( ( self.GetThreadNumber() * gpusPerTask ), ( self.GetThreadNumber() * gpusPerTask ) + gpusPerTask ): 295 | gpuList.append( str( i ) ) 296 | resultGPUs = gpuList 297 | 298 | resultGPUs = list( resultGPUs ) 299 | 300 | return resultGPUs 301 | 302 | def PreRenderTasks(self): 303 | # set 'IS_HOUDINI_RENDERING' to be 'TRUE' so that PreFirstCreate of houdini submission script don't sync file from deadline repository. 304 | self.SetEnvironmentVariable( 'IS_HOUDINI_RENDERING', "TRUE" ) 305 | # Use Escape License if requested 306 | slave = self.GetSlaveName().lower() 307 | ELicSlaves = self.GetConfigEntryWithDefault( "ELicSlaves", "" ).lower().split( ',' ) 308 | if slave in ELicSlaves: 309 | self.LogInfo( "This Worker will use a Houdini Escape license to render" ) 310 | self.SetEnvironmentVariable("HOUDINI_SCRIPT_LICENSE", "hescape") 311 | 312 | if self.GetBooleanConfigEntryWithDefault( "EnablePathMapping", True ): 313 | mappings = RepositoryUtils.GetPathMappings( ) 314 | #Remove Mappings with no to path. 315 | mappings = [ mappingPair for mappingPair in mappings if mappingPair[1] ] 316 | if len(mappings) >0: 317 | houdiniPathmap = "" 318 | if Environment.GetEnvironmentVariable( "HOUDINI_PATHMAP" ) != None: 319 | houdiniPathmap = Environment.GetEnvironmentVariable( "HOUDINI_PATHMAP" ) 320 | 321 | if houdiniPathmap.endswith( "}" ): 322 | houdiniPathmap = houdiniPathmap[:-1] 323 | 324 | for map in mappings: 325 | if houdiniPathmap != "": 326 | houdiniPathmap += ", " 327 | 328 | originalPath = map[0].replace("\\","/") 329 | newPath = map[1].replace("\\","/") 330 | if originalPath != "" and newPath != "": 331 | if SystemUtils.IsRunningOnWindows(): 332 | if newPath.startswith( "/" ) and newPath[0:2] != "//": 333 | newPath = "/" + newPath 334 | else: 335 | if newPath.startswith( "/" ) and newPath[0:2] == "//": 336 | newPath = newPath[1:len(newPath)] 337 | 338 | houdiniPathmap += "\"" + originalPath + "\":\"" + newPath + "\"" 339 | 340 | if not houdiniPathmap.startswith("{"): 341 | houdiniPathmap = "{"+houdiniPathmap 342 | if not houdiniPathmap.endswith("}"): 343 | houdiniPathmap = houdiniPathmap+"}" 344 | 345 | self.LogInfo("Set HOUDINI_PATHMAP to " + houdiniPathmap ) 346 | self.SetEnvironmentVariable( 'HOUDINI_PATHMAP', houdiniPathmap ) 347 | 348 | self.SetRedshiftPathmappingEnv( mappings ) 349 | 350 | if self.GetBooleanPluginInfoEntryWithDefault( "SimJob", False ) and self.GetBooleanPluginInfoEntryWithDefault( "SimRequiresTracking", True ) and self.GetCurrentTaskId() == "0": 351 | self.LogInfo( "Sim Job: Starting Sim Tracker process because this is the first task for this sim job" ) 352 | 353 | if SystemUtils.IsRunningOnWindows(): 354 | pythonExe = "dpython.exe" 355 | else: 356 | pythonExe = "dpython" 357 | 358 | pythonPath = Path.Combine( ClientUtils.GetBinDirectory(), pythonExe ) 359 | 360 | version = self.GetPluginInfoEntryWithDefault( "Version", "16.0" ).replace( ".", "_" ) 361 | simTrackerList = self.GetConfigEntry( "Houdini" + version + "_SimTracker" ) 362 | simTracker = FileUtils.SearchFileList( simTrackerList ) 363 | if simTracker == "": 364 | self.FailRender( "Sim Job: Houdini " + version + " sim tracker file was not found in the semicolon separated list \"" + simTrackerList + "\". The path to the sim tracker file can be configured from the Plugin Configuration in the Deadline Monitor." ) 365 | 366 | trackerPort = self.GetIntegerConfigEntry( "Houdini_SimTracker_Tracker_Port" ) 367 | webServicePort = self.GetIntegerConfigEntry( "Houdini_SimTracker_Web_Service_Port" ) 368 | 369 | # Check if either of the ports are already in use. 370 | trackerPortInUse = False 371 | webServicePortInUse = False 372 | 373 | s = None 374 | try: 375 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 376 | s.bind(('', trackerPort)) 377 | s.close() 378 | s = None 379 | except: 380 | s.close() 381 | s = None 382 | self.LogWarning( traceback.format_exc() ) 383 | trackerPortInUse = True 384 | 385 | try: 386 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 387 | s.bind(('', webServicePort)) 388 | s.close() 389 | s = None 390 | except: 391 | s.close() 392 | s = None 393 | self.LogWarning( traceback.format_exc() ) 394 | webServicePortInUse = True 395 | 396 | if trackerPortInUse and webServicePortInUse: 397 | self.FailRender("Unable to start the Sim Tracker process because tracker port {0} and web service port {1} are in use.".format(trackerPort,webServicePort)) 398 | elif trackerPortInUse: 399 | self.FailRender("Unable to start the Sim Tracker process because tracker port {0} is in use.".format(trackerPort)) 400 | elif webServicePortInUse: 401 | self.FailRender("Unable to start the Sim Tracker process because web service port {0} is in use.".format(webServicePort)) 402 | 403 | arguments = "\"" + simTracker + "\" " + str(trackerPort) + " " + str(webServicePort) 404 | self.LogInfo("Sim Job: Starting the Sim Tracker process") 405 | self.StartMonitoredProgram( "SimTracker", pythonPath, arguments, Path.GetDirectoryName( simTracker ) ) # string name, string executable, string arguments, string startupDirectory 406 | 407 | self.LogInfo("Starting Houdini Job") 408 | self.SetProgress(0) 409 | 410 | def PostRenderTasks(self): 411 | if self.GetBooleanPluginInfoEntryWithDefault( "SimJob", False ) and self.GetBooleanPluginInfoEntryWithDefault( "SimRequiresTracking", True ) and self.GetCurrentTaskId() == "0": 412 | self.LogInfo("Sim Job: Waiting for all other tasks for this job to complete before stopping theSim Tracker process") 413 | 414 | incompleteTasks = [] 415 | 416 | jobComplete = False 417 | while not jobComplete: 418 | if self.IsCanceled(): 419 | self.FailRender( "Task canceled" ) 420 | 421 | SystemUtils.Sleep( 5000 ) 422 | 423 | currentJob = self.GetJob() 424 | tasks = RepositoryUtils.GetJobTasks( currentJob, True ) 425 | 426 | jobComplete = True 427 | for task in tasks: 428 | if task.TaskID > 0: 429 | if task.TaskStatus != "Completed": 430 | taskIdStr = str(task.TaskID) 431 | 432 | # Don't want to log more than once for any incomplete task. 433 | if taskIdStr not in incompleteTasks: 434 | incompleteTasks.append( taskIdStr ) 435 | self.LogInfo("Sim Job, still waiting for task: "+str(task.TaskID)) 436 | 437 | jobComplete = False 438 | break 439 | 440 | self.LogInfo("Sim Job: Stopping the Sim Tracker process") 441 | self.ShutdownMonitoredProgram( "SimTracker" ) 442 | 443 | self.LogInfo("Finished Houdini Job") 444 | 445 | def HandleStdoutRenderer(self): 446 | self.FailRender(self.GetRegexMatch(1)) 447 | 448 | def HandleStdoutError(self): 449 | self.FailRender(self.GetRegexMatch(1)) 450 | 451 | def HandleStdoutLicense(self): 452 | self.FailRender(self.GetRegexMatch(1)) 453 | 454 | def HandleStdoutUnknown(self): 455 | self.FailRender( "Bad command line: " + self.RenderArgument() + "\nHoudini Error: " + self.GetRegexMatch(1) ) 456 | 457 | def HandleStdoutFrameProgress(self): 458 | if self.ropType in ("ifd", "rop_ifd"): 459 | frameCount = self.GetEndFrame() - self.GetStartFrame() + 1 460 | if frameCount != 0: 461 | completedFrameProgress = float(self.completedFrames) * 100.0 462 | currentFrameProgress = float(self.GetRegexMatch(1)) 463 | overallProgress = (completedFrameProgress + currentFrameProgress) / float(frameCount) 464 | self.SetProgress(overallProgress) 465 | self.SetStatusMessage( "Progress: " + str(overallProgress) + " %" ) 466 | 467 | elif self.ropType in ("arnold", "geometry", "rop_geometry", "wedge", "Octane_ROP", "ris::22") or "VFXTricks::cacher" in self.ropType: 468 | overallProgress = float(self.GetRegexMatch(1)) 469 | self.SetProgress(overallProgress) 470 | self.SetStatusMessage( "Progress: " + str(overallProgress) + " %" ) 471 | 472 | def HandleStdoutFrameComplete(self): 473 | if self.ropType in ("ifd", "rop_ifd"): 474 | self.completedFrames = self.completedFrames + 1 475 | 476 | def HandleStdoutDoneRender(self): 477 | self.SetStatusMessage("Finished Render") 478 | self.SetProgress(100) 479 | 480 | def SetRopType(self): 481 | self.ropType = self.GetRegexMatch(1) 482 | 483 | def SetRedshiftPathmappingEnv( self, mappings ): 484 | try: 485 | if not mappings: 486 | return 487 | 488 | self.LogInfo( "Redshift Path Mapping..." ) 489 | 490 | # "C:\MyTextures\" "\\MYSERVER01\Textures\" ... 491 | redshiftMappingRE = re.compile( r"\"([^\"]*)\"\s+\"([^\"]*)\"" ) 492 | 493 | oldRSMappingFileName = Environment.GetEnvironmentVariable( "REDSHIFT_PATHOVERRIDE_FILE" ) 494 | if oldRSMappingFileName: 495 | self.LogInfo( '[REDSHIFT_PATHOVERRIDE_FILE]="%s"' % oldRSMappingFileName ) 496 | with io.open( oldRSMappingFileName, mode="r", encoding="utf-8" ) as oldRSMappingFile: 497 | for line in oldRSMappingFile: 498 | mappings.extend( redshiftMappingRE.findall( line ) ) 499 | 500 | oldRSMappingString = Environment.GetEnvironmentVariable( "REDSHIFT_PATHOVERRIDE_STRING" ) 501 | if oldRSMappingString: 502 | self.LogInfo( '[REDSHIFT_PATHOVERRIDE_STRING]="%s"' % oldRSMappingString ) 503 | mappings.extend( redshiftMappingRE.findall( oldRSMappingString ) ) 504 | 505 | newRSMappingFileName = os.path.join( self.CreateTempDirectory("RSMapping"), "RSMapping.txt" ) 506 | with io.open( newRSMappingFileName, mode="w", encoding="utf-8" ) as newRSMappingFile: 507 | for mappingPair in mappings: 508 | self.LogInfo( u'source: "%s" dest: "%s"' % (mappingPair[0], mappingPair[1] )) 509 | newRSMappingFile.write( u'"%s" "%s"\n' % (mappingPair[0], mappingPair[1] )) 510 | 511 | self.LogInfo( '[REDSHIFT_PATHOVERRIDE_FILE] now set to: "%s"' % newRSMappingFileName ) 512 | self.SetEnvironmentVariable( "REDSHIFT_PATHOVERRIDE_FILE", newRSMappingFileName ) 513 | except: 514 | self.LogWarning( "Failed to set Redshift Pathmapping Environment variable." ) 515 | self.LogWarning( traceback.format_exc()) -------------------------------------------------------------------------------- /DeadlineRepository10/custom/plugins/HoudiniVFXTricks/hrender_dlVFXTricks.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import traceback 4 | from collections import namedtuple 5 | from itertools import chain 6 | 7 | import os 8 | import re 9 | import subprocess 10 | 11 | import hou 12 | 13 | 14 | def setupTileOutput(file, tileNumber): 15 | frameRegex = re.compile("\$F", re.IGNORECASE) 16 | matches = frameRegex.findall(file) 17 | if matches is not None and len(matches) > 0: 18 | paddingString = matches[len(matches) - 1] 19 | padding = "_tile"+str(tileNumber)+"_$F" 20 | file = RightReplace(file, paddingString, padding, 1) 21 | else: 22 | paddedNumberRegex = re.compile("([0-9]+)", re.IGNORECASE) 23 | matches = paddedNumberRegex.findall(file) 24 | if matches is not None and len(matches) > 0: 25 | paddingString = matches[len(matches) - 1] 26 | padding = "_tile"+str(tileNumber)+"_"+paddingString 27 | file = RightReplace(file, paddingString, padding, 1) 28 | else: 29 | splitFilename = os.path.splitext(file) 30 | file = splitFilename[0]+"_tile"+str(tileNumber)+"_"+splitFilename[1] 31 | 32 | return file 33 | 34 | 35 | def includeNodeInTakeAndAllowEditing(node): 36 | currentTake = hou.takes.currentTake() 37 | currentTake.addParmTuplesFromNode(node) 38 | node.allowEditingOfContents() 39 | 40 | 41 | def RightReplace(fullString, oldString, newString, occurences): 42 | return newString.join(fullString.rsplit(oldString, occurences)) 43 | 44 | 45 | def ApplySettingsToRop(rop, settingsDict): 46 | """ 47 | Iterates over a dictionary of settings for the specified ROP and applies their values. 48 | :param rop: The ROP to apply settings to 49 | :param settingsDict: A dictionary of settings with the setting name being the 'Key' and the desired value as the 'Value' 50 | """ 51 | for settingName in settingsDict: 52 | setting = rop.parm(settingName) 53 | if setting is not None: 54 | setting.set(settingsDict.get(settingName)) 55 | 56 | 57 | def GetDeadlineCommand(): 58 | deadlineBin = "" 59 | try: 60 | deadlineBin = os.environ['DEADLINE_PATH'] 61 | except KeyError: 62 | # if the error is a key error it means that DEADLINE_PATH is not set. however Deadline command may be in the PATH or on OSX it could be in the file /Users/Shared/Thinkbox/DEADLINE_PATH 63 | pass 64 | 65 | # On OSX, we look for the DEADLINE_PATH file if the environment variable does not exist. 66 | if deadlineBin == "" and os.path.exists("/Users/Shared/Thinkbox/DEADLINE_PATH"): 67 | with open("/Users/Shared/Thinkbox/DEADLINE_PATH") as f: 68 | deadlineBin = f.read().strip() 69 | 70 | deadlineCommand = os.path.join(deadlineBin, "deadlinecommand") 71 | 72 | return deadlineCommand 73 | 74 | 75 | def CallDeadlineCommand(arguments, hideWindow=True): 76 | deadlineCommand = GetDeadlineCommand() 77 | startupinfo = None 78 | creationflags = 0 79 | if os.name == 'nt': 80 | if hideWindow: 81 | # Python 2.6 has subprocess.STARTF_USESHOWWINDOW, and Python 2.7 has subprocess._subprocess.STARTF_USESHOWWINDOW, so check for both. 82 | if hasattr(subprocess, '_subprocess') and hasattr(subprocess._subprocess, 'STARTF_USESHOWWINDOW'): 83 | startupinfo = subprocess.STARTUPINFO() 84 | startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW 85 | elif hasattr(subprocess, 'STARTF_USESHOWWINDOW'): 86 | startupinfo = subprocess.STARTUPINFO() 87 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 88 | else: 89 | # still show top-level windows, but don't show a console window 90 | CREATE_NO_WINDOW = 0x08000000 # MSDN process creation flag 91 | creationflags = CREATE_NO_WINDOW 92 | 93 | arguments.insert(0, deadlineCommand) 94 | 95 | # Specifying PIPE for all handles to workaround a Python bug on Windows. The unused handles are then closed immediatley afterwards. 96 | proc = subprocess.Popen(arguments, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 97 | startupinfo=startupinfo, creationflags=creationflags) 98 | output, errors = proc.communicate() 99 | 100 | return output 101 | 102 | 103 | def pathmap_file(in_file_name, out_file_name=None, print_stdout=True): 104 | """ 105 | Runs Deadlines CheckPathMappingInFile Deadline command in order to perform pathmapping on a file. 106 | :param in_file_name: The name of the File to be pathmapped 107 | :param out_file_name: The name of the output file (defaults to the input filename) 108 | :param print_stdout: If the Stdout should be printed to the log 109 | :return: None 110 | """ 111 | 112 | if not out_file_name: 113 | out_file_name = in_file_name 114 | 115 | std_output = CallDeadlineCommand(["--CheckPathMappingInFile", in_file_name, out_file_name]) 116 | if print_stdout: 117 | print(std_output) 118 | 119 | 120 | def is_parm_valid_for_pathmapping(parm): 121 | """ 122 | Helper function used to determine if a parm is valid for the purposes of pathmapping. 123 | This will return False for all nodes that meet at least 1 of the following conditions: 124 | * Parm is None 125 | * Parm is locked (non editable) 126 | * Parm is inside of a Locked HDA (Node is not editable) unless it is specified as editable 127 | * Parm is Disabled 128 | * Parm contains an empty string value 129 | * Parm is a soho_program parm (file run when render is called on the specified node 130 | :param parm: The Parm that we wish to check validity on 131 | :return: If the parm is Valid 132 | """ 133 | # If the parm is none then the value is intrinisic to the scene and we cannot modify it 134 | if not parm: 135 | return False 136 | 137 | # Locked Parms cannot be modified 138 | if parm.isLocked(): 139 | return False 140 | 141 | node = parm.node() 142 | # if the node is inside a locked HDA and is not specifically marked as Editable then the value is derived 143 | # from somewhere else 144 | if node.isInsideLockedHDA() and not node.isEditableInsideLockedHDA(): 145 | return False 146 | 147 | # soho_program parms point to the script that a rop will run when told to render and as such should never need to be mapped. 148 | if parm.name() == "soho_program": 149 | return False 150 | 151 | # Disabled nodes have no effect so we should be fine ignoring them. 152 | if parm.isDisabled(): 153 | return False 154 | 155 | # Make sure that we actually have a non empty string without keyframes 156 | # Keyframes will need special casing 157 | try: 158 | if not parm.unexpandedString(): 159 | return False 160 | except hou.OperationFailed: 161 | return False 162 | return True 163 | 164 | 165 | def gather_parms_to_map(): 166 | """ 167 | Builds up a list of all parms in the houdini scene file that need to be pathmapped 168 | and puts them into a list 169 | :return: list of hou.parm that may need pathmapping 170 | """ 171 | 172 | # Get all non empty file references in the scene not marked as Write Only 173 | input_refs = (parm for parm, _ in hou.fileReferences()) 174 | 175 | # Get all Output only file parms from all Arnold ROPs in the scene 176 | 177 | arnold_node_type = hou.nodeType('Driver/arnold') 178 | arnold_output_parms = () 179 | if arnold_node_type: 180 | arnold_output_parms = ( 181 | parm 182 | for node in arnold_node_type.instances() 183 | for parm in node.globParms("ar_aov_separate_file* ar_picture ar_ass_file") 184 | ) 185 | 186 | # Check validity of all parms to narrow down the ones we need to map. 187 | return [ 188 | parm 189 | for parm in chain(input_refs, arnold_output_parms) 190 | if is_parm_valid_for_pathmapping(parm) 191 | ] 192 | 193 | 194 | def pathmap_arnold_procedurals(tempdir, start_frame, end_frame): 195 | """ 196 | Pathmaps all .ass files that are referenced by arnold procedural nodes 197 | :param tempdir: The base directory that the procedural nodes should be copied to. 198 | :param start_frame: The first frame we are rendering with this task 199 | :param end_frame: The last frame we are rendering with this task 200 | :return: None 201 | """ 202 | hou_procedural_type = 'Object/arnold_procedural' 203 | 204 | print("Begin path mapping Arnold procedurals") 205 | for node in hou.nodeType(hou_procedural_type).instances(): 206 | parm = node.parm('ar_filename') 207 | 208 | if not is_parm_valid_for_pathmapping(parm): 209 | print("Skipping parm: %s" % parm.path()) 210 | continue 211 | 212 | # Arnold procedurals can point to multiple file types, we only want to copy .ass files currently. 213 | try: 214 | if os.path.splitext(parm.evalAsString())[1].lower() != ".ass": 215 | continue 216 | except hou.OperationFailed: 217 | # We were not able to evaluate the parm. 218 | continue 219 | 220 | print("Updating parm: %s" % parm.path()) 221 | 222 | # Determine the location to copy the files to and create the directory. 223 | # The Path will be (/tempdir_/path/to/node 224 | node_tempdir = os.path.join(tempdir, node.path().strip('/')) 225 | if not os.path.isdir(node_tempdir): 226 | os.makedirs(node_tempdir) 227 | 228 | # Get the unexpanded File path so we can update the Parm after copying files. 229 | unexpanded = parm.unexpandedString() 230 | unexpanded_name = os.path.split(unexpanded)[1] 231 | 232 | copied_files = set() 233 | 234 | for frame in range(start_frame, end_frame + 1): 235 | # Eval all tokens and expressions at each frame 236 | expanded_file = parm.evalAsStringAtFrame(frame) 237 | 238 | if not expanded_file in copied_files: 239 | # Perform Pathmapping 240 | print("Performing path mapping on '%s'" % expanded_file) 241 | expanded_file_name = os.path.split(expanded_file)[1] 242 | pathmap_file(expanded_file, os.path.join(node_tempdir, expanded_file_name)) 243 | copied_files.add(expanded_file) 244 | 245 | # Update the Parm 246 | try: 247 | parm.set(os.path.join(node_tempdir, unexpanded_name)) 248 | except hou.OperationFailed as e: 249 | print('Failed to update Parm "%s" received the following exception: %s' % (parm.path(), e)) 250 | 251 | print("End path mapping Arnold procedurals") 252 | 253 | 254 | def pathmap_parms(tempdir, parms): 255 | """ 256 | Performs path mapping on a list of parms 257 | :param tempdir: The temporary directory that we will use as part of pathmapping 258 | :param parms: A list of parms that we will be performing pathmapping 259 | :return: None 260 | """ 261 | ver = hou.applicationVersion()[0] 262 | # Write out a temporary file with each path on a separate line 263 | pathmapFileName = os.path.join(tempdir, "parm_pathmap.txt") 264 | with open(pathmapFileName, 'w') as pathmap_handle: 265 | if ver < 18: 266 | pathmap_handle.write( 267 | "\n".join(parm.unexpandedString() for parm in parms) 268 | ) 269 | else: 270 | pathmap_handle.write( 271 | "\n".join(parm.rawValue() for parm in parms) 272 | ) 273 | # Perform pathmapping on the file, so each line is swapped to the new mapped location. 274 | pathmap_file(pathmapFileName, pathmapFileName) 275 | 276 | # Read back in the mapped file and update all parms to the new values 277 | with open(pathmapFileName, 'r') as pathmap_handle: 278 | for parm, path in zip(parms, pathmap_handle.readlines()): 279 | if ver < 18: 280 | raw_value = parm.unexpandedString() 281 | else: 282 | raw_value = parm.rawValue() 283 | 284 | if not raw_value == path.strip(): 285 | try: 286 | pass 287 | #print('Updating Parm "%s" from "%s" to "%s"' % (parm.path(), raw_value, path.strip())) 288 | # parm.set(path.strip()) 289 | except hou.OperationFailed as e: 290 | print('Failed to update Parm "%s" received the following exception: %s' % (parm.path(), e)) 291 | 292 | 293 | def pathmap_envs(tempdir, envs): 294 | """ 295 | Performs path mapping on a list of DLPathmapEnvs 296 | :param tempdir: The temporary directory that we will use as part of pathmapping 297 | :param envs: A list of envs that we will be performing pathmapping 298 | :return: None 299 | """ 300 | # Write out a temporary file with each path on a separate line 301 | pathmap_file_name = os.path.join(tempdir, "env_pathmap.txt") 302 | with open(pathmap_file_name, 'w') as pathmap_handle: 303 | pathmap_handle.write( 304 | "\n".join(env.val for env in envs) 305 | ) 306 | 307 | # Perform pathmapping on the file, so each line is swapped to the new mapped location. 308 | pathmap_file(pathmap_file_name, pathmap_file_name) 309 | 310 | # Read back in the mapped file and update all parms to the new values 311 | with open(pathmap_file_name, 'r') as pathmap_handle: 312 | for orig, line in zip(envs, pathmap_handle.readlines()): 313 | val = line.strip() 314 | if val != orig.val: 315 | print('Setting variable "{name}" to {val}'.format(name=orig.name, val=val)) 316 | hou.putenv(orig.name, val) 317 | 318 | # Varchange updates all nodes in the scene to use the latest versions of vals 319 | hou.hscript('varchange') 320 | 321 | 322 | def gather_envs_to_map(): 323 | """ 324 | Builds up a list of all variable in the houdini env that need to be pathmapped 325 | and puts them into a list 326 | :return: list of EnvToMap that may need pathmapping 327 | """ 328 | env_to_map = namedtuple('EnvToMap', ['name', 'val']) 329 | 330 | found_envs = [] 331 | 332 | # gather all globally set houdini variables 333 | # this will return everything in 2 strings the first string of the format: 334 | # ENV\t= VAL\n 335 | # While the second is the Errors from the command 336 | set_envs = hou.hscript('setenv') 337 | 338 | for line in set_envs[0].split('\n'): 339 | if line.strip(): 340 | env, val = line.split('\t= ', 1) 341 | found_envs.append(env_to_map(env.strip(), val.strip())) 342 | 343 | return found_envs 344 | 345 | 346 | def perform_pathmapping(tempdir): 347 | """ 348 | Perform pathmapping on all input parameters and other select parameters 349 | :param tempdir: the temporary location that a text file will be written to to aid pathmapping. 350 | :return: None 351 | """ 352 | if not tempdir: 353 | print("Temporary Directory has not been set. Skipping Pathmapping.") 354 | return 355 | 356 | print("Begin Path Mapping") 357 | # gather a list of all parameters that need to be pathmapped 358 | parms = gather_parms_to_map() 359 | if parms: 360 | pathmap_parms(tempdir, parms) 361 | 362 | envs = gather_envs_to_map() 363 | if envs: 364 | pathmap_envs(tempdir, envs) 365 | 366 | print("End Path Mapping") 367 | 368 | 369 | try: 370 | print("Detected Houdini version: " + str(hou.applicationVersion())) 371 | 372 | args = sys.argv 373 | print(args) 374 | 375 | startFrame = 0 376 | endFrame = 0 377 | increment = 1 378 | frameTuple = () 379 | # Parse the arguments 380 | if "-f" in args: 381 | frameIndex = args.index("-f") 382 | startFrame = int(args[frameIndex + 1]) 383 | endFrame = int(args[frameIndex + 2]) 384 | increment = int(args[frameIndex + 3]) 385 | print("Start: " + str(startFrame)) 386 | print("End: " + str(endFrame)) 387 | print("Increment: " + str(increment)) 388 | frameTuple = (startFrame, endFrame, increment) 389 | 390 | resolution = () 391 | if "-r" in args: 392 | resolutionIndex = args.index("-r") 393 | width = int(args[resolutionIndex + 1]) 394 | height = int(args[resolutionIndex + 2]) 395 | resolution = (width, height) 396 | print("Width: " + str(width)) 397 | print("Height: " + str(height)) 398 | 399 | ignoreInputs = False 400 | if "-g" in args: 401 | ignoreInputs = True 402 | print("Ignore Inputs: True") 403 | else: 404 | print("Ignore Inputs: False") 405 | 406 | if "-o" not in args: 407 | output = None 408 | ext = None 409 | print("No output specified. Output will be handled by the driver") 410 | else: 411 | outputIndex = args.index("-o") 412 | output = args[outputIndex + 1] 413 | print("Output: " + output) 414 | 415 | if "-i" not in args: 416 | ifd = None 417 | else: 418 | ifdIndex = args.index("-i") 419 | ifd = args[ifdIndex + 1] 420 | print("IFD: " + ifd) 421 | 422 | gpus = None 423 | if "-gpu" in args: 424 | gpusIndex = args.index("-gpu") 425 | gpus = args[gpusIndex + 1] 426 | print("GPUs: " + gpus) 427 | 428 | tileRender = False 429 | xStart = 0 430 | xEnd = 0 431 | yStart = 0 432 | yEnd = 0 433 | currTile = 0 434 | if "-t" in args: 435 | tileRender = True 436 | tileIndex = args.index("-t") 437 | currTile = int(args[tileIndex + 1]) 438 | xStart = float(args[tileIndex + 2]) 439 | xEnd = float(args[tileIndex + 3]) 440 | yStart = float(args[tileIndex + 4]) 441 | yEnd = float(args[tileIndex + 5]) 442 | 443 | if "-wedgenum" not in args: 444 | wedgeNum = -1 445 | else: 446 | wedgeNumIndex = args.index("-wedgenum") 447 | wedgeNum = int(args[wedgeNumIndex + 1]) 448 | print("Wedge Number: " + str(wedgeNum)) 449 | 450 | driverIndex = args.index("-d") 451 | driver = args[driverIndex + 1] 452 | # if not driver.startswith( "/" ): 453 | # driver = "/out/" + driver 454 | print("Driver: " + driver) 455 | 456 | inputFile = args[len(args) - 1] 457 | print("Input File: " + inputFile) 458 | 459 | isSim = False 460 | sliceNum = 0 461 | trackerMachine = "" 462 | trackerPort = -1 463 | if "-s" in args: 464 | isSim = True 465 | sliceIndex = args.index("-s") 466 | sliceNum = int(args[sliceIndex + 1]) 467 | trackerMachine = args[sliceIndex + 2] 468 | trackerPort = int(args[sliceIndex + 3]) 469 | 470 | tempdir = "" 471 | if "-tempdir" in args: 472 | tempdirIndex = args.index("-tempdir") 473 | tempdir = args[tempdirIndex + 1] 474 | 475 | arnoldAbortOnLicenseFail = 1 476 | if "-arnoldAbortOnLicenseFail" in args: 477 | arnoldAbortOnLicenseFailIndex = args.index("-arnoldAbortOnLicenseFail") 478 | arnoldAbortOnLicenseFail = int(args[arnoldAbortOnLicenseFailIndex + 1]) 479 | 480 | if "-section" in args: 481 | sectionIndex = args.index("-section") 482 | section = args[sectionIndex + 1] 483 | #hou.putenv('SECTION', str(section)) 484 | 485 | if "-type" in args: 486 | typeIndex = args.index("-type") 487 | type = args[typeIndex + 1] 488 | print(type) 489 | 490 | if "-node" in args: 491 | nodeIndex = args.index("-node") 492 | node = args[nodeIndex + 1] 493 | print(node) 494 | # Print out load warnings, but continue on a successful load. 495 | try: 496 | hou.hipFile.load(inputFile) 497 | except hou.LoadWarning as e: 498 | print(e) 499 | 500 | # Get the output driver. 501 | rop = hou.node(driver) 502 | 503 | # set playbar range 504 | hou.playbar.setFrameRange(startFrame, endFrame) 505 | hou.playbar.setPlaybackRange(startFrame, endFrame) 506 | hou.setFrame(startFrame) 507 | 508 | if rop == None: 509 | print("Error: Driver \"" + driver + "\" does not exist") 510 | else: 511 | print(section) 512 | if section != 'None': 513 | if type == 'Cacher': 514 | readerPath = rop.parm("readerpath").eval() 515 | readerNode = hou.node(readerPath) 516 | currentSectionParm = readerNode.parm("currentsection") 517 | currentSectionParm.set(section) 518 | if type == 'OceanTiler': 519 | node = hou.node(node) 520 | currentSectionParm = node.parm("currenttile") 521 | currentSectionParm.set(section) 522 | 523 | includeNodeInTakeAndAllowEditing(rop) 524 | if isSim: 525 | sliceType = rop.parm("slice_type").evalAsString() 526 | if sliceType == "volume" or sliceType == "particle": 527 | # Sim job, so update the sim control node and get the actual ROP for rendering. 528 | simControlName = rop.parm("hq_sim_controls").evalAsString() 529 | print("Sim control node: " + simControlName) 530 | 531 | hou.hscript("setenv SLICE="+str(sliceNum)) 532 | hou.hscript("varchange") 533 | print("Sim slice: " + str(sliceNum)) 534 | 535 | simControlNode = hou.node(simControlName) 536 | includeNodeInTakeAndAllowEditing(simControlNode) 537 | if simControlNode.parm("visaddress") is not None: 538 | simControlNode.parm("visaddress").set(trackerMachine) 539 | else: 540 | simControlNode.parm("address").set(trackerMachine) 541 | 542 | simControlNode.parm("port").set(trackerPort) 543 | 544 | print("Sim Tracker: " + trackerMachine) 545 | print("Sim Port: " + str(trackerPort)) 546 | elif sliceType == "cluster": 547 | # Sim job, so update the sim control node and get the actual ROP for rendering. 548 | simControlName = rop.parm("hq_sim_controls").evalAsString() 549 | print("Sim control node: " + simControlName) 550 | 551 | hou.hscript("setenv CLUSTER="+str(sliceNum)) 552 | hou.hscript("varchange") 553 | print("Sim cluster: " + str(sliceNum)) 554 | 555 | rop = hou.node(rop.parm("hq_driver").eval()) 556 | includeNodeInTakeAndAllowEditing(rop) 557 | startFrame = int(rop.evalParm("f1")) 558 | endFrame = int(rop.evalParm("f2")) 559 | increment = int(rop.evalParm("f3")) 560 | 561 | # Set the necessar IFD settings if exporting IFD files. 562 | if ifd is not None: 563 | print("Setting SOHO output mode to 1") 564 | ifdExportParm = rop.parm("soho_outputmode") 565 | if ifdExportParm is not None: 566 | ifdExportParm.set(1) 567 | 568 | print("Setting SOHO disk file to " + ifd) 569 | ifdFilenameParm = rop.parm("soho_diskfile") 570 | if ifdFilenameParm is not None: 571 | ifdFilenameParm.set(ifd) 572 | 573 | # perform_pathmapping(tempdir) 574 | # Turn progress reporting on, and set the output path. The reason we set the output path here instead of 575 | # in the 'render' function below is that the 'render' function always seems to replace the $F padding with 576 | # frame 1. So the output for each frame always overwrites the previous. 577 | ropType = rop.type().name() 578 | safeROPType = rop.type().nameWithCategory() 579 | print("ROP type: " + ropType) 580 | 581 | wedgeNode = None 582 | 583 | isWedge = (ropType == "wedge") 584 | numTasks = 1 585 | 586 | # If this is a wedge rop we need to do some additional set up 587 | if isWedge: 588 | 589 | # Get the render rop and make sure the frame range is set correctly 590 | renderNode = rop.node(rop.parm("driver").eval()) 591 | includeNodeInTakeAndAllowEditing(renderNode) 592 | renderNode.parm("f1").set(startFrame) 593 | renderNode.parm("f2").set(endFrame) 594 | renderNode.parm("f3").set(increment) 595 | frameTuple = () 596 | if (wedgeNum >= 0): 597 | # We are only using one wedge, set it up as such 598 | rop.parm("wrange").set(1) 599 | rop.parm("wedgenum").set(wedgeNum) 600 | else: 601 | # Do all the wedges for the frame range. We will use this scripts last call to render as the last wedge's render call, 602 | # so we just need to render the first n-1 wedges here and then set up the rop for the last render. 603 | wedgeMethod = rop.parm("wedgemethod").evalAsString() 604 | if wedgeMethod == "channel": 605 | numParams = rop.parm("wedgeparams").eval() 606 | random = rop.parm("random").eval() 607 | 608 | if random: 609 | # We're using the random settings 610 | numRandom = rop.parm("numrandom").eval() 611 | numTasks = numRandom * numParams 612 | else: 613 | # Using the number wedge params to determine task count 614 | for i in range(1, numParams+1): 615 | numTasks = numTasks * int(rop.parm("steps"+str(i)).eval()) 616 | elif wedgeMethod == "take": 617 | takename = rop.parm("roottake").eval() 618 | parentTake = hou.takes.findTake(takename) 619 | if parentTake: 620 | children = parentTake.children() 621 | numTasks = len(children) 622 | 623 | rop.parm("wrange").set(1) 624 | 625 | # Store the wedge rop for rendering later, set the output driver rop as the current rop to ensure 626 | # all our output and progress is tracked. 627 | wedgeNode = rop 628 | rop = renderNode 629 | ropType = rop.type().name() 630 | 631 | if ropType == 'rop_geometry': 632 | # Turn on Alfred-style progress reporting on Geo ROP. 633 | alf_prog_parm = rop.parm("alfprogress") 634 | if alf_prog_parm is not None: 635 | alf_prog_parm.set(1) 636 | 637 | elif ropType == 'geometry': 638 | alfredProgress = rop.parm("alfprogress") 639 | if alfredProgress is not None: 640 | alfredProgress.set(1) 641 | print("Enabled Alfred style progress") 642 | 643 | reportNetwork = rop.parm("reportnetwork") 644 | if reportNetwork is not None: 645 | reportNetwork.set(1) 646 | print("Enabled network use reporting") 647 | 648 | if output is not None: 649 | print('about to render3') 650 | outputFile = rop.parm("sopoutput") 651 | if outputFile is not None: 652 | outputFile.set(output) 653 | 654 | elif ropType == 'ifd': 655 | alfredProgress = rop.parm("vm_alfprogress") 656 | if alfredProgress is not None: 657 | alfredProgress.set(1) 658 | print("Enabled Alfred style progress") 659 | 660 | verbosity = rop.parm("vm_verbose") 661 | if verbosity is not None: 662 | verbosity.set(3) 663 | print("Set verbosity to 3") 664 | 665 | if tileRender: 666 | if output == None: 667 | output = rop.parm("vm_picture").unexpandedString() 668 | 669 | output = setupTileOutput(output, currTile) 670 | 671 | ropTilesEnabled = rop.parm("vm_tile_render") 672 | if ropTilesEnabled is not None: 673 | ropTilesEnabled.set(0) 674 | 675 | if output is not None: 676 | outputFile = rop.parm("vm_picture") 677 | if outputFile is not None: 678 | outputFile.set(output) 679 | 680 | elif ropType == 'arnold': 681 | logToConsole = rop.parm("ar_log_console_enable") 682 | if logToConsole is not None: 683 | logToConsole.set(1) 684 | print("Enabled log to console") 685 | 686 | logVerbosity = rop.parm("ar_log_verbosity") 687 | if logVerbosity is not None: 688 | logVerbosity.set('detailed') 689 | print("Set verbosity to " + logVerbosity.eval()) 690 | 691 | abortOnLicenseFail = rop.parm("ar_abort_on_license_fail") 692 | if abortOnLicenseFail is not None: 693 | abortOnLicenseFail.set(arnoldAbortOnLicenseFail) 694 | print("Set Arnold abort on license fail to %s" % abortOnLicenseFail.eval()) 695 | 696 | if tileRender: 697 | if output == None: 698 | output = rop.parm("ar_picture").unexpandedString() 699 | 700 | output = setupTileOutput(output, currTile) 701 | 702 | if output is not None: 703 | outputFile = rop.parm("ar_picture") 704 | if outputFile is not None: 705 | outputFile.set(output) 706 | 707 | # Arnold does not work with Houdini's normal pathmapping so we use our own. 708 | pathmap_arnold_procedurals(tempdir, startFrame, endFrame) 709 | 710 | elif ropType == 'baketexture': 711 | if output is not None: 712 | outputFile = rop.parm("vm_uvoutputpicture1") 713 | if outputFile is not None: 714 | outputFile.set(output) 715 | 716 | elif ropType == "comp": 717 | if output is not None: 718 | outputFile = rop.parm("copoutput") 719 | if outputFile is not None: 720 | outputFile.set(output) 721 | 722 | elif ropType == "channel": 723 | if output is not None: 724 | outputFile = rop.parm("chopoutput") 725 | if outputFile is not None: 726 | outputFile.set(output) 727 | 728 | elif ropType == "dop": 729 | if output is not None: 730 | outputFile = rop.parm("dopoutput") 731 | if outputFile is not None: 732 | outputFile.set(output) 733 | 734 | elif ropType == 'filmboxfbx': 735 | makePath = rop.parm("mkpath") 736 | if makePath is not None: 737 | makePath.set(1) 738 | 739 | if output is not None: 740 | outputFile = rop.parm("sopoutput") 741 | if outputFile is not None: 742 | outputFile.set(output) 743 | 744 | elif ropType == 'opengl': 745 | if output is not None: 746 | outputFile = rop.parm("picture") 747 | if outputFile is not None: 748 | outputFile.set(output) 749 | 750 | elif ropType == "rib": 751 | if tileRender: 752 | if output is None: 753 | output = rop.parm("ri_display").unexpandedString() 754 | 755 | output = setupTileOutput(output, currTile) 756 | 757 | if output is not None: 758 | outputFile = rop.parm("ri_display") 759 | if outputFile is not None: 760 | outputFile.set(output) 761 | 762 | elif ropType == "ris::22": 763 | if tileRender: 764 | if output is None: 765 | # No need to check displays here. Tile Rendering is only available with Override Output. 766 | # We check if there is a single display later in the Override Output. 767 | output = rop.parm("ri_display_0").unexpandedString() 768 | 769 | if output is not None: 770 | output = setupTileOutput(output, currTile) 771 | 772 | if output is not None: 773 | displays = rop.parm("ri_displays") 774 | if displays is not None: 775 | total_displays = displays.eval() 776 | 777 | if total_displays == 1: 778 | outputFile = rop.parm("ri_display_0") 779 | if outputFile is not None: 780 | outputFile.set(output) 781 | else: 782 | raise RuntimeError("Override Output is supported only for a single display but found {0}.".format(total_displays)) 783 | 784 | printProgress = rop.parm("progress") 785 | if printProgress is not None: 786 | printProgress.set(1) 787 | 788 | elif ropType == "rop_alembic": 789 | if output is not None: 790 | outputFile = rop.parm("filename") 791 | if outputFile is not None: 792 | outputFile.set(output) 793 | 794 | elif ropType == "Redshift_ROP": 795 | redshiftHardSettingsDict = { 796 | "RS_nonBlockingRendering": 0, 797 | "RS_renderAOVsToMPlay": 0, 798 | "RS_overwriteMPlayImage": 0, 799 | "RS_MPlay_disabledNonGUI": 0, 800 | "RS_renderToMPlay": 0 801 | } 802 | 803 | if output is not None: 804 | redshiftHardSettingsDict["RS_outputFileNamePrefix"] = output 805 | 806 | ApplySettingsToRop(rop, redshiftHardSettingsDict) 807 | 808 | if gpus is not None: 809 | print("This Slave is overriding its GPU affinity, so the following GPUs will be used by RedShift: " + gpus) 810 | gpus = gpus.split(",") 811 | gpuSettingString = "" 812 | for i in range(8): 813 | if str(i) in gpus: 814 | gpuSettingString += "1" 815 | else: 816 | gpuSettingString += "0" 817 | hou.hscript("Redshift_setGPU -s "+gpuSettingString) 818 | 819 | elif ropType == "Octane_ROP": 820 | octaneHardSettingsDict = { 821 | "HO_statisticsMPlay": 0, 822 | "HO_statisticsFinalMPlay": 0, 823 | "HO_overwriteMPlay": 0, 824 | "HO_renderToMPlay": 0, 825 | "HO_img_enable": 1, 826 | "HO_img_createDir": 1 827 | } 828 | 829 | if output is not None: 830 | octaneHardSettingsDict["HO_img_fileName"] = output 831 | 832 | ApplySettingsToRop(rop, octaneHardSettingsDict) 833 | 834 | if gpus is not None: 835 | print("This Slave is overriding its GPU affinity, so the following GPUs will be used by Octane: " + gpus) 836 | gpus = gpus.split(",") 837 | for i in range(16): 838 | hou.hscript("Octane_setGPU -g %s -s %s" % (i, int(str(i) in gpus))) 839 | 840 | elif safeROPType == "Driver/vray_renderer": 841 | if output is not None: 842 | outputFile = rop.parm("SettingsOutput_img_file_path") 843 | if outputFile is not None: 844 | outputFile.set(output) 845 | if ifd is not None: 846 | ifdFile = rop.parm("render_export_filepath") 847 | if ifdFile is not None: 848 | ifdFile.set(ifd) 849 | 850 | if tileRender: 851 | camera = rop.parm("camera").eval() 852 | cameraNode = hou.node(camera) 853 | includeNodeInTakeAndAllowEditing(cameraNode) 854 | 855 | cropLeft = cameraNode.parm("cropl") 856 | if cropLeft is not None: 857 | cropLeft.set(xStart) 858 | 859 | cropRight = cameraNode.parm("cropr") 860 | if cropRight is not None: 861 | cropRight.set(xEnd) 862 | 863 | cropBottom = cameraNode.parm("cropb") 864 | if cropBottom is not None: 865 | cropBottom.set(yStart) 866 | 867 | cropTop = cameraNode.parm("cropt") 868 | if cropTop is not None: 869 | cropTop.set(yEnd) 870 | 871 | frameString = "" 872 | # Render the frames. 873 | if startFrame == endFrame: 874 | frameString = "frame " + str(startFrame) 875 | else: 876 | frameString = "frame " + str(startFrame) + " to " + str(endFrame) 877 | 878 | if isWedge: 879 | rop = wedgeNode 880 | if wedgeNum == -1: 881 | # Do all the wedges for the frame range. We will use this scripts' last call to render as the last wedge's render call, 882 | # so we just need to render the first n-1 wedges here and then set up the rop for the last render. 883 | for i in range(0, (numTasks - 1)): 884 | print("Rendering wedge " + str(i) + " for " + frameString) 885 | rop.parm("wedgenum").set(i) 886 | rop.render(frameTuple, resolution, ignore_inputs=ignoreInputs) 887 | 888 | # Since we looped to the second last, we need to set the wedge number for the last render call 889 | print("Rendering wedge " + str(numTasks-1) + " for " + frameString) 890 | rop.parm("wedgenum").set(numTasks-1) 891 | else: 892 | # This is a single wedge job 893 | print("Rendering wedge " + str(wedgeNum) + " for " + frameString) 894 | else: 895 | print("Rendering " + frameString) 896 | 897 | # Renders the given rop. Renders the last wedge of a multi-wedge wedge job. 898 | if frameTuple == (0, 0, 1): 899 | print('single frame') 900 | rop.render(res=resolution, ignore_inputs=ignoreInputs) 901 | else: 902 | print('frame range') 903 | rop.render(frameTuple, resolution, ignore_inputs=ignoreInputs) 904 | 905 | print("Finished Rendering") 906 | except Exception as e: 907 | print("Error: Caught exception: " + str(e)) 908 | raise 909 | --------------------------------------------------------------------------------