├── .githooks ├── post-checkout ├── pre-commit ├── shotgun.pyi └── update-venv ├── .gitignore ├── .gitmodules ├── Dungance Designer.lnk ├── Dungance Painter.lnk ├── Dungaya.lnk ├── Dungaya.sh ├── Dungini.lnk ├── Dungini.sh ├── LICENSE ├── Nungeon.lnk ├── Nungeon.sh ├── README.md ├── desktop-files ├── Dungaya.desktop ├── Dungini.desktop └── Nungeon.desktop ├── pipeline ├── __main__.py ├── env.py.md ├── lib │ ├── hdri │ │ └── STUDIO_DIFFUSE_4k.hdr │ ├── icon │ │ ├── chameleon.png │ │ ├── envelope.png │ │ ├── houdini.ico │ │ ├── houdini.png │ │ ├── material-help.svg │ │ ├── maya.ico │ │ ├── maya.png │ │ ├── monocle.png │ │ ├── nuke.ico │ │ ├── nuke.png │ │ ├── rig_arm.png │ │ ├── robin.png │ │ └── skull.png │ ├── normal2height.sbs │ ├── ocio │ │ └── love-v01 │ │ │ ├── config.ocio │ │ │ ├── rman_color_config_love-v01.json │ │ │ └── transforms │ │ │ ├── EGamut_2_XYZ.spimtx │ │ │ ├── TLog_2_Linear.spi1d │ │ │ └── TLog_2_sRGB_TCAMv2.cub │ ├── sbs │ │ └── normal2height.sbsar │ ├── splash │ │ ├── MayaEDUStartupImage.png │ │ ├── MayaEDUStartupImage_150.png │ │ ├── MayaEDUStartupImage_200.png │ │ └── dunginisplash19.5.png │ ├── tex │ │ └── Reflective-Macbeth-chart.png │ └── usd │ │ └── kinds │ │ ├── config │ │ └── Icons │ │ │ └── SCENEGRAPH_kind_character.png │ │ └── plugInfo.json ├── pipe │ ├── __init__.py │ ├── db │ │ ├── __init__.py │ │ ├── interface.py │ │ ├── sgaadb.py │ │ ├── shotgun_api3 │ │ └── typing.py │ ├── glui │ │ ├── __init__.py │ │ └── dialogs.py │ ├── h │ │ ├── __init__.py │ │ ├── animpostprocess.py │ │ ├── hipfile │ │ │ ├── __init__.py │ │ │ ├── asset.py │ │ │ ├── environment.py │ │ │ ├── filemanager.py │ │ │ └── shot.py │ │ ├── local.py │ │ ├── nodelayouts.py │ │ ├── shading.py │ │ └── util │ │ │ ├── __init__.py │ │ │ └── parmdata.py │ ├── m │ │ ├── AlexTesting │ │ │ ├── AGFunctions.py │ │ │ ├── FaceBaseJointsAutoCreator.py │ │ │ └── MouthScript.py │ │ ├── ToolBox │ │ │ ├── EyeSocket_V1_01.py │ │ │ ├── Katie_Toolbox_GUI.py │ │ │ ├── Updating_Scripts.py │ │ │ ├── V2_EyeUIandBasics.py │ │ │ ├── add_bind_joint_attribute.py │ │ │ └── select_face_bind_joints.py │ │ ├── __init__.py │ │ ├── local.py │ │ ├── picker.py │ │ ├── playblast │ │ │ ├── __init__.py │ │ │ ├── anim.py │ │ │ ├── playblaster.py │ │ │ ├── previs.py │ │ │ ├── struct.py │ │ │ └── ui.py │ │ ├── publish │ │ │ ├── __init__.py │ │ │ ├── anim.py │ │ │ ├── asset.py │ │ │ ├── camera.py │ │ │ ├── publisher.py │ │ │ ├── rig.py │ │ │ └── usdchaser.py │ │ ├── reload.py │ │ ├── rig_publish.py │ │ ├── shotfile │ │ │ ├── __init__.py │ │ │ ├── anim.py │ │ │ ├── rlo.py │ │ │ ├── shotfile_manager.py │ │ │ └── timeline.py │ │ ├── space_switch.py │ │ ├── studiolibrary.py │ │ └── util.py │ ├── sp │ │ ├── __init__.py │ │ ├── channels.py │ │ ├── export.py │ │ ├── local.py │ │ ├── metadata.py │ │ ├── reload.py │ │ └── ui.py │ ├── struct │ │ ├── __init__.py │ │ ├── db.py │ │ ├── material.py │ │ ├── timeline.py │ │ └── util.py │ ├── texconverter.py │ └── util │ │ ├── __init__.py │ │ ├── filemanager.py │ │ ├── playblaster.py │ │ └── struct.py ├── shared │ ├── __init__.py │ └── util.py ├── sitecustomize.py └── software │ ├── __init__.py │ ├── baseclass.py │ ├── houdini │ ├── __init__.py │ ├── dcc.py │ └── hsite │ │ ├── houdini19.5 │ │ └── presets │ │ │ └── Lop │ │ │ ├── hdprmanrenderproperties.idx │ │ │ ├── rendersettings.idx │ │ │ └── sdm223-main-LnD_Load_Layers-1.0.idx │ │ ├── houdini20.5 │ │ ├── desktop │ │ │ └── Solungeon.desk │ │ ├── houdini.pref │ │ ├── otls │ │ │ ├── .gitignore │ │ │ ├── Recipes.hdanc │ │ │ ├── generic_ascii_hda.hda │ │ │ │ ├── INDEX__SECTION │ │ │ │ ├── Sections.list │ │ │ │ ├── houdini.hdalibrary │ │ │ │ └── sdm223_8_8Lop_1generic__ascii__hda_8_81.0 │ │ │ │ │ ├── Contents.dir │ │ │ │ │ ├── Contents.createtimes │ │ │ │ │ ├── Contents.houdini_versions │ │ │ │ │ ├── Contents.mime │ │ │ │ │ ├── Contents.modtimes │ │ │ │ │ └── Sections.list │ │ │ │ │ ├── CreateScript │ │ │ │ │ ├── DialogScript │ │ │ │ │ ├── ExtraFileOptions │ │ │ │ │ ├── Help │ │ │ │ │ ├── InternalFileOptions │ │ │ │ │ ├── Sections.list │ │ │ │ │ ├── Tools.shelf │ │ │ │ │ └── TypePropertiesOptions │ │ │ ├── lop_dbclark.render_uv_variant.1.0.hdanc │ │ │ ├── lop_sdm222.lnd_anim_postprocess.1.0.hdanc │ │ │ ├── lop_sdm222.lnd_asset_cluster.hdanc │ │ │ ├── lop_sdm222.lnd_camera_shake.1.0.hdanc │ │ │ ├── lop_sdm222.lnd_render_layer.1.0.hdanc │ │ │ ├── lop_sdm222.lnd_render_layer.1.1.hdanc │ │ │ ├── lop_sdm223.LnD_Character_Config.1.0.hdanc │ │ │ ├── lop_sdm223.LnD_Import_Camera.hdanc │ │ │ ├── lop_sdm223.dev.LnD_BaseMaterial.1.0.hdanc │ │ │ ├── lop_sdm223.dev.LnD_Char_Setup.1.0.hdanc │ │ │ ├── lop_sdm223.dev.LnD_Hair_Asset.1.0.hdanc │ │ │ ├── lop_sdm223.dev.LnD_Lookdev.1.0.hdanc │ │ │ ├── lop_sdm223.dev.LnD_Shot_Hair.1.0.hdanc │ │ │ ├── lop_sdm223.dev.end_sublayer.1.0.hdanc │ │ │ ├── lop_sdm223.dev.lnd_componentconfig.1.0.hdanc │ │ │ ├── lop_sdm223.lnd_nodelayouts.hdanc │ │ │ ├── lop_sdm223.main.LnD_Load_Layers.1.0.hdanc │ │ │ ├── lop_sdm223.main.LnD_MatLib.1.0.hdanc │ │ │ ├── lop_sopmodify2.hdanc │ │ │ └── sop_sdm223.main.LnD_Pack_UDIMs.1.0.hdanc │ │ ├── packages │ │ │ ├── AeSVG.json │ │ │ ├── LYNX.json │ │ │ ├── MOPS.json │ │ │ ├── axiom3.2.082.json │ │ │ ├── renderman_for_houdini.json │ │ │ └── tlops.json │ │ ├── python3.11libs │ │ │ ├── pythonrc.py │ │ │ └── uiready.py │ │ ├── scripts │ │ │ ├── 456.py │ │ │ ├── lop │ │ │ │ ├── assetreference_OnCreated.py │ │ │ │ ├── hdprmanrenderproperties_OnCreated.py │ │ │ │ ├── instancer_OnCreated.py │ │ │ │ ├── materiallibrary_OnCreated.py │ │ │ │ ├── rendersettings_OnCreated.py │ │ │ │ ├── tlops-tractor_configure-1.0_OnCreated.py │ │ │ │ └── tlops-tractor_denoise-1.0_OnCreated.py │ │ │ ├── obj │ │ │ │ └── lopimportcam_OnCreated.py │ │ │ └── sop │ │ │ │ └── filecache-2.0_OnCreated.py │ │ └── toolbar │ │ │ └── lnd_solaris.shelf │ │ ├── matl │ │ ├── b2r.cpio │ │ └── standard.cpio │ │ └── sop │ │ └── component.cpio │ ├── interface.py │ ├── maya │ ├── __init__.py │ ├── dcc.py │ ├── scripts │ │ ├── .gitignore │ │ ├── dwpicker │ │ ├── modelChecker │ │ └── timeline_marker │ ├── shelves │ │ ├── shelf_LnD_Animation.mel │ │ ├── shelf_LnD_Assets.mel │ │ ├── shelf_LnD_RLO.mel │ │ └── shelf_LnD_Rigging.mel │ └── userSetup │ │ └── userSetup.py │ ├── nuke │ ├── __init__.py │ ├── dcc.py │ └── tools │ │ ├── NungeonTools │ │ ├── gizmos │ │ │ ├── FrameBurn.gizmo │ │ │ ├── Lens.gizmo │ │ │ ├── grade_AOV.gizmo │ │ │ ├── lumaDistort.gizmo │ │ │ └── roughenEdges.gizmo │ │ ├── images │ │ │ ├── lensdirt.jpg │ │ │ ├── noThumbnailIcon.jpg │ │ │ ├── nungeonIcon.png │ │ │ ├── openShot.jpg │ │ │ └── rayden.jpg │ │ ├── menu.py │ │ ├── scripts │ │ │ ├── import_usd_camera.py │ │ │ ├── ld_write_node.py │ │ │ ├── ld_write_node_v2.py │ │ │ ├── open_shot.py │ │ │ ├── render_layer_selector.py │ │ │ ├── render_layer_selector.py~ │ │ │ └── set_frameRange_and_aspectRatio.py │ │ └── toolsets │ │ │ ├── deep_fog.nk │ │ │ ├── depth_fog.nk │ │ │ ├── eyelights.nk │ │ │ ├── ld_lightwrap │ │ │ ├── ld_lightwrap.nk │ │ │ ├── ld_skydome_basic.nk │ │ │ ├── ld_snow_glitter_clamp.nk │ │ │ ├── relight_template.nk │ │ │ ├── shotTemplate.nk │ │ │ ├── template.nk~ │ │ │ └── wireframe_breakdown.nk │ │ └── init.py │ ├── substance_designer │ ├── __init__.py │ ├── dcc.py │ ├── lnd_configuration.sbscfg │ └── lnd_project.sbsprj │ └── substance_painter │ ├── __init__.py │ ├── dcc.py │ └── plugins │ └── startup │ ├── export.py │ ├── preflight.py │ └── shelf.py ├── poetry.lock ├── poetry.toml └── pyproject.toml /.githooks/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PROD_DIR=/groups/dungeons/pipeline 6 | LOCAL_DIR=$(git rev-parse --show-toplevel) 7 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 8 | 9 | # Create symlink if Axiom folder does not exist 10 | if [[ ! -d "${LOCAL_DIR}/pipeline/lib/axiom" ]]; then 11 | ln -s "${PROD_DIR}/pipeline/lib/axiom" "${LOCAL_DIR}/pipeline/lib/axiom" 12 | fi 13 | 14 | # Update the env file 15 | if [[ "${PROD_DIR}" != "${LOCAL_DIR}" ]]; then 16 | cp "${PROD_DIR}/pipeline/env.py" "${LOCAL_DIR}/pipeline/env.py" 17 | cp "${PROD_DIR}/pipeline/env_sg.py" "${LOCAL_DIR}/pipeline/env_sg.py" 18 | fi 19 | 20 | # Update the venv and set it up if it doesn't exist 21 | source "${LOCAL_DIR}/.githooks/update-venv" 22 | 23 | # copy the pin shift tool over 24 | if [[ "${PROD_DIR}" != "${LOCAL_DIR}" ]]; then 25 | cp "${PROD_DIR}/pipeline/software/maya/scripts/pin_shift.py" \ 26 | "${LOCAL_DIR}/pipeline/software/maya/scripts/pin_shift.py" 27 | fi 28 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PROD_DIR=/groups/dungeons/pipeline 6 | LOCAL_DIR=$(git rev-parse --show-toplevel) 7 | 8 | source "${LOCAL_DIR}/.githooks/update-venv" 9 | 10 | if [[ "${PROD_DIR}" != "${LOCAL_DIR}" ]]; then 11 | echo "Static type checking (mypy)..." 12 | mypy 13 | fi 14 | 15 | echo "Linting (ruff)..." 16 | ruff check "${LOCAL_DIR}" 17 | echo "Formatting (ruff)..." 18 | ruff format "${LOCAL_DIR}" 19 | -------------------------------------------------------------------------------- /.githooks/update-venv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Updating dev venv..." 3 | 4 | command -v pipx || { 5 | python3 -m pip install --user pipx && python3 -m pipx ensurepath 6 | } 7 | command -v poetry || pipx install poetry 8 | pipx upgrade poetry 9 | 10 | PROD_DIR=/groups/dungeons/pipeline 11 | LOCAL_DIR=$(git rev-parse --show-toplevel) 12 | 13 | if [[ "${PROD_DIR}" == "${LOCAL_DIR}" ]]; then 14 | poetry sync --without dev 15 | else 16 | poetry sync 17 | fi 18 | 19 | if [ -z "${VIRTUAL_ENV}" ]; then 20 | source "${LOCAL_DIR}/.venv/bin/activate" 21 | fi 22 | 23 | cat "${LOCAL_DIR}/.githooks/shotgun.pyi" > "${LOCAL_DIR}/pipeline/lib/shotgun_api3/shotgun_api3/__init__.pyi" 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore IDE files 2 | **/.idea/ 3 | **/.vscode/ 4 | **/.venv/ 5 | 6 | # Ignore temporary files 7 | **/__pycache__/ 8 | **/__private/ 9 | **.ruff_cache/ 10 | **.backup/ 11 | *.pyc 12 | *.swp 13 | *.bak 14 | *_bak*.hdanc 15 | 16 | # Ignore large binary files 17 | *.bgeo.sc 18 | *.tex 19 | *.usd 20 | *.usdc 21 | *.vdb 22 | 23 | # Ignore keys 24 | env.py 25 | env_sg.py 26 | 27 | # Ignore proprietary plugins 28 | pipeline/lib/axiom 29 | 30 | # Ignore third-party Python libraries 31 | pipeline/lib/python 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pipeline/lib/shotgun_api3"] 2 | path = pipeline/lib/shotgun_api3 3 | url = https://github.com/shotgunsoftware/python-api.git 4 | ignore = dirty 5 | [submodule "pipeline/software/maya/scripts/rjg"] 6 | path = pipeline/software/maya/scripts/rjg 7 | url = https://github.com/jgashler/rjg 8 | ignore = dirty 9 | [submodule "pipeline/software/maya/scripts/dwpicker_git"] 10 | path = pipeline/software/maya/scripts/dwpicker_git 11 | url = https://github.com/DreamWall-Animation/dwpicker.git 12 | [submodule "pipeline/lib/MOPS"] 13 | path = pipeline/lib/MOPS 14 | url = https://github.com/toadstorm/MOPS.git 15 | ignore = dirty 16 | [submodule "pipeline/lib/VFX-LYNX"] 17 | path = pipeline/lib/VFX-LYNX 18 | url = https://github.com/LucaScheller/VFX-LYNX.git 19 | [submodule "pipeline/software/nuke/tools/NukeSurvivalToolkit_publicRelease"] 20 | path = pipeline/software/nuke/tools/NukeSurvivalToolkit_publicRelease 21 | url = https://github.com/CreativeLyons/NukeSurvivalToolkit_publicRelease.git 22 | [submodule "pipeline/software/maya/scripts/studiolibrary"] 23 | path = pipeline/software/maya/scripts/studiolibrary 24 | url = https://github.com/krathjen/studiolibrary.git 25 | [submodule "pipeline/software/maya/scripts/modelChecker_git"] 26 | path = pipeline/software/maya/scripts/modelChecker_git 27 | url = https://github.com/JakobJK/modelChecker.git 28 | [submodule "pipeline/software/maya/scripts/maya-timeline-marker_git"] 29 | path = pipeline/software/maya/scripts/maya-timeline-marker_git 30 | url = https://github.com/scottdmilner/maya-timeline-marker.git 31 | [submodule "pipeline/software/maya/scripts/maya-capture"] 32 | path = pipeline/software/maya/scripts/mayacapture 33 | url = https://github.com/scottdmilner/maya-capture.git 34 | [submodule "pipeline/software/maya/scripts/mayacapture"] 35 | url = https://github.com/scottdmilner/maya-capture.git 36 | [submodule "pipeline/lib/tractor-lops"] 37 | path = pipeline/lib/tractor-lops 38 | url = https://github.com/scottdmilner/tractor-lops.git 39 | [submodule "pipeline/software/houdini/hsite/ae_SVG"] 40 | path = pipeline/software/houdini/hsite/ae_SVG 41 | url = https://github.com/takavfx/ae_SVG.git 42 | -------------------------------------------------------------------------------- /Dungance Designer.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/Dungance Designer.lnk -------------------------------------------------------------------------------- /Dungance Painter.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/Dungance Painter.lnk -------------------------------------------------------------------------------- /Dungaya.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/Dungaya.lnk -------------------------------------------------------------------------------- /Dungaya.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | nohup /usr/bin/env python pipeline maya >> "${TMPDIR}/pipe-launch.log" 2>&1 & 3 | sleep 1 4 | exit 0 5 | -------------------------------------------------------------------------------- /Dungini.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/Dungini.lnk -------------------------------------------------------------------------------- /Dungini.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | nohup /usr/bin/env python pipeline houdini >> "${TMPDIR}/pipe-launch.log" 2>&1 & 3 | sleep 1 4 | exit 0 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Pipeline Team, BYU Animation Class of 2025 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Nungeon.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/Nungeon.lnk -------------------------------------------------------------------------------- /Nungeon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | nohup /usr/bin/env python pipeline nuke >> "${TMPDIR}/pipe-launch.log" 2>&1 & 3 | sleep 1 4 | exit 0 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dungeon-pipeline 2 | 3 | An OS-agnostic, portable, extensible 3D pipeline for the BYU Center for Animation's 2025 Capstone film, *Love & Dungeons*. 4 | 5 | `dungeon-pipeline` is currently being used on EL8 and Windows 10 systems. It should also be functional on macOS systems, but that has not been tested. 6 | 7 | ## Repo structure 8 | ``` 9 | dungeon-pipeline/ 10 | ├── Dungance Painter.lnk # Linux and Windows launchers for DCCs. 11 | ├── Dungaya.desktop # These are at the root so they're easy for artists to locate 12 | ├── ... 13 | ├── LICENSE 14 | ├── pipeline 15 | │   ├── env.py.md # How to build set up env.py 16 | │   ├── lib # Python libraries and other resource files 17 | │   ├── __main__.py 18 | │   ├── pipe # Python module for code that is imported and run from the DCC 19 | │   ├── shared # Utilities used by `pipe` and `software` modules 20 | │   └── software # Module called by `__main__.py` to initialize environments and launch DCCs 21 | ├── pyproject.toml 22 | └── README.md 23 | ``` 24 | 25 | ## Setting up a copy of `dungeon-pipeline` 26 | 1. Fork this repo and clone it to the production location. 27 | 1. Create an `pipeline/env.py` file following the specifications in `pipeline/env.py.md`. This will get things like ShotGrid auth set up, and provide OS-specific DCC executable paths. 28 | 1. Install needed python libraries into `pipeline/lib/python/any`. (This will soon by managed via Poetry (Issue #137), for now see the list in `.githooks/setup-venv.sh`). 29 | 1. Clone branches for development locally, copy over the env files and get to work! 30 | 31 | ## Setting up a dev environment in the labs 32 | 1. Generate a GitHub SSH key and upload it to your GitHub 33 | - ```bash 34 | ssh-keygen -t ed25519 -C "yourgithubemail@email.com" 35 | cat ~/.ssh/github.pub 36 | ``` 37 | - When it asks for a path, type '/users/animation/yournetid/.ssh/github' 38 | - Only provide a passphrase if you want to type that every time you push or pull 39 | - Go to https://github.com/settings/keys and add the contents of `~/.ssh/github.pub` as a **New SSH key** 40 | 1. Make a local copy of the git repo 41 | ```bash 42 | cd ~/Documents 43 | git clone --recurse-submodules -c core.sshCommand='ssh -i ~/.ssh/github' git@github.com:scottdmilner/dungeon-pipeline.git 44 | cd dungeon-pipeline 45 | ``` 46 | 1. Configure the git repo to use the new SSH key and our git hooks 47 | ```bash 48 | git config --add --local core.sshCommand 'ssh -i ~/.ssh/github' 49 | git config --local core.hooksPath .githooks/ 50 | ``` 51 | 1. Check out a dev branch for the feature you are working on (or create a general dev branch (`yourname-dev`)) 52 | ```bash 53 | git checkout -B feature-name-yourname 54 | # don't need -B if it already exists 55 | git push --set-upstream origin feature-name-yourname 56 | ``` 57 | 58 | ## Code Style 59 | 60 | For this project, we are using the Black style of Python formatting. There is a Git pre-commit hook that will automatically run the `ruff` formatter on your code whenever you make a commit. If it changes any of your formatting it will print a message that looks like this: 61 | 62 | ``` 63 | Formatting with ruff... 64 | 3 files reformatted, 12 files left unchanged 65 | ``` 66 | 67 | After that, you can amend your commit to include the changes that `ruff` made with 68 | 69 | ```bash 70 | git add 71 | git commit --amend 72 | ``` 73 | 74 | This should generally be avoided, but if you need to override the Black style for some reason, use the following comments to suppress `ruff`: 75 | 76 | ```python 77 | ... 78 | # fmt: off 79 | unformatted code here 80 | # fmt: on 81 | ... 82 | ``` 83 | -------------------------------------------------------------------------------- /desktop-files/Dungaya.desktop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xdg-open 2 | [Desktop Entry] 3 | Version=0.1 4 | Name=Dungaya 5 | Name[en_US]=Dungaya 6 | Comment=Love & Dungeon's Maya environment 7 | Comment[en_US.UTF-8]=Love & Dungeon's Maya environment 8 | Exec=/groups/dungeons/pipeline/pipeline/__main__.py maya 9 | Icon=/groups/dungeons/pipeline/pipeline/lib/icon/maya.png 10 | Terminal=false 11 | Type=Application 12 | Categories=Utility;Application; 13 | -------------------------------------------------------------------------------- /desktop-files/Dungini.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=Dungini 4 | Name[en_US]=Dungini 5 | Comment=Love & Dungeon's Houdini environment 6 | Comment[en_US.UTF-8]=Love & Dungeon's Houdini environment 7 | Exec=/groups/dungeons/pipeline/pipeline/__main__.py houdini 8 | Icon=/groups/dungeons/pipeline/pipeline/lib/icon/houdini.png 9 | Terminal=false 10 | Type=Application 11 | Categories=Utility; 12 | 13 | -------------------------------------------------------------------------------- /desktop-files/Nungeon.desktop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xdg-open 2 | [Desktop Entry] 3 | Version=0.1 4 | Name=Nungeon 5 | Name[en_US]=Nungeon 6 | Comment=Love & Dungeon's Nuke environment 7 | Comment[en_US.UTF-8]=Love & Dungeon's Nuke environment 8 | Exec=/groups/dungeons/pipeline/pipeline/__main__.py nuke 9 | Icon=/groups/dungeons/pipeline/pipeline/lib/icon/nuke.png 10 | Terminal=false 11 | Type=Application 12 | Categories=Utility;Application; 13 | -------------------------------------------------------------------------------- /pipeline/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import annotations 3 | 4 | import logging 5 | import site 6 | import sys 7 | 8 | from argparse import ArgumentParser 9 | 10 | from shared.util import find_implementation 11 | from software.interface import DCCInterface 12 | 13 | r"""Launch the BYU 2025 Capstone pipeline ("Love & Dungeons") 14 | 15 | With much credit to Matthew Minson and the 2024 Capstone team. 16 | 17 | When run as a script, parse the software from the command line 18 | arguments, then run launch(). 19 | """ 20 | 21 | 22 | # Configure logging 23 | log = logging.getLogger(__name__) 24 | 25 | 26 | def getLevelNamesMapping(): 27 | """Implement the same-named method from the logging module. 28 | 29 | TODO: REPLACE ONCE OUR PYTHON IS >= 3.11 30 | """ 31 | return logging._nameToLevel.keys() 32 | 33 | 34 | def launch( 35 | software_name: str, 36 | is_python_shell: bool = False, 37 | extra_args: list[str] | None = None, 38 | ) -> None: 39 | if sys.platform == "linux": 40 | # raise file descriptor limit for the enclosing Python process 41 | import resource 42 | 43 | _, max_fd = resource.getrlimit(resource.RLIMIT_NOFILE) 44 | resource.setrlimit(resource.RLIMIT_NOFILE, (max_fd, max_fd)) 45 | 46 | software = find_implementation(DCCInterface, f"software.{software_name}") 47 | software(is_python_shell, extra_args).launch() 48 | 49 | 50 | if __name__ == "__main__": 51 | parser = ArgumentParser(description="Launch pipeline software") 52 | parser.add_argument( 53 | "software", 54 | help="launch the specified software", 55 | ) 56 | parser.add_argument( 57 | "-l", 58 | "--log-level", 59 | help="log at the specified level. Possible values are %(choices)s (default: %(default)s)", 60 | choices=getLevelNamesMapping(), 61 | default=logging.getLevelName(logging.root.level), 62 | type=str.upper, 63 | metavar="LEVEL", 64 | ) 65 | parser.add_argument( 66 | "-p", 67 | "--python", 68 | help="Open a Python shell in this DCC instead of launching the GUI", 69 | action="store_true", 70 | ) 71 | 72 | args, extras = parser.parse_known_args() 73 | 74 | logging.basicConfig( 75 | level=args.log_level, 76 | format="%(asctime)s %(processName)s(%(process)s) %(threadName)s [%(name)s(%(lineno)s)] [%(levelname)s] %(message)s", 77 | ) 78 | 79 | # Windows Python explicitly needs site.main to be called 80 | site.main() 81 | 82 | launch(args.software, args.python, extras) 83 | 84 | log.info("Exiting") 85 | -------------------------------------------------------------------------------- /pipeline/env.py.md: -------------------------------------------------------------------------------- 1 | # `env.py` Contents 2 | 3 | In this folder the pipeline expects a file named `env.py` with the following 4 | defined variables to properly function. Note that they will need to be defined 5 | conditionally based off of operating system for functionality on multiple 6 | operating systems. 7 | 8 | ```python 9 | import platform 10 | 11 | from dataclasses import dataclass 12 | from pathlib import Path 13 | 14 | production_path: Path # absolute path to this repository (ie /groups/project/dungeon-pipeline) 15 | 16 | @dataclass 17 | class Executables: 18 | hfs: Path # absolute path to the Houdini HFS folder (ie /opt/hfs19.5.640) 19 | houdini: Path # absolute path to the Houdini binary 20 | hython: Path # absolute path to the Hython binary 21 | mayabin: Path # absolute path to the Maya bin folder (ie /usr/autodesk/maya2024/bin) 22 | maya: Path # absolute path to the Maya executable 23 | mayapy: Path # absolute path to the mayapy executable 24 | nukedir: Path # absolute path to the Nuke installation dir (ie /opt/Nuke14.0v5) 25 | nuke: Path # absolute path to the Nuke executable 26 | nuke_python: Path # absolute path to the Nuke python executable 27 | oiiotool: Path # absolute path to the oiiotool executable (such as the one bundled with Houdini) 28 | substance_designer: Path # absolute path to the Substance Designer executable 29 | substance_painter: Path # absolute path to the Substance Painter executable 30 | txmake: Path # absolute path to the txmake execuatable (such as the one bundled with RenderMan) 31 | 32 | 33 | @dataclass 34 | class SG_Config: 35 | project_id: int 36 | # DO NOT SHARE/COMMIT THE sg_key!!! IT'S EQUIVALENT TO AN ADMIN PW!!! 37 | sg_key: str 38 | sg_script: str 39 | sg_server: str 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /pipeline/lib/hdri/STUDIO_DIFFUSE_4k.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/hdri/STUDIO_DIFFUSE_4k.hdr -------------------------------------------------------------------------------- /pipeline/lib/icon/chameleon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/chameleon.png -------------------------------------------------------------------------------- /pipeline/lib/icon/envelope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/envelope.png -------------------------------------------------------------------------------- /pipeline/lib/icon/houdini.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/houdini.ico -------------------------------------------------------------------------------- /pipeline/lib/icon/houdini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/houdini.png -------------------------------------------------------------------------------- /pipeline/lib/icon/material-help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pipeline/lib/icon/maya.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/maya.ico -------------------------------------------------------------------------------- /pipeline/lib/icon/maya.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/maya.png -------------------------------------------------------------------------------- /pipeline/lib/icon/monocle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/monocle.png -------------------------------------------------------------------------------- /pipeline/lib/icon/nuke.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/nuke.ico -------------------------------------------------------------------------------- /pipeline/lib/icon/nuke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/nuke.png -------------------------------------------------------------------------------- /pipeline/lib/icon/rig_arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/rig_arm.png -------------------------------------------------------------------------------- /pipeline/lib/icon/robin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/robin.png -------------------------------------------------------------------------------- /pipeline/lib/icon/skull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/icon/skull.png -------------------------------------------------------------------------------- /pipeline/lib/ocio/love-v01/transforms/EGamut_2_XYZ.spimtx: -------------------------------------------------------------------------------- 1 | 0.742167 0.172592 0.085241 0.0 2 | 0.280131 0.820207 -0.100337 0.0 3 | -0.095295 -0.066945 1.162240 0.0 -------------------------------------------------------------------------------- /pipeline/lib/sbs/normal2height.sbsar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/sbs/normal2height.sbsar -------------------------------------------------------------------------------- /pipeline/lib/splash/MayaEDUStartupImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/splash/MayaEDUStartupImage.png -------------------------------------------------------------------------------- /pipeline/lib/splash/MayaEDUStartupImage_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/splash/MayaEDUStartupImage_150.png -------------------------------------------------------------------------------- /pipeline/lib/splash/MayaEDUStartupImage_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/splash/MayaEDUStartupImage_200.png -------------------------------------------------------------------------------- /pipeline/lib/splash/dunginisplash19.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/splash/dunginisplash19.5.png -------------------------------------------------------------------------------- /pipeline/lib/tex/Reflective-Macbeth-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/tex/Reflective-Macbeth-chart.png -------------------------------------------------------------------------------- /pipeline/lib/usd/kinds/config/Icons/SCENEGRAPH_kind_character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/lib/usd/kinds/config/Icons/SCENEGRAPH_kind_character.png -------------------------------------------------------------------------------- /pipeline/lib/usd/kinds/plugInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "Plugins":[ 3 | { 4 | "Type": "python", 5 | "Name": "dungeonKinds", 6 | "Info":{ 7 | "Kinds":{ 8 | "character":{ 9 | "baseKind":"component", 10 | "description":"A (usually animated) character" 11 | }, 12 | "fx":{ 13 | "baseKind":"component", 14 | "description":"A FX asset" 15 | }, 16 | "set":{ 17 | "baseKind":"assembly", 18 | "description":"A individual section of an environment, typically a room or hallway." 19 | } 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /pipeline/pipe/__init__.py: -------------------------------------------------------------------------------- 1 | import logging as _l 2 | from os import environ as _e 3 | 4 | from . import db 5 | from . import glui 6 | from . import struct 7 | from . import texconverter 8 | from . import util 9 | 10 | __all__ = [ 11 | "db", 12 | "glui", 13 | "struct", 14 | "texconverter", 15 | "util", 16 | ] 17 | 18 | # import DCC-specific modules 19 | from os import getenv as _getenv 20 | 21 | _dcc = _getenv("DCC", "") 22 | 23 | if _dcc == "houdini": 24 | from . import h 25 | 26 | __all__ += ["h"] 27 | 28 | elif _dcc == "maya": 29 | from . import m 30 | 31 | __all__ += ["m"] 32 | 33 | elif _dcc == "substance_painter": 34 | from . import sp 35 | 36 | __all__ += ["sp"] 37 | 38 | # configure logging 39 | _log = _l.getLogger(__name__) 40 | _l.basicConfig( 41 | level=int(_e.get("PIPE_LOG_LEVEL") or 0), 42 | format="%(asctime)s %(processName)s(%(process)s) %(threadName)s [%(name)s(%(lineno)s)] [%(levelname)s] %(message)s", 43 | ) 44 | -------------------------------------------------------------------------------- /pipeline/pipe/db/__init__.py: -------------------------------------------------------------------------------- 1 | from .interface import DBInterface 2 | from .sgaadb import SGaaDB as DB, SG_Config as Config 3 | 4 | __all__ = [ 5 | "Config", 6 | "DB", 7 | "DBInterface", 8 | ] 9 | -------------------------------------------------------------------------------- /pipeline/pipe/db/shotgun_api3: -------------------------------------------------------------------------------- 1 | ../../lib/shotgun_api3/shotgun_api3 -------------------------------------------------------------------------------- /pipeline/pipe/db/typing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Iterable, Literal, Optional, Protocol, TypedDict, Union 4 | from typing_extensions import NotRequired, Unpack 5 | 6 | from pipe.struct.db import ( 7 | Asset, 8 | AssetStub, 9 | Environment, 10 | EnvironmentStub, 11 | Sequence, 12 | SequenceStub, 13 | SGEntity, 14 | Shot, 15 | ShotStub, 16 | ) 17 | 18 | from .interface import DBInterface 19 | 20 | TimeUnit = Literal["HOUR", "DAY", "WEEK", "MONTH", "YEAR"] 21 | BasicFilter = Union[ 22 | tuple[ 23 | str, 24 | Literal[ 25 | "is", "is_not", "less_than", "greater_than", "contains", "not_contains" 26 | ], 27 | Any, 28 | ], 29 | tuple[str, Literal["starts_with", "ends_with"], str], 30 | tuple[str, Literal["between", "not_between"], Any, Any], 31 | tuple[str, Literal["in_last", "in_next"], int, TimeUnit], 32 | tuple[str, Literal["in"], list[Any]], 33 | tuple[str, Literal["type_is", "type_is_not"], Optional[str]], 34 | tuple[ 35 | str, Literal["in_calendar_day", "in_calendar_week", "in_calendar_month"], int 36 | ], 37 | tuple[ 38 | str, 39 | Literal[ 40 | "name_contains", "name_not_contains", "name_starts_with", "name_ends_with" 41 | ], 42 | str, 43 | ], 44 | ] 45 | 46 | 47 | class ComplexFilter(TypedDict): 48 | filter_operator: Literal["any", "all"] 49 | filters: list[BasicFilter] 50 | 51 | 52 | Filter = Union[BasicFilter, ComplexFilter] 53 | 54 | 55 | class AttrMappingKwargs(TypedDict): 56 | child_mode: NotRequired[DBInterface.ChildQueryMode] 57 | 58 | 59 | class T_GetAssetAttrList(Protocol): 60 | def __call__( 61 | self, 62 | attr: str, 63 | *, 64 | sorted: bool = False, 65 | child_mode: DBInterface.ChildQueryMode = DBInterface.ChildQueryMode.LEAVES, 66 | ) -> list[str]: ... 67 | 68 | 69 | class T_GetAssetByAttr(Protocol): 70 | def __call__(self, attr: str, attr_val: str | int) -> Asset: ... 71 | 72 | 73 | class T_GetAssetById(Protocol): 74 | def __call__(self, id: int) -> Asset: ... 75 | 76 | 77 | class T_GetAssetByName(Protocol): 78 | def __call__(self, name: str) -> Asset: ... 79 | 80 | 81 | class T_GetAssetByStub(Protocol): 82 | def __call__(self, stub: AssetStub) -> Asset: ... 83 | 84 | 85 | class T_GetAssetNameList(Protocol): 86 | def __call__( 87 | self, 88 | child_mode: DBInterface.ChildQueryMode = DBInterface.ChildQueryMode.LEAVES, 89 | sorted: bool = False, 90 | ) -> list[str]: ... 91 | 92 | 93 | class T_GetAssetsByStub(Protocol): 94 | def __call__(self, stubs: Iterable[AssetStub]) -> list[Asset]: ... 95 | 96 | 97 | class T_GetAttrList(Protocol): 98 | def __call__(self, attr: str, *, sorted: bool = False) -> list[str]: ... 99 | 100 | 101 | class T_GetCodeList(Protocol): 102 | def __call__( 103 | self, 104 | *, 105 | sorted: bool = False, 106 | child_mode: DBInterface.ChildQueryMode = DBInterface.ChildQueryMode.LEAVES, 107 | ) -> list[str]: ... 108 | 109 | 110 | class T_GetEntityByCode(Protocol): 111 | def __call__(self, entity_type: type[SGEntity], code: str) -> SGEntity: ... 112 | 113 | 114 | class T_GetEntityCodeList(Protocol): 115 | def __call__( 116 | self, 117 | entity_type: type[SGEntity], 118 | *, 119 | sorted: bool = False, 120 | **kwargs: Unpack[AttrMappingKwargs], 121 | ) -> list[str]: ... 122 | 123 | 124 | class T_GetEnvByAttr(Protocol): 125 | def __call__(self, attr: str, attr_val: str | int) -> Environment: ... 126 | 127 | 128 | class T_GetEnvByCode(Protocol): 129 | def __call__(self, code: str) -> Environment: ... 130 | 131 | 132 | class T_GetEnvById(Protocol): 133 | def __call__(self, id: int) -> Environment: ... 134 | 135 | 136 | class T_GetEnvByStub(Protocol): 137 | def __call__(self, stub: EnvironmentStub) -> Environment: ... 138 | 139 | 140 | class T_GetEnvsByStub(Protocol): 141 | def __call__(self, stubs: Iterable[EnvironmentStub]) -> list[Environment]: ... 142 | 143 | 144 | class T_GetSeqByAttr(Protocol): 145 | def __call__(self, attr: str, attr_val: str | int) -> Sequence: ... 146 | 147 | 148 | class T_GetSeqByCode(Protocol): 149 | def __call__(self, code: str) -> Sequence: ... 150 | 151 | 152 | class T_GetSeqById(Protocol): 153 | def __call__(self, id: int) -> Sequence: ... 154 | 155 | 156 | class T_GetSeqByStub(Protocol): 157 | def __call__(self, stub: SequenceStub) -> Sequence: ... 158 | 159 | 160 | class T_GetSeqsByStub(Protocol): 161 | def __call__(self, stubs: Iterable[SequenceStub]) -> list[Sequence]: ... 162 | 163 | 164 | class T_GetShotByAttr(Protocol): 165 | def __call__(self, attr: str, attr_val: str | int) -> Shot: ... 166 | 167 | 168 | class T_GetShotByCode(Protocol): 169 | def __call__(self, code: str) -> Shot: ... 170 | 171 | 172 | class T_GetShotById(Protocol): 173 | def __call__(self, id: int) -> Shot: ... 174 | 175 | 176 | class T_GetShotByStub(Protocol): 177 | def __call__(self, stub: ShotStub) -> Shot: ... 178 | 179 | 180 | class T_GetShotsByStub(Protocol): 181 | def __call__(self, stubs: Iterable[ShotStub]) -> list[Shot]: ... 182 | -------------------------------------------------------------------------------- /pipeline/pipe/glui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/pipe/glui/__init__.py -------------------------------------------------------------------------------- /pipeline/pipe/h/__init__.py: -------------------------------------------------------------------------------- 1 | from . import hipfile as hipfile 2 | from . import local as local 3 | from . import shading as shading 4 | -------------------------------------------------------------------------------- /pipeline/pipe/h/animpostprocess.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hou 4 | 5 | from abc import ABCMeta, abstractmethod 6 | from typing import TYPE_CHECKING 7 | 8 | from pipe.db import DB 9 | from pipe.h.hipfile import HShotFileManager 10 | from env_sg import DB_Config 11 | 12 | if TYPE_CHECKING: 13 | pass 14 | 15 | 16 | class PostProcessor(metaclass=ABCMeta): 17 | _conn: DB 18 | 19 | def __init__(self): 20 | self._conn = DB(DB_Config) 21 | 22 | @abstractmethod 23 | def run(self, shot_code: str) -> None: 24 | pass 25 | 26 | 27 | class AnimPostProcessor(PostProcessor): 28 | def run(self, shot_code: str) -> None: 29 | # Set up 30 | shot = self._conn.get_shot_by_code(shot_code) 31 | hou.playbar.setFrameRange(shot.cut_in - 5, shot.cut_out + 5) 32 | hou.playbar.setPlaybackRange(shot.cut_in - 5, shot.cut_out + 5) 33 | 34 | stage_ctx: hou.Node = hou.node("/stage") # type: ignore[assignment] 35 | 36 | load_layer = stage_ctx.createNode("sdm223::main::LnD_Load_Layers::1.0") 37 | load_layer.parm("shot").set(f"$JOB/{shot.path}") # type: ignore[union-attr] 38 | 39 | for dep in ["cfx", "fx", "flo", "lighting"]: 40 | load_layer.parm(f"{dep}_enable").set(0) # type: ignore[union-attr] 41 | 42 | if env_stub := (shot.set or self._conn.get_sequence_by_stub(shot.sequence).set): # type: ignore[arg-type] 43 | layout = self._conn.get_env_by_stub(env_stub) 44 | load_layer.parm("layout_path").set(f"$JOB/{layout.path}/main.usd") # type: ignore[union-attr] 45 | 46 | layer_break = stage_ctx.createNode("layerbreak") 47 | 48 | postprocess = stage_ctx.createNode("sdm222::lnd_anim_postprocess::1.0") 49 | 50 | publish = stage_ctx.createNode("usd_rop") 51 | 52 | publish.parm("trange").set("normal") # type: ignore[union-attr] 53 | publish.parm("lopoutput").set(f"$JOB/{shot.path}/anim/usd/post-process.usd") # type: ignore[union-attr] 54 | publish.parm("savestyle").set("flattenalllayers") # type: ignore[union-attr] 55 | 56 | layer_break.setInput(0, load_layer) 57 | postprocess.setInput(0, layer_break) 58 | publish.setInput(0, postprocess) 59 | 60 | publish.parm("execute").pressButton() # type: ignore[union-attr] 61 | 62 | 63 | class CfxPostProcessor(PostProcessor): 64 | def run(self, shot_code: str) -> None: 65 | HShotFileManager( 66 | override_dept="cfx", 67 | override_entity_code=shot_code, 68 | ignore_load_warnings=True, 69 | ).open_file() 70 | 71 | publish = hou.node("/stage/PUBLISH") 72 | publish.parm("execute").pressButton() # type: ignore[union-attr] 73 | -------------------------------------------------------------------------------- /pipeline/pipe/h/hipfile/__init__.py: -------------------------------------------------------------------------------- 1 | from .asset import HAssetFileManager as HAssetFileManager 2 | from .environment import HEnvFileManager as HEnvFileManager 3 | from .shot import HShotFileManager as HShotFileManager 4 | -------------------------------------------------------------------------------- /pipeline/pipe/h/hipfile/asset.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hou 4 | import logging 5 | from pathlib import Path 6 | from typing import cast 7 | 8 | from pipe.struct.db import Asset, SGEntity 9 | 10 | from .filemanager import HFileManager 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class HAssetFileManager(HFileManager): 16 | def __init__(self, ignore_load_warnings: bool = False) -> None: 17 | super().__init__(Asset, ignore_load_warnings=ignore_load_warnings) 18 | 19 | def _generate_filename_ext(self, entity) -> tuple[str, str]: 20 | asset = cast(Asset, entity) 21 | return asset.name, "hipnc" 22 | 23 | def _setup_file(self, path: Path, entity: SGEntity) -> None: 24 | super(HAssetFileManager, HAssetFileManager)._setup_file(self, path, entity) 25 | 26 | hip_path = Path(hou.hscriptStringExpression("$HIP")) 27 | hou.setContextOption("ASSET", hip_path.name) 28 | -------------------------------------------------------------------------------- /pipeline/pipe/h/hipfile/environment.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hou 4 | import logging 5 | from pathlib import Path 6 | from typing import cast 7 | 8 | from pipe.struct.db import Environment, SGEntity 9 | 10 | from .filemanager import HFileManager 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class HEnvFileManager(HFileManager): 16 | def __init__(self, ignore_load_warnings: bool = False) -> None: 17 | super().__init__(Environment, ignore_load_warnings=ignore_load_warnings) 18 | 19 | def _generate_filename_ext(self, entity) -> tuple[str, str]: 20 | env = cast(Environment, entity) 21 | return env.name, "hipnc" 22 | 23 | def _setup_file(self, path: Path, entity: SGEntity) -> None: 24 | super(HEnvFileManager, HEnvFileManager)._setup_file(self, path, entity) 25 | 26 | hip_path = Path(hou.hscriptStringExpression("$HIP")) 27 | hou.setContextOption("ENVIRON", hip_path.name) 28 | -------------------------------------------------------------------------------- /pipeline/pipe/h/hipfile/filemanager.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hou 4 | import logging 5 | from pathlib import Path 6 | 7 | import pipe.h 8 | from pipe.db import DB 9 | from pipe.struct.db import SGEntity 10 | from pipe.util import FileManager 11 | 12 | from env_sg import DB_Config 13 | 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | class HFileManager(FileManager): 18 | _ignore_load_warnings: bool 19 | 20 | def __init__( 21 | self, 22 | entity_type: type[SGEntity], 23 | versioning: bool = False, 24 | version_glob: str = "", 25 | override_entity_code: str | None = None, 26 | ignore_load_warnings: bool = False, 27 | ) -> None: 28 | conn = DB.Get(DB_Config, auto_update=False) 29 | window = pipe.h.local.get_main_qt_window() 30 | self._ignore_load_warnings = ignore_load_warnings 31 | super().__init__( 32 | conn, 33 | entity_type, 34 | window, 35 | versioning=versioning, 36 | version_glob=version_glob, 37 | override_entity_code=override_entity_code, 38 | ) 39 | 40 | def _check_unsaved_changes(self) -> bool: 41 | if self._override_entity_code: 42 | return True 43 | 44 | if hou.hipFile.hasUnsavedChanges(): 45 | warning_response = hou.ui.displayMessage( 46 | "The current file has not been saved. Continue anyways?", 47 | buttons=("Continue", "Cancel"), 48 | severity=hou.severityType.ImportantMessage, 49 | default_choice=1, 50 | ) 51 | if warning_response == 1: 52 | return False 53 | return True 54 | 55 | def _open_file(self, path: Path) -> None: 56 | hou.hipFile.load( 57 | str(path), 58 | suppress_save_prompt=True, 59 | ignore_load_warnings=self._ignore_load_warnings, 60 | ) 61 | 62 | def _setup_file(self, path: Path, entity: SGEntity) -> None: 63 | hou.hipFile.clear(suppress_save_prompt=True) 64 | hou.hipFile.save(str(path)) 65 | -------------------------------------------------------------------------------- /pipeline/pipe/h/local.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hou 4 | import re 5 | import sys 6 | 7 | from Qt import QtWidgets 8 | 9 | from software.baseclass import DCCLocalizer 10 | 11 | 12 | class _HoudiniLocalizer(DCCLocalizer): 13 | def __init__(self) -> None: 14 | super().__init__("houdini") 15 | 16 | def get_main_qt_window(self) -> QtWidgets.QWidget | None: 17 | if not self.is_headless(): 18 | return hou.qt.mainWindow() 19 | return None 20 | 21 | def is_headless(self) -> bool: 22 | return bool(re.match(r"^.*ython(?:\.exe)?3?", sys.executable)) 23 | 24 | 25 | _l = _HoudiniLocalizer() 26 | 27 | get_main_qt_window = _l.get_main_qt_window 28 | is_headless = _l.is_headless 29 | -------------------------------------------------------------------------------- /pipeline/pipe/h/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .parmdata import parmfield as parmfield 2 | from .parmdata import ParmData as ParmData 3 | -------------------------------------------------------------------------------- /pipeline/pipe/h/util/parmdata.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | import hou 8 | from typing import Any, Protocol 9 | 10 | class T_ParmData(Protocol): 11 | _node: hou.Node 12 | 13 | 14 | def get_eval_fn(typ: str) -> str: 15 | if typ == "str": 16 | eval_fn = "evalAsString" 17 | elif typ == "float": 18 | eval_fn = "evalAsFloat" 19 | elif typ == "int": 20 | eval_fn = "evalAsInt" 21 | else: 22 | raise AttributeError(f"Unrecognized parm type: {typ}") 23 | return eval_fn 24 | 25 | 26 | def get_cast(typ: str) -> type: 27 | t_cast: type 28 | if typ == "str": 29 | t_cast = str 30 | elif typ == "float": 31 | t_cast = float 32 | elif typ == "int": 33 | t_cast = int 34 | else: 35 | raise AttributeError(f"Unrecognized parm type: {typ}") 36 | return t_cast 37 | 38 | 39 | def create_property( 40 | typ: str, parm: str, has_toggle: bool = False, writable: bool = False 41 | ) -> property: 42 | def getter(self: T_ParmData): 43 | return getattr(self._node.parm(parm), get_eval_fn(typ))() 44 | 45 | def getter_toggled(self: T_ParmData): 46 | if self._node.parm("toggle_" + parm).evalAsInt(): # type: ignore[union-attr] 47 | return getter(self) 48 | return None 49 | 50 | def setter(self: T_ParmData, value): 51 | self._node.parm(parm).set(get_cast(typ)(value)) # type: ignore[union-attr] 52 | 53 | return property( 54 | getter_toggled if has_toggle else getter, setter if writable else None 55 | ) 56 | 57 | 58 | @dataclass 59 | class parmfield: 60 | has_toggle: bool = False 61 | writable: bool = False 62 | 63 | 64 | class ParmData(type): 65 | def __new__(cls, name, bases, attrs: dict) -> ParmData: 66 | def __init__(self: T_ParmData, node: hou.Node) -> None: 67 | self._node = node 68 | for attr in self.__annotations__.keys(): 69 | if not node.parm(attr): 70 | raise AttributeError(f"Parm {attr} not found on node {node.name()}") 71 | 72 | new_attrs: dict[str, Any] = { 73 | cls.__init__.__name__: __init__, 74 | } 75 | 76 | annotations: dict[str, str] = attrs.get("__annotations__") # type: ignore[assignment] 77 | if annotations: 78 | for attr, typ in annotations.items(): 79 | has_toggle = False 80 | writable = False 81 | if isinstance(f := attrs.get(attr), parmfield): 82 | has_toggle = f.has_toggle 83 | writable = f.writable 84 | 85 | new_attrs[attr] = create_property(typ, attr, has_toggle, writable) 86 | 87 | return super().__new__(cls, name, bases, {**attrs, **new_attrs}) 88 | -------------------------------------------------------------------------------- /pipeline/pipe/m/AlexTesting/FaceBaseJointsAutoCreator.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="call-arg,arg-type" 2 | import maya.cmds as cmds 3 | 4 | 5 | def parentChain(chainList): 6 | i = 1 7 | while i < (len(chainList)): 8 | cmds.parent(chainList[i], chainList[i - 1]) 9 | i += 1 10 | 11 | 12 | def parentAll(list): 13 | try: 14 | for each in list: 15 | cmds.parent(each, list[0]) 16 | except Exception: 17 | print("oopsies") 18 | 19 | 20 | def locToJoint(headLocator): 21 | cmds.select(cl=True) 22 | 23 | def recursiveHelper(callOn, parentJointName): 24 | if cmds.listRelatives(callOn, c=True, type="transform") is not None: 25 | for each in cmds.listRelatives(str(callOn), c=True, type="transform"): 26 | translate = cmds.xform(each, query=True, ws=True, rotatePivot=True) 27 | 28 | cmds.select(cl=True) 29 | 30 | cmds.joint(p=(translate[0], translate[1], translate[2])) 31 | 32 | currentJointName = cmds.rename( 33 | cmds.ls(selection=True), str(each)[: (len(str(each)) - 5)] 34 | ) 35 | 36 | # HEY DUMDUM YOU NEED TO GIVE THE PARENT COMMAND THE NAME OF THE JOINTS NOT THE LOCATORS. 37 | # YOU SHOULD ALSO CREATE THIS AS A PARAMETER TO BE FED INTO THE RECURSIVE FUNCTION 38 | 39 | cmds.parent(currentJointName, parentJointName) 40 | 41 | recursiveHelper(each, currentJointName) 42 | 43 | translate = cmds.xform(str(headLocator), query=True, ws=True, rotatePivot=True) 44 | cmds.joint(p=(translate[0], translate[1], translate[2])) 45 | cmds.rename( 46 | cmds.ls(selection=True), str(headLocator)[: (len(str(headLocator)) - 5)] 47 | ) 48 | 49 | rootJointName = str(headLocator)[: (len(str(headLocator)) - 5)] 50 | 51 | if cmds.listRelatives(headLocator): 52 | recursiveHelper(headLocator, rootJointName) 53 | 54 | 55 | # if cmds.listRelatives("head_JNT_temp"): 56 | # print(cmds.listRelatives("head_JNT_temp", c = True, type = "transform")) 57 | 58 | 59 | tempList = [] 60 | 61 | cmds.spaceLocator(n="head_JNT_temp") 62 | cmds.xform(t=(0, 151.177, -4.038)) 63 | tempList.append(cmds.ls(selection=True)[0]) 64 | 65 | cmds.spaceLocator(n="jaw_JNT_temp") 66 | cmds.xform(t=(0, 148.835, -0.835)) 67 | tempList.append(cmds.ls(selection=True)[0]) 68 | 69 | cmds.spaceLocator(n="upperTeeth_JNT_temp") 70 | cmds.xform(t=(0, 149.432, 5.857)) 71 | tempList.append(cmds.ls(selection=True)[0]) 72 | 73 | cmds.spaceLocator(n="lowerTeeth_JNT_temp") 74 | cmds.xform(t=(0, 147.52, 5.738)) 75 | tempList.append(cmds.ls(selection=True)[0]) 76 | 77 | cmds.parent("upperTeeth_JNT_temp", "jaw_JNT_temp") 78 | cmds.parent("lowerTeeth_JNT_temp", "jaw_JNT_temp") 79 | cmds.parent("jaw_JNT_temp", "head_JNT_temp") 80 | 81 | 82 | locToJoint("head_JNT_temp") 83 | 84 | 85 | if cmds.about(nt=True): 86 | cmds.file( 87 | "G:\dungeons\character\Rigging\Rigs\RobinFace\Controls\RobinGlobalMouthControls.ma", 88 | i=True, 89 | ) 90 | if cmds.about(os=True) == "linux64": 91 | cmds.file( 92 | "/groups/dungeons/character/Rigging/Rigs/RobinFace/Controls/RobinGlobalMouthControls.ma", 93 | i=True, 94 | ) 95 | 96 | 97 | cmds.addAttr("jaw_ctrl", ln="LipInfluence", k=True, dv=1, min=0, max=2, at="double") 98 | 99 | cmds.parentConstraint("jaw_ctrl", "jaw_JNT") 100 | -------------------------------------------------------------------------------- /pipeline/pipe/m/ToolBox/Katie_Toolbox_GUI.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | from importlib import reload 3 | 4 | 5 | def grab_face_bind_joints(*args): 6 | import pipe.m.ToolBox.select_face_bind_joints 7 | 8 | reload( 9 | pipe.m.ToolBox.select_face_bind_joints 10 | ) # Optional: Reload the module if it has changed 11 | pipe.m.ToolBox.select_face_bind_joints.face_bind_selection() 12 | 13 | 14 | def mark_face_bind_joints(*args): 15 | import pipe.m.ToolBox.add_bind_joint_attribute 16 | 17 | reload( 18 | pipe.m.ToolBox.add_bind_joint_attribute 19 | ) # Optional: Reload the module if it has changed 20 | pipe.m.ToolBox.add_bind_joint_attribute.add_joints() 21 | 22 | 23 | def Button_updateLocators(*args): 24 | import pipe.m.ToolBox.Updating_Scripts 25 | 26 | reload( 27 | pipe.m.ToolBox.Updating_Scripts 28 | ) # Optional: Reload the module if it has changed 29 | pipe.m.ToolBox.Updating_Scripts.create_gui() 30 | 31 | 32 | def Button_eyelids(*args): 33 | import pipe.m.ToolBox.V2_EyeUIandBasics 34 | 35 | reload( 36 | pipe.m.ToolBox.V2_EyeUIandBasics 37 | ) # Optional: Reload the module if it has changed 38 | pipe.m.ToolBox.V2_EyeUIandBasics.createUI() 39 | 40 | 41 | def Button_eyesockets(*args): 42 | import pipe.m.ToolBox.EyeSocket_V1_01 43 | 44 | reload( 45 | pipe.m.ToolBox.EyeSocket_V1_01 46 | ) # Optional: Reload the module if it has changed 47 | pipe.m.ToolBox.EyeSocket_V1_01.eyesocket() 48 | 49 | 50 | def createUI(): 51 | import pipe.util as pu 52 | 53 | pu.reload_pipe() 54 | 55 | if cmds.window( 56 | "Katies_Toolbox", exists=True 57 | ): # Check if the window already exists and delete it if true 58 | cmds.deleteUI("Katies_Toolbox") 59 | 60 | window = cmds.window( 61 | "Katies_Toolbox", title="Katies_Toolbox", widthHeight=(200, 100) 62 | ) # Create a new window 63 | 64 | cmds.columnLayout(adj=True) # Create a layout for UI elements 65 | 66 | cmds.button(label="Mark as face bind joint", command=mark_face_bind_joints) 67 | cmds.button(label="Select face bind joints", command=grab_face_bind_joints) 68 | cmds.separator(height=10, style="none") # Separate 69 | cmds.button(label="Base Locators", command=Button_updateLocators) 70 | cmds.button(label="Eyeslids", command=Button_eyelids) 71 | cmds.button(label="Eyesockets", command=Button_eyesockets) 72 | 73 | cmds.showWindow(window) # Show the window 74 | 75 | 76 | createUI() 77 | -------------------------------------------------------------------------------- /pipeline/pipe/m/ToolBox/Updating_Scripts.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | 3 | 4 | # Function to create a locator at a specific position 5 | def create_locator(name, x, y, z): 6 | locator = cmds.spaceLocator(name=name)[0] 7 | cmds.move(x, y, z, locator) 8 | 9 | 10 | # Function to get the current position of the locator 11 | def get_locator_position(locator_name): 12 | pos = cmds.xform(locator_name, query=True, translation=True, worldSpace=True) 13 | return pos 14 | 15 | 16 | # Function to update the script with the new locator positions 17 | def update_script(): 18 | script_path = "pipeline.pipe.m.ToolBox.Robin_Face_Locators.py" 19 | with open(script_path, "w") as file: 20 | file.write("# Building the Face\n# Locators\n\n") 21 | file.write("import maya.cmds as cmds\n\n") 22 | file.write('cmds.group(em=True, name="Locators")\n\n') 23 | 24 | file.write("def create_locator(name, x, y, z):\n") 25 | file.write(" locator = cmds.spaceLocator(name=name)[0]\n") 26 | file.write(" cmds.move(x, y, z, locator)\n") 27 | for locator_name in locator_names: 28 | pos = get_locator_position(locator_name) 29 | file.write( 30 | f'create_locator("{locator_name}", {pos[0]}, {pos[1]}, {pos[2]})\n' 31 | ) 32 | file.write(f'cmds.parent("{locator_name}", "Locators")\n\n') 33 | 34 | 35 | # Function to create all locators at their specific positions 36 | def create_all_locators(): 37 | cmds.group(em=True, name="Locators") 38 | 39 | locator_positions = { 40 | "skull_bind_LOC": (0, 148.946, -1.415), 41 | "face_upper_bind_LOC": (0, 148.535, -0.646), 42 | "head_tip_bind_LOC": (0, 156.237, 0.066), 43 | "brow_inner_l_bind_LOC": (1.532247, 157.231934, 8.437251), 44 | "brow_main_l_bind_LOC": (4.194504, 157.582428, 7.474292), 45 | "brow_peak_l_bind_LOC": (6.571188, 157.360886, 5.917207), 46 | "brow_inner_r_bind_LOC": (-1.532247, 157.231934, 8.437251), 47 | "brow_main_r_bind_LOC": (-4.194504, 157.582428, 7.474292), 48 | "brow_peak_r_bind_LOC": (-6.571189, 157.360886, 5.917207), 49 | "eyeSocket_r_bind_LOC": (-3.842108, 154.854385, 4.520485), 50 | "eye_r_bind_LOC": (-3.842108, 154.854385, 4.520485), 51 | "iris_r_bind_LOC": (-3.986634, 154.847443, 7.280398), 52 | "pupil_r_bind_LOC": (-3.986634, 154.847443, 7.280398), 53 | "eyeSocket_l_bind_LOC": (3.842108, 154.854385, 4.520485), 54 | "eye_l_bind_LOC": (3.842108, 154.854385, 4.520485), 55 | "iris_l_bind_LOC": (3.986634, 154.847443, 7.280398), 56 | "pupil_l_bind_LOC": (3.986634, 154.847443, 7.280398), 57 | "face_lower_bind_LOC": (0, 154.289, -0.867), 58 | "face_mid_bind_LOC": (0, 147.761, -0.203), 59 | "nose_bridge_bind_LOC": (0, 155.019, 7.697), 60 | "nose_bind_LOC": (0, 151.722, 9.133), 61 | "jaw_bind_LOC": (0, 150.539, -4.177), 62 | "jaw_end_bind_LOC": (0, 144.651, 7.095), 63 | "nose_l_nostril_bind_LOC": (0.495, 150.669, 8.234), 64 | "nose_r_nostril_bind_LOC": (-0.495, 150.669, 8.234), 65 | "nose_bottom_bind_LOC": (0, 150.425, 8.533), 66 | "nose_l_outer_nostril_bind_LOC": (1.451, 150.847, 7.997), 67 | "nose_r_outer_nostril_bind_LOC": (-1.451, 150.847, 7.997), 68 | } 69 | 70 | for locator_name, position in locator_positions.items(): 71 | create_locator(locator_name, *position) 72 | cmds.parent(locator_name, "Locators") 73 | 74 | 75 | # Function to create the GUI window 76 | def create_gui(): 77 | if cmds.window("locatorToolWindow", exists=True): 78 | cmds.deleteUI("locatorToolWindow", window=True) 79 | 80 | window = cmds.window( 81 | "locatorToolWindow", title="Locator Tool", widthHeight=(200, 100) 82 | ) 83 | 84 | cmds.columnLayout(adjustableColumn=True) 85 | cmds.text(label="Create Locators:") 86 | cmds.button( 87 | label="Create Generic Locators", command=lambda *args: create_all_locators() 88 | ) 89 | 90 | cmds.text(label="Update Script:") 91 | cmds.button(label="Update Robins Locator Positions", command=update_script_button) 92 | 93 | cmds.showWindow(window) 94 | 95 | 96 | # Function to update the script with the new locator positions 97 | def update_script_button(*args): 98 | update_script() 99 | 100 | 101 | # Global variable to store locator names 102 | locator_names = [ 103 | "skull_bind_LOC", 104 | "face_upper_bind_LOC", 105 | "head_tip_bind_LOC", 106 | "brow_inner_l_bind_LOC", 107 | "brow_main_l_bind_LOC", 108 | "brow_peak_l_bind_LOC", 109 | "brow_inner_r_bind_LOC", 110 | "brow_main_r_bind_LOC", 111 | "brow_peak_r_bind_LOC", 112 | "eyeSocket_r_bind_LOC", 113 | "eye_r_bind_LOC", 114 | "iris_r_bind_LOC", 115 | "pupil_r_bind_LOC", 116 | "eyeSocket_l_bind_LOC", 117 | "eye_l_bind_LOC", 118 | "iris_l_bind_LOC", 119 | "pupil_l_bind_LOC", 120 | "face_lower_bind_LOC", 121 | "face_mid_bind_LOC", 122 | "nose_bridge_bind_LOC", 123 | "nose_bind_LOC", 124 | "jaw_bind_LOC", 125 | "jaw_end_bind_LOC", 126 | "nose_l_nostril_bind_LOC", 127 | "nose_r_nostril_bind_LOC", 128 | "nose_bottom_bind_LOC", 129 | "nose_l_outer_nostril_bind_LOC", 130 | "nose_r_outer_nostril_bind_LOC", 131 | ] 132 | 133 | # Create the GUI 134 | create_gui() 135 | -------------------------------------------------------------------------------- /pipeline/pipe/m/ToolBox/add_bind_joint_attribute.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | 3 | 4 | def add_joints(*args): 5 | # Get a list of selected objects 6 | selection = cmds.ls(selection=True) 7 | 8 | # Iterate over each selected object and add the boolean attribute 9 | for obj in selection: 10 | cmds.addAttr( 11 | obj, 12 | longName="face_bind_joint", 13 | attributeType="bool", 14 | defaultValue=True, 15 | keyable=False, 16 | ) 17 | -------------------------------------------------------------------------------- /pipeline/pipe/m/ToolBox/select_face_bind_joints.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | 3 | 4 | def face_bind_selection(*args): 5 | # Get a list of all objects in the scene 6 | all_objects = cmds.ls(type="transform", visible=True) 7 | 8 | # Initialize an empty list to store objects with the attribute set to True 9 | selected_objects = [] 10 | 11 | # Iterate over each object to check if it has the attribute "face_bind_joint" set to True 12 | for obj in all_objects: 13 | if cmds.attributeQuery("face_bind_joint", node=obj, exists=True): 14 | value = cmds.getAttr(obj + ".face_bind_joint") 15 | if value: 16 | selected_objects.append(obj) 17 | 18 | # Select the objects that have the attribute set to True 19 | if selected_objects: 20 | cmds.select(selected_objects) 21 | else: 22 | print("No objects found with 'face_bind_joint' attribute set to True.") 23 | -------------------------------------------------------------------------------- /pipeline/pipe/m/__init__.py: -------------------------------------------------------------------------------- 1 | # Import nested modules 2 | from . import local as local 3 | from . import picker as picker 4 | from . import reload as reload 5 | from . import rig_publish as rig_publish 6 | -------------------------------------------------------------------------------- /pipeline/pipe/m/local.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | import sys 5 | 6 | from Qt import QtCompat, QtWidgets 7 | import maya.OpenMayaUI as omUI 8 | 9 | from software.baseclass import DCCLocalizer 10 | 11 | 12 | class _MayaLocalizer(DCCLocalizer): 13 | def __init__(self) -> None: 14 | super().__init__("maya") 15 | 16 | def get_main_qt_window(self) -> QtWidgets.QWidget | None: 17 | if not self.is_headless(): 18 | ptr = omUI.MQtUtil.mainWindow() 19 | if ptr is not None: 20 | return QtCompat.wrapInstance(int(ptr), QtWidgets.QWidget) # type: ignore[attr-defined] 21 | return None 22 | 23 | def is_headless(self) -> bool: 24 | pattern = re.compile("^.*mayapy(?:\.?(?:bin|exe))$") 25 | return bool(pattern.match(sys.executable)) 26 | 27 | 28 | _l = _MayaLocalizer() 29 | 30 | get_main_qt_window = _l.get_main_qt_window 31 | is_headless = _l.is_headless 32 | -------------------------------------------------------------------------------- /pipeline/pipe/m/picker.py: -------------------------------------------------------------------------------- 1 | import dwpicker 2 | 3 | from shared.util import get_rigging_path 4 | 5 | 6 | def run(): 7 | picker_folder_path = get_rigging_path() / "Pickers" 8 | 9 | picker_filepaths = [ 10 | str(p) for p in picker_folder_path.iterdir() if p.suffix == ".json" 11 | ] 12 | print("Picker filepaths", picker_filepaths) 13 | 14 | dwpicker.show(pickers=picker_filepaths) 15 | -------------------------------------------------------------------------------- /pipeline/pipe/m/playblast/__init__.py: -------------------------------------------------------------------------------- 1 | from .anim import AnimPlayblastDialog as AnimPlayblastDialog 2 | from .previs import PrevisPlayblastDialog as PrevisPlayblastDialog 3 | -------------------------------------------------------------------------------- /pipeline/pipe/m/playblast/struct.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | 5 | from dataclasses import dataclass, field 6 | from typing import TYPE_CHECKING 7 | 8 | from pipe.struct.db import Shot 9 | 10 | if TYPE_CHECKING: 11 | from pathlib import Path 12 | from pipe.util import Playblaster 13 | from typing import Callable, Literal 14 | 15 | 16 | log = logging.getLogger(__name__) 17 | 18 | 19 | def dummy_shot(code: str, cut_in: int, cut_out: int, cut_duration: int) -> Shot: 20 | """Generate a generic `Shot` object to hold cut info that doesn't 21 | correspond to a ShotGrid shot""" 22 | return Shot( 23 | code=code, 24 | id=0, 25 | assets=[], 26 | cut_in=cut_in, 27 | cut_out=cut_out, 28 | cut_duration=cut_duration, 29 | sequence=None, 30 | set=None, 31 | ) 32 | 33 | 34 | @dataclass 35 | class HudDefinition: 36 | """ 37 | Definition for a viewport HUD. 38 | Attributes 39 | name: str 40 | Internal name used by Maya for the HUD 41 | command: Callable[[], str] 42 | Command for the HUD to call 43 | section: int 44 | HUD section to occupy (see Maya docs) 45 | label: str 46 | String that precedes the return value of `command` 47 | event: str 48 | Event string that triggers a refresh (see Maya docs) 49 | idle_refresh: bool 50 | Alternative to `event`, will refresh every frame 51 | blockSize: Literal["small", "large"] 52 | Amount of HUD space to occupy 53 | labelFontSize: Literal["small", "large"] 54 | """ 55 | 56 | name: str 57 | command: Callable[[], str] 58 | section: int 59 | label: str = "" 60 | event: str = "" 61 | idle_refresh: bool = False 62 | blockSize: Literal["small", "large"] = "small" 63 | labelFontSize: Literal["small", "large"] = "small" 64 | 65 | 66 | @dataclass 67 | class MShotDialogConfig: 68 | """Information needed to add a shot to the playblast dialog 69 | id: str 70 | Unique id for this shot 71 | name: str 72 | Display name of the shot 73 | save_locs: list[tuple[SaveLocation, bool]] 74 | List of save locations, paired with their default enable value 75 | """ 76 | 77 | id: str 78 | name: str 79 | save_locs: list[tuple[SaveLocation, bool]] 80 | 81 | 82 | @dataclass 83 | class MShotPlayblastConfig: 84 | """Information needed to playblast a shot. 85 | Attributes: 86 | camera: str | None 87 | Camera to use. Value ignored if `use_sequencer` is set 88 | shot: Shot 89 | Shot struct to hold shot code, cut in, cut out, and duration 90 | paths: dict[Playblaster.PRESET, list[str | Path]] 91 | Paths to output to 92 | tails: tuple[int, int] 93 | How many frames early/late to start playblasting 94 | use_sequencer: bool = False 95 | Whether to playblast from the sequencer. If set to True, `camera` 96 | will be ignored 97 | """ 98 | 99 | camera: str | None 100 | shot: Shot 101 | paths: dict[Playblaster.PRESET, list[str | Path]] = field(default_factory=dict) 102 | tails: tuple[int, int] = (0, 0) 103 | use_sequencer: bool = False 104 | 105 | def set_enabled(self, enabled: bool) -> None: 106 | self.enabled = enabled 107 | 108 | def set_paths(self, paths: dict[Playblaster.PRESET, list[str | Path]]) -> None: 109 | self.paths = paths 110 | 111 | 112 | @dataclass 113 | class MPlayblastConfig: 114 | """Information needed to configure a Maya playblast 115 | Attributes: 116 | builtin_huds: list[str] 117 | List of valid Maya builtin HUD names 118 | custom_huds: list[HudDefinition] 119 | List of `HudDefinition`s 120 | dof: bool 121 | Toggle depth of field 122 | hardware_fog: bool 123 | Toggle hardware fog 124 | lighting: bool 125 | Toggle viewport lighting 126 | shadows: bool 127 | Toggle viewport shadows 128 | shots: list[MShotPlayblastConfig] 129 | List of shots to playblast 130 | ssao: bool 131 | Toggle viewport screen-space anti-aliasing 132 | """ 133 | 134 | builtin_huds: list[str] 135 | custom_huds: list[HudDefinition] 136 | dof: bool 137 | hardware_fog: bool 138 | lighting: bool 139 | shadows: bool 140 | shots: list[MShotPlayblastConfig] 141 | ssao: bool 142 | 143 | 144 | class SaveLocation: 145 | """Information needed for a save location. If a lambda is provided to 146 | `path` it will call that and return the value""" 147 | 148 | name: str 149 | preset: Playblaster.PRESET 150 | _path: str | Path | Callable[[], str | Path] 151 | 152 | def __init__( 153 | self, 154 | name: str, 155 | path: str | Path | Callable[[], str | Path], 156 | preset: Playblaster.PRESET, 157 | ): 158 | self.name = name 159 | self._path = path 160 | self.preset = preset 161 | 162 | @property 163 | def path(self) -> str | Path: 164 | if callable(self._path): 165 | return self._path() 166 | else: 167 | return self._path 168 | -------------------------------------------------------------------------------- /pipeline/pipe/m/publish/__init__.py: -------------------------------------------------------------------------------- 1 | from .anim import AnimPublisher as AnimPublisher 2 | from .rig import RigPublisher as RigPublisher 3 | from .asset import AssetPublisher as AssetPublisher 4 | from .asset import ModelChecker as ModelChecker 5 | from .camera import CameraPublisher as CameraPublisher 6 | from .usdchaser import ExportChaser as ExportChaser 7 | -------------------------------------------------------------------------------- /pipeline/pipe/m/publish/camera.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | from pathlib import Path 5 | from Qt.QtCore import QRegExp 6 | from Qt.QtGui import QRegExpValidator 7 | from Qt.QtWidgets import QComboBox, QHBoxLayout, QLabel, QWidget 8 | from typing import TYPE_CHECKING, cast 9 | 10 | if TYPE_CHECKING: 11 | from typing import Any, Sequence 12 | 13 | import maya.cmds as mc 14 | 15 | from pipe.glui.dialogs import FilteredListDialog, MessageDialog 16 | from pipe.struct.db import SGEntity, Shot 17 | from shared.util import get_production_path 18 | 19 | from .publisher import Publisher 20 | from .usdchaser import ExportChaser, ChaserMode 21 | 22 | 23 | log = logging.getLogger(__name__) 24 | 25 | 26 | class PublishCameraDialog(FilteredListDialog): 27 | _camera: QComboBox 28 | 29 | def __init__(self, parent: QWidget | None, items: Sequence[str]) -> None: 30 | super().__init__( 31 | parent, 32 | items, 33 | "Publish Camera", 34 | "Select a shot to publish the camera for", 35 | accept_button_name="Publish", 36 | ) 37 | 38 | self._camera = QComboBox( 39 | self, 40 | ) 41 | cameras = mc.ls(cameras=True, visible=True) 42 | self._camera.addItems(cameras) 43 | self._camera.setCurrentText(cameras[0]) 44 | validator = QRegExpValidator(QRegExp("|".join(cameras))) 45 | self._camera.setValidator(validator) 46 | 47 | camera_widget = QWidget() 48 | camera_layout = QHBoxLayout(camera_widget) 49 | camera_label = QLabel("Camera:") 50 | camera_layout.addWidget(camera_label, 1) 51 | camera_layout.addWidget(self._camera, 99) 52 | 53 | self._layout.insertWidget(0, camera_widget) 54 | 55 | 56 | class CameraPublisher(Publisher): 57 | def __init__(self) -> None: 58 | super().__init__(PublishCameraDialog) 59 | 60 | def _get_entity_list(self) -> list[str]: 61 | return self._conn.get_shot_code_list(sorted=True) 62 | 63 | def _get_entity_from_name(self, name: str) -> SGEntity | None: 64 | return self._conn.get_shot_by_code(name) 65 | 66 | def _get_save_path(self) -> Path | None: 67 | try: 68 | assert self._entity.path is not None 69 | except AssertionError: 70 | error = MessageDialog( 71 | self._window, 72 | "Error: No path for this Shot set in ShotGrid. Nothing exported", 73 | "Error", 74 | ) 75 | error.exec_() 76 | return None 77 | 78 | return get_production_path() / self._entity.path / "cam" / "cam.usd" 79 | 80 | def _presave(self) -> bool: 81 | mc.select(self._camera, replace=True) 82 | return True 83 | 84 | def _get_mayausd_kwargs(self) -> dict[str, Any]: 85 | shot = cast(Shot, self._entity) 86 | start = shot.cut_in - 5 87 | end = shot.cut_out + 5 88 | return { 89 | "chaser": [ExportChaser.ID], 90 | "chaserArgs": [(ExportChaser.ID, "mode", ChaserMode.CAM)], 91 | "frameRange": (start, end), 92 | "frameStride": 1.0 / shot.substeps, 93 | } 94 | 95 | def _get_confirm_message(self) -> str: 96 | return f"The camera has been exported to {self._publish_path}" 97 | 98 | @property 99 | def _camera(self) -> str: 100 | return cast(PublishCameraDialog, self._dialog)._camera.currentText() 101 | -------------------------------------------------------------------------------- /pipeline/pipe/m/publish/rig.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | if TYPE_CHECKING: 8 | from typing import Any 9 | 10 | import maya.cmds as mc 11 | 12 | from .publisher import Publisher 13 | from .usdchaser import ChaserMode, ExportChaser 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | CACHE_SET = "cache_SET" 18 | PROP_SET = "prop_SET" 19 | 20 | 21 | class RigPublisher(Publisher): 22 | def __init__(self) -> None: 23 | super().__init__(use_sg_entity=False) 24 | 25 | def _get_entity_list(self) -> list[str]: 26 | cache_sets = mc.ls("::" + CACHE_SET, sets=True) 27 | return [s.split(":")[0] for s in cache_sets] 28 | 29 | def _get_mayausd_kwargs(self) -> dict[str, Any]: 30 | kwargs = { 31 | "chaser": [ExportChaser.ID], 32 | "chaserArgs": [(ExportChaser.ID, "mode", ChaserMode.CHAR)], 33 | "exportCollectionBasedBindings": True, 34 | "exportMaterialCollections": True, 35 | "legacyMaterialScope": True, 36 | "materialCollectionsPath": "/ROOT/MODEL", 37 | "shadingMode": "useRegistry", 38 | } 39 | 40 | return kwargs 41 | 42 | def _presave(self) -> bool: 43 | mc.select(self._selected_item + ":" + CACHE_SET) 44 | return True 45 | -------------------------------------------------------------------------------- /pipeline/pipe/m/reload.py: -------------------------------------------------------------------------------- 1 | def reload_pipe() -> None: 2 | from pipe.util import reload_pipe as _reload_pipe 3 | 4 | _reload_pipe() 5 | 6 | # wrap this in a try block because it will fail in headless mode 7 | try: 8 | import mayaUsd.lib as mayaUsdLib # type: ignore[import-not-found] 9 | from pipe.m.publish import ExportChaser 10 | 11 | mayaUsdLib.ExportChaser.Unregister(ExportChaser, ExportChaser.ID) 12 | mayaUsdLib.ExportChaser.Register(ExportChaser, ExportChaser.ID) 13 | except Exception: 14 | pass 15 | -------------------------------------------------------------------------------- /pipeline/pipe/m/rig_publish.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from Qt import QtWidgets 4 | 5 | import maya.cmds as mc 6 | import os 7 | 8 | import shared.util as su 9 | from pipe.m.local import get_main_qt_window 10 | 11 | rig_list = [ 12 | "select rig", 13 | "Robin", 14 | "RobinFace", 15 | "Rayden", 16 | "RaydenFace", 17 | "DungeonMonster", 18 | "Skeleton", 19 | "Crossbow", 20 | "Cipher", 21 | "LootBag", 22 | "Door", 23 | "test", 24 | ] 25 | 26 | 27 | class RigPublishUI(QtWidgets.QDialog): 28 | def __init__(self, parent=get_main_qt_window()): 29 | super().__init__(parent) 30 | 31 | self.setWindowTitle("Rig Publish") 32 | self.setMinimumWidth(200) 33 | 34 | self.create_widgets() 35 | self.create_layouts() 36 | self.create_connections() 37 | 38 | def create_widgets(self): 39 | self.rig_options = QtWidgets.QComboBox() 40 | self.rig_options.addItems(rig_list) 41 | 42 | self.anim_check = QtWidgets.QCheckBox("Update Anim Symlink") 43 | self.pvis_check = QtWidgets.QCheckBox("Update Previs Symlink") 44 | self.anim_check.setChecked(False) 45 | self.pvis_check.setChecked(False) 46 | 47 | self.publish_btn = QtWidgets.QPushButton("Publish") 48 | self.cancel_btn = QtWidgets.QPushButton("Cancel") 49 | 50 | def create_layouts(self): 51 | main_layout = QtWidgets.QVBoxLayout(self) 52 | 53 | options_layout = QtWidgets.QFormLayout() 54 | options_layout.addWidget(self.rig_options) 55 | options_layout.addWidget(self.anim_check) 56 | options_layout.addWidget(self.pvis_check) 57 | 58 | buttons_layout = QtWidgets.QHBoxLayout() 59 | buttons_layout.addStretch() 60 | buttons_layout.addWidget(self.publish_btn) 61 | buttons_layout.addWidget(self.cancel_btn) 62 | 63 | main_layout.addLayout(options_layout) 64 | main_layout.addLayout(buttons_layout) 65 | 66 | def create_connections(self): 67 | self.publish_btn.clicked.connect(self.on_publish) 68 | self.cancel_btn.clicked.connect(self.on_cancel) 69 | 70 | def on_publish(self): 71 | file_name = self.rig_options.currentText() 72 | 73 | if file_name == "select rig": 74 | mc.warning("Select a rig to publish.") 75 | return 76 | 77 | update_anim = self.anim_check.isChecked() 78 | update_pvis = self.pvis_check.isChecked() 79 | 80 | dir_path = su.get_rigging_path() / "Rigs" / file_name / "RigVersions" 81 | 82 | # search directory for all versions and determine new version number 83 | ls_dir = dir_path.iterdir() 84 | latest_version = 0 85 | 86 | for item in ls_dir: 87 | try: 88 | version = int(str(item).split(".")[-2]) 89 | if version > latest_version: 90 | latest_version = version 91 | except Exception as e: 92 | print(f"exception '{e}' for: {item}") 93 | 94 | v_string = str(latest_version + 1).zfill(3) 95 | 96 | # save file to path 97 | full_name = dir_path / f"{file_name}.{v_string}.mb" 98 | mc.file(rename=full_name) 99 | saved = mc.file(s=True, f=True, typ="mayaBinary") 100 | 101 | print(f"File saved to '{saved}'") 102 | 103 | # create symlinks 104 | if update_anim: 105 | anim_link_dir_path = su.get_anim_path() / "Rigs" 106 | temp_name = f"{anim_link_dir_path}\\tmp" 107 | os.symlink(full_name, temp_name) 108 | os.rename(temp_name, f"{anim_link_dir_path}/{file_name}.mb") 109 | 110 | print( 111 | f"Link to file created or updated at '{anim_link_dir_path}/{file_name}.mb'\n" 112 | ) 113 | if update_pvis: 114 | pvis_link_dir_path = su.get_previs_path() / "Rigs" 115 | temp_name = f"{pvis_link_dir_path}\\tmp" 116 | os.symlink(full_name, temp_name) 117 | os.rename(temp_name, f"{pvis_link_dir_path}/{file_name}.mb") 118 | 119 | print( 120 | f"Link to file created or updated at '{pvis_link_dir_path}/{file_name}.mb'\n" 121 | ) 122 | self.close() 123 | 124 | def on_cancel(self): 125 | print("Cancelled Rig Publish") 126 | self.close() 127 | 128 | 129 | rig_pub: RigPublishUI | None = None 130 | 131 | 132 | def run(): 133 | global rig_pub 134 | try: 135 | assert rig_pub is not None 136 | rig_pub.close() 137 | rig_pub.deleteLater() 138 | except AssertionError: 139 | pass 140 | 141 | rig_pub = RigPublishUI() 142 | rig_pub.show() 143 | 144 | 145 | if __name__ == "__main__": 146 | try: 147 | assert rig_pub is not None 148 | rig_pub.close() 149 | rig_pub.deleteLater() 150 | except AssertionError: 151 | pass 152 | 153 | rig_pub = RigPublishUI() 154 | rig_pub.show() 155 | -------------------------------------------------------------------------------- /pipeline/pipe/m/shotfile/__init__.py: -------------------------------------------------------------------------------- 1 | from .anim import MAnimShotFileManager as MAnimShotFileManager 2 | from .rlo import MRLOShotFileManager as MRLOShotFileManager 3 | from .timeline import shot_timeline_generator as shot_timeline_generator 4 | from .timeline import timeline_generator as timeline_generator 5 | -------------------------------------------------------------------------------- /pipeline/pipe/m/shotfile/anim.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import maya.cmds as mc 3 | 4 | from pathlib import Path 5 | from pxr import Usd, UsdGeom 6 | 7 | from shared.util import get_production_path 8 | 9 | from .shotfile_manager import MShotFileManager 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class MAnimShotFileManager(MShotFileManager): 15 | @classmethod 16 | def run_on_open(cls): 17 | super().run_on_open() 18 | 19 | # Duplicate the USD camera into a temp Maya camera 20 | CAM_NAME = "shotCam" 21 | try: 22 | mc.mayaUsdDiscardEdits(CAM_NAME) 23 | except RuntimeError: 24 | pass 25 | finally: 26 | camera_prim = next( 27 | prim 28 | for prim in cls.get_stage().Traverse(Usd.PrimIsDefined) 29 | if prim.IsA(UsdGeom.Camera) and prim.GetName() == CAM_NAME 30 | ) 31 | mc.mayaUsdEditAsMaya( 32 | cls.get_stage_shape() + "," + str(camera_prim.GetPrimPath()) 33 | ) 34 | camera_shape = mc.listRelatives(CAM_NAME, fullPath=True, shapes=True)[0] 35 | mc.lookThru(CAM_NAME) 36 | mc.camera(camera_shape, edit=True, lockTransform=True) 37 | 38 | def _get_subpath(self) -> str: 39 | return "anim" 40 | 41 | def _setup_scene(self) -> None: 42 | self._import_camera() 43 | self._import_env() 44 | 45 | # Import Rigs 46 | for asset_stub in self.shot.assets: 47 | asset = self._conn.get_asset_by_stub(asset_stub) 48 | if not asset.path: 49 | continue 50 | rig_folder = get_production_path() / asset.path / "rig" 51 | rig_list = sorted(list(rig_folder.glob("rig*.mb"))) 52 | try: 53 | rig_path = rig_list.pop() 54 | if rig_path.exists(): 55 | mc.file(str(rig_path), reference=True, namespace=asset.name) 56 | else: 57 | raise FileNotFoundError 58 | 59 | except (FileNotFoundError, IndexError): 60 | print(f'Unable to find rig for asset "{asset.disp_name}"') 61 | 62 | def _setup_file(self, path: Path, entity) -> None: 63 | mc.file(newFile=True, force=True) 64 | super()._setup_file(path, entity) 65 | -------------------------------------------------------------------------------- /pipeline/pipe/m/shotfile/rlo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pathlib import Path 4 | 5 | from pipe.glui.dialogs import MessageDialogCustomButtons 6 | from pipe.struct.db import SGEntity 7 | 8 | from .shotfile_manager import MShotFileManager 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | class MRLOShotFileManager(MShotFileManager): 14 | def __init__(self): 15 | super().__init__(version_glob="{}*.{}", version_msg="Open alt version") 16 | 17 | def _check_unsaved_changes(self) -> bool: 18 | return True 19 | 20 | def _get_subpath(self) -> str: 21 | return "rlo" 22 | 23 | def _setup_scene(self) -> None: 24 | self._import_env() 25 | 26 | def _setup_file(self, path: Path, entity: SGEntity) -> None: 27 | if not path.exists(): 28 | prompt_create = MessageDialogCustomButtons( 29 | self._main_window, 30 | f"The RLO file for shot {entity.code} does not exist. Continue " 31 | "to save a copy of the current file as the RLO file?", 32 | has_cancel_button=True, 33 | ok_name="Continue", 34 | cancel_name="Cancel", 35 | ) 36 | if not bool(prompt_create.exec_()): 37 | return 38 | super()._setup_file(path, entity) 39 | -------------------------------------------------------------------------------- /pipeline/pipe/m/shotfile/timeline.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | def timeline_generator( 5 | pre_roll: list[tuple[str, tuple[int, int, int], int]], 6 | roll: list[tuple[str, tuple[int, int, int], int]], 7 | /, 8 | start_frame: int = 1001, 9 | ) -> tuple[list[int], list[tuple[int, int, int]], list[str]]: 10 | colors = [] 11 | comments = [] 12 | pre_duration = 0 13 | post_duration = 0 14 | 15 | for comment, color, duration in pre_roll: 16 | comments += [comment] * duration 17 | colors += [color] * duration 18 | pre_duration += duration 19 | for comment, color, duration in roll: 20 | comments += [comment] * duration 21 | colors += [color] * duration 22 | post_duration += duration 23 | 24 | frames = list(range(start_frame - pre_duration, start_frame + post_duration)) 25 | return frames, colors, comments 26 | 27 | 28 | def shot_timeline_generator( 29 | shot_duration: int, 30 | shot_start_frame: int = 1001, 31 | ) -> tuple[list[int], list[tuple[int, int, int]], list[str]]: 32 | return timeline_generator( 33 | [ 34 | ("Rest Pose @Origin", (70, 0, 0), 15), 35 | ("Rest Pose -> Windup", (150, 0, 0), 15), 36 | ("Hold Windup", (255, 0, 0), 10), 37 | ("Windup", (128, 128, 0), 15), 38 | ("Head", (128, 255, 128), 5), 39 | ], 40 | [ 41 | ("Animate!", (0, 255, 0), shot_duration), 42 | ("Tail", (100, 160, 255), 5), 43 | ], 44 | start_frame=shot_start_frame, 45 | ) 46 | -------------------------------------------------------------------------------- /pipeline/pipe/m/space_switch.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import maya.cmds as mc 4 | 5 | 6 | def createSpaceSwitch(): 7 | sel = mc.ls(sl=True) 8 | sources = sel 9 | 10 | target = sel[-1] 11 | sources.remove(target) 12 | sourceNames = [] 13 | 14 | colonSourceStr = "" 15 | for source in sources: 16 | name = source.replace("_CTRL", "") 17 | if ":" in source: 18 | c = source.index(":") 19 | name = source[c + 1 :] 20 | colonSourceStr += name + ":" 21 | sourceNames.append(name) 22 | 23 | if mc.attributeQuery("spaceSwitch", node=target, exists=True): 24 | mc.deleteAttr(target, at="spaceSwitch") 25 | 26 | mc.addAttr( 27 | target, 28 | ln="spaceSwitch", 29 | at="enum", 30 | en="default:" + colonSourceStr, 31 | keyable=True, 32 | ) 33 | 34 | mc.select(target) 35 | grp = target + "_space_switch_GRP" 36 | 37 | parent = (mc.listRelatives(target, parent=True)[0],) 38 | 39 | if not mc.objExists(grp): 40 | grp = mc.group( 41 | name=target + "_space_switch_GRP", 42 | em=True, 43 | ) 44 | fix = mc.group(em=True, name=target + "_space_switch_TARG") 45 | mc.matchTransform(fix, target) 46 | mc.parent(fix, grp) 47 | pc = mc.parentConstraint(fix, parent, mo=True) 48 | 49 | if mc.listRelatives(grp, type="constraint") is not None: 50 | constraint = mc.listRelatives(grp, type="constraint")[0] 51 | mc.delete(constraint) 52 | pc = mc.parentConstraint(sources, grp, mo=True)[0] 53 | 54 | pcTrgs = mc.parentConstraint(pc, wal=True, q=True, mo=True) 55 | 56 | defaultCond = mc.createNode("condition", n="default_COND") 57 | mc.connectAttr(target + ".spaceSwitch", defaultCond + ".firstTerm") 58 | mc.setAttr(defaultCond + ".colorIfTrueR", 0) 59 | mc.setAttr(defaultCond + ".colorIfFalseR", 1) 60 | mc.setAttr(defaultCond + ".operation", 0) 61 | 62 | for count, source in enumerate(sources): 63 | print(source) 64 | print(pcTrgs[count]) 65 | cond = mc.createNode("condition", n=pcTrgs[count] + "_COND") 66 | mc.connectAttr(target + ".spaceSwitch", cond + ".firstTerm") 67 | mc.setAttr(cond + ".secondTerm", count + 1) 68 | mc.setAttr(cond + ".colorIfTrueR", 1) 69 | mc.setAttr(cond + ".colorIfFalseR", 0) 70 | mc.connectAttr(defaultCond + ".outColorR", cond + ".colorIfTrueR") 71 | mc.connectAttr(cond + ".outColorR", pc + "." + pcTrgs[count]) 72 | 73 | mc.select(target) 74 | 75 | 76 | def run(): 77 | createSpaceSwitch() 78 | -------------------------------------------------------------------------------- /pipeline/pipe/m/studiolibrary.py: -------------------------------------------------------------------------------- 1 | import studiolibrary 2 | from shared.util import get_anim_path 3 | 4 | 5 | def run(): 6 | libraries = [ 7 | { 8 | "name": "LnD Poses", 9 | "path": str(get_anim_path() / "studiolibrary/lnd-poses"), 10 | "default": True, 11 | "theme": { 12 | "accentColor": "rgb(97, 30, 10)", 13 | }, 14 | }, 15 | ] 16 | studiolibrary.setLibraries(libraries) 17 | studiolibrary.main() 18 | -------------------------------------------------------------------------------- /pipeline/pipe/m/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import maya.cmds as mc 4 | 5 | from contextlib import contextmanager 6 | from typing import TYPE_CHECKING 7 | 8 | if TYPE_CHECKING: 9 | from typing import Generator 10 | 11 | 12 | @contextmanager 13 | def maintain_selection() -> Generator[None, None, None]: 14 | selection = mc.ls(selection=True, long=True, ufeObjects=True, absoluteName=True) 15 | 16 | try: 17 | yield 18 | finally: 19 | mc.select(*selection, replace=True) 20 | -------------------------------------------------------------------------------- /pipeline/pipe/sp/__init__.py: -------------------------------------------------------------------------------- 1 | from . import channels as channels 2 | from . import export as export 3 | from . import local as local 4 | from . import metadata as metadata 5 | from . import reload as reload 6 | -------------------------------------------------------------------------------- /pipeline/pipe/sp/channels.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import substance_painter as sp 4 | 5 | from pipe.sp.local import get_main_qt_window 6 | from pipe.glui.dialogs import MessageDialog 7 | 8 | 9 | class sRGBChecker: 10 | srgb_channels: list[sp.textureset.Channel] 11 | 12 | def __init__(self) -> None: 13 | self.srgb_channels = [] 14 | 15 | def check(self) -> bool: 16 | """Return True if sRGB channels are properly configured""" 17 | for ts in sp.textureset.all_texture_sets(): 18 | try: 19 | stack = ts.get_stack() 20 | except ValueError: 21 | MessageDialog( 22 | get_main_qt_window(), 23 | "Warning! sRGB Checker could not get stack! You are doing something cool with material layering. Please show this to Scott so he can fix it.", 24 | ).exec_() 25 | return False 26 | 27 | for ch in stack.all_channels().values(): 28 | if ch.format() in [ 29 | sp.textureset.ChannelFormat.sRGB8, 30 | sp.textureset.ChannelFormat.RGB8, 31 | ]: 32 | self.srgb_channels.append(ch) 33 | 34 | return not bool(self.srgb_channels) 35 | 36 | def prompt_srgb_fix(self) -> bool: 37 | """Return True if fix is successful""" 38 | fix_channels = MessageDialog( 39 | get_main_qt_window(), 40 | "Warning! Some of your color channels do not have a high enough bit depth for this color space! (sRGB8, RGB8). Would you like to convert them to RGB16 now?", 41 | "Color Bit Depth Issue", 42 | has_cancel_button=True, 43 | ).exec_() 44 | 45 | if not fix_channels: 46 | return False 47 | 48 | for ch in self.srgb_channels: 49 | ch.edit(sp.textureset.ChannelFormat.RGB16) 50 | 51 | MessageDialog(get_main_qt_window(), "Color bit depth has been updated.").exec_() 52 | return True 53 | -------------------------------------------------------------------------------- /pipeline/pipe/sp/local.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from substance_painter import ui 4 | 5 | from Qt import QtWidgets 6 | 7 | from software.baseclass import DCCLocalizer 8 | 9 | 10 | class _SubstancePainterLocalizer(DCCLocalizer): 11 | def __init__(self) -> None: 12 | super().__init__("substance_painter") 13 | 14 | def get_main_qt_window(self) -> QtWidgets.QWidget | None: 15 | return ui.get_main_window() 16 | 17 | def is_headless(self) -> bool: 18 | return False 19 | 20 | 21 | _l = _SubstancePainterLocalizer() 22 | 23 | get_main_qt_window = _l.get_main_qt_window 24 | is_headless = _l.is_headless 25 | -------------------------------------------------------------------------------- /pipeline/pipe/sp/metadata.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import substance_painter as sp 4 | 5 | from pipe.db import DB 6 | from pipe.glui.dialogs import FilteredListDialog, MessageDialog 7 | from pipe.sp.local import get_main_qt_window 8 | 9 | from env_sg import DB_Config 10 | 11 | 12 | class MetadataUpdater: 13 | _conn: DB 14 | 15 | def __init__(self) -> None: 16 | self._conn = DB.Get(DB_Config) 17 | 18 | def check(self) -> bool: 19 | data = sp.project.Metadata("LnD") 20 | return data.get("asset_id") in self._conn.get_asset_attr_list("id") 21 | 22 | def prompt_update(self) -> bool: 23 | if self.check(): 24 | return True 25 | 26 | update = MessageDialog( 27 | get_main_qt_window(), 28 | "It looks like this file is not associated with an asset in ShotGrid. Would you like to associate it now?", 29 | "Associate Asset with ShotGrid?", 30 | has_cancel_button=True, 31 | ).exec_() 32 | 33 | if not update: 34 | MessageDialog( 35 | get_main_qt_window(), 36 | "Warning! You will need to associate this asset with ShotGrid before exporting textures.", 37 | "No asset selected", 38 | ).exec_() 39 | return False 40 | 41 | return self.do_update() 42 | 43 | def do_update(self) -> bool: 44 | fld = FilteredListDialog( 45 | get_main_qt_window(), 46 | self._conn.get_asset_name_list(sorted=True), 47 | "Associate Asset with ShotGrid", 48 | "Select an asset to associate this Substance Painter file with", 49 | accept_button_name="Associate", 50 | ) 51 | if not fld.exec_(): 52 | return False 53 | item = fld.get_selected_item() 54 | 55 | if item is None: 56 | MessageDialog( 57 | get_main_qt_window(), 58 | "Warning! No asset selected, you will need to associate this asset with ShotGrid before exporting.", 59 | "No asset selected", 60 | ).exec_() 61 | return False 62 | 63 | asset = self._conn.get_asset_by_name(item) 64 | assert asset is not None 65 | data = sp.project.Metadata("LnD") 66 | data.set("asset_id", asset.id) 67 | 68 | MessageDialog( 69 | get_main_qt_window(), 70 | f"Successfully associated with asset {asset.disp_name} in ShotGrid!", 71 | "Success", 72 | ).exec_() 73 | return True 74 | -------------------------------------------------------------------------------- /pipeline/pipe/sp/reload.py: -------------------------------------------------------------------------------- 1 | import substance_painter_plugins as spp 2 | from pipe.util import reload_pipe as _reload_pipe 3 | 4 | 5 | def reload_pipe() -> None: 6 | sp_plugins = [ 7 | spp.plugins["export"], 8 | spp.plugins["shelf"], 9 | ] 10 | _reload_pipe(sp_plugins) 11 | 12 | for plugin in sp_plugins: 13 | plugin.close_plugin() 14 | plugin.start_plugin() 15 | -------------------------------------------------------------------------------- /pipeline/pipe/struct/__init__.py: -------------------------------------------------------------------------------- 1 | from . import db as db 2 | from . import material as material 3 | from . import timeline as timeline 4 | from . import util as util 5 | -------------------------------------------------------------------------------- /pipeline/pipe/struct/material.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import attrs 4 | 5 | from enum import IntEnum 6 | 7 | from .util import JsonSerializable 8 | 9 | 10 | class DisplacementSource(IntEnum): 11 | NONE = 0 12 | HEIGHT = 1 13 | DISPLACEMENT = 2 14 | 15 | 16 | class NormalSource(IntEnum): 17 | NORMAL_HEIGHT = 0 18 | NORMAL_ONLY = 1 19 | 20 | 21 | class NormalType(IntEnum): 22 | STANDARD = 0 23 | BUMP_ROUGHNESS = 1 24 | 25 | 26 | @attrs.define 27 | class TexSetInfo(JsonSerializable): 28 | displacement_source: DisplacementSource = DisplacementSource.NONE 29 | has_udims: bool = True 30 | normal_source: NormalSource = NormalSource.NORMAL_HEIGHT 31 | normal_type: NormalType = NormalType.STANDARD 32 | 33 | 34 | @attrs.define 35 | class MaterialInfo(JsonSerializable): 36 | tex_sets: dict[str, TexSetInfo] = dict() 37 | -------------------------------------------------------------------------------- /pipeline/pipe/struct/timeline.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import attrs 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | if TYPE_CHECKING: 8 | from .db import Shot 9 | 10 | from .util import JsonSerializable 11 | 12 | 13 | PREROLL_DURATION = 55 14 | 15 | 16 | @attrs.define(frozen=True) 17 | class Timeline(JsonSerializable): 18 | start: int 19 | end: int 20 | head_duration: int = attrs.field(default=5) 21 | tail_duration: int = attrs.field(default=5) 22 | preroll_duration: int = attrs.field(default=PREROLL_DURATION) 23 | head: int = attrs.field( 24 | init=False, 25 | default=attrs.Factory(lambda s: s.start - s.head_duration, takes_self=True), 26 | ) 27 | tail: int = attrs.field( 28 | init=False, 29 | default=attrs.Factory(lambda s: s.end + s.tail_duration, takes_self=True), 30 | ) 31 | preroll: int = attrs.field( 32 | init=False, 33 | default=attrs.Factory(lambda s: s.head - s.preroll_duration, takes_self=True), 34 | ) 35 | 36 | @classmethod 37 | def from_shot( 38 | cls: type[Timeline], shot: Shot, preroll_duration: int = PREROLL_DURATION 39 | ) -> Timeline: 40 | return cls( 41 | start=shot.cut_in, 42 | end=shot.cut_out, 43 | preroll_duration=preroll_duration, 44 | head_duration=5, 45 | tail_duration=5, 46 | ) 47 | -------------------------------------------------------------------------------- /pipeline/pipe/struct/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | 5 | import attrs 6 | import cattrs 7 | 8 | from attr._make import _frozen_setattrs 9 | from copy import deepcopy 10 | from typing import Any, Type, TypeVar, Union 11 | 12 | _S = TypeVar("_S") 13 | 14 | 15 | @attrs.define 16 | class JsonSerializable: 17 | """Dataclass with methods to (de)serialize JSON""" 18 | 19 | @classmethod 20 | def from_json(cls: Type[_S], json_data: Union[str, bytes, bytearray]) -> _S: 21 | return cattrs.structure(json.loads(json_data), cls) 22 | 23 | def to_json(self) -> str: 24 | c = cattrs.Converter(unstruct_collection_overrides={set: list}) 25 | return json.dumps(c.unstructure(self)) 26 | 27 | 28 | @attrs.define 29 | class Diffable(JsonSerializable): 30 | """JsonSerializable dataclass that tracks changes to it since initialization""" 31 | 32 | _initial_state: dict[str, Any] = attrs.field( 33 | alias="_initial_state", 34 | eq=False, 35 | init=False, 36 | order=False, 37 | repr=False, 38 | ) 39 | 40 | def __attrs_post_init__(self) -> None: 41 | # don't store initial state if frozen 42 | if type(self.__class__.__setattr__) is _frozen_setattrs: 43 | object.__setattr__(self, "_initial_state", {}) 44 | 45 | # get a deepcopy of each slot 46 | name: str 47 | state: dict[str, Any] = {} 48 | for name in (f.name for f in attrs.fields(self.__class__)): 49 | if name == "_initial_state": 50 | continue 51 | state[name] = deepcopy(getattr(self, name)) 52 | 53 | # save the initial state 54 | object.__setattr__(self, "_initial_state", state) 55 | 56 | def diff(self) -> dict[str, Any]: 57 | if self._initial_state == {}: 58 | return {} 59 | 60 | # loop through keys and find changes 61 | diff: dict[str, Any] = {} 62 | name: str 63 | for name in (f.name for f in attrs.fields(self.__class__)): 64 | if name == "_initial_state": 65 | # prevent infinite loop 66 | continue 67 | if (val := getattr(self, name)) != self._initial_state[name]: 68 | diff[name] = val 69 | return diff 70 | -------------------------------------------------------------------------------- /pipeline/pipe/util/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .filemanager import FileManager 4 | from .playblaster import Playblaster 5 | from .struct import dict_index, dotdict 6 | 7 | import logging 8 | import platform 9 | import subprocess 10 | import sys 11 | 12 | from functools import wraps 13 | from Qt import QtWidgets 14 | 15 | from typing import TYPE_CHECKING 16 | 17 | 18 | if TYPE_CHECKING: 19 | from typing import Any, Callable, Sequence 20 | from types import ModuleType 21 | 22 | log = logging.getLogger(__name__) 23 | 24 | 25 | def checkbox_callback_helper( 26 | checkbox: QtWidgets.QCheckBox, widget: QtWidgets.QWidget 27 | ) -> Callable[[], None]: 28 | """Helper function to generate a callback to enable/disable a widget when 29 | a checkbox is checked""" 30 | 31 | def inner() -> None: 32 | widget.setEnabled(checkbox.isChecked()) 33 | 34 | return inner 35 | 36 | 37 | def log_errors(fun): 38 | @wraps(fun) 39 | def wrap(*args, **kwargs): 40 | try: 41 | return fun(*args, **kwargs) 42 | except Exception as e: 43 | log.error(e, exc_info=True) 44 | raise 45 | 46 | return wrap 47 | 48 | 49 | def reload_pipe(extra_modules: Sequence[ModuleType] | None = None) -> None: 50 | """Reload all pipe python modules""" 51 | if extra_modules is None: 52 | extra_modules = [] 53 | else: 54 | extra_modules = list(extra_modules) 55 | 56 | pipe_modules = [ 57 | module 58 | for name, module in sys.modules.items() 59 | if (name.startswith("pipe") or name.startswith("shared")) 60 | and ("shotgun_api3" not in name) 61 | or (name == "env") 62 | ] + extra_modules 63 | 64 | for module in pipe_modules: 65 | if (name := module.__name__) in sys.modules: 66 | log.info(f"Unloading {name}") 67 | del sys.modules[name] 68 | 69 | 70 | try: 71 | 72 | def silent_startupinfo() -> subprocess.STARTUPINFO | None: # type: ignore[name-defined] 73 | """Returns a Windows-only object to make sure tasks launched through 74 | subprocess don't open a cmd window. 75 | 76 | Returns: 77 | subprocess.STARTUPINFO -- the properly configured object if we are on 78 | Windows, otherwise None 79 | """ 80 | startupinfo = None 81 | if platform.system() == "Windows": 82 | startupinfo = subprocess.STARTUPINFO() # type: ignore[attr-defined] 83 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore[attr-defined] 84 | return startupinfo 85 | except Exception: 86 | 87 | def silent_startupinfo() -> Any | None: 88 | pass 89 | 90 | 91 | __all__ = [ 92 | "checkbox_callback_helper", 93 | "dict_index", 94 | "dotdict", 95 | "log_errors", 96 | "reload_pipe", 97 | "silent_startupinfo", 98 | "FileManager", 99 | "Playblaster", 100 | ] 101 | -------------------------------------------------------------------------------- /pipeline/pipe/util/playblaster.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ffmpeg # type: ignore[import-untyped] 4 | import logging 5 | import os 6 | import shutil 7 | 8 | from abc import ABCMeta, abstractmethod 9 | from dataclasses import dataclass 10 | from enum import Enum 11 | from pathlib import Path 12 | 13 | from typing import TYPE_CHECKING 14 | 15 | if TYPE_CHECKING: 16 | from typing import Any 17 | from typing_extensions import Self 18 | from pipe.struct.db import Shot 19 | 20 | 21 | log = logging.getLogger(__name__) 22 | 23 | 24 | @dataclass(frozen=True) 25 | class FFMpegPreset: 26 | ext: str 27 | out_kwargs: dict[str, Any] 28 | 29 | def __hash__(self): 30 | return hash(frozenset(self.out_kwargs.items())) 31 | 32 | 33 | class Playblaster(metaclass=ABCMeta): 34 | """Parent class for creating playblasters. Uses FFmpeg to encode videos""" 35 | 36 | _shot: Shot 37 | _in_context: bool 38 | 39 | FR = 24 40 | 41 | class PRESET(FFMpegPreset, Enum): 42 | EDIT_SQ = ( 43 | "mov", 44 | { 45 | "vcodec": "dnxhd", 46 | "pix_fmt": "yuv422p", 47 | "vprofile": "dnxhr_sq", 48 | # this number comes from Avid's table in the DNxHD whitepaper 49 | "video_bitrate": "124M", 50 | }, 51 | ) 52 | EDIT_HQX = ( 53 | "mov", 54 | { 55 | "vcodec": "dnxhd", 56 | "pix_fmt": "yuv422p10le", 57 | "vprofile": "dnxhr_hqx", 58 | "video_bitrate": "188M", 59 | }, 60 | ) 61 | WEB = ( 62 | "mp4", 63 | { 64 | "vcodec": "libx264", 65 | "preset": "veryslow", 66 | "tune": "animation", 67 | "crf": 20, 68 | }, 69 | ) 70 | 71 | def __init__(self) -> None: 72 | pass 73 | 74 | @abstractmethod 75 | def _write_images(self, path: str) -> None: 76 | pass 77 | 78 | def __enter__(self) -> Self: 79 | self._in_context = True 80 | return self 81 | 82 | def __call__(self, shot: Shot, *args): 83 | self._shot = shot 84 | return self 85 | 86 | def __exit__(self, *args) -> None: 87 | self._in_context = False 88 | 89 | def _do_playblast( 90 | self, 91 | out_paths: dict[PRESET, list[Path | str]] | None = None, 92 | tails: tuple[int, int] = (0, 0), 93 | ) -> None: 94 | if not self._in_context: 95 | raise RuntimeError("_do_playblast not called from within context self") 96 | 97 | if not out_paths: 98 | out_paths = {} 99 | 100 | tempdir = Path(os.getenv("TMPDIR", os.getenv("TEMP", "tmp"))).resolve() 101 | 102 | FILENAME = "lnd_pb_temp." + self._shot.code 103 | 104 | # remove any old playblasts 105 | for p in tempdir.glob(FILENAME + "*"): 106 | p.unlink() 107 | 108 | # do the playblast 109 | self._write_images(str(tempdir / FILENAME)) 110 | 111 | # use ffmpeg to encode the video 112 | start_frame = int(self._shot.cut_in) - tails[0] 113 | images = ffmpeg.input( 114 | str(tempdir / FILENAME) + ".%04d.png", 115 | start_number=start_frame, 116 | r=self.FR, 117 | # precisely define input colorspace 118 | colorspace="bt709", 119 | color_trc="iec61966-2-1", 120 | ) 121 | for preset, paths in out_paths.items(): 122 | out_filename = str(tempdir / FILENAME) + "." + preset.ext 123 | ffmpeg.output( 124 | images, 125 | out_filename, 126 | **preset.out_kwargs, 127 | timecode="00:00:{:02}:{:02}".format( 128 | start_frame // self.FR, 129 | start_frame % self.FR, 130 | ), 131 | r=self.FR, 132 | ).overwrite_output().run() 133 | 134 | # copy video out of tempdir 135 | for path in (Path(str(p) + "." + preset.ext) for p in paths): 136 | if not path.parent.exists(): 137 | path.parent.mkdir(mode=0o770, parents=True) 138 | shutil.copyfile(out_filename, path) 139 | 140 | # clean up if not in debug mode 141 | if not log.isEnabledFor(logging.DEBUG): 142 | for p in tempdir.glob(FILENAME + "*"): 143 | p.unlink() 144 | 145 | @abstractmethod 146 | def playblast(self) -> None: 147 | """Function to be called by the user to trigger a playblast. 148 | This should call `_do_playblast` from within a `with self(...)` 149 | block. 150 | Looks something like: 151 | >>> def playblast(self) -> None: 152 | >>> with self(shot): 153 | >>> super()._do_playblast([filepath]) 154 | """ 155 | pass 156 | -------------------------------------------------------------------------------- /pipeline/pipe/util/struct.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | 8 | if TYPE_CHECKING: 9 | from typing import Any, ClassVar, Protocol, TypeVar 10 | 11 | KT = TypeVar("KT") 12 | VT = TypeVar("VT") 13 | 14 | class IsDataclass(Protocol): 15 | __dataclass_fields__: ClassVar[dict[str, Any]] 16 | __match_args__: ClassVar[tuple[str]] 17 | 18 | 19 | log = logging.getLogger(__name__) 20 | 21 | 22 | class dotdict(dict): 23 | """dot notation access to dictionary attributes""" 24 | 25 | __getattr__ = dict.get 26 | __setattr__ = dict.__setitem__ # type: ignore[assignment] 27 | __delattr__ = dict.__delitem__ # type: ignore[assignment] 28 | 29 | 30 | def dataclass_as_tuple(dc: IsDataclass) -> tuple[Any]: 31 | return tuple((getattr(dc, a) for a in dc.__match_args__)) 32 | 33 | 34 | def dict_index(d: dict[KT, VT], v: VT) -> KT: 35 | """List index function for dicts""" 36 | return list(d.keys())[list(d.values()).index(v)] 37 | -------------------------------------------------------------------------------- /pipeline/shared/__init__.py: -------------------------------------------------------------------------------- 1 | from . import util as util 2 | -------------------------------------------------------------------------------- /pipeline/shared/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import importlib 4 | import importlib.util 5 | import platform 6 | import subprocess 7 | 8 | from inspect import getmembers, isabstract, isclass 9 | from pathlib import Path 10 | 11 | from env import production_path as _prp 12 | 13 | 14 | def find_implementation(cls: type, module: str, package: str | None = None) -> type: 15 | """Find an implementation of the class in the specified module.""" 16 | # Check if the specified module exists 17 | if importlib.util.find_spec(module, package): 18 | # Import the module 19 | imported_module = importlib.import_module(module, package) 20 | 21 | # Check if the submodule contains an implementation of the class 22 | classes = getmembers( 23 | imported_module, 24 | lambda obj: isclass(obj) and not isabstract(obj) and issubclass(obj, cls), 25 | ) 26 | 27 | # Check if more or less than one implementation was found 28 | if len(classes) < 1: 29 | raise AssertionError( 30 | f"module '{module}' does not contain an " 31 | f"implementation of class '{cls.__name__}'" 32 | ) 33 | elif len(classes) > 1: 34 | raise AssertionError( 35 | f"module '{module}' contains multiple " 36 | f"implementations of class '{cls.__name__}'" 37 | ) 38 | 39 | # Return the implementing class 40 | return classes[0][1] 41 | 42 | else: 43 | raise ValueError(f"could not find module '{module}'") 44 | 45 | 46 | def fix_launcher_metadata() -> None: 47 | if platform.system() != "Linux": 48 | return 49 | try: 50 | procs = [ 51 | subprocess.Popen( 52 | [ 53 | "gio", 54 | "set", 55 | str(item), 56 | "metadata::caja-trusted-launcher", 57 | "true", 58 | ] 59 | ) 60 | for item in get_pipe_path().parent.iterdir() 61 | if item.suffix == ".desktop" 62 | ] 63 | for p in procs: 64 | p.wait() 65 | 66 | except Exception: 67 | pass 68 | 69 | 70 | def get_anim_path() -> Path: 71 | return get_production_path().parent / "anim" 72 | 73 | 74 | def get_asset_path() -> Path: 75 | return get_production_path() / "asset" 76 | 77 | 78 | def get_character_path() -> Path: 79 | return get_production_path().parent / "character" 80 | 81 | 82 | def get_edit_path() -> Path: 83 | return get_production_path().parent / "edit/shots" 84 | 85 | 86 | def get_pipe_path() -> Path: 87 | return Path(__file__).resolve().parents[1] 88 | 89 | 90 | def get_previs_path() -> Path: 91 | return get_production_path().parent / "previs" 92 | 93 | 94 | def get_production_path() -> Path: 95 | return _prp 96 | 97 | 98 | def get_rigging_path() -> Path: 99 | return get_character_path() / "Rigging" 100 | 101 | 102 | def resolve_mapped_path(path: str | Path) -> Path: 103 | """Windows mapped drive workaround. Adapated from: https://bugs.python.org/msg309160""" 104 | path = Path(path).resolve() 105 | 106 | if platform.system() != "Windows": 107 | return path 108 | 109 | mapped_paths = [] 110 | for drive in "ZYXWVUTSRQPONMLKJIHGFEDCBA": 111 | root = Path("{}:/".format(drive)) 112 | try: 113 | mapped_paths.append(root / path.relative_to(root.resolve())) 114 | except (ValueError, OSError): 115 | pass 116 | return min(mapped_paths, key=lambda x: len(str(x)), default=path) 117 | -------------------------------------------------------------------------------- /pipeline/sitecustomize.py: -------------------------------------------------------------------------------- 1 | import site 2 | from pathlib import Path 3 | 4 | ROOT = Path(__file__).parents[1] 5 | venv_version: str 6 | 7 | with open(ROOT / ".venv/pyvenv.cfg", "r") as cfg: 8 | for line in cfg: 9 | if not line.startswith("version_info"): 10 | continue 11 | version_str = line.split(" = ")[1] 12 | venv_version = "python" + ".".join(version_str.split(".", 2)[:2]) 13 | break 14 | 15 | SITEDIR = ROOT / ".venv/lib" / venv_version / "site-packages" 16 | site.addsitedir(str(SITEDIR)) 17 | -------------------------------------------------------------------------------- /pipeline/software/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "houdini", 3 | "maya", 4 | "nuke", 5 | "substance_designer", 6 | "substance_painter", 7 | "unreal", 8 | ] 9 | -------------------------------------------------------------------------------- /pipeline/software/baseclass.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import logging 5 | import os 6 | import subprocess 7 | 8 | from typing import TYPE_CHECKING 9 | 10 | if TYPE_CHECKING: 11 | import typing 12 | 13 | from shared.util import fix_launcher_metadata 14 | 15 | from .interface import DCCInterface, DCCLocalizerInterface 16 | 17 | """Baseclasses for interacting with DCCs""" 18 | 19 | log = logging.getLogger(__name__) 20 | 21 | 22 | class DCC(DCCInterface): 23 | command: str 24 | args: list[str] | None 25 | env_vars: typing.Mapping[str, int | str | None] 26 | pre_launch_tasks: typing.Callable[[], None] 27 | 28 | def __init__( 29 | self, 30 | command: str, 31 | args: typing.Sequence[str] | None = None, 32 | env_vars: typing.Mapping[str, int | str | None] | None = None, 33 | pre_launch_tasks: typing.Callable[[], None] | None = None, 34 | ) -> None: 35 | """Initialize DCC object. 36 | 37 | Keyword arguments: 38 | - command -- the command to launch the software 39 | - args -- the arguments to pass to the command 40 | """ 41 | 42 | if args is None: 43 | args = [] 44 | 45 | self.command = command 46 | self.args = list(args) if args else None 47 | self.env_vars = env_vars or {} 48 | self.pre_launch_tasks = pre_launch_tasks or (lambda: None) 49 | 50 | def _get_env_vars( 51 | self, env_vars: typing.Mapping[str, int | str | None] | None = None 52 | ) -> dict[str, str]: 53 | """(Un)Set environment variables to their associated values. 54 | 55 | All values will be converted to strings. If a value is None, 56 | that environment variable will be unset. 57 | """ 58 | BASE_ENVIRON = "BASE_ENVIRON" 59 | 60 | if BASE_ENVIRON not in os.environ: 61 | venv = os.environ.copy() 62 | venv[BASE_ENVIRON] = json.dumps(venv) 63 | else: 64 | venv = json.loads(os.environ[BASE_ENVIRON]) 65 | 66 | if env_vars is None: 67 | env_vars = self.env_vars 68 | 69 | log.info("(Un)setting environment vars") 70 | 71 | for key, val in env_vars.items(): 72 | if val is None: 73 | if key in venv: 74 | del venv[key] 75 | else: 76 | venv[key] = str(val) 77 | 78 | PYTHONPATH = "PYTHONPATH" 79 | if PYTHONPATH not in venv: 80 | venv[PYTHONPATH] = "" 81 | 82 | print(venv[PYTHONPATH]) 83 | return venv 84 | 85 | def launch( 86 | self, 87 | command: str | None = None, 88 | args: typing.Sequence[str] | None = None, 89 | pre_launch_tasks: typing.Callable[[], None] | None = None, 90 | ) -> None: 91 | """Launch the software with the specified arguments. 92 | 93 | Passing in optional parameters will override their default 94 | values. 95 | """ 96 | 97 | if command is None: 98 | command = self.command 99 | if args is None: 100 | args = self.args 101 | if pre_launch_tasks is None: 102 | pre_launch_tasks = self.pre_launch_tasks 103 | 104 | fix_launcher_metadata() 105 | pre_launch_tasks() 106 | venv = self._get_env_vars() 107 | 108 | log.info("Launching the software") 109 | log.debug(f"Command: {command}, Args: {args}") 110 | subprocess.call([command] + list(args or []), env=venv) 111 | 112 | 113 | class DCCLocalizer(DCCLocalizerInterface): 114 | id: str 115 | 116 | def __init__(self, id: str) -> None: 117 | self.id = id 118 | -------------------------------------------------------------------------------- /pipeline/software/houdini/__init__.py: -------------------------------------------------------------------------------- 1 | from .dcc import HoudiniDCC as HoudiniDCC 2 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini19.5/presets/Lop/hdprmanrenderproperties.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini19.5/presets/Lop/hdprmanrenderproperties.idx -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini19.5/presets/Lop/rendersettings.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini19.5/presets/Lop/rendersettings.idx -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini19.5/presets/Lop/sdm223-main-LnD_Load_Layers-1.0.idx: -------------------------------------------------------------------------------- 1 | INDXPermanent Defaultssg)�#PSI_PRESET 2 | version 2.0a 3 | opvalues 4 | { 5 | version 0.8 6 | shot [ 0 locks=0 ] ( $JOB/`@SHOT` ) 7 | sepparm [ 0 locks=0 ] ( ) 8 | camera_enable [ 0 locks=0 ] ( "on" ) 9 | camera_path [ 0 locks=0 ] ( `chs(\"./shot\")`/cam/cam.usd ) 10 | camera_mute [ 0 locks=0 ] ( "off" ) 11 | camera_reload [ 0 locks=0 ] ( 0 ) 12 | layout_enable [ 0 locks=0 ] ( "on" ) 13 | layout_path [ 0 locks=0 ] ( "" ) 14 | layout_mute [ 0 locks=0 ] ( "off" ) 15 | layout_reload [ 0 locks=0 ] ( 0 ) 16 | layout_over_enable [ 0 locks=0 ] ( "on" ) 17 | _filepath2 [ 0 locks=0 ] ( `chs(\"./shot\")`/set/maya_override.usd ) 18 | layout_over_mute [ 0 locks=0 ] ( "off" ) 19 | layout_reload2 [ 0 locks=0 ] ( 0 ) 20 | num_files [ 0 locks=0 ] ( 2 ) 21 | rig_reload [ 0 locks=0 ] ( 0 ) 22 | anim_enable [ 0 locks=0 ] ( "on" ) 23 | anim_path [ 0 locks=0 ] ( `chs(\"./shot\")`/anim/usd/main.usd ) 24 | anim_mute [ 0 locks=0 ] ( "off" ) 25 | anim_reload [ 0 locks=0 ] ( 0 ) 26 | cfx_enable [ 0 locks=0 ] ( "on" ) 27 | cfx_path [ 0 locks=0 ] ( `chs(\"./shot\")`/cfx/usd/main.usd ) 28 | cfx_mute [ 0 locks=0 ] ( "off" ) 29 | cfx_reload [ 0 locks=0 ] ( 0 ) 30 | fx_enable [ 0 locks=0 ] ( "on" ) 31 | fx_path [ 0 locks=0 ] ( `chs(\"./shot\")`/fx/usd/main.usd ) 32 | fx_mute [ 0 locks=0 ] ( "off" ) 33 | fx_reload [ 0 locks=0 ] ( 0 ) 34 | lighting_enable [ 0 locks=0 ] ( "on" ) 35 | lighting_path [ 0 locks=0 ] ( `chs(\"./shot\")`/lighting/usd/main.usd ) 36 | lighting_mute [ 0 locks=0 ] ( "off" ) 37 | lighting_reload [ 0 locks=0 ] ( 0 ) 38 | enable1 [ 0 locks=0 ] ( "on" ) 39 | filepath1 [ 0 locks=0 ] ( $JOB/character/rayden/usd/main.usd ) 40 | mute1 [ 0 locks=0 ] ( "off" ) 41 | sublayerfile_group1 [ 0 locks=0 ] ( 0 ) 42 | enable2 [ 0 locks=0 ] ( "on" ) 43 | filepath2 [ 0 locks=0 ] ( $JOB/character/robin/usd/main.usd ) 44 | mute2 [ 0 locks=0 ] ( "off" ) 45 | sublayerfile_group2 [ 0 locks=0 ] ( 0 ) 46 | } 47 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/houdini.pref: -------------------------------------------------------------------------------- 1 | general.desk.val := "Solungeon"; 2 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/.gitignore: -------------------------------------------------------------------------------- 1 | rman_kaboom_box.hda 2 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/Recipes.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/Recipes.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/INDEX__SECTION: -------------------------------------------------------------------------------- 1 | Operator: sdm223::generic_ascii_hda::1.0 2 | Label: Generic ASCII HDA 3 | Path: oplib:/sdm223::Lop/generic_ascii_hda::1.0?sdm223::Lop/generic_ascii_hda::1.0 4 | Icon: LOP_subnet 5 | Table: Lop 6 | License: 7 | Extra: 8 | User: 9 | Inputs: 0 to 1 10 | Subnet: true 11 | Python: false 12 | Empty: false 13 | Modified: Tue Nov 5 16:23:22 2024 14 | 15 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/Sections.list: -------------------------------------------------------------------------------- 1 | "" 2 | INDEX__SECTION INDEX_SECTION 3 | houdini.hdalibrary houdini.hdalibrary 4 | sdm223_8_8Lop_1generic__ascii__hda_8_81.0 sdm223::Lop/generic_ascii_hda::1.0 5 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/houdini.hdalibrary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/houdini.hdalibrary -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Contents.createtimes: -------------------------------------------------------------------------------- 1 | { 2 | "hdaroot/warn_no_representation_set.def":1708980551, 3 | "hdaroot/output0.def":1698215383, 4 | "hdaroot.def":1729551657, 5 | "hdaroot/reference.def":1698150558 6 | } 7 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Contents.houdini_versions: -------------------------------------------------------------------------------- 1 | { 2 | "values":["19.5.640" 3 | ], 4 | "indexes":{ 5 | "hdaroot/output0.userdata":0, 6 | "hdaroot/reference.userdata":0, 7 | "hdaroot/warn_no_representation_set.userdata":0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Contents.mime: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Contents.mime -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Contents.modtimes: -------------------------------------------------------------------------------- 1 | { 2 | "hdaroot/warn_no_representation_set.def":1729552033, 3 | "hdaroot/output0.def":1729551715, 4 | "hdaroot.def":1729552052, 5 | "hdaroot/reference.def":1729552032 6 | } 7 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Contents.dir/Sections.list: -------------------------------------------------------------------------------- 1 | "" 2 | Contents.mime Contents.mime 3 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/CreateScript: -------------------------------------------------------------------------------- 1 | # Automatically generated script 2 | \set noalias = 1 3 | # 4 | # Creation script for sdm223::generic_ascii_hda::1.0 operator 5 | # 6 | 7 | if ( "$arg1" == "" ) then 8 | echo This script is intended as a creation script 9 | exit 10 | endif 11 | 12 | # Node $arg1 (sdm223::Lop/generic_ascii_hda::1.0) 13 | opexprlanguage -s hscript $arg1 14 | opuserdata -n '___Version___' -v '' $arg1 15 | opuserdata -n 'wirestyle' -v 'rounded' $arg1 16 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/DialogScript: -------------------------------------------------------------------------------- 1 | # Dialog script for sdm223::generic_ascii_hda::1.0 automatically generated 2 | 3 | { 4 | name sdm223::generic_ascii_hda::1.0 5 | script load_asset::1.0 6 | label "Generic Unpacked HDA" 7 | 8 | help { 9 | "" 10 | } 11 | 12 | inputlabel 1 "Input Stage" 13 | inputlabel 2 "Input 2" 14 | inputlabel 3 "Input 3" 15 | inputlabel 4 "Input 4" 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/ExtraFileOptions: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Help: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Help -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/InternalFileOptions: -------------------------------------------------------------------------------- 1 | { 2 | "nodeconntype":{ 3 | "type":"bool", 4 | "value":false 5 | }, 6 | "nodeparmtype":{ 7 | "type":"bool", 8 | "value":false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Sections.list: -------------------------------------------------------------------------------- 1 | "" 2 | DialogScript DialogScript 3 | CreateScript CreateScript 4 | InternalFileOptions InternalFileOptions 5 | Contents.gz Contents.gz 6 | TypePropertiesOptions TypePropertiesOptions 7 | Tools.shelf Tools.shelf 8 | Help Help 9 | ExtraFileOptions ExtraFileOptions 10 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Tools.shelf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/Tools.shelf -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/generic_ascii_hda.hda/sdm223_8_8Lop_1generic__ascii__hda_8_81.0/TypePropertiesOptions: -------------------------------------------------------------------------------- 1 | CheckExternal := 1; 2 | ContentsCompressionType := 1; 3 | ForbidOutsideParms := 1; 4 | GzipContents := 1; 5 | LockContents := 1; 6 | MakeDefault := 1; 7 | ParmsFromVfl := 0; 8 | PrefixDroppedParmLabel := 0; 9 | PrefixDroppedParmName := 0; 10 | SaveCachedCode := 0; 11 | SaveIcon := 1; 12 | SaveSpareParms := 0; 13 | UnlockOnCreate := 0; 14 | UseDSParms := 1; 15 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_dbclark.render_uv_variant.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_dbclark.render_uv_variant.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_anim_postprocess.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_anim_postprocess.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_asset_cluster.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_asset_cluster.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_camera_shake.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_camera_shake.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_render_layer.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_render_layer.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_render_layer.1.1.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm222.lnd_render_layer.1.1.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.LnD_Character_Config.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.LnD_Character_Config.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.LnD_Import_Camera.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.LnD_Import_Camera.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_BaseMaterial.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_BaseMaterial.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Char_Setup.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Char_Setup.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Hair_Asset.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Hair_Asset.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Lookdev.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Lookdev.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Shot_Hair.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.LnD_Shot_Hair.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.end_sublayer.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.end_sublayer.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.lnd_componentconfig.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.dev.lnd_componentconfig.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.lnd_nodelayouts.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.lnd_nodelayouts.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.main.LnD_Load_Layers.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.main.LnD_Load_Layers.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.main.LnD_MatLib.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sdm223.main.LnD_MatLib.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/lop_sopmodify2.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/lop_sopmodify2.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/otls/sop_sdm223.main.LnD_Pack_UDIMs.1.0.hdanc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/houdini/hsite/houdini20.5/otls/sop_sdm223.main.LnD_Pack_UDIMs.1.0.hdanc -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/packages/AeSVG.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | { 4 | "AESVG": "${HSITE}/ae_SVG" 5 | } 6 | ], 7 | "path": [ 8 | "$AESVG" 9 | ] 10 | } -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/packages/LYNX.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | { 4 | "LYNX": "$PIPE_PATH/lib/VFX-LYNX" 5 | }, 6 | { 7 | "HOUDINI_PATH": [ 8 | { 9 | "value": "$LYNX/plugins/SideFX/Houdini", 10 | "method": "append" 11 | } 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/packages/MOPS.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | { 4 | "MOPS": "$PIPE_PATH/lib/MOPS" 5 | }, 6 | { 7 | "HOUDINI_PYTHONWARNINGS": "ignore" 8 | }, 9 | { 10 | "HOUDINI_PATH": [ 11 | { 12 | "value": "$MOPS", 13 | "method": "append" 14 | } 15 | ] 16 | } 17 | ], 18 | "path": "$MOPS" 19 | } -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/packages/renderman_for_houdini.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_package_once": true, 3 | "env": [ 4 | { 5 | "RMANTREE": {"houdini_os == 'linux'": "/groups/dungeons/production/opt/pixar/RenderManProServer-26.3"} 6 | }, 7 | { 8 | "RMANTREE": {"houdini_os == 'windows'": "G:/dungeons/production/PFiles/Pixar/RenderManProServer-26.3"} 9 | }, 10 | { 11 | "RFHTREE": {"houdini_os == 'linux'": "/groups/dungeons/production/opt/pixar/RenderManForHoudini-26.3"} 12 | }, 13 | { 14 | "RFHTREE": {"houdini_os == 'windows'": "G:/dungeons/production/PFiles/Pixar/RenderManForHoudini-26.3/"} 15 | 16 | }, 17 | { 18 | "RFH_HOUDINI_VERS" : 19 | [ 20 | {"houdini_version<'21.0' and houdini_version>='20.5.278'": "20.5.278"}, 21 | {"houdini_version<'20.5.278' and houdini_version>='20.5.278'": "20.5.278"}, 22 | {"houdini_version<'20.5' and houdini_version>='20.0.751'": "20.0.751"}, 23 | {"houdini_version<'20.0.751' and houdini_version>='20.0.751'": "20.0.751"}, 24 | {"houdini_version<'20.0.751' and houdini_version>='20.0.724'": "20.0.724"}, 25 | {"houdini_version<'20.0.724' and houdini_version>='20.0.724'": "20.0.724"}, 26 | {"houdini_version<'20.0.724' and houdini_version>='20.0.653'": "20.0.653"}, 27 | {"houdini_version<'20.0.653' and houdini_version>='20.0.653'": "20.0.653"} 28 | ] 29 | }, 30 | { 31 | "RFH_PY_VERS": 32 | [ 33 | { "houdini_python=='python2'": "2.7" }, 34 | { "houdini_python=='python3' and houdini_version<'19.5'": "3.7" }, 35 | { "houdini_python=='python3.7'": "3.7" }, 36 | { "houdini_python=='python3.9'": "3.9" }, 37 | { "houdini_python=='python3.10'": "3.10" }, 38 | { "houdini_python=='python3.11'": "3.11" } 39 | ] 40 | }, 41 | { "HOUDINI_PATH": "${RFHTREE}/${RFH_PY_VERS}/${RFH_HOUDINI_VERS}" }, 42 | { 43 | "RMAN_PROCEDURALPATH": 44 | { 45 | "value": "${RFHTREE}/${RFH_PY_VERS}/${RFH_HOUDINI_VERS}/openvdb", 46 | "method": "prepend" 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/packages/tlops.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | { 4 | "TLOPS": "$PIPE_PATH/lib/tractor-lops" 5 | }, 6 | { 7 | "HOUDINI_PATH": [ 8 | { 9 | "value": "$TLOPS", 10 | "method": "append" 11 | } 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/python3.11libs/pythonrc.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | # allow embedded variables to update 4 | hou.allowEnvironmentToOverwriteVariable("HOUDINI_ASSETGALLERY_DB_FILE", True) 5 | hou.allowEnvironmentToOverwriteVariable("JOB", True) 6 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/python3.11libs/uiready.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | # set the default flipbook resolution 4 | scene = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer) 5 | fb_settings = scene.flipbookSettings() # type: ignore[union-attr] 6 | fb_settings.resolution((1920, 816)) 7 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/456.py: -------------------------------------------------------------------------------- 1 | import hou 2 | import os 3 | 4 | from shared.util import get_production_path, resolve_mapped_path 5 | 6 | # create embedded $ASSET variable if needed 7 | hip_path = resolve_mapped_path(hou.hscriptStringExpression("$HIP")) 8 | if any(get_production_path() / p in hip_path.parents for p in ["asset", "character"]): 9 | if not hou.contextOption("ASSET"): 10 | hou.setContextOption("ASSET", hip_path.name) 11 | 12 | # ensure ASSETGALLERY_DATA_SOURCE is correct 13 | hou.hscript( 14 | f"setenv ASSETGALLERY_DATA_SOURCE='{os.getenv('HOUDINI_ASSETGALLERY_DATA_SOURCE')}'" 15 | ) 16 | 17 | # mark any node referencing above vars as dirty 18 | hou.hscript("varchange") 19 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/assetreference_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | from pathlib import Path 4 | 5 | from shared.util import get_production_path 6 | 7 | """This OnCreated hook runs whenever an Asset Reference node is created and 8 | ensures that the filepath is always relative to $JOB""" 9 | 10 | 11 | def update_filepath( 12 | node: hou.Node, parm_tuple: hou.ParmTuple, event_type: hou.nodeEventType, **kwargs 13 | ) -> None: 14 | if parm_tuple.name() != "filepath": 15 | return 16 | # this callback only needs to run once 17 | node.removeEventCallback([event_type], callback=update_filepath) # type: ignore[list-item] 18 | 19 | path = Path(parm_tuple.evalAsStrings()[0]) 20 | ppth = get_production_path() 21 | 22 | if not path.is_relative_to(ppth): 23 | if ppth.anchor == "G:\\": 24 | path = Path("G:/") / path.relative_to("/groups") 25 | else: 26 | path = Path("/groups") / path.relative_to("G:/") 27 | 28 | parm_tuple.set(("$JOB/" + str(path.relative_to(ppth)).replace("\\", "/"),)) 29 | 30 | 31 | def update_destination_prim( 32 | node: hou.Node, parm_tuple: hou.ParmTuple, event_type: hou.nodeEventType, **kwargs 33 | ) -> None: 34 | if parm_tuple.name() != "primpath": 35 | return 36 | # this callback only needs to run once 37 | node.removeEventCallback([event_type], callback=update_destination_prim) # type: ignore[list-item] 38 | 39 | # don't mess with this if we're inside a Layout node 40 | if node.parent().name() == "ASSETS": 41 | return 42 | 43 | primpath = parm_tuple.evalAsStrings()[0] 44 | parm_tuple.set((f"`@PATH`{primpath}",)) 45 | 46 | 47 | try: 48 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 49 | if not me.parent().name() == "ASSETS": 50 | for callback in (update_filepath, update_destination_prim): 51 | me.addEventCallback([hou.nodeEventType.ParmTupleChanged], callback=callback) 52 | except Exception: # in case this is created as a locked node 53 | pass 54 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/hdprmanrenderproperties_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | resolutionx = me.parm("resolutionx") 6 | resolutiony = me.parm("resolutiony") 7 | aspectRatioConformPolicy = me.parm("aspectRatioConformPolicy") 8 | assert resolutionx is not None 9 | assert resolutiony is not None 10 | assert aspectRatioConformPolicy is not None 11 | resolutionx.set(1920) 12 | resolutiony.set(816) 13 | aspectRatioConformPolicy.set("cropAperture") 14 | except Exception: # in case this is created as a locked node 15 | pass 16 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/instancer_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | primpath = me.parm("primpath") 6 | assert primpath is not None 7 | primpath.set("`@PATH`/$OS") 8 | except Exception: # in case this is created as a locked node 9 | pass 10 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/materiallibrary_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | tabmenumask = me.parm("tabmenumask") 6 | assert tabmenumask is not None 7 | tabmenumask.set("risnet USD ^hmtlx* MaterialX collect parameter subnet") 8 | except Exception: # in case this is created as a locked node 9 | pass 10 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/rendersettings_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | res_mode = me.parm("res_mode") 6 | resolution1 = me.parm("resolution1") 7 | resolution2 = me.parm("resolution2") 8 | aspectRatioConformPolicy = me.parm("aspectRatioConformPolicy") 9 | assert res_mode is not None 10 | assert resolution1 is not None 11 | assert resolution2 is not None 12 | assert aspectRatioConformPolicy is not None 13 | res_mode.set("manual") 14 | resolution1.set(1920) 15 | resolution2.set(816) 16 | aspectRatioConformPolicy.set("cropAperture") 17 | except Exception: # in case this is created as a locked node 18 | pass 19 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/tlops-tractor_configure-1.0_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | overscan = me.parm("overscan") 6 | renderer = me.parm("renderer") 7 | assert overscan is not None 8 | assert renderer is not None 9 | overscan.set(6.0) 10 | renderer.set("HdPrmanLoaderRendererPlugin") 11 | except Exception: # in case this is created as a locked node 12 | pass 13 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/lop/tlops-tractor_denoise-1.0_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | from shared.util import get_production_path 3 | 4 | 5 | try: 6 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 7 | rmantree = me.parm("rmantree_override") 8 | passthrough = me.parm("passthrough") 9 | assert rmantree is not None 10 | assert passthrough is not None 11 | rmantree.set(str(get_production_path() / "opt/pixar/RenderManProServer-26.3")) 12 | passthrough.set( 13 | " ".join( 14 | "/Render/Products/Vars/" + var 15 | for var in ["__Nworld", "__Pworld", "__st", "normal"] 16 | ) 17 | ) 18 | except Exception: # in case this is created as a locked node 19 | pass 20 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/obj/lopimportcam_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | resx = me.parm("resx") 6 | resy = me.parm("resy") 7 | assert resx is not None 8 | assert resy is not None 9 | # set the default resolution 10 | resx.set(1920) 11 | resy.deleteAllKeyframes() # remove the expression 12 | resy.set(816) 13 | except Exception: # in case this is created as a locked node 14 | pass 15 | -------------------------------------------------------------------------------- /pipeline/software/houdini/hsite/houdini20.5/scripts/sop/filecache-2.0_OnCreated.py: -------------------------------------------------------------------------------- 1 | import hou 2 | 3 | try: 4 | me: hou.Node = kwargs["node"] # type: ignore[name-defined] # noqa: F821 5 | basename = me.parm("basename") 6 | assert basename is not None 7 | basename.set("$OS") 8 | except Exception: # in case this is created as a locked node 9 | pass 10 | -------------------------------------------------------------------------------- /pipeline/software/interface.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABCMeta, abstractmethod 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | import typing 7 | 8 | """Interfaces for interacting with DCCs""" 9 | 10 | 11 | class DCCInterface(metaclass=ABCMeta): 12 | """interface for DCCs""" 13 | 14 | @abstractmethod 15 | def __init__(self): 16 | """Initialize the DCC""" 17 | raise NotImplementedError 18 | 19 | @abstractmethod 20 | def launch(self) -> None: 21 | """Launch the software""" 22 | raise NotImplementedError 23 | 24 | 25 | class DCCLocalizerInterface(metaclass=ABCMeta): 26 | """interface for functions that need to be localized to the DCC""" 27 | 28 | @abstractmethod 29 | def __init__(self) -> None: 30 | """Initialize the pipe instance""" 31 | raise NotImplementedError 32 | 33 | @abstractmethod 34 | def get_main_qt_window(self) -> typing.Any: 35 | """Get the QT object representing the main application window. 36 | Use for the parent of other QT popups""" 37 | raise NotImplementedError 38 | 39 | @abstractmethod 40 | def is_headless(self) -> bool: 41 | """Check if this is a headless environment (no GUI)""" 42 | raise NotImplementedError 43 | -------------------------------------------------------------------------------- /pipeline/software/maya/__init__.py: -------------------------------------------------------------------------------- 1 | from .dcc import MayaDCC as MayaDCC 2 | -------------------------------------------------------------------------------- /pipeline/software/maya/dcc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import platform 6 | import shutil 7 | 8 | from pathlib import Path 9 | from typing import TYPE_CHECKING 10 | 11 | if TYPE_CHECKING: 12 | import typing 13 | 14 | from ..baseclass import DCC 15 | from shared.util import get_production_path, get_rigging_path 16 | from env import Executables 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | class MayaDCC(DCC): 22 | """Maya DCC class""" 23 | 24 | shelf_path: str 25 | 26 | def __init__( 27 | self, is_python_shell: bool = False, extra_args: list[str] | None = None 28 | ) -> None: 29 | this_path = Path(__file__).resolve() 30 | pipe_path = this_path.parents[2] 31 | 32 | system = platform.system() 33 | 34 | self.shelf_path = str( 35 | Path(os.getenv("TMPDIR", os.getenv("TEMP", "tmp"))).resolve() / "shelves" 36 | ) 37 | 38 | env_vars: typing.Mapping[str, int | str | None] | None 39 | env_vars = { 40 | "DCC": str(this_path.parent.name), 41 | "DWPICKER_PROJECT_DIRECTORY": str(get_rigging_path() / "Pickers"), 42 | "MAYA_SHELF_PATH": self.shelf_path, 43 | "MAYAUSD_EXPORT_MAP1_AS_PRIMARY_UV_SET": 1, 44 | "MAYAUSD_IMPORT_PRIMARY_UV_SET_AS_MAP1": 1, 45 | "PYTHONPATH": os.pathsep.join( 46 | [ 47 | str(pipe_path), 48 | str(this_path.parent / "scripts"), 49 | str(this_path.parent / "userSetup"), 50 | str(this_path.parent / "scripts/studiolibrary/src"), 51 | os.environ.get("RMANTREE", "") + "/bin", 52 | ] 53 | ), 54 | "OCIO": str(pipe_path / "lib/ocio/love-v01/config.ocio"), 55 | "QT_FONT_DPI": os.getenv("MAYA_FONT_DPI") if system == "Linux" else None, 56 | "QT_PLUGIN_PATH": None, 57 | # Configure Asset Resolver 58 | "PXR_AR_DEFAULT_SEARCH_PATH": os.pathsep.join( 59 | [ 60 | str(get_production_path()), 61 | ] 62 | ), 63 | # USD Plugins 64 | "PXR_PLUGINPATH_NAME": os.pathsep.join( 65 | [ 66 | str(pipe_path / "lib/usd/kinds"), 67 | os.environ.get("PXR_PLUGINPATH_NAME", ""), 68 | ] 69 | ), 70 | "TRACTOR_ENGINE": "tractor-engine.cs.byu.edu:443", 71 | # Icons 72 | "XBMLANGPATH": os.pathsep.join( 73 | [ 74 | str(pth) + ("/%B" if system == "Linux" else "") 75 | for pth in [ 76 | this_path.parent 77 | / "scripts/studiolibrary/src/studiolibrary/resource/icons", 78 | pipe_path / "lib/icon", 79 | pipe_path / "lib/splash", 80 | ] 81 | ] 82 | ), 83 | } 84 | 85 | launch_command = "" 86 | launch_args: list[str] = [] 87 | if is_python_shell: 88 | launch_command = str(Executables.mayapy) 89 | cmd_str = "" 90 | extra_args_preamble = [] 91 | 92 | print(extra_args) 93 | 94 | if extra_args: 95 | # extract the cmd arg so we can append it to everything else 96 | try: 97 | cmd_flag_index = next( 98 | ( 99 | i 100 | for i, f in enumerate(extra_args) 101 | if (f[0] == "-") and (f[-1] == "c") 102 | ) 103 | ) 104 | cmd_str = extra_args[cmd_flag_index + 1] 105 | if len(extra_args[cmd_flag_index]) > 2: 106 | cmd_str_other_flags = ["-" + extra_args[cmd_flag_index][1:-1]] 107 | else: 108 | cmd_str_other_flags = [] 109 | 110 | extra_args_preamble = ( 111 | extra_args[:cmd_flag_index] 112 | + extra_args[cmd_flag_index + 2 :] 113 | + cmd_str_other_flags 114 | ) 115 | except StopIteration: 116 | pass 117 | 118 | launch_args = extra_args_preamble + [ 119 | "-ic", 120 | ";".join( 121 | [ 122 | "import atexit", 123 | "import maya.standalone", 124 | "maya.standalone.initialize()", 125 | "atexit.register(maya.standalone.uninitialize)", 126 | cmd_str, 127 | ] 128 | ), 129 | ] 130 | else: 131 | launch_command = str(Executables.maya) 132 | if extra_args: 133 | launch_args = extra_args 134 | 135 | super().__init__( 136 | launch_command, launch_args, env_vars, lambda: self.set_up_shelf_path() 137 | ) 138 | 139 | def set_up_shelf_path(self) -> None: 140 | prod_dir = str(Path(__file__).parent / "shelves") 141 | local_dir = self.shelf_path 142 | 143 | shutil.copytree(prod_dir, local_dir, dirs_exist_ok=True) 144 | -------------------------------------------------------------------------------- /pipeline/software/maya/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | pin_shift.py -------------------------------------------------------------------------------- /pipeline/software/maya/scripts/dwpicker: -------------------------------------------------------------------------------- 1 | dwpicker_git/dwpicker -------------------------------------------------------------------------------- /pipeline/software/maya/scripts/modelChecker: -------------------------------------------------------------------------------- 1 | modelChecker_git/modelChecker -------------------------------------------------------------------------------- /pipeline/software/maya/scripts/timeline_marker: -------------------------------------------------------------------------------- 1 | maya-timeline-marker_git/scripts/timeline_marker -------------------------------------------------------------------------------- /pipeline/software/maya/shelves/shelf_LnD_Assets.mel: -------------------------------------------------------------------------------- 1 | global proc shelf_LnD_Assets () { 2 | global string $gBuffStr; 3 | global string $gBuffStr0; 4 | global string $gBuffStr1; 5 | 6 | shelfButton 7 | -enableCommandRepeat 1 8 | -flexibleWidthType 3 9 | -flexibleWidthValue 32 10 | -enable 1 11 | -width 35 12 | -height 34 13 | -manage 1 14 | -visible 1 15 | -preventOverride 0 16 | -annotation "Model Checker" 17 | -enableBackground 0 18 | -backgroundColor 0 0 0 19 | -highlightColor 0.321569 0.521569 0.65098 20 | -align "center" 21 | -label "Model Chcker" 22 | -labelOffset 0 23 | -rotation 0 24 | -flipX 0 25 | -flipY 0 26 | -useAlpha 1 27 | -imageOverlayLabel "Checker" 28 | -overlayLabelColor 2.624876 0.801436 0.012446 29 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 30 | -image "monocle.png" 31 | -image1 "monocle.png" 32 | -style "iconOnly" 33 | -marginWidth 0 34 | -marginHeight 1 35 | -command "from pipe.m.publish import ModelChecker; chck = ModelChecker.get(); chck.configure(); chck.show_UI();" 36 | -sourceType "python" 37 | -commandRepeatable 1 38 | -flat 1 39 | ; 40 | shelfButton 41 | -enableCommandRepeat 1 42 | -flexibleWidthType 3 43 | -flexibleWidthValue 32 44 | -enable 1 45 | -width 35 46 | -height 34 47 | -manage 1 48 | -visible 1 49 | -preventOverride 0 50 | -annotation "Tool for L&D asset publishing" 51 | -enableBackground 0 52 | -backgroundColor 0 0 0 53 | -highlightColor 0.321569 0.521569 0.65098 54 | -align "center" 55 | -label "Publish" 56 | -labelOffset 0 57 | -rotation 0 58 | -flipX 0 59 | -flipY 0 60 | -useAlpha 1 61 | -imageOverlayLabel "Publish Asset" 62 | -overlayLabelColor 2.624876 0.801436 0.012446 63 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 64 | -image "createContainer.png" 65 | -image1 "createContainer.png" 66 | -style "iconOnly" 67 | -marginWidth 0 68 | -marginHeight 1 69 | -command "from pipe.m.publish import AssetPublisher; AssetPublisher().publish();" 70 | -sourceType "python" 71 | -commandRepeatable 1 72 | -flat 1 73 | ; 74 | shelfButton 75 | -enableCommandRepeat 1 76 | -flexibleWidthType 3 77 | -flexibleWidthValue 32 78 | -enable 1 79 | -width 35 80 | -height 34 81 | -manage 1 82 | -visible 1 83 | -preventOverride 0 84 | -annotation "Rig Export" 85 | -enableBackground 0 86 | -backgroundColor 0 0 0 87 | -highlightColor 0.321569 0.521569 0.65098 88 | -align "center" 89 | -label "Rig Export" 90 | -labelOffset 0 91 | -rotation 0 92 | -flipX 0 93 | -flipY 0 94 | -useAlpha 1 95 | -imageOverlayLabel "Rig Export" 96 | -overlayLabelColor 2.624876 0.801436 0.012446 97 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 98 | -image "createContainer.png" 99 | -image1 "createContainer.png" 100 | -style "iconOnly" 101 | -marginWidth 0 102 | -marginHeight 1 103 | -command "from pipe.m.publish import RigPublisher; RigPublisher().publish();" 104 | -sourceType "python" 105 | -commandRepeatable 1 106 | -flat 1 107 | ; 108 | } 109 | -------------------------------------------------------------------------------- /pipeline/software/maya/shelves/shelf_LnD_RLO.mel: -------------------------------------------------------------------------------- 1 | global proc shelf_LnD_RLO () { 2 | global string $gBuffStr; 3 | global string $gBuffStr0; 4 | global string $gBuffStr1; 5 | 6 | shelfButton 7 | -enableCommandRepeat 1 8 | -flexibleWidthType 3 9 | -flexibleWidthValue 32 10 | -enable 1 11 | -width 35 12 | -height 34 13 | -manage 1 14 | -visible 1 15 | -preventOverride 0 16 | -annotation "Open RLO File" 17 | -enableBackground 0 18 | -backgroundColor 0 0 0 19 | -highlightColor 0.321569 0.521569 0.65098 20 | -align "center" 21 | -label "OpenRLO" 22 | -labelOffset 0 23 | -rotation 0 24 | -flipX 0 25 | -flipY 0 26 | -useAlpha 1 27 | -imageOverlayLabel "Open RLO" 28 | -overlayLabelColor 2.624876 0.801436 0.012446 29 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 30 | -image "openLoadGeneric.png" 31 | -image1 "openLoadGeneric.png" 32 | -style "iconOnly" 33 | -marginWidth 0 34 | -marginHeight 1 35 | -command "from pipe.m.shotfile import MRLOShotFileManager; fm = MRLOShotFileManager(); fm.open_file()" 36 | -sourceType "python" 37 | -commandRepeatable 1 38 | -flat 1 39 | ; 40 | shelfButton 41 | -enableCommandRepeat 1 42 | -flexibleWidthType 3 43 | -flexibleWidthValue 32 44 | -enable 1 45 | -width 35 46 | -height 34 47 | -manage 1 48 | -visible 1 49 | -preventOverride 0 50 | -annotation "Tool for shifting around timelines" 51 | -enableBackground 0 52 | -backgroundColor 0 0 0 53 | -highlightColor 0.321569 0.521569 0.65098 54 | -align "center" 55 | -label "PinShift" 56 | -labelOffset 0 57 | -rotation 0 58 | -flipX 0 59 | -flipY 0 60 | -useAlpha 1 61 | -imageOverlayLabel "Pin Shift" 62 | -overlayLabelColor 2.624876 0.801436 0.012446 63 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 64 | -image "pin.png" 65 | -image1 "pin.png" 66 | -style "iconOnly" 67 | -marginWidth 0 68 | -marginHeight 1 69 | -command "import pin_shift; pin_shift.run();" 70 | -sourceType "python" 71 | -commandRepeatable 1 72 | -flat 1 73 | ; 74 | shelfButton 75 | -enableCommandRepeat 1 76 | -flexibleWidthType 3 77 | -flexibleWidthValue 32 78 | -enable 1 79 | -width 35 80 | -height 34 81 | -manage 1 82 | -visible 1 83 | -preventOverride 0 84 | -annotation "Tool for L&D camera publishing" 85 | -enableBackground 0 86 | -backgroundColor 0 0 0 87 | -highlightColor 0.321569 0.521569 0.65098 88 | -align "center" 89 | -label "Publish" 90 | -labelOffset 0 91 | -rotation 0 92 | -flipX 0 93 | -flipY 0 94 | -useAlpha 1 95 | -imageOverlayLabel "Publish Camera" 96 | -overlayLabelColor 2.624876 0.801436 0.012446 97 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 98 | -image "camera.closed.svg" 99 | -image1 "camera.closed.svg" 100 | -style "iconOnly" 101 | -marginWidth 0 102 | -marginHeight 1 103 | -command "from pipe.m.publish import CameraPublisher; CameraPublisher().publish();" 104 | -sourceType "python" 105 | -commandRepeatable 1 106 | -flat 1 107 | ; 108 | shelfButton 109 | -enableCommandRepeat 1 110 | -flexibleWidthType 3 111 | -flexibleWidthValue 32 112 | -enable 1 113 | -width 35 114 | -height 34 115 | -manage 1 116 | -visible 1 117 | -preventOverride 0 118 | -annotation "Previs Playblasting" 119 | -enableBackground 0 120 | -backgroundColor 0 0 0 121 | -highlightColor 0.321569 0.521569 0.65098 122 | -align "center" 123 | -label "Publish" 124 | -labelOffset 0 125 | -rotation 0 126 | -flipX 0 127 | -flipY 0 128 | -useAlpha 1 129 | -imageOverlayLabel "Previs Playblast" 130 | -overlayLabelColor 2.624876 0.801436 0.012446 131 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 132 | -image "playblast.png" 133 | -image1 "playblast.png" 134 | -style "iconOnly" 135 | -marginWidth 0 136 | -marginHeight 1 137 | -command "from pipe.m.playblast import PrevisPlayblastDialog; from pipe.m.local import get_main_qt_window; PrevisPlayblastDialog(get_main_qt_window()).show();" 138 | -sourceType "python" 139 | -commandRepeatable 1 140 | -flat 1 141 | ; 142 | } 143 | -------------------------------------------------------------------------------- /pipeline/software/maya/shelves/shelf_LnD_Rigging.mel: -------------------------------------------------------------------------------- 1 | global proc shelf_LnD_Rigging () { 2 | global string $gBuffStr; 3 | global string $gBuffStr0; 4 | global string $gBuffStr1; 5 | 6 | 7 | shelfButton 8 | -enableCommandRepeat 1 9 | -flexibleWidthType 3 10 | -flexibleWidthValue 32 11 | -enable 1 12 | -width 35 13 | -height 34 14 | -manage 1 15 | -visible 1 16 | -preventOverride 0 17 | -annotation "Open rig build UI" 18 | -enableBackground 0 19 | -backgroundColor 0 0 0 20 | -highlightColor 0.321569 0.521569 0.65098 21 | -align "center" 22 | -label "Build" 23 | -labelOffset 0 24 | -rotation 0 25 | -flipX 0 26 | -flipY 0 27 | -useAlpha 1 28 | -imageOverlayLabel "Build" 29 | -overlayLabelColor 2.624876 0.801436 0.012446 30 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 31 | -image "rig_arm.png" 32 | -image1 "rig_arm.png" 33 | -style "iconOnly" 34 | -marginWidth 0 35 | -marginHeight 1 36 | -command "import rjg.build_scripts.build_ui as bui; bui.run();" 37 | -sourceType "python" 38 | -commandRepeatable 1 39 | -flat 1 40 | ; 41 | 42 | shelfButton 43 | -enableCommandRepeat 1 44 | -flexibleWidthType 3 45 | -flexibleWidthValue 32 46 | -enable 1 47 | -width 35 48 | -height 34 49 | -manage 1 50 | -visible 1 51 | -preventOverride 0 52 | -annotation "Open rig publish UI" 53 | -enableBackground 0 54 | -backgroundColor 0 0 0 55 | -highlightColor 0.321569 0.521569 0.65098 56 | -align "center" 57 | -label "Publish" 58 | -labelOffset 0 59 | -rotation 0 60 | -flipX 0 61 | -flipY 0 62 | -useAlpha 1 63 | -imageOverlayLabel "Publish" 64 | -overlayLabelColor 2.624876 0.801436 0.012446 65 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 66 | -image "envelope.png" 67 | -image1 "envelope.png" 68 | -style "iconOnly" 69 | -marginWidth 0 70 | -marginHeight 1 71 | -command "import pipe.m.rig_publish; pipe.m.rig_publish.run();" 72 | -sourceType "python" 73 | -commandRepeatable 1 74 | -flat 1 75 | ; 76 | 77 | shelfButton 78 | -enableCommandRepeat 1 79 | -flexibleWidthType 3 80 | -flexibleWidthValue 32 81 | -enable 1 82 | -width 35 83 | -height 34 84 | -manage 1 85 | -visible 1 86 | -preventOverride 0 87 | -annotation "ReloadPipe" 88 | -enableBackground 0 89 | -backgroundColor 0 0 0 90 | -highlightColor 0.321569 0.521569 0.65098 91 | -align "center" 92 | -label "ReloadPipe" 93 | -labelOffset 0 94 | -rotation 0 95 | -flipX 0 96 | -flipY 0 97 | -useAlpha 1 98 | -imageOverlayLabel "ReloadPipe" 99 | -overlayLabelColor 2.624876 0.801436 0.012446 100 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 101 | -image "createContainer.png" 102 | -image1 "createContainer.png" 103 | -style "iconOnly" 104 | -marginWidth 0 105 | -marginHeight 1 106 | -command "import pipe.util as pu; pu.reload_pipe();" 107 | -sourceType "python" 108 | -commandRepeatable 1 109 | -flat 1 110 | ; 111 | 112 | separator 113 | -width 12 114 | -height 35 115 | -style shelf 116 | -hr 0 117 | -parent "LnD_Rigging"; 118 | 119 | shelfButton 120 | -enableCommandRepeat 1 121 | -flexibleWidthType 3 122 | -flexibleWidthValue 32 123 | -enable 1 124 | -width 35 125 | -height 34 126 | -manage 1 127 | -visible 1 128 | -preventOverride 0 129 | -annotation "katie_Tool" 130 | -enableBackground 0 131 | -backgroundColor 0 0 0 132 | -highlightColor 0.321569 0.521569 0.65098 133 | -align "center" 134 | -label "katie_Tool" 135 | -labelOffset 0 136 | -rotation 0 137 | -flipX 0 138 | -flipY 0 139 | -useAlpha 1 140 | -imageOverlayLabel "katie_Tool" 141 | -overlayLabelColor 2.624876 0.801436 0.012446 142 | -overlayLabelBackColor 0.227129 0.227129 0.227129 0.5 143 | -image "createContainer.png" 144 | -image1 "createContainer.png" 145 | -style "iconOnly" 146 | -marginWidth 0 147 | -marginHeight 1 148 | -command "import pipe.m.ToolBox.Katie_Toolbox_GUI; pipe.m.ToolBox.Katie_Toolbox_GUI.createUI();" 149 | -sourceType "python" 150 | -commandRepeatable 1 151 | -flat 1 152 | ; 153 | 154 | } 155 | -------------------------------------------------------------------------------- /pipeline/software/maya/userSetup/userSetup.py: -------------------------------------------------------------------------------- 1 | """Initialize Maya environment on startup""" 2 | 3 | import maya.cmds as mc 4 | 5 | 6 | def main(): 7 | # Enable required plugins 8 | plugins = [ 9 | "mayaUsdPlugin", 10 | ] 11 | pluginInfo = mc.pluginInfo(q=True, listPlugins=True) 12 | for plugin in plugins: 13 | if plugin not in pluginInfo: 14 | mc.loadPlugin(plugin) 15 | 16 | from shared.util import get_production_path 17 | 18 | # set workspace 19 | mc.workspace(str(get_production_path().parent), openWorkspace=True) 20 | 21 | if not mc.about(batch=True): 22 | # enable timeline-marker plugin 23 | from timeline_marker import install # type: ignore[import-not-found] 24 | 25 | install.execute() 26 | 27 | # register USD Export chaser 28 | import mayaUsd.lib as mayaUsdLib # type: ignore[import-not-found] 29 | from pipe.m.publish import ExportChaser 30 | 31 | mayaUsdLib.ExportChaser.Register(ExportChaser, ExportChaser.ID) 32 | 33 | 34 | mc.evalDeferred(main) 35 | -------------------------------------------------------------------------------- /pipeline/software/nuke/__init__.py: -------------------------------------------------------------------------------- 1 | from .dcc import NukeDCC as NukeDCC 2 | -------------------------------------------------------------------------------- /pipeline/software/nuke/dcc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import platform 6 | import sys 7 | 8 | from pathlib import Path 9 | 10 | from shared.util import get_production_path, resolve_mapped_path 11 | 12 | from ..baseclass import DCC 13 | from env import Executables 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | class NukeDCC(DCC): 19 | """Nuke DCC class""" 20 | 21 | def __init__( 22 | self, is_python_shell: bool = False, extra_args: list[str] | None = None 23 | ) -> None: 24 | this_path = Path(__file__).resolve() 25 | pipe_path = this_path.parents[2] 26 | 27 | system = platform.system() 28 | 29 | env_vars = { 30 | "NUKE_PATH": str(resolve_mapped_path(this_path.parent / "tools")), 31 | "OCIO": str(pipe_path / "lib/ocio/love-v01/config.ocio"), 32 | "PYTHONPATH": os.pathsep.join( 33 | [ 34 | str(pipe_path), 35 | str( 36 | get_production_path() 37 | / f"../pipeline/pipeline/lib/python/3.9/{sys.platform}" 38 | ), 39 | ] 40 | ), 41 | "QT_SCALE_FACTOR": os.getenv("NUKE_SCALE_FACTOR") 42 | if system == "Linux" 43 | else None, 44 | } 45 | 46 | launch_command = "" 47 | if is_python_shell: 48 | launch_command = str(Executables.nuke_python) 49 | else: 50 | launch_command = str(Executables.nuke) 51 | 52 | if is_python_shell: 53 | launch_args = extra_args or [] 54 | else: 55 | launch_args = ["--nukex", *(extra_args or [])] 56 | 57 | super().__init__(launch_command, launch_args, env_vars) 58 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/gizmos/FrameBurn.gizmo: -------------------------------------------------------------------------------- 1 | #! /opt/Nuke15.0v4/libnuke-15.0.4.so -nx 2 | version 15.0 v4 3 | Gizmo { 4 | } 5 | Input { 6 | inputs 0 7 | name Input1 8 | xpos 39 9 | ypos -17 10 | } 11 | Text2 { 12 | font_size_toolbar 100 13 | font_width_toolbar 100 14 | font_height_toolbar 100 15 | message "frame \[metadata input/frame]" 16 | old_message {{102 114 97 109 101 32 49 48 54 48} 17 | } 18 | old_expression_markers {{6 9} 19 | } 20 | box {0 10 1910 800} 21 | xjustify right 22 | yjustify bottom 23 | transforms {{0 2} 24 | } 25 | cursor_position 28 26 | font {{ Cantarell : Regular : cantarell/Cantarell-Regular.otf : 0 }} 27 | global_font_scale 0.3 28 | center {1024 778} 29 | cursor_initialised true 30 | autofit_bbox false 31 | initial_cursor_position {{1659.5 122.5} 32 | } 33 | group_animations {{0} imported: 0 selected: items: "root transform/"} 34 | animation_layers {{1 11 1024 778 0 0 1 1 0 0 0 0} 35 | } 36 | name Text1 37 | xpos 39 38 | ypos 23 39 | } 40 | Output { 41 | name Output1 42 | xpos 39 43 | ypos 123 44 | } 45 | end_group 46 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/gizmos/lumaDistort.gizmo: -------------------------------------------------------------------------------- 1 | #! C:/Program Files/Nuke11.3v1/nuke-11.3.1.dll -nx 2 | version 11.3 v1 3 | Gizmo { 4 | inputs 2 5 | tile_color 0x55ffbfff 6 | label v2.0 7 | addUserKnob {20 user l Controls} 8 | addUserKnob {26 text l "Luma Distort" T " "} 9 | addUserKnob {26 text_1 l " " T " "} 10 | addUserKnob {7 x l X-Axis R 0 4} 11 | x 10 12 | addUserKnob {7 y l Y-Axis R 0 4} 13 | y 10 14 | addUserKnob {14 distortion l Distortion R 0 100} 15 | distortion 100 16 | addUserKnob {7 offset l Offset} 17 | addUserKnob {26 ""} 18 | addUserKnob {26 copyright1 l "" +STARTLINE T "T_LumaDistort v2.0 - Petar Tsonev (c)"} 19 | } 20 | BackdropNode { 21 | inputs 0 22 | name BackdropNode1 23 | tile_color 0x388e8e00 24 | label "Footage to Distort" 25 | note_font_size 42 26 | xpos 477 27 | ypos -225 28 | bdwidth 366 29 | bdheight 257 30 | } 31 | BackdropNode { 32 | inputs 0 33 | name BackdropNode2 34 | tile_color 0x8e3737ff 35 | label "Distortion Map" 36 | note_font_size 42 37 | xpos 1247 38 | ypos -1281 39 | bdwidth 366 40 | bdheight 257 41 | } 42 | BackdropNode { 43 | inputs 0 44 | name BackdropNode3 45 | tile_color 0x398e37ff 46 | label X-AXIS 47 | note_font_size 42 48 | xpos 1336 49 | ypos -890 50 | bdwidth 188 51 | bdheight 339 52 | } 53 | BackdropNode { 54 | inputs 0 55 | name BackdropNode4 56 | tile_color 0x398e37ff 57 | label Y-AXIS 58 | note_font_size 42 59 | xpos 1666 60 | ypos -890 61 | bdwidth 188 62 | bdheight 339 63 | } 64 | BackdropNode { 65 | inputs 0 66 | name BackdropNode5 67 | tile_color 0x50378eff 68 | label "Distortion Amount" 69 | note_font_size 42 70 | xpos 1228 71 | ypos 315 72 | bdwidth 405 73 | bdheight 234 74 | } 75 | Input { 76 | inputs 0 77 | name Map 78 | xpos 1390 79 | ypos -1162 80 | } 81 | Dot { 82 | name Dot14 83 | xpos 1424 84 | ypos -774 85 | } 86 | set N3903d400 [stack 0] 87 | Dot { 88 | name Dot15 89 | xpos 1754 90 | ypos -774 91 | } 92 | Shuffle { 93 | red black 94 | blue black 95 | alpha black 96 | name Shuffle4 97 | xpos 1720 98 | ypos -682 99 | } 100 | Grade { 101 | white {{parent.y}} 102 | name yaxis_Control 103 | xpos 1720 104 | ypos -586 105 | } 106 | Dot { 107 | name Dot16 108 | xpos 1754 109 | ypos -486 110 | } 111 | push $N3903d400 112 | Shuffle { 113 | green black 114 | blue black 115 | alpha black 116 | name Shuffle6 117 | xpos 1390 118 | ypos -682 119 | } 120 | Grade { 121 | white {{parent.x}} 122 | name xaxis_Control 123 | xpos 1390 124 | ypos -586 125 | } 126 | Merge2 { 127 | inputs 2 128 | name Merge12 129 | xpos 1390 130 | ypos -490 131 | } 132 | Input { 133 | inputs 0 134 | name Plate 135 | xpos 620 136 | ypos -106 137 | number 1 138 | } 139 | ShuffleCopy { 140 | inputs 2 141 | red red 142 | green green 143 | out forward 144 | name ShuffleCopy1 145 | xpos 1390 146 | ypos -106 147 | } 148 | IDistort { 149 | uv forward 150 | uv_offset {{parent.offset}} 151 | uv_scale {{parent.distortion}} 152 | name IDistort1 153 | xpos 1390 154 | ypos 416 155 | } 156 | Output { 157 | name Output1 158 | xpos 1390 159 | ypos 614 160 | } 161 | end_group -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/images/lensdirt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/nuke/tools/NungeonTools/images/lensdirt.jpg -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/images/noThumbnailIcon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/nuke/tools/NungeonTools/images/noThumbnailIcon.jpg -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/images/nungeonIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/nuke/tools/NungeonTools/images/nungeonIcon.png -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/images/openShot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/nuke/tools/NungeonTools/images/openShot.jpg -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/images/rayden.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottdmilner/dungeon-pipeline/04d49503298180694230a54c6d1f76feb4a8578d/pipeline/software/nuke/tools/NungeonTools/images/rayden.jpg -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/menu.py: -------------------------------------------------------------------------------- 1 | import nuke 2 | from shared.util import get_pipe_path 3 | 4 | nuke.pluginAddPath("./gizmos") 5 | nuke.pluginAddPath("./icons") 6 | nuke.pluginAddPath("./images") 7 | nuke.pluginAddPath("./nk_files") 8 | nuke.pluginAddPath("./toolsets") 9 | nuke.pluginAddPath("./scripts") 10 | 11 | # aspect ratio 12 | nuke.addFormat("1920 816 Love_and_Dungeons_aspect_ratio") 13 | 14 | 15 | def make_ld_write_node(): 16 | import ld_write_node_v2 # type: ignore[import-not-found] 17 | 18 | ld_write_node_v2.main() 19 | 20 | 21 | def import_render_layers(): 22 | import render_layer_selector # type: ignore[import-not-found] 23 | 24 | render_layer_selector.run() 25 | 26 | 27 | def import_USD_cam(): 28 | import import_usd_camera # type: ignore[import-not-found] 29 | 30 | import_usd_camera.run() 31 | 32 | 33 | def choose_shot(): 34 | import open_shot # type: ignore[import-not-found] 35 | 36 | open_shot.run() 37 | 38 | 39 | def set_frameRange_and_aspectRatio(): 40 | import set_frameRange_and_aspectRatio # type: ignore[import-not-found] 41 | 42 | set_frameRange_and_aspectRatio.run() 43 | 44 | 45 | ################################### Nungeon buttons (Sidebar) ################################### 46 | toolbar = nuke.menu("Nodes") 47 | m = toolbar.addMenu("Nungeon", icon="nungeonIcon.png") 48 | 49 | m.addCommand( 50 | "Wireframe Breakdown", 51 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/wireframe_breakdown.nk")}")', 52 | icon="nungeonIcon.png", 53 | ) 54 | m.addCommand( 55 | "Template", 56 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/shotTemplate.nk")}")', 57 | icon="nungeonIcon.png", 58 | ) 59 | m.addCommand( 60 | "Depth Fog", 61 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/depth_fog.nk")}")', 62 | icon="nungeonIcon.png", 63 | ) 64 | m.addCommand( 65 | "Deep Fog", 66 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/deep_fog.nk")}")', 67 | icon="nungeonIcon.png", 68 | ) 69 | m.addCommand( 70 | "Fix Snow Flashes", 71 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/ld_snow_glitter_clamp.nk")}")', 72 | icon="nungeonIcon.png", 73 | ) 74 | m.addCommand( 75 | "Lightwrap (upper matrix)", 76 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/ld_lightwrap.nk")}")', 77 | icon="nungeonIcon.png", 78 | ) 79 | m.addCommand( 80 | "Relight", 81 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/relight_template.nk")}")', 82 | icon="nungeonIcon.png", 83 | ) 84 | 85 | m.addCommand( 86 | "Eye Light", 87 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/eyelights.nk")}")', 88 | icon="nungeonIcon.png", 89 | ) 90 | m.addCommand( 91 | "Sky Dome (Basic)", 92 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/ld_skydome_basic.nk")}")', 93 | icon="nungeonIcon.png", 94 | ) 95 | m.addCommand( 96 | "Fix Snow Sparkles in fog layer", 97 | f'nuke.nodePaste("{str(get_pipe_path() / "software/nuke/tools/NungeonTools/toolsets/ld_snow_glitter_clamp.nk")}")', 98 | icon="nungeonIcon.png", 99 | ) 100 | 101 | # m.addCommand("FrameBurn", "nuke.createNode('FrameBurn')", icon="nungeonIcon.png") 102 | m.addCommand("Grade_AOV", "nuke.createNode('grade_AOV')", icon="nungeonIcon.png") 103 | m.addCommand("luma Distort", "nuke.createNode('lumaDistort')", icon="nungeonIcon.png") 104 | m.addCommand("Roughen Edges", "nuke.createNode('roughenEdges')", icon="nungeonIcon.png") 105 | # lens node 106 | m.addCommand("Lens", "nuke.createNode('Lens')", icon="nungeonIcon.png") 107 | print( 108 | f"nuke.nodePaste({str(get_pipe_path() / 'software/nuke/tools/NungeonTools/toolsets/shotTemplate.nk')})" 109 | ) 110 | m.addCommand("L&D Write Node", "make_ld_write_node()", icon="nungeonIcon.png") 111 | 112 | 113 | ################################### Nungeon Shelf Tool Buttons ################################### 114 | menu = nuke.menu("Nuke") 115 | menu.addCommand("[Choose Shot]", "choose_shot()") 116 | menu.addCommand("[Import Render Layers]", "import_render_layers()") 117 | menu.addCommand("[Import USD Camera]", "import_USD_cam()") 118 | menu.addCommand("[Set Project Settings]", "set_frameRange_and_aspectRatio()") 119 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/scripts/set_frameRange_and_aspectRatio.py: -------------------------------------------------------------------------------- 1 | import nuke 2 | from pipe.db import DB 3 | from env_sg import DB_Config 4 | import os 5 | 6 | project_file = nuke.root()["name"].value() 7 | 8 | 9 | def get_project_name(): 10 | project_name = "" 11 | if project_file: 12 | project_name_with_ext = os.path.basename(project_file) 13 | project_name, ext = os.path.splitext(project_name_with_ext) 14 | else: 15 | project_name = "Unsaved Project" 16 | return project_name 17 | 18 | 19 | def get_frame_range(project_name): 20 | conn = DB.Get(DB_Config) 21 | shot_info = conn.get_shot_by_code(project_name) 22 | return [shot_info.cut_in, shot_info.cut_out] 23 | 24 | 25 | def set_frame_range(frame_in, frame_out): 26 | nuke.root()["first_frame"].setValue(frame_in) 27 | nuke.root()["last_frame"].setValue(frame_out) 28 | nuke.root()["lock_range"].setValue(True) 29 | 30 | 31 | def set_full_frame_size(root_node): 32 | root_node["format"].setValue("Love_and_Dungeons_aspect_ratio") 33 | 34 | 35 | def set_frame_rate(root_node): 36 | root_node["fps"].setValue(24) 37 | 38 | 39 | def run(): 40 | project_name = get_project_name() 41 | root_node = nuke.root() 42 | set_full_frame_size(root_node) 43 | 44 | # set frame range if the file is saved 45 | if project_name == "Unsaved Project": 46 | return 47 | else: 48 | frame_in, frame_out = get_frame_range(project_name) 49 | print("Frame out: " + str(frame_out)) 50 | set_frame_range(frame_in, frame_out) 51 | set_frame_rate(root_node) 52 | 53 | 54 | # run() 55 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/depth_fog.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | push $cut_paste_input 4 | Group { 5 | name Z_Depth_Fog 6 | tile_color 0xff383aff 7 | note_font_size 20 8 | selected true 9 | xpos 2192 10 | ypos -1376 11 | postage_stamp true 12 | addUserKnob {20 User} 13 | addUserKnob {4 nodeViewer l View_Node +INVISIBLE M {0 1 2 "" "" "" "" "" ""}} 14 | addUserKnob {26 step1d l "Step 1:" T "Click the \"Step 1\" Button. "} 15 | addUserKnob {22 step1 l "Step 1" T "node = nuke.thisNode()\ndropdown_knob = 'nodeViewer'\nnode\[dropdown_knob].setValue(1)" +STARTLINE} 16 | addUserKnob {26 Instruction_1 l "" +STARTLINE T "Using the color picking tool, find the \nlargest value (from any individual channel)\nand type it in the box below:"} 17 | addUserKnob {41 temp_expr0 l = T Input_Largest_Z1.temp_expr0} 18 | addUserKnob {26 ""} 19 | addUserKnob {26 text_1 l "Step 2:" T "Click the \"Step 2\" Button. "} 20 | addUserKnob {22 step2 l "Step 2" T "node = nuke.thisNode()\ndropdown_knob = 'nodeViewer'\nnode\[dropdown_knob].setValue(2)" +STARTLINE} 21 | addUserKnob {26 text_2 l "" +STARTLINE T "Use the slider to dial in which parts of the image you want to be foggy \n(White is fog. Black is no fog)"} 22 | addUserKnob {41 black l lift T adjust_alpha1.black} 23 | addUserKnob {26 ""} 24 | addUserKnob {26 Step3 l "Step 3:" T "Click the \"Step 3\" Button."} 25 | addUserKnob {22 step3 l "Step 3:" T "node = nuke.thisNode()\ndropdown_knob = 'nodeViewer'\nnode\[dropdown_knob].setValue(0)" +STARTLINE} 26 | addUserKnob {26 text l "" +STARTLINE} 27 | addUserKnob {41 black_1 l "Fog Level" T Fog_effect1.black} 28 | } 29 | BackdropNode { 30 | inputs 0 31 | name BackdropNode2 32 | tile_color 0xaadfffff 33 | label "Depth Fog" 34 | note_font_size 42 35 | note_font_color 0xffffffff 36 | xpos 159 37 | ypos 66 38 | appearance Border 39 | bdwidth 779 40 | bdheight 210 41 | } 42 | StickyNote { 43 | inputs 0 44 | name StickyNote2 45 | label "Steps:\n\n1) View the node called \"Find Largest Z.\"\n2) Using the color picker, find the highest color value (for any individual color channel)\n3) Use the \"adjust_alpha\" Node to dial in which parts of the image you want to be foggy\n4) Adjust the \"Lift\" parameter in the \"Fog_effect\" Node to change how much fog you want.\n" 46 | xpos 464 47 | ypos 151 48 | } 49 | Input { 50 | inputs 0 51 | name Input1 52 | xpos 167 53 | ypos -70 54 | } 55 | Dot { 56 | name Dot4 57 | xpos 201 58 | ypos 39 59 | } 60 | Dot { 61 | name Dot5 62 | xpos 201 63 | ypos 139 64 | } 65 | set N171d90c0 [stack 0] 66 | add_layer {__depth __depth.red __depth.green __depth.blue} 67 | Shuffle2 { 68 | fromInput1 {{0} B} 69 | in1 __depth 70 | fromInput2 {{0} B} 71 | mappings "4 __depth.red 0 0 rgba.red 0 0 __depth.red 0 0 rgba.green 0 1 __depth.red 0 0 rgba.blue 0 2 __depth.red 0 0 rgba.alpha 0 3" 72 | name Find_largest_Z1 73 | selected true 74 | xpos 357 75 | ypos 135 76 | } 77 | set N171de260 [stack 0] 78 | Expression { 79 | temp_name0 big_val 80 | temp_expr0 0 81 | temp_name1 . 82 | temp_expr1 "^^^^Put largest value in the top right box! ^^^" 83 | expr0 "R / big_val" 84 | channel3 {__depth.red __depth.green __depth.blue __depth.red} 85 | name Input_Largest_Z1 86 | xpos 357 87 | ypos 172 88 | } 89 | Shuffle2 { 90 | fromInput1 {{0} B} 91 | fromInput2 {{0} B} 92 | mappings "4 rgba.red 0 0 rgba.red 0 0 rgba.red 0 0 rgba.alpha 0 3 rgba.red 0 0 rgba.green 0 1 rgba.red 0 0 rgba.blue 0 2" 93 | name Ignore1 94 | xpos 357 95 | ypos 198 96 | } 97 | Grade { 98 | channels rgba 99 | name adjust_alpha1 100 | xpos 357 101 | ypos 240 102 | } 103 | set N1b76bf00 [stack 0] 104 | Viewer { 105 | frame_range 1000-1330 106 | name Viewer1 107 | xpos 438 108 | ypos 325 109 | } 110 | push $N1b76bf00 111 | Shuffle2 { 112 | fromInput1 {{0} B} 113 | fromInput2 {{0} B} 114 | name Shuffle1 115 | xpos 438 116 | ypos 299 117 | } 118 | push $N171de260 119 | push $N1b76bf00 120 | push $N171d90c0 121 | Grade { 122 | inputs 1+1 123 | name Fog_effect1 124 | xpos 167 125 | ypos 240 126 | } 127 | Switch { 128 | inputs 3 129 | which {{parent.nodeViewer}} 130 | name Switch1 131 | xpos 167 132 | ypos 350 133 | } 134 | Output { 135 | name Output1 136 | xpos 167 137 | ypos 420 138 | } 139 | end_group 140 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/eyelights.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | BackdropNode { 4 | inputs 0 5 | name BackdropNode8 6 | tile_color 0x7171c600 7 | label "Eye lights" 8 | note_font_size 42 9 | selected true 10 | xpos 3892 11 | ypos -1161 12 | bdwidth 721 13 | bdheight 492 14 | } 15 | push $cut_paste_input 16 | Dot { 17 | name Dot19 18 | selected true 19 | xpos 4274 20 | ypos -1142 21 | } 22 | Dot { 23 | name Dot38 24 | selected true 25 | xpos 4274 26 | ypos -1033 27 | } 28 | set N11b60880 [stack 0] 29 | Dot { 30 | name Dot37 31 | selected true 32 | xpos 4378 33 | ypos -1033 34 | } 35 | Cryptomatte { 36 | pickerAdd {0.03774875402 0.1039800644 0.1659202576 0 592 557 593 558} 37 | pickerRemove {1.037748814 1.103980064 0.1659202576 1 599 557 600 558} 38 | matteList /character/robin/Robin_EXTRAS/Cornea 39 | lastSelectedCryptoLayerName identifier_name 40 | name Cryptomatte14 41 | selected true 42 | xpos 4344 43 | ypos -967 44 | } 45 | push $N11b60880 46 | Dot { 47 | name Dot41 48 | selected true 49 | xpos 4053 50 | ypos -1033 51 | } 52 | Multiply { 53 | inputs 1+1 54 | value 0 55 | invert_mask true 56 | name Multiply12 57 | selected true 58 | xpos 4019 59 | ypos -973 60 | } 61 | add_layer {__nworld __nworld.red __nworld.green __nworld.blue} 62 | Shuffle2 { 63 | fromInput1 {{0} B} 64 | in1 __nworld 65 | fromInput2 {{0} B} 66 | mappings "4 __nworld.red 0 0 rgba.red 0 0 __nworld.green 0 1 rgba.green 0 1 __nworld.blue 0 2 rgba.blue 0 2 black -1 -1 rgba.alpha 0 3" 67 | name Shuffle4 68 | selected true 69 | xpos 3902 70 | ypos -967 71 | } 72 | Group { 73 | name NE_NormaLight4 74 | tile_color 0xe1111ff 75 | gl_color 0x93ff 76 | note_font_color 0x5ae2e0ff 77 | selected true 78 | xpos 3902 79 | ypos -914 80 | addUserKnob {20 User l "Edit Light"} 81 | addUserKnob {26 Expected_mapping l "Input method :" t "R=>n.r\nG=>n.g\nB=>n.b" T "Normals are expected to be shuffled in rgba\nlayer within R,G and B channels."} 82 | addUserKnob {41 format l "Normals format" T Constant1.format} 83 | addUserKnob {26 ""} 84 | addUserKnob {41 light_height l "Light Height" T Main.light_height} 85 | addUserKnob {41 light_direction l "Light Direction" T Main.light_direction} 86 | addUserKnob {26 ""} 87 | addUserKnob {41 beam l Beam T Main.beam} 88 | addUserKnob {41 decay l Decay T Main.decay} 89 | addUserKnob {26 ""} 90 | addUserKnob {7 mult l "Global mult" R 0 5} 91 | mult 5 92 | addUserKnob {41 Clamp -STARTLINE T Main.boolean} 93 | } 94 | Input { 95 | inputs 0 96 | name normals 97 | xpos 521 98 | ypos 205 99 | } 100 | Constant { 101 | inputs 0 102 | channels rgb 103 | color {{"cos(radians(light_height))* cos(radians(light_direction))"} {sin(radians(light_height))} {"cos(radians(light_height))* sin(radians(light_direction))"} 0} 104 | name Constant1 105 | xpos 782 106 | ypos 322 107 | addUserKnob {20 User} 108 | addUserKnob {7 light_height l "Light height" R -360 360} 109 | light_height -10 110 | addUserKnob {7 light_direction l "Light Direction" R -360 360} 111 | light_direction -320 112 | } 113 | Difference { 114 | inputs 2 115 | name Difference1 116 | xpos 521 117 | ypos 339 118 | } 119 | Grade { 120 | channels alpha 121 | multiply {{parent.Main.beam}} 122 | gamma {{parent.Main.decay}} 123 | white_clamp true 124 | name Grade1 125 | xpos 521 126 | ypos 428 127 | } 128 | Invert { 129 | channels alpha 130 | name Invert1 131 | xpos 521 132 | ypos 464 133 | } 134 | Multiply { 135 | channels alpha 136 | value {{parent.mult}} 137 | name Multiply1 138 | xpos 521 139 | ypos 500 140 | } 141 | Clamp { 142 | name Clamp1 143 | xpos 521 144 | ypos 561 145 | disable {{Main.boolean==0?1:0}} 146 | } 147 | Shuffle { 148 | red alpha 149 | green alpha 150 | blue alpha 151 | name Shuffle3 152 | xpos 521 153 | ypos 623 154 | } 155 | Premult { 156 | name Premult1 157 | xpos 521 158 | ypos 647 159 | } 160 | Output { 161 | name Output1 162 | xpos 521 163 | ypos 726 164 | } 165 | NoOp { 166 | inputs 0 167 | name Main 168 | xpos 409 169 | ypos 278 170 | addUserKnob {20 User} 171 | addUserKnob {41 light_height l "Light height" T Constant1.light_height} 172 | addUserKnob {41 light_direction l "Light Direction" T Constant1.light_direction} 173 | addUserKnob {26 ""} 174 | addUserKnob {7 beam l Beam R 1 50} 175 | beam 37.5 176 | addUserKnob {7 decay l Decay R 1 50} 177 | decay 1 178 | addUserKnob {6 boolean l Clamp +STARTLINE} 179 | } 180 | end_group 181 | Dot { 182 | name Dot39 183 | selected true 184 | xpos 3936 185 | ypos -844 186 | } 187 | push $N11b60880 188 | Merge2 { 189 | inputs 2 190 | name Merge9 191 | selected true 192 | xpos 4240 193 | ypos -848 194 | } 195 | Dot { 196 | name Dot40 197 | selected true 198 | xpos 4274 199 | ypos -692 200 | } 201 | StickyNote { 202 | inputs 0 203 | name StickyNote10 204 | label "sometimes I think it's helpful to disable to multiply\nnode above and adjust the NE_NormalLight node\nuntil the eye lights are actually showing up in the\neyes. Up to you though" 205 | selected true 206 | xpos 4005 207 | ypos -922 208 | } 209 | StickyNote { 210 | inputs 0 211 | name StickyNote6 212 | label "In the cryptomatte node, select your character's corneas. \nBy default, Robin's are selected" 213 | selected true 214 | xpos 4317 215 | ypos -919 216 | } 217 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/ld_lightwrap: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | BackdropNode { 4 | inputs 0 5 | name BackdropNode2 6 | tile_color 0x7171c600 7 | label "lightwrap (upper matrix)" 8 | note_font_size 42 9 | selected true 10 | xpos 2503 11 | ypos -672 12 | bdwidth 487 13 | bdheight 486 14 | } 15 | Roto { 16 | inputs 0 17 | output alpha 18 | curves {{{v x3f99999a} 19 | {f 0} 20 | {n 21 | {layer Root 22 | {f 2097152} 23 | {t x44700000 x43cc0000} 24 | {a pt1x 0 pt1y 0 pt2x 0 pt2y 0 pt3x 0 pt3y 0 pt4x 0 pt4y 0 ptex00 0 ptex01 0 ptex02 0 ptex03 0 ptex10 0 ptex11 0 ptex12 0 ptex13 0 ptex20 0 ptex21 0 ptex22 0 ptex23 0 ptex30 0 ptex31 0 ptex32 0 ptex33 0 ptof1x 0 ptof1y 0 ptof2x 0 ptof2y 0 ptof3x 0 ptof3y 0 ptof4x 0 ptof4y 0 pterr 0 ptrefset 0 ptmot x40800000 ptref 0} 25 | {curvegroup Bezier1 512 bezier 26 | {{cc 27 | {f 8192} 28 | {px x44826000 29 | {xc2b80000 x40400000} 30 | {x436c99a0 x43f83333} 31 | {x42b80000 xc0400000} 32 | {0 0} 33 | {x44f31335 x4406e666} 34 | {0 0} 35 | {x424c0000 xc0e00000} 36 | {x44f9a000 x447fc000} 37 | {xc24c0000 x40e00000} 38 | {0 0} 39 | {x43700000 x44724000} 40 | {0 0}}} idem} 41 | {tx x44826000 x4488d800 x4431a000} 42 | {a fx x42c80000 fy x42c80000 osw x41200000 osf 0 str 1 spx x44700000 spy x43cc0000 sb 1 ltn x44826000 ltm x44826000 tt x40800000}}}}}} 43 | toolbox {selectAll { 44 | { selectAll str 1 ssx 1 ssy 1 sf 1 } 45 | { createBezier str 1 ssx 1 ssy 1 sf 1 sb 1 tt 4 } 46 | { createBezierCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 47 | { createBSpline str 1 ssx 1 ssy 1 sf 1 sb 1 } 48 | { createEllipse str 1 ssx 1 ssy 1 sf 1 sb 1 } 49 | { createRectangle str 1 ssx 1 ssy 1 sf 1 sb 1 } 50 | { createRectangleCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 51 | { brush str 1 ssx 1 ssy 1 sf 1 sb 1 } 52 | { eraser src 2 str 1 ssx 1 ssy 1 sf 1 sb 1 } 53 | { clone src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 54 | { reveal src 3 str 1 ssx 1 ssy 1 sf 1 sb 1 } 55 | { dodge src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 56 | { burn src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 57 | { blur src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 58 | { sharpen src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 59 | { smear src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 60 | } } 61 | toolbar_brush_hardness 0.200000003 62 | toolbar_source_transform_scale {1 1} 63 | toolbar_source_transform_center {960 408} 64 | colorOverlay {0 0 0 0} 65 | lifetime_type "all frames" 66 | lifetime_start 1043 67 | lifetime_end 1043 68 | motionblur_shutter_offset_type centred 69 | feather 100 70 | source_black_outside true 71 | name Roto10 72 | selected true 73 | xpos 2815 74 | ypos -228 75 | } 76 | push $cut_paste_input 77 | Dot { 78 | name Dot8 79 | selected true 80 | xpos 2547 81 | ypos -594 82 | } 83 | set Ncf064730 [stack 0] 84 | Dot { 85 | name Dot9 86 | selected true 87 | xpos 2882 88 | ypos -594 89 | } 90 | Dot { 91 | name Dot10 92 | selected true 93 | xpos 2882 94 | ypos -536 95 | } 96 | set N2c934530 [stack 0] 97 | Cryptomatte { 98 | pickerAdd {0.1092655659 0.248249054 0.2429962158 0 1047 454 1048 455} 99 | matteList /character/* 100 | lastSelectedCryptoLayerName identifier_name 101 | name Cryptomatte5 102 | selected true 103 | xpos 2848 104 | ypos -466 105 | } 106 | set Nf6d9000 [stack 0] 107 | Dot { 108 | name Dot11 109 | selected true 110 | xpos 2882 111 | ypos -256 112 | } 113 | push $Nf6d9000 114 | push $N2c934530 115 | Dot { 116 | name Dot12 117 | selected true 118 | xpos 2720 119 | ypos -536 120 | } 121 | Multiply { 122 | inputs 1+1 123 | value 0 124 | invert_mask true 125 | name Multiply10 126 | selected true 127 | xpos 2686 128 | ypos -472 129 | } 130 | Matrix { 131 | matrix { 132 | {-1 -2 -1} 133 | {0 0 0} 134 | {1 2 1} 135 | } 136 | name Matrix1 137 | selected true 138 | xpos 2686 139 | ypos -422 140 | } 141 | Dilate { 142 | size -0.25 143 | name Dilate4 144 | selected true 145 | xpos 2686 146 | ypos -372 147 | } 148 | Blur { 149 | size 1 150 | name Blur1 151 | selected true 152 | xpos 2686 153 | ypos -318 154 | } 155 | Multiply { 156 | inputs 1+1 157 | value 0 158 | invert_mask true 159 | name Multiply11 160 | selected true 161 | xpos 2686 162 | ypos -266 163 | } 164 | Multiply { 165 | inputs 1+1 166 | value 0 167 | invert_mask true 168 | name Multiply12 169 | selected true 170 | xpos 2686 171 | ypos -228 172 | } 173 | push $Ncf064730 174 | Grade { 175 | inputs 1+1 176 | white 2.42 177 | name Grade12 178 | selected true 179 | xpos 2513 180 | ypos -222 181 | } 182 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/ld_lightwrap.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | BackdropNode { 4 | inputs 0 5 | name BackdropNode2 6 | tile_color 0x7171c600 7 | label "lightwrap (upper matrix)" 8 | note_font_size 42 9 | selected true 10 | xpos 2503 11 | ypos -672 12 | bdwidth 487 13 | bdheight 486 14 | } 15 | Roto { 16 | inputs 0 17 | output alpha 18 | curves {{{v x3f99999a} 19 | {f 0} 20 | {n 21 | {layer Root 22 | {f 2097152} 23 | {t x44700000 x43cc0000} 24 | {a pt1x 0 pt1y 0 pt2x 0 pt2y 0 pt3x 0 pt3y 0 pt4x 0 pt4y 0 ptex00 0 ptex01 0 ptex02 0 ptex03 0 ptex10 0 ptex11 0 ptex12 0 ptex13 0 ptex20 0 ptex21 0 ptex22 0 ptex23 0 ptex30 0 ptex31 0 ptex32 0 ptex33 0 ptof1x 0 ptof1y 0 ptof2x 0 ptof2y 0 ptof3x 0 ptof3y 0 ptof4x 0 ptof4y 0 pterr 0 ptrefset 0 ptmot x40800000 ptref 0} 25 | {curvegroup Bezier1 512 bezier 26 | {{cc 27 | {f 8192} 28 | {px x44826000 29 | {xc2b80000 x40400000} 30 | {x436c99a0 x43f83333} 31 | {x42b80000 xc0400000} 32 | {0 0} 33 | {x44f31335 x4406e666} 34 | {0 0} 35 | {x424c0000 xc0e00000} 36 | {x44f9a000 x447fc000} 37 | {xc24c0000 x40e00000} 38 | {0 0} 39 | {x43700000 x44724000} 40 | {0 0}}} idem} 41 | {tx x44826000 x4488d800 x4431a000} 42 | {a fx x42c80000 fy x42c80000 osw x41200000 osf 0 str 1 spx x44700000 spy x43cc0000 sb 1 ltn x44826000 ltm x44826000 tt x40800000}}}}}} 43 | toolbox {selectAll { 44 | { selectAll str 1 ssx 1 ssy 1 sf 1 } 45 | { createBezier str 1 ssx 1 ssy 1 sf 1 sb 1 tt 4 } 46 | { createBezierCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 47 | { createBSpline str 1 ssx 1 ssy 1 sf 1 sb 1 } 48 | { createEllipse str 1 ssx 1 ssy 1 sf 1 sb 1 } 49 | { createRectangle str 1 ssx 1 ssy 1 sf 1 sb 1 } 50 | { createRectangleCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 51 | { brush str 1 ssx 1 ssy 1 sf 1 sb 1 } 52 | { eraser src 2 str 1 ssx 1 ssy 1 sf 1 sb 1 } 53 | { clone src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 54 | { reveal src 3 str 1 ssx 1 ssy 1 sf 1 sb 1 } 55 | { dodge src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 56 | { burn src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 57 | { blur src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 58 | { sharpen src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 59 | { smear src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 60 | } } 61 | toolbar_brush_hardness 0.200000003 62 | toolbar_source_transform_scale {1 1} 63 | toolbar_source_transform_center {960 408} 64 | colorOverlay {0 0 0 0} 65 | lifetime_type "all frames" 66 | lifetime_start 1043 67 | lifetime_end 1043 68 | motionblur_shutter_offset_type centred 69 | feather 100 70 | source_black_outside true 71 | name Roto10 72 | selected true 73 | xpos 2815 74 | ypos -228 75 | } 76 | push $cut_paste_input 77 | Dot { 78 | name Dot8 79 | selected true 80 | xpos 2547 81 | ypos -594 82 | } 83 | set Ncf064730 [stack 0] 84 | Dot { 85 | name Dot9 86 | selected true 87 | xpos 2882 88 | ypos -594 89 | } 90 | Dot { 91 | name Dot10 92 | selected true 93 | xpos 2882 94 | ypos -536 95 | } 96 | set N2c934530 [stack 0] 97 | Cryptomatte { 98 | pickerAdd {0.1092655659 0.248249054 0.2429962158 0 1047 454 1048 455} 99 | matteList /character/* 100 | lastSelectedCryptoLayerName identifier_name 101 | name Cryptomatte5 102 | selected true 103 | xpos 2848 104 | ypos -466 105 | } 106 | set Nf6d9000 [stack 0] 107 | Dot { 108 | name Dot11 109 | selected true 110 | xpos 2882 111 | ypos -256 112 | } 113 | push $Nf6d9000 114 | push $N2c934530 115 | Dot { 116 | name Dot12 117 | selected true 118 | xpos 2720 119 | ypos -536 120 | } 121 | Multiply { 122 | inputs 1+1 123 | value 0 124 | invert_mask true 125 | name Multiply10 126 | selected true 127 | xpos 2686 128 | ypos -472 129 | } 130 | Matrix { 131 | matrix { 132 | {-1 -2 -1} 133 | {0 0 0} 134 | {1 2 1} 135 | } 136 | name Matrix1 137 | selected true 138 | xpos 2686 139 | ypos -422 140 | } 141 | Dilate { 142 | size -0.25 143 | name Dilate4 144 | selected true 145 | xpos 2686 146 | ypos -372 147 | } 148 | Blur { 149 | size 1 150 | name Blur1 151 | selected true 152 | xpos 2686 153 | ypos -318 154 | } 155 | Multiply { 156 | inputs 1+1 157 | value 0 158 | invert_mask true 159 | name Multiply11 160 | selected true 161 | xpos 2686 162 | ypos -266 163 | } 164 | Multiply { 165 | inputs 1+1 166 | value 0 167 | invert_mask true 168 | name Multiply12 169 | selected true 170 | xpos 2686 171 | ypos -228 172 | } 173 | push $Ncf064730 174 | Grade { 175 | inputs 1+1 176 | white 2.42 177 | name Grade12 178 | selected true 179 | xpos 2513 180 | ypos -222 181 | } 182 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/ld_skydome_basic.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | Read { 4 | inputs 0 5 | file_type hdr 6 | file /groups/dungeons/lighting/HDRs/G_HDR_1.exr 7 | format "4096 2048 0 0 4096 2048 1 4K_LatLong" 8 | origset true 9 | name Read2 10 | selected true 11 | xpos 5100 12 | ypos -5826 13 | } 14 | Group { 15 | name SB_Skydome1 16 | selected true 17 | xpos 5019 18 | ypos -5670 19 | addUserKnob {20 User} 20 | addUserKnob {26 text l "Skydome Transformations"} 21 | addUserKnob {41 translate l Translate T Sphere1.translate} 22 | addUserKnob {41 rotate l Rotate T Sphere1.rotate} 23 | addUserKnob {41 uniform_scale l "Uniform Scale" T Sphere1.uniform_scale} 24 | addUserKnob {26 ""} 25 | addUserKnob {26 text_1 l "" +STARTLINE T "\n\n(Instructions for Merging)\nif the merge node's B pipe is plugged into this skydome node:\n\n-Set \"metadata from\" to 'A'\n-Set \"frame range from\" to 'A' \n-Set \"Also Merge\" to 'all'\n"} 26 | } 27 | Input { 28 | inputs 0 29 | name Camera 30 | xpos 509 31 | ypos -182 32 | number 1 33 | } 34 | Input { 35 | inputs 0 36 | name Map 37 | xpos 375 38 | ypos -318 39 | } 40 | Sphere { 41 | radius 10000 42 | name Sphere1 43 | selected true 44 | xpos 375 45 | ypos -256 46 | } 47 | push 0 48 | ScanlineRender { 49 | inputs 3 50 | conservative_shader_sampling false 51 | motion_vectors_type distance 52 | name ScanlineRender1 53 | xpos 375 54 | ypos -182 55 | } 56 | Output { 57 | name Output1 58 | xpos 375 59 | ypos -104 60 | } 61 | Output { 62 | name Output2 63 | xpos 375 64 | ypos -4 65 | } 66 | end_group 67 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/NungeonTools/toolsets/ld_snow_glitter_clamp.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | version 15.0 v4 3 | BackdropNode { 4 | inputs 0 5 | name BackdropNode1 6 | tile_color 0x8e8e3800 7 | label "clampin them snowflakes" 8 | note_font_size 42 9 | selected true 10 | xpos -544 11 | ypos -1228 12 | bdwidth 490 13 | bdheight 523 14 | } 15 | push $cut_paste_input 16 | Dot { 17 | name Dot6 18 | selected true 19 | xpos -110 20 | ypos -1079 21 | } 22 | set Ncbe8c30 [stack 0] 23 | add_layer {specular specular.red specular.green specular.blue} 24 | Shuffle2 { 25 | fromInput1 {{0} B} 26 | in1 specular 27 | fromInput2 {{0} B} 28 | mappings "4 specular.red 0 0 rgba.red 0 0 specular.green 0 1 rgba.green 0 1 specular.blue 0 2 rgba.blue 0 2 black -1 -1 rgba.alpha 0 3" 29 | name Shuffle3 30 | selected true 31 | xpos -429 32 | ypos -1083 33 | } 34 | set Ncbef600 [stack 0] 35 | Dot { 36 | name Dot7 37 | selected true 38 | xpos -494 39 | ypos -1079 40 | } 41 | Clamp { 42 | maximum 0.029 43 | name Clamp1 44 | selected true 45 | xpos -528 46 | ypos -907 47 | } 48 | Dot { 49 | name Dot5 50 | selected true 51 | xpos -494 52 | ypos -731 53 | } 54 | push $Ncbef600 55 | Dot { 56 | name Dot4 57 | selected true 58 | xpos -395 59 | ypos -945 60 | } 61 | push $Ncbe8c30 62 | Merge2 { 63 | inputs 2 64 | operation from 65 | name Merge1 66 | selected true 67 | xpos -144 68 | ypos -949 69 | } 70 | Merge2 { 71 | inputs 2 72 | operation plus 73 | name Merge2 74 | selected true 75 | xpos -144 76 | ypos -735 77 | } 78 | -------------------------------------------------------------------------------- /pipeline/software/nuke/tools/init.py: -------------------------------------------------------------------------------- 1 | import nuke 2 | 3 | nuke.pluginAddPath("./NukeSurvivalToolkit_publicRelease/NukeSurvivalToolkit") 4 | nuke.pluginAddPath("./NungeonTools") 5 | -------------------------------------------------------------------------------- /pipeline/software/substance_designer/__init__.py: -------------------------------------------------------------------------------- 1 | from .dcc import SubstanceDesignerDCC as SubstanceDesignerDCC 2 | -------------------------------------------------------------------------------- /pipeline/software/substance_designer/dcc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import platform 6 | 7 | from pathlib import Path 8 | 9 | from ..baseclass import DCC 10 | from env import Executables 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class SubstanceDesignerDCC(DCC): 16 | """Substance Designer DCC class""" 17 | 18 | def __init__( 19 | self, is_python_shell: bool = False, extra_args: list[str] | None = None 20 | ) -> None: 21 | this_path = Path(__file__).resolve() 22 | pipe_path = this_path.parents[2] 23 | 24 | system = platform.system() 25 | 26 | env_vars = { 27 | "DCC": str(this_path.parent.name), 28 | "OCIO": str(pipe_path / "lib/ocio/love-v01/config.ocio"), 29 | "PYTHONPATH": os.pathsep.join( 30 | [ 31 | str(pipe_path), 32 | ] 33 | ), 34 | "QT_PLUGIN_PATH": "", 35 | } 36 | 37 | if is_python_shell: 38 | raise NotImplementedError("Python shell is not supported for this DCC") 39 | 40 | launch_command = str(Executables.substance_designer) 41 | if not launch_command: 42 | raise NotImplementedError( 43 | f"The operating system {system} is not a supported OS for this DCC software" 44 | ) 45 | 46 | launch_args = [ 47 | "--config-file", 48 | str(this_path.parent / "lnd_configuration.sbscfg"), 49 | *(extra_args or []), 50 | ] 51 | 52 | super().__init__(launch_command, launch_args, env_vars) 53 | -------------------------------------------------------------------------------- /pipeline/software/substance_designer/lnd_configuration.sbscfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | <_1 prefix="_"> 7 | lnd_project.sbsprj 8 | 9 | 10 | 11 | 12 | 13 | sbs_engine_v9 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /pipeline/software/substance_designer/lnd_project.sbsprj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | 6 | 7 | 8 | 9 | 10 | 11 | $(PROJECT_DIR)://user_view3d_settings.sbsscn 12 | false 13 | false 14 | true 15 | sd-3dview-shaders://adobe_standard_material.glslfx 16 | sd-3dview-maps://panorama_map.hdr 17 | 18 | 19 | 20 | 2 21 | 22 | 23 | 24 | ACEScg 25 | Technical 26 | Display Rec.709 27 | true 28 | parse_filename 29 | ACEScg 30 | ACEScg 31 | 32 | 33 | 34 | 2 35 | 36 | 37 | 38 | ocio 39 | 40 | 41 | true 42 | relative-colorimetric 43 | Raw 44 | srgb 45 | sRGB IEC61966-2.1 46 | false 47 | sRGB IEC61966-2.1 48 | sRGB IEC61966-2.1 49 | 50 | 51 | true 52 | 53 | -------------------------------------------------------------------------------- /pipeline/software/substance_painter/__init__.py: -------------------------------------------------------------------------------- 1 | from .dcc import SubstancePainterDCC as SubstancePainterDCC 2 | -------------------------------------------------------------------------------- /pipeline/software/substance_painter/dcc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import platform 6 | 7 | from pathlib import Path 8 | from typing import TYPE_CHECKING 9 | 10 | if TYPE_CHECKING: 11 | import typing 12 | 13 | from shared.util import resolve_mapped_path 14 | from ..baseclass import DCC 15 | from env import Executables 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | class SubstancePainterDCC(DCC): 21 | """Substance Painter DCC class""" 22 | 23 | def __init__( 24 | self, is_python_shell: bool = False, extra_args: list[str] | None = None 25 | ) -> None: 26 | this_path = Path(__file__).resolve() 27 | pipe_path = this_path.parents[2] 28 | 29 | system = platform.system() 30 | 31 | env_vars: typing.Mapping[str, int | str | None] | None 32 | env_vars = { 33 | "DCC": str(this_path.parent.name), 34 | "OCIO": str( 35 | resolve_mapped_path(pipe_path / "lib/ocio/love-v01/config.ocio") 36 | ), 37 | "PIPE_LOG_LEVEL": log.getEffectiveLevel(), 38 | "PIPE_PATH": str(pipe_path), 39 | "PYTHONPATH": os.pathsep.join( 40 | [ 41 | str(pipe_path), 42 | ] 43 | ), 44 | "QT_PLUGIN_PATH": "", 45 | "SUBSTANCE_PAINTER_PLUGINS_PATH": str(this_path.parent / "plugins"), 46 | } 47 | 48 | if is_python_shell: 49 | raise NotImplementedError("Python shell is not supported for this DCC") 50 | 51 | launch_command = str(Executables.substance_painter) 52 | if not launch_command: 53 | raise NotImplementedError( 54 | f"The operating system {system} is not a supported OS for this DCC software" 55 | ) 56 | 57 | launch_args: list[str] = extra_args or [] 58 | 59 | super().__init__(launch_command, launch_args, env_vars) 60 | -------------------------------------------------------------------------------- /pipeline/software/substance_painter/plugins/startup/export.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from Qt import QtWidgets 4 | 5 | import substance_painter as sp 6 | 7 | import pipe.sp 8 | from pipe.sp.ui import SubstanceExportWindow 9 | from pipe.glui.dialogs import MessageDialog 10 | 11 | 12 | plugin_widgets: list[QtWidgets.QWidget] = [] 13 | 14 | 15 | def start_plugin(): 16 | # Create text widget for menu 17 | action = QtWidgets.QAction("LnD — Publish Textures") 18 | action.triggered.connect(launch_exporter) 19 | 20 | # Add widget to the File menu 21 | sp.ui.add_action(sp.ui.ApplicationMenu.File, action) 22 | 23 | # Store the widget for proper cleanup later 24 | plugin_widgets.append(action) 25 | 26 | 27 | def close_plugin(): 28 | for widget in plugin_widgets: 29 | sp.ui.delete_ui_element(widget) 30 | 31 | plugin_widgets.clear() 32 | 33 | 34 | if __name__ == "__main__": 35 | window = start_plugin() 36 | 37 | 38 | def launch_exporter(): 39 | if not sp.project.is_open(): 40 | MessageDialog( 41 | pipe.sp.local.get_main_qt_window(), 42 | "Please open a project before trying to publish", 43 | "No project open", 44 | ).exec_() 45 | return 46 | 47 | # remove existing windows before opening a new one 48 | for widget in plugin_widgets: 49 | if isinstance(widget, SubstanceExportWindow): 50 | widget.close() 51 | sp.ui.delete_ui_element(widget) 52 | plugin_widgets.remove(widget) 53 | break 54 | 55 | # launch window 56 | global window 57 | window = SubstanceExportWindow() 58 | window.show() 59 | 60 | print("Launching Substance Exporter") 61 | -------------------------------------------------------------------------------- /pipeline/software/substance_painter/plugins/startup/preflight.py: -------------------------------------------------------------------------------- 1 | """Preflight checks to run on file load""" 2 | 3 | import substance_painter as sp 4 | 5 | import pipe.sp 6 | from pipe.db import DB 7 | from env_sg import DB_Config 8 | 9 | conn = DB.Get(DB_Config) 10 | 11 | 12 | def start_plugin(): 13 | sp.event.DISPATCHER.connect_strong(sp.event.ProjectEditionEntered, do_preflight) 14 | 15 | 16 | def close_plugin(): 17 | sp.event.DISPATCHER.disconnect(sp.event.ProjectEditionEntered, do_preflight) 18 | 19 | 20 | def do_preflight(event: sp.event.Event) -> None: 21 | metaUpdater = pipe.sp.metadata.MetadataUpdater() 22 | srgbChecker = pipe.sp.channels.sRGBChecker() 23 | metaUpdater.check() or metaUpdater.prompt_update() 24 | srgbChecker.check() or srgbChecker.prompt_srgb_fix() 25 | 26 | 27 | if __name__ == "__main__": 28 | window = start_plugin() 29 | -------------------------------------------------------------------------------- /pipeline/software/substance_painter/plugins/startup/shelf.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import substance_painter as sp 4 | 5 | from shared.util import get_production_path 6 | 7 | _SHELF_NAME = "lnd-resources" 8 | 9 | 10 | def start_plugin(): 11 | # create the LnD shelf 12 | sp.resource.Shelves().add( 13 | _SHELF_NAME, str(get_production_path() / "painter_assets") 14 | ) 15 | 16 | 17 | def close_plugin(): 18 | sp.resource.Shelves().remove(_SHELF_NAME) 19 | 20 | 21 | if __name__ == "__main__": 22 | window = start_plugin() 23 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.mypy] 2 | exclude = [ 3 | ".git", 4 | 'MOPS', 5 | "NukeSurvivalToolkit_publicRelease", 6 | 'VFX-LYNX', 7 | 'ae_SVG', 8 | 'dwpicker.*', 9 | 'lib/python', 10 | 'maya-timeline-marker_git', 11 | "mayacapture", 12 | 'modelChecker', 13 | 'pin_shift.py', 14 | 'rjg', 15 | 'shotgun_api3', 16 | 'studiolibrary', 17 | 'timeline_marker', 18 | ] 19 | files = 'pipeline' 20 | 21 | [[tool.mypy.overrides]] 22 | module = "dwpicker" 23 | ignore_missing_imports = true 24 | 25 | [[tool.mypy.overrides]] 26 | module = "modelChecker.*" 27 | ignore_missing_imports = true 28 | 29 | [[tool.mypy.overrides]] 30 | module = "substance_painter_plugins" 31 | ignore_missing_imports = true 32 | 33 | [tool.ruff] 34 | exclude = [ 35 | ".git", 36 | "MOPS", 37 | "NukeSurvivalToolkit_publicRelease", 38 | "VFX-LYNX", 39 | "ae_SVG", 40 | "dwpicker", 41 | "dwpicker_git", 42 | "lib/python", 43 | "mayacapture", 44 | "maya-timeline-marker_git", 45 | "modelChecker", 46 | "modelChecker_git", 47 | 'pin_shift.py', 48 | "rjg", 49 | "shotgun_api3", 50 | "studiolibrary", 51 | "timeline_marker", 52 | ] 53 | 54 | [tool.poetry] 55 | name = "dungeon-pipeline" 56 | version = "0.1.0" 57 | description = "Pipeline for BYU Animation's Class of 2025 capstone film" 58 | license = "BSD-3" 59 | authors = ["Scott Milner "] 60 | readme = "README.md" 61 | package-mode = false 62 | 63 | [tool.poetry.dependencies] 64 | python = "^3.9" 65 | attrs = "^24.2.0" 66 | cattrs = "^23.2.3" 67 | ffmpeg-python = "^0.2.0" 68 | qt-py = {extras = ["stubs"], version = "^1.4.1"} 69 | filelock = "^3.17.0" 70 | ruff = "^0.9.9" 71 | 72 | [tool.poetry.group.dev.dependencies] 73 | maya-stubs = "^0.4.1" 74 | types-usd = "^24.5.1" 75 | types-houdini = "^19.5.1" 76 | types-katana = "^5.0.5.0" 77 | types-mari = "^5.0.5.0" 78 | types-nuke = "^13.2.8.0" 79 | types-opencolorio = "^2.2.1.2" 80 | types-pyside2 = "^5.15.2.1.7" 81 | types-substance-painter = "^2023.8.3.0.0" 82 | nptyping = "^2.5.0" 83 | qt-py = {extras = ["stubs"], version = "^1.4.1"} 84 | mypy = "^1.15.0" 85 | --------------------------------------------------------------------------------