├── documentation ├── theme │ └── slate │ │ ├── __init__.py │ │ ├── mkdocs_theme.yml │ │ └── css │ │ └── base.css ├── docs │ ├── media │ │ ├── layer_alchemy_200px.png │ │ └── parameters │ │ │ ├── GradeBeauty.png │ │ │ ├── GradeLayerSet.png │ │ │ ├── FlattenLayerSet.png │ │ │ ├── RemoveLayerSet.png │ │ │ ├── GradeBeautyLayer.png │ │ │ ├── MultiplyLayerSet.png │ │ │ ├── GradeBeautyLayerSet.png │ │ │ └── GradeBeauty_uncollapsed.png │ ├── index.md │ ├── MultiplyLayerSet.md │ ├── GradeBeautyLayer.md │ ├── GradeLayerSet.md │ ├── RemoveLayerSet.md │ ├── GradeBeautyLayerSet.md │ ├── FlattenLayerSet.md │ ├── about.md │ ├── dev_guide.md │ ├── core.md │ ├── configs_and_topology.md │ ├── tools.md │ └── GradeBeauty.md ├── mkdocs.yml └── CMakeLists.txt ├── _config.yml ├── python ├── layer_alchemy │ ├── __init__.py │ └── _config.py └── nuke │ ├── layer_alchemy │ ├── __init__.py │ ├── constants.py │ ├── callbacks.py │ ├── documentation.py │ └── utilities.py │ ├── init.py │ └── menu.py ├── configs ├── channels │ ├── facility.yaml │ ├── topology.yaml │ ├── channels.yaml │ └── openexr.yaml ├── layers │ ├── nuke.yaml │ ├── layers.yaml │ ├── facility.yaml │ ├── _deprecated │ │ └── arnold4.yaml │ └── arnold5.yaml ├── channels.yaml └── layers.yaml ├── icons ├── GradeBeauty.png ├── GradeLayerSet.png ├── documentation.png ├── layer_alchemy.png ├── FlattenLayerSet.png ├── RemoveLayerSet.png ├── GradeBeautyLayer.png ├── MultiplyLayerSet.png ├── GradeBeautyLayerSet.png └── layer_alchemy_200px.png ├── .gitignore ├── include ├── version.h ├── LayerSetTypes.h ├── LayerSetConfig.h ├── nuke │ ├── LayerSet.h │ └── LayerSetKnob.h └── LayerSetCore.h ├── docker └── linux │ └── Dockerfile ├── .github ├── pull_request_template.md └── ISSUE_TEMPLATE │ └── bug_report.md ├── src ├── LayerSetConfig.cpp ├── nuke │ ├── MultiplyLayerSet.cpp │ ├── RemoveLayerSet.cpp │ ├── CMakeLists.txt │ ├── GradeLayerSet.cpp │ ├── FlattenLayerSet.cpp │ ├── LayerSetKnob.cpp │ ├── LayerSet.cpp │ ├── GradeBeautyLayer.cpp │ ├── GradeBeautyLayerSet.cpp │ └── GradeBeauty.cpp ├── ConfigTester.cpp ├── LayerTester.cpp └── LayerSetCore.cpp ├── LICENSE.md ├── LICENSE-THIRD-PARTY.md ├── README.md └── CMakeLists.txt /documentation/theme/slate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /python/layer_alchemy/__init__.py: -------------------------------------------------------------------------------- 1 | from _config import collapse -------------------------------------------------------------------------------- /configs/channels/facility.yaml: -------------------------------------------------------------------------------- 1 | # you can add custom channels here 2 | -------------------------------------------------------------------------------- /python/nuke/layer_alchemy/__init__.py: -------------------------------------------------------------------------------- 1 | """LayerAlchemy python module init""" 2 | -------------------------------------------------------------------------------- /icons/GradeBeauty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/GradeBeauty.png -------------------------------------------------------------------------------- /icons/GradeLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/GradeLayerSet.png -------------------------------------------------------------------------------- /icons/documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/documentation.png -------------------------------------------------------------------------------- /icons/layer_alchemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/layer_alchemy.png -------------------------------------------------------------------------------- /icons/FlattenLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/FlattenLayerSet.png -------------------------------------------------------------------------------- /icons/RemoveLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/RemoveLayerSet.png -------------------------------------------------------------------------------- /icons/GradeBeautyLayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/GradeBeautyLayer.png -------------------------------------------------------------------------------- /icons/MultiplyLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/MultiplyLayerSet.png -------------------------------------------------------------------------------- /icons/GradeBeautyLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/GradeBeautyLayerSet.png -------------------------------------------------------------------------------- /icons/layer_alchemy_200px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/icons/layer_alchemy_200px.png -------------------------------------------------------------------------------- /documentation/theme/slate/mkdocs_theme.yml: -------------------------------------------------------------------------------- 1 | # Config options for 'slate' theme 2 | 3 | extends: mkdocs 4 | 5 | hljs_style: darcula 6 | -------------------------------------------------------------------------------- /documentation/docs/media/layer_alchemy_200px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/layer_alchemy_200px.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/GradeBeauty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/GradeBeauty.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/GradeLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/GradeLayerSet.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/FlattenLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/FlattenLayerSet.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/RemoveLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/RemoveLayerSet.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/GradeBeautyLayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/GradeBeautyLayer.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/MultiplyLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/MultiplyLayerSet.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/GradeBeautyLayerSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/GradeBeautyLayerSet.png -------------------------------------------------------------------------------- /documentation/docs/media/parameters/GradeBeauty_uncollapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebjacob/LayerAlchemy/HEAD/documentation/docs/media/parameters/GradeBeauty_uncollapsed.png -------------------------------------------------------------------------------- /python/nuke/init.py: -------------------------------------------------------------------------------- 1 | """LayerAlchemy Nuke init""" 2 | 3 | import layer_alchemy.utilities 4 | import layer_alchemy.callbacks 5 | 6 | if layer_alchemy.utilities.nukeVersionCompatible(): 7 | layer_alchemy.utilities.validateConfigFileEnvironmentVariables() 8 | layer_alchemy.utilities.pluginAddPaths() 9 | layer_alchemy.callbacks.setupCallbacks() 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | /nbproject/ 3 | *.bin 4 | *.pyc 5 | *.py[cod] 6 | ._* 7 | .DS_Store 8 | *~ 9 | .nfs* 10 | */.nfs* 11 | *.bak 12 | *.bk.py 13 | Thumbs.db 14 | *.orig 15 | *.bck 16 | */#*.*# 17 | .project 18 | .pydevproject 19 | *.rvpkg 20 | /build 21 | /docs/build 22 | /.coverage 23 | /.cache 24 | /htmlcov 25 | .idea 26 | documentation/site 27 | .vscode 28 | -------------------------------------------------------------------------------- /configs/channels/topology.yaml: -------------------------------------------------------------------------------- 1 | # defines which keys can be used to generate a topology 2 | # they are meant to be mirrorred as they are alternate ways of encoding the same channel 3 | 4 | topology: 5 | - _alpha 6 | - _uv 7 | - _vec3 8 | - _vec4 9 | - _xyz 10 | - _z 11 | 12 | exr_topology: 13 | - _alpha_exr 14 | - _uv_exr 15 | - _vec3_exr 16 | - _vec4_exr 17 | - _xyz_exr 18 | - _z_exr 19 | -------------------------------------------------------------------------------- /documentation/docs/index.md: -------------------------------------------------------------------------------- 1 | ![logo](media/layer_alchemy_200px.png) 2 | 3 | # Welcome to LayerAlchemy 4 | LayerAlchemy is a suite of Nuke plugins for nuke to simplify 2d nodal multichannel workflows 5 | 6 | Under the hood, it uses a c++ [yaml](https://yaml.org/) config file based layer name classifier that can be 7 | adapted to other cg render engines or pipelines. 8 | 9 | [Getting involved](about.md#Contributing) 10 | -------------------------------------------------------------------------------- /include/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define LAYER_ALCHEMY_VERSION_MAJOR 0 3 | #define LAYER_ALCHEMY_VERSION_MINOR 9 4 | #define LAYER_ALCHEMY_VERSION_PATCH 1 5 | 6 | #include 7 | 8 | static const std::string LAYER_ALCHEMY_VERSION_STRING = 9 | std::to_string(LAYER_ALCHEMY_VERSION_MAJOR) + "." + 10 | std::to_string(LAYER_ALCHEMY_VERSION_MINOR) + "." + 11 | std::to_string(LAYER_ALCHEMY_VERSION_PATCH); 12 | -------------------------------------------------------------------------------- /configs/channels/channels.yaml: -------------------------------------------------------------------------------- 1 | 2 | channels_red: 3 | - red 4 | channels_green: 5 | - green 6 | channels_blue: 7 | - blue 8 | channels_alpha: 9 | - alpha 10 | 11 | _alpha: 12 | - alpha 13 | 14 | _z: 15 | - Z 16 | 17 | _uv: 18 | - u 19 | - v 20 | 21 | _vec3: 22 | - red 23 | - green 24 | - blue 25 | 26 | _vec4: 27 | - red 28 | - green 29 | - blue 30 | - alpha 31 | 32 | _xyz: 33 | - X 34 | - Y 35 | - Z 36 | -------------------------------------------------------------------------------- /docker/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:6.10 2 | RUN yum install wget -y 3 | RUN wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo 4 | RUN yum install -y devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++ mesa-libGLU-devel cmake git centos-release-scl && yum install -y python27 5 | RUN echo source /opt/rh/devtoolset-2/enable >> /root/.bash_profile 6 | RUN scl enable python27 "/opt/rh/python27/root/usr/bin/easy_install-2.7 pip && /opt/rh/python27/root/usr/bin/pip2.7 install requests mkdocs" 7 | -------------------------------------------------------------------------------- /include/LayerSetTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LayerSetTypes.h 3 | * Author: sjacob 4 | * 5 | * Created on November 22, 2018, 6:28 p.m. 6 | */ 7 | #pragma once 8 | #include 9 | #include 10 | #include 11 | 12 | using std::string; 13 | using std::vector; 14 | using std::map; 15 | 16 | // Type alias for storing a string vector of layer names 17 | typedef vector StrVecType; 18 | // Type alias for storing layer category names and a vector of layer names 19 | typedef map > StrMapType; 20 | -------------------------------------------------------------------------------- /documentation/docs/MultiplyLayerSet.md: -------------------------------------------------------------------------------- 1 | # MultiplyLayerSet 2 | 3 | MultiplyLayerSet provides a simple way to multiply a selection of layers using [LayerSets](core.md#layersets) 4 | 5 | It's exactly like the Nuke Multiply node except that, you can multiply a selection of layers at the same time 6 | 7 | ![MultiplyLayerSet](media/parameters/MultiplyLayerSet.png) 8 | 9 | ## Knob reference 10 | 11 | | knob name | type | what it does | 12 | | --------- | ---- | ------------ 13 | | layer_set | enumeration | decides which [LayerSet](core.md#layersets) to use | 14 | 15 | -------------------------------------------------------------------------------- /configs/channels/openexr.yaml: -------------------------------------------------------------------------------- 1 | # openexr channels 2 | _alpha_exr: 3 | - A 4 | 5 | _z_exr: 6 | - Z 7 | 8 | _uv_exr: 9 | - u 10 | - v 11 | 12 | _vec3_exr: 13 | - B 14 | - G 15 | - R 16 | 17 | _vec4_exr: 18 | - A 19 | - B 20 | - G 21 | - R 22 | 23 | _xyz_exr: 24 | - X 25 | - Y 26 | - Z 27 | # these are the bare minimal channels for a valid deepexr file 28 | exr_channels_deep_minimal: 29 | - A 30 | - Z 31 | # this is the full deep channel set 32 | exr_deep_channels: 33 | - A 34 | - Z 35 | - Zback 36 | - deep.B 37 | - deep.G 38 | - deep.R 39 | -------------------------------------------------------------------------------- /configs/layers/nuke.yaml: -------------------------------------------------------------------------------- 1 | # stock nuke layer names 2 | 3 | uv: !!set &nuke_uv 4 | ? forward 5 | ? backward 6 | ? motion 7 | ? uv_extra 8 | 9 | matte: !!set &nuke_matte 10 | ? mask 11 | ? mask_planartrack 12 | ? mask_splinewarp 13 | ? rotopaint_mask 14 | 15 | depth: !!set &nuke_depth 16 | ? depth 17 | ? depth_extra 18 | 19 | disparity: !!set &nuke_disparity 20 | ? disparityl 21 | ? disparityr 22 | 23 | _alpha: !!set &nuke_alpha 24 | <<: *nuke_matte 25 | 26 | _vec2: !!set 27 | <<: *nuke_uv 28 | 29 | _z: !!set 30 | ? depth 31 | 32 | non_color: !!set 33 | <<: *nuke_matte 34 | <<: *nuke_disparity 35 | <<: *nuke_alpha 36 | <<: *nuke_uv 37 | <<: *nuke_depth 38 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Explain what, why and the though process leading to the changes 4 | 5 | link to issue if relevant. 6 | 7 | ### code changes 8 | - point form description of changes 9 | 10 | ## Type of change 11 | 12 | is this a major or a minor or patch update ? 13 | 14 | ## How Has This Been Tested? 15 | 16 | Describe operating system and Nuke versions 17 | 18 | ## Checklist: 19 | 20 | - [ ] My code follows the style guidelines of this project 21 | - [ ] I have performed a self-review of my own code 22 | - [ ] I have commented my code, particularly in hard-to-understand areas 23 | - [ ] I have made corresponding changes to the documentation 24 | - [ ] My changes generate no new warnings 25 | -------------------------------------------------------------------------------- /documentation/docs/GradeBeautyLayer.md: -------------------------------------------------------------------------------- 1 | # GradeBeautyLayer 2 | 3 | !!! info "" 4 | 5 | GradeBeautyLayer provides a simple way to specifically grade a cg layer and replace it in the beauty 6 | 7 | #### Order of operations : 8 | - input layer subtracted from the target layer 9 | - layer is modified 10 | - modified source layer is added to the target layer 11 | 12 | 13 | ![GradeBeautyLayer](media/parameters/GradeBeautyLayer.png) 14 | 15 | ## Knob reference 16 | 17 | | knob name | type | what it does | 18 | | --------- | ---- | ------------ 19 | | source_layer | enumeration | layer to to grade | 20 | | target_layer | enumeration | layer to subtract and add the modied source_layer to | 21 | | reset values | button | resets all color knobs to their defaults | 22 | 23 | -------------------------------------------------------------------------------- /include/LayerSetConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LayerSetConfig.h 3 | * Author: sjacob 4 | * 5 | * Created on November 22, 2018, 6:14 p.m. 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include "LayerSetTypes.h" 11 | 12 | //In the channels config files, these values will be used for topology functions 13 | static const string TOPOLOGY_KEY_LEXICAL = "topology"; 14 | 15 | // simple function to load yaml or json from a file path to a YAML::Node object 16 | YAML::Node _loadConfigFromPath(const string&); 17 | // converts YAML::Node data to a map of strings (StrMapType) 18 | StrMapType _categoryMapFromConfig(const YAML::Node&); 19 | // simple wrapper function to load a yaml or json file to a map of strings (StrMapType) 20 | StrMapType loadConfigToMap(const string&); 21 | -------------------------------------------------------------------------------- /documentation/docs/GradeLayerSet.md: -------------------------------------------------------------------------------- 1 | # GradeLayerSet 2 | 3 | !!! info "" 4 | 5 | GradeLayerSet provides a simple way to grade multiple layers using [LayerSets](core.md#layersets) 6 | 7 | 8 | It's exactly like the Nuke Grade node except that, you can grade multiple layers at the same time 9 | 10 | If you are grading cg layers and wish to propagate the changes to the beauty a the same time, have a look at 11 | [GradeBeautyLayerSet](GradeBeautyLayerSet.md) 12 | 13 | ![GradeLayerSet](media/parameters/GradeLayerSet.png) 14 | 15 | ## Knob reference 16 | 17 | | knob name | type | what it does | 18 | | --------- | ---- | ------------ 19 | | layer_set | enumeration | decides which [LayerSet](core.md#layersets) to use | 20 | | reset values | button | resets all color knobs to their defaults | 21 | -------------------------------------------------------------------------------- /documentation/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name : LayerAlchemy Documentation 2 | repo_url: https://github.com/sebjacob/LayerAlchemy 3 | site_description : LayerAlchemy documentation 4 | site_author : Sebastien Jacob 5 | use_directory_urls : false 6 | docs_dir : 'docs' 7 | markdown_extensions: 8 | - admonition 9 | 10 | theme: # https://github.com/mkdocs/mkdocs-bootswatch 11 | name : mkdocs 12 | custom_dir : theme/slate 13 | 14 | nav : 15 | - Home : index.md 16 | - Concept : core.md 17 | - Nuke plugins : 18 | - GradeBeauty.md 19 | - GradeBeautyLayerSet.md 20 | - GradeBeautyLayer.md 21 | - GradeLayerSet.md 22 | - FlattenLayerSet.md 23 | - RemoveLayerSet.md 24 | - MultiplyLayerSet.md 25 | - Dev Guide : 26 | - dev_guide.md 27 | - configs_and_topology.md 28 | - tools.md 29 | - About : about.md 30 | -------------------------------------------------------------------------------- /src/LayerSetConfig.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * implementation code focused on configuration file handling 3 | */ 4 | #include "LayerSetConfig.h" 5 | 6 | YAML::Node _loadConfigFromPath(const string& path) { 7 | YAML::Node config = YAML::LoadFile(path); 8 | return config; 9 | } 10 | 11 | StrMapType _categoryMapFromConfig(const YAML::Node& config) { 12 | StrMapType categoryMap; 13 | for (YAML::const_iterator it = config.begin(); it != config.end(); it++) { 14 | string categoryName = it->first.as(); 15 | StrVecType values = it->second.as(); 16 | categoryMap[categoryName] = values; 17 | } 18 | return categoryMap; 19 | } 20 | 21 | StrMapType loadConfigToMap(const string& yamlFilePath) { 22 | const YAML::Node config = _loadConfigFromPath(yamlFilePath); 23 | StrMapType categoryMap = _categoryMapFromConfig(config); 24 | return categoryMap; 25 | } -------------------------------------------------------------------------------- /configs/channels.yaml: -------------------------------------------------------------------------------- 1 | _alpha: 2 | - alpha 3 | _alpha_exr: 4 | - A 5 | _uv: 6 | - u 7 | - v 8 | _uv_exr: 9 | - u 10 | - v 11 | _vec3: 12 | - red 13 | - green 14 | - blue 15 | _vec3_exr: 16 | - B 17 | - G 18 | - R 19 | _vec4: 20 | - red 21 | - green 22 | - blue 23 | - alpha 24 | _vec4_exr: 25 | - A 26 | - B 27 | - G 28 | - R 29 | _xyz: 30 | - X 31 | - Y 32 | - Z 33 | _xyz_exr: 34 | - X 35 | - Y 36 | - Z 37 | _z: 38 | - Z 39 | _z_exr: 40 | - Z 41 | channels_alpha: 42 | - alpha 43 | channels_blue: 44 | - blue 45 | channels_green: 46 | - green 47 | channels_red: 48 | - red 49 | exr_channels_deep_minimal: 50 | - A 51 | - Z 52 | exr_deep_channels: 53 | - A 54 | - Z 55 | - Zback 56 | - deep.B 57 | - deep.G 58 | - deep.R 59 | exr_topology: 60 | - _alpha_exr 61 | - _uv_exr 62 | - _vec3_exr 63 | - _vec4_exr 64 | - _xyz_exr 65 | - _z_exr 66 | topology: 67 | - _alpha 68 | - _uv 69 | - _vec3 70 | - _vec4 71 | - _xyz 72 | - _z 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /documentation/docs/RemoveLayerSet.md: -------------------------------------------------------------------------------- 1 | # RemoveLayerSet 2 | 3 | !!! info "" 4 | 5 | RemoveLayerSet provides a simple way to isolate [LayerSets](core.md#layersets) from multichannel streams 6 | 7 | ![RemoveLayerSet](media/parameters/RemoveLayerSet.png) 8 | 9 | 10 | ## Knob reference 11 | 12 | 13 | | knob name | type | what it does | 14 | | --------- | ---- | ------------ 15 | | operation | enumeration | keep or remove | 16 | | layer_set | enumeration | decides which [LayerSet](core.md#layersets) to use | 17 | | keep_rgba | bool | if you want to keep rgba in all circumstances, enable this | 18 | 19 | ## Knob value detail 20 | 21 | ### operation 22 | 23 | | math mode | what it does | 24 | | --------- | ------------ | 25 | | copy | _replaces the target layer with the additive combination of the [LayerSet](core.md#layersets)_ 26 | | add | _add the additive combination of the [LayerSet](core.md#layersets) to the target layer_ 27 | | remove | _subtract the additive combination of the [LayerSet](core.md#layersets) from the target layer_ 28 | -------------------------------------------------------------------------------- /documentation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(PYTHON "python") 2 | find_program(MKDOCS_ABSPATH NAMES mkdocs) 3 | include(GNUInstallDirs) 4 | 5 | get_filename_component(DOCUMENTATION_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/documentation" ABSOLUTE) 6 | 7 | add_custom_target(documentation ALL 8 | COMMAND ${MKDOCS_ABSPATH} build -c -s -v -q --site-dir ${DOCUMENTATION_BUILD_DIR} 9 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 10 | COMMENT "Generating mkdocs documentation in ${CMAKE_CURRENT_SOURCE_DIR}" 11 | ) 12 | 13 | add_custom_target(documentation_package 14 | DEPENDS documentation 15 | COMMENT "creating documentation package for ${PROJECT_PACKAGE_NAME}" 16 | COMMAND ${CMAKE_COMMAND} -E tar "cfvz" "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}-documentation.tar.gz" "${CMAKE_CURRENT_BINARY_DIR}/documentation" 17 | COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}-documentation.tar.gz" "../" 18 | ) 19 | 20 | install( 21 | DIRECTORY ${DOCUMENTATION_BUILD_DIR} 22 | DESTINATION ${CMAKE_INSTALL_PREFIX} 23 | ) 24 | -------------------------------------------------------------------------------- /documentation/docs/GradeBeautyLayerSet.md: -------------------------------------------------------------------------------- 1 | # GradeBeautyLayerSet 2 | 3 | !!! info "" 4 | 5 | GradeBeautyLayerSet provides a simple way to specifically grade multiple cg layers using a [LayerSet](core 6 | .md#layersets) 7 | 8 | 9 | 10 | Image processing math is exactly like the Nuke Grade node except that, you can grade multiple layers at the 11 | same time 12 | 13 | ![GradeBeautyLayerSet](media/parameters/GradeBeautyLayerSet.png) 14 | 15 | ## Knob reference 16 | 17 | | knob name | type | what it does | 18 | | --------- | ---- | ------------ 19 | | layer_set | enumeration | decides which [LayerSet](core.md#layersets) to use | 20 | | output_mode | enumeration | specifies the type of output, add or copy 21 | | reset values | button | resets all color knobs to their defaults | 22 | 23 | 24 | ## Output Modes 25 | 26 | | mode name | what it does | 27 | | --------- | ------------ | 28 | | copy | outputs only the modified layers to the target layer (added together)| 29 | | add | this first subtracts all layers from the target layer, then adds each of them back 30 | -------------------------------------------------------------------------------- /documentation/docs/FlattenLayerSet.md: -------------------------------------------------------------------------------- 1 | # FlattenLayerSet 2 | 3 | !!! info "" 4 | 5 | FlattenLayerSet provides a simple way to merge additive [LayerSets](core.md#layersets) 6 | from multichannel cg render passes to any single layer 7 | 8 | ![FlattenLayerSet](media/parameters/FlattenLayerSet.png) 9 | 10 | ## Knob reference 11 | 12 | | knob name | type | what it does | 13 | | --------- | ---- | ------------ | 14 | | operation | enumeration | decides which method combination to use | 15 | | layer_set | enumeration | decides which [LayerSet](core.md#layersets) to use | 16 | | target_layer | enumeration | selects which layer to pre-subtract layers from (if enabled) and add the modified layers to | 17 | 18 | ## Knob reference in detail 19 | 20 | ### operation 21 | 22 | | math mode | what it does | 23 | | --------- | ------------ | 24 | | copy | _replaces the target layer with the additive combination of the [LayerSet](core.md#layersets)_ 25 | | add | _add the additive combination of the [LayerSet](core.md#layersets) to the target layer_ 26 | | remove | _subtract the additive combination of the [LayerSet](core.md#layersets) from the target layer_ 27 | -------------------------------------------------------------------------------- /configs/layers/layers.yaml: -------------------------------------------------------------------------------- 1 | # encompasses the baseline 2 | 3 | _primary_layer: !!set &primary_layer 4 | ? rgba 5 | 6 | _primary: !!set &primary 7 | ? rgb 8 | ? beauty 9 | ? primary 10 | <<: *primary_layer 11 | 12 | crypto_asset: !!set &crypto_asset 13 | ? crypto_asset 14 | ? crypto_asset00 15 | ? crypto_asset01 16 | ? crypto_asset02 17 | 18 | crypto_material: !!set &crypto_material 19 | ? crypto_material 20 | ? crypto_material00 21 | ? crypto_material01 22 | ? crypto_material02 23 | 24 | crypto_object: !!set &crypto_object 25 | ? crypto_object 26 | ? crypto_object00 27 | ? crypto_object01 28 | ? crypto_object02 29 | 30 | crypto: !!set &crypto 31 | <<: *crypto_asset 32 | <<: *crypto_object 33 | <<: *crypto_material 34 | 35 | p: !!set &position 36 | ? P 37 | ? Pref 38 | 39 | pref: !!set &pref 40 | ? Pref 41 | 42 | uv: !!set &uv 43 | ? uv 44 | ? motion 45 | 46 | deep: !!set &deep 47 | ? deep 48 | 49 | non_color: !!set 50 | <<: *crypto 51 | <<: *deep 52 | <<: *uv 53 | <<: *position 54 | <<: *pref 55 | 56 | # channel mappings 57 | 58 | _uv: !!set 59 | <<: *uv 60 | 61 | _xyz: !!set 62 | <<: *position 63 | <<: *pref 64 | 65 | _vec4: !!set 66 | <<: *primary_layer 67 | <<: *crypto 68 | -------------------------------------------------------------------------------- /documentation/docs/about.md: -------------------------------------------------------------------------------- 1 | # LayerAlchemy team 2 | 3 | | name | role | email | LinkedIn | 4 | | ---- | ---- | ----- | -------- | 5 | | Sébastien Jacob | author, designer | [email](mailto:sebjacobvfx@gmail.com) | [LinkedIn](https://www.linkedin.com/in/s%C3%A9bastien-jacob-3b05112/) 6 | 7 | # special thanks 👍 8 | 9 | - Charles Fleche 10 | - Christian Morin 11 | - Mathieu Dupuis 12 | - Jean-Christophe Morin 13 | - Gregory Starck 14 | 15 | # Contributing 16 | 17 | 18 | Absolutely! 19 | 20 | [I](mailto:sebjacobvfx@gmail.com), developed this while commuting to work to : 21 | 22 | - practice c++ coding 23 | - get familiar with the NDK and how Nuke fundamentally operates 24 | 25 | Would love to see this project grow and be expanded with the help of others. 26 | 27 | 28 | !!! info "[click here for the source code, feature requests or bugs](https://github.com/sebjacob/LayerAlchemy)" 29 | 30 | * artists : likes and dislikes, bugs feature requests ? 31 | * c++ coders : Any constructive criticism or PR welcome. 32 | * python gurus : making the c++ core accessible in python would be of interest. 33 | * anyone with a knowledge of cmake cross compiling : would be nice to consolidate the multiple platforms 34 | for easier delivery. 35 | -------------------------------------------------------------------------------- /python/nuke/layer_alchemy/constants.py: -------------------------------------------------------------------------------- 1 | """shared constants module for LayerAlchemy""" 2 | 3 | import os 4 | 5 | LAYER_ALCHEMY_URL = 'https://github.com/sebjacob/LayerAlchemy' 6 | 7 | LAYER_ALCHEMY_PLUGIN_NAMES = [ 8 | 'GradeBeauty', 9 | 'GradeBeautyLayerSet', 10 | 'GradeBeautyLayer', 11 | 'GradeLayerSet', 12 | 'MultiplyLayerSet', 13 | 'RemoveLayerSet', 14 | 'FlattenLayerSet' 15 | ] 16 | 17 | LAYER_ALCHEMY_CONFIGS_DICT = { 18 | 'LAYER_ALCHEMY_LAYER_CONFIG': 'layers.yaml', 19 | 'LAYER_ALCHEMY_CHANNEL_CONFIG': 'channels.yaml' 20 | } 21 | 22 | _thisDir = os.path.dirname(os.path.realpath(__file__)) 23 | _layerAlchemyNukeDir = os.path.abspath(os.path.join(_thisDir, '..')) 24 | 25 | LAYER_ALCHEMY_PLUGIN_ROOT_DIR = os.path.abspath( 26 | os.path.join(_layerAlchemyNukeDir, 'plugins') 27 | ) 28 | LAYER_ALCHEMY_ICON_DIR = os.path.abspath( 29 | os.path.join(_layerAlchemyNukeDir, 'icons') 30 | ) 31 | LAYER_ALCHEMY_CONFIGS_DIR = os.path.abspath( 32 | os.path.join(_layerAlchemyNukeDir, '..', 'configs') 33 | ) 34 | LAYER_ALCHEMY_CONFIGTESTER_BIN = os.path.abspath( 35 | os.path.join(_layerAlchemyNukeDir, '..', 'bin', 'ConfigTester') 36 | ) 37 | LAYER_ALCHEMY_DOCUMENTATION_DIR = os.path.abspath( 38 | os.path.join(_layerAlchemyNukeDir, '..', 'documentation') 39 | ) 40 | -------------------------------------------------------------------------------- /documentation/docs/dev_guide.md: -------------------------------------------------------------------------------- 1 | # Building, installation and testing 2 | 3 | !!! note "once compiled or downloaded, add this to your init.py" 4 | There is a nuke folder in the install, this folder is what you need to tell Nuke to use 5 | 6 | _nuke.pluginAddPath('/path/to/LayerAlchemy/nuke')_ 7 | 8 | # Build instruction examples 9 | 10 | First you must run cmake to configure the compilation 11 | 12 | !!! example "_using environment variables :_" 13 | export NUKE_ROOT=/Applications/Nuke11.3v4 14 | export LAYER_ALCHEMY_DIR=/path/to/git/cloned/LayerAlchemy 15 | export LAYER_ALCHEMY_BUILD_DIR=/a/new/folder/to/build/in 16 | export LAYER_ALCHEMY_INSTALL_DIR=/a/new/folder/to/install/to 17 | cd $LAYER_ALCHEMY_BUILD_DIR 18 | cmake $LAYER_ALCHEMY_DIR -DNUKE_ROOT=$NUKE_ROOT -DCMAKE_INSTALL_PREFIX=$LAYER_ALCHEMY_INSTALL_DIR 19 | !!! example "_no environment variables :_" 20 | mkdir /a/new/folder/to/build/in 21 | cd /a/new/folder/to/build/in 22 | cmake /path/to/git/cloned/LayerAlchemyDir -DNUKE_ROOT=/Applications/Nuke11.3v4 -DCMAKE_INSTALL_PREFIX=/Path/to/install/to 23 | 24 | Then you can actually start compiling, or create a package. 25 | 26 | !!! example "building" 27 | make # compile the code 28 | make documentation # build the documentation 29 | make install # copies the compiled files to the install directory 30 | make package # creates a compressed file containing the install directory for distribution 31 | 32 | # Config Tools 33 | 34 | The following commandline tools can help fine tune config files 35 | [LayerTester](tools.md#LayerTester) 36 | [ConfigTester](tools.md#ConfigTester) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | LayerAlchemy and all code, documentation, and other materials contained 2 | therein are: 3 | 4 | Copyright 2018-2019 Sebastien Jacob. All Rights Reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of the software's owners nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (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 | 30 | (This is the Modified BSD License) 31 | 32 | See [LICENSE-THIRD-PARTY.md](LICENSE-THIRD-PARTY.md) for license information 33 | about portions that have been imported from other projects. 34 | -------------------------------------------------------------------------------- /python/nuke/layer_alchemy/callbacks.py: -------------------------------------------------------------------------------- 1 | """LayerAlchemy callback module""" 2 | 3 | 4 | import nuke 5 | 6 | import constants 7 | 8 | 9 | def setupCallbacks(): 10 | """ 11 | Utility function to add all callbacks for plugins in this suite. 12 | """ 13 | for pluginName in constants.LAYER_ALCHEMY_PLUGIN_NAMES: 14 | nuke.addAutolabel(_autolabel, nodeClass=pluginName) 15 | pass 16 | 17 | 18 | def _autolabel(): 19 | """ 20 | Common autolabel function for plugins in this suite 21 | :return: the formatted text to use as a label 22 | :rtype: str 23 | """ 24 | 25 | node = nuke.thisNode() 26 | nodeName = node.name() 27 | text = [] 28 | for knobName in ('layer_set', 'channels', 'maskChannelInput', 'unpremult'): 29 | knob = node.knob(knobName) 30 | if knob: 31 | index = int(knob.getValue()) 32 | value = knob.enumName(index) if isinstance(knob, nuke.Enumeration_Knob) else knob.value() 33 | if value and value != 'none': 34 | text.append(value) 35 | 36 | node.knob('indicators').setValue(_getIndicatorValue(node)) 37 | if text: 38 | return '{name}\n({layers})'.format(name=nodeName, layers=' / '.join(text)) 39 | else: 40 | return nodeName 41 | 42 | 43 | def _getIndicatorValue(node): 44 | """ 45 | simple function to calculate the indicator value for a node 46 | :param node: the Nuke node object 47 | :type node: :class:`nuke.Node` 48 | :return: the integer indicator value 49 | :rtype: int 50 | """ 51 | indicators = 0 52 | knobs = node.allKnobs() 53 | mixKnob = node.knob('mix') 54 | maskKnob = node.knob('maskChannelInput') 55 | if mixKnob and mixKnob.value() != 1: 56 | indicators += 16 57 | if maskKnob and maskKnob.getValue() != 0: 58 | indicators += 4 59 | if node.clones(): 60 | indicators += 8 61 | if any(knob.isAnimated() for knob in knobs): 62 | indicators += 1 63 | if any(knob.hasExpression() for knob in knobs): 64 | indicators += 2 65 | return indicators 66 | -------------------------------------------------------------------------------- /LICENSE-THIRD-PARTY.md: -------------------------------------------------------------------------------- 1 | See [LICENSE.md](LICENSE.md) for the main open source license of original 2 | code written for the LayerAlchemy project. 3 | 4 | The remainder of this file reproduces the open source licensing details 5 | of other projects that have been imported, incorporated into, or derived 6 | into parts of LayerAlchemy. 7 | 8 | ------------------------------------------------------------------------- 9 | 10 | yaml-cpp 11 | 12 | https://github.com/jbeder/yaml-cpp 13 | 14 | yaml-cpp license 15 | 16 | Copyright (c) 2008-2015 Jesse Beder. 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | 36 | ------------------------------------------------------------------------- 37 | 38 | argparse 39 | 40 | https://github.com/jamolnng/argparse 41 | 42 | argparse license 43 | 44 | argparse.h is free software: you can redistribute it and/or modify 45 | it under the terms of the GNU General Public License v3 as published by 46 | the Free Software Foundation. 47 | 48 | argparse.h is distributed in the hope that it will be useful, 49 | but WITHOUT ANY WARRANTY; without even the implied warranty of 50 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 51 | GNU General Public License for more details. 52 | 53 | You should have received a copy of the GNU General Public License 54 | along with argparse.h. If not, see . 55 | 56 | Author: Jesse Laning 57 | 58 | If we have left anything out, it is unintentional. Please let us know. 59 | -------------------------------------------------------------------------------- /configs/layers/facility.yaml: -------------------------------------------------------------------------------- 1 | # custom layer conventions, like custom facility tokens or workflows 2 | 3 | mp: !!set &mp_layer 4 | ? mp 5 | 6 | mptd: !!set &mptd_layer 7 | ? mptd 8 | 9 | _rotopaint_degrain_layer: !!set &rotopaint_degrain_layer 10 | ? rotopaint_degrain 11 | 12 | _rotopaint_grain_layer: !!set &rotopaint_grain_layer 13 | ? rotopaint_grain 14 | 15 | rotopaint: !!set &rotopaint 16 | <<: *rotopaint_degrain_layer 17 | <<: *rotopaint_grain_layer 18 | ? rotopaint 19 | ? platepremult 20 | ? rotopaint_mask 21 | 22 | roto: !!set &roto_layer 23 | ? roto 24 | 25 | _grain_layer: !!set &grain_layer 26 | ? grain 27 | 28 | matte: !!set &matte 29 | ? m 30 | ? matte 31 | ? rotopaint_mask 32 | <<: *roto_layer 33 | 34 | puz: !!set &puz 35 | ? puz 36 | 37 | non_color: !!set 38 | <<: *matte 39 | <<: *puz 40 | 41 | # 'step layers' are layers that can be appended with pipeline steps suffixes 42 | _step: !!set &_step 43 | ? P 44 | ? uv 45 | 46 | # 'asset layers' are layers that can potentially have a user defined name as a suffix 47 | _asset: !!set &_asset 48 | ? Pref 49 | ? puz 50 | ? mptd 51 | ? mp 52 | ? rotopaint 53 | ? roto 54 | ? motion 55 | 56 | # 'sanitizable layers' are layers that should be changed, for techical reasons. 57 | # example : !!set 'uv' clashes with nuke's internal naming, so this is why it is also defined in _step 58 | _sanitizable: !!set 59 | ? P 60 | ? uv 61 | 62 | depth: !!set &depth 63 | # depth layers invented by facility 64 | ? Z2 65 | 66 | deep: !!set 67 | # deep layers invented by facility 68 | ? deep_scanline 69 | 70 | # this category is for layers that are known, or should be considered _ 71 | _prefix: !!set 72 | <<: *_step 73 | <<: *_asset 74 | <<: *roto_layer 75 | <<: *matte 76 | 77 | 78 | # layer categories that group layer categories 79 | _global: !!set 80 | ? diffuse 81 | ? direct 82 | ? indirect 83 | ? specular 84 | ? sss 85 | ? transmission 86 | ? volume 87 | ? mp 88 | ? mptd 89 | 90 | _facility_exr_package_names: !!set 91 | ? multipart 92 | ? deep_scanline 93 | 94 | # layer category for declaring (red,green,blue,alpha) type topology for these layers 95 | 96 | 97 | _alpha: !!set &alpha 98 | <<: *roto_layer 99 | <<: *matte 100 | <<: *depth 101 | 102 | _vec3: !!set 103 | <<: *puz 104 | 105 | _vec4: !!set 106 | <<: *mp_layer 107 | <<: *mptd_layer 108 | ? platepremult 109 | -------------------------------------------------------------------------------- /include/nuke/LayerSet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "LayerSetCore.h" 9 | #include "version.h" 10 | 11 | namespace LayerAlchemy { 12 | 13 | //layerCollection initialized here once. 14 | static LayerCollection layerCollection; 15 | //using DD::Image::ChannelSet; 16 | typedef map ChannelSetMapType; 17 | 18 | namespace LayerSet { 19 | typedef map ChannelSetMapType; 20 | //Gets a vector of unique layer names for a ChannelSet 21 | StrVecType getLayerNames(const DD::Image::ChannelSet&); 22 | StrVecType getCategories(const ChannelSetMapType&); 23 | DD::Image::ChannelSet getChannelSet(const ChannelSetMapType&); 24 | 25 | ChannelSetMapType categorizeChannelSet(const LayerCollection&, const DD::Image::ChannelSet&); 26 | ChannelSetMapType categorizeChannelSet(const LayerCollection&, const DD::Image::ChannelSet&, const CategorizeFilter&); 27 | ChannelSetMapType _layerMaptoChannelMap(const LayerMap&, const DD::Image::ChannelSet& inChannels); 28 | } // End namespace LayerSet 29 | 30 | namespace Utilities { 31 | void hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::ChannelSet channels, DD::Image::Row& toRow); 32 | float* hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::Channel channel, DD::Image::Row& toRow); 33 | // centralized pixel engine code for Grade type plugins 34 | void gradeChannelPixelEngine(const DD::Image::Row& in, int y, int x, int r, DD::Image::ChannelSet& channels, DD::Image::Row& aRow, float* A, float* B, float* G, bool reverse, bool clampBlack, bool clampWhite); 35 | // use to validate if a target layer the user selects is within the required color ranges 36 | void validateTargetLayerColorIndex(DD::Image::Op* t_op, const DD::Image::ChannelSet& targetLayer, unsigned minIndex, unsigned maxIndex); 37 | // test float value for pow functions, NDK states that linux behaves badly for very large or very small exponent values. 38 | float validateGammaValue(const float& gammaValue); 39 | } // End namespace Utilities 40 | 41 | namespace Knobs { 42 | DD::Image::Knob* createDocumentationButton(DD::Image::Knob_Callback&); 43 | DD::Image::Knob* createColorKnobResetButton(DD::Image::Knob_Callback&); 44 | DD::Image::Knob* createVersionTextKnob(DD::Image::Knob_Callback&); 45 | } // End namespace Knobs 46 | } // End namespace LayerAlchemy 47 | -------------------------------------------------------------------------------- /python/nuke/menu.py: -------------------------------------------------------------------------------- 1 | """LayerAlchemy Nuke Menu""" 2 | 3 | import nuke 4 | 5 | import layer_alchemy.utilities 6 | 7 | if layer_alchemy.utilities.nukeVersionCompatible(): 8 | toolbar = nuke.menu("Nodes") 9 | menu = toolbar.addMenu("LayerAlchemy", icon="layer_alchemy.png", index=-1) 10 | 11 | menu.addCommand(name='GradeBeauty', 12 | command="nuke.createNode('GradeBeauty')", 13 | icon="GradeBeauty.png" 14 | ) 15 | menu.addCommand(name='GradeBeautyLayerSet', 16 | command="nuke.createNode('GradeBeautyLayerSet')", 17 | icon="GradeBeautyLayerSet.png" 18 | ) 19 | menu.addCommand(name='GradeBeautyLayer', 20 | command="nuke.createNode('GradeBeautyLayer')", 21 | icon="GradeBeautyLayer.png" 22 | ) 23 | menu.addSeparator() 24 | menu.addCommand(name='GradeLayerSet', 25 | command="nuke.createNode('GradeLayerSet')", 26 | icon="GradeLayerSet.png" 27 | ) 28 | menu.addCommand(name='MultiplyLayerSet', 29 | command="nuke.createNode('MultiplyLayerSet')", 30 | icon="MultiplyLayerSet.png" 31 | ) 32 | menu.addSeparator() 33 | menu.addCommand(name='FlattenLayerSet', 34 | command="nuke.createNode('FlattenLayerSet')", 35 | icon="FlattenLayerSet.png" 36 | ) 37 | menu.addCommand(name='RemoveLayerSet', 38 | command="nuke.createNode('RemoveLayerSet')", 39 | icon="RemoveLayerSet.png" 40 | ) 41 | 42 | menu.addSeparator() 43 | menu.addCommand(name="documentation", 44 | command=( 45 | "import layer_alchemy.documentation\n" 46 | "webview = layer_alchemy.documentation.displayDocumentation(node=None)\n" 47 | "if webview:\n" 48 | " webview.show()"), 49 | icon="documentation.png" 50 | ) 51 | 52 | else: 53 | currentNukeVersion = layer_alchemy.utilities.getNukeVersionString() 54 | message = "LayerAlchemy : not loading because no installed plugins found for Nuke {version}".format( 55 | version=currentNukeVersion) 56 | consoleMessage = "\xE2\x9D\x97 \x1B[31m{message}\033[0m".format(message=message) 57 | nuke.tprint(consoleMessage) 58 | if nuke.GUI: 59 | nuke.tcl('alert "{message}"'.format(message=message)) 60 | -------------------------------------------------------------------------------- /src/nuke/MultiplyLayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "LayerSet.h" 6 | #include "LayerSetKnob.h" 7 | 8 | namespace MultiplyLayerSet { 9 | 10 | using namespace DD::Image; 11 | 12 | static const char* const HELP = "Multiplies multiple channels using LayerSets"; 13 | 14 | class MultiplyLayerSet : public PixelIop { 15 | 16 | private: 17 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 18 | int index; 19 | float m_multValue[4] = {1, 1, 1, 1}; 20 | ChannelSet m_targetLayerSet; 21 | 22 | public: 23 | void knobs(Knob_Callback); 24 | void _validate(bool); 25 | bool pass_transform() const {return true;} 26 | void in_channels(int, ChannelSet&) const; 27 | void pixel_engine(const Row &in, int y, int x, int r, ChannelMask, Row & out); 28 | const char* Class() const {return description.name;} 29 | const char* node_help() const {return HELP;} 30 | static const Iop::Description description; 31 | // channel set that contains all channels that are modified by the node 32 | ChannelSet activeChannelSet() const {return ChannelSet(m_lsKnobData.m_selectedChannels);} 33 | 34 | MultiplyLayerSet(Node* node) : PixelIop(node) {} 35 | ~MultiplyLayerSet(); 36 | }; 37 | 38 | // register 39 | static Op* build(Node* node) { 40 | return (new NukeWrapper(new MultiplyLayerSet(node)))->noChannels()->noUnpremult()->mixLuminance(); 41 | } 42 | 43 | MultiplyLayerSet::~MultiplyLayerSet() {} 44 | 45 | const Iop::Description MultiplyLayerSet::description( 46 | "MultiplyLayerSet", 47 | "LayerAlchemy/MultiplyLayerSet", 48 | build 49 | ); 50 | 51 | // update layer set knob and gather selectedChannels 52 | void MultiplyLayerSet::_validate(bool for_real) 53 | { 54 | copy_info(); // this copies the input info to the output 55 | ChannelSet inChannels = info_.channels(); 56 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels)) { 57 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels); 58 | } 59 | set_out_channels(activeChannelSet()); 60 | } 61 | 62 | void MultiplyLayerSet::in_channels(int input, ChannelSet& mask) const {} 63 | 64 | void MultiplyLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) 65 | { 66 | foreach(z, channels) { 67 | const float c = m_multValue[colourIndex(z)]; 68 | const float* inptr = in[z] + x; 69 | const float* END = inptr + (r - x); 70 | float* outptr = out.writable(z) + x; 71 | while (inptr < END) 72 | *outptr++ = *inptr++ * c; 73 | } 74 | } 75 | 76 | void MultiplyLayerSet::knobs(Knob_Callback f) 77 | { 78 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 79 | LayerAlchemy::Knobs::createDocumentationButton(f); 80 | LayerAlchemy::Knobs::createVersionTextKnob(f); 81 | Divider(f, 0); // separates layer set knobs from the rest 82 | AColor_knob(f, m_multValue, IRange(0, 4), "value"); 83 | } 84 | } // End namespace MultiplyLayerSet -------------------------------------------------------------------------------- /src/ConfigTester.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple executable to test if a yaml file can be loaded and a LayerMap object can be constructed 3 | * usage example: ConfigTester /path/to/config.yaml 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "argparse.h" 10 | 11 | #include "LayerSetCore.h" 12 | #include "version.h" 13 | 14 | static const string greenText = "\x1B[92m"; 15 | static const string redText = "\x1B[31m"; 16 | static const string endColor = "\033[0m"; 17 | static const string emojiOk = "\xE2\x9C\x85 "; 18 | static const string emojiMedical = "\xF0\x9F\x98\xB7 "; 19 | static const string emojiError = "\xE2\x9D\x97 "; 20 | 21 | static const std::string DESCRIPTION = "Simple executable to validate yaml files"; 22 | static const string LAYER_ALCHEMY_PROJECT_URL = "https://github.com/sebjacob/LayerAlchemy"; 23 | 24 | static const string HEADER = 25 | "\nConfigTester " + emojiMedical + "\n\n" + DESCRIPTION + "\n\n" 26 | "LayerAlchemy " + LAYER_ALCHEMY_VERSION_STRING + "\n" + 27 | LAYER_ALCHEMY_PROJECT_URL + "\n\n" 28 | "Example usage: \n\nConfigTester --config /path/to/config.yaml\n" 29 | "ConfigTester --config /path/to/config1.yaml /path/to/config2.yaml"; 30 | 31 | 32 | void logException(const char* filePath, const std::exception& e) 33 | { 34 | std::cerr << emojiError << redText << "[ERROR] LayerAlchemy : " << filePath << e.what() << std::endl << endColor; 35 | } 36 | 37 | int main(int argc, const char* argv[]) 38 | { 39 | ArgumentParser parser(DESCRIPTION); 40 | parser.add_argument("--config", "List of layer names to test", true); 41 | parser.add_argument("--quiet", "disable terminal output, return code only", false); 42 | 43 | try 44 | { 45 | parser.parse(argc, argv); 46 | } 47 | catch (const ArgumentParser::ArgumentNotFound &ex) 48 | { 49 | std::cout << HEADER << std::endl; 50 | parser.print_help(); 51 | 52 | std::cout << ex.what() << std::endl; 53 | return 0; 54 | } 55 | if (parser.is_help()) 56 | return 0; 57 | 58 | auto configs = parser.getv("config"); 59 | bool quiet = parser.get("quiet"); 60 | 61 | for(auto it = configs.begin(); it != configs.end(); ++it) 62 | { 63 | auto configFilePath = it->c_str(); 64 | std::ifstream inputFile(configFilePath); 65 | 66 | if (!inputFile || opendir(configFilePath)) 67 | { 68 | if (!quiet) 69 | { 70 | logException(configFilePath, std::invalid_argument(" is not a file")); 71 | } 72 | return 1; 73 | } 74 | try 75 | { 76 | LayerMap testLayerMap = LayerMap(loadConfigToMap(configFilePath)); 77 | if (!quiet) 78 | { 79 | std::cout << greenText << emojiOk << "LayerAlchemy : valid configuration file " << configFilePath 80 | << std::endl << endColor; 81 | } 82 | } 83 | catch (const std::exception& e) 84 | { 85 | if (!quiet) 86 | { 87 | logException(configFilePath, e); 88 | } 89 | return 1; 90 | } 91 | } 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /python/layer_alchemy/_config.py: -------------------------------------------------------------------------------- 1 | """Python code for collapsing the configuration files into a single yaml file""" 2 | 3 | import os 4 | import glob 5 | import yaml 6 | 7 | 8 | class ConfigError(Exception): 9 | """Raised when errors occurs in the Configurations.""" 10 | 11 | 12 | def collapse(folder, outputFile): 13 | """ 14 | Wrapper function that takes a folder, scans for yaml files, accumulates all keys ans values 15 | and saves the product as outputFile 16 | Sorts layer configurations, but not channels, as channels have a set order. 17 | 18 | :param folder: the folder to process 19 | :param outputFile: the absolute path to write to 20 | :return: loads the outputFile yaml 21 | :rtype dict 22 | """ 23 | configFiles = glob.glob(os.path.join(folder, '*.y*ml')) 24 | configDictList = [_load(config) for config in configFiles if config] 25 | collapsedConfigs = _add(configDictList, sort=(True if folder.endswith('layers') else False)) 26 | _save(collapsedConfigs, outputFile) 27 | writtenConfig = _load(outputFile) # read back, to be sure. 28 | return writtenConfig 29 | 30 | 31 | def _load(yamlFile): 32 | """ 33 | load a yaml file 34 | :param yamlFile: absolute path to a yaml file 35 | :return: the loaded yaml file 36 | :raises ConfigError: config is not a mapping of sets or lists 37 | """ 38 | config = None 39 | with open(yamlFile, 'r') as stream: 40 | config = yaml.load(stream) 41 | if not config: 42 | return config 43 | if not all(isinstance(key, basestring) and isinstance(value, (set, list)) 44 | for key, value in config.items()): 45 | raise ConfigError('{} config is not a mapping of sets or lists'.format(yamlFile)) 46 | return config 47 | 48 | 49 | def _save(collapsedConfigs, yamlFile): 50 | """ 51 | save a dictionary as a yaml file 52 | :param collapsedConfigs: the final product to write out 53 | :param yamlFile: the absolute file path to write to 54 | """ 55 | kwargs = {'width': 1, 'default_flow_style': False, 'indent': 4} 56 | with open(yamlFile, 'w+') as stream: 57 | yaml.dump(collapsedConfigs, stream, **kwargs) 58 | 59 | 60 | def _add(configDictList, sort=False): 61 | """ 62 | Accumulates a list of dictionaries into one output dictionary 63 | :param list configDictList: all the config dictionaries to process 64 | :param bool sort: as an option, soft the values 65 | :return: a dict that contains all keys and values 66 | :rtype dict 67 | """ 68 | func = sorted if sort else list 69 | outDict = {} 70 | for config in configDictList: 71 | if config is not None: # placeholder files check 72 | for key, layerSet in config.items(): 73 | if key in outDict: 74 | layers = outDict[key] 75 | if isinstance(layerSet, set): 76 | outDict[key] = layers.union(layerSet) 77 | elif isinstance(layerSet, list): 78 | for item in layerSet: 79 | outDict[key].append(item) 80 | else: 81 | outDict[key] = layerSet 82 | outDict = {key: func(value) for key, value in outDict.items()} 83 | return outDict 84 | -------------------------------------------------------------------------------- /python/nuke/layer_alchemy/documentation.py: -------------------------------------------------------------------------------- 1 | """shared documentation/help module for LayerAlchemy""" 2 | 3 | import os 4 | 5 | import nuke 6 | 7 | import constants 8 | import utilities 9 | 10 | if nuke.GUI: 11 | if nuke.NUKE_VERSION_MAJOR > 10: 12 | from PySide2.QtWebEngineWidgets import QWebEngineView as qWebview 13 | from PySide2.QtCore import QUrl 14 | from PySide2.QtWidgets import QApplication 15 | else: 16 | from PySide.QtWebKit import QWebView as qWebview 17 | from PySide.QtCore import QUrl 18 | from PySide.QtGui import QApplication 19 | 20 | 21 | def documentationPath(node=None): 22 | """ 23 | find an absolute path to the project documentation, or the html file for the chosen node 24 | :param node: the Nuke node object 25 | :type node: :class:`nuke.Node` 26 | :return: absolute html file path 27 | :rtype: str 28 | :raises ValueError if absolutely no html file can be found 29 | """ 30 | documentationIndexPath = utilities.getDocumentationIndexPath() 31 | htmlFile = documentationIndexPath 32 | if not documentationIndexPath or not os.path.exists(documentationIndexPath): 33 | message = 'Local documentation is unavailable, please visit :\n\n{website}'.format( 34 | website=constants.LAYER_ALCHEMY_URL) 35 | if nuke.GUI: 36 | nuke.message(message) 37 | raise ValueError(message) 38 | if node: 39 | pluginDocFileName = '{0}.html'.format(node.Class()) 40 | htmlFile = os.path.join(os.path.dirname(documentationIndexPath), pluginDocFileName) 41 | if not os.path.isfile(htmlFile): 42 | htmlFile = documentationIndexPath 43 | return htmlFile 44 | 45 | 46 | def documentationWebViewWidget(documentationPath): 47 | """ 48 | Creates a PySide web view Widget 49 | :param str documentationPath: the absolute path to the html file to display 50 | :return: a PySide web view widget set up with the local documentation 51 | :rtype: PySide2.QtWebEngineWidgets.QWebEngineView 52 | """ 53 | webView = qWebview() 54 | webView.setWindowTitle('LayerAlchemy Documentation') 55 | qUrl = QUrl.fromLocalFile(documentationPath) 56 | webView.load(qUrl) 57 | screenGeo = QApplication.desktop().geometry() 58 | webView.setMinimumHeight(screenGeo.height() / 2) 59 | center = screenGeo.center() 60 | webView.move(center - webView.rect().center()) 61 | return webView 62 | 63 | 64 | def displayDocumentation(node=None): 65 | """ 66 | Wrapper function to display documentation in Nuke as a PySide2.QtWebEngineWidgets.QWebEngineView 67 | Due to a bug in Nuke 11, the documentation will be displayed using the web browser 68 | https://support.foundry.com/hc/en-us/articles/360000148684-Q100379-Importing-PySide2-QtWebEngine-into-Nuke-11 69 | :param node: the Nuke node object 70 | :type node: :class:`nuke.Node` 71 | :return: a PySide web view widget set up with the local documentation 72 | :rtype: PySide2.QtWebEngineWidgets.QWebEngineView 73 | """ 74 | htmlFile = documentationPath(node) 75 | if nuke.env['NukeVersionMajor'] == 11: 76 | import nukescripts 77 | nukescripts.start(htmlFile) 78 | else: 79 | return documentationWebViewWidget(htmlFile) 80 | -------------------------------------------------------------------------------- /src/nuke/RemoveLayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "LayerSet.h" 6 | #include "LayerSetKnob.h" 7 | 8 | namespace RemoveLayerSet { 9 | 10 | using namespace DD::Image; 11 | 12 | static const char* const HELP = "Remove of keep channels using LayerSets"; 13 | 14 | static const char* const enumOperation[] = { 15 | "remove", "keep", 0 16 | }; 17 | 18 | class RemoveLayerSet : public Iop { 19 | 20 | private: 21 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 22 | int m_operation; // 0 = remove, 1 = keep 23 | bool m_keepRGBA; 24 | 25 | protected: 26 | void _validate(bool for_real); 27 | void _request(int x, int y, int r, int t, ChannelMask c1, int count); 28 | 29 | public: 30 | virtual void knobs(Knob_Callback); 31 | static const Description description; 32 | void engine(int y, int x, int r, ChannelMask c1, Row& out_row); 33 | const char* Class() const {return description.name;} 34 | const char* node_help() const {return HELP;} 35 | RemoveLayerSet(Node* node); 36 | ~RemoveLayerSet(); 37 | // channel set that contains all channels that are modified by the node 38 | ChannelSet activeChannelSet() const {return ChannelSet(m_lsKnobData.m_selectedChannels);} 39 | 40 | }; 41 | RemoveLayerSet::RemoveLayerSet(Node* node) : Iop(node) 42 | { 43 | m_operation = 1; 44 | m_keepRGBA = true; 45 | } 46 | 47 | static Iop* build(Node* node) 48 | { 49 | return new RemoveLayerSet(node); 50 | } 51 | 52 | RemoveLayerSet::~RemoveLayerSet() {} 53 | 54 | const Iop::Description RemoveLayerSet::description( 55 | "RemoveLayerSet", 56 | "LayerAlchemy/RemoveLayerSet", 57 | build 58 | ); 59 | 60 | void RemoveLayerSet::_validate(bool for_real) 61 | { 62 | copy_info(); // this copies the input info to the output 63 | ChannelSet inChannels = info_.channels(); 64 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels)) { 65 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels); 66 | } 67 | 68 | ChannelSet activeChannels = activeChannelSet(); 69 | 70 | if (m_operation) { // keep 71 | info_.channels() &= activeChannels; 72 | set_out_channels(info_.channels()); 73 | } else { //remove 74 | info_.turn_off(activeChannels); 75 | set_out_channels(activeChannels); 76 | } 77 | if (m_keepRGBA) { 78 | info_.turn_on(Mask_RGBA); 79 | } 80 | } 81 | 82 | void RemoveLayerSet::_request(int x, int y, int r, int t, ChannelMask c1, int count) 83 | { 84 | input0().request(x, y, r, t, c1, count); 85 | } 86 | 87 | void RemoveLayerSet::engine(int y, int x, int r, ChannelMask c1, Row& out_row) 88 | { 89 | out_row.get(input0(), y, x, r, c1); 90 | return; 91 | } 92 | 93 | void RemoveLayerSet::knobs(Knob_Callback f) 94 | { 95 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 96 | LayerAlchemy::Knobs::createDocumentationButton(f); 97 | LayerAlchemy::Knobs::createVersionTextKnob(f); 98 | Divider(f, 0); // separates layer set knobs from the rest 99 | 100 | Enumeration_knob(f, &m_operation, enumOperation, "operation"); 101 | Bool_knob(f, &m_keepRGBA, "keep_rgba", "keep rgba"); 102 | Tooltip(f, "if enabled, RGBA is always output"); 103 | } 104 | } // End namespace RemoveLayerSet -------------------------------------------------------------------------------- /include/nuke/LayerSetKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "LayerSet.h" 8 | 9 | namespace LayerAlchemy { 10 | namespace LayerSetKnob { 11 | 12 | static const char* LAYER_SET_KNOB_NAME = "layer_set"; 13 | 14 | /** 15 | * Stores the data needed to manage LayerSet enumeration knob 16 | */ 17 | class LayerSetKnobData { 18 | public: 19 | // stores the last used LayerSet name, useful to compare if an update is required, or can be skipped 20 | string categoryName {""}; 21 | // stores the list of categories in the enumeration knob 22 | const char* items[2] = {"", 0}; 23 | //stores the index of the the enumeration knob 24 | int selectedLayerSetIndex {0}; 25 | // stores the result ChannelSet filtered by the knob. 26 | DD::Image::ChannelSet m_selectedChannels; 27 | // stores the ChannelSet last used, useful to compare if an update is required, or can be skipped 28 | DD::Image::ChannelSet m_allChannels; 29 | LayerSetKnobData(); 30 | ~LayerSetKnobData(); 31 | }; 32 | 33 | /** 34 | * convenience functions directly related to creating and manipulating the enumeration knob 35 | */ 36 | 37 | // centralized function for creating LayerSet Enumeration Knob in a node's knobs() function 38 | DD::Image::Knob* LayerSetKnob(DD::Image::Knob_Callback&, LayerSetKnobData&); 39 | // get the current LayerSet on an Op 40 | string getLayerSetKnobEnumString(DD::Image::Op*); 41 | void populateLayerSetKnobEnum(DD::Image::Op*, std::vector&); 42 | 43 | /** 44 | * validation functions that manage if going through an reconfiguration of the enumeration knob is necessary 45 | * Reasons : 46 | * - Nuke runs _validate a lot, this eliminates wasting cpu cycles, when possible 47 | * - Reliability, we want to give the user a chance to fix any errors, so this handles the assertions 48 | */ 49 | 50 | // first stage of update handling : inexpensive check to see if an update is required 51 | bool _basicValidateLayerSetKnobUpdate(DD::Image::Op*, const LayerSetKnobData&, const DD::Image::ChannelSet&); 52 | // second stage of update handling : categorizes the incoming channels, and decides if an update is redundant. 53 | bool _categorizedValidateLayerSetKnobUpdate(DD::Image::Op*, const LayerMap&, const string&); 54 | // main validation function for managing LayerSetKnob updating 55 | bool validateLayerSetKnobUpdate(DD::Image::Op*, const LayerSetKnobData&, LayerCollection&, const DD::Image::ChannelSet&); 56 | // main validation filtered function for managing LayerSetKnob updating 57 | bool validateLayerSetKnobUpdate(DD::Image::Op*, const LayerSetKnobData&, LayerCollection&, const DD::Image::ChannelSet&, const CategorizeFilter&); 58 | 59 | /** 60 | * convenience functions for doing the actual updating of the updating of both the knob and it's storage 61 | */ 62 | 63 | // private function to do the actual data updating to the enumeration knob 64 | void _updateLayerSetKnob(DD::Image::Op*, LayerSetKnobData&, ChannelSetMapType&, const DD::Image::ChannelSet&); 65 | // main update function to update an Op's LayerSetKnob 66 | void updateLayerSetKnob(DD::Image::Op*, LayerSetKnobData&, LayerCollection&, DD::Image::ChannelSet&); 67 | // main update function to update an Op's filtered LayerSetKnob 68 | void updateLayerSetKnob(DD::Image::Op*, LayerSetKnobData&, LayerCollection&, DD::Image::ChannelSet&, const CategorizeFilter&); 69 | } // End namespace LayerSetKnob 70 | } // End namespace LayerAlchemy 71 | -------------------------------------------------------------------------------- /python/nuke/layer_alchemy/utilities.py: -------------------------------------------------------------------------------- 1 | """shared utilities module for LayerAlchemy""" 2 | 3 | import os 4 | import subprocess 5 | 6 | import nuke 7 | 8 | import constants 9 | 10 | 11 | def pluginAddPaths(): 12 | """ 13 | adds the icon and plugin directories to Nuke's pluginPath 14 | """ 15 | nuke.pluginAddPath(constants.LAYER_ALCHEMY_ICON_DIR) 16 | nuke.pluginAddPath(_getPluginDirForCurrentNukeVersion()) 17 | 18 | 19 | def getDocumentationIndexPath(): 20 | """ 21 | Find the absolute path to the documentation's main index.html file 22 | :return: the absolute path or None if it doesn't exist 23 | :rtype str 24 | """ 25 | siteIndex = os.path.join(constants.LAYER_ALCHEMY_DOCUMENTATION_DIR, 'index.html') 26 | return siteIndex if os.path.isfile(siteIndex) else '' 27 | 28 | 29 | def getNukeVersionString(): 30 | """ 31 | Utility function to get a formatted 'major.minor' Nuke version string. 32 | Compiled plugins are installed in a folder name per major/minor 33 | :return: version string like '11.3' 34 | :rtype str 35 | """ 36 | return '{major}.{minor}'.format(major=nuke.env['NukeVersionMajor'], minor=nuke.env['NukeVersionMinor']) 37 | 38 | 39 | def _getPluginDirForCurrentNukeVersion(): 40 | """ 41 | Utility Function to get the directory path relevant to the current nuke version 42 | :return: absolute directory path to plugin directory 43 | :rtype str 44 | """ 45 | return os.path.join(constants.LAYER_ALCHEMY_PLUGIN_ROOT_DIR, getNukeVersionString()) 46 | 47 | 48 | def nukeVersionCompatible(): 49 | """ 50 | Utility Function check if suitable LAYER_ALCHEMY_PLUGINS are compiled for the running nuke version 51 | :return: true if a folder was found, false otherwise 52 | :rtype bool 53 | """ 54 | return getNukeVersionString() in os.listdir(constants.LAYER_ALCHEMY_PLUGIN_ROOT_DIR) 55 | 56 | 57 | def _validateConfigFile(configFilePath): 58 | """ 59 | Test a configuration file path to be sure it is usable in the plugin 60 | Uses a binary included in the project to test a given configuration file, and will raise an exception 61 | if something is not valid. 62 | The idea if to fail fast at startup for any configuration file issue. 63 | :param configFilePath: absolute path to a yaml file 64 | :raises ValueError if configFilePath is missing or is not a valid yaml file 65 | """ 66 | if not os.path.isfile(configFilePath): 67 | raise ValueError('missing configuration file') 68 | return subprocess.call( 69 | [constants.LAYER_ALCHEMY_CONFIGTESTER_BIN, '--config', configFilePath, '--quiet'] 70 | ) 71 | 72 | 73 | def validateConfigFileEnvironmentVariables(): 74 | """ 75 | Tests if all required configurations are valid files as defined in constants.LAYER_ALCHEMY_CONFIGS_DICT. 76 | If current environment variable defines a custom yaml config, it will validate it. 77 | If none exists, validate and set it the included default configuration 78 | """ 79 | for envVarName, baseName in constants.LAYER_ALCHEMY_CONFIGS_DICT.items(): 80 | configFile = os.environ.get(envVarName) # test if a custom configuration is present and validate it 81 | if not configFile: 82 | configFile = os.path.join(constants.LAYER_ALCHEMY_CONFIGS_DIR, baseName) 83 | os.environ[envVarName] = configFile 84 | error = _validateConfigFile(configFile) 85 | if error: 86 | raise ValueError('invalid configuration file {}'.format(configFile)) 87 | -------------------------------------------------------------------------------- /documentation/docs/core.md: -------------------------------------------------------------------------------- 1 | # General concepts and motivation 2 | 3 | ## Introduction 4 | 5 | The initial motivation behind the development of this plugin suite 6 | came about when dealing with ever increasing amounts of layers in production, and the adoption of multipart 7 | exr workflows. 8 | 9 | Compositing wise, the outcome if often: over complicated compositing scripts, because the layer mechanics are generally visible. 10 | 11 | To fully understand how this plugin suite works, and leverage it, we must cover some key concepts, 12 | as they will will be referenced throughout the documentation 13 | 14 | 15 | !!! info "The consequence of more layers, and the reliance on richer render passes also means that :" 16 | 17 | - the layer isolation and recombining requires a lot of nodes, makes the script look more complicated 18 | - accurate layer management is time taken away from the creative aspect of compositing 19 | - manual layer management is error prone, at some point, an error/omission will creep in. 20 | 21 | !!! info "Python powered Groups or Gizmos do a good job, and I've written them before, but they have their limits:" 22 | 23 | - limited mostly by the callback system to trigger behaviors. 24 | - cannot be cloned in Nuke. 25 | - internally, the algorithm is spread out across multiple Nuke nodes that must be managed. 26 | - lots python and expression code, workarounds are required. 27 | - some knobs are c++ only. 28 | - In general, the fewer the nodes, the faster the comp. 29 | 30 | ## LayerSets 31 | 32 | 33 | If a _layer_ is a set of _channels_, a _LayerSet_ is then exactly what you think. a set of layers. 34 | 35 | Imagine if you could look at a huge list of channels, classify them, and leverage this in native Nuke plugins. 36 | 37 | This is the core of LayerAlchemy. 38 | 39 | Let's take the common layer name ***"specular_direct"*** for example : 40 | 41 | In this plugin suite, if it is found this layer will be categorized as : 42 | 43 | The included commandline tool [LayerTester](tools.md#LayerTester) to visualize the classification : 44 | 45 | ```bash 46 | ./LayerTester --layers specular_direct 47 | 48 | 49 | 50 | { 51 | 'all' : ('specular_direct', ), 52 | 'beauty_shading' : ('specular_direct', ), 53 | 'direct' : ('specular_direct', ), 54 | 'specular' : ('specular_direct', ), 55 | } 56 | 57 | ``` 58 | 59 | 60 | !!! info "we can see here that this single layer name is actually part of 3 [LayerSets](core.md#layersets)" 61 | 62 | - beauty_shading 63 | - direct 64 | - specular 65 | 66 | !!! note "Any LayerSet knob in this plugin suite will allow you to concentrate on [LayerSets](core.md#layersets) rather than layers." 67 | 68 | this means that if you are making a template, and use nodes in this plugin suite, you won't have to 69 | update them (unless something is *horribly* missing) 70 | 71 | 72 | ## Config Files 73 | 74 | In this project, [LayerSets](core.md#layersets) are defined in [yaml](https://yaml.org/) files 75 | 76 | This, to some extent, separates the layer names and the categories ([LayerSets](core.md#layersets)) for the 77 | c++ code so they are adaptable without recompiling rhe plugins. 78 | 79 | The closest analogy I can think of is OCIO configs and OCIOColorSpace nodes in Nuke. 80 | 81 | The idea is to group layer names, so that, in Nuke, what the artist interacts with is the category, nver 82 | the layers. 83 | 84 | _please read [channels and topology](configs_and_topology.md) if you want to know more, or need to tweak them_ 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LayerAlchemy 2 | ## C++ Multichannel Nuke plugins 3 | ![logo](./icons/layer_alchemy_200px.png) 4 | 5 | 6 | ### Nuke installation for compiled builds 7 | 8 | ```code 9 | # add this to your init.py and adapt the path to where the package is uncompressed 10 | nuke.pluginAddPath('/path/to/plugin/LayerAlchemy/nuke') 11 | ``` 12 | 13 | 14 | ### Build instruction example : 15 | 16 | ```code 17 | git clone https://github.com/sebjacob/LayerAlchemy 18 | ``` 19 | 20 | ```code 21 | mkdir /a/new/folder/to/build/in 22 | cd /a/new/folder/to/build/in 23 | cmake /path/to/git/cloned/LayerAlchemyDir -DNUKE_ROOT=/Applications/Nuke11.3v4 -DCMAKE_INSTALL_PREFIX=/Path/to/install/to 24 | ``` 25 | ```code 26 | make # compile the code 27 | make documentation # build the documentation, needs mkdocs (pip install mkdocs) 28 | make install # copies the compiled files to the install directory 29 | make package # creates a compressed file containing the project 30 | ``` 31 | ### Nuke Plugins 32 | 33 | ## ![GradeBeautyIcon](./icons/GradeBeauty.png) [GradeBeauty](./documentation/docs/GradeBeauty.md) 34 | GradeBeauty provides artist friendly controls to manipulate multichannel cg render passes 35 | The GradeBeauty design is purposely utilitarian and simple. 36 | 37 | ![GradeBeautyParams](./documentation/docs/media/parameters/GradeBeauty.png) 38 | 39 | ## ![GradeBeautyLayerSetIcon](./icons/GradeBeautyLayerSet.png) [GradeBeautyLayerSet](./documentation/docs/GradeBeautyLayerSet.md) 40 | GradeBeautyLayerSet provides a simple way to specifically grade multiple cg layers using a 41 | [LayerSet](./documentation/docs/core.md#layersets) 42 | 43 | Image processing math is exactly like the Nuke Grade node except that, you can grade multiple layers at the 44 | same time 45 | 46 | ![GradeBeautyLayerSet](./documentation/docs/media/parameters/GradeBeautyLayerSet.png) 47 | 48 | ## ![GradeBeautyLayerIcon](./icons/GradeBeautyLayer.png) [GradeBeautyLayer](./documentation/docs/GradeBeautyLayer.md) 49 | GradeBeautyLayer provides a simple way to specifically grade a cg layer and replace it in the beauty 50 | 51 | ![GradeBeautyLayer](./documentation/docs/media/parameters/GradeBeautyLayer.png) 52 | 53 | 54 | ## ![FlattenLayerSetIcon](./icons/FlattenLayerSet.png) [FlattenLayerSet](./documentation/docs/FlattenLayerSet.md) 55 | FlattenLayerSet provides a simple way to merge additive [LayerSet](./documentation/docs/core.md#layersets) 56 | from multichannel cg render passes to any single layer 57 | 58 | ![FlattenLayerSetParams](./documentation/docs/media/parameters/FlattenLayerSet.png) 59 | 60 | ## ![RemoveLayerSetIcon](./icons/RemoveLayerSet.png) [RemoveLayerSet](./documentation/docs/RemoveLayerSet.md) 61 | RemoveLayerSet provides a simple way to isolate [LayerSet](./documentation/docs/core.md#layersets) from multichannel streams 62 | 63 | ![RemoveLayerSetParams](./documentation/docs/media/parameters/RemoveLayerSet.png) 64 | 65 | ## ![GradeLayerSetIcon](./icons/GradeLayerSet.png) [GradeLayerSet](./documentation/docs/GradeLayerSet.md) 66 | GradeLayerSet provides a simple way to grade multiple layers using [LayerSet](./documentation/docs/core.md#layersets) 67 | 68 | _It's exactly like the Nuke Grade node except that, you can grade multiple layers at the same time_ 69 | 70 | _If you are grading cg layers and wish to propagate the changes to the beauty a the same time, have a look at [FlattenLayerSet](./documentation/docs/FlattenLayerSet.md)_ 71 | 72 | ![GradeLayerSetParams](./documentation/docs/media/parameters/GradeLayerSet.png) 73 | 74 | ## ![MultiplyLayerSetIcon](./icons/MultiplyLayerSet.png) [MultiplyLayerSet](./documentation/docs/MultiplyLayerSet.md) 75 | MultiplyLayerSet provides a simple way to multiply a selection of layers using [LayerSet](./documentation/docs/core.md#layersets) 76 | 77 | _It's exactly like the Nuke Multiply node except that, you can multiply a selection of layers at the same time_ 78 | 79 | ![MultiplyLayerSetParams](./documentation/docs/media/parameters/MultiplyLayerSet.png) 80 | -------------------------------------------------------------------------------- /src/nuke/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # cmake file for LayerAlchemy Nuke plugins 2 | 3 | #find_package(GLEW REQUIRED) # This is needed, but not right now. 4 | 5 | if(EXISTS ${NUKE_ROOT}) 6 | message(STATUS "Found Nuke: ${NUKE_ROOT}") 7 | else() 8 | message(FATAL_ERROR "Couldn't find Nuke root (${NUKE_ROOT}). Please set $NUKE_ROOT with -DNUKE_ROOT") 9 | endif() 10 | 11 | if(APPLE) 12 | get_filename_component(real_nuke_version ${NUKE_ROOT} NAME) 13 | get_bundle_main_executable(${NUKE_ROOT}/${real_nuke_version}.app nuke_main_executable) 14 | get_filename_component(NUKE_ROOT ${nuke_main_executable} DIRECTORY) 15 | set(CMAKE_MACOSX_RPATH 1) 16 | endif() 17 | 18 | find_library(LIB_DDIMAGE DDImage ${NUKE_ROOT}) 19 | 20 | # get the Nuke version info 21 | file(READ ${NUKE_ROOT}/include/DDImage/ddImageVersionNumbers.h ver) 22 | string(REGEX MATCH "#define kDDImageVersionMajorNum ([0-9]*)" _ ${ver}) 23 | set(NUKE_MAJOR ${CMAKE_MATCH_1}) 24 | string(REGEX MATCH "#define kDDImageVersionMinorNum ([0-9]*)" _ ${ver}) 25 | set(NUKE_MINOR ${CMAKE_MATCH_1}) 26 | string(REGEX MATCH "#define kDDImageVersionReleaseNum ([0-9]*)" _ ${ver}) 27 | set(NUKE_RELEASE_NUM ${CMAKE_MATCH_1}) 28 | 29 | set(NUKE_RELEASE ${NUKE_MAJOR}.${NUKE_MINOR} CACHE STRING "" FORCE) 30 | 31 | include_directories( 32 | ${NUKE_ROOT}/include 33 | ${CMAKE_SOURCE_DIR}/include/nuke/ 34 | ${CMAKE_SOURCE_DIR}/src/nuke/ 35 | ) 36 | set(NUKE_PLUGIN_DESTINATION nuke/plugins/${NUKE_RELEASE}) 37 | set(PROJECT_NUKE_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include/nuke) 38 | set(PROJECT_NUKE_SRC_DIR ${CMAKE_SOURCE_DIR}/src/nuke) 39 | 40 | # layer_alchemy libs 41 | add_library(LayerSet STATIC ${PROJECT_NUKE_SRC_DIR}/LayerSet.cpp) 42 | target_link_libraries(LayerSet ${LAYERSET_LIBS} ${LIB_DDIMAGE}) 43 | set_target_properties(LayerSet 44 | PROPERTIES PREFIX "" 45 | PUBLIC_HEADER ${PROJECT_NUKE_INCLUDE_DIR}/LayerSet.h) 46 | list(APPEND NUKE_LAYERSET_LIBS LayerSet) 47 | 48 | add_library(LayerSetKnob STATIC ${PROJECT_NUKE_SRC_DIR}/LayerSetKnob.cpp) 49 | target_link_libraries(LayerSetKnob LayerSet ${LAYERSET_LIBS} ${LIB_DDIMAGE}) 50 | set_target_properties(LayerSetKnob 51 | PROPERTIES PREFIX "" 52 | PUBLIC_HEADER ${PROJECT_NUKE_INCLUDE_DIR}/LayerSetKnob.h) 53 | list(APPEND NUKE_LAYERSET_LIBS LayerSetKnob) 54 | 55 | # plugins 56 | 57 | add_library(RemoveLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/RemoveLayerSet.cpp) 58 | list(APPEND NUKE_PLUGINS RemoveLayerSet) 59 | 60 | add_library(MultiplyLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/MultiplyLayerSet.cpp) 61 | list(APPEND NUKE_PLUGINS MultiplyLayerSet) 62 | 63 | add_library(GradeLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/GradeLayerSet.cpp) 64 | list(APPEND NUKE_PLUGINS GradeLayerSet) 65 | 66 | add_library(FlattenLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/FlattenLayerSet.cpp) 67 | list(APPEND NUKE_PLUGINS FlattenLayerSet) 68 | 69 | add_library(GradeBeauty SHARED ${PROJECT_NUKE_SRC_DIR}/GradeBeauty.cpp) 70 | list(APPEND NUKE_PLUGINS GradeBeauty) 71 | 72 | add_library(GradeBeautyLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/GradeBeautyLayerSet.cpp) 73 | list(APPEND NUKE_PLUGINS GradeBeautyLayerSet) 74 | 75 | add_library(GradeBeautyLayer SHARED ${PROJECT_NUKE_SRC_DIR}/GradeBeautyLayer.cpp) 76 | list(APPEND NUKE_PLUGINS GradeBeautyLayer) 77 | 78 | foreach(PLUGIN ${NUKE_PLUGINS}) 79 | set_target_properties(${PLUGIN} PROPERTIES PREFIX "") 80 | target_link_libraries(${PLUGIN} ${LAYERSET_LIBS} ${NUKE_LAYERSET_LIBS} ${LIB_DDIMAGE}) 81 | endforeach() 82 | 83 | install( 84 | TARGETS ${NUKE_PLUGINS} 85 | DESTINATION ${NUKE_PLUGIN_DESTINATION} 86 | ) 87 | 88 | install( 89 | TARGETS ${NUKE_LAYERSET_LIBS} 90 | DESTINATION ${NUKE_PLUGIN_DESTINATION} 91 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/nuke 92 | ) 93 | 94 | # # copy menu and icons and configs over to the build Dir 95 | install( 96 | DIRECTORY ${CMAKE_SOURCE_DIR}/icons 97 | DESTINATION nuke 98 | FILES_MATCHING PATTERN "*.png" 99 | ) 100 | 101 | install( 102 | DIRECTORY ${CMAKE_SOURCE_DIR}/python/nuke 103 | DESTINATION ${CMAKE_INSTALL_PREFIX} 104 | FILES_MATCHING PATTERN "*.py" 105 | ) 106 | -------------------------------------------------------------------------------- /configs/layers/_deprecated/arnold4.yaml: -------------------------------------------------------------------------------- 1 | 2 | beauty_shading_global: !!set &arnold_global 3 | ? specular 4 | ? diffuse 5 | ? volume 6 | ? sss 7 | 8 | beauty_direct_indirect: !!set &beauty_direct_indirect 9 | ? direct 10 | ? indirect 11 | 12 | base_color: !!set &arnold_base_color 13 | ? diffuse_color 14 | 15 | depth: !!set &arnold_depth 16 | ? Z 17 | 18 | uv: !!set &arnold_uv 19 | ? uv 20 | ? motionvector 21 | 22 | id: !!set &arnold_id 23 | ? id_1 24 | ? id_2 25 | ? id_3 26 | ? id_4 27 | ? id_5 28 | ? id_6 29 | ? id_7 30 | ? id_8 31 | 32 | diffuse: !!set &arnold_diffuse 33 | ? direct_diffuse 34 | ? direct_diffuse_cel 35 | ? direct_diffuse_raw 36 | ? direct_diffuse_raw_cel 37 | ? indirect_diffuse 38 | ? indirect_diffuse_cel 39 | ? indirect_diffuse_raw 40 | ? indirect_diffuse_raw_cel 41 | 42 | direct: !!set &arnold_direct 43 | ? direct_backlight 44 | ? direct_diffuse 45 | ? direct_diffuse_cel 46 | ? direct_diffuse_raw 47 | ? direct_diffuse_raw_cel 48 | ? direct_glint 49 | ? direct_global 50 | ? direct_local 51 | ? direct_specular 52 | ? direct_specular_2 53 | ? direct_sss 54 | ? direct_transmission 55 | 56 | specular: !!set &arnold_specular 57 | ? direct_specular 58 | ? direct_specular_2 59 | ? indirect_specular 60 | ? indirect_specular_2 61 | 62 | 63 | indirect: !!set &arnold_indirect 64 | ? indirect_backlight 65 | ? indirect_diffuse 66 | ? indirect_diffuse_cel 67 | ? indirect_diffuse_raw 68 | ? indirect_glint 69 | ? indirect_global 70 | ? indirect_local 71 | ? indirect_specular 72 | ? indirect_specular_2 73 | ? indirect_sss 74 | ? indirect_transmission 75 | 76 | secondary: !!set &arnold_secondary 77 | ? emission 78 | ? reflection 79 | ? refraction 80 | ? single_scatter 81 | ? sss 82 | 83 | light_group: !!set &arnold_light_group 84 | ? light_group_1 85 | ? light_group_2 86 | ? light_group_3 87 | ? light_group_4 88 | ? light_group_5 89 | ? light_group_6 90 | ? light_group_7 91 | ? light_group_8 92 | ? mesh_light_beauty 93 | 94 | shadow: !!set &arnold_shadow 95 | ? shadow 96 | ? shadow_diff 97 | ? shadow_group_1 98 | ? shadow_group_2 99 | ? shadow_group_3 100 | ? shadow_group_4 101 | ? shadow_group_5 102 | ? shadow_group_6 103 | ? shadow_group_7 104 | ? shadow_group_8 105 | 106 | opacity: !!set &arnold_opacity 107 | ? opacity 108 | ? volume_opacity 109 | 110 | 111 | volume: !!set &arnold_volume 112 | ? volume 113 | ? volume_direct 114 | ? volume_indirect 115 | ? volume_opacity 116 | 117 | raw: !!set &arnold_raw 118 | ? direct_diffuse_raw 119 | ? direct_diffuse_raw_cel 120 | ? indirect_diffuse_raw 121 | ? indirect_diffuse_raw_cel 122 | 123 | matte: !!set &arnold_matte 124 | <<: *arnold_depth 125 | ? shadow_matte 126 | ? volume_z 127 | ? volume_opacity 128 | 129 | non_color: !!set &non_color 130 | <<: *arnold_id 131 | <<: *arnold_uv 132 | <<: *arnold_depth 133 | <<: *arnold_opacity 134 | <<: *arnold_matte 135 | 136 | beauty_shading: !!set &arnold_beauty_shading 137 | ? direct_backlight 138 | ? direct_diffuse 139 | ? direct_glint 140 | ? direct_specular 141 | ? direct_specular_2 142 | ? direct_sss 143 | ? direct_transmission 144 | ? indirect_backlight 145 | ? indirect_diffuse 146 | ? indirect_specular 147 | ? indirect_specular_2 148 | ? indirect_sss 149 | ? indirect_transmission 150 | ? emission 151 | ? reflection 152 | ? refraction 153 | ? single_scatter 154 | 155 | _prefix: !!set 156 | ? id 157 | 158 | # channel mappings 159 | 160 | _alpha: !!set &arnold_alpha 161 | <<: *arnold_depth 162 | <<: *arnold_opacity 163 | 164 | _uv: !!set 165 | <<: *arnold_uv 166 | 167 | _vec3: !!set 168 | <<: *arnold_id 169 | <<: *arnold_beauty_shading 170 | <<: *arnold_light_group 171 | <<: *arnold_base_color 172 | <<: *arnold_global 173 | <<: *arnold_raw 174 | <<: *arnold_shadow 175 | <<: *arnold_direct 176 | <<: *arnold_indirect 177 | 178 | _vec4: !!set 179 | ? primary 180 | ? beauty 181 | ? rgba 182 | 183 | _xyz: !!set 184 | ? P 185 | ? Pref 186 | ? N 187 | -------------------------------------------------------------------------------- /configs/layers/arnold5.yaml: -------------------------------------------------------------------------------- 1 | 2 | arnold_debug: !!set &arnold_debug 3 | ? cputime 4 | ? raycount 5 | ? AA_inv_density 6 | 7 | albedo: !!set &arnold_albedo 8 | ? albedo 9 | ? diffuse_albedo 10 | ? coat_albedo 11 | ? specular_albedo 12 | ? sss_albedo 13 | ? transmission_albedo 14 | ? volume_albedo 15 | 16 | coat: !!set &arnold_coat 17 | ? coat 18 | ? coat_albedo 19 | ? coat_direct 20 | ? coat_indirect 21 | 22 | diffuse: !!set &arnold_diffuse 23 | ? diffuse 24 | ? diffuse_albedo 25 | ? diffuse_direct 26 | ? diffuse_indirect 27 | 28 | sheen: !!set &arnold_sheen 29 | ? sheen 30 | ? sheen_albedo 31 | ? sheen_direct 32 | ? sheen_indirect 33 | 34 | sss: !!set &arnold_sss 35 | ? sss 36 | ? sss_albedo 37 | ? sss_direct 38 | ? sss_indirect 39 | 40 | transmission: !!set &arnold_transmission 41 | ? transmission 42 | ? transmission_albedo 43 | ? transmission_direct 44 | ? transmission_indirect 45 | 46 | volume: !!set &arnold_volume 47 | ? volume 48 | ? volume_albedo 49 | ? volume_direct 50 | ? volume_indirect 51 | 52 | light_group: !!set &arnold_light_group 53 | ? light_group_1 54 | ? light_group_2 55 | ? light_group_3 56 | ? light_group_4 57 | ? light_group_5 58 | ? light_group_6 59 | ? light_group_7 60 | ? light_group_8 61 | ? light_group_9 62 | ? light_group_10 63 | ? light_group_11 64 | ? light_group_12 65 | ? light_group_13 66 | ? light_group_14 67 | ? light_group_15 68 | ? light_group_16 69 | 70 | specular: !!set &arnold_specular 71 | ? coat 72 | ? coat_direct 73 | ? coat_indirect 74 | ? specular 75 | ? specular_direct 76 | ? specular_indirect 77 | 78 | direct: !!set &arnold_direct 79 | ? direct 80 | ? coat_direct 81 | ? diffuse_direct 82 | ? sheen_direct 83 | ? specular_direct 84 | ? sss_direct 85 | ? transmission_direct 86 | ? volume_direct 87 | 88 | indirect: !!set &arnold_indirect 89 | ? indirect 90 | ? coat_indirect 91 | ? diffuse_indirect 92 | ? sheen_indirect 93 | ? specular_indirect 94 | ? sss_indirect 95 | ? transmission_indirect 96 | ? volume_indirect 97 | 98 | base_color: !!set &arnold_color_layer 99 | ? diffuse_albedo 100 | 101 | secondary: !!set &arnold_secondary 102 | ? emission 103 | ? reflection 104 | ? refraction 105 | ? single_scatter 106 | ? sss_direct 107 | ? sss_indirect 108 | 109 | id: !!set &arnold_id 110 | ? id 111 | ? ID 112 | 113 | deep: !!set &arnold_deep 114 | ? deep 115 | 116 | depth: !!set &arnold_depth 117 | ? Z 118 | ? depth 119 | 120 | uv: !!set &arnold_uv 121 | ? uv 122 | ? motionvector 123 | 124 | p: !!set &arnold_p 125 | ? P 126 | 127 | pref: !!set &arnold_pref 128 | ? Pref 129 | 130 | shadow: !!set &arnold_shadow 131 | ? shadow_matte 132 | ? shadow_diff 133 | ? shadow 134 | 135 | matte: !!set &arnold_matte 136 | <<: *arnold_depth 137 | ? shadow_matte 138 | ? volume_z 139 | ? volume_opacity 140 | 141 | 142 | beauty_shading: !!set &arnold_beauty_shading 143 | <<: *arnold_secondary 144 | ? coat_direct 145 | ? coat_indirect 146 | ? diffuse_direct 147 | ? diffuse_indirect 148 | ? sheen_direct 149 | ? sheen_indirect 150 | ? specular_direct 151 | ? specular_indirect 152 | ? sss_direct 153 | ? sss_indirect 154 | ? transmission_direct 155 | ? transmission_indirect 156 | ? volume_direct 157 | ? volume_indirect 158 | 159 | beauty_shading_global: !!set &arnold_beauty_shading_global 160 | ? background 161 | ? diffuse 162 | ? specular 163 | ? sss 164 | ? transmission 165 | ? volume 166 | ? sheen 167 | 168 | beauty_direct_indirect: !!set &arnold_beauty_direct_indirect 169 | ? direct 170 | ? indirect 171 | 172 | non_color: !!set &arnold_non_color 173 | <<: *arnold_id 174 | <<: *arnold_uv 175 | <<: *arnold_depth 176 | <<: *arnold_deep 177 | <<: *arnold_p 178 | <<: *arnold_pref 179 | <<: *arnold_debug 180 | <<: *arnold_matte 181 | ? N 182 | 183 | _prefix: !!set 184 | ? id 185 | #? light_group 186 | 187 | # channel mappings 188 | 189 | _alpha: !!set &arnold_alpha 190 | <<: *arnold_depth 191 | <<: *arnold_debug 192 | ? volume_z 193 | 194 | _uv: !!set 195 | <<: *arnold_uv 196 | 197 | _vec3: !!set 198 | <<: *arnold_id 199 | <<: *arnold_beauty_shading 200 | <<: *arnold_beauty_shading_global 201 | <<: *arnold_beauty_direct_indirect 202 | <<: *arnold_light_group 203 | <<: *arnold_albedo 204 | <<: *arnold_color_layer 205 | ? opacity 206 | ? volume_opacity 207 | ? shadow 208 | ? shadow_diff 209 | 210 | _vec4: !!set 211 | ? primary 212 | ? beauty 213 | ? rgba 214 | 215 | _xyz: !!set 216 | ? P 217 | ? N 218 | ? Pref -------------------------------------------------------------------------------- /include/LayerSetCore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LayerSetTypes.h" 4 | #include "LayerSetConfig.h" 5 | 6 | #define LAYER_ENV_VAR "LAYER_ALCHEMY_LAYER_CONFIG" 7 | #define CHANNEL_ENV_VAR "LAYER_ALCHEMY_CHANNEL_CONFIG" 8 | 9 | //Enumeration class to qualify the type of categories to search for. 10 | enum class categorizeType { 11 | /**"private" categories : 12 | * Categories starting with "_" in the configuration files. 13 | * the categories are to be used internally in the code 14 | */ 15 | priv, 16 | /**"public" categories : 17 | * Categories that the client sees in applications, or tools leveraging this library 18 | */ 19 | pub 20 | }; 21 | 22 | //Enumeration class to qualify the types of channel naming. 23 | enum class topologyStyle { 24 | /** 25 | * This topology type means that the channel name is an actual word 26 | * example : the RED channel for the 'direct_diffuse' layer would be : 27 | * direct_diffuse.red 28 | */ 29 | lexical, 30 | /** 31 | * This topology type means that the channel name is openexr style 32 | * example : the RED channel for the 'direct_diffuse' layer would be : 33 | * direct_diffuse.R 34 | */ 35 | exr 36 | }; 37 | 38 | /** 39 | * Convenience class for specifying how to interpret incoming channels/layers 40 | */ 41 | class CategorizeFilter { 42 | public: 43 | enum modes { 44 | INCLUDE = 0, EXCLUDE, ONLY 45 | }; 46 | CategorizeFilter(); 47 | CategorizeFilter(const StrVecType&, int); 48 | int filterMode; 49 | StrVecType categories; 50 | }; 51 | 52 | /** 53 | * Base class for manipulating mappings. 54 | */ 55 | class LayerMap { 56 | public: 57 | //data stored publicly, for now. 58 | StrMapType strMap; 59 | LayerMap(); 60 | LayerMap(const LayerMap&); 61 | LayerMap(const StrMapType&); 62 | LayerMap(const string&); 63 | virtual ~LayerMap(); 64 | 65 | StrVecType operator[](const StrVecType&) const; 66 | StrVecType operator[](const string&) const; 67 | 68 | //adds to category a layer 69 | void add(const string, const string); 70 | //returns the category names in this LayerMap 71 | const StrVecType categories() const; 72 | //returns the category names for a given layer name 73 | const StrVecType categories(string&) const; 74 | //Get a vector of category names for each category type 75 | const StrVecType categoriesByType(const categorizeType&) const; 76 | //test if a category contains an element 77 | bool isMember(const string&, const string&) const; 78 | //test if a category contains an element 79 | bool contains(const string&) const; 80 | //test if a category contains a multiple elements 81 | bool contains(const StrVecType&) const; 82 | //returns a vector of unique layer names in this LayerMap 83 | const StrVecType uniqueLayers() const; 84 | //returns the contents of the object in string form 85 | string toString() const; 86 | bool empty() const; 87 | int size() const; 88 | }; 89 | 90 | /** 91 | * Object used to store layer configurations, categorize layer names, or create full channel names (topology) 92 | * It stores both channel and layer LayerMap objects, and the contents are loaded from the defined environment variables 93 | */ 94 | class LayerCollection { 95 | 96 | private: 97 | // takes a given layer name and returns a base category prefix or and empty string otherwise 98 | string dePrefix(const string) const; 99 | 100 | public: 101 | // default constructor 102 | LayerCollection(); 103 | // returns a LayerMap of categorized items 104 | LayerMap categorizeLayers(const StrVecType&, const categorizeType&) const; 105 | // returns a LayerMap of categorized items, but filtered with a CategorizeFilter 106 | LayerMap categorizeLayers(const StrVecType&, const categorizeType&, const CategorizeFilter& catFilter) const; 107 | //The notion of topology is basically adding, for example ".red" to a layer name based on a topologyStyle. 108 | //Unknown layer names return as .red, .green, .blue, .alpha or A, B, G, R 109 | LayerMap topology(const StrVecType&, const topologyStyle&) const; 110 | 111 | // houses the map to channel configurations 112 | LayerMap channels; 113 | // houses the map to layer configurations 114 | LayerMap layers; 115 | //destructor 116 | virtual ~LayerCollection(); 117 | }; 118 | 119 | //Various useful functions 120 | namespace utilities { 121 | string getLayerFromChannel(const string&); 122 | StrVecType applyChannelNames(const string&, const StrVecType&); 123 | } // utilities 124 | -------------------------------------------------------------------------------- /documentation/docs/configs_and_topology.md: -------------------------------------------------------------------------------- 1 | # Customizing config files 2 | 3 | !!! warning "knowledge of this is only required if, for example :" 4 | 5 | * adding aovs from another renderer 6 | * adding custom layers and categories in your workflow 7 | 8 | The configuration system contains two folders in _/configs_ that contain multiple yaml files 9 | 10 | | directory | uses | collapsed target yaml file | 11 | | --------- | ---- | -------------------------- | 12 | |/configs/layers | comprised of yaml files containing sets of layer names | /configs/layers.yaml | 13 | |/configs/channels | list based yaml files, order matters in channels | /configs/channels.yaml | 14 | 15 | ## private vs public category types (categorizeType) : 16 | | category | uses | note | 17 | | -------- | ---- | ---- | 18 | |private | meant to be used by the core to help classification only | Names all start with the standard underscore nomenclature ex: "_xyz" | 19 | |public | meant to be viewed by the user to populate menus for example | anything but starting with "_" | 20 | 21 | ## topology (topologyStyle): 22 | 23 | !!! warning "usage" 24 | - The nuke plugins do not use topology at all. 25 | - Nuke plugins only use layer names. 26 | - The topology code is intended for use, for example, in an OpenImageIO multipart creation tool. 27 | - It helps define and enforce channel naming for reliability and safety reasons(bad channels break Nuke scripts) 28 | 29 | Conceptually, a layer is a grouping of channels. 30 | To create channel names in this system topology style is used. 31 | Topology, and all channel related classifiers are defined in the yaml files located in the channels configs 32 | folder 33 | 34 | topology styles are basically mirrored alternative name given to a channel 35 | 36 | | topology style | definition | example | 37 | | -------------- | ---------- | ------- | 38 | |lexical | This topology type means that the channel name is an actual word | _the "red" channel for the 'direct_diffuse' layer would be direct_diffuse.red_ | 39 | | exr | the openEXR style with single capital letters like layer.R for red | _direct_diffuse.R_ 40 | 41 | ### Defining a new layer called ('qc.grain', 'qc.edges', 'qc.diff'): 42 | 43 | Adding layers is as easy as adding to the existing yaml files, so this example will showcase something a bit more complex. 44 | 45 | We will add the ability to have a new type of layer category that has ('.grain', '.edges', '.diff') channels 46 | 47 | We'll start with adding the custom channel names : 48 | 49 | - create a facility.yaml file in _configs/channels/_ 50 | 51 | !!! note "add new private categories: called '_qc' and '_qc_exr' to this new yaml file" 52 | _qc: &facility_qc 53 | - grain 54 | - edges 55 | - diff 56 | _qc_exr: 57 | *facility_qc # because in exr style we want it to be the same as the lexical 58 | 59 | - final step for topology is registering this in _configs/channels/topology.yaml_: 60 | !!! note "add this to the list of available topologies" 61 | 62 | topology: 63 | - _alpha 64 | - _uv 65 | - _vec3 66 | - _vec4 67 | - _xyz 68 | - _z 69 | - _qc 70 | 71 | exr_topology: 72 | - _alpha_exr 73 | - _uv_exr 74 | - _vec3_exr 75 | - _vec4_exr 76 | - _xyz_exr 77 | - _z_exr 78 | - _qc_exr 79 | 80 | 81 | Now that the channels are defined, let's add a layer that'll use it : 82 | 83 | !!! note "add the 'qc' layer in the appropriate configs/layers yaml file, example in facility.yaml" 84 | 85 | # this creates a quality control layer set, and adds a layer called "qc" to it 86 | # 87 | qualitity_control: !!set 88 | ? qc 89 | # this links the qc layer to the _qc topology 90 | _qc: !!set 91 | ? qc 92 | 93 | 94 | As of writing you need to manually collapse the yaml files using python, this will be improved in the 95 | future: 96 | 97 | ```python 98 | layer_alchemy.collapse('path to layer config folder', '/path/to/layers.yaml') 99 | layer_alchemy.collapse('path to channels config folder', '/path/to/channels.yaml') 100 | ``` 101 | 102 | Then rebuild the project (make install) 103 | 104 | You can now test everything is ok with [LayerTester](tools.md#LayerTester) 105 | 106 | ```bash 107 | ./LayerTester --layers qc 108 | 109 | 110 | 111 | { 112 | 'all' : ('qc', ), 113 | 'quality_control' : ('qc', ), 114 | } 115 | ``` 116 | 117 | ```bash 118 | ./LayerTester --layers qc --topology 119 | 120 | Layer names : 'qc' 121 | Topology style : EXR 122 | 123 | 124 | { 125 | 'qc' : ('qc.grain', 'qc.edges', 'qc.diff', ), 126 | } 127 | 128 | Topology style : 'LEXICAL' 129 | 130 | 131 | { 132 | 'qc' : ('qc.grain', 'qc.edges', 'qc.diff', ), 133 | } 134 | ``` -------------------------------------------------------------------------------- /src/LayerTester.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple executable to test the layer categorization 3 | * This will just print out various methods of categorizing. 4 | */ 5 | #include 6 | 7 | #include "argparse.h" 8 | 9 | #include "LayerSetCore.h" 10 | #include "version.h" 11 | 12 | static const std::string emojiSick = "\xF0\x9F\x98\xB7 "; 13 | static const string redText = "\x1B[31m"; 14 | static const string endColor = "\033[0m"; 15 | static const std::string DESCRIPTION = "Simple executable to test the layer categorization"; 16 | static const string LAYER_ALCHEMY_PROJECT_URL = "https://github.com/sebjacob/LayerAlchemy"; 17 | static const std::string HEADER = 18 | "\nLayerTester " + emojiSick + "\n" + DESCRIPTION + 19 | "\n\nLayerAlchemy " + LAYER_ALCHEMY_VERSION_STRING + "\n" + 20 | LAYER_ALCHEMY_PROJECT_URL + "\n"; 21 | 22 | void _printLayerMap(const LayerMap &layerMap, std::string message) 23 | { 24 | std::cout << message + "\n" + layerMap.toString(); 25 | } 26 | 27 | void _printLayerMapTopology(LayerCollection *layerCollection, const StrVecType layerNames) 28 | { 29 | _printLayerMap( 30 | layerCollection->topology(layerNames, topologyStyle::exr), "Topology style : EXR"); 31 | std::cout << std::endl; 32 | _printLayerMap( 33 | layerCollection->topology(layerNames, topologyStyle::lexical), "Topology style : LEXICAL"); 34 | } 35 | 36 | int main(int argc, const char* argv[]) 37 | { 38 | if ((getenv(CHANNEL_ENV_VAR) == NULL) | (getenv(LAYER_ENV_VAR) == NULL)) 39 | { 40 | std::cerr << HEADER << std::endl; 41 | std::cerr << redText << std::endl << "MISSING ENVIRONMENT VARIABLES" << std::endl; 42 | std::cerr << std::endl << "You need to set environment variables pointing to yaml files for :\n" 43 | << std::endl << LAYER_ENV_VAR << std::endl << CHANNEL_ENV_VAR << std::endl << endColor << std::endl; 44 | return 1; 45 | } 46 | ArgumentParser parser(DESCRIPTION); 47 | parser.add_argument("--layers", "List of layer names to test", true); 48 | parser.add_argument("-c", "--categories", "List of categories to filter", false); 49 | parser.add_argument("--topology", "outputs topology", false); 50 | parser.add_argument("--use_private", "test the private categorization", false); 51 | 52 | try 53 | { 54 | parser.parse(argc, argv); 55 | } 56 | catch (const ArgumentParser::ArgumentNotFound &ex) 57 | { 58 | std::cout << HEADER << std::endl; 59 | parser.print_help(); 60 | 61 | std::cout << ex.what() << std::endl; 62 | return 0; 63 | } 64 | if (parser.is_help()) 65 | return 0; 66 | 67 | auto layerNames = parser.getv("layers"); 68 | auto categories = parser.getv("categories"); 69 | bool useTopology = parser.get("topology"); 70 | bool usePrivate = parser.get("use_private"); 71 | categorizeType catType = usePrivate ? categorizeType::priv : categorizeType::pub; 72 | 73 | try 74 | { 75 | LayerCollection layerCollection; 76 | 77 | std::cout << std::endl; 78 | if (categories.size() == 0) 79 | { // if categories are requested, print all permutations 80 | if (useTopology) 81 | { // if topology is selected, print all permutations, then exit 82 | std::cout << "Layer names : '" + parser.get("layers") << "'\n"; 83 | _printLayerMapTopology(&layerCollection, layerNames); 84 | return 0; 85 | } 86 | else 87 | { 88 | _printLayerMap(layerCollection.categorizeLayers(layerNames, catType), ""); 89 | } 90 | return 0; 91 | } 92 | else 93 | { 94 | map filterMapping; 95 | filterMapping["EXCLUDE"] = CategorizeFilter(categories, CategorizeFilter::EXCLUDE); 96 | filterMapping["INCLUDE"] = CategorizeFilter(categories, CategorizeFilter::INCLUDE); 97 | filterMapping["ONLY"] = CategorizeFilter(categories, CategorizeFilter::ONLY); 98 | for (auto &kvp : filterMapping) 99 | { 100 | LayerMap categorizedLayers = layerCollection.categorizeLayers(layerNames, catType, kvp.second); 101 | std::cout << "-----------------------------------------------" << std::endl 102 | << "Layer names : '" + parser.get("layers") << "'\n" 103 | << "CategorizeFilter : " << kvp.first << std::endl 104 | << "Category names : '" << parser.get("categories") << "'"; 105 | 106 | if (useTopology) 107 | { 108 | _printLayerMapTopology(&layerCollection, categorizedLayers.uniqueLayers()); 109 | } 110 | else 111 | { 112 | _printLayerMap(categorizedLayers, " "); 113 | } 114 | } 115 | return 0; 116 | } 117 | } 118 | catch (const std::exception &e) 119 | { 120 | std::cerr << "LayerAlchemy ERROR : " << e.what() << std::endl; 121 | return 1; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /documentation/docs/tools.md: -------------------------------------------------------------------------------- 1 | # Config file tools 2 | 3 | ## ConfigTester 4 | 5 | Simple executable to test if a yaml file can be loaded and a LayerMap object can be constructed 6 | 7 | This also runs at Nuke startup to make sure the config files are ok 8 | 9 | ```bash 10 | ./ConfigTester 11 | 12 | ConfigTester 😷 13 | 14 | Simple executable to validate yaml files 15 | 16 | LayerAlchemy 0.9.0 17 | https://github.com/sebjacob/LayerAlchemy 18 | 19 | Example usage: 20 | 21 | ConfigTester --config /path/to/config.yaml 22 | ConfigTester --config /path/to/config1.yaml /path/to/config2.yaml 23 | Usage: ./ConfigTester [options] 24 | Options: 25 | --config List of layer names to test (Required) 26 | --quiet disable terminal output, return code only 27 | ``` 28 | 29 | ```bash 30 | ./ConfigTester --config $LAYER_ALCHEMY_LAYER_CONFIG 31 | ✅ LayerAlchemy : valid configuration file /path/to/layers.yaml 32 | ``` 33 | 34 | ## LayerTester 35 | Simple command line utility to verify how the system classifies layer names. 36 | 37 | Useful for verifying custom yaml config files. 38 | 39 | It uses the main following environment variables the config files at this time. 40 | 41 | set those first : 42 | ```bash 43 | export LAYER_ALCHEMY_CHANNEL_CONFIG=$PWD/configs/channels.yaml 44 | export LAYER_ALCHEMY_LAYER_CONFIG=$PWD/configs/layers.yaml 45 | ``` 46 | 47 | ```bash 48 | ./LayerTester 49 | 50 | LayerTester 😷 51 | Simple executable to test the layer categorization 52 | 53 | LayerAlchemy 0.5.1 https://github.com/sebjacob/LayerAlchemy 54 | 55 | Usage: ../../build_darwin18/LayerTester [options] 56 | Options: 57 | --layers List of layer names to test (Required) 58 | -c, --categories List of categories to filter 59 | --topology outputs topology 60 | --use_private test the private categorization 61 | Required argument not found: --layers 62 | ``` 63 | 64 | ### test public categorization 65 | 66 | ```bash 67 | ./LayerTester --layers P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect 68 | 69 | 70 | 71 | { 72 | 'albedo' : ('diffuse_albedo', ), 73 | 'all' : ('P', 'diffuse_direct', 'roto_head', 'diffuse_albedo', 'puz_custom', 'specular_indirect', ), 74 | 'base_color' : ('diffuse_albedo', ), 75 | 'beauty_shading' : ('diffuse_direct', 'specular_indirect', ), 76 | 'diffuse' : ('diffuse_direct', 'diffuse_albedo', ), 77 | 'direct' : ('diffuse_direct', ), 78 | 'indirect' : ('specular_indirect', ), 79 | 'matte' : ('roto_head', ), 80 | 'non_color' : ('P', 'roto_head', 'puz_custom', ), 81 | 'p' : ('P', ), 82 | 'puz' : ('puz_custom', ), 83 | 'roto' : ('roto_head', ), 84 | 'specular' : ('specular_indirect', ), 85 | } 86 | ``` 87 | ### test private categorization 88 | 89 | ```bash 90 | ./LayerTester --use_private --layers P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect 91 | 92 | 93 | 94 | 95 | { 96 | '_alpha' : ('roto_head', ), 97 | '_asset' : ('roto_head', 'puz_custom', ), 98 | '_prefix' : ('P', 'roto_head', 'puz_custom', ), 99 | '_sanitizable' : ('P', ), 100 | '_step' : ('P', ), 101 | '_vec3' : ('diffuse_direct', 'diffuse_albedo', 'puz_custom', 'specular_indirect', ), 102 | '_xyz' : ('P', ), 103 | 'all' : ('P', 'diffuse_direct', 'roto_head', 'diffuse_albedo', 'puz_custom', 'specular_indirect', ), 104 | } 105 | ``` 106 | ### test filtered categorization 107 | Following example take various layers but focusses on _non_color_ and _base_color_ categories 108 | 109 | ```bash 110 | ./LayerTester --layers P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect -c non_color base_color 111 | 112 | ----------------------------------------------- 113 | Layer names : 'P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect' 114 | CategorizeFilter : EXCLUDE 115 | Category names : 'non_color base_color' 116 | 117 | 118 | { 119 | 'all' : ('diffuse_direct', 'specular_indirect', ), 120 | 'beauty_shading' : ('diffuse_direct', 'specular_indirect', ), 121 | 'diffuse' : ('diffuse_direct', ), 122 | 'direct' : ('diffuse_direct', ), 123 | 'indirect' : ('specular_indirect', ), 124 | 'specular' : ('specular_indirect', ), 125 | } 126 | ----------------------------------------------- 127 | Layer names : 'P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect' 128 | CategorizeFilter : INCLUDE 129 | Category names : 'non_color base_color' 130 | 131 | 132 | { 133 | 'albedo' : ('diffuse_albedo', ), 134 | 'all' : ('P', 'roto_head', 'diffuse_albedo', 'puz_custom', ), 135 | 'base_color' : ('diffuse_albedo', ), 136 | 'diffuse' : ('diffuse_albedo', ), 137 | 'matte' : ('roto_head', ), 138 | 'non_color' : ('P', 'roto_head', 'puz_custom', ), 139 | 'p' : ('P', ), 140 | 'puz' : ('puz_custom', ), 141 | 'roto' : ('roto_head', ), 142 | } 143 | ----------------------------------------------- 144 | Layer names : 'P diffuse_direct roto_head diffuse_albedo puz_custom specular_indirect' 145 | CategorizeFilter : ONLY 146 | Category names : 'non_color base_color' 147 | 148 | 149 | { 150 | 'base_color' : ('diffuse_albedo', ), 151 | 'non_color' : ('P', 'roto_head', 'puz_custom', ), 152 | } 153 | ``` -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # LayerAlchemy CMake build file 2 | # For build instructions, consult README.md 3 | 4 | cmake_minimum_required(VERSION 2.8) 5 | include(BundleUtilities) 6 | include(ExternalProject) 7 | 8 | project(LayerAlchemy) 9 | 10 | # get the LayerAlchemy version info 11 | set(LAYER_ALCHEMY_VERSION_HEADER_FILE ${CMAKE_SOURCE_DIR}/include/version.h) 12 | file(READ ${LAYER_ALCHEMY_VERSION_HEADER_FILE} version) 13 | string(REGEX MATCH "#define LAYER_ALCHEMY_VERSION_MAJOR ([0-9]*)" _ ${version}) 14 | set(LAYER_ALCHEMY_VERSION_MAJOR ${CMAKE_MATCH_1}) 15 | string(REGEX MATCH "#define LAYER_ALCHEMY_VERSION_MINOR ([0-9]*)" _ ${version}) 16 | set(LAYER_ALCHEMY_VERSION_MINOR ${CMAKE_MATCH_1}) 17 | string(REGEX MATCH "#define LAYER_ALCHEMY_VERSION_PATCH ([0-9]*)" _ ${version}) 18 | set(LAYER_ALCHEMY_VERSION_PATCH ${CMAKE_MATCH_1}) 19 | set(LAYER_ALCHEMY_VERSION_STRING 20 | ${LAYER_ALCHEMY_VERSION_MAJOR}.${LAYER_ALCHEMY_VERSION_MINOR}.${LAYER_ALCHEMY_VERSION_PATCH} 21 | ) 22 | option(BUILD_APPS "Build binary applications" ON) 23 | option(BUILD_DOCS "Build html documentation" ON) 24 | option(BUILD_NUKE "Build the Nuke plugins" ON) 25 | option(VERBOSE "Add more verbosity to cmake" OFF) 26 | 27 | set(NUKE_ROOT "/opt/nuke/Nuke12.0v1" CACHE PATH "Path to Nuke install root") 28 | set(CMAKE_CXX_COMPILER g++) 29 | set(CMAKE_CXX_STANDARD 11) 30 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 31 | set(CMAKE_CXX_FLAGS "-msse -O3 -DYAML=LAYER_ALCHEMY_YAML") # avoid Nuke 12 OCIO yaml-cpp namespace clashing 32 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Tells CMake to add -fPic flag. 33 | 34 | if(UNIX AND NOT CYGWIN) 35 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ") 36 | endif() 37 | 38 | if(NOT CMAKE_BUILD_TYPE) 39 | set(CMAKE_BUILD_TYPE Release) 40 | endif() 41 | 42 | set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS}) 43 | 44 | # Placeholders for later, Nuke plugins can need this. 45 | #find_package(GLEW REQUIRED) 46 | #find_package(OpenGL REQUIRED) 47 | 48 | ExternalProject_Add(yaml_cpp 49 | GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git 50 | GIT_TAG yaml-cpp-0.6.2 51 | # YAML_CPP_BUILD_TESTS -> Don't build tests 52 | # CMAKE_POSITION_INDEPENDENT_CODE -> Tells CMake to add -fPic flag. 53 | # By default yaml-cpp uses fPic only when building in shared mode. 54 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/libs -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} -DYAML_CPP_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON 55 | PREFIX "${CMAKE_BINARY_DIR}/third-party/yaml_cpp" 56 | ) 57 | 58 | ExternalProject_Add(argparse 59 | # A simple C++ header only command line argument parser 60 | # Jesse Laning https://github.com/jamolnng 61 | GIT_REPOSITORY https://github.com/jamolnng/argparse.git 62 | GIT_TAG v0.1.0 63 | BUILD_COMMAND "" 64 | INSTALL_COMMAND "" 65 | CONFIGURE_COMMAND "" 66 | UPDATE_COMMAND "" # for cmake cache, 67 | PREFIX "${CMAKE_BINARY_DIR}/third-party/argparse" 68 | ) 69 | 70 | link_directories( 71 | ${CMAKE_CURRENT_BINARY_DIR}/libs/lib 72 | ) 73 | 74 | include_directories( 75 | ${CMAKE_SOURCE_DIR}/include 76 | ${CMAKE_CURRENT_BINARY_DIR}/libs/include 77 | ${CMAKE_BINARY_DIR}/third-party/argparse/src/argparse 78 | ) 79 | 80 | add_library(LayerSetConfig STATIC ${CMAKE_SOURCE_DIR}/src/LayerSetConfig.cpp) 81 | add_dependencies(LayerSetConfig yaml_cpp) 82 | target_link_libraries(LayerSetConfig PRIVATE yaml-cpp) 83 | set_target_properties(LayerSetConfig PROPERTIES PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/LayerSetConfig.h) 84 | list(APPEND LAYERSET_LIBS LayerSetConfig) 85 | 86 | add_library(LayerSetCore STATIC ${CMAKE_SOURCE_DIR}/src/LayerSetCore.cpp) 87 | add_dependencies(LayerSetCore LayerSetConfig) 88 | set_target_properties(LayerSetCore PROPERTIES PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/LayerSetCore.h) 89 | list(APPEND LAYERSET_LIBS LayerSetCore) 90 | 91 | install( 92 | TARGETS ${LAYERSET_LIBS} 93 | ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib 94 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include 95 | ) 96 | 97 | if(BUILD_APPS) 98 | add_executable(LayerTester ${CMAKE_SOURCE_DIR}/src/LayerTester.cpp) 99 | target_link_libraries(LayerTester LayerSetCore LayerSetConfig) 100 | add_dependencies(LayerTester argparse) 101 | 102 | add_executable(ConfigTester ${CMAKE_SOURCE_DIR}/src/ConfigTester.cpp) 103 | target_link_libraries(ConfigTester LayerSetCore LayerSetConfig) 104 | add_dependencies(ConfigTester argparse) 105 | install( 106 | TARGETS LayerTester ConfigTester 107 | RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin 108 | ) 109 | endif() 110 | 111 | file(GLOB YAML_CONFIGS "${CMAKE_SOURCE_DIR}/configs/*.y*ml") 112 | file(GLOB TEXT_FILES "${CMAKE_SOURCE_DIR}/*.md") 113 | 114 | install( 115 | FILES ${YAML_CONFIGS} 116 | DESTINATION ${CMAKE_INSTALL_PREFIX}/configs 117 | ) 118 | install( 119 | FILES ${TEXT_FILES} 120 | DESTINATION ${CMAKE_INSTALL_PREFIX}/ 121 | ) 122 | install( 123 | FILES ${LAYER_ALCHEMY_VERSION_HEADER_FILE} 124 | DESTINATION ${CMAKE_INSTALL_PREFIX}/include 125 | ) 126 | 127 | if(BUILD_NUKE) 128 | add_subdirectory(src/nuke) 129 | endif() 130 | 131 | if(BUILD_DOCS) 132 | add_subdirectory(documentation) 133 | endif() 134 | 135 | # Section for packaging the project 136 | 137 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 138 | set(platformName "osx") 139 | else() 140 | string(TOLOWER ${CMAKE_SYSTEM_NAME} platformName) 141 | endif() 142 | 143 | set(PROJECT_PACKAGE_NAME "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}-${platformName}") 144 | 145 | if(BUILD_NUKE) 146 | set(PROJECT_PACKAGE_NAME "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}-nuke-${NUKE_RELEASE}-${platformName}") 147 | endif() 148 | 149 | 150 | if (WIN32) 151 | set(PROJECT_PACKAGE_COMMAND "cfv" "${PROJECT_PACKAGE_NAME}.zip" --format=zip "${CMAKE_INSTALL_PREFIX}") 152 | else() 153 | set(PROJECT_PACKAGE_COMMAND "cfvz" "${PROJECT_PACKAGE_NAME}.tgz" "${CMAKE_INSTALL_PREFIX}") 154 | endif() 155 | 156 | add_custom_target(package 157 | COMMENT "creating package for ${PROJECT_PACKAGE_NAME}" 158 | COMMAND ${CMAKE_COMMAND} -E tar ${PROJECT_PACKAGE_COMMAND} 159 | DEPENDS ${CMAKE_INSTALL_PREFIX} # make sure the install directory exists 160 | DEPENDS documentation 161 | ) -------------------------------------------------------------------------------- /src/nuke/GradeLayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "LayerSet.h" 6 | #include "LayerSetKnob.h" 7 | 8 | namespace GradeLayerSet { 9 | 10 | using namespace DD::Image; 11 | 12 | const char* const HELP = 13 | "

This is the classic Grade node augmented to use Layer sets, operations are applied " 14 | "to every layer in the chosen set

" 15 | "

Applies a linear ramp followed by a gamma function to each color channel.

" 16 | "

A = multiply * (gain-lift)/(whitepoint-blackpoint)
" 17 | " B = offset + lift - A*blackpoint
" 18 | " output = pow(A*input + B, 1/gamma)

" 19 | "The reverse option is also provided so that you can copy-paste this node to " 20 | "invert the grade. This will do the opposite gamma correction followed by the " 21 | "opposite linear ramp."; 22 | 23 | class GradeLayerSet : public PixelIop { 24 | 25 | private: 26 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 27 | float blackpoint[4]{0.0f, 0.0f, 0.0f, 0.0f}; 28 | float whitepoint[4]{1.0f, 1.0f, 1.0f, 1.0f}; 29 | float lift[4]{0.0f, 0.0f, 0.0f, 0.0f}; 30 | float gain[4]{1.0f, 1.0f, 1.0f, 1.0f}; 31 | float offset[4]{0.0f, 0.0f, 0.0f, 0.0f}; 32 | float multiply[4]{1.0f, 1.0f, 1.0f, 1.0f}; 33 | float gamma[4]{1.0f, 1.0f, 1.0f, 1.0f}; 34 | bool reverse{false}; 35 | bool clampBlack{true}; 36 | bool clampWhite{false}; 37 | // intermediate grade algorithm storage 38 | float A[4]{0.0f, 0.0f, 0.0f, 0.0f}; 39 | float B[4]{0.0f, 0.0f, 0.0f, 0.0f}; 40 | float G[4]{0.0f, 0.0f, 0.0f, 0.0f}; 41 | 42 | public: 43 | void knobs(Knob_Callback); 44 | void _validate(bool); 45 | bool pass_transform() const {return true;} 46 | void in_channels(int, ChannelSet&) const; 47 | void pixel_engine(const Row&, int, int, int, ChannelMask, Row&); 48 | const char* Class() const {return description.name;} 49 | const char* node_help() const {return HELP;} 50 | static const Iop::Description description; 51 | GradeLayerSet(Node* node); 52 | ~GradeLayerSet(); 53 | // This function calculates and stores the grade algorithm's intermediate calculations 54 | bool precomputeValues(); 55 | // channel set that contains all channels that are modified by the node 56 | ChannelSet activeChannelSet() const {return ChannelSet(m_lsKnobData.m_selectedChannels);} 57 | }; 58 | 59 | GradeLayerSet::GradeLayerSet(Node* node) : PixelIop(node) {} 60 | 61 | static Op* build(Node* node) 62 | { 63 | return (new NukeWrapper(new GradeLayerSet(node)))->noChannels()->mixLuminance(); 64 | } 65 | GradeLayerSet::~GradeLayerSet() {} 66 | 67 | const Iop::Description GradeLayerSet::description( 68 | "GradeLayerSet", 69 | "LayerAlchemy/GradeLayerSet", 70 | build 71 | ); 72 | 73 | void GradeLayerSet::in_channels(int input, ChannelSet& mask) const {} 74 | 75 | void GradeLayerSet::_validate(bool for_real) 76 | { 77 | copy_info(); // this copies the input info to the output 78 | bool changeZero = precomputeValues(); 79 | info_.black_outside(!changeZero); 80 | ChannelSet inChannels = info_.channels(); 81 | 82 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels)) { 83 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels); 84 | } 85 | set_out_channels(activeChannelSet()); 86 | } 87 | 88 | bool GradeLayerSet::precomputeValues() { 89 | bool changeZero = false; 90 | for (unsigned int chanIdx = 0; chanIdx < 4; chanIdx++) { 91 | float a = whitepoint[chanIdx] - blackpoint[chanIdx]; 92 | a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f; 93 | a *= multiply[chanIdx]; 94 | float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a; 95 | float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]); 96 | A[chanIdx] = a; 97 | B[chanIdx] = b; 98 | G[chanIdx] = g; 99 | if (a != 1.0f || b != 0.0f || g != 1.0f) 100 | { 101 | if (b) 102 | { 103 | changeZero = true; 104 | } 105 | } 106 | } 107 | return changeZero; 108 | } 109 | 110 | void GradeLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask inChannels, Row& out) 111 | { 112 | Row aRow(x, r); 113 | ChannelSet channels = ChannelSet(inChannels); 114 | LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite); 115 | LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out); 116 | } 117 | 118 | void GradeLayerSet::knobs(Knob_Callback f) 119 | { 120 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 121 | LayerAlchemy::Knobs::createDocumentationButton(f); 122 | LayerAlchemy::Knobs::createColorKnobResetButton(f); 123 | LayerAlchemy::Knobs::createVersionTextKnob(f); 124 | Divider(f, 0); // separates layer set knobs from the rest 125 | 126 | AColor_knob(f, blackpoint, IRange(-1, 1), "blackpoint"); 127 | Tooltip(f, "This color is turned into black"); 128 | AColor_knob(f, whitepoint, IRange(0, 4), "whitepoint"); 129 | Tooltip(f, "This color is turned into white"); 130 | AColor_knob(f, lift, IRange(-1, 1), "lift", "lift"); 131 | Tooltip(f, "Black is turned into this color"); 132 | AColor_knob(f, gain, IRange(0, 4), "gain", "gain"); 133 | Tooltip(f, "White is turned into this color"); 134 | AColor_knob(f, multiply, IRange(0, 4), "multiply"); 135 | Tooltip(f, "Constant to multiply result by"); 136 | AColor_knob(f, offset, IRange(-1, 1), "offset", "offset"); 137 | Tooltip(f, "Constant to offset to result (raises both black & white, unlike lift)"); 138 | AColor_knob(f, gamma, IRange(.2, 5), "gamma"); 139 | Tooltip(f, "Gamma correction applied to final result"); 140 | Newline(f, " "); 141 | Bool_knob(f, &reverse, "reverse"); 142 | Tooltip(f, "Invert the math to undo the correction"); 143 | Bool_knob(f, &clampBlack, "clampBlack", "black clamp"); 144 | Tooltip(f, "Output that is less than zero is changed to zero"); 145 | Bool_knob(f, &clampWhite, "clampWhite", "white clamp"); 146 | Tooltip(f, "Output that is greater than 1 is changed to 1"); 147 | } 148 | } // End namespace GradeLayerSet 149 | -------------------------------------------------------------------------------- /documentation/docs/GradeBeauty.md: -------------------------------------------------------------------------------- 1 | # GradeBeauty 2 | 3 | !!! info "" 4 | 5 | GradeBeauty provides artist friendly controls to manipulate multichannel cg render passes 6 | The GradeBeauty design is purposely utilitarian and simple. 7 | 8 | 9 | ## Features 10 | - ***Dynamic reconfiguration*** 11 | * based on layers found connected to its input 12 | * categorization of found layer names based on what is defined in the configuration files 13 | - ***Color knob change behavior*** 14 | * reflected on the related multichannel layers 15 | * the beauty pass is simultaneously rebuilt with the modified layers 16 | 17 | | "beauty shading" collapsed | "beauty shading" uncollapsed | 18 | | -------------------------- | ---------------------------- | 19 | |![GradeBeauty](media/parameters/GradeBeauty.png)|![GradeBeauty detail](media/parameters/GradeBeauty_uncollapsed.png)| 20 | 21 | ## Workflow ideas, tips, suggestions 22 | 23 | Lighters can use it with the light_group [LayerSet](core.md#layersets) to : 24 | 25 | * Tweak light group values or animations, (flicker matching) 26 | * Use two nodes, one in _multiply_ mode, and one in _stops_ mode to mimic to the exposure/gain decoupling 27 | * Send the values back to their cg application ! 28 | 29 | Compositors: 30 | 31 | * Same as above for the lighters, but also share settings easily across multiple shots/artists. 32 | * Cloning the node for global control, if that's possible. 33 | * Use roto/cryptomattes to polish, fix, or address client notes quickly without re-rendering 34 | * If using _beauty_shading_ style, try to use the main controls over individual aovs, unless you have a specific need to (like zeroing it out) 35 | 36 | 37 | ## Knob reference 38 | 39 | | knob name | type | what it does | 40 | | --------- | ---- | ------------ | 41 | | layer_set | enumeration | decides which beauty [LayerSet](core.md#layersets) method to use, only found sets will be visible | 42 | | target_layer | enumeration | selects which layer to pre-subtract layers from (if enabled) and add the modified layers to | 43 | | math_type | enumeration | selects the color knob preference 44 | | subtract | bool | controls pre-subtracting the [LayerSet](core.md#layersets) from the target layer | 45 | | black_clamp | bool | clamp negative values from all output layers | 46 | | reset values | button | resets all color knobs to their defaults | 47 | 48 | ## [PixelIop](https://learn.foundry.com/nuke/developers/11.3/ndkdevguide/2d/pixeliops.html) Knobs 49 | | knob name | type | what it does | 50 | | --------- | ---- | ------------ | 51 | | maskChannelInput | bool/ChannelKnob | selects which masking channel to use | 52 | | unpremult | bool/ChannelKnob | unpremults each relevant layer by this channel | 53 | | mix | float | mixes in input with the result | 54 | 55 | ## Knob value detail 56 | ### math_type 57 | 58 | | math mode | what it does | value processing explained | notes | 59 | | --------- | ------------ | -------------------------- | ----- | 60 | | multiply | acts like a chain of multiply nodes |master * all global contributions (if any) * layer | _since the layer is multiplied last, you can also use this mode to disable the layer_ 61 | | stops | same math as the Nuke exposure node for each layer | it adds all contributions and then does the exposure conversion | _if master is set to 1.0 and the layer is set to 0.0, this means (1.0 + 0 .0) = one stop over_ 62 | 63 | ### subtract 64 | The purpose of the subtract knob is to make sure that the output will always match the beauty render, even if 65 | some aov layers are missing in the beauty render. 66 | 67 | This can happen, so it is enabled by default. 68 | 69 | | value | what it does | notes | 70 | | ----- | ------------ | ----- | 71 | | enabled | the additive sum of the chosen [LayerSet](core.md#layersets) is subtracted from the target layer before recombining with this node's modifications |

this means any difference between the target layer and the render layers is kept in the final output

72 | | disabled | bypasses aov/target layer pre-subtraction | _when this is disabled, it replaces the target layer with the result_ 73 | 74 | !!! info "" 75 | 76 | If enabled and you completely remove layers, it's possible that you get negative values when 77 | completely removing aovs AND subtracting. In this scenario, enable black_clamp 78 | 79 | ### black_clamp 80 | The purpose of the black_clamp knob is to make sure that the output pixels always are alaways positive. 81 | 82 | | value | what it does | notes | 83 | | ----- | ------------ | ----- | 84 | | enabled | no negative values are passed to the output |

in the odd case that you need to stop negative values this will do the job

85 | | disabled | bypasses aov/target layer negative clamping | 86 | 87 | 88 | ## Arnold 5 LayerSets 89 | 90 | LayerAlchemy includes arnold configurations by default 91 | 92 | Arnold can separate the beauty render components in various ways. 93 | 94 | The following type are natively supported with the [default](https://docs.arnoldrenderer.com/display/A5AFMUG/AOVs#AOVs-AOVs) aov naming : 95 | 96 | | layer set name | contents | 97 | | -------------- | --------------- | 98 | | _beauty_direct_indirect_ | direct + indirect illumination layers only | 99 | | _beauty_shading_global_ | shader components like the result of specular, diffuse, coat | 100 | | _beauty_shading_ | this is a mix of the preceding, except that for each global, there are both direct and indirect aovs. For example, the Color Knob for direct affects all direct aovs | 101 | | _light_group_ | recombine using seperated light contributions only _(control over shading is lost)_ | 102 | 103 | If a multichannel input contains any aov belonging to any one of these [LayerSets](core.md#layersets): 104 | 105 | * They will become available in the layer_set knob 106 | * GradeBeauty will configure itself to reconstruct the beauty with the chosen [LayerSet](core.md#layersets) 107 | * _If a multichannel exr has **light_group** and **beauty** aovs, **both** LayerSets will be available_ 108 | 109 | ## Modifying configurations 110 | 111 | Your workflow can be different from what is included with this version. 112 | 113 | * For cg layer names, the default configuration is Arnold 5 stock layer names only 114 | * The configurations can be adapted to any additive type renderer 115 | * To change this, you must simply modify the configuration files accordingly 116 | * _please read [channels and topology](configs_and_topology.md) if you want to know more, or need to tweak 117 | them_ 118 | -------------------------------------------------------------------------------- /src/nuke/FlattenLayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "LayerSet.h" 6 | #include "LayerSetKnob.h" 7 | 8 | 9 | namespace FlattenLayerSet { 10 | 11 | using namespace DD::Image; 12 | 13 | static const char* const HELP = 14 | "

This PixelIop manipulates pixel values of a target layer by adding layers" 15 | " from a layer set in the incoming image stream.

" 16 | ; 17 | 18 | // exclude non color layers, makes no sense for this node 19 | static const StrVecType categoryExcludeFilterList = {"non_color", "base_color", "albedo"}; 20 | static const CategorizeFilter excludeLayerFilter(categoryExcludeFilterList, CategorizeFilter::modes::EXCLUDE); 21 | 22 | static const StrVecType categoryFilterList = 23 | { 24 | "beauty_direct_indirect", "beauty_shading_global", "light_group", "beauty_shading" 25 | }; 26 | static const CategorizeFilter layerFilter(categoryFilterList, CategorizeFilter::modes::INCLUDE); 27 | 28 | enum operationModes { 29 | COPY = 0, ADD, REMOVE 30 | }; 31 | 32 | static const char* const operationNames[] = { 33 | "copy", "add", "remove", 0 34 | }; 35 | 36 | class FlattenLayerSet : public PixelIop { 37 | 38 | private: 39 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 40 | ChannelSet m_targetLayer {Mask_RGB}; 41 | int m_operation; 42 | 43 | public: 44 | void knobs(Knob_Callback); 45 | void _validate(bool); 46 | bool pass_transform() const {return true;} 47 | void in_channels(int, ChannelSet&) const; 48 | void pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out); 49 | const char* Class() const {return description.name;} 50 | const char* node_help() const {return HELP;} 51 | static const Iop::Description description; 52 | FlattenLayerSet(Node* node); 53 | ~FlattenLayerSet(); 54 | // channel set that contains all channels that are modified by the node 55 | ChannelSet activeChannelSet() const; 56 | }; 57 | 58 | FlattenLayerSet::FlattenLayerSet(Node* node) : PixelIop(node) 59 | { 60 | m_operation = operationModes::COPY; 61 | m_targetLayer = Mask_RGB; 62 | } 63 | 64 | static Op* build(Node* node) 65 | { 66 | return (new FlattenLayerSet(node)); 67 | } 68 | 69 | FlattenLayerSet::~FlattenLayerSet() {} 70 | 71 | const Iop::Description FlattenLayerSet::description( 72 | "FlattenLayerSet", 73 | "LayerAlchemy/FlattenLayerSet", 74 | build 75 | ); 76 | 77 | void FlattenLayerSet::in_channels(int input_number, ChannelSet& mask) const 78 | { 79 | mask += ChannelMask(activeChannelSet()); 80 | } 81 | 82 | ChannelSet FlattenLayerSet::activeChannelSet() const 83 | { 84 | ChannelSet outChans; 85 | foreach(channel, ChannelSet(m_targetLayer + m_lsKnobData.m_selectedChannels)) 86 | { 87 | if (colourIndex(channel) <= 2) 88 | { 89 | outChans += channel; 90 | } 91 | } 92 | return outChans; 93 | } 94 | 95 | void FlattenLayerSet::_validate(bool for_real) 96 | { 97 | copy_info(); // this copies the input info to the output 98 | ChannelSet inChannels = info_.channels(); 99 | LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2); 100 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, excludeLayerFilter)) 101 | { 102 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, excludeLayerFilter); 103 | } 104 | set_out_channels(activeChannelSet()); 105 | info_.turn_on(m_targetLayer); 106 | } 107 | 108 | void FlattenLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) 109 | { 110 | ChannelSet inChannels = ChannelSet(channels); 111 | ChannelSet activeChannels = activeChannelSet(); 112 | bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size(); 113 | if (!isTargetLayer) 114 | { 115 | return; 116 | } 117 | Row aRow(x, r); 118 | ChannelSet bty = m_targetLayer.intersection(inChannels); 119 | ChannelSet aovs = activeChannels - bty; 120 | 121 | map btyPtrIdxMap; 122 | map aovInPtrIdxMap; 123 | 124 | foreach(channel, bty) { 125 | unsigned chanIdx = colourIndex(channel); 126 | float* rowBtyChan; 127 | if (m_operation != operationModes(COPY)) 128 | { 129 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 130 | rowBtyChan = aRow.writable(channel); 131 | } else 132 | { 133 | rowBtyChan = aRow.writableConstant(0.0f, channel); 134 | } 135 | btyPtrIdxMap[chanIdx] = rowBtyChan; 136 | } 137 | 138 | LayerAlchemy::Utilities::hard_copy(in, x, r, aovs, aRow); 139 | 140 | for (const auto& kvp : btyPtrIdxMap) 141 | { 142 | unsigned btyChanIdx = kvp.first; 143 | float* aRowBty = kvp.second; 144 | 145 | foreach(aov, aovs) 146 | { 147 | if (btyChanIdx != colourIndex(aov)) 148 | { 149 | continue; 150 | } 151 | const float* inAovChan = aRow[aov]; 152 | 153 | for (int X = x; X < r; X++) 154 | { 155 | float aovPixel = inAovChan[X]; 156 | float btyPixel = aRowBty[X]; 157 | 158 | if (m_operation == operationModes(REMOVE)) { 159 | aRowBty[X] -= aovPixel; 160 | } else { 161 | aRowBty[X] += aovPixel; 162 | } 163 | } 164 | } 165 | } 166 | LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out); 167 | } 168 | 169 | void FlattenLayerSet::knobs(Knob_Callback f) 170 | { 171 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 172 | LayerAlchemy::Knobs::createDocumentationButton(f); 173 | LayerAlchemy::Knobs::createVersionTextKnob(f); 174 | 175 | Divider(f, 0); // separates layer set knobs from the rest 176 | 177 | Input_ChannelMask_knob(f, &m_targetLayer, 0, "target layer"); 178 | Tooltip(f, "

Selects which layer to output the processing to

"); 179 | Enumeration_knob(f, &m_operation, operationNames, "operation", "operation"); 180 | Tooltip(f, "

Selects a type of processing

" 181 | "copy : copies the added layers in the layer set to the target layer\n" 182 | "add : add the added layers in the layer set to the target layer\n" 183 | "remove : subtracts the added layers in the layer set to the target layer"); 184 | } 185 | } // End namespace FlattenLayerSet -------------------------------------------------------------------------------- /src/nuke/LayerSetKnob.cpp: -------------------------------------------------------------------------------- 1 | #include "LayerSetKnob.h" 2 | 3 | namespace LayerAlchemy { 4 | namespace LayerSetKnob { 5 | 6 | LayerSetKnobData::LayerSetKnobData() 7 | { 8 | //printf("created LayerSetKnobData %p\n", (void*) this); 9 | } 10 | 11 | LayerSetKnobData::~LayerSetKnobData() 12 | { 13 | //printf("destroyed LayerSetKnobData %p\n", (void*) this); 14 | } 15 | 16 | DD::Image::Knob* LayerSetKnob(DD::Image::Knob_Callback& f, LayerSetKnobData& pLayerSetKnobData) 17 | { 18 | DD::Image::Knob* layerSetKnob = DD::Image::Enumeration_knob( 19 | f, &pLayerSetKnobData.selectedLayerSetIndex, pLayerSetKnobData.items, LAYER_SET_KNOB_NAME, "layer set"); 20 | Tooltip(f, "This selects a specific layer set for processing in this node"); 21 | SetFlags(f, DD::Image::Knob::SAVE_MENU); 22 | SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); 23 | SetFlags(f, DD::Image::Knob::EXPAND_TO_CONTENTS); 24 | SetFlags(f, DD::Image::Knob::STARTLINE); 25 | return layerSetKnob; 26 | } 27 | 28 | string getLayerSetKnobEnumString(DD::Image::Op* t_op) 29 | { 30 | return t_op->knob(LAYER_SET_KNOB_NAME)->enumerationKnob()->getSelectedItemString(); 31 | } 32 | 33 | void populateLayerSetKnobEnum(DD::Image::Op* t_op, std::vector& items) 34 | { 35 | t_op->knob(LAYER_SET_KNOB_NAME)->enumerationKnob()->menu(items); 36 | } 37 | 38 | void _updateLayerSetKnobEnum(DD::Image::Op* t_op, LayerSetKnobData& layerSetKnobData, ChannelSetMapType& channelSetLayerMap, const DD::Image::ChannelSet& inChannels) 39 | { 40 | DD::Image::Knob* layerSetKnob = t_op->knob(LAYER_SET_KNOB_NAME); 41 | DD::Image::Enumeration_KnobI* layerSetEnumKnob = layerSetKnob->enumerationKnob(); 42 | int selectedIndex = layerSetEnumKnob->getSelectedItemIndex(); 43 | StrVecType newCategories = LayerAlchemy::LayerSet::getCategories(channelSetLayerMap); 44 | StrVecType currentCategories = layerSetEnumKnob->menu(); 45 | string layerSetName = layerSetEnumKnob->getSelectedItemString(); 46 | 47 | int indexCurrentItem = std::find(newCategories.begin(), newCategories.end(), layerSetEnumKnob->getSelectedItemString()) - newCategories.begin(); 48 | int categoryAmount = newCategories.size(); 49 | if (categoryAmount == 0) { 50 | StrVecType blank = {""}; 51 | layerSetEnumKnob->menu(blank); 52 | layerSetKnob->set_value(0); 53 | return; 54 | } else if (indexCurrentItem < categoryAmount) { 55 | selectedIndex = indexCurrentItem; 56 | } else if (selectedIndex >= categoryAmount) { 57 | selectedIndex = 0; 58 | } 59 | layerSetEnumKnob->menu(newCategories); 60 | layerSetKnob->set_value(selectedIndex); 61 | 62 | layerSetKnobData.m_selectedChannels.clear(); 63 | layerSetKnobData.m_selectedChannels += channelSetLayerMap[layerSetName]; 64 | layerSetKnobData.categoryName = layerSetName; 65 | layerSetKnobData.m_allChannels = inChannels; 66 | //printf("_updateLayerSetKnobEnum categorized %s\n", layerSetName.c_str()); 67 | } 68 | void updateLayerSetKnob(DD::Image::Op* t_op, LayerSetKnobData& layerSetKnobData, LayerCollection& collection, DD::Image::ChannelSet& inChannels) 69 | { 70 | if (!inChannels.empty()) 71 | { 72 | ChannelSetMapType channelSetLayerMap = LayerAlchemy::LayerSet::categorizeChannelSet(collection, inChannels); 73 | _updateLayerSetKnobEnum(t_op, layerSetKnobData, channelSetLayerMap, inChannels); 74 | } 75 | } 76 | void updateLayerSetKnob(DD::Image::Op* t_op, LayerSetKnobData& layerSetKnobData, LayerCollection& collection, DD::Image::ChannelSet& inChannels, const CategorizeFilter& categorizeFilter) 77 | { 78 | if (!inChannels.empty()) 79 | { 80 | ChannelSetMapType channelSetLayerMap = LayerAlchemy::LayerSet::categorizeChannelSet(collection, inChannels, categorizeFilter); 81 | _updateLayerSetKnobEnum(t_op, layerSetKnobData, channelSetLayerMap, inChannels); 82 | } 83 | } 84 | 85 | bool _basicValidateLayerSetKnobUpdate(DD::Image::Op* t_op, const LayerSetKnobData& layerSetKnobData, const DD::Image::ChannelSet& inChannels) 86 | { 87 | string currentLayerName = getLayerSetKnobEnumString(t_op); 88 | //printf("validateLayerSetKnobUpdate categorizeFilter : %s\n", currentLayerName.c_str()); 89 | 90 | if (inChannels == DD::Image::Chan_Black || inChannels.empty()) 91 | { 92 | // printf("validateLayerSetKnobUpdate categorizeFilter : empty input channels\n"); 93 | return false; 94 | } 95 | if (layerSetKnobData.m_allChannels.empty() || layerSetKnobData.m_allChannels != inChannels) 96 | { 97 | // printf("validateLayerSetKnobUpdate categorizeFilter : different input channelset or m_allChannels empty=%d\n", layerSetKnobData.m_allChannels.empty()); 98 | return true; 99 | } 100 | else if (currentLayerName != layerSetKnobData.categoryName) 101 | { 102 | // printf("validateLayerSetKnobUpdate categorizeFilter : category changed\n"); 103 | return true; 104 | } 105 | else if (currentLayerName == layerSetKnobData.categoryName) 106 | { 107 | // printf("validateLayerSetKnobUpdate categorizeFilter : category is the same\n"); 108 | return false; 109 | } 110 | else 111 | { 112 | return false; 113 | } 114 | 115 | } 116 | bool _categorizedValidateLayerSetKnobUpdate(DD::Image::Op* t_op, const LayerMap& categorized, const string& currentLayerSetName) 117 | { 118 | if (categorized.empty()) 119 | { 120 | t_op->error("can't find any relevant layer set for this node"); 121 | return false; 122 | } 123 | else if (currentLayerSetName == "") 124 | { 125 | return true; 126 | } 127 | else if (!categorized.contains(currentLayerSetName)) 128 | { 129 | t_op->error("input is missing the '%s' layer set", currentLayerSetName.c_str()); 130 | return false; 131 | } 132 | else 133 | { 134 | return true; 135 | } 136 | 137 | } 138 | 139 | bool validateLayerSetKnobUpdate(DD::Image::Op* t_op, const LayerSetKnobData& layerSetKnobData, LayerCollection& layerCollection, const DD::Image::ChannelSet& inChannels) 140 | { 141 | string currentLayerSetName = getLayerSetKnobEnumString(t_op); 142 | if (!_basicValidateLayerSetKnobUpdate(t_op, layerSetKnobData, inChannels)) { 143 | return false; 144 | } 145 | StrVecType inLayers = LayerSet::getLayerNames(inChannels); 146 | LayerMap categorized = layerCollection.categorizeLayers(LayerSet::getLayerNames(inChannels), categorizeType::pub); 147 | return _categorizedValidateLayerSetKnobUpdate(t_op, categorized, currentLayerSetName); 148 | } 149 | 150 | bool validateLayerSetKnobUpdate(DD::Image::Op* t_op, const LayerSetKnobData& layerSetKnobData, LayerCollection& layerCollection, const DD::Image::ChannelSet& inChannels, const CategorizeFilter& categorizeFilter) 151 | { 152 | string currentLayerSetName = getLayerSetKnobEnumString(t_op); 153 | if (!_basicValidateLayerSetKnobUpdate(t_op, layerSetKnobData, inChannels)) { 154 | return false; 155 | } 156 | StrVecType inLayers = LayerSet::getLayerNames(inChannels); 157 | LayerMap categorized = layerCollection.categorizeLayers(inLayers, categorizeType::pub, categorizeFilter); 158 | return _categorizedValidateLayerSetKnobUpdate(t_op, categorized, currentLayerSetName); 159 | } 160 | } // LayerSetKnob 161 | } // LayerAlchemy 162 | -------------------------------------------------------------------------------- /documentation/theme/slate/css/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | } 4 | 5 | ul.nav .main { 6 | font-weight: bold; 7 | } 8 | 9 | .col-md-3 { 10 | padding-left: 0; 11 | } 12 | 13 | .col-md-9 { 14 | padding-bottom: 100px; 15 | } 16 | 17 | .source-links { 18 | float: right; 19 | } 20 | 21 | .col-md-9 img { 22 | max-width: 100%; 23 | } 24 | 25 | /* 26 | * The code below adds some padding to the top of the current anchor target so 27 | * that, when navigating to it, the header isn't hidden by the navbar at the 28 | * top. This is especially complicated because we want to *remove* the padding 29 | * after navigation so that hovering over the header shows the permalink icon 30 | * correctly. Thus, we create a CSS animation to remove the extra padding after 31 | * a second. We have two animations so that navigating to an anchor within the 32 | * page always restarts the animation. 33 | * 34 | * See for more details. 35 | */ 36 | :target::before { 37 | content: ""; 38 | display: block; 39 | margin-top: -75px; 40 | height: 75px; 41 | pointer-events: none; 42 | animation: 0s 1s forwards collapse-anchor-padding-1; 43 | } 44 | 45 | .clicky :target::before { 46 | animation-name: collapse-anchor-padding-2; 47 | } 48 | 49 | @keyframes collapse-anchor-padding-1 { 50 | to { 51 | margin-top: 0; 52 | height: 0; 53 | } 54 | } 55 | 56 | @keyframes collapse-anchor-padding-2 { 57 | to { 58 | margin-top: 0; 59 | height: 0; 60 | } 61 | } 62 | 63 | pre, code { 64 | background: #1c1e22; 65 | border: solid 1px #0c0d0e; 66 | color: #c8c8c8; 67 | } 68 | 69 | code { 70 | padding: 1px 3px; 71 | } 72 | 73 | pre code { 74 | background: transparent; 75 | border: none; 76 | } 77 | 78 | a code { 79 | color: #fff; 80 | } 81 | 82 | kbd { 83 | padding: 2px 4px; 84 | font-size: 90%; 85 | color: #fff; 86 | background-color: #333; 87 | border-radius: 3px; 88 | -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 89 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 90 | } 91 | 92 | footer { 93 | margin-top: 30px; 94 | margin-bottom: 10px; 95 | text-align: center; 96 | font-weight: 200; 97 | } 98 | 99 | .modal-dialog { 100 | margin-top: 60px; 101 | } 102 | 103 | /* Style the admonitions. */ 104 | 105 | .admonition { 106 | margin-bottom: 20px; 107 | border: 1px solid #000; 108 | border-radius: 4px; 109 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 110 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 111 | background-color: #2e3338; 112 | } 113 | 114 | .admonition > .admonition-title { 115 | margin: 0; 116 | padding: 10px 15px; 117 | border-bottom: 1px solid #000; 118 | border-top-left-radius: 3px; 119 | border-top-right-radius: 3px; 120 | color: #fff; 121 | background-color: #3e444c; 122 | font-size: 16px; 123 | font-weight: 500; 124 | line-height: 1.2; 125 | } 126 | 127 | .admonition > * { 128 | margin-left: 15px; 129 | margin-right: 15px; 130 | } 131 | 132 | .admonition > *:not(.admonition-title):first-of-type, 133 | .admonition > .admonition-title + * { 134 | margin-top: 15px; 135 | } 136 | 137 | .admonition > *:last-child { 138 | margin-bottom: 15px; 139 | } 140 | 141 | /* Style each kind of admonition. */ 142 | 143 | .admonition.note > .admonition-title { 144 | background-color: #5bc0de; 145 | } 146 | 147 | .admonition.warning > .admonition-title { 148 | background-color: #f89406; 149 | } 150 | 151 | .admonition.danger > .admonition-title { 152 | background-color: #ee5f5b; 153 | } 154 | 155 | /* 156 | * Side navigation 157 | * 158 | * Scrollspy and affixed enhanced navigation to highlight sections and secondary 159 | * sections of docs content. 160 | */ 161 | 162 | /* By default it's not affixed in mobile views, so undo that */ 163 | .bs-sidebar.affix { 164 | position: static; 165 | } 166 | 167 | .bs-sidebar.well { 168 | padding: 0; 169 | } 170 | 171 | /* First level of nav */ 172 | .bs-sidenav { 173 | margin-top: 30px; 174 | margin-bottom: 30px; 175 | padding-top: 10px; 176 | padding-bottom: 10px; 177 | border-radius: 5px; 178 | } 179 | 180 | /* All levels of nav */ 181 | .bs-sidebar .nav > li > a { 182 | display: block; 183 | padding: 5px 20px; 184 | z-index: 1; 185 | } 186 | .bs-sidebar .nav > li > a:hover, 187 | .bs-sidebar .nav > li > a:focus { 188 | text-decoration: none; 189 | border-right: 1px solid; 190 | } 191 | .bs-sidebar .nav > .active > a, 192 | .bs-sidebar .nav > .active:hover > a, 193 | .bs-sidebar .nav > .active:focus > a { 194 | font-weight: bold; 195 | background-color: transparent; 196 | border-right: 1px solid; 197 | } 198 | 199 | /* Nav: second level (shown on .active) */ 200 | .bs-sidebar .nav .nav { 201 | display: none; /* Hide by default, but at >768px, show it */ 202 | margin-bottom: 8px; 203 | } 204 | .bs-sidebar .nav .nav > li > a { 205 | padding-top: 3px; 206 | padding-bottom: 3px; 207 | padding-left: 30px; 208 | font-size: 90%; 209 | } 210 | 211 | /* Show and affix the side nav when space allows it */ 212 | @media (min-width: 992px) { 213 | .bs-sidebar .nav > .active > ul { 214 | display: block; 215 | } 216 | /* Widen the fixed sidebar */ 217 | .bs-sidebar.affix, 218 | .bs-sidebar.affix-bottom { 219 | width: 213px; 220 | } 221 | .bs-sidebar.affix { 222 | position: fixed; /* Undo the static from mobile first approach */ 223 | top: 80px; 224 | } 225 | .bs-sidebar.affix-bottom { 226 | position: absolute; /* Undo the static from mobile first approach */ 227 | } 228 | .bs-sidebar.affix-bottom .bs-sidenav, 229 | .bs-sidebar.affix .bs-sidenav { 230 | margin-top: 0; 231 | margin-bottom: 0; 232 | } 233 | } 234 | @media (min-width: 1200px) { 235 | /* Widen the fixed sidebar again */ 236 | .bs-sidebar.affix-bottom, 237 | .bs-sidebar.affix { 238 | width: 263px; 239 | } 240 | } 241 | 242 | .headerlink { 243 | display: none; 244 | padding-left: .5em; 245 | } 246 | 247 | h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink, h4:hover .headerlink, h5:hover .headerlink, h6:hover .headerlink{ 248 | display:inline-block; 249 | } 250 | 251 | /* display submenu relative to parent*/ 252 | .dropdown-submenu { 253 | position: relative; 254 | } 255 | 256 | /* sub menu stlye */ 257 | .dropdown-submenu>.dropdown-menu { 258 | top: 0; 259 | left: 100%; 260 | margin-top: 0px; 261 | margin-left: -1px; 262 | -webkit-border-radius: 0 4px 4px 4px; 263 | -moz-border-radius: 0 4px 4px; 264 | border-radius: 0 4px 4px 4px; 265 | } 266 | 267 | /* display sub menu on hover*/ 268 | .dropdown-submenu:hover>.dropdown-menu { 269 | display: block; 270 | } 271 | 272 | /* little arrow */ 273 | .dropdown-submenu>a:after { 274 | display: block; 275 | content: " "; 276 | float: right; 277 | width: 0; 278 | height: 0; 279 | border-color: transparent; 280 | border-style: solid; 281 | border-width: 5px 0 5px 5px; 282 | border-left-color: #999999; 283 | margin-top: 5px; 284 | margin-right: -10px; 285 | } 286 | 287 | /* little arrow of parent menu */ 288 | .dropdown-submenu:hover>a:after { 289 | border-left-color: #fff; 290 | } 291 | -------------------------------------------------------------------------------- /configs/layers.yaml: -------------------------------------------------------------------------------- 1 | _alpha: 2 | - AA_inv_density 3 | - Z 4 | - Z2 5 | - cputime 6 | - depth 7 | - m 8 | - mask 9 | - mask_planartrack 10 | - mask_splinewarp 11 | - matte 12 | - raycount 13 | - roto 14 | - rotopaint_mask 15 | - volume_z 16 | _asset: 17 | - Pref 18 | - motion 19 | - mp 20 | - mptd 21 | - puz 22 | - roto 23 | - rotopaint 24 | _facility_exr_package_names: 25 | - deep_scanline 26 | - multipart 27 | _global: 28 | - diffuse 29 | - direct 30 | - indirect 31 | - mp 32 | - mptd 33 | - specular 34 | - sss 35 | - transmission 36 | - volume 37 | _grain_layer: 38 | - grain 39 | _prefix: 40 | - P 41 | - Pref 42 | - id 43 | - m 44 | - matte 45 | - motion 46 | - mp 47 | - mptd 48 | - puz 49 | - roto 50 | - rotopaint 51 | - rotopaint_mask 52 | - uv 53 | _primary: 54 | - beauty 55 | - primary 56 | - rgb 57 | - rgba 58 | _primary_layer: 59 | - rgba 60 | _rotopaint_degrain_layer: 61 | - rotopaint_degrain 62 | _rotopaint_grain_layer: 63 | - rotopaint_grain 64 | _sanitizable: 65 | - P 66 | - uv 67 | _step: 68 | - P 69 | - uv 70 | _uv: 71 | - motion 72 | - motionvector 73 | - uv 74 | _vec2: 75 | - backward 76 | - forward 77 | - motion 78 | - uv_extra 79 | _vec3: 80 | - ID 81 | - albedo 82 | - background 83 | - coat 84 | - coat_albedo 85 | - coat_direct 86 | - coat_indirect 87 | - diffuse 88 | - diffuse_albedo 89 | - diffuse_direct 90 | - diffuse_indirect 91 | - direct 92 | - emission 93 | - id 94 | - indirect 95 | - light_group_1 96 | - light_group_2 97 | - light_group_3 98 | - light_group_4 99 | - light_group_5 100 | - light_group_6 101 | - light_group_7 102 | - light_group_8 103 | - light_group_9 104 | - light_group_10 105 | - light_group_11 106 | - light_group_12 107 | - light_group_13 108 | - light_group_14 109 | - light_group_15 110 | - light_group_16 111 | - opacity 112 | - puz 113 | - reflection 114 | - refraction 115 | - shadow 116 | - shadow_diff 117 | - sheen 118 | - sheen_direct 119 | - sheen_indirect 120 | - single_scatter 121 | - specular 122 | - specular_albedo 123 | - specular_direct 124 | - specular_indirect 125 | - sss 126 | - sss_albedo 127 | - sss_direct 128 | - sss_indirect 129 | - transmission 130 | - transmission_albedo 131 | - transmission_direct 132 | - transmission_indirect 133 | - volume 134 | - volume_albedo 135 | - volume_direct 136 | - volume_indirect 137 | - volume_opacity 138 | _vec4: 139 | - beauty 140 | - crypto_asset 141 | - crypto_asset00 142 | - crypto_asset01 143 | - crypto_asset02 144 | - crypto_material 145 | - crypto_material00 146 | - crypto_material01 147 | - crypto_material02 148 | - crypto_object 149 | - crypto_object00 150 | - crypto_object01 151 | - crypto_object02 152 | - mp 153 | - mptd 154 | - platepremult 155 | - primary 156 | - rgba 157 | _xyz: 158 | - N 159 | - P 160 | - Pref 161 | _z: 162 | - depth 163 | albedo: 164 | - albedo 165 | - coat_albedo 166 | - diffuse_albedo 167 | - specular_albedo 168 | - sss_albedo 169 | - transmission_albedo 170 | - volume_albedo 171 | arnold_debug: 172 | - AA_inv_density 173 | - cputime 174 | - raycount 175 | base_color: 176 | - diffuse_albedo 177 | beauty_direct_indirect: 178 | - direct 179 | - indirect 180 | beauty_shading: 181 | - coat_direct 182 | - coat_indirect 183 | - diffuse_direct 184 | - diffuse_indirect 185 | - emission 186 | - reflection 187 | - refraction 188 | - sheen_direct 189 | - sheen_indirect 190 | - single_scatter 191 | - specular_direct 192 | - specular_indirect 193 | - sss_direct 194 | - sss_indirect 195 | - transmission_direct 196 | - transmission_indirect 197 | - volume_direct 198 | - volume_indirect 199 | beauty_shading_global: 200 | - background 201 | - diffuse 202 | - sheen 203 | - specular 204 | - sss 205 | - transmission 206 | - volume 207 | coat: 208 | - coat 209 | - coat_albedo 210 | - coat_direct 211 | - coat_indirect 212 | crypto: 213 | - crypto_asset 214 | - crypto_asset00 215 | - crypto_asset01 216 | - crypto_asset02 217 | - crypto_material 218 | - crypto_material00 219 | - crypto_material01 220 | - crypto_material02 221 | - crypto_object 222 | - crypto_object00 223 | - crypto_object01 224 | - crypto_object02 225 | crypto_asset: 226 | - crypto_asset 227 | - crypto_asset00 228 | - crypto_asset01 229 | - crypto_asset02 230 | crypto_material: 231 | - crypto_material 232 | - crypto_material00 233 | - crypto_material01 234 | - crypto_material02 235 | crypto_object: 236 | - crypto_object 237 | - crypto_object00 238 | - crypto_object01 239 | - crypto_object02 240 | deep: 241 | - deep 242 | - deep_scanline 243 | depth: 244 | - Z 245 | - Z2 246 | - depth 247 | - depth_extra 248 | diffuse: 249 | - diffuse 250 | - diffuse_albedo 251 | - diffuse_direct 252 | - diffuse_indirect 253 | direct: 254 | - coat_direct 255 | - diffuse_direct 256 | - direct 257 | - sheen_direct 258 | - specular_direct 259 | - sss_direct 260 | - transmission_direct 261 | - volume_direct 262 | disparity: 263 | - disparityl 264 | - disparityr 265 | id: 266 | - ID 267 | - id 268 | indirect: 269 | - coat_indirect 270 | - diffuse_indirect 271 | - indirect 272 | - sheen_indirect 273 | - specular_indirect 274 | - sss_indirect 275 | - transmission_indirect 276 | - volume_indirect 277 | light_group: 278 | - light_group_1 279 | - light_group_2 280 | - light_group_3 281 | - light_group_4 282 | - light_group_5 283 | - light_group_6 284 | - light_group_7 285 | - light_group_8 286 | - light_group_9 287 | - light_group_10 288 | - light_group_11 289 | - light_group_12 290 | - light_group_13 291 | - light_group_14 292 | - light_group_15 293 | - light_group_16 294 | matte: 295 | - Z 296 | - depth 297 | - m 298 | - mask 299 | - mask_planartrack 300 | - mask_splinewarp 301 | - matte 302 | - roto 303 | - rotopaint_mask 304 | - shadow_matte 305 | - volume_opacity 306 | - volume_z 307 | mp: 308 | - mp 309 | mptd: 310 | - mptd 311 | non_color: 312 | - AA_inv_density 313 | - ID 314 | - N 315 | - P 316 | - Pref 317 | - Z 318 | - backward 319 | - cputime 320 | - crypto_asset 321 | - crypto_asset00 322 | - crypto_asset01 323 | - crypto_asset02 324 | - crypto_material 325 | - crypto_material00 326 | - crypto_material01 327 | - crypto_material02 328 | - crypto_object 329 | - crypto_object00 330 | - crypto_object01 331 | - crypto_object02 332 | - deep 333 | - depth 334 | - depth_extra 335 | - disparityl 336 | - disparityr 337 | - forward 338 | - id 339 | - m 340 | - mask 341 | - mask_planartrack 342 | - mask_splinewarp 343 | - matte 344 | - motion 345 | - motionvector 346 | - puz 347 | - raycount 348 | - roto 349 | - rotopaint_mask 350 | - shadow_matte 351 | - uv 352 | - uv_extra 353 | - volume_opacity 354 | - volume_z 355 | p: 356 | - P 357 | - Pref 358 | pref: 359 | - Pref 360 | puz: 361 | - puz 362 | roto: 363 | - roto 364 | rotopaint: 365 | - platepremult 366 | - rotopaint 367 | - rotopaint_degrain 368 | - rotopaint_grain 369 | - rotopaint_mask 370 | secondary: 371 | - emission 372 | - reflection 373 | - refraction 374 | - single_scatter 375 | - sss_direct 376 | - sss_indirect 377 | shadow: 378 | - shadow 379 | - shadow_diff 380 | - shadow_matte 381 | sheen: 382 | - sheen 383 | - sheen_albedo 384 | - sheen_direct 385 | - sheen_indirect 386 | specular: 387 | - coat 388 | - coat_direct 389 | - coat_indirect 390 | - specular 391 | - specular_direct 392 | - specular_indirect 393 | sss: 394 | - sss 395 | - sss_albedo 396 | - sss_direct 397 | - sss_indirect 398 | transmission: 399 | - transmission 400 | - transmission_albedo 401 | - transmission_direct 402 | - transmission_indirect 403 | uv: 404 | - backward 405 | - forward 406 | - motion 407 | - motionvector 408 | - uv 409 | - uv_extra 410 | volume: 411 | - volume 412 | - volume_albedo 413 | - volume_direct 414 | - volume_indirect 415 | -------------------------------------------------------------------------------- /src/nuke/LayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include "LayerSet.h" 2 | 3 | namespace LayerAlchemy { 4 | namespace LayerSet { 5 | 6 | StrVecType getLayerNames(const DD::Image::ChannelSet& inChannels) 7 | { 8 | DD::Image::ChannelSet selectedChannels; 9 | StrVecType layerNames; 10 | 11 | foreach(z, inChannels) { 12 | string layer = DD::Image::getLayerName(z); 13 | bool contains = (find(begin(layerNames), end(layerNames), layer)) != layerNames.end(); 14 | 15 | if (!contains) { 16 | layerNames.emplace_back(layer); 17 | } 18 | } 19 | return layerNames; 20 | } 21 | 22 | ChannelSetMapType _layerMaptoChannelMap(const LayerMap& layerMap, const DD::Image::ChannelSet& inChannels) 23 | { 24 | ChannelSetMapType channelSetLayerMap; 25 | for (auto& kvp : layerMap.strMap) { 26 | foreach (channel, inChannels) { 27 | string layerName = getLayerName(channel); 28 | if (layerMap.isMember(kvp.first, layerName)) { 29 | channelSetLayerMap[kvp.first].insert(channel); 30 | } 31 | } 32 | } 33 | return channelSetLayerMap; 34 | } 35 | 36 | ChannelSetMapType categorizeChannelSet(const LayerCollection& collection, const DD::Image::ChannelSet& inChannels) 37 | { 38 | StrVecType inLayers = LayerSet::getLayerNames(inChannels); 39 | LayerMap layerMap = collection.categorizeLayers(inLayers, categorizeType::pub); 40 | return _layerMaptoChannelMap(layerMap, inChannels); 41 | } 42 | 43 | ChannelSetMapType categorizeChannelSet(const LayerCollection& collection, const DD::Image::ChannelSet& inChannels, const CategorizeFilter& categorizeFilter) 44 | { 45 | StrVecType inLayers = LayerSet::getLayerNames(inChannels); 46 | LayerMap layerMap = collection.categorizeLayers(inLayers, categorizeType::pub, categorizeFilter); 47 | layerMap.strMap.erase("all"); // not useful for Nuke when CategorizeFilter is used 48 | return _layerMaptoChannelMap(layerMap, inChannels); 49 | } 50 | 51 | StrVecType getCategories(const ChannelSetMapType& channelSetLayerMap) 52 | { 53 | StrVecType categories; 54 | categories.reserve(channelSetLayerMap.size()); 55 | for (auto& kvp : channelSetLayerMap) { 56 | categories.emplace_back(kvp.first); 57 | } 58 | return categories; 59 | } 60 | 61 | DD::Image::ChannelSet getChannelSet(const ChannelSetMapType& channelSetLayerMap) 62 | { 63 | DD::Image::ChannelSet channels; 64 | for (auto& kvp : channelSetLayerMap) { 65 | channels += kvp.second; 66 | } 67 | return channels; 68 | } 69 | 70 | } // End namespace LayerSet 71 | namespace Knobs { 72 | 73 | DD::Image::Knob* createDocumentationButton(DD::Image::Knob_Callback& f) 74 | { 75 | const char* docButtonScript = 76 | "import layer_alchemy.documentation\n" 77 | "qtWidget = layer_alchemy.documentation.displayDocumentation(node=nuke.thisNode())\n" 78 | "if qtWidget:\n" 79 | " qtWidget.show()"; 80 | 81 | DD::Image::Knob* docButton = PyScript_knob(f, docButtonScript, "documentation"); 82 | Tooltip(f, "

This will display the included plugin documentation

"); 83 | return docButton; 84 | } 85 | 86 | DD::Image::Knob* createColorKnobResetButton(DD::Image::Knob_Callback& f) { 87 | const char* colorKnobResetScript = 88 | "currentNode = nuke.thisNode()\n" 89 | "colorKnobs = [knob for knob in currentNode.allKnobs() if isinstance(knob, nuke.Color_Knob)]\n" 90 | "for knob in colorKnobs:\n" 91 | " knob.clearAnimated()\n" 92 | " knob.setValue(knob.defaultValue())\n"; 93 | return PyScript_knob(f, colorKnobResetScript, "reset values"); 94 | } 95 | DD::Image::Knob* createVersionTextKnob(DD::Image::Knob_Callback& f) 96 | { 97 | string label = "v" + LAYER_ALCHEMY_VERSION_STRING + ""; 98 | DD::Image::Knob* versionTextKnob = Text_knob(f, label.c_str()); 99 | return versionTextKnob; 100 | } 101 | 102 | 103 | } // End namespace Knobs 104 | 105 | namespace Utilities { 106 | 107 | void validateTargetLayerColorIndex(DD::Image::Op* t_op, const DD::Image::ChannelSet& targetLayer, unsigned minIndex, unsigned 108 | maxIndex) 109 | { 110 | foreach(channel, targetLayer) 111 | { 112 | unsigned colorIdx = colourIndex(channel); 113 | if (colorIdx < minIndex || colorIdx > maxIndex) { 114 | t_op->error("target layer's current channels are out of color range"); 115 | } 116 | } 117 | } 118 | 119 | float* hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::Channel channel, DD::Image::Row& toRow) 120 | { 121 | float* outAovValue; 122 | if (fromRow.is_zero(channel)) 123 | { 124 | float* outAovValue = toRow.writableConstant(0.0f, channel); 125 | } 126 | else 127 | { 128 | const float* inAovValue = fromRow[channel]; 129 | float* outAovValue = toRow.writable(channel); 130 | for (int X = x; X < r; X++) 131 | { 132 | outAovValue[X] = inAovValue[X]; 133 | } 134 | } 135 | return outAovValue; 136 | } 137 | 138 | void hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::ChannelSet channels, DD::Image::Row& toRow) 139 | { 140 | foreach(channel, channels) 141 | { 142 | hard_copy(fromRow, x, r, channel, toRow); 143 | } 144 | } 145 | 146 | void gradeChannelPixelEngine(const DD::Image::Row& in, int y, int x, int r, DD::Image::ChannelSet& channels, DD::Image::Row& aRow, float* A, float* B, float* G, bool reverse, bool clampBlack, bool clampWhite) 147 | { 148 | // patch for linux alphas because the pow function behaves badly 149 | // for very large or very small exponent values. 150 | static bool LINUX = false; 151 | #ifdef __alpha 152 | LINUX = true; 153 | #endif 154 | 155 | 156 | map aovPtrIdxMap; 157 | foreach(channel, channels) 158 | { 159 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 160 | aovPtrIdxMap[channel] = aRow.writable(channel); 161 | } 162 | 163 | foreach(channel, channels) { 164 | unsigned chanIdx = colourIndex(channel); 165 | const float* inAovValue = in[channel]; 166 | float* outAovValue = aovPtrIdxMap[channel]; 167 | 168 | float _A = A[chanIdx]; 169 | float _B = B[chanIdx]; 170 | float _G = G[chanIdx]; 171 | 172 | for (int X = x; X < r; X++) 173 | { 174 | float outPixel = inAovValue[X]; 175 | 176 | if (!reverse) { 177 | if (_A != 1.0f || _B) { 178 | outPixel *= _A; 179 | outPixel += _B; 180 | } 181 | if (clampWhite || clampBlack) { 182 | if (outPixel < 0.0f && clampBlack) { // clamp black 183 | outPixel = 0.0f; 184 | } 185 | if (outPixel > 1.0f && clampWhite) { // clamp white 186 | outPixel = 1.0f; 187 | } 188 | } 189 | if (_G <= 0) { 190 | if (outPixel < 1.0f) { 191 | outPixel = 0.0f; 192 | } else if (outPixel > 1.0f) { 193 | outPixel = INFINITY; 194 | } 195 | } else if (_G != 1.0f) { 196 | float power = 1.0f / _G; 197 | if (LINUX & (outPixel <= 1e-6f && power > 1.0f)) { 198 | outPixel = 0.0f; 199 | } else if (outPixel < 1) { 200 | outPixel = powf(outPixel, power); 201 | } else { 202 | outPixel = (1.0f + outPixel - 1.0f) * power; 203 | } 204 | } 205 | } 206 | if (reverse) { // Reverse gamma: 207 | if (_G <= 0) { 208 | outPixel = outPixel > 0.0f ? 1.0f : 0.0f; 209 | } 210 | if (_G != 1.0f) { 211 | if (LINUX & (outPixel <= 1e-6f && _G > 1.0f)) { 212 | outPixel = 0.0f; 213 | } else if (outPixel < 1.0f) { 214 | outPixel = powf(outPixel, _G); 215 | } else { 216 | outPixel = 1.0f + (outPixel - 1.0f) * _G; 217 | } 218 | } 219 | // Reverse the linear part: 220 | if (_A != 1.0f || _B) { 221 | float b = _B; 222 | float a = _A; 223 | if (a) { 224 | a = 1 / a; 225 | } else { 226 | a = 1.0f; 227 | } 228 | b = -b * a; 229 | outPixel = (outPixel * a) + b; 230 | } 231 | } 232 | // clamp 233 | if (clampWhite || clampBlack) { 234 | if (outPixel < 0.0f && clampBlack) 235 | { 236 | outPixel = 0.0f; 237 | } 238 | else if (outPixel > 1.0f && clampWhite) 239 | { 240 | outPixel = 1.0f; 241 | } 242 | } 243 | outAovValue[X] = outPixel; 244 | } 245 | } 246 | } 247 | 248 | float validateGammaValue(const float& gammaValue) 249 | { 250 | static bool LINUX = false; 251 | #ifdef __alpha 252 | LINUX = true; 253 | #endif 254 | if (LINUX) 255 | { 256 | if (gammaValue < 0.008f) 257 | { 258 | return 0.0f; 259 | } 260 | else if (gammaValue > 125.0f) 261 | { 262 | return 125.0f; 263 | } 264 | } 265 | return gammaValue; 266 | } 267 | } // End namespace Utilities 268 | } // End namespace LayerAlchemy 269 | 270 | -------------------------------------------------------------------------------- /src/nuke/GradeBeautyLayer.cpp: -------------------------------------------------------------------------------- 1 | #include "math.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "LayerSet.h" 7 | #include "LayerSetKnob.h" 8 | #include "GradeBeautyLayerSet.cpp" 9 | 10 | 11 | namespace GradeBeautyLayer { 12 | 13 | const char* const HELP = 14 | "

Grade node for cg multichannel beauty aovs

" 15 | "order of operations is : \n\n" 16 | "1 - subtract source layer from the target layer\n" 17 | "2 - perform grade modification to the source layer\n" 18 | "3 - add the modified source layer to the target layer\n"; 19 | 20 | static const char* const layerNames[] = { 21 | " ", 0 22 | }; 23 | 24 | using namespace DD::Image; 25 | 26 | static const StrVecType all = { 27 | "beauty_direct_indirect", "beauty_shading_global", "light_group", "beauty_shading" 28 | }; 29 | static const CategorizeFilter CategorizeFilterAllBeauty(all, CategorizeFilter::modes::INCLUDE); 30 | 31 | class GradeBeautyLayer : public PixelIop { 32 | 33 | private: 34 | float blackpoint[3]{0.0f, 0.0f, 0.0f}; 35 | float whitepoint[3]{1.0f, 1.0f, 1.0f}; 36 | float lift[3]{0.0f, 0.0f, 0.0f}; 37 | float gain[3]{1.0f, 1.0f, 1.0f}; 38 | float offset[3]{0.0f, 0.0f, 0.0f}; 39 | float multiply[3]{1.0f, 1.0f, 1.0f}; 40 | float gamma[3]{1.0f, 1.0f, 1.0f}; 41 | bool reverse{false}; 42 | bool clampBlack{true}; 43 | bool clampWhite{false}; 44 | ChannelSet m_targetLayer{Mask_RGB}; 45 | ChannelSet m_sourceLayer{Mask_None}; 46 | ChannelSet m_selectedLayers; 47 | 48 | // intermediate grade algorithm storage 49 | float A[3]{0.0f, 0.0f, 0.0f}; 50 | float B[3]{0.0f, 0.0f, 0.0f}; 51 | float G[3]{0.0f, 0.0f, 0.0f}; 52 | 53 | public: 54 | void knobs(Knob_Callback); 55 | void _validate(bool for_real); 56 | bool pass_transform() const {return true;} 57 | void in_channels(int, ChannelSet& channels) const; 58 | void pixel_engine(const Row&, int, int, int, ChannelMask, Row&); 59 | int knob_changed(Knob*); 60 | const char* Class() const {return description.name;} 61 | const char* node_help() const {return HELP;} 62 | static const Iop::Description description; 63 | // channel set that contains all channels that are modified by the node 64 | ChannelSet activeChannelSet() const; 65 | // pixel engine function when anything but the target layer is requested to render 66 | void channelPixelEngine(const Row&, int, int, int, ChannelSet&, Row&); 67 | // pixel engine functon when the target layer is requested to render 68 | void beautyPixelEngine(const Row&, int y, int x, int r, ChannelSet&, Row&); 69 | // This function calculates and stores the grade algorithm's intermediate calculations 70 | bool precomputeValues(); 71 | GradeBeautyLayer(Node* node); 72 | ~GradeBeautyLayer(); 73 | }; 74 | 75 | GradeBeautyLayer::GradeBeautyLayer(Node* node) : PixelIop(node) 76 | { 77 | precomputeValues(); 78 | } 79 | 80 | static Op* build(Node* node) 81 | { 82 | return (new NukeWrapper(new GradeBeautyLayer(node)))->noChannels()->mixLuminance(); 83 | } 84 | 85 | GradeBeautyLayer::~GradeBeautyLayer() {} 86 | 87 | const Iop::Description GradeBeautyLayer::description( 88 | "GradeBeautyLayer", 89 | "LayerAlchemy/GradeBeautyLayer", 90 | build 91 | ); 92 | 93 | ChannelSet GradeBeautyLayer::activeChannelSet() const 94 | { 95 | ChannelSet outChans = ChannelSet(m_targetLayer + m_sourceLayer); 96 | return outChans; 97 | } 98 | 99 | void GradeBeautyLayer::in_channels(int input_number, ChannelSet& mask) const { 100 | mask += activeChannelSet(); 101 | } 102 | 103 | bool GradeBeautyLayer::precomputeValues() { 104 | bool changeZero = false; 105 | for (unsigned int chanIdx = 0; chanIdx < 3; chanIdx++) { 106 | float a = whitepoint[chanIdx] - blackpoint[chanIdx]; 107 | a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f; 108 | a *= multiply[chanIdx]; 109 | float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a; 110 | float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]); 111 | A[chanIdx] = a; 112 | B[chanIdx] = b; 113 | G[chanIdx] = g; 114 | if (a != 1.0f || b != 0.0f || g != 1.0f) 115 | { 116 | if (b) 117 | { 118 | changeZero = true; 119 | } 120 | } 121 | } 122 | return changeZero; 123 | } 124 | 125 | void GradeBeautyLayer::_validate(bool for_real) { 126 | copy_info(); // this copies the input info to the output 127 | bool changeZero = precomputeValues(); 128 | info_.black_outside(!changeZero); 129 | ChannelSet inChannels = info_.channels(); 130 | LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2); 131 | m_selectedLayers = activeChannelSet(); 132 | set_out_channels(m_selectedLayers); 133 | info_.turn_on(m_targetLayer); 134 | } 135 | void GradeBeautyLayer::channelPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow) 136 | { 137 | LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite); 138 | } 139 | void GradeBeautyLayer::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow) 140 | { 141 | ChannelSet bty = m_targetLayer.intersection(channels); 142 | ChannelSet aovs = m_sourceLayer.intersection(channels); 143 | 144 | map btyPtrIdxMap; 145 | map aovPtrIdxMap; 146 | map aovInPtrIdxMap; 147 | 148 | foreach(channel, bty) { 149 | unsigned chanIdx = colourIndex(channel); 150 | float* rowBtyChan; 151 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 152 | rowBtyChan = aRow.writable(channel); 153 | btyPtrIdxMap[chanIdx] = rowBtyChan; 154 | 155 | } 156 | foreach(channel, aovs) { 157 | aovPtrIdxMap[channel] = aRow.writable(channel); 158 | aovInPtrIdxMap[channel] = in[channel]; 159 | } 160 | 161 | for (const auto& kvp : btyPtrIdxMap) 162 | { 163 | unsigned btyChanIdx = kvp.first; 164 | float* aRowBty = kvp.second; 165 | 166 | foreach(aov, aovs) 167 | { 168 | unsigned aovChanIdx = colourIndex(aov); 169 | if (btyChanIdx != aovChanIdx) 170 | { 171 | continue; 172 | } 173 | 174 | float* aRowBty = btyPtrIdxMap[aovChanIdx]; 175 | float* aRowAov = aovPtrIdxMap[aov]; 176 | const float* inAov = aovInPtrIdxMap[aov]; 177 | for (int X = x; X < r; X++) 178 | { 179 | float aovPixel = aRowAov[X]; 180 | float btyPixel = aRowBty[X]; 181 | float aovInPixel = inAov[X]; 182 | btyPixel -= aovInPixel; 183 | float resultPixel = btyPixel + aovPixel; 184 | aRowBty[X] = btyPixel + aovPixel; 185 | } 186 | } 187 | // clamp 188 | if (clampWhite || clampBlack) { 189 | for (int X = x; X < r; X++) 190 | { 191 | float btyPixel = aRowBty[X]; 192 | 193 | if (btyPixel < 0.0f && clampBlack) 194 | { 195 | btyPixel = 0.0f; 196 | } 197 | else if (btyPixel > 1.0f && clampWhite) 198 | { 199 | btyPixel = 1.0f; 200 | } 201 | aRowBty[X] = btyPixel; 202 | } 203 | } 204 | } 205 | } 206 | 207 | void GradeBeautyLayer::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) { 208 | ChannelSet inChannels = ChannelSet(channels); 209 | ChannelSet activeChannels = m_selectedLayers; 210 | Row aRow(x, r); 211 | bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size(); 212 | 213 | if (isTargetLayer) 214 | { 215 | LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, m_sourceLayer, aRow, A, B, G, reverse, clampBlack, clampWhite); 216 | beautyPixelEngine(in, y, x, r, activeChannels, aRow); 217 | } 218 | else 219 | { 220 | LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, inChannels, aRow, A, B, G, reverse, clampBlack, clampWhite); 221 | } 222 | LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out); 223 | } 224 | 225 | void GradeBeautyLayer::knobs(Knob_Callback f) { 226 | ChannelSet_knob(f, &m_sourceLayer, "channels", "source layer"); 227 | Tooltip(f, 228 | "

Order of operations :

" 229 | "

1 - source_layer subtracted from target_layer

" 230 | "

2 - source_layer is graded

" 231 | "

2 - graded source_layer is added to target_layer

" 232 | ); 233 | SetFlags(f, Knob::NO_ALPHA_PULLDOWN); 234 | SetFlags(f, Knob::NO_CHECKMARKS); 235 | 236 | LayerAlchemy::Knobs::createDocumentationButton(f); 237 | LayerAlchemy::Knobs::createColorKnobResetButton(f); 238 | LayerAlchemy::Knobs::createVersionTextKnob(f); 239 | Divider(f, 0); // separates layer set knobs from the rest 240 | 241 | Input_ChannelMask_knob(f, &m_targetLayer, 0, "target layer"); 242 | SetFlags(f, Knob::NO_ALPHA_PULLDOWN); 243 | Tooltip(f, "

Selects which layer to pre-subtract layers from (if enabled) and offset the modified layers to

"); 244 | SetFlags(f, Knob::EXPAND_TO_CONTENTS); 245 | 246 | Divider(f, 0); // separates layer set knobs from the rest 247 | 248 | Color_knob(f, blackpoint, IRange(-1, 1), "blackpoint"); 249 | Tooltip(f, "This color is turned into black"); 250 | Color_knob(f, whitepoint, IRange(0, 4), "whitepoint"); 251 | Tooltip(f, "This color is turned into white"); 252 | Color_knob(f, lift, IRange(-1, 1), "lift", "lift"); 253 | Tooltip(f, "Black is turned into this color"); 254 | Color_knob(f, gain, IRange(0, 4), "gain", "gain"); 255 | Tooltip(f, "White is turned into this color"); 256 | Color_knob(f, multiply, IRange(0, 4), "multiply"); 257 | Tooltip(f, "Constant to multiply result by"); 258 | Color_knob(f, offset, IRange(-1, 1), "offset", "offset"); 259 | Tooltip(f, "Constant to offset to result (raises both black & white, unlike lift)"); 260 | Color_knob(f, gamma, IRange(.2, 5), "gamma"); 261 | Tooltip(f, "Gamma correction applied to final result"); 262 | Newline(f, " "); 263 | Bool_knob(f, &reverse, "reverse"); 264 | Tooltip(f, "Invert the math to undo the correction"); 265 | Bool_knob(f, &clampBlack, "clampBlack", "black clamp"); 266 | Tooltip(f, "Output that is less than zero is changed to zero"); 267 | Bool_knob(f, &clampWhite, "clampWhite", "white clamp"); 268 | Tooltip(f, "Output that is greater than 1 is changed to 1"); 269 | 270 | Divider(f, 0); // separates NukeWrapper knobs created after this 271 | } 272 | 273 | int GradeBeautyLayer::knob_changed(Knob* k) { 274 | return 1; 275 | } 276 | } // End namespace GradeBeautyLayer 277 | -------------------------------------------------------------------------------- /src/LayerSetCore.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * implementation code for the LayerMap and LayerCollection objects 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "LayerSetCore.h" 9 | 10 | LayerMap::LayerMap() { 11 | }; 12 | 13 | LayerMap::LayerMap(const LayerMap& other) 14 | : strMap(other.strMap) { 15 | } 16 | 17 | LayerMap::LayerMap(const StrMapType& other) 18 | : strMap(other) { 19 | } 20 | 21 | LayerMap::~LayerMap() { 22 | } 23 | 24 | LayerMap::LayerMap(const string& yamlFilePath) : strMap(loadConfigToMap(yamlFilePath)) { 25 | } 26 | 27 | StrVecType LayerMap::operator[](const StrVecType& categoryNames) const { 28 | StrVecType items; 29 | for (auto it = categoryNames.begin(); it != categoryNames.end(); it++) { 30 | if (strMap.find(*it) != strMap.end()) { 31 | StrVecType categories = strMap.at(*it); 32 | if (categories.size() != 0) { 33 | for (auto it2 = categories.begin(); it2 != categories.end(); it2++) { 34 | items.push_back(*it2); 35 | } 36 | } 37 | } 38 | } 39 | return items; 40 | } 41 | 42 | StrVecType LayerMap::operator[](const string& categoryName) const { 43 | if (strMap.find(categoryName) != strMap.end()) { 44 | return strMap.at(categoryName); 45 | } else { 46 | return StrVecType(); 47 | } 48 | } 49 | 50 | bool LayerMap::isMember(const string& categoryName, const string& layer) const { 51 | auto it = strMap.find(categoryName); 52 | if (it != strMap.end()) { 53 | return find(begin(it->second), end(it->second), layer) != it->second.end(); 54 | } else { 55 | return false; 56 | } 57 | } 58 | 59 | void LayerMap::add(const string categoryName, const string layer) { 60 | if (strMap.count(categoryName)) { 61 | if (!isMember(categoryName, layer)) { 62 | strMap[categoryName].emplace_back(layer); 63 | } 64 | } else { 65 | strMap[categoryName].emplace_back(layer); 66 | } 67 | } 68 | 69 | const StrVecType LayerMap::categories() const { 70 | StrVecType itemKeys; 71 | itemKeys.reserve(strMap.size()); 72 | for (auto& kvp : strMap) { 73 | if (kvp.first.find("_") != 0) { 74 | itemKeys.emplace_back(kvp.first); 75 | } 76 | } 77 | return itemKeys; 78 | } 79 | 80 | const StrVecType LayerMap::categories(string& layerName) const { 81 | StrVecType itemKeys; 82 | for (auto& kvp : strMap) { 83 | if (isMember(kvp.first, layerName)) { 84 | itemKeys.emplace_back(layerName); 85 | } 86 | } 87 | return itemKeys; 88 | } 89 | const StrVecType LayerMap::uniqueLayers() const { 90 | StrVecType uniqueLayers; 91 | for (auto & kvp : strMap) { 92 | for (auto layer : kvp.second) { 93 | if (!(find(begin(uniqueLayers), end(uniqueLayers), layer) != uniqueLayers.end())) { 94 | uniqueLayers.push_back(layer); 95 | } 96 | } 97 | } 98 | return uniqueLayers; 99 | } 100 | 101 | string LayerMap::toString() const { 102 | std::ostringstream reprStream; 103 | string repr; 104 | reprStream << 105 | " \n\n{\n"; 106 | for (auto & kvp : strMap) { 107 | reprStream << "'" << (string) kvp.first << "'" << " : ("; 108 | for (unsigned m = 0; m < kvp.second.size(); m++) { 109 | reprStream << "'" << (string) kvp.second[m] << "'" << ", "; 110 | } 111 | reprStream << "),\n"; 112 | } 113 | reprStream << "}\n"; 114 | repr = reprStream.str(); 115 | return repr; 116 | } 117 | bool LayerMap::empty() const { 118 | return strMap.empty(); 119 | } 120 | 121 | int LayerMap::size() const { 122 | return strMap.size(); 123 | } 124 | 125 | const StrVecType LayerMap::categoriesByType(const categorizeType& catType) const { 126 | StrVecType relevantCats; 127 | relevantCats.reserve(strMap.size()); 128 | for (const auto& kvp : strMap) { 129 | int firstToken = kvp.first.find("_"); 130 | if ((firstToken == 0) && (catType == categorizeType::priv)) { // looking for categories starting with _ in priv mode 131 | relevantCats.emplace_back(kvp.first); 132 | } else { 133 | if ((firstToken != 0) && (catType == categorizeType::pub)) { 134 | relevantCats.emplace_back(kvp.first); 135 | } 136 | } 137 | }; 138 | return relevantCats; 139 | }; 140 | 141 | CategorizeFilter::CategorizeFilter() { 142 | filterMode = CategorizeFilter::modes::INCLUDE; 143 | } 144 | 145 | CategorizeFilter::CategorizeFilter(const StrVecType& selectedCategories, int mode) { 146 | filterMode = mode; 147 | categories = selectedCategories; 148 | } 149 | 150 | LayerCollection::LayerCollection() : 151 | channels(LayerMap(loadConfigToMap((getenv(CHANNEL_ENV_VAR))))), 152 | layers(LayerMap(loadConfigToMap((getenv(LAYER_ENV_VAR))))) { 153 | } 154 | 155 | LayerCollection::~LayerCollection() { 156 | } 157 | 158 | string LayerCollection::dePrefix(const string layerName) const { 159 | string unPrefixedLayerName, outputLayerName; 160 | StrVecType prefix = layers.strMap.at("_prefix"); 161 | 162 | for (StrVecType::const_iterator iterPrefix = prefix.begin(); iterPrefix != prefix.end(); iterPrefix++) { 163 | string prefixName = *iterPrefix; 164 | string prefixToFind = prefixName + "_"; 165 | size_t prefixLocation = layerName.rfind(prefixToFind); 166 | string pos = layerName.substr(0, prefixLocation); 167 | if ((prefixLocation != string::npos) || (layerName.substr(0, prefixToFind.length()) == prefixToFind)) { 168 | unPrefixedLayerName = prefixName; 169 | } 170 | } 171 | return (unPrefixedLayerName.length() > 0) ? unPrefixedLayerName : layerName; 172 | } 173 | 174 | bool LayerMap::contains(const string& categoryName) const { 175 | StrVecType categories = this->categories(); 176 | bool result = std::any_of(categories.begin(), categories.end(), [categoryName](const string & str) { 177 | return str == categoryName; }); 178 | return result; 179 | } 180 | 181 | bool LayerMap::contains(const StrVecType& categoryNames) const { 182 | StrVecType categories = this->categories(); 183 | return std::includes(categories.begin(), categories.end(), categoryNames.begin(), categoryNames.end()); 184 | } 185 | 186 | string utilities::getLayerFromChannel(const string& layerName) { 187 | return layerName.substr(0, layerName.find(".")); 188 | }; 189 | 190 | StrVecType utilities::applyChannelNames(const string& layerName, const StrVecType& topologyVector) { 191 | StrVecType channelNames; 192 | channelNames.reserve(topologyVector.size()); 193 | for (StrVecType::const_iterator iterChannel = topologyVector.begin(); iterChannel != topologyVector.end(); iterChannel++) { 194 | channelNames.emplace_back(layerName + "." + *iterChannel); 195 | } 196 | return channelNames; 197 | } 198 | 199 | LayerMap LayerCollection::categorizeLayers(const StrVecType& layersToCategorize, const categorizeType& catType) const { 200 | LayerMap categorizedLayerMap; 201 | StrVecType relevantCats = layers.categoriesByType(catType); 202 | 203 | for (StrVecType::const_iterator iterLayer = layersToCategorize.begin(); iterLayer != layersToCategorize.end(); iterLayer++) { 204 | string realLayerName; 205 | string layerName = utilities::getLayerFromChannel(*iterLayer); 206 | string dePrefixedLayerName = dePrefix(layerName); 207 | categorizedLayerMap.add("all", layerName); 208 | 209 | for (StrVecType::const_iterator iterCat = relevantCats.begin(); iterCat != relevantCats.end(); iterCat++) { 210 | if (layers.isMember(*iterCat, dePrefixedLayerName) | layers.isMember(*iterCat, layerName)) { 211 | categorizedLayerMap.add(*iterCat, layerName); 212 | } 213 | } 214 | } 215 | return categorizedLayerMap; 216 | }; 217 | 218 | LayerMap LayerCollection::categorizeLayers(const StrVecType& layersToCategorize, const categorizeType& catType, const CategorizeFilter& catFilter) const { 219 | LayerMap categorizedLayerMap; 220 | StrVecType foundCats; 221 | StrVecType relevantCats = layers.categoriesByType(catType); 222 | //loop over the requested categories, make sure they are found in the LayerCollection object 223 | for (auto iterCat = catFilter.categories.begin(); iterCat != catFilter.categories.end(); iterCat++) { 224 | bool found = std::find(relevantCats.begin(), relevantCats.end(), *iterCat) != relevantCats.end(); 225 | if (found) { 226 | foundCats.push_back(*iterCat); 227 | } 228 | } 229 | if (catFilter.filterMode == CategorizeFilter::ONLY) { // constrain categories for ONLY 230 | relevantCats = foundCats; 231 | } 232 | 233 | for (auto iterLayer = layersToCategorize.begin(); iterLayer != layersToCategorize.end(); iterLayer++) { 234 | string layerName = utilities::getLayerFromChannel(*iterLayer); 235 | string dePrefixedLayerName = dePrefix(layerName); 236 | bool validLayer = false; 237 | for (auto iterFilter = catFilter.categories.begin(); iterFilter != catFilter.categories.end(); iterFilter++) { 238 | if (layers.isMember(*iterFilter, dePrefixedLayerName)) { 239 | validLayer = true; 240 | } 241 | } 242 | 243 | if ((validLayer & (catFilter.filterMode != CategorizeFilter::EXCLUDE)) || 244 | (!validLayer & (catFilter.filterMode != CategorizeFilter::INCLUDE))) { 245 | 246 | for (auto iterCat = relevantCats.begin(); iterCat != relevantCats.end(); iterCat++) { 247 | 248 | if (layers.isMember(*iterCat, dePrefixedLayerName)) { 249 | if (catFilter.filterMode != CategorizeFilter::ONLY) { 250 | categorizedLayerMap.add("all", layerName); 251 | } 252 | categorizedLayerMap.add(*iterCat, layerName); 253 | } 254 | } 255 | } 256 | } 257 | return categorizedLayerMap; 258 | }; 259 | 260 | LayerMap LayerCollection::topology(const StrVecType& layerNames, const topologyStyle& style) const { 261 | // in the channel config file, naming is defined by _vec4_exr _vec4 262 | const string defaultCategory = "_vec4"; 263 | const string exrToken = "_exr"; 264 | const string defaultTopology = style == topologyStyle::exr ? defaultCategory + exrToken : defaultCategory; 265 | const StrVecType defaultChannels = channels[defaultTopology]; 266 | 267 | const StrVecType topoNames = channels[TOPOLOGY_KEY_LEXICAL]; // all styles derived from lexical 268 | 269 | LayerMap channelMapping; 270 | LayerMap categorized = categorizeLayers(layerNames, categorizeType::priv); 271 | 272 | for (auto iterCategory = topoNames.begin(); iterCategory != topoNames.end(); iterCategory++) { 273 | if (!categorized.contains(*iterCategory)) { 274 | string _topoType = style == topologyStyle::exr ? *iterCategory + exrToken : *iterCategory; 275 | StrVecType channelNames = channels[_topoType]; // look up channel names for this type 276 | StrVecType categorizedLayers = categorized[*iterCategory]; 277 | for (auto iterLayer = categorizedLayers.begin(); iterLayer != categorizedLayers.end(); iterLayer++) { 278 | channelMapping.strMap[*iterLayer] = utilities::applyChannelNames(*iterLayer, channelNames); 279 | } 280 | } 281 | } 282 | // this is for layers that are were not classified, make them RGBA 283 | for (auto iterLayer = layerNames.begin(); iterLayer != layerNames.end(); iterLayer++) { 284 | // in case channel names are used, get the layer name 285 | string layerName = utilities::getLayerFromChannel(*iterLayer); 286 | if (!channelMapping.contains(layerName)) { 287 | channelMapping.strMap[layerName] = utilities::applyChannelNames(layerName, defaultChannels); 288 | } 289 | } 290 | return channelMapping; 291 | }; 292 | -------------------------------------------------------------------------------- /src/nuke/GradeBeautyLayerSet.cpp: -------------------------------------------------------------------------------- 1 | #include "math.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "LayerSet.h" 7 | #include "LayerSetKnob.h" 8 | 9 | namespace GradeBeautyLayerSet { 10 | 11 | static const char* const HELP = 12 | "Provides artist friendly controls to manipulate multichannel cg render passes\n\n" 13 | "GradeBeautyLayerSet will dynamically reconfigure itself depending on : \n\n" 14 | " * layers found connected to its input\n" 15 | " * categorization of found layer names based on what is defined in the configuration files\n\n" 16 | "Any color knob change affects :\n\n" 17 | " * the related multichannel layers\n" 18 | " * the beauty pass is then rebuilt with the modified layers\n\n" 19 | "Configurations: \n\n" 20 | " * default configuration is Arnold 5 stock layer names\n" 21 | " * can be adapted to any additive type renderer\n" 22 | " * To change this, you must modify the configuration files accordingly\n\n" 23 | " * more info with the documentation button" 24 | ; 25 | 26 | enum operationModes { 27 | ADD = 0, COPY 28 | }; 29 | 30 | static const char* const operationNames[] = { 31 | "add", "copy", 0 32 | }; 33 | 34 | 35 | using namespace DD::Image; 36 | 37 | static const StrVecType all = { 38 | "beauty_direct_indirect", "beauty_shading_global", "light_group", "beauty_shading" 39 | }; 40 | static const CategorizeFilter CategorizeFilterAllBeauty(all, CategorizeFilter::modes::INCLUDE); 41 | 42 | class GradeBeautyLayerSet : public PixelIop { 43 | 44 | private: 45 | float blackpoint[3]{0.0f, 0.0f, 0.0f}; 46 | float whitepoint[3]{1.0f, 1.0f, 1.0f}; 47 | float lift[3]{0.0f, 0.0f, 0.0f}; 48 | float gain[3]{1.0f, 1.0f, 1.0f}; 49 | float offset[3]{0.0f, 0.0f, 0.0f}; 50 | float multiply[3]{1.0f, 1.0f, 1.0f}; 51 | float gamma[3]{1.0f, 1.0f, 1.0f}; 52 | bool reverse{false}; 53 | bool clampBlack{true}; 54 | bool clampWhite{false}; 55 | int m_operation{operationModes::ADD}; 56 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 57 | ChannelSet m_targetLayer{Mask_RGB}; 58 | // intermediate grade algorithm storage 59 | float A[3]{0.0f, 0.0f, 0.0f}; 60 | float B[3]{0.0f, 0.0f, 0.0f}; 61 | float G[3]{0.0f, 0.0f, 0.0f}; 62 | 63 | public: 64 | void knobs(Knob_Callback); 65 | void _validate(bool for_real); 66 | bool pass_transform() const {return true;} 67 | void in_channels(int, ChannelSet& channels) const; 68 | void pixel_engine(const Row&, int, int, int, ChannelMask, Row&); 69 | int knob_changed(Knob*); 70 | const char* Class() const {return description.name;} 71 | const char* node_help() const {return HELP;} 72 | static const Iop::Description description; 73 | 74 | // This function calculates and stores the grade algorithm's intermediate calculations 75 | bool precomputeValues(); 76 | GradeBeautyLayerSet(Node* node); 77 | ~GradeBeautyLayerSet(); 78 | // channel set that contains all channels that are modified by the node 79 | ChannelSet activeChannelSet() const; 80 | // pixel engine function when anything but the target layer is requested to render 81 | void channelPixelEngine(const Row&, int, int, int, ChannelSet&, Row&); 82 | // pixel engine functon when the target layer is requested to render 83 | void beautyPixelEngine(const Row&, int y, int x, int r, ChannelSet&, Row&); 84 | }; 85 | 86 | GradeBeautyLayerSet::GradeBeautyLayerSet(Node* node) : PixelIop(node) 87 | { 88 | precomputeValues(); 89 | } 90 | 91 | static Op* build(Node* node) 92 | { 93 | return (new NukeWrapper(new GradeBeautyLayerSet(node)))->noChannels()->mixLuminance(); 94 | } 95 | 96 | GradeBeautyLayerSet::~GradeBeautyLayerSet() {} 97 | 98 | const Iop::Description GradeBeautyLayerSet::description( 99 | "GradeBeautyLayerSet", 100 | "LayerAlchemy/GradeBeautyLayerSet", 101 | build 102 | ); 103 | 104 | ChannelSet GradeBeautyLayerSet::activeChannelSet() const 105 | { 106 | ChannelSet outChans; 107 | std::vector targetIndexes; 108 | foreach(channel, m_targetLayer) 109 | { 110 | targetIndexes.emplace_back(colourIndex(channel)); 111 | } 112 | 113 | foreach(channel, ChannelSet(m_targetLayer + m_lsKnobData.m_selectedChannels)) 114 | { 115 | unsigned chanIdx = colourIndex(channel); 116 | bool relevant = find(begin(targetIndexes), end(targetIndexes), chanIdx) != targetIndexes.end(); 117 | if (chanIdx <= 2 && relevant) 118 | { 119 | outChans += channel; 120 | } 121 | } 122 | return outChans; 123 | } 124 | 125 | void GradeBeautyLayerSet::in_channels(int input_number, ChannelSet& mask) const { 126 | mask += activeChannelSet(); 127 | } 128 | 129 | bool GradeBeautyLayerSet::precomputeValues() { 130 | bool changeZero = false; 131 | for (unsigned int chanIdx = 0; chanIdx < 3; chanIdx++) { 132 | float a = whitepoint[chanIdx] - blackpoint[chanIdx]; 133 | a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f; 134 | a *= multiply[chanIdx]; 135 | float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a; 136 | float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]); 137 | A[chanIdx] = a; 138 | B[chanIdx] = b; 139 | G[chanIdx] = g; 140 | if (a != 1.0f || b != 0.0f || g != 1.0f) 141 | { 142 | if (b) 143 | { 144 | changeZero = true; 145 | } 146 | } 147 | } 148 | return changeZero; 149 | } 150 | 151 | void GradeBeautyLayerSet::_validate(bool for_real) { 152 | copy_info(); // this copies the input info to the output 153 | bool changeZero = precomputeValues(); 154 | info_.black_outside(!changeZero); 155 | ChannelSet inChannels = info_.channels(); 156 | LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2); 157 | 158 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, CategorizeFilterAllBeauty)) { 159 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, CategorizeFilterAllBeauty); 160 | } 161 | set_out_channels(activeChannelSet()); 162 | info_.turn_on(m_targetLayer); 163 | } 164 | void GradeBeautyLayerSet::channelPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow) 165 | { 166 | LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite); 167 | } 168 | void GradeBeautyLayerSet::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow) 169 | { 170 | ChannelSet bty = m_targetLayer.intersection(channels); 171 | ChannelSet aovs = m_lsKnobData.m_selectedChannels.intersection(channels); 172 | 173 | map btyPtrIdxMap; 174 | map aovPtrIdxMap; 175 | map aovInPtrIdxMap; 176 | 177 | foreach(channel, bty) { 178 | unsigned chanIdx = colourIndex(channel); 179 | float* rowBtyChan; 180 | if (m_operation == operationModes::ADD) 181 | { 182 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 183 | rowBtyChan = aRow.writable(channel); 184 | } 185 | else 186 | { 187 | rowBtyChan = aRow.writableConstant(0.0f, channel); 188 | } 189 | btyPtrIdxMap[chanIdx] = rowBtyChan; 190 | 191 | } 192 | foreach(channel, aovs) { 193 | aovPtrIdxMap[channel] = aRow.writable(channel); 194 | aovInPtrIdxMap[channel] = in[channel]; 195 | } 196 | 197 | for (const auto& kvp : btyPtrIdxMap) 198 | { 199 | unsigned btyChanIdx = kvp.first; 200 | float* aRowBty = kvp.second; 201 | 202 | foreach(aov, aovs) 203 | { 204 | unsigned aovChanIdx = colourIndex(aov); 205 | if (btyChanIdx != aovChanIdx) 206 | { 207 | continue; 208 | } 209 | 210 | float* aRowBty = btyPtrIdxMap[aovChanIdx]; 211 | float* aRowAov = aovPtrIdxMap[aov]; 212 | const float* inAov = aovInPtrIdxMap[aov]; 213 | for (int X = x; X < r; X++) 214 | { 215 | float aovPixel = aRowAov[X]; 216 | float btyPixel = aRowBty[X]; 217 | if (m_operation == operationModes::ADD) 218 | { 219 | float aovInPixel = inAov[X]; 220 | btyPixel -= aovInPixel; 221 | } 222 | float resultPixel = btyPixel + aovPixel; 223 | aRowBty[X] = btyPixel + aovPixel; 224 | } 225 | } 226 | // clamp 227 | if (clampWhite || clampBlack) { 228 | for (int X = x; X < r; X++) 229 | { 230 | float btyPixel = aRowBty[X]; 231 | 232 | if (btyPixel < 0.0f && clampBlack) 233 | { 234 | btyPixel = 0.0f; 235 | } 236 | else if (btyPixel > 1.0f && clampWhite) 237 | { 238 | btyPixel = 1.0f; 239 | } 240 | aRowBty[X] = btyPixel; 241 | } 242 | } 243 | } 244 | } 245 | 246 | void GradeBeautyLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) { 247 | ChannelSet inChannels = ChannelSet(channels); 248 | ChannelSet activeChannels = activeChannelSet(); 249 | Row aRow(x, r); 250 | bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size(); 251 | 252 | if (isTargetLayer) 253 | { 254 | channelPixelEngine(in, y, x, r, m_lsKnobData.m_selectedChannels, aRow); 255 | beautyPixelEngine(in, y, x, r, activeChannels, aRow); 256 | } 257 | else 258 | { 259 | channelPixelEngine(in, y, x, r, inChannels, aRow); 260 | } 261 | LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out); 262 | } 263 | 264 | void GradeBeautyLayerSet::knobs(Knob_Callback f) { 265 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 266 | LayerAlchemy::Knobs::createDocumentationButton(f); 267 | LayerAlchemy::Knobs::createColorKnobResetButton(f); 268 | LayerAlchemy::Knobs::createVersionTextKnob(f); 269 | Divider(f, 0); // separates layer set knobs from the rest 270 | 271 | Input_ChannelMask_knob(f, &m_targetLayer, 0, "target layer"); 272 | SetFlags(f, Knob::NO_ALPHA_PULLDOWN); 273 | Tooltip(f, "

Selects which layer to pre-subtract layers from (if enabled) and offset the modified layers to

"); 274 | Enumeration_knob(f, &m_operation, operationNames, "output mode"); 275 | Tooltip(f, 276 | "

add : the additive sum of the chosen layer set is subtracted from the target layer " 277 | "before recombining with this node's modifications

" 278 | "

(this means any difference between the target layer " 279 | "and the render layers is kept in the final output)

" 280 | "

copy: outputs the additive recombination of the layer set to the target layer " 281 | "and changes are reflected in layers part of the chosen layer set

"); 282 | SetFlags(f, Knob::STARTLINE); 283 | SetFlags(f, Knob::EXPAND_TO_CONTENTS); 284 | 285 | Divider(f, 0); // separates layer set knobs from the rest 286 | 287 | Color_knob(f, blackpoint, IRange(-1, 1), "blackpoint"); 288 | Tooltip(f, "This color is turned into black"); 289 | Color_knob(f, whitepoint, IRange(0, 4), "whitepoint"); 290 | Tooltip(f, "This color is turned into white"); 291 | Color_knob(f, lift, IRange(-1, 1), "lift", "lift"); 292 | Tooltip(f, "Black is turned into this color"); 293 | Color_knob(f, gain, IRange(0, 4), "gain", "gain"); 294 | Tooltip(f, "White is turned into this color"); 295 | Color_knob(f, multiply, IRange(0, 4), "multiply"); 296 | Tooltip(f, "Constant to multiply result by"); 297 | Color_knob(f, offset, IRange(-1, 1), "offset", "offset"); 298 | Tooltip(f, "Constant to offset to result (raises both black & white, unlike lift)"); 299 | Color_knob(f, gamma, IRange(.2, 5), "gamma"); 300 | Tooltip(f, "Gamma correction applied to final result"); 301 | Newline(f, " "); 302 | Bool_knob(f, &reverse, "reverse"); 303 | Tooltip(f, "Invert the math to undo the correction"); 304 | Bool_knob(f, &clampBlack, "clampBlack", "black clamp"); 305 | Tooltip(f, "Output that is less than zero is changed to zero"); 306 | Bool_knob(f, &clampWhite, "clampWhite", "white clamp"); 307 | Tooltip(f, "Output that is greater than 1 is changed to 1"); 308 | 309 | Divider(f, 0); // separates NukeWrapper knobs created after this 310 | } 311 | 312 | int GradeBeautyLayerSet::knob_changed(Knob* k) { 313 | return 1; 314 | } 315 | } // End namespace GradeBeautyLayerSet 316 | -------------------------------------------------------------------------------- /src/nuke/GradeBeauty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "LayerSet.h" 8 | #include "LayerSetKnob.h" 9 | 10 | namespace GradeBeauty { 11 | 12 | static const char* const HELP = 13 | "Provides artist friendly controls to manipulate multichannel cg render passes\n\n" 14 | "GradeBeauty will dynamically reconfigure itself depending on : \n\n" 15 | " * layers found connected to its input\n" 16 | " * categorization of found layer names based on what is defined in the configuration files\n\n" 17 | "Any color knob change affects :\n\n" 18 | " * the related multichannel layers\n" 19 | " * the beauty pass is then rebuilt with the modified layers\n\n" 20 | "Configurations: \n\n" 21 | " * default configuration is Arnold 5 stock layer names\n" 22 | " * can be adapted to any additive type renderer\n" 23 | " * To change this, you must modify the configuration files accordingly\n\n" 24 | " * more info with the documentation button" 25 | ; 26 | 27 | static const char* DEFAULT_VALUE_PYSCRIPT = 28 | "node = nuke.thisNode()\n" 29 | "colorKnobs = [knob for knob in node.allKnobs() if isinstance(knob, nuke.Color_Knob)]\n" 30 | "default = node.knob('math_type').getValue()\n" 31 | "defaultValue = [default] * 3\n" 32 | "for knob in colorKnobs:\n" 33 | " if knob.defaultValue() != default:\n" 34 | " knobToScript = knob.toScript()\n" 35 | " knob.setDefaultValue(defaultValue)\n" 36 | " knob.fromScript(knobToScript)" 37 | ; 38 | 39 | namespace BeautyLayerSetConstants { 40 | // frequently used lists of layer set category names 41 | namespace categories 42 | { 43 | static const StrVecType all = { 44 | "beauty_direct_indirect", "beauty_shading_global", "light_group", "beauty_shading" 45 | }; 46 | static const string shading = "beauty_shading"; 47 | static const StrVecType nonShading = {"beauty_direct_indirect", "beauty_shading_global", "light_group"}; 48 | static const StrVecType global = {"beauty_shading_global", "beauty_direct_indirect"}; 49 | } 50 | // frequently used lists of layers 51 | namespace layers 52 | { 53 | static const StrVecType all = LayerAlchemy::layerCollection.layers[categories::all]; 54 | static const StrVecType shading = LayerAlchemy::layerCollection.layers[categories::shading]; 55 | static const StrVecType nonShading = LayerAlchemy::layerCollection.layers[categories::nonShading]; 56 | static const StrVecType global = LayerAlchemy::layerCollection.layers[categories::global]; 57 | }; 58 | } 59 | 60 | using namespace DD::Image; 61 | using namespace BeautyLayerSetConstants; 62 | 63 | static const CategorizeFilter CategorizeFilterAllBeauty(BeautyLayerSetConstants::categories::all, CategorizeFilter::modes::ONLY); 64 | 65 | //The two modes that define the default value of this node's color knobs, and the math formula to apply 66 | enum GRADE_BEAUTY_MATH_MODE { 67 | STOPS = 0, 68 | MULTIPLY 69 | }; 70 | //defines the range of the Color_Knobs for each mode 71 | static map> COLOR_KNOB_RANGES = { 72 | {GRADE_BEAUTY_MATH_MODE::STOPS, std::array{-10, 20}}, 73 | {GRADE_BEAUTY_MATH_MODE::MULTIPLY, std::array{0, 50}}, 74 | }; 75 | 76 | // names to be used in the enumeration knob to describe the mathModes 77 | static const char* const mathModeNames[] = {"stops", "multiply", 0}; 78 | //name given to the knob that acts on all layers 79 | static const char* const MASTER_KNOB_NAME = "master"; 80 | /** 81 | * Convenience object for storing, accessing, and calculating color knob values specific to GradeBeauty 82 | * 83 | * Most cg render engines output buffers as additive layers, so the math is extremely simple as it is additive only 84 | * 85 | * To keep the pixel engine as light as possible, the relationship between the knobs is calculated once during 86 | * construction as a vector of float pointers pointing back to the main private float values 87 | * 88 | * So each knob represents a possible cg layer, has it's own float[3], and a mapping of pointers to other values that could be relevant. 89 | * 90 | * In conjunction with a chosen mathModes enum value: 91 | * 92 | * - calculates the multiply value for a layer name for a given math mode 93 | * - calculates if the stored value is default 94 | * - stores relevant values, as to keep any logic out of the pixel_engine 95 | */ 96 | class GradeBeautyValueMap { 97 | 98 | private: 99 | map m_valueMap; 100 | map> ptrValueMap; 101 | public: 102 | map multipliers; 103 | vector m_colorKnobs; 104 | 105 | //initializes the layer value mapping and interconnect between of layers 106 | GradeBeautyValueMap() 107 | { 108 | LayerMap layerMapBeautyShading = LayerAlchemy::layerCollection.categorizeLayers(layers::shading, categorizeType::pub); 109 | m_colorKnobs.reserve(categories::all.size() + 1); 110 | float* ptrMaster = m_valueMap[MASTER_KNOB_NAME]; 111 | ptrValueMap[MASTER_KNOB_NAME].emplace_back(ptrMaster); 112 | for (auto iterLayer = layers::all.begin(); iterLayer != layers::all.end(); iterLayer++) 113 | { 114 | ptrValueMap[*iterLayer].emplace_back(m_valueMap[*iterLayer]); 115 | ptrValueMap[*iterLayer].emplace_back(ptrMaster); 116 | } 117 | 118 | for (auto iterLayer = layers::shading.begin(); iterLayer != layers::shading.end(); iterLayer++) 119 | { 120 | for (auto iterGlobal = layers::global.begin(); iterGlobal != layers::global.end(); iterGlobal++) 121 | { 122 | if (layerMapBeautyShading.contains(*iterGlobal)) 123 | { 124 | if (layerMapBeautyShading.isMember(*iterGlobal, *iterLayer)) 125 | { 126 | ptrValueMap[*iterLayer].emplace_back(m_valueMap[*iterGlobal]); 127 | } 128 | } 129 | } 130 | } 131 | //printf("created value map %p\n", (void*) this); 132 | } 133 | 134 | // returns a vector of pointers to all float values associated with this layer at a specific color index 135 | vector layerValuePointersForIndex(const string& layerName, const int& colorIndex) const 136 | { 137 | vector outputVector; 138 | auto it = ptrValueMap.find(layerName); 139 | int amount = it->second.size(); 140 | outputVector.reserve(amount); 141 | for (int idx = 0; idx < amount; idx++) { 142 | outputVector.emplace_back(&it->second[idx][colorIndex]); 143 | } 144 | return outputVector; 145 | } 146 | 147 | //returns the pointer specific to this layer 148 | float* getLayerFloatPointer(const string& knobName) const 149 | { 150 | return ptrValueMap.find(knobName)->second[0]; // the first index if where the layer pointer is. 151 | } 152 | 153 | // computes, for a given layer name, the total value to multiply the pixels with 154 | // at a specific color index for any given math mode 155 | float getLayerMultiplier(const string& layerName, const int& colorIndex, const int& mode) const 156 | { 157 | vector values = layerValuePointersForIndex(layerName, colorIndex); 158 | float out = 0.0f; 159 | if (mode == GRADE_BEAUTY_MATH_MODE::STOPS) { 160 | for (auto iterV = values.begin(); iterV != values.end(); iterV++) { 161 | out += *(*iterV); 162 | } 163 | out = (out > 0 ? std::pow(2, out) : 1.0f / pow(4, (fabsf(fabsf(out) / 2.0f)))); 164 | } else if (mode == GRADE_BEAUTY_MATH_MODE::MULTIPLY) { 165 | out = 1.0f; 166 | for (auto iterV = values.begin(); iterV != values.end(); iterV++) { 167 | if (iterV != values.begin()) { // skip the actual layer, it multiplies 168 | out *= *(*iterV); 169 | } 170 | } 171 | out *= getLayerFloatPointer(layerName)[colorIndex]; 172 | } 173 | return out; 174 | } 175 | bool isDefault(const string& layerName, const int& mode) const 176 | { 177 | float sum = 0; 178 | for (int colorIndex = 0; colorIndex < 3; colorIndex++ ){ 179 | sum += getLayerFloatPointer(layerName)[colorIndex]; 180 | } 181 | bool isDefault = (sum == (float) mode); 182 | return isDefault; 183 | } 184 | void addColorKnob(DD::Image::Knob* knob) 185 | { 186 | m_colorKnobs.emplace_back(knob); 187 | } 188 | }; 189 | 190 | class GradeBeauty : public DD::Image::PixelIop { 191 | private: 192 | bool m_firstRun {true}; 193 | LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData; 194 | int m_mathMode {GRADE_BEAUTY_MATH_MODE::STOPS}; 195 | bool m_clampBlack {true}; 196 | bool m_beautyDiff {true}; 197 | ChannelSet m_targetLayer {Mask_RGB}; 198 | GradeBeautyValueMap m_valueMap; 199 | // utility function to create color knobs for this node 200 | Knob* createColorKnob(Knob_Callback, float*, const string&, const bool&); 201 | // utility function to set color knob ranges, uses the integer value of mathModes as the center 202 | // can reset values with the bool parameter 203 | void setKnobRanges(const int&, const bool&); 204 | // utility function that handles the visibility of knobs based on the instance's state 205 | void setKnobVisibility(); 206 | // utility function that handles the setting of the knob's default value, uses python, couldn't find a c++ equivalent 207 | void setKnobDefaultValue(DD::Image::Op* nukeOpPtr); 208 | //this function simply tests that the private vector of color knob pointers is complete 209 | bool colorKnobsPopulated() const; 210 | // this calculates the multiply value for layers for the pixel engine. 211 | void calculateLayerValues(const DD::Image::ChannelSet&, GradeBeautyValueMap&); 212 | // channel set that contains all channels that are modified by the node 213 | 214 | public: 215 | void knobs(Knob_Callback); 216 | void _validate(bool); 217 | bool pass_transform() const {return true;} 218 | void in_channels(int, ChannelSet&) const; 219 | void pixel_engine(const Row&, int, int, int, ChannelMask, Row&); 220 | int knob_changed(Knob*); 221 | const char* Class() const {return description.name;} 222 | const char* node_help() const {return HELP;} 223 | static const Iop::Description description; 224 | GradeBeauty(Node* node); 225 | ~GradeBeauty(); 226 | // channel set that contains all channels that are modified by the node 227 | ChannelSet activeChannelSet() const; 228 | // pixel engine function when anything but the target layer is requested to render 229 | void channelPixelEngine(const Row&, int, int, int, ChannelMask, Row&); 230 | // pixel engine function when the target layer is requested to render 231 | void beautyPixelEngine(const Row&, int y, int x, int r, ChannelSet&, Row&); 232 | GradeBeauty* firstGradeBeauty(); 233 | 234 | }; 235 | 236 | GradeBeauty::GradeBeauty(Node* node) : PixelIop(node) {} 237 | 238 | static Op* build(Node* node) 239 | { 240 | return (new NukeWrapper(new GradeBeauty(node)))->noChannels()->noUnpremult()->mixLuminance(); 241 | } 242 | 243 | GradeBeauty::~GradeBeauty() {} 244 | 245 | const Iop::Description GradeBeauty::description( 246 | "GradeBeauty", 247 | "LayerAlchemy/GradeBeauty", 248 | build 249 | ); 250 | 251 | GradeBeauty* GradeBeauty::firstGradeBeauty() 252 | { 253 | return static_cast( this->firstOp() ); 254 | } 255 | 256 | void GradeBeauty::in_channels(int input_number, ChannelSet& mask) const 257 | { 258 | mask += ChannelMask(activeChannelSet()); 259 | } 260 | 261 | ChannelSet GradeBeauty::activeChannelSet() const 262 | { 263 | ChannelSet outChans; 264 | foreach(z, ChannelSet(m_targetLayer + m_lsKnobData.m_selectedChannels)) 265 | { 266 | int chanIdx = colourIndex(z); 267 | if (chanIdx <= 2) { 268 | outChans += z; 269 | } 270 | } 271 | return outChans; 272 | } 273 | 274 | void GradeBeauty::_validate(bool for_real) 275 | { 276 | copy_info(); // this copies the input info to the output 277 | ChannelSet inChannels = info_.channels(); 278 | LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2); 279 | 280 | if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, CategorizeFilterAllBeauty)) 281 | { 282 | updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, CategorizeFilterAllBeauty); 283 | setKnobVisibility(); 284 | setKnobRanges(m_mathMode, false); 285 | setKnobDefaultValue(this); 286 | _validate(true); // this will refresh the node UI in case the node was blank 287 | } 288 | ChannelSet activeChannels = activeChannelSet(); 289 | calculateLayerValues(activeChannels - m_targetLayer, m_valueMap); 290 | set_out_channels(activeChannels); 291 | info_.turn_on(m_targetLayer); 292 | } 293 | 294 | void GradeBeauty::channelPixelEngine(const Row& in, int y, int x, int r, ChannelMask channels, Row& aRow) 295 | { 296 | map aovFloatPtrChannelMap; 297 | foreach(channel, channels) 298 | { 299 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 300 | aovFloatPtrChannelMap[channel] = aRow.writable(channel); 301 | } 302 | 303 | foreach(channel, channels) 304 | { 305 | unsigned chanIdx = colourIndex(channel); 306 | string layerName = getLayerName(channel); 307 | float* outAovValue = aovFloatPtrChannelMap[channel]; 308 | const float* inAovValue = in[channel]; 309 | 310 | float multValue = m_valueMap.multipliers[channel]; 311 | 312 | for (unsigned X = x; X < r; X++) { 313 | float origValue = inAovValue[X]; 314 | if (m_clampBlack) 315 | { 316 | outAovValue[X] = std::max(0.0f, (origValue * multValue)); 317 | } else { 318 | outAovValue[X] = origValue * multValue; 319 | } 320 | } 321 | } 322 | } 323 | 324 | void GradeBeauty::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow) 325 | { 326 | ChannelSet bty = m_targetLayer.intersection(channels); 327 | ChannelSet aovs = m_lsKnobData.m_selectedChannels.intersection(channels); 328 | 329 | map btyPtrIdxMap; 330 | map aovFloatPtrChannelMap; 331 | map aovConstFloatPtrChannelMap; 332 | 333 | foreach(channel, bty) { 334 | unsigned chanIdx = colourIndex(channel); 335 | float* rowBtyChan; 336 | if (m_beautyDiff) 337 | { 338 | LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow); 339 | rowBtyChan = aRow.writable(channel); 340 | } else 341 | { 342 | rowBtyChan = aRow.writableConstant(0.0f, channel); 343 | } 344 | btyPtrIdxMap[chanIdx] = rowBtyChan; 345 | } 346 | 347 | foreach(channel, aovs) { 348 | aovFloatPtrChannelMap[channel] = aRow.writable(channel); 349 | aovConstFloatPtrChannelMap[channel] = in[channel]; 350 | } 351 | 352 | for (const auto& kvp : btyPtrIdxMap) 353 | { 354 | unsigned btyChanIdx = kvp.first; 355 | float* aRowBty = kvp.second; 356 | 357 | foreach(aov, aovs) 358 | { 359 | unsigned aovChanIdx = colourIndex(aov); 360 | if (btyChanIdx != aovChanIdx) 361 | { 362 | continue; 363 | } 364 | float* aRowBty = btyPtrIdxMap[aovChanIdx]; 365 | float* aRowAov = aovFloatPtrChannelMap[aov]; 366 | const float* inAov = aovConstFloatPtrChannelMap[aov]; 367 | 368 | for (int X = x; X < r; X++) 369 | { 370 | float aovPixel = aRowAov[X]; 371 | float btyPixel = aRowBty[X]; 372 | if (m_beautyDiff) 373 | { 374 | float aovInPixel = inAov[X]; 375 | btyPixel -= aovInPixel; 376 | } 377 | float result = btyPixel + aovPixel; 378 | aRowBty[X] = m_clampBlack ? std::max(0.0f, result) : result; 379 | } 380 | } 381 | } 382 | } 383 | 384 | void GradeBeauty::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) 385 | { 386 | ChannelSet inChannels = ChannelSet(channels); 387 | Row aRow(x, r); 388 | bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size(); 389 | if (isTargetLayer) 390 | { 391 | ChannelSet activeChannels = activeChannelSet(); 392 | channelPixelEngine(in, y, x, r, activeChannels - m_targetLayer, aRow); 393 | beautyPixelEngine(in, y, x, r, activeChannels, aRow); 394 | } 395 | else 396 | { 397 | channelPixelEngine(in, y, x, r, inChannels, aRow); 398 | } 399 | LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out); 400 | } 401 | 402 | void GradeBeauty::knobs(Knob_Callback f) 403 | { 404 | bool colorKnobVectorComplete = colorKnobsPopulated(); 405 | 406 | LayerAlchemy::LayerSetKnob::LayerSetKnob(f, m_lsKnobData); 407 | LayerAlchemy::Knobs::createDocumentationButton(f); 408 | LayerAlchemy::Knobs::createColorKnobResetButton(f); 409 | LayerAlchemy::Knobs::createVersionTextKnob(f); 410 | Divider(f, 0); // separates layer set knobs from the rest 411 | 412 | Input_ChannelMask_knob(f, &m_targetLayer, 0, "target layer"); 413 | SetFlags(f, Knob::NO_ALPHA_PULLDOWN); 414 | ClearFlags(f, Knob::STARTLINE); 415 | Tooltip(f, "

Selects which layer to pre-subtract layers from (if enabled) and add the modified layers to

"); 416 | 417 | Enumeration_knob(f, &m_mathMode, mathModeNames, "math_type", "math type"); 418 | Tooltip(f, 419 | "

multiply : this mode acts like a chain of multiply nodes:

" 420 | "

master * all global contributions (if any) * layer

" 421 | "

since the layer is multiplied last, you can also use this mode to disable the layer

" 422 | "

stops : this mode acts like a single exposure node for each layer

" 423 | "

it adds all contributions and then does the exposure conversion

" 424 | "

(for example, if master is set to 1.0 and the layer is set to 0.0, this means one stop over)

"); 425 | SetFlags(f, Knob::ALWAYS_SAVE); 426 | 427 | Bool_knob(f, &m_beautyDiff, "subtract", "subtract layer set from target layer"); 428 | Tooltip(f, 429 | "

enabled : the additive sum of the chosen layer set is subtracted from the target layer " 430 | "before recombining with this node's modifications

" 431 | "

(this means any difference between the target layer " 432 | "and the render layers is kept in the final output)

"); 433 | 434 | Bool_knob(f, &m_clampBlack, "black_clamp", "black clamp"); 435 | Tooltip(f, 436 | "

enabled : clamp negative values from all output layers

" 437 | "

disabled : negative values permitted

"); 438 | 439 | Divider(f, 0); // separates master from the rest 440 | 441 | Knob* masterKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(MASTER_KNOB_NAME), MASTER_KNOB_NAME, true); 442 | Tooltip(f, "this knob contributes to each layer"); 443 | if (!colorKnobVectorComplete) 444 | { 445 | m_valueMap.addColorKnob(masterKnob); 446 | } 447 | 448 | Divider(f, 0); // separates master from the rest 449 | 450 | for (auto iterLayerName = layers::nonShading.begin(); iterLayerName != layers::nonShading.end(); iterLayerName++) 451 | { 452 | Knob* aovKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(*iterLayerName), *iterLayerName, false); 453 | Tooltip(f, "applies to this layer or to layers in this layer set"); 454 | if (!colorKnobVectorComplete) 455 | { 456 | m_valueMap.addColorKnob(aovKnob); 457 | } 458 | } 459 | BeginClosedGroup(f, "shading_group", "beauty shading layers"); 460 | SetFlags(f, Knob::HIDDEN); 461 | 462 | for (auto iterLayerName = layers::shading.begin(); iterLayerName != layers::shading.end(); iterLayerName++) 463 | { 464 | Knob* aovKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(*iterLayerName), *iterLayerName, false); 465 | Tooltip(f, "apply to this layer only"); 466 | if (!colorKnobVectorComplete) 467 | { 468 | m_valueMap.addColorKnob(aovKnob); 469 | } 470 | } 471 | EndGroup(f); 472 | Divider(f, 0); // separates NukeWrapper knobs created after this 473 | 474 | // Toolbar 475 | BeginToolbar(f, "toolbar", "toolbar"); 476 | Link_knob(f, MASTER_KNOB_NAME, "viewerLink", "GradeBeauty master"); 477 | SetFlags(f, Knob::HIDE_ANIMATION_AND_VIEWS | Knob::NO_UNDO | Knob::NO_RERENDER | Knob::STARTLINE); 478 | Link_knob(f, "reset values", "", "reset values"); 479 | EndToolbar(f); 480 | } 481 | 482 | int GradeBeauty::knob_changed(Knob* k) 483 | { 484 | if (k->is("math_type")) 485 | { 486 | setKnobRanges(m_mathMode, true); 487 | setKnobDefaultValue(this); 488 | } 489 | if (k == &DD::Image::Knob::inputChange) 490 | { 491 | _validate(true); 492 | } 493 | return 1; 494 | } 495 | 496 | bool GradeBeauty::colorKnobsPopulated() const 497 | { 498 | return (m_valueMap.m_colorKnobs.size() >= (categories::all.size() + 1)); 499 | } 500 | 501 | Knob* GradeBeauty::createColorKnob(Knob_Callback f, float* valueStore, const string& name, const bool& visible) 502 | { 503 | const char* knobName = name.c_str(); 504 | Knob* colorKnob = Color_knob(f, valueStore, IRange(COLOR_KNOB_RANGES[this->m_mathMode][0], COLOR_KNOB_RANGES[m_mathMode][1]), knobName, knobName); 505 | SetFlags(f, Knob::LOG_SLIDER); 506 | SetFlags(f, Knob::NO_COLOR_DROPDOWN); 507 | if (f.makeKnobs()) { 508 | colorKnob->visible(visible); 509 | } 510 | return colorKnob; 511 | } 512 | 513 | void GradeBeauty::setKnobRanges(const int& modeValue, const bool& reset) 514 | { 515 | const char* script = (modeValue == 0 ? "{0}" : "{1}"); 516 | for (auto iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++) 517 | { 518 | if (reset) { 519 | (*iterKnob)->from_script(script); 520 | } 521 | (*iterKnob)->set_range(COLOR_KNOB_RANGES[modeValue][0], COLOR_KNOB_RANGES[modeValue][1], true); 522 | } 523 | } 524 | 525 | void GradeBeauty::setKnobVisibility() 526 | { 527 | bool isBeautyShading = LayerAlchemy::LayerSetKnob::getLayerSetKnobEnumString(this) == categories::shading; 528 | 529 | auto layerNames = LayerAlchemy::LayerSet::getLayerNames(m_lsKnobData.m_selectedChannels); 530 | LayerMap categorized = LayerAlchemy::layerCollection.categorizeLayers(layerNames, categorizeType::pub); 531 | 532 | for (vector::const_iterator iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++) 533 | { 534 | Knob* colorKnob = *iterKnob; 535 | string knobName = colorKnob->name(); 536 | bool isGlobalLayer = categorized.contains(knobName); 537 | bool contains = categorized.isMember("all", knobName); 538 | if (knobName == MASTER_KNOB_NAME) 539 | { 540 | contains = true; 541 | } 542 | else if (isBeautyShading && isGlobalLayer && categorized.contains(knobName)) 543 | { 544 | contains = true; 545 | } 546 | 547 | if (contains) 548 | { 549 | colorKnob->clear_flag(Knob::DO_NOT_WRITE); 550 | colorKnob->set_flag(Knob::ALWAYS_SAVE); 551 | } else // this is run on knobs that are not visible to the user 552 | { 553 | colorKnob->clear_flag(Knob::ALWAYS_SAVE); 554 | colorKnob->set_flag(Knob::DO_NOT_WRITE); 555 | if (firstGradeBeauty()->m_firstRun) 556 | { 557 | colorKnob->set_value(m_mathMode); // make sure the unsaved knobs are the correct default 558 | } 559 | } 560 | colorKnob->visible(contains); 561 | } 562 | knob("shading_group")->visible(isBeautyShading); 563 | firstGradeBeauty()->m_firstRun = false; 564 | } 565 | 566 | void GradeBeauty::setKnobDefaultValue(DD::Image::Op* nukeOpPtr) 567 | { 568 | nukeOpPtr->script_command(DEFAULT_VALUE_PYSCRIPT, true, false); 569 | } 570 | 571 | void GradeBeauty::calculateLayerValues(const DD::Image::ChannelSet& channels, GradeBeautyValueMap& valueMap) 572 | { 573 | foreach(channel, channels) { 574 | int chanIdx = colourIndex(channel); 575 | string layerName = getLayerName(channel); 576 | m_valueMap.multipliers[channel] = m_valueMap.getLayerMultiplier(layerName, chanIdx, m_mathMode); 577 | } 578 | } 579 | } // End namespace GradeBeauty 580 | --------------------------------------------------------------------------------