diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 16f3d5919b..73281b6035 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -31,12 +31,21 @@ jobs:
compiler_version: "14"
python: 3.9
- - name: Linux_GCC_14_Python311
+ - name: Linux_GCC_14_Python311_expand_lib
os: ubuntu-24.04
compiler: gcc
compiler_version: "14"
python: 3.11
test_render: ON
+ cmake_config: -DMATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS=ON -DMATERIALX_BUILD_BAKE_NAMED_VALUES=ON
+
+ - name: Linux_GCC_14_Python311_unexpanded_lib
+ os: ubuntu-24.04
+ compiler: gcc
+ compiler_version: "14"
+ python: 3.11
+ test_render: ON
+ cmake_config: -DMATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS=OFF -DMATERIALX_BUILD_BAKE_NAMED_VALUES=OFF
- name: Linux_GCC_CoverageAnalysis
os: ubuntu-24.04
diff --git a/.gitignore b/.gitignore
index f2d3d5dccd..62ac061f6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
build
dist
+dev_spec/dest
+dev_spec/nodes
.DS_Store
+.cache
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c9836e1225..5b3b6d03a8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,6 +51,8 @@ option(MATERIALX_BUILD_BENCHMARK_TESTS "Build benchmark tests." OFF)
option(MATERIALX_BUILD_SHARED_LIBS "Build MaterialX libraries as shared rather than static." OFF)
option(MATERIALX_BUILD_DATA_LIBRARY "Build generated products from the MaterialX data library." OFF)
+option(MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS "Process the data library files at build time to expand any template elements." ON)
+option(MATERIALX_BUILD_BAKE_NAMED_VALUES "Process the data library files at build time to bake out the named values." ON)
option(MATERIALX_BUILD_MONOLITHIC "Build a single monolithic MaterialX library." OFF)
option(MATERIALX_BUILD_USE_CCACHE "Enable the use of ccache to speed up build time, if present." ON)
option(MATERIALX_PYTHON_LTO "Enable link-time optimizations for MaterialX Python." ON)
@@ -68,6 +70,10 @@ if (MATERIALX_BUILD_IOS)
set(CMAKE_SYSTEM_NAME iOS)
endif()
+list(APPEND CMAKE_MODULE_PATH
+ ${PROJECT_SOURCE_DIR}/cmake/macros)
+include(Public)
+
# Apple ecosystem cross-compilation
# https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos
set(MATERIALX_BUILD_APPLE_EMBEDDED OFF)
@@ -306,124 +312,6 @@ else()
endif()
endif()
-# Shared functions
-function(assign_source_group prefix)
- foreach(_source IN ITEMS ${ARGN})
- if(IS_ABSOLUTE "${_source}")
- file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}")
- else()
- set(_source_rel "${_source}")
- endif()
- get_filename_component(_source_path "${_source_rel}" PATH)
- string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
- source_group("${prefix}\\${_source_path_msvc}" FILES "${_source}")
- endforeach()
-endfunction(assign_source_group)
-
-function(mx_add_library MATERIALX_MODULE_NAME)
- set(options ADD_OBJECTIVE_C_CODE)
- set(oneValueArgs EXPORT_DEFINE)
- set(multiValueArgs
- SOURCE_FILES
- HEADER_FILES
- INLINED_FILES
- MTLX_MODULES)
- cmake_parse_arguments(args
- "${options}"
- "${oneValueArgs}"
- "${multiValueArgs}"
- ${ARGN})
-
- if (APPLE AND args_ADD_OBJECTIVE_C_CODE)
- file(GLOB_RECURSE materialx_source_oc "${CMAKE_CURRENT_SOURCE_DIR}/*.m*")
- set_source_files_properties(${materialx_source_oc} PROPERTIES
- COMPILE_FLAGS "-x objective-c++")
- set(args_SOURCE_FILES ${args_SOURCE_FILES} ${materialx_source_oc})
- endif()
-
- assign_source_group("Source Files" ${args_SOURCE_FILES})
- assign_source_group("Source Files" ${args_INLINED_FILES})
- assign_source_group("Header Files" ${args_HEADER_FILES})
-
- if (NOT MATERIALX_BUILD_MONOLITHIC)
- set(TARGET_NAME ${MATERIALX_MODULE_NAME})
- add_library(${TARGET_NAME})
-
- # Create version resource
- if(MATERIALX_BUILD_SHARED_LIBS AND MSVC)
- configure_file(${PROJECT_SOURCE_DIR}/cmake/modules/MaterialXVersion.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
- target_sources(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
- endif()
-
- target_link_libraries(${TARGET_NAME}
- PUBLIC
- ${args_MTLX_MODULES}
- ${CMAKE_DL_LIBS})
-
- target_include_directories(${TARGET_NAME}
- PUBLIC
- $
- $
- PRIVATE
- ${EXTERNAL_INCLUDE_DIRS})
-
- set_target_properties(
- ${TARGET_NAME} PROPERTIES
- OUTPUT_NAME ${MATERIALX_MODULE_NAME}${MATERIALX_LIBNAME_SUFFIX}
- COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}"
- LINK_FLAGS "${EXTERNAL_LINK_FLAGS}"
- INSTALL_RPATH "${MATERIALX_SAME_DIR_RPATH}"
- VERSION "${MATERIALX_LIBRARY_VERSION}"
- SOVERSION "${MATERIALX_MAJOR_VERSION}")
- else()
- set(TARGET_NAME ${MATERIALX_MONOLITHIC_TARGET})
- add_library(${MATERIALX_MODULE_NAME} ALIAS ${MATERIALX_MONOLITHIC_TARGET})
-
- # Store the aliased MaterialX modules name to create CMake export aliases later.
- set_property(GLOBAL APPEND PROPERTY MATERIALX_MODULES ${MATERIALX_MODULE_NAME})
- endif()
-
- set_target_properties(${TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
- set_target_properties(${TARGET_NAME} PROPERTIES CMAKE_VISIBILITY_INLINES_HIDDEN 1)
-
- target_sources(${TARGET_NAME}
- PRIVATE
- ${args_SOURCE_FILES}
- PUBLIC
- FILE_SET
- mxHeaders
- TYPE
- HEADERS
- BASE_DIRS
- ${CMAKE_CURRENT_SOURCE_DIR}/..
- ${CMAKE_CURRENT_BINARY_DIR}/..
- FILES
- ${args_HEADER_FILES}
- ${args_INLINED_FILES})
-
- target_include_directories(${TARGET_NAME} PUBLIC
- $)
-
- target_compile_definitions(${TARGET_NAME} PRIVATE "-D${args_EXPORT_DEFINE}")
-
- if(NOT SKBUILD)
- if(NOT MATERIALX_BUILD_MONOLITHIC)
- install(TARGETS ${MATERIALX_MODULE_NAME}
- EXPORT MaterialX
- ARCHIVE DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
- LIBRARY DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
- RUNTIME DESTINATION bin
- FILE_SET mxHeaders DESTINATION ${MATERIALX_INSTALL_INCLUDE_PATH})
- endif()
-
- install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${MATERIALX_MODULE_NAME}.pdb"
- DESTINATION "${MATERIALX_INSTALL_LIB_PATH}/" OPTIONAL)
- endif()
-
- # Pass TARGET_NAME back to call site, so the caller can modify the build target.
- set(TARGET_NAME ${TARGET_NAME} PARENT_SCOPE)
-endfunction()
-
# Propagate shared library setting to NanoGUI
if(MATERIALX_BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS "ON")
@@ -431,15 +319,57 @@ else()
set(BUILD_SHARED_LIBS "OFF")
endif()
+# If we're baking the named "Value:" entries - then we have to also expand any
+# template elements
+if (MATERIALX_BUILD_BAKE_NAMED_VALUES)
+ set(MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS ON)
+endif()
+
+if (MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS OR MATERIALX_BUILD_BAKE_NAMED_VALUES)
+ set(MATERIALX_BUILD_DATA_LIBRARY ON)
+endif()
+
# Build a monolithic target - needs to be added before the other build targets that may be included.
if (MATERIALX_BUILD_MONOLITHIC)
set(MATERIALX_MONOLITHIC_TARGET MaterialX)
add_subdirectory(source)
endif()
+message("XXX : MATERIALX_BUILD_BAKE_NAMED_VALUES : ${MATERIALX_BUILD_BAKE_NAMED_VALUES}")
+message("XXX : MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS : ${MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS}")
+message("XXX : MATERIALX_BUILD_DATA_LIBRARY : ${MATERIALX_BUILD_DATA_LIBRARY}")
+
# Add core subdirectories
add_subdirectory(source/MaterialXCore)
add_subdirectory(source/MaterialXFormat)
+if (MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS OR MATERIALX_BUILD_BAKE_NAMED_VALUES)
+ if (CMAKE_CROSSCOMPILING)
+ set(_MaterialXBuildLibrary "${CMAKE_BINARY_DIR}/BUILD_TOOLS/MaterialXBuildLibrary")
+
+ add_custom_command(
+ OUTPUT ${_MaterialXBuildLibrary}
+ COMMAND ${CMAKE_COMMAND}
+ -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/BUILD_TOOLS
+ -DMATERIALX_MAJOR_VERSION=${MATERIALX_MAJOR_VERSION}
+ -DMATERIALX_MINOR_VERSION=${MATERIALX_MINOR_VERSION}
+ -DMATERIALX_BUILD_VERSION=${MATERIALX_BUILD_VERSION}
+ -DMATERIALX_NAMESPACE=${MATERIALX_NAMESPACE}
+ -DMATERIALX_BUILD_BAKE_NAMED_VALUES=ON
+ -B"BUILD_TOOLS"
+ -H"${CMAKE_SOURCE_DIR}/source/MaterialXBuildTools"
+ COMMAND ${CMAKE_COMMAND} --build BUILD_TOOLS
+ DEPENDS MaterialXCore MaterialXFormat
+ )
+ add_custom_target(MaterialXBuildLibrary ALL
+ DEPENDS ${_MaterialXBuildLibrary})
+ else()
+ set(_MaterialXBuildLibrary MaterialXBuildLibrary)
+ add_subdirectory(source/MaterialXBuildTools/buildLibrary)
+ add_subdirectory(source/MaterialXBuildTools/buildDocs)
+ add_subdirectory(dev_spec)
+ endif()
+endif()
# Add shader generation subdirectories
add_subdirectory(source/MaterialXGenShader)
diff --git a/cmake/macros/Public.cmake b/cmake/macros/Public.cmake
new file mode 100644
index 0000000000..e21c2f7e2a
--- /dev/null
+++ b/cmake/macros/Public.cmake
@@ -0,0 +1,119 @@
+# Shared functions need to be extracted to allow them to be shared with the subproject
+# in source/MaterialXBuildTools
+
+function(assign_source_group prefix)
+ foreach(_source IN ITEMS ${ARGN})
+ if(IS_ABSOLUTE "${_source}")
+ file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}")
+ else()
+ set(_source_rel "${_source}")
+ endif()
+ get_filename_component(_source_path "${_source_rel}" PATH)
+ string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
+ source_group("${prefix}\\${_source_path_msvc}" FILES "${_source}")
+ endforeach()
+endfunction(assign_source_group)
+
+function(mx_add_library MATERIALX_MODULE_NAME)
+ set(options ADD_OBJECTIVE_C_CODE)
+ set(oneValueArgs EXPORT_DEFINE)
+ set(multiValueArgs
+ SOURCE_FILES
+ HEADER_FILES
+ INLINED_FILES
+ MTLX_MODULES)
+ cmake_parse_arguments(args
+ "${options}"
+ "${oneValueArgs}"
+ "${multiValueArgs}"
+ ${ARGN})
+
+ if (APPLE AND args_ADD_OBJECTIVE_C_CODE)
+ file(GLOB_RECURSE materialx_source_oc "${CMAKE_CURRENT_SOURCE_DIR}/*.m*")
+ set_source_files_properties(${materialx_source_oc} PROPERTIES
+ COMPILE_FLAGS "-x objective-c++")
+ set(args_SOURCE_FILES ${args_SOURCE_FILES} ${materialx_source_oc})
+ endif()
+
+ assign_source_group("Source Files" ${args_SOURCE_FILES})
+ assign_source_group("Source Files" ${args_INLINED_FILES})
+ assign_source_group("Header Files" ${args_HEADER_FILES})
+
+ if (NOT MATERIALX_BUILD_MONOLITHIC)
+ set(TARGET_NAME ${MATERIALX_MODULE_NAME})
+ add_library(${TARGET_NAME})
+
+ # Create version resource
+ if(MATERIALX_BUILD_SHARED_LIBS AND MSVC)
+ configure_file(${PROJECT_SOURCE_DIR}/cmake/modules/MaterialXVersion.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ target_sources(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ endif()
+
+ target_link_libraries(${TARGET_NAME}
+ PUBLIC
+ ${args_MTLX_MODULES}
+ ${CMAKE_DL_LIBS})
+
+ target_include_directories(${TARGET_NAME}
+ PUBLIC
+ $
+ $
+ PRIVATE
+ ${EXTERNAL_INCLUDE_DIRS})
+
+ set_target_properties(
+ ${TARGET_NAME} PROPERTIES
+ OUTPUT_NAME ${MATERIALX_MODULE_NAME}${MATERIALX_LIBNAME_SUFFIX}
+ COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}"
+ LINK_FLAGS "${EXTERNAL_LINK_FLAGS}"
+ INSTALL_RPATH "${MATERIALX_SAME_DIR_RPATH}"
+ VERSION "${MATERIALX_LIBRARY_VERSION}"
+ SOVERSION "${MATERIALX_MAJOR_VERSION}")
+ else()
+ set(TARGET_NAME ${MATERIALX_MONOLITHIC_TARGET})
+ add_library(${MATERIALX_MODULE_NAME} ALIAS ${MATERIALX_MONOLITHIC_TARGET})
+
+ # Store the aliased MaterialX modules name to create CMake export aliases later.
+ set_property(GLOBAL APPEND PROPERTY MATERIALX_MODULES ${MATERIALX_MODULE_NAME})
+ endif()
+
+ set_target_properties(${TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
+ set_target_properties(${TARGET_NAME} PROPERTIES CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+
+ target_sources(${TARGET_NAME}
+ PRIVATE
+ ${args_SOURCE_FILES}
+ PUBLIC
+ FILE_SET
+ mxHeaders
+ TYPE
+ HEADERS
+ BASE_DIRS
+ ${CMAKE_CURRENT_SOURCE_DIR}/..
+ ${CMAKE_CURRENT_BINARY_DIR}/..
+ FILES
+ ${args_HEADER_FILES}
+ ${args_INLINED_FILES})
+
+ target_include_directories(${TARGET_NAME} PUBLIC
+ $)
+
+ target_compile_definitions(${TARGET_NAME} PRIVATE "-D${args_EXPORT_DEFINE}")
+
+ if(NOT SKBUILD)
+ if(NOT MATERIALX_BUILD_MONOLITHIC)
+ install(TARGETS ${MATERIALX_MODULE_NAME}
+ EXPORT MaterialX
+ ARCHIVE DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
+ LIBRARY DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
+ RUNTIME DESTINATION bin
+ FILE_SET mxHeaders DESTINATION ${MATERIALX_INSTALL_INCLUDE_PATH})
+ endif()
+
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${MATERIALX_MODULE_NAME}.pdb"
+ DESTINATION "${MATERIALX_INSTALL_LIB_PATH}/" OPTIONAL)
+ endif()
+
+ # Pass TARGET_NAME back to call site, so the caller can modify the build target.
+ set(TARGET_NAME ${TARGET_NAME} PARENT_SCOPE)
+endfunction()
diff --git a/dev_spec/CMakeLists.txt b/dev_spec/CMakeLists.txt
new file mode 100644
index 0000000000..6d52705575
--- /dev/null
+++ b/dev_spec/CMakeLists.txt
@@ -0,0 +1,31 @@
+
+
+set(MATX_LIBRARIES_DIR ${PROJECT_SOURCE_DIR}/libraries)
+set(DOCS_OUTPUT_DIR ${PROJECT_SOURCE_DIR}/dev_spec/nodes)
+
+set(MD_SRC_DIR ${PROJECT_SOURCE_DIR}/dev_spec/source)
+set(MD_DEST_DIR ${PROJECT_SOURCE_DIR}/dev_spec/dest)
+
+set(NODE_MD_MANIFEST ${DOCS_OUTPUT_DIR}/_manifest.txt)
+
+file(GLOB_RECURSE MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES
+ LIST_DIRECTORIES false
+ ${MATX_LIBRARIES_DIR}/*.mtlx)
+
+file(GLOB_RECURSE MATERIALX_MD_SOURCE_FILES
+ LIST_DIRECTORIES false
+ ${MD_SRC_DIR}/*.md)
+
+add_custom_command(
+ OUTPUT ${NODE_MD_MANIFEST}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${DOCS_OUTPUT_DIR}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${MD_DEST_DIR}
+ COMMAND MaterialXBuildDocs --sourceLibraryRoot ${MATX_LIBRARIES_DIR} --destDocRoot ${DOCS_OUTPUT_DIR} --sourceMDRoot ${MD_SRC_DIR} --destMDRoot ${MD_DEST_DIR} --manifestFile ${NODE_MD_MANIFEST}
+ DEPENDS ${MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES} ${MATERIALX_MD_SOURCE_FILES} MaterialXBuildDocs
+)
+
+add_custom_target(MaterialXBuildSpecNodeTables ALL
+ DEPENDS ${NODE_MD_MANIFEST})
+
+
+
diff --git a/dev_spec/source/nprlib_spec.md b/dev_spec/source/nprlib_spec.md
new file mode 100644
index 0000000000..c487049d97
--- /dev/null
+++ b/dev_spec/source/nprlib_spec.md
@@ -0,0 +1,53 @@
+# NPR Nodes
+
+NPR nodes support both physically-based and non-physically-based rendering.
+
+### viewdirection
+
+The current scene view direction, as defined by the shading environment.
+
+@MX_TABLE_viewdirection@
+
+The view direction is a normalized vector from the camera/eye, to visible objects in the scene. In a PBR shading context, this would only include the primary ray direction, and not any secondary/reflection rays.
+
+### facingratio
+
+The geometric facing ratio of surface normals and the view direction.
+
+@MX_TABLE_facingratio@
+
+Facing ratio is computed as the dot product between the view direction and geometric normal. Output values of 1.0 are facing towards the view direction, while values of -1.0 are facing away. Values of 0 are surface normals perpendicular to the view direction.
+
+### gooch_shade
+
+Computes the color from single-pass shading portion of the Gooch[^Gooch1998] lighting model.
+
+@MX_TABLE_gooch_shade@
+
+Gooch shade provides an illustrative shading effect by blending colors based on the angle between the surface normal and the light direction. It also provides a very simple specular highlight, on top of the warm and cool colors.
+
+The `warm_color` input corresponds to $C_w$ in the model, and represents colors facing towards the `light_direction`.
+
+The `cool_color` input corresponds to $C_c$, and represents colors facing away from the `light_direction`.
+
+The `specular_intensity` input corresponds to $N$, and represents the intensity of the highlight.
+
+The `shininess` input corresponds to $H$, and represents the size of the highlight across surface.
+
+The `light_direction` input corresponds to $L$, and represents the world space direction of the light.
+
+### Gooch Equations
+
+$$M = \frac{1+({N} \cdot {L})}{2}$$
+
+$$D = (1-{M}) * {C_w} + {M} * {C_c}$$
+
+$$E = reflect({V},{N})$$
+
+$$S = ( max(E \cdot -{L}, 0 )^{H} ) * {N}$$
+
+$$out = {D} + {S}$$
+
+# References
+
+[^Gooch1998]: Gooch et al., **A Non-Photorealistic Lighting Model For Automatic Technical Illustration**, , 1998.
\ No newline at end of file
diff --git a/dev_spec/source/pbrlib_spec.md b/dev_spec/source/pbrlib_spec.md
new file mode 100644
index 0000000000..cd46a59a19
--- /dev/null
+++ b/dev_spec/source/pbrlib_spec.md
@@ -0,0 +1,108 @@
+# pbrlib
+
+### oren_nayar_diffuse_bsdf
+Constructs a diffuse reflection BSDF based on the Oren-Nayar reflectance model.
+
+@MX_TABLE_oren_nayar_diffuse_bsdf@
+ |
+
+The `color` input represents the diffuse albedo, and corresponds to $\rho$ (rho) in the Oren-Nayar model.
+
+The `roughness` input corresponds to $\sigma$ (sigma) in the Oren-Nayar model, and a `roughness` of 0.0 produces Lambertian reflectance.
+
+The `energy_compensation` input selects between the Qualitative Oren-Nayar[^Oren1994] and Energy-Preserving Oren-Nayar[^Portsmouth2025] models of diffuse reflectance.
+
+### Qualitative Oren-Nayar Reflectance Equations
+
+$$A=1-0.5\left(\frac{\sigma^2}{\sigma^2+0.33}\right)$$
+
+$$B=1-0.45\left(\frac{\sigma^2}{\sigma^2+0.09}\right)$$
+
+$$g(\omega_i,\omega_o)=\cos_{(\theta_i-\theta_o)}\sin_{{max}(\theta_i,\theta_o)}\tan_{{min}(\theta_i,\theta_o)}$$
+
+$$L_r(\omega_i,\omega_o)=\frac{\rho}{\pi}(A+Bg(\omega_i,\omega_o))$$
+
+### dielectric_bsdf
+Constructs a reflection and/or transmission BSDF based on a microfacet model and a Fresnel curve for dielectrics[^Walter2007].
+
+@MX_TABLE_dielectric_bsdf@
+
+The `tint` input corresponds to $t$ in the dielectric model, and represents a non-physical color weight to tint the reflected and transmitted light.
+
+The `ior` input corresponds to $\eta$ (eta) in the dielectric model, and represents the real-valued index of refraction of the surface.
+
+The `scatter_mode` input selects between `R` (reflection only), `T` (transmission only), and `RT` (both reflection and transmision).
+
+If reflection scattering is enabled, this node may be layered vertically over a base BSDF for the surface beneath the dielectric layer. By chaining multiple `dielectric_bsdf` nodes you can describe a surface with multiple specular lobes.
+
+If transmission scattering is enabled, this node may be layered over a VDF describing the surface interior to handle absorption and scattering inside the medium, useful for colored glass, turbid water, etc.
+
+#### Dielectric Equations
+
+These equations are based on a standard optimized implementation[^Lagarde2013], rather than the original paper[^Walter2007].
+
+$$c=\cos_{\theta}$$
+
+$$g=\sqrt{\eta^2+c^2-1}$$
+
+$$R=t\frac{(g-c)^2}{2(g-c)^2}\left(1+\frac{(c(g+c)-1)^2}{(c(g-c)+1)^2}\right)$$
+
+### generalized_schlick_bsdf
+Constructs a reflection and/or transmission BSDF based on a microfacet model and a generalized Schlick Fresnel curve[^Hoffman2023].
+
+@MX_TABLE_generalized_schlick_bsdf@
+
+The `color0` and `color90` inputs correspond to $r_0$ and $r_{90}$ in the generalized Schlick model.
+
+The `color82` input corresponds to $t$ in the Generalized Schlick model.
+
+The `exponent` input is the exponent for Schlick blending between `color0` and `color90`, and corresponds to $\alpha$ in the generalized Schlick model.
+
+The `scatter_mode` input selects between `R` (reflection only), `T` (transmission only), and `RT` (both reflection and transmision).
+
+If reflection scattering is enabled, this node may be layered vertically over a base BSDF for the surface beneath the generalized Schlick layer. By chaining multiple `generalized_schlick_bsdf` nodes you can describe a surface with multiple specular lobes.
+
+If transmission scattering is enabled, this node may be layered over a VDF describing the surface interior to handle absorption and scattering inside the medium, useful for colored glass, turbid water, etc.
+
+#### Generalized Schlick Equations
+
+$$\theta_{max}=\arccos(\frac{1}{7})$$
+
+$$a=\frac{(r_0+(r_{90}-r_0))(1-\cos_{\theta_{max}})^\alpha(1-t)}{\cos_{\theta_{max}}(1-\cos_{\theta_{max}})^6}$$
+
+$$F_\theta=(r_0+(r_{90}-r_0))(1-\cos_{\theta})^\alpha-a\cos_{\theta}(1-\cos_{\theta})^6$$
+
+### sheen_bsdf
+Constructs a microfacet BSDF for the back-scattering properties of cloth-like materials.
+
+@MX_TABLE_sheen_bsdf@
+
+The `color` input corresponds to $c$ in the sheen model, and represents a non-physical color tint on the sheen lobe.
+
+The `roughness` input corresponds to $r$ in the sheen model, and represents the degree to which the microfibers diverge from the normal direction.
+
+The `mode` option selects between two available sheen models, Conty-Kulla[^Conty2017] and Zeltner[^Zeltner2022].
+
+This node may be layered vertically over a base BSDF for the surface beneath the sheen layer.
+
+#### Conty-Kulla Sheen Equations
+
+$$D_\theta=\frac{(2 + \frac{1}{r})(1 - {\cos_\theta}^2)^\frac{0.5}{r}}{2\pi}$$
+
+$$L_r(\omega_i,\omega_o,\omega_h)=\frac{cD(\theta_h)}{cos{\theta_i}+cos{\theta_o}-cos{\theta_i}cos{\theta_o}}$$
+
+# References
+
+[^Conty2017]: Alejandro Conty, Christopher Kulla, **Production Friendly Microfacet Sheen BRDF**, , 2017
+
+[^Hoffman2023]: Naty Hoffman, **Generalization of Adobe's Fresnel Model**, , 2023
+
+[^Lagarde2013]: Sébastien Lagarde, **Memo on Fresnel equations**, https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/, 2013
+
+[^Oren1994]: Michael Oren, Shree K. Nayar, **Generalization of Lambert’s Reflectance Model**, , 1994
+
+[^Portsmouth2025]: Portsmouth et al., **EON: A practical energy-preserving rough diffuse BRDF**, , 2025.
+
+[^Walter2007]: Bruce Walter et al., **Microfacet Models for Refraction through Rough Surfaces**, , 2007
+
+[^Zeltner2022]: Tizian Zeltner et al., **Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines**, , 2022
\ No newline at end of file
diff --git a/dev_spec/source/placeholder.md b/dev_spec/source/placeholder.md
new file mode 100644
index 0000000000..4d43737a98
--- /dev/null
+++ b/dev_spec/source/placeholder.md
@@ -0,0 +1,1779 @@
+## Place holder for specification document
+
+To generate the markdown table for the node ports just add the string `@MX_TABLE_@` in the markdown file.
+
+
+## Texture Nodes
+
+### image
+Samples data from a single image, or from a layer within a multi-layer image.
+
+@MX_TABLE_image@
+
+---
+
+### tiledimage
+Samples data from a single image, with provisions for tiling and offsetting the image across uv space.
+
+@MX_TABLE_tiledimage@
+
+---
+
+### latlongimage
+Samples an equiangular map along a view direction with adjustable latitudinal offset.
+
+@MX_TABLE_latlongimage@
+
+---
+
+### triplanarprojection
+Samples data from three images (or layers within multi-layer images), and projects a tiled representation of the images along each of the three respective coordinate axes, computing a weighted blend of the three samples using the geometric normal.
+
+@MX_TABLE_triplanarprojection@
+
+
+## Procedural Nodes
+
+### constant
+Outputs a constant value.
+
+@MX_TABLE_constant@
+
+The output, `out`, of this node corresponds directly to its input, `value`, in both value and type.
+
+$$out = value$$
+
+---
+
+### ramplr
+A left-to-right linear value ramp
+
+@MX_TABLE_ramplr@
+
+The ramp interpolation is calculated using the variable `U`, which corresponds to the `x` co-ordinate of the `texcoord` input.
+
+$$U = Clamp(texcoord.x, 0.0, 1.0)$$
+
+By default the first set of UV coordinates will be used for `texcoord`.
+
+The output, `out`, of this node is the linear interpolation of `value_l` and `value_r` with respect to interpolant parameter `U`:
+
+\[
+\begin{align*}
+U &= \text{Clamp}(\text{texcoord}_x, 0.0, 1.0) \\
+\text{out}_i &= \text{value}_{r_i} \cdot U + \text{value}_{l_i} \cdot (1 - U)
+\end{align*}
+\]
+
+---
+
+### ramptb
+A top-to-bottom linear value ramp.
+
+@MX_TABLE_ramptb@
+
+The ramp interpolation is calculated using the variable `V`, which corresponds to the `y` co-ordinate of the `texcoord` input.
+
+$$V = Clamp(texcoord.y, 0.0, 1.0)$$
+
+By default the first set of UV coordinates will be used for `texcoord`.
+
+The output, `out`, of this node is the linear interpolation of `value_l` and `value_r` with respect to interpolant parameter `V`:
+
+\[
+\begin{align*}
+V &= \text{Clamp}(\text{texcoord}_y, 0.0, 1.0) \\
+\text{out}_i &= \text{value}_{r_i} \cdot V + \text{value}_{l_i} \cdot (1 - V)
+\end{align*}
+\]
+
+---
+
+### ramp4
+A 4-corner bilinear value ramp.
+
+@MX_TABLE_ramp4@
+
+
+The output, `out`, is given by a bilinear interpolation of `valuetl`, `valuetr`, `valuebl`, `valuebr`, with respect to interpolant parameters derived from the `texcoord` parameter
+
+
+\[
+\begin{align*}
+S &= \text{Clamp}(\text{texcoord}_x, 0.0, 1.0) \\
+T &= \text{Clamp}(\text{texcoord}_y, 0.0, 1.0) \\
+\text{Top}_{\text{mix}} &= \text{Mix}(\text{valuetl}, \text{valuetr}, S) \\
+\text{Bottom}_{\text{mix}} &= \text{Mix}(\text{valuebl}, \text{valuebr}, S) \\
+\text{out} &= \text{Mix}(\text{Top}_{\text{mix}}, \text{Bottom}_{\text{mix}}, T)
+\end{align*}
+\]
+
+---
+
+### splitlr
+A left-right split matte, split at a specified `U` value.
+
+@MX_TABLE_splitlr@
+
+The output, `out`, is given by linear interpolation of `value_l` and `value_r` with respect to linear interpolant, `T`, given by:
+
+\[
+\begin{align*}
+T &= \text{aastep}(\text{center}, \text{texcoord}_x) \\
+\text{out}_i &= \text{value}_{r_i} \cdot T + \text{value}_l \cdot (1 - T)
+\end{align*}
+\]
+
+---
+
+### splittb
+A top-bottom split matte, split at a specified `V`` value.
+
+@MX_TABLE_splittb@
+
+The output, `out`, is given by linear interpolation of `value_l` and `value_r` with respect to linear interpolant, `T`, given by:
+
+\[
+\begin{align*}
+T &= \text{aastep}(\text{center}, \text{texcoord}_y) \\
+\text{out}_i &= \text{value}_{r_i} \cdot T + \text{value}_l \cdot (1 - T)
+\end{align*}
+\]
+
+---
+
+### noise2d
+2D Perlin noise in 1, 2, 3 or 4 channels.
+
+@MX_TABLE_noise2d@
+
+---
+
+### noise3d
+@MX_TABLE_noise3d@
+
+---
+
+### fractal3d
+@MX_TABLE_fractal3d@
+
+---
+
+### cellnoise2s
+@MX_TABLE_cellnoise2d@
+
+---
+
+### cellnoise3d
+@MX_TABLE_cellnoise3d@
+
+---
+
+### worleynoise2d
+@MX_TABLE_worleynoise2d@
+
+---
+
+### worleynoise3d
+@MX_TABLE_worleynoise3d@
+
+---
+
+### unifiednoise2d
+@MX_TABLE_unifiednoise2d@
+
+The `type` input must be used to select an underlying noise to evaluate.
+
+| `type` | `node` |
+|--------|------------------|
+| 0 | `noise2d` |
+| 1 | `cellnoise2d` |
+| 2 | `worleynoise2d` |
+| 3 | `fractalnoise3d` |
+
+** NOTE there is currently no `fractalnoise2d` - texcoord is promoted to position**
+
+The `texcoord` input must be scaled by the `freq` input value and then have the `offset` input value added to it. This computed result is then used as the `texcoord` input in the corresponding noise type.
+
+$$computedTexcoord_i = (texcoord_i * freq_i) + offset_i$$
+
+The `jitter` input must be used as the `jitter` input of the `worleynoise2d` nose if that noise type is selected corresponding input
+
+The `octaves`, `lacunarity` and `diminish` inputs must be used as the corresponding inputs of the `fractalnoise2d` node if that type is selected.
+
+The `out` output must be computed by taking the computed result from the selected noise type. That value must then remapped to the range defined by the `outmin` and `outmax` inputs, and if the `clampoutput` input is true, then the remapped value must be clamped between these two values.
+
+$$remapped = computed * (outmax - outmin) + outmin$$
+
+$$out =
+\begin{cases}
+remapped & \text{if } clampoutput \text{ is } false \text{ or } remapped \in [outmin, outmax]\\
+outmin & \text{if } clampoutput \text{ is } true \text{ and } remapped < outmin\\
+outmax & \text{if } clampoutput \text{ is } true \text{ and } remapped > outmax\\
+\end{cases}$$
+
+---
+
+### unifiednoise3d
+@MX_TABLE_unifiednoise3d@
+
+The `type` input must be used to select an underlying noise to evaluate.
+
+| `type` | `node` |
+|--------|------------------|
+| 0 | `noise3d` |
+| 1 | `cellnoise3d` |
+| 2 | `worleynoise3d` |
+| 3 | `fractalnoise3d` |
+
+The `position` input must be scaled by the `freq` input value and then have the `offset` input value added to it. This computed result is then used as the `position` input in the corresponding noise type.
+
+$$computedPosition_i = (position_i * freq_i) + offset_i$$
+
+The `jitter` input must be used as the `jitter` input of the `worleynoise3d` nose if that noise type is selected corresponding input
+
+The `octaves`, `lacunarity` and `diminish` inputs must be used as the corresponding inputs of the `fractalnoise3d` node if that type is selected.
+
+The `out` output must be computed by taking the computed result from the selected noise type. That value must then remapped to the range defined by the `outmin` and `outmax` inputs, and if the `clampoutput` input is true, then the remapped value must be clamped between these two values.
+
+$$remapped = computed * (outmax - outmin) + outmin$$
+
+$$out =
+\begin{cases}
+remapped & \text{if } clampoutput \text{ is } false \text{ or } remapped \in [outmin, outmax]\\
+outmin & \text{if } clampoutput \text{ is } true \text{ and } remapped < outmin\\
+outmax & \text{if } clampoutput \text{ is } true \text{ and } remapped > outmax\\
+\end{cases}$$
+
+---
+
+### randomfloat:
+Produces a stable randomized float value between 'min' and 'max', based on an 'input' signal and 'seed' value. Uses a 2d cellnoise function to produce the output.
+
+@MX_TABLE_randomfloat@
+
+\[
+\begin{aligned}
+s &= \text{float}(\text{seed}) \\
+x &= \begin{cases}
+ \text{in} \cdot 4096, & \text{if in is float} \\
+ \text{float}(\text{in}), & \text{if in is integer}
+\end{cases} \\
+\vec{v} &= (x, s) \\
+n &= \text{cellnoise2d}(\vec{v}) \quad \text{where } n \in [0, 1] \\
+\text{out} &= \min + n \cdot (\max - \min)
+\end{aligned}
+\]
+
+---
+
+### randomcolor:
+Produces a randomized RGB color within a randomized hue, saturation and brightness range, based on an 'input' signal and 'seed' value. Output type color3.
+
+@MX_TABLE_randomcolor@
+
+The output, `out`, is a random color computed by the method below:
+
+\[
+\begin{aligned}
+\text{seed}_f &= \text{float}(\text{seed}) \\
+\text{seed}_\text{hue} &= \left\lceil \text{seed}_f + 413.3 \right\rceil \\
+\text{seed}_\text{sat} &= \left\lceil \text{seed}_f + 1522.4 \right\rceil \\
+\text{seed}_\text{val} &= \left\lceil \text{seed}_f + 1813.8 \right\rceil \\
+\text{rand}_\text{hue} &= \text{RandomFloat}(\text{input}, \text{seed}_\text{hue}) \\
+\text{rand}_\text{sat} &= \text{RandomFloat}(\text{input}, \text{seed}_\text{sat}) \\
+\text{rand}_\text{val} &= \text{RandomFloat}(\text{input}, \text{seed}_\text{val}) \\
+\text{hue} &= \text{hue}_{\text{low}} + \text{rand}_\text{hue} \cdot (\text{hue}_{\text{high}} - \text{hue}_{\text{low}}) \\
+\text{sat} &= \text{sat}_{\text{low}} + \text{rand}_\text{sat} \cdot (\text{sat}_{\text{high}} - \text{sat}_{\text{low}}) \\
+\text{val} &= \text{val}_{\text{low}} + \text{rand}_\text{val} \cdot (\text{val}_{\text{high}} - \text{val}_{\text{low}}) \\
+\text{out} &= \text{HSVtoRGB}(\text{hue}, \text{sat}, \text{val})
+\end{aligned}
+\]
+
+## Shape Nodes
+
+### checkerboard
+2D checkerboard pattern.
+
+
+@MX_TABLE_checkerboard@
+Draws a checkerboard pattern
+[there will be a vector illustration figure here]
+
+---
+
+### line
+2D line pattern.
+
+
+@MX_TABLE_line@
+
+Returns 1 if texcoord is at less than radius distance from a line segment defined by point1 and point2; otherwise returns 0. Note that this makes the shapes at the end
+of the lines semi-circles.
+[there will be a vector illustration figure here]
+
+---
+
+### circle
+2D circle(disk) pattern.
+
+@MX_TABLE_circle@
+
+Returns 1 if texcoord is inside a circle defined by center and radius; otherwise returns 0
+[there will be a vector illustration figure here]
+
+---
+
+### cloverleaf
+2D cloverleaf pattern: four semicircles on the edges of a square defined by center and radius.
+
+@MX_TABLE_cloverleaf@
+
+Returns 1 if texcoord is inside a cloverleaf shape described by four semicircles on the edges of a square defined by center and radius; otherwise returns 0.
+[there will be a vector illustration figure here]
+
+---
+
+### hexagon
+2D hexagon pattern.
+
+@MX_TABLE_hexagon@
+
+Returns 1 if texcoord is inside a hexagon shape inscribed by a circle defined by center and radius; otherwise returns 0.
+[there will be a vector illustration figure here]
+
+---
+
+### grid
+Creates a grid pattern of (1, 1, 1) white lines on a (0, 0, 0) black background with the given tiling, offset, and line thickness. Pattern can be regular or staggered.
+
+@MX_TABLE_grid@
+
+---
+
+### crosshatch
+Creates a crosshatch pattern with the given tiling, offset, and line thickness. Pattern can be regular or staggered.
+
+@MX_TABLE_crosshatch@
+
+---
+
+### tiledcircles
+Creates a black and white pattern of circles with a defined tiling and size (diameter). Pattern can be regular or staggered.
+
+@MX_TABLE_tiledcircles@
+
+---
+
+### tiledcloverleafs
+Creates a black and white pattern of cloverleafs with a defined tiling and size (diameter of the circles circumscribing the shape). Pattern can be regular or staggered.
+
+@MX_TABLE_tiledcloverleafs@
+
+---
+
+### tiledhexagons
+Creates a black and white pattern of hexagons with a defined tiling and size (diameter of the circles circumscribing the shape). Pattern can be regular or staggered.
+
+@MX_TABLE_tiledhexagons@
+
+## Geometric Nodes
+
+### position
+The coordinates associated with the currently-processed data, as defined in a specific coordinate space.
+
+
+@MX_TABLE_position@
+
+
+Outputs the three-dimensional coordinate (x,y,z) of the currently processed data.
+
+---
+
+### normal
+The normalized geometric normal associated with the currently-processed data, as defined in a specific coordinate space.
+
+@MX_TABLE_normal@
+
+Outputs the normalized observer/ray facing normal. If the geometry has texture coordinates, this is equal to the normalized cross product of tangent and bitangent.
+When going between the different coordinate spaces, the transformation matrices of the spaces are applied to the normal as their inverse transpose
+(or equivalently, transpose inverse), with the exception of the model space, which
+
+---
+
+### tangent
+The geometric tangent vector associated with the currently-processed data, as defined in a specific coordinate space.
+
+@MX_TABLE_tangent@
+
+
+Outputs the un-normalized partial derivative of "position" with respect to the first element of the texture coordinate touple for the given texture index. If there are no
+texture coordinates on the geometry, or the texture coordinate index is out of range, the output falls back to the default vector3(0,0,0). Note that the tangent is
+not necessarily orthogonal to the "bitangent".
+For curves, tangent goes along the length of the curve.
+
+---
+
+### bitangent
+The geometric bi-tangent vector associated with the currently-processed data, as defined in a specific coordinate space.
+
+@MX_TABLE_bitangent@
+
+Outputs the un-normalized partial derivative at "position" with respect to the second element 'v' in the texture coordinate touple ('u','v'). If there are no
+texture coordinates on the geometry, or the texture coordinate index is out of range, the output falls back to the default vector3(0,0,0). For curves, bitangent goes across the width of the curve.
+
+---
+
+### bump
+The normalized normal computed by offsetting the surface world space position along its world space normal.
+
+@MX_TABLE_bump@
+
+Outputs the un-normalized partial derivative at "position" with respect to the second element 'v' in the texture coordinate touple ('u','v'). If there are no
+texture coordinates on the geometry, or the texture coordinate index is out of range, the output falls back to the default vector3(0,0,0). For curves, bitangent goes across the width of the curve.
+
+---
+
+### texcoord
+The 2D or 3D texture coordinates associated with the currently-processed data
+
+@MX_TABLE_texcoord@
+
+
+---
+
+
+### geomcolor
+The color associated with the current geometry at the current position, generally bound via per-vertex color values. The type must match the type of the "color" bound to the geometry.
+
+@MX_TABLE_geomcolor@
+
+---
+
+### geompropvalue
+The value of the specified varying geometric property (defined using ) of the currently-bound geometry. This node's type must match that of the referenced geomprop.
+
+@MX_TABLE_geompropvalue@
+
+---
+
+### geompropvalueuniform
+The value of the specified uniform geometric property (defined using ) of the currently-bound geometry. This node's type must match that of the referenced geomprop.
+
+@MX_TABLE_geompropvalueuniform@
+
+---
+
+### Geometric Spaces
+
+There are two types of spaces: affine spaces are defined by an affine linear transform (scale, rotate, translate, shear), and mapping spaces that maps each point
+
+|Space | Description |
+|-----------------|---------------------------------------------------|
+|"model" | optional mapping space
+|"object" | position on the deformed (displaced and subdivided) geometry before any transforms are applied |
+|"world" | position in object space with all transforms applied|
+
+## Application Nodes
+
+### frame
+The current frame number as defined by the host environment.
+
+@MX_TABLE_frame@
+
+Applications may use any appropriate method to communicate the current frame number to the node implementation, whether via an internal state variable, a custom input, or other approach.
+
+### time
+
+The current time in seconds, as defined by the host environment.
+
+@MX_TABLE_time@
+
+Applications may use any appropriate method to communicate the current time to the node implementation, whether via an internal state variable, a custom input, dividing the current frame number by a local "frames per second" value, or other method; real-time applications may return some variation of wall-clock time.
+
+
+## Math Nodes
+
+### add
+Add a value to the incoming float/color/vector/matrix
+
+@MX_TABLE_add@
+
+`out` shall be computed as the component-wise addition of `in1` and `in2`
+
+```math
+\mathtt{out}_{i,j} = \mathtt{in1}_{i,j} + \mathtt{in2}_{i,j}
+```
+
+---
+
+### subtract
+Subtract a value from the incoming float/color/vector/matrix
+
+@MX_TABLE_subtract@
+
+`out` shall be computed as the component-wise subtraction of `in2` from `in1`
+
+```math
+\mathtt{out}_{i,j} = \mathtt{in2}_{i,j} - \mathtt{in1}_{i,j}
+```
+
+---
+
+### multiply
+Multiply two values together. Scalar and vector types multiply component-wise, while matrices multiply with the standard matrix product.
+
+@MX_TABLE_multiply@
+
+For scalar and vector types, `out` shall be computed as the component-wise multiplication of `in1` with `in2`
+
+```math
+\mathtt{out}_{i} = \mathtt{in1}_{i} * \mathtt{in2}_{i}
+```
+
+For matrix types, `out` shall be computed as the matrix inner product.
+
+---
+
+### divide
+Divide one value by another. Scalar and vector types divide component-wise, while for matrices `in1` is multiplied with the inverse of `in2`.
+
+@MX_TABLE_divide@
+
+For scalar and vector types, `out` shall be computed as the component-wise division of `in2` by `in1`
+
+```math
+\mathtt{out}_{i} = \mathtt{in1}_{i} / \mathtt{in2}_{i}
+```
+
+For matrix types, `out` shall be computed as the matrix inner product of `in1` and the inverse of `in2`.
+
+```math
+\mathtt{out} = \mathtt{in}_1 \mathtt{in}_2^{-1}
+```
+
+---
+
+### modulo
+The remaining fraction after dividing an incoming float/color/vector by a value and subtracting the integer portion. Modulo always returns a non-negative result.
+
+@MX_TABLE_modulo@
+
+`out` shall be computed as the absolute value of the component-wise modulo of `in1` and `in2`
+```math
+\mathtt{out}_{i} = \lvert \mathtt{in1}_{i} \space mod \space \mathtt{in2}_{i} \rvert
+```
+
+---
+
+### fract
+Returns the fractional part of the floating-point input.
+
+@MX_TABLE_fract@
+
+`out` shall be computed component-wise as the fractional part of `in1`.
+```math
+\begin{align}
+x &= \lvert \mathtt{in}_{i} \rvert \\
+\mathtt{out}_{i} &= x - \lfloor x \rfloor \\
+\end{align}
+```
+
+---
+
+### invert
+subtract the incoming float, color, or vector from `amount` in all channels, outputting: `amount - in`.
+
+@MX_TABLE_invert@
+
+`out` shall be computed as `in` subtracted from `amount`:
+```math
+\mathtt{out}_{i} = \mathtt{amount}_{i} - \mathtt{in}_{i}
+```
+
+---
+
+### absval
+The per-channel absolute value of the incoming float/color/vector.
+
+@MX_TABLE_absval@
+
+`out` shall be computed as the channel-wise absolute value of `in`:
+```math
+\mathtt{out}_{i} = \lvert \mathtt{in}_{i} \rvert
+```
+
+---
+
+### floor
+The per-channel nearest integer value less than or equal to the incoming float/color/vector. The output remains in floating point per-channel, i.e. the same type as the input, except that the floor(float) also has a variant outputting an integer type.
+
+
+@MX_TABLE_floor@
+
+`out` shall be computed as the channel-wise floor of `in`:
+```math
+\mathtt{out}_{i} = \lfloor \mathtt{in}_{i} \rfloor
+```
+
+---
+
+### ceil
+The per-channel nearest integer value greater than or equal to the incoming float/color/vector. The output remains in floating point per-channel, i.e. the same type as the input, except that the ceil(float) also has a variant outputting an integer type.
+
+@MX_TABLE_ceil@
+
+`out` shall be computed as the channel-wise ceil of `in`:
+
+```math
+\mathtt{out}_{i} = \lceil \mathtt{in}_{i} \rceil
+```
+
+---
+
+### round
+Round each channel of the incoming float/color/vector values to the nearest integer value.
+
+@MX_TABLE_round@
+
+Round each channel of `in` to the nearest whole number, storing the result in the corresponding channel of `out`. Rounding shall be calculated using IEEE 754 round-to-nearest-ties-away-from-zero mode.
+
+```math
+\mathtt{out}_{i} = \lfloor \mathtt{in}_{i} \rceil
+```
+
+---
+
+### power
+Raise incoming float/color values to the specified exponent, commonly used for "gamma" adjustment.
+
+@MX_TABLE_power@
+
+`out` shall be computed as the component-wise raising of `in1` to the `in2`th power:
+
+```math
+\mathtt{out}_{i} = \mathtt{in1}_{i}^{\mathtt{in2}_{i}}
+```
+
+---
+
+### safepower
+Raise incoming float/color values to the specified exponent. Negative "in1" values will result in negative output values.
+
+@MX_TABLE_power@
+
+`out` shall be computed as the component-wise raising of `in1` to the `in2`th power:
+
+```math
+\mathtt{out}_{i} = (sgn \space \mathtt{in1}_{i}) \mathtt{in1}_{i}^{\lvert \mathtt{in2}_{i} \rvert}
+```
+
+---
+
+### sin
+The sine of the incoming value, which is expected to be expressed in radians.
+
+@MX_TABLE_sin@
+
+`out` shall be computed as the component-wise sin of `in`. `in` shall be in radians.
+
+```math
+\mathtt{out}_{i} = \sin{\mathtt{in}_{i}}
+```
+---
+
+### cos
+The cosine of the incoming value, which is expected to be expressed in radians.
+
+@MX_TABLE_cos@
+
+`out` shall be computed as the component-wise cosine of `in`. `in` shall be in radians:
+
+```math
+\mathtt{out}_{i} = \cos{\mathtt{in}_{i}}
+```
+---
+
+### tan
+The tangent of the incoming value, which is expected to be expressed in radians.
+
+@MX_TABLE_tan@
+
+`out` shall be computed as the component-wise tangent of `in`. `in` shall be in radians:
+
+```math
+\mathtt{out}_{i} = \tan{\mathtt{in}_{i}}
+```
+---
+
+### asin
+The arcsine of the incoming value. The output will be expressed in radians.
+
+@MX_TABLE_asin@
+
+`out` shall be computed as the component-wise arcsine of `in`. `out` shall be in radians.
+
+```math
+\mathtt{out}_{i} = \sin^{-1}{\mathtt{in}_{i}}
+```
+---
+
+### acos
+The arccosine of the incoming value. The output will be expressed in radians.
+
+@MX_TABLE_acos@
+
+`out` shall be computed as the component-wise arccosine of `in`. `out` shall be in radians.
+
+```math
+\mathtt{out}_{i} = \cos^{-1}{\mathtt{in}_{i}}
+```
+---
+
+### atan2
+the arctangent of the expression (`iny`/`inx`). The output will be expressed in radians.
+
+@MX_TABLE_atan2@
+
+> [!WARNING]
+> Need to define all the rules about 0 values?
+
+`out` shall be computed as the component-wise arctangent of `iny` divided by `inx`. `out` shall be in radians.
+
+```math
+\mathtt{out}_{i} = \tan^{-1}{\frac{\mathtt{iny}_{i}}{\mathtt{inx}_{i}}}
+```
+
+---
+
+### sqrt
+The square root of the incoming value.
+
+@MX_TABLE_sqrt@
+
+`out` shall be computed as the component-wise square root of `in`.
+
+```math
+\mathtt{out}_{i} = \sqrt{\max(\mathtt{in}_{i}, 0)}
+```
+
+---
+
+### ln
+The natural logarithm of the incoming value.
+
+@MX_TABLE_ln@
+
+`out` shall be computed as the component-wise natural logarithm of `in`.
+
+```math
+\mathtt{out}_{i} = \ln{\mathtt{in}_{i}}
+```
+
+---
+
+### exp
+$e$ to the power of the incoming value.
+
+@MX_TABLE_exp@
+
+`out` shall be computed as the component-wise raising of the base of natural logarithms, $e$, to the power `in`.
+
+```math
+\mathtt{out}_{i} = e^{\mathtt{in}_{i}}
+```
+
+---
+
+### sign
+The per-channel sign of the incoming float/color/vector value: -1 for negative, +1 for positive, or 0 for zero.
+
+@MX_TABLE_sign@
+
+`out` shall be computed as the channel-wise sign of `in`:
+```math
+\mathtt{out}_{i} = sgn \space \mathtt{in}_{i}
+```
+
+---
+
+### clamp
+Clamp incoming values per-channel to a specified range of float/color/vector values.
+
+@MX_TABLE_clamp@
+
+`out` shall be computed as the component-wise clamping of `in` between `low` and `high`. That is:
+
+```math
+\mathtt{out}_{i} = \max(\mathtt{low}_{i}, \min(\mathtt{high}_{i}, {\mathtt{in}_{i}}))
+```
+---
+
+### min
+Select the minimum of the two incoming values
+
+@MX_TABLE_min@
+
+`out` shall be computed as the component-wise minimum of `in1` and `in2`:
+```math
+\mathtt{out}_{i} = \min(\mathtt{in1}_{i}, \mathtt{in2}_{i})
+```
+
+---
+
+### max
+Select the maximum of the two incoming values
+
+@MX_TABLE_max@
+
+`out` shall be computed as the component-wise maximum of `in1` and `in2`:
+```math
+\mathtt{out}_{i} = \max(\mathtt{in1}_{i}, \mathtt{in2}_{i})
+```
+
+---
+
+### normalize
+Output the incoming vectorN stream normalized.
+
+@MX_TABLE_normalize@
+
+`out` shall be computed as `in` divided by the euclidean norm of `in`:
+
+```math
+\mathtt{out} = \frac{\mathtt{in}}{\lVert \mathtt{in} \rVert}
+```
+
+---
+
+### magnitude
+Output the float magnitude (vector length) of the incoming vectorN stream; cannot be used on float or colorN streams. Note: the fourth channel in vector4 streams is not treated any differently, e.g. not as a homogeneous "w" value.
+
+@MX_TABLE_magnitude@
+
+`out` shall be computed as the euclidean norm of `in`:
+
+```math
+\mathtt{out} = \lVert \mathtt{in} \rVert = \sqrt{\sum_{i=0}^{N-1} {\mathtt{in}_i}^2}
+```
+
+---
+
+### distance
+Measures the distance between two points in 2D, 3D, or 4D.
+
+@MX_TABLE_distance@
+
+`out` shall be computed as the magnitude of the vector pointing from `in1` to `in2`, that is the euclidean norm of `in2 - in1`:
+
+```math
+\mathtt{out} = \lVert \mathtt{in2} - \mathtt{in1} \rVert
+```
+
+---
+
+### dotproduct
+Output the (float) dot product of two incoming vectorN streams; cannot be used on float or colorN streams.
+
+@MX_TABLE_dotproduct@
+
+`out` shall be computed as the dot (inner) product of `in1` and `in2`
+
+```math
+\mathtt{out} = \mathtt{in1} \cdot \mathtt{in2}
+```
+
+---
+
+### crossproduct
+Output the (vector3) cross product of two incoming vector3 streams; cannot be used on any other stream type. A disabled crossproduct node passes through the value of `in1` unchanged.
+
+@MX_TABLE_crossproduct@
+
+`out` shall be computed as the cross product of `in1` and `in2`:
+
+```math
+\mathtt{out} = \mathtt{in1} \times \mathtt{in2}
+```
+If the node is disabled, `out` shall be `in1`.
+
+---
+
+### transformpoint
+Transform the incoming vector3 coordinate from one specified space to another; cannot be used on any other stream type.
+
+@MX_TABLE_transformpoint@
+
+
+`in` shall be reinterpreted as a 4-dimensional row vector, _p_, by the addition of a homogeneous coordinate with a value of 1. _p_ shall be multipled with the matrix _M_.
+
+The matrix, _M_, shall be computed by the renderer as the matrix necessary to transform from `fromspace` to `tospace`. If either of the named spaces are unknown to the renderer, or no suitable matrix can be computed, _M_ shall be the identity matrix.
+
+_p'_ shall be computed as multiplication of _p_ with _M_.
+
+`out` shall be computed from _p'_ by dividing the first three coordinates by the homogeneous coordinate:
+
+```math
+\begin{align}
+p &= \lbrack \mathtt{in}_{0}, \mathtt{in}_{1}, \mathtt{in}_{2}, 1 \rbrack \\
+p' &= p \cdot M \\
+\mathtt{out} &= \frac{\lbrack p'_{0}, p'_{1}, p'_{2} \rbrack }{p'_{3}}
+\end{align}
+```
+
+> [!WARNING]
+> What did we decide about common vs world?
+
+---
+
+### transformvector
+Transform the incoming vector3 coordinate from one specified space to another; cannot be used on any other stream type.
+
+@MX_TABLE_transformvector@
+
+`in` shall be reinterpreted as a 4-dimensional row vector, _v_, by the addition of a homogeneous coordinate with a value of 0. _v_ shall be multipled with the matrix _M_.
+
+The matrix, _M_, shall be computed by the renderer as the matrix necessary to transform from `fromspace` to `tospace`. If either of the named spaces are unknown to the renderer, or no suitable matrix can be computed, _M_ shall be the identity matrix.
+
+_v'_ shall be computed as multiplication of _v_ with _M_.
+
+`out` shall be computed from _v'_ by removing the homogeneous coordinate.
+
+```math
+\begin{align}
+
+\vec{v} &= [\mathtt{in}_{0}, \mathtt{in}_{1}, \mathtt{in}_{2}, 1] \notag \\
+\vec{v}' &= \vec{v} \cdot M \notag \\
+\mathtt{out} &= [\vec{v}'_{0}, \vec{v}'_{1}, \vec{v}'_{2}] \notag
+
+\end{align}
+```
+
+> [!WARNING]
+> What did we decide about common vs world?
+
+---
+
+### transformnormal
+Transform the incoming vector3 normal from one specified space to another; cannot be used on any other stream type.
+
+@MX_TABLE_transformnormal@
+
+---
+
+### transformmatrix
+Transform the incoming vectorN by the specified matrix.
+
+@MX_TABLE_transformmatrix@
+
+---
+
+### normalmap
+Transform a normal vector from the encoded tangent space to world space. The input normal vector is assumed to be encoded with all channels in the [0-1] range, as would commonly be output from a normal map.
+
+@MX_TABLE_normalmap@
+
+---
+
+---
+
+### creatematrix
+
+Build a 3x3 or 4x4 matrix from three vector3 or four vector3 or vector4 inputs. A matrix44 may also be created from vector3 input values, in which case the fourth value will be set to 0.0 for `in1`-`in3`, and to 1.0 for `in4` when creating the matrix44.
+
+@MX_TABLE_creatematrix@
+
+---
+
+### hextilednormalmap
+Transform a normal vector from the encoded tangent space to world space. The input normal vector is assumed to be encoded with all channels in the [0-1] range, as would commonly be output from a normal map.
+
+@MX_TABLE_hextilednormalmap@
+
+---
+
+### transpose
+Transpose the incoming matrix
+
+@MX_TABLE_transpose@
+
+`out` shall be computed as the transpose of `in`, that is:
+
+```math
+\mathrm{out} = \mathrm{in}^T
+```
+
+---
+
+### determinant
+Output the determinant of the incoming matrix.
+
+@MX_TABLE_determinant@
+
+`out` shall be computed as the determinant of `in`, that is:
+
+```math
+\mathrm{out} = \det(\mathrm{in})
+```
+
+---
+
+### invertmatrix
+Invert the incoming matrix.
+
+@MX_TABLE_invertmatrix@
+
+`out` shall be computed as the matrix inverse of `in`. If `in` is not invertible, every component out `out` shall be set to `NaN`.
+
+---
+
+### rotate2d
+Rotate the incoming 2D vector about the origin.
+
+@MX_TABLE_rotate2d@
+
+`out` shall be computed as the right-handed rotation of `in` about the origin by `amount` degrees. That is, `in` shall be multiplied by the matrix:
+
+```math
+\begin{bmatrix}
+\cos\theta & -\sin\theta \\
+\sin\theta & \cos\theta
+\end{bmatrix}
+```
+
+where:
+```math
+\theta = \frac{\mathrm{amount} \cdot \pi}{180}
+```
+
+---
+
+### rotate3d
+Rotate the incoming 3D vector about the specified unit axis vector.
+
+@MX_TABLE_rotate3d@
+
+`out` shall be computed as the conjugation of `in` with a unit quaternion `q`:
+
+```math
+\begin{align}
+\boldsymbol{q} &= \alpha_0 \sin\frac{\theta}{2} \boldsymbol{i} + \alpha_1 \sin\frac{\theta}{2} \boldsymbol{j} + \alpha_2 \sin\frac{\theta}{2} \boldsymbol{k} + \cos\frac{\theta}{2} \notag \\
+\mathtt{out} &= \boldsymbol{q} \space \omega \space \boldsymbol{q^{-1}} \notag
+\end{align}
+```
+
+where:
+```math
+\begin{align}
+\omega &= \mathtt{in} \notag \\
+\theta &= \frac{\mathtt{amount}\cdot\pi}{180} \notag \\
+\alpha &= \mathtt{axis} \notag \\
+\end{align}
+```
+
+---
+
+### place2d
+Transform incoming 2D texture coordinates from one frame of reference to another.
+
+@MX_TABLE_place2d@
+
+
+---
+
+### trianglewave
+Generate a triangle wave from the given scalar input. The generated wave ranges from zero to one and repeats on integer boundaries.
+
+@MX_TABLE_trianglewave@
+
+`out` shall be computed as a triangle wave over the domain of `in`, with the range [0, 1] and period 1 according to the equation:
+
+```math
+\mathtt{out} = 2 \lvert \mathtt{in} - \lfloor \mathtt{in} + \frac{1}{2} \rfloor \rvert
+```
+> [!WARNING]
+> define defaultinput?
+
+---
+
+### reflect
+Reflect the incoming 3D vector about a surface normal vector.
+
+@MX_TABLE_reflect@
+
+`out` shall be computed as the reflection of `in` about `normal`:
+
+```math
+\mathtt{out} = \omega_i -2 \lparen \omega_i \cdot N \rparen N
+```
+
+where:
+
+```math
+\begin{align}
+
+\omega_i &= \mathtt{in} \notag \\
+N &= \mathtt{normal} \notag
+
+\end{align}
+```
+
+---
+
+
+### refract
+Refract the incoming 3D vector through a surface with the given surface normal and relative index of refraction.
+
+@MX_TABLE_refract@
+
+`out` shall be computed as the refraction of `in` through the interface whose surface normal is `normal` and has relative index of refraction `ior`:
+
+```math
+\mathtt{out} = -\eta \omega_i + \lparen \eta \cos\theta_i - \cos\theta_t \rparen N
+```
+
+where:
+```math
+\begin{align}
+\omega_i &= \mathtt{in} \notag \\
+N &= \mathtt{normal} \notag \\
+\eta &= \mathtt{ior} \notag \\
+\theta_i &= \cos^{-1}(\omega_i \cdot N) \notag \\
+\theta_t &= \sqrt{\eta^{2} \sin^2{\theta_i}} \notag \\
+\end{align}
+```
+
+
+
+## Adjustment Nodes
+
+### contrast
+Increase or decrease the contrast of the incoming `in` values using `amount` as a linear slope multiplier.
+
+@MX_TABLE_contrast@
+
+The output is determined as follows:
+
+$out = (in - pivot) \cdot amount + pivot$
+
+Note that the contrast increases when `amount` $>$ 1, and decreases when `amount` is between 0 and 1. Also `pivot` will not change as contrast is adjusted.
+
+---
+
+### remap
+Linearly remap incoming values from one range of values [`inlow`, `inhigh`] to another [`outlow`, `outhigh`].
+
+@MX_TABLE_remap@
+
+The output is determined as follows:
+
+$out = outlow + \left( \dfrac{in - inlow}{inhigh - inlow} \right) \cdot (outhigh - outlow)$
+
+
+
+### range
+Remap incoming values from one range of values to another, optionally applying a gamma correction "in the middle".
+
+@MX_TABLE_range@
+
+This node first remaps the input from [`inlow`, `inhigh`] to [0,1], using the remap node. It then applies gamma correction, using $1/gamma$ as the exponent. Note that gamma values greater than `1.0` make midtones brighter. This gamma corrected result is then remaped from [0,1], to [`outlow`, `outhigh`]. Then based on the value of `doclamp` the final output may be clamped to [`outlow`, `outhigh`].
+
+---
+
+### smoothstep
+Output a smooth, hermite-interpolated remapping of input values from [`low`, `high`] to [0,1].
+
+
+@MX_TABLE_smoothstep@
+
+This remaps the input to [0,1], using a hermite-interpolated or smoothstep remapping.
+Note that inputs outside the [`low`, `high`] range are clamped.
+
+$out = \begin{cases}
+ 1, \ in \geq high\\
+ 0, \ in \leq low \\
+ remap(low, high, in), \ otherwise
+\end{cases}$
+
+---
+
+### luminance
+Output a grayscale value containing the luminance of the incoming RGB color in all color channels
+
+@MX_TABLE_luminance@
+
+Output a grayscale value containing the luminance of the incoming RGB color in all color channels, computed using the dot product of the incoming color `in` with the luma coefficients `lumacoeffs` of the working colorspace; the alpha channel is left unchanged if present.
+
+---
+
+### rgbtohsv
+Convert an incoming color from RGB to HSV space (with H and S ranging from 0 to 1); the alpha channel is left unchanged if present. This conversion is not affected by the current color space.
+
+@MX_TABLE_rgbtohsv@
+
+See Foley & van Dam
+
+---
+
+### hsvtorgb
+Convert an incoming color from HSV to RGB space; the alpha channel is left unchanged if present. This conversion is not affected by the current color space.
+
+@MX_TABLE_hsvtorgb@
+
+See Foley & van Dam
+---
+
+### hsvadjust
+Adjust the hue, saturation and value of an RGB color by converting the input color to HSV, adding amount.x to the hue, multiplying the saturation by amount.y, multiplying the value by amount.z, then converting back to RGB.
+
+@MX_TABLE_hsvadjust@
+
+Note:
+- `amount.x` $>1$ rotates hue in the "red to green to blue" direction,
+- `amount.x` = 1.0 is equivalent to a 360 degree (e.g. no-op) rotation.
+- `amount.x` values that are $<$ 0 or $>$ 1 are allowed, the values are wrapped at the 0-1 boundaries.
+- The internal conversions between RGB and HSV spaces are not affected by the current color space.
+- For color4 inputs, the alpha value is unchanged.
+
+---
+
+### saturate
+Adjust the saturation of a color, the alpha channel will be unchanged if present.
+
+@MX_TABLE_saturate@
+
+The `amount` value is used to perform linear interpolation between the incomihng color and the grayscale luminance of the input computed using the provided luma coefficients.
+
+Note that this operation is not equivalent to the saturation adjustment of the `hsvadjust` node, as that operator does not take the working or any other colorspace into account.
+
+---
+
+### colorcorrect
+Combines various adjustment nodes into one artist-friendly color correction node. For color4 inputs, the alpha value is unchanged.
+
+@MX_TABLE_colorcorrect@
+
+Note
+- `hue` values that are $<$ 0 or $>$ 1 are allowed, but the values are wrapped at the 0-1 boundaries.
+- the color brightness is increased or decreased by $2^{\large exposure}$.
+- the `constrastpivot` value does not change as contrast is adjusted.
+
+
+## Compositing Nodes
+
+### mix
+Mix between two inputs according to an input mix amount.
+
+@MX_TABLE_mix@
+
+**TODO** determine if we're including the `xxxshader` variants here? It doesn't align with the subscript behavior description below.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective components of the `fg` and `bg` inputs, using the `mix` input as the interpolant.
+
+$$out_i = mix * fg_i + (1.0 - mix) * bg_i$$
+
+---
+
+### premult
+Multiply the R or RGB channels of the input by the Alpha channel of the input.
+
+@MX_TABLE_premult@
+
+Each of the `r`, `g` and `b` components of the `out` output should be calculated by multiplying the respective components of the `in` input by the `a` component of the `in` input. The `a` component of `out` should be equal to the `a` component of the `in` input.
+
+$$out_i =
+\begin{cases}
+in_i * in_a & \text{if } i \text{ is } r,g,b\\
+in_a & \text{if } i \text{ is } a
+\end{cases}$$
+
+
+
+---
+
+### unpremult
+Divide the RGB channels of the input by the Alpha channel of the input. If the Alpha value is zero, it is passed through unchanged.
+
+@MX_TABLE_power@
+
+If the `a` component of the `in` input is zero, then each component of `out` should be equal
+
+Each of the `r`, `g` and `b` components of the `out` output should be calculated by dividing the respective components of the `in` input by the `a` component of the `in` input. The `a` component of `out` should be equal to the `a` component of the `in` input.
+
+**TODO** - what happens if `in.a` is zero?
+
+$$out_i =
+\begin{cases}
+in_i & \text{if } in_a = 0 \\
+\frac{in_i}{in_a} & \text{else if } i \text{ is } r,g,b\\
+in_a & \text{else if } i \text{ is } a \\
+\end{cases}$$
+
+
+## Blend Nodes
+
+Blend nodes take two 1-4 channel inputs and apply the same operator to all channels (the math for alpha is the same as for R or RGB). In the Blend Operator table, "F" and "B" refer to any individual channel of the `fg` and `bg` inputs respectively. Blend nodes support an additional float input `mix`, which is used to mix the original `bg` value (`mix`=0) with the result of the blend operation (`mix`=1, the default).
+
+| Blend Operator | Each Channel Output | Supported Types |
+|------------------|----------------------------------------------|------------------------|
+| **`plus`** | B+F | float, colorN |
+| **`minus`** | B-F | float, colorN |
+| **`difference`** | abs(B-F) | float, colorN |
+| **`burn`** | 1-(1-B)/F | float, colorN |
+| **`dodge`** | B/(1-F) | float, colorN |
+| **`screen`** | 1-(1-F)(1-B) | float, colorN |
+| **`overlay`** | 2FB if B<0.5; 1-2(1-F)(1-B) if B>=0.5 | float, colorN |
+
+
+### plus
+Add two 1-4 channel inputs, with optional mixing between the bg input and the result.
+
+@MX_TABLE_plus@
+
+Each component of the `fg` input must be added to the corresponding component of the `bg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i=mix*(bg_i + fg_i) + (1.0-mix)*bg_i$$
+
+
+
+---
+
+### minus
+Subtract two 1-4 channel inputs, with optional mixing between the bg input and the result.
+
+@MX_TABLE_minus@
+
+Each component of the `fg` input must be subtracted from the corresponding component of the `bg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i=mix*(bg_i - fg_i) + (1.0-mix)*bg_i$$
+
+---
+
+### difference
+Absolute-value difference of two 1-4 channel inputs, with optional mixing between the bg input and the result.
+
+@MX_TABLE_difference@
+
+Each component of the `fg` input must be subtracted from the corresponding component of the `bg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix* | bg_i - fg_i | + (1.0-mix)*bg_i$$
+
+---
+
+### burn
+Take two 1-4 channel inputs and apply the same operator to all channels:
+1-(1-B)/F
+
+@MX_TABLE_burn@
+
+Each component of the `out` output should be set to zero if the respective component of the `fg` input is zero or less.
+
+Otherwise, each component of the `bg` input is inverted and then divided by the respective component of the `fg` input, and finally inverted again to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i =
+\begin{cases}
+mix*(1.0 - (\frac{1.0 - bg_i}{fg_i}) + (1.0-mix)*bg_i & \text{if } fg_i > 0\\
+0 & \text{if } fg_i <= 0
+\end{cases}$$
+
+---
+
+### dodge
+Take two 1-4 channel inputs and apply the same operator to all channels:
+B/(1-F)
+
+@MX_TABLE_dodge@
+
+Each component of the `out` output should be set to zero if the respective component of the `fg` input when inverted is zero or less.
+
+Otherwise, each component of the `bg` input is divided by the respective component of `fg` inverted to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i =
+\begin{cases}
+mix*\frac{bg_i}{1.0 - fg_i} + (1.0-mix)*bg_i & \text{if } (1.0 - fg_i) > 0\\
+0 & \text{if } (1.0 - fg_i) <= 0
+\end{cases}$$
+
+---
+
+### screen
+Take two 1-4 channel inputs and apply the same operator to all channels:
+1-(1-F)*(1-B)
+
+@MX_TABLE_screen@
+
+Each respective component of the `fg` input and the `bg` must be inverted and then multiplied together, this product should then inverted again to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix*(1.0 - (1.0 - fg_i) * (1.0 - bg_)) + (1.0-mix)*bg_i$$
+
+---
+
+### overlay
+Take two 1-4 channel inputs and apply the same operator to all channels:
+2FB if B<0.5;
+1-2(1-F)(1-B) if B>=0.5
+
+@MX_TABLE_overlay@
+
+For each component of the `bg` input, if the value is less than 0.5 then both the respective `fg` and `bg` components are multiplied together and then multiplied by two to create an intermediate result. Otherwise, if the component of `bg` is greater than or equal to 0.5, then both the `fg` and `bg` components are inverted, multiplied together and then multiplied by 2. Finally this value is inverted to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i =
+\begin{cases}
+mix*(2 * fg_i * bg_i) + (1.0-mix)*bg_i & \text{ if } bg_i < 0.5 \\
+mix*(1 - 2(1 - fg_i)(1 - bg_i)) + (1.0-mix)*bg_i & \text{ if } bg_i >= 0.5
+\end{cases}$$
+
+
+## Merge Nodes
+
+Merge nodes take two 4-channel (color4) inputs and use the built-in alpha channel(s) to control the
+compositing of the `fg` and `bg` inputs. In the node descriptions below, `F` and `B` refer to the
+non-alpha channels of the `fg` and `bg` inputs respectively, and `f` and `b` refer to the alpha
+channels of the `fg` and `bg` inputs. Merge nodes are not defined for 1-channel or 3-channel
+inputs, and cannot be used on vectorN streams. Merge nodes support an optional float
+input `mix`, which can be used to mix the original `bg` value (`mix=0`) with the result of the
+blend operation (`mix=1`, the default).
+
+### disjointover
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F+B if f+b<=1
+F+B(1-f)/b if f+b>1
+alpha: min(f+b,1)
+
+| **`disjointover`** | F+B if f+b<=1; F+B(1-f)/b if f+b>1 | min(f+b,1) |
+
+
+@MX_TABLE_disjointover@
+
+If the sum of the `a` components of both the `fg` and `bg` inputs is less than or equal to 1, then each respective component of the `fg` and `bg` inputs is summed to create an intermediate result. Otherwise, if the sum of the `a` components of both the `fg` and `bg` inputs is greater than 1, then each component of the `bg` input is multiplied by the ratio of inverse of the `a` component of the `fg` input to the `a` component of the `bg` input, finally the corresponding component of the `fg` input is added to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i =
+\begin{cases}
+mix*(fg_i + bg_i) + (1.0-mix)*bg_i & \text{ if } fg_a + bg_a <= 1 \\
+mix*(fg_i+bg_i(1-fg_a)/bg_a) + (1.0-mix)*bg_i & \text{ if } fg_a + bg_a > 1
+\end{cases}$$
+
+---
+
+### in
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+* RGB = Fb
+* Alpha = fb
+
+@MX_TABLE_in@
+
+Each component of the `fg` input must be multiplied by the `a` component of the `bg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix*(fg_i*bg_a) + (1.0-mix) * bg_i$$
+
+---
+
+### mask
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+Bf (alpha: bf)
+
+@MX_TABLE_mask@
+
+Each component of the `bg` input must be multiplied by the `a` component of the `fg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix*(bg_i*fg_a) + (1.0-mix)*bg_i$$
+
+---
+
+### matte
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+Ff+B(1-f) (alpha: f+b(1-f))
+
+@MX_TABLE_matte@
+
+Each of the `r`,`g` and `b` components of `fg` and `bg` inputs are linearly interpolated, using the `a` component of the `fg` input as the interpolant to create an intermediate result. The `a` component of the `fg` input is used as the `a` component of this intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i =
+\begin{cases}
+mix*(fg_i*fg_a + bg_i*(1.0-fg_a)) + (1.0-mix)*bg_i & \text{ if } i \text{ in } r,g,b \\
+mix*(fg_i + bg_i*(1.0-fg_a)) + (1.0-mix)*bg_i & \text{ if } i = a
+\end{cases}$$
+
+---
+
+### out
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F(1-b) (alpha: f(1-b))
+
+@MX_TABLE_out@
+
+Each component of the `fg` input must be multiplied by the inverted `a` component of the `bg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix * (fg_i*(1.0-bg_a)) + (1.0-mix) * bg_i$$
+
+---
+
+### over
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F+B(1-f) (alpha: f+b(1-f))
+
+@MX_TABLE_over@
+
+Each component of the `bg` input must be multiplied by the inverted `a` component of the `fg` input, and then added to the corresponding component of the `fg` input to create an intermediate result.
+
+Each component of the `out` output should be calculated by linearly interpolating the respective component of the the absolute value of this intermediate result and the respective component of `bg` using the `mix` input as the interpolant.
+
+$$out_i = mix * (fg_i + (bg_i*(1.0-fg_a))) + (1.0-mix) * bg_i$$
+
+
+## Masking Nodes
+
+Masking nodes take one 1-4 channel input `in` plus a separate float `mask` input and apply the same operator to all channels (if present, the math for alpha is the same as for R or RGB).
+
+### inside
+Take one 1-4 channel input "in" and multiply each channel by a separate float "mask" input.
+
+@MX_TABLE_inside@
+
+The components of `out` must be calculated by multiplying the components `in` by the float input `mask`.
+
+$$out_i = in_i * mask$$
+
+---
+
+### outside
+Take one 1-4 channel input `in` and multiply each channel by the inverse of a separate float `mask` input.
+
+@MX_TABLE_outside@
+
+The components of `out` must be calculated by multiplying the components `in` by the inverse of the float input `mask`.
+
+$$out_i = in_i * (1.0 - mask)$$
+
+
+## Conditional Nodes
+
+Conditional nodes are used to compare values of two streams, or to select a value from one of several streams.
+
+### ifgreater
+
+Output the value of the `in1` or `in2` stream depending on whether the `value1` input is greater than the `value2` input.
+
+@MX_TABLE_ifgreater@
+
+---
+
+### ifgreatereq
+
+Output the value of the `in1` or `in2` stream depending on whether the `value1` input is greater or equal to the `value2` input.
+
+@MX_TABLE_ifgreatereq@
+
+---
+
+### ifequal
+
+@MX_TABLE_ifequal@
+
+---
+
+### switch
+
+Output the value of one of up to ten input streams, according to the value of a selector input `which`. Note that not all inputs need to be connected. The output has the same type as `in1`, with a default value of __zero__.
+
+@MX_TABLE_switch@
+
+The value of `which` determines the output as follows:
+
+$out = \begin{cases}
+ in1, \ &which < 1\\
+ in2, \ 1 \leq &which < 2 \\
+ in3, \ 2 \leq &which < 3 \\
+ ... \\
+ in10, \ 9 \leq &which < 10 \\
+\end{cases}$
+
+For values `which` $\geq 10$ we return __zero__
+
+## Channel Nodes
+
+Channel nodes are used to perform channel manipulations and data type conversions on streams.
+
+### extract
+
+Isolate a single float channel from a __vectorN__ or __colorN__ stream. The output value is of type `float` with a default value of __zero__.
+
+@MX_TABLE_extract@
+
+The valid range for `index` should be clamped to $[0,N)$ in the user interface, where __N__ is the size of the input vector stream. `index` is a uniform, non-varying value. Any `index` values outside of the valid range should result in an error.
+
+---
+
+### separate2
+
+Split the channels of a 2-channel stream into separate float outputs.
+
+@MX_TABLE_separate2@
+
+For the vector2-input `in`, `outx` and `outy` correspond to the x- and y-components of `in`..
+
+---
+
+### separate3
+
+Split the channels of a 3-channel stream into separate float outputs.
+
+@MX_TABLE_separate3@
+
+When the input `in` is a color3, `outr`, `outg`, and `outb` correspond to the r-, g-, and b-components of `in`, respectively.
+
+When the input `in` is a vector3, `outx`, `outy`, and `outz` correspond to the x-, y-, and z-components of `in`, respectively.
+
+---
+
+### separate4
+
+Split the channels of a 4-channel stream into separate float outputs.
+
+@MX_TABLE_separate4@
+
+When the input `in` is a color4, `outr`, `outg`, `outb`, and `outa` correspond to the r-, g-, b-, and alpha components of `in`, respectively.
+
+When the input `in` is a vector4, `outx`, `outy`, `outz`, and `outw` correspond to the x-, y-, z-, and w-components of `in`, respectively.
+
+---
+
+### combine2
+
+Combine the channels from two streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine2@
+
+---
+
+### combine3
+
+Combine the channels from three streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine3@
+
+---
+
+### combine4
+
+Combine the channels from four streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine4@
+
+
+## Convolution Nodes
+
+Convolution nodes have one input named `in`, and apply a defined convolution function to the input stream.
+
+### blur
+
+Applies a convolution blur to the input stream.
+
+@MX_TABLE_blur@
+
+#### Filter Types
+
+|Name |Description |
+|--------------|----------------------|
+|"box" |Linear box filter |
+|"gaussian" |Gaussian smoothing |
+
+#### Box Filter
+
+Blurs the input, based on the average of neighboring values:
+
+$$B = sum_{i=1}^sc a_i = a_1 + a_2 + ... + a_sc$$
+
+#### Gaussian Filter
+
+Applies a gaussian filter to the input:
+
+$$G(u,v) = \frac{1}{2 \pi \sigma ^2} e ^{- \frac{u^2 + v^2}{2 \sigma ^2}}$$
+
+---
+
+### heighttonormal
+
+Convert a scalar height map to a tangent-space normal map of type `vector3`. The output normal map is encoded with all channels in the [0-1] range, enabling its storage in unsigned image formats.
+
+@MX_TABLE_heighttonormal@
+
+Let the scalar values be represented as a function $h(u, v)$, where $(u, v)$ are coordinates of the image.
+
+The partial derivatives of the height field are:
+$$\frac{\partial h}{\partial u} \text{ and } \frac{\partial h}{\partial v}$$
+
+The normal vector $\vec{n}$ at each point $(x, y)$ can be calculated as:
+$$N = \left(-\frac{\partial h}{\partial u}, -\frac{\partial h}{\partial v}, 1\right)$$
+
+To normalize the normal vector, we divide it by its magnitude:
+$$N = \frac{1}{\sqrt{1 + \left(\frac{\partial h}{\partial u}\right)^2 + \left(\frac{\partial h}{\partial v}\right)^2}} \left(-\frac{\partial h}{\partial u}, -\frac{\partial h}{\partial v}, 1\right)$$
+
+# Logical Operator Nodes
+
+## and
+logically AND the two input boolean values
+
+@MX_TABLE_and@
+
+Output the boolean value resulting from the logical AND of the input values.
+
+---
+
+## or
+logically Inclusive OR the two input boolean values
+
+@MX_TABLE_or@
+
+Output the boolean value resulting from the logical Inclusive OR of the input values.
+
+---
+
+## xor
+logically Exclusive OR the two input boolean values
+
+@MX_TABLE_xor@
+
+Output the boolean value resulting from the logical Exclusive OR of the input values.
+
+---
+
+## not
+logically NOT the input boolean value
+
+@MX_TABLE_not@
+
+Output the boolean value resulting from the logical NOT of the input value.
+
+## Organization Nodes
+
+### dot
+
+
+@MX_TABLE_dot@
+
+A no-op, passes its input through to its output unchanged. Users can use dot nodes to shape edge connection paths or provide documentation checkpoints in node graph layout UI's. Dot nodes may also pass uniform values from or other nodes with uniform="true" outputs to uniform s and s.
\ No newline at end of file
diff --git a/dev_spec/source/stdlib_spec.md b/dev_spec/source/stdlib_spec.md
new file mode 100644
index 0000000000..0e2ee0be87
--- /dev/null
+++ b/dev_spec/source/stdlib_spec.md
@@ -0,0 +1,980 @@
+
+
+
+# MaterialX Standard Nodes
+
+**Version 1.39**
+Doug Smythe - Industrial Light & Magic
+Jonathan Stone - Lucasfilm Advanced Development Group
+March 15, 2025
+
+
+# Introduction
+
+The MaterialX Specification defines a content schema to describe materials, image processing and shading networks and how the nodes in those networks access textural and geometric information, in a platform- and shading-language-independent manner.
+
+This document describes a specific set of **Standard Nodes** that can be used to read and process image and geometric attribute data, as well as create new image data procedurally. These "stdlib" nodes are an essential core part of all MaterialX implementations. Additional nodes are described in companion documents [**MaterialX Physically Based Shading Nodes**](./MaterialX.PBRSpec.md) and [**MaterialX NPR Shading Nodes**](./MaterialX.NPRSpec.md).
+
+In the descriptions below, a node with an "(NG)" annotation indicates a node that is implemented using a nodegraph in the MaterialX distribution, while unannotated nodes are implemented natively in the various renderer shading languages.
+
+
+## Table of Contents
+
+**[Introduction](#introduction)**
+
+**[Standard Source Nodes](#standard-source-nodes)**
+ [Texture Nodes](#texture-nodes)
+ [Procedural Nodes](#procedural-nodes)
+ [Noise Nodes](#noise-nodes)
+ [Shape Nodes](#shape-nodes)
+ [Geometric Nodes](#geometric-nodes)
+ [Application Nodes](#application-nodes)
+
+**[Standard Operator Nodes](#standard-operator-nodes)**
+ [Math Nodes](#math-nodes)
+ [Logical Operator Nodes](#logical-operator-nodes)
+ [Adjustment Nodes](#adjustment-nodes)
+ [Compositing Nodes](#compositing-nodes)
+ [Conditional Nodes](#conditional-nodes)
+ [Channel Nodes](#channel-nodes)
+ [Convolution Nodes](#convolution-nodes)
+
+**[Standard Shader Nodes](#standard-shader-nodes)**
+
+
+
+
+# Standard Source Nodes
+
+Source nodes use external data and/or procedural functions to form an output; they do not have any required inputs. Each source node must define its output type.
+
+This section defines the Source Nodes that all MaterialX implementations are expected to support. Standard Source Nodes are grouped into the following classifications: [Texture Nodes](#texture-nodes), [Procedural Nodes](#procedural-nodes), [Noise Nodes](#noise-nodes), [Shape Nodes](#shape-nodes), [Geometric Nodes](#geometric-nodes) and [Application Nodes](#application-nodes).
+
+
+## Texture Nodes
+
+Texture nodes are used to read filtered image data from image or texture map files for processing within a node graph.
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+Standard Texture nodes:
+
+### `image`
+
+Samples data from a single image, or from a layer within a multi-layer image. When used in the context of rendering a geometry, the image is mapped onto the geometry based on geometry UV coordinates, with the lower-left corner of an image mapping to the (0,0) UV coordinate (or to the fractional (0,0) UV coordinate for tiled images).
+
+The type of the <image> node determines the number of channels output, which may be less than the number of channels in the image file, outputting the first N channels from the image file. So a `float` <image> would return the Red channel of an RGB image, and a `color3` <image> would return the RGB channels of an RGBA image. If the type of the <image> node has more channels than the referenced image file, then the output will contain zero values in all channels beyond the N channels of the image file.
+
+The `file` input can include one or more substitutions to change the file name that is accessed, as described in the [Filename Substitutions](./MaterialX.Specification.md#filename-substitutions) section in the main Specification document. The `filtertype` input supports options `closest` (nearest-neighbor single-sample), `linear`, and `cubic`.
+
+@MX_TABLE_image@
+
+### `tiledimage`
+Samples data from a single image, with provisions for tiling and offsetting the image across uv space.
+
+The `file` input can include one or more substitutions to change the file name that is accessed, as described in the [Filename Substitutions](./MaterialX.Specification.md#filename-substitutions) section in the main Specification document.
+
+@MX_TABLE_tiledimage@
+
+### `latlongimage`
+Samples an equiangular map along a view direction with adjustable latitudinal offset.
+
+The `file` input can include one or more substitutions to change the file name that is accessed, as described in the [Filename Substitutions](./MaterialX.Specification.md#filename-substitutions) section in the main Specification document.
+
+@MX_TABLE_latlongimage@
+
+### `triplanarprojection`
+Samples data from three images (or layers within multi-layer images), and projects a tiled representation of the images along each of the three respective coordinate axes, computing a weighted blend of the three samples using the geometric normal.
+
+@MX_TABLE_triplanarprojection@
+
+
+
+
+The following values are supported by `uaddressmode` and `vaddressmode` inputs of [image](#node-image) nodes:
+
+* “constant”: Texture coordinates outside the 0-1 range return the value of the node's `default` input.
+* “clamp”: Texture coordinates are clamped to the 0-1 range before sampling the image.
+* “periodic”: Texture coordinates outside the 0-1 range "wrap around", effectively being processed by a modulo 1 operation before sampling the image.
+* "mirror": Texture coordinates outside the 0-1 range will be mirrored back into the 0-1 range, e.g. u=-0.01 will return the u=0.01 texture coordinate value, and u=1.01 will return the u=0.99 texture coordinate value.
+
+
+Texture nodes using `file*` inputs also support the following inputs to handle boundary conditions for image file frame ranges for all `file*` inputs:
+
+* `framerange` (uniform string): a string "_minframe_-_maxframe_", e.g. "10-99", to specify the range of frames that the image file is allowed to have, usually the range of image files on disk. Default is unbounded.
+* `frameoffset` (integer): a number that is added to the current frame number to get the image file frame number. E.g. if `frameoffset` is 25, then processing frame 100 will result in reading frame 125 from the imagefile sequence. Default is no frame offset.
+* `frameendaction` (uniform string): what to do when the resolved image frame number is outside the `framerange` range:
+ * "constant": Return the value of the node's `default` input (default action)
+ * "clamp": Hold the minframe image for all frames before _minframe_ and hold the maxframe image for all frames after _maxframe_
+ * "periodic": Frame numbers "wrap around", so after the _maxframe_ it will start again at _minframe_ (and similar before _minframe_ wrapping back around to _maxframe_)
+ * "mirror": Frame numbers "mirror" or "ping-pong" at the endpoints of framerange, so a read of the frame after _maxframe_ will return the image from frame _maxframe_-1, and a read of the frame before _minframe_ will return the image from frame _minframe_+1.
+
+Arbitrary frame number expressions and speed changes are not supported.
+
+
+
+## Procedural Nodes
+
+Procedural nodes are used to generate value data programmatically.
+
+```xml
+
+
+
+
+
+
+
+```
+
+Standard Procedural nodes:
+
+### `constant`
+Outputs a constant value.
+
+@MX_TABLE_constant@
+
+### `ramplr`
+A left-to-right linear value ramp
+
+@MX_TABLE_ramplr@
+
+### `ramptb`
+A top-to-bottom linear value ramp.
+
+@MX_TABLE_ramptb@
+
+### `ramp4`
+A 4-corner bilinear value ramp.
+
+@MX_TABLE_ramp4@
+
+### `splitlr`
+A left-right split matte, split at a specified `U` value.
+
+@MX_TABLE_splitlr@
+
+### `splittb`
+A top-bottom split matte, split at a specified `V`` value.
+
+@MX_TABLE_splittb@
+
+To scale or offset `rampX` or `splitX` input coordinates, use a <texcoord> or similar Geometric node processed by vector2 <multiply>, <rotate> and/or <add> nodes, and connect to the node's `texcoord` input.
+
+
+
+## Noise Nodes
+
+Noise nodes are used to generate value data using one of several procedural noise functions.
+
+```xml
+
+
+
+
+```
+
+Standard Noise nodes:
+
+### `noise2d`
+2D Perlin noise in 1, 2, 3 or 4 channels.
+@MX_TABLE_noise2d@
+
+### `noise3d`
+3D Perlin noise in 1, 2, 3 or 4 channels.
+@MX_TABLE_noise3d@
+
+### `fractal3d`
+Zero-centered 3D Fractal noise in 1, 2, 3 or 4 channels, created by summing several octaves of 3D Perlin noise, increasing the frequency and decreasing the amplitude at each octave.
+@MX_TABLE_fractal3d@
+
+### `cellnoise2d`
+2D cellular noise, 1 or 3 channels (type float or vector3).
+@MX_TABLE_cellnoise2d@
+
+### `cellnoise3d`
+3D cellular noise, 1 or 3 channels (type float or vector3).
+@MX_TABLE_cellnoise3d@
+
+### `worleynoise2d`
+2D Worley noise using centered jitter, outputting float (distance metric to closest feature), vector2 (distance metrics to closest 2 features) or vector3 (distance metrics to closest 3 features).
+@MX_TABLE_worleynoise2d@
+
+### `worleynoise3d`
+3D Worley noise using centered jitter, outputting float (distance metric to closest feature), vector2 (distance metrics to closest 2 features) or vector3 (distance metrics to closest 3 features).
+@MX_TABLE_worleynoise3d@
+
+### `unifiednoise2d`
+(NG): a single node supporting 2D Perlin, Cell, Worley or Fractal noise in a unified interface.
+@MX_TABLE_unifiednoise2d@
+
+### `unifiednoise3d`
+(NG): a single node supporting 3D Perlin, Cell, Worley or Fractal noise in a unified interface.
+@MX_TABLE_unifiednoise3d@
+
+To scale or offset the noise pattern generated by a 3D noise node such as `noise3d`, `fractal3d` or `cellnoise3d`, use a <position> or other [Geometric Node](#geometric-nodes) (see below) connected to vector3 <multiply> and/or <add> nodes, in turn connected to the noise node's `position` input. To scale or offset the noise pattern generated by a 2D noise node such as `noise2d` or `cellnoise2d`, use a <texcoord> or similar Geometric node processed by vector2 <multiply>, <rotate> and/or <add> nodes, and connect to the node's `texcoord` input.
+
+
+
+## Shape Nodes
+
+Shape nodes are used to generate shapes or patterns in UV space.
+
+```xml
+
+
+
+
+
+```
+
+Standard Shape nodes:
+
+### `checkerboard`
+2D checkerboard pattern.
+@MX_TABLE_checkerboard@
+
+
+### `line`
+2D line pattern.
+@MX_TABLE_line@
+
+### `circle`
+2D circle(disk) pattern.
+@MX_TABLE_circle@
+
+### `cloverleaf`
+2D cloverleaf pattern: four semicircles on the edges of a square defined by center and radius.
+@MX_TABLE_cloverleaf@
+
+### `hexagon`
+2D hexagon pattern.
+@MX_TABLE_hexagon@
+
+### `grid`
+Creates a grid pattern of (1, 1, 1) white lines on a (0, 0, 0) black background with the given tiling, offset, and line thickness. Pattern can be regular or staggered.
+@MX_TABLE_grid@
+
+### `crosshatch`
+Creates a crosshatch pattern with the given tiling, offset, and line thickness. Pattern can be regular or staggered.
+@MX_TABLE_crosshatch@
+
+### `tiledcircles`
+Creates a black and white pattern of circles with a defined tiling and size (diameter). Pattern can be regular or staggered.
+@MX_TABLE_tiledcircles@
+
+### `tiledcloverleafs`
+Creates a black and white pattern of cloverleafs with a defined tiling and size (diameter of the circles circumscribing the shape). Pattern can be regular or staggered.
+@MX_TABLE_tiledcloverleafs@
+
+### `tiledhexagons`
+Creates a black and white pattern of hexagons with a defined tiling and size (diameter of the circles circumscribing the shape). Pattern can be regular or staggered.
+@MX_TABLE_tiledhexagons@
+
+
+
+## Geometric Nodes
+
+Geometric nodes are used to reference local geometric properties from within a node graph:
+
+```xml
+
+
+
+
+```
+
+Standard Geometric nodes:
+
+### `position`
+The coordinates associated with the currently-processed data, as defined in a specific coordinate space.
+@MX_TABLE_position@
+
+
+### `normal`
+The normalized geometric normal associated with the currently-processed data, as defined in a specific coordinate space.
+@MX_TABLE_normal@
+
+
+### `tangent`
+The geometric tangent vector associated with the currently-processed data, as defined in a specific coordinate space.
+@MX_TABLE_tangent@
+
+
+### `bitangent`
+The geometric bi-tangent vector associated with the currently-processed data, as defined in a specific coordinate space.
+@MX_TABLE_bitangent@
+
+
+### `bump`
+The normalized normal computed by offsetting the surface world space position along its world space normal.
+@MX_TABLE_bump@
+
+
+### `texcoord`
+The 2D or 3D texture coordinates associated with the currently-processed data
+@MX_TABLE_texcoord@
+
+
+### `geomcolor`
+The color associated with the current geometry at the current position, generally bound via per-vertex color values. The type must match the type of the "color" bound to the geometry.
+@MX_TABLE_geomcolor@
+
+
+### `geompropvalue`
+The value of the specified varying geometric property (defined using ) of the currently-bound geometry. This node's type must match that of the referenced geomprop.
+@MX_TABLE_geompropvalue@
+
+
+### `geompropvalueuniform`
+The value of the specified uniform geometric property (defined using ) of the currently-bound geometry. This node's type must match that of the referenced geomprop.
+@MX_TABLE_geompropvalueuniform@
+
+
+Additionally, the `geomcolor` and `geompropvalue` nodes for color3/color4-type properties can take a `colorspace` attribute to declare what colorspace the color property value is in; the default is "none" for no colorspace declaration (and hence no colorspace conversion).
+
+
+
+## Application Nodes
+
+Application nodes are used to reference application-defined properties within a node graph, and have no inputs:
+
+```xml
+
+
+```
+
+Standard Application nodes:
+
+### `frame`
+The current frame number as defined by the host environment.
+@MX_TABLE_frame@
+
+### `time`
+The current time in seconds, as defined by the host environment.
+@MX_TABLE_time@
+
+
+# Standard Operator Nodes
+
+Operator nodes process one or more required input streams to form an output. Like other nodes, each operator must define its output type, which in most cases also determines the type(s) of the required input streams.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The inputs of compositing operators are called "fg" and "bg" (plus "alpha" for float and color3 variants, and "mix" for all variants of the `mix` operator), while the inputs of other operators are called "in" if there is exactly one input, or "in1", "in2" etc. if there are more than one input. If an implementation does not support a particular operator, it should pass through the "bg", "in" or "in1" input unchanged.
+
+This section defines the Operator Nodes that all MaterialX implementations are expected to support. Standard Operator Nodes are grouped into the following classifications: [Math Nodes](#math-nodes), [Adjustment Nodes](#adjustment-nodes), [Compositing Nodes](#compositing-nodes), [Conditional Nodes](#conditional-nodes), [Channel Nodes](#channel-nodes) and [Convolution Nodes](#convolution-nodes).
+
+
+
+## Math Nodes
+
+Math nodes have one or two spatially-varying inputs, and are used to perform a math operation on values in one spatially-varying input stream, or to combine two spatially-varying input streams using a specified math operation. The given math operation is performed for each channel of the input stream(s), and the data type of each input must either match that of the input stream(s), or be a float value that will be applied to each channel separately.
+
+
+### `add`
+Add a value to the incoming float/color/vector/matrix
+@MX_TABLE_add@
+
+### `subtract`
+Subtract a value from the incoming float/color/vector/matrix
+@MX_TABLE_subtract@
+
+### `multiply`
+Multiply two values together. Scalar and vector types multiply component-wise, while matrices multiply with the standard matrix product.
+@MX_TABLE_multiply@
+
+### `divide`
+Divide one value by another. Scalar and vector types divide component-wise, while for matrices `in1` is multiplied with the inverse of `in2`.
+@MX_TABLE_divide@
+
+
+### `modulo`
+The remaining fraction after dividing an incoming float/color/vector by a value and subtracting the integer portion. Modulo always returns a non-negative result.
+@MX_TABLE_modulo@
+
+### `fract`
+Returns the fractional part of the floating-point input.
+@MX_TABLE_fract@
+
+### `invert`
+subtract the incoming float, color, or vector from `amount` in all channels, outputting: `amount - in`.
+@MX_TABLE_invert@
+
+### `absval`
+The per-channel absolute value of the incoming float/color/vector.
+@MX_TABLE_absval@
+
+### `floor`
+The per-channel nearest integer value less than or equal to the incoming float/color/vector. The output remains in floating point per-channel, i.e. the same type as the input, except that the floor(float) also has a variant outputting an integer type.
+@MX_TABLE_floor@
+
+### `ceil`
+The per-channel nearest integer value greater than or equal to the incoming float/color/vector. The output remains in floating point per-channel, i.e. the same type as the input, except that the ceil(float) also has a variant outputting an integer type.
+@MX_TABLE_ceil@
+
+### `round`
+Round each channel of the incoming float/color/vector values to the nearest integer value.
+@MX_TABLE_round@
+
+### `power`
+Raise incoming float/color values to the specified exponent, commonly used for "gamma" adjustment.
+@MX_TABLE_power@
+
+### `safepower`
+Raise incoming float/color values to the specified exponent. Negative "in1" values will result in negative output values.
+@MX_TABLE_power@
+
+### `sin`
+The sine of the incoming value, which is expected to be expressed in radians.
+@MX_TABLE_sin@
+
+### `cos`
+The cosine of the incoming value, which is expected to be expressed in radians.
+@MX_TABLE_cos@
+
+### `tan`
+The tangent of the incoming value, which is expected to be expressed in radians.
+@MX_TABLE_tan@
+
+### `asin`
+The arcsine of the incoming value. The output will be expressed in radians.
+@MX_TABLE_asin@
+
+### `acos`
+The arccosine of the incoming value. The output will be expressed in radians.
+@MX_TABLE_acos@
+
+### `atan2`
+the arctangent of the expression (`iny`/`inx`). The output will be expressed in radians.
+@MX_TABLE_atan2@
+
+### `sqrt`
+The square root of the incoming value.
+@MX_TABLE_sqrt@
+
+### `ln`
+The natural logarithm of the incoming value.
+@MX_TABLE_ln@
+
+### `exp`
+$e$ to the power of the incoming value.
+@MX_TABLE_exp@
+
+### `sign`
+The per-channel sign of the incoming float/color/vector value: -1 for negative, +1 for positive, or 0 for zero.
+@MX_TABLE_sign@
+
+### `clamp`
+Clamp incoming values per-channel to a specified range of float/color/vector values.
+@MX_TABLE_clamp@
+
+### `min`
+Select the minimum of the two incoming values
+@MX_TABLE_min@
+
+### `max`
+Select the maximum of the two incoming values
+@MX_TABLE_max@
+
+### `normalize`
+Output the incoming vectorN stream normalized.
+@MX_TABLE_normalize@
+
+### `magnitude`
+Output the float magnitude (vector length) of the incoming vectorN stream; cannot be used on float or colorN streams. Note: the fourth channel in vector4 streams is not treated any differently, e.g. not as a homogeneous "w" value.
+@MX_TABLE_magnitude@
+
+### `distance`
+Measures the distance between two points in 2D, 3D, or 4D.
+@MX_TABLE_distance@
+
+### `dotproduct`
+Output the (float) dot product of two incoming vectorN streams; cannot be used on float or colorN streams.
+@MX_TABLE_dotproduct@
+
+### `crossproduct`
+Output the (vector3) cross product of two incoming vector3 streams; cannot be used on any other stream type. A disabled crossproduct node passes through the value of `in1` unchanged.
+@MX_TABLE_crossproduct@
+
+### `transformpoint`
+Transform the incoming vector3 coordinate from one specified space to another; cannot be used on any other stream type.
+@MX_TABLE_transformpoint@
+
+### `transformvector`
+Transform the incoming vector3 coordinate from one specified space to another; cannot be used on any other stream type.
+@MX_TABLE_transformvector@
+
+### `transformnormal`
+Transform the incoming vector3 normal from one specified space to another; cannot be used on any other stream type.
+@MX_TABLE_transformnormal@
+
+### `transformmatrix`
+Transform the incoming vectorN by the specified matrix.
+@MX_TABLE_transformmatrix@
+
+### `normalmap`
+Transform a normal vector from the encoded tangent space to world space. The input normal vector is assumed to be encoded with all channels in the [0-1] range, as would commonly be output from a normal map.
+@MX_TABLE_normalmap@
+
+### `creatematrix`
+Build a 3x3 or 4x4 matrix from three vector3 or four vector3 or vector4 inputs. A matrix44 may also be created from vector3 input values, in which case the fourth value will be set to 0.0 for `in1`-`in3`, and to 1.0 for `in4` when creating the matrix44.
+@MX_TABLE_creatematrix@
+
+
+### `hextilednormalmap`
+Transform a normal vector from the encoded tangent space to world space. The input normal vector is assumed to be encoded with all channels in the [0-1] range, as would commonly be output from a normal map.
+@MX_TABLE_hextilednormalmap@
+
+
+### `transpose`
+Transpose the incoming matrix
+@MX_TABLE_transpose@
+
+### `determinant`
+Output the determinant of the incoming matrix.
+@MX_TABLE_determinant@
+
+
+### `invertmatrix`
+Invert the incoming matrix.
+@MX_TABLE_invertmatrix@
+
+
+### `rotate2d`
+Rotate the incoming 2D vector about the origin.
+@MX_TABLE_rotate2d@
+
+### `rotate3d`
+Rotate the incoming 3D vector about the specified unit axis vector.
+@MX_TABLE_rotate3d@
+
+### `place2d`
+Transform incoming 2D texture coordinates from one frame of reference to another.
+@MX_TABLE_place2d@
+
+
+### `trianglewave`
+Generate a triangle wave from the given scalar input. The generated wave ranges from zero to one and repeats on integer boundaries.
+@MX_TABLE_trianglewave@
+
+### `reflect`
+Reflect the incoming 3D vector about a surface normal vector.
+@MX_TABLE_reflect@
+
+### `refract`
+Refract the incoming 3D vector through a surface with the given surface normal and relative index of refraction.
+@MX_TABLE_refract@
+
+## Logical Operator Nodes
+
+Logical operator nodes have one or two boolean typed inputs, and are used to construct higher level logical flow through the nodegraph.
+
+### `and`
+logically AND the two input boolean values
+@MX_TABLE_and@
+
+
+### `or`
+logically Inclusive OR the two input boolean values
+@MX_TABLE_or@
+
+
+### `xor`
+logically Exclusive OR the two input boolean values
+@MX_TABLE_xor@
+
+
+### `not`
+logically NOT the input boolean value
+@MX_TABLE_not@
+
+
+## Adjustment Nodes
+
+Adjustment nodes have one input named "in", and apply a specified function to values in the incoming stream.
+
+### `contrast`
+Increase or decrease the contrast of the incoming `in` values using `amount` as a linear slope multiplier.
+@MX_TABLE_contrast@
+
+
+### `remap`
+Linearly remap incoming values from one range of values [`inlow`, `inhigh`] to another [`outlow`, `outhigh`].
+@MX_TABLE_remap@
+
+### `range`
+Remap incoming values from one range of values to another, optionally applying a gamma correction "in the middle".
+@MX_TABLE_range@
+
+
+### `smoothstep`
+Output a smooth, hermite-interpolated remapping of input values from [`low`, `high`] to [0,1].
+@MX_TABLE_smoothstep@
+
+### `luminance`
+Output a grayscale value containing the luminance of the incoming RGB color in all color channels
+@MX_TABLE_luminance@
+
+### `rgbtohsv`
+Convert an incoming color from RGB to HSV space (with H and S ranging from 0 to 1); the alpha channel is left unchanged if present. This conversion is not affected by the current color space.
+@MX_TABLE_rgbtohsv@
+
+### `hsvtorgb`
+Convert an incoming color from HSV to RGB space; the alpha channel is left unchanged if present. This conversion is not affected by the current color space.
+@MX_TABLE_hsvtorgb@
+
+### `hsvadjust`
+Adjust the hue, saturation and value of an RGB color by converting the input color to HSV, adding amount.x to the hue, multiplying the saturation by amount.y, multiplying the value by amount.z, then converting back to RGB.
+@MX_TABLE_hsvadjust@
+
+### `saturate`
+Adjust the saturation of a color, the alpha channel will be unchanged if present.
+@MX_TABLE_saturate@
+
+### `colorcorrect`
+Combines various adjustment nodes into one artist-friendly color correction node. For color4 inputs, the alpha value is unchanged.
+@MX_TABLE_colorcorrect@
+
+## Compositing Nodes
+
+Compositing nodes have two (required) inputs named `fg` and `bg`, and apply a function to combine them. Compositing nodes are split into five subclassifications: [Premult Nodes](#premult-nodes), [Blend Nodes](#blend-nodes), [Merge Nodes](#merge-nodes), [Masking Nodes](#masking-nodes), and the [Mix Node](#mix-node).
+
+
+### Premult Nodes
+
+Premult nodes operate on 4-channel (color4) inputs/outputs, have one input named `in`, and either apply or unapply the alpha to the float or RGB color.
+
+### `premult`
+Multiply the R or RGB channels of the input by the Alpha channel of the input.
+@MX_TABLE_premult@
+
+### `unpremult`
+Divide the RGB channels of the input by the Alpha channel of the input. If the Alpha value is zero, it is passed through unchanged.
+@MX_TABLE_power@
+
+
+### Blend Nodes
+
+Blend nodes take two 1-4 channel inputs and apply the same operator to all channels (the math for alpha is the same as for R or RGB); below, "F" and "B" refer to any individual channel of the `fg` and `bg` inputs respectively.
+
+
+### `plus`
+Add two 1-4 channel inputs, with optional mixing between the bg input and the result.
+@MX_TABLE_plus@
+
+### `minus`
+Subtract two 1-4 channel inputs, with optional mixing between the bg input and the result.
+@MX_TABLE_minus@
+
+### `difference`
+Absolute-value difference of two 1-4 channel inputs, with optional mixing between the bg input and the result.
+@MX_TABLE_difference@
+
+
+### `burn`
+Take two 1-4 channel inputs and apply the same operator to all channels:
+1-(1-B)/F
+@MX_TABLE_burn@
+
+### `dodge`
+Take two 1-4 channel inputs and apply the same operator to all channels:
+B/(1-F)
+@MX_TABLE_dodge@
+
+### `screen`
+Take two 1-4 channel inputs and apply the same operator to all channels:
+1-(1-F)*(1-B)
+@MX_TABLE_screen@
+
+
+### `overlay`
+Take two 1-4 channel inputs and apply the same operator to all channels:
+2FB if B<0.5;
+1-2(1-F)(1-B) if B>=0.5
+@MX_TABLE_overlay@
+
+
+### Merge Nodes
+
+Merge nodes take two 4-channel (color4) inputs and use the built-in alpha channel(s) to control the compositing of the `fg` and `bg` inputs; "F" and "B" refer to individual non-alpha channels of the `fg` and `bg` inputs respectively, while "f" and "b" refer to the alpha channels of the `fg` and `bg` inputs. Merge nodes are not defined for 1-channel or 3-channel inputs, and cannot be used on vectorN streams.
+
+
+### `disjointover`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F+B if f+b<=1
+F+B(1-f)/b if f+b>1
+alpha: min(f+b,1)
+
+| **`disjointover`** | F+B if f+b<=1; F+B(1-f)/b if f+b>1 | min(f+b,1) |
+
+@MX_TABLE_disjointover@
+
+### `in`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+* RGB = Fb
+* Alpha = fb
+
+@MX_TABLE_in@
+
+
+### `mask`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+Bf (alpha: bf)
+
+@MX_TABLE_mask@
+
+
+### `matte`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+Ff+B(1-f) (alpha: f+b(1-f))
+
+@MX_TABLE_matte@
+
+### `out`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F(1-b) (alpha: f(1-b))
+
+@MX_TABLE_out@
+
+
+### `over`
+Take two color4 inputs and use the built-in alpha channel(s) to control the compositing of the fg and bg inputs:
+F+B(1-f) (alpha: f+b(1-f))
+
+@MX_TABLE_over@
+
+### Masking Nodes
+
+Masking nodes take one 1-4 channel input `in` plus a separate float `mask` input and apply the same operator to all "in" channels; "F" refers to any individual channel of the `in` input, while "m" refers to the mask input.
+
+
+### `inside`
+An "inside" mask operation returning Fm
+
+@MX_TABLE_inside@
+
+
+### `outside`
+An "outside" mask operation returning F(1-m)
+
+@MX_TABLE_outside@
+
+
+### Mix Node
+
+The Mix node takes two 1-4 channel inputs `fg` and `bg` plus a separate 1-channel (float) or N-channel (same type and number of channels as `fg` and `bg`) `mix` input and mixes the `fg` and `bg` according to the mix value, either uniformly for a "float" `mix` type, or per-channel for non-float `mix` types; "F" refers to any individual channel of the `in` input, while "m" refers to the appropriate channel of the mix input.
+
+### `mix`
+A "mix" operation blending from "bg" to "fg" according to the mix amount, returning Fm+B(1-m)
+
+@MX_TABLE_mix@
+
+See also the [Standard Shader Nodes](#standard-shader-nodes) section below for additional shader-semantic variants of the [`mix` node](#node-mix-shader).
+
+
+## Conditional Nodes
+
+Conditional nodes are used to compare values of two streams, or to select a value from one of several streams.
+
+
+### `ifgreater`
+
+Output the value of the `in1` or `in2` stream depending on whether the `value1` input is greater than the `value2` input.
+
+
+|Port |Description |Type |Default |Accepted Values|
+|--------|--------------------------------------------------|-----------------------------------------|--------|---------------|
+|`value1`|The first value to be compared |float, integer |__one__ | |
+|`value2`|The second value to be compared |float, integer |__zero__| |
+|`in1` |The value stream to output if `value1` > `value2` |float, colorN, vectorN, matrixNN, integer|__zero__| |
+|`in2` |The value stream to output if `value1` <= `value2`|Same as `in1` |__zero__| |
+|`out` |Output: the result of the comparison |Same as `in1` |`in1` | |
+
+
+
+### `ifgreatereq`
+
+Output the value of the `in1` or `in2` stream depending on whether the `value1` input is greater or equal to the `value2` input.
+
+
+|Port |Description |Type |Default |Accepted Values|
+|--------|--------------------------------------------------|-----------------------------------------|--------|---------------|
+|`value1`|the first value to be compared |float, integer |__one__ | |
+|`value2`|the second value to be compared |float, integer |__zero__| |
+|`in1` |The value stream to output if `value1` >= `value2`|float, colorN, vectorN, matrixNN, integer|__zero__| |
+|`in2` |The value stream to output if `value1` < `value2` |Same as `in1` |__zero__| |
+|`out` |Output: the result of the comparison |Same as `in1` |`in1` | |
+
+
+### `ifequal`
+
+
+|Port |Description |Type |Default |Accepted Values|
+|--------|--------------------------------------------------|-----------------------------------------|--------|---------------|
+|`value1`|the first value to be compared |float, integer |__one__ | |
+|`value2`|the second value to be compared |float, integer |__zero__| |
+|`in1` |The value stream to output if `value1` = `value2` |float, colorN, vectorN, matrixNN, integer|__zero__| |
+|`in2` |The value stream to output if `value1` != `value2`|Same as `in1` |__zero__| |
+|`out` |Output: the result of the comparison |Same as `in1` |`in1` | |
+
+
+|Port |Description |Type |Default |Accepted Values|
+|--------|--------------------------------------------------|-----------------------------------------|--------|---------------|
+|`value1`|The first value to be compared |boolean |false | |
+|`value2`|The second value to be compared |boolean |false | |
+|`in1` |The value stream to output if `value1` = `value2` |float, colorN, vectorN, matrixNN, integer|__zero__| |
+|`in2` |The value stream to output if `value1` != `value2`|Same as `in1` |__zero__| |
+|`out` |Output: the result of the comparison |Same as `in1` |`in1` | |
+
+
+|Port |Description |Type |Default|Accepted Values|
+|--------|----------------------------------|-------|-------|---------------|
+|`value1`|The first value to be compared |boolean|false | |
+|`value2`|The first value to be compared |boolean|false | |
+|`out` |Output: true if `value1` = `value2|boolean| | |
+
+
+### `switch`
+
+Output the value of one of up to ten input streams, according to the value of a selector input `which`. Note that not all inputs need to be connected. The output has the same type as `in1`, with a default value of __zero__.
+
+@MX_TABLE_switch@
+
+
+
+## Channel Nodes
+
+Channel nodes are used to perform channel manipulations and data type conversions on streams.
+
+
+### `extract`
+
+Isolate a single float channel from a __vectorN__ or __colorN__ stream. The output value is of type `float` with a default value of __zero__.
+
+@MX_TABLE_extract@
+
+The valid range for `index` should be clamped to $[0,N)$ in the user interface, where __N__ is the size of the input vector stream. `index` is a uniform, non-varying value. Any `index` values outside of the valid range should result in an error.
+
+
+### `separate2`
+
+Split the channels of a 2-channel stream into separate float outputs.
+
+@MX_TABLE_separate2@
+
+For the vector2-input `in`, `outx` and `outy` correspond to the x- and y-components of `in`..
+
+
+### `separate3`
+
+Split the channels of a 3-channel stream into separate float outputs.
+
+@MX_TABLE_separate3@
+
+When the input `in` is a color3, `outr`, `outg`, and `outb` correspond to the r-, g-, and b-components of `in`, respectively.
+
+When the input `in` is a vector3, `outx`, `outy`, and `outz` correspond to the x-, y-, and z-components of `in`, respectively.
+
+
+### `separate4`
+
+Split the channels of a 4-channel stream into separate float outputs.
+
+@MX_TABLE_separate4@
+
+When the input `in` is a color4, `outr`, `outg`, `outb`, and `outa` correspond to the r-, g-, b-, and alpha components of `in`, respectively.
+
+When the input `in` is a vector4, `outx`, `outy`, `outz`, and `outw` correspond to the x-, y-, z-, and w-components of `in`, respectively.
+
+
+### `combine2`
+
+Combine the channels from two streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine2@
+
+
+### `combine3`
+
+Combine the channels from three streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine3@
+
+
+### `combine4`
+
+Combine the channels from four streams into the same number of channels of a single output stream of a compatible type.
+
+@MX_TABLE_combine4@
+
+
+
+## Convolution Nodes
+
+Convolution nodes have one input named "in", and apply a defined convolution function on the input stream. Some of these nodes may not be implementable in ray tracing applications; they are provided for the benefit of purely 2D image processing applications.
+
+
+### `blur`
+
+Applies a convolution blur to the input stream.
+
+@MX_TABLE_blur@
+
+
+### `heighttonormal`
+
+Convert a scalar height map to a tangent-space normal map of type `vector3`. The output normal map is encoded with all channels in the [0-1] range, enabling its storage in unsigned image formats.
+
+@MX_TABLE_heighttonormal@
+
+
+# Standard Shader Nodes
+
+The Standard MaterialX Library defines the following nodes and node variants operating on "shader"-semantic types. Standard library shaders do not respond to external illumination; please refer to the [**MaterialX Physically Based Shading Nodes**](./MaterialX.PBRSpec.md#materialx-pbs-library) document for definitions of additional nodes and shader constructors which do respond to illumination, as well as [**MaterialX NPR Shading Nodes**](./MaterialX.NPRSpec.md) for definitions of shaders and nodes applicable to non-photorealistic rendering.
+
+
+
+* **`surface_unlit`**: an unlit surface shader node, representing a surface that can emit and transmit light, but does not receive illumination from light sources or other surfaces. Output type surfaceshader.
+ * `emission` (float): the surface emission amount; default is 1.0
+ * `emission_color` (color3): surface emission color; default is (1, 1, 1)
+ * `transmission` (float): the surface transmission amount; default is 0
+ * `transmission_color` (color3): surface transmission color; default is (1, 1, 1)
+ * `opacity` (float): surface cutout opacity; default is 1.0
+
+
+
+* **`displacement`**: Constructs a displacement shader describing geometric modification to surfaces. Output type "displacementshader".
+ * `displacement` (float or vector3): Scalar (along the surface normal direction) or vector displacement (in (dPdu, dPdv, N) tangent/normal space) for each position. Default is 0.
+ * `scale` (float): Scale factor for the displacement vector. Default is 1.0.
+
+
+
+* **`mix`**: linear blend between two surface/displacement/volumeshader closures.
+ * `bg` (surface/displacement/volumeshader): the name of the background shader-semantic node
+ * `fg` (surface/displacement/volumeshader): the name of the foreground shader-semantic node
+ * `mix` (float): the blending factor used to mix the two input closures
diff --git a/javascript/MaterialXTest/xmlIo.spec.js b/javascript/MaterialXTest/xmlIo.spec.js
index 670b0df43d..591459d472 100644
--- a/javascript/MaterialXTest/xmlIo.spec.js
+++ b/javascript/MaterialXTest/xmlIo.spec.js
@@ -10,7 +10,7 @@ describe('XmlIo', () =>
// These should be relative to cwd
const includeTestPath = 'data/includes';
- const libraryPath = '../../libraries/stdlib';
+ const libraryPath = '../build/libraries/DataLibraryBuild/stdlib';
const examplesPath = '../../resources/Materials/Examples';
// TODO: Is there a better way to get these filenames than hardcoding them here?
// The C++ tests load all files in the given directories. This would work in Node, but not in the browser.
diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt
index cc8139aef3..af2decab62 100644
--- a/libraries/CMakeLists.txt
+++ b/libraries/CMakeLists.txt
@@ -1,3 +1,4 @@
+
if(MATERIALX_BUILD_DATA_LIBRARY)
# Build generated products from the MaterialX data library.
# Initially, this step is a simple copy across folders, but our intent
@@ -5,17 +6,54 @@ if(MATERIALX_BUILD_DATA_LIBRARY)
set(DATA_LIBRARY_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/DataLibraryBuild)
- file(GLOB_RECURSE MATERIALX_DATA_LIBRARY_SOURCE_FILES
- RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
- LIST_DIRECTORIES false
- *.mtlx
- *.md
- *.glsl
- *.osl
- *.h
- *.metal)
-
- foreach(SOURCE_FILE IN LISTS MATERIALX_DATA_LIBRARY_SOURCE_FILES)
+ file(GLOB_RECURSE MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ LIST_DIRECTORIES false
+ *.mtlx)
+
+ file(GLOB_RECURSE MATERIALX_DATA_LIBRARY_SHADER_SOURCE_FILES
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ LIST_DIRECTORIES false
+ *.md
+ *.glsl
+ *.osl
+ *.h
+ *.metal)
+
+ if (MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS OR MATERIALX_BUILD_BAKE_NAMED_VALUES)
+ foreach(SOURCE_FILE IN LISTS MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES)
+ set(DEST_FILEPATH ${DATA_LIBRARY_BUILD_DIR}/${SOURCE_FILE})
+ list(APPEND MATERIALX_DATA_LIBRARY_MTLX_BUILD_FILES ${DEST_FILEPATH})
+ endforeach()
+
+ if (MATERIALX_BUILD_EXPAND_TEMPLATE_ELEMS)
+ set(EXPAND_TEMPLATE_ELEMS_ARG "--expandTemplateElems")
+ endif()
+ if (MATERIALX_BUILD_BAKE_NAMED_VALUES)
+ set(BAKE_NAMED_VALUES_ARG "--bakeNamedValues")
+ endif()
+
+ add_custom_command(
+ OUTPUT ${MATERIALX_DATA_LIBRARY_MTLX_BUILD_FILES}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${DATA_LIBRARY_BUILD_DIR}
+ COMMAND ${_MaterialXBuildLibrary} --sourceLibraryRoot ${CMAKE_CURRENT_SOURCE_DIR} --destLibraryRoot ${DATA_LIBRARY_BUILD_DIR} ${EXPAND_TEMPLATE_ELEMS_ARG} ${BAKE_NAMED_VALUES_ARG}
+ DEPENDS ${MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES} MaterialXBuildLibrary
+ )
+
+ list(APPEND MATERIALX_DATA_LIBRARY_BUILD_FILES ${MATERIALX_DATA_LIBRARY_MTLX_BUILD_FILES})
+ else()
+ foreach(SOURCE_FILE IN LISTS MATERIALX_DATA_LIBRARY_MTLX_SOURCE_FILES)
+ set(SOURCE_FILEPATH ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE})
+ set(DEST_FILEPATH ${DATA_LIBRARY_BUILD_DIR}/${SOURCE_FILE})
+ add_custom_command(
+ OUTPUT ${DEST_FILEPATH}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SOURCE_FILEPATH} ${DEST_FILEPATH}
+ DEPENDS ${SOURCE_FILEPATH})
+ list(APPEND MATERIALX_DATA_LIBRARY_BUILD_FILES ${DEST_FILEPATH})
+ endforeach()
+ endif()
+
+ foreach(SOURCE_FILE IN LISTS MATERIALX_DATA_LIBRARY_SHADER_SOURCE_FILES)
set(SOURCE_FILEPATH ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE})
set(DEST_FILEPATH ${DATA_LIBRARY_BUILD_DIR}/${SOURCE_FILE})
add_custom_command(
diff --git a/libraries/nprlib/nprlib_defs.mtlx b/libraries/nprlib/nprlib_defs.mtlx
index bb6d2e8ff0..9e50a1619e 100644
--- a/libraries/nprlib/nprlib_defs.mtlx
+++ b/libraries/nprlib/nprlib_defs.mtlx
@@ -22,8 +22,8 @@
The current scene view direction, as defined by the shading environment.
-->
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/libraries/pbrlib/pbrlib_defs.mtlx b/libraries/pbrlib/pbrlib_defs.mtlx
index 0bb75716e6..c772e9a39b 100644
--- a/libraries/pbrlib/pbrlib_defs.mtlx
+++ b/libraries/pbrlib/pbrlib_defs.mtlx
@@ -24,12 +24,12 @@
A BSDF node for diffuse reflection.
-->
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -122,186 +122,59 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ The filename can include one or more substitutions to change the file name (including frame number) that is accessed, as described in the Filename Substitutions section.
+ If no value for layer is provided and the input file has multiple layers, then the 'default' layer will be used, or 'rgba' if there is no 'default' layer.
+ If `filtertype` is not specified, an application may use its own filtering method.
+ -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -466,54 +237,26 @@
A constant value. When exposed as a public parameter, this is a way to create a
value that can be accessed in multiple places in the opgraph.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -1332,8 +762,8 @@
as defined in a specific coordinate space.
-->
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -1482,7 +868,7 @@
The current frame number as defined by the host environment.
-->
-
+
-
+
@@ -1501,242 +887,94 @@
Node:
Add "in2" value/stream to the incoming float/integer/color/vector/matrix.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -2894,390 +1567,134 @@
Node:
Remap a value from one range of float/color/vector values to another.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3354,8 +1754,8 @@
Multiply the R or RGB channels of the input by the Alpha channel of the input.
-->
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
@@ -3553,9 +1883,9 @@
channel(s) to control the compositing of the fg and bg inputs: Fb (alpha: fb)
-->
-
-
-
+
+
+
@@ -3565,9 +1895,9 @@
channel(s) to control the compositing of the fg and bg inputs: Bf (alpha: bf)
-->
-
-
-
+
+
+
@@ -3577,9 +1907,9 @@
channel(s) to control the compositing of the fg and bg inputs: Ff+B(1-f) (alpha: f+b(1-f))
-->
-
-
-
+
+
+
@@ -3589,9 +1919,9 @@
channel(s) to control the compositing of the fg and bg inputs: F(1-b) (alpha: f(1-b))
-->
-
-
-
+
+
+
@@ -3601,9 +1931,9 @@
channel(s) to control the compositing of the fg and bg inputs: F+B(1-f) (alpha: f+b(1-f))
-->
-
-
-
+
+
+
@@ -3612,131 +1942,48 @@
Take one 1-4 channel input "in" plus a separate float "mask" input and apply the same
operator to all channels: in * mask
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3746,721 +1993,164 @@
Node:
Output the value of in1 if value1>value2, or the value of in2 if value1<=value2.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4471,202 +2161,68 @@
Convert a stream from one type to another; only certain unambiguous conversion
types are supported.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -4764,65 +2311,66 @@
Extract a single channel from a colorN or vectorN stream, outputting a float.
-->
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -4833,52 +2381,24 @@
Node:
A gaussian-falloff blur.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -4890,9 +2410,9 @@
Logical And operation for two boolean values.
-->
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
@@ -4932,85 +2452,12 @@
Node:
No-op; passes its input to the output unchanged.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/libraries/stdlib/stdlib_ng.mtlx b/libraries/stdlib/stdlib_ng.mtlx
index 33e0ac0787..28d87af399 100644
--- a/libraries/stdlib/stdlib_ng.mtlx
+++ b/libraries/stdlib/stdlib_ng.mtlx
@@ -3487,193 +3487,45 @@
Raise incoming half/float/color/vector values to the "in2" power.
Negative "in1" values will result in negative output values. ie. out = sign(in1)*pow(abs(in1),in2)
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/MaterialXTest/main.py b/python/MaterialXTest/main.py
index 7f878b3d28..a2ed00ad5e 100644
--- a/python/MaterialXTest/main.py
+++ b/python/MaterialXTest/main.py
@@ -25,8 +25,9 @@
[1.0, 2.0, 3.0],
['one', 'two', 'three'])
+_defaultSearchPath = mx.getDefaultDataSearchPath().asString()
_fileDir = os.path.dirname(os.path.abspath(__file__))
-_libraryDir = os.path.join(_fileDir, '../../libraries/stdlib/')
+_libraryDir = os.path.join(_defaultSearchPath, 'libraries/stdlib/')
_exampleDir = os.path.join(_fileDir, '../../resources/Materials/Examples/')
_searchPath = _libraryDir + mx.PATH_LIST_SEPARATOR + _exampleDir
diff --git a/source/JsMaterialX/CMakeLists.txt b/source/JsMaterialX/CMakeLists.txt
index aaadafda74..242dcdd559 100644
--- a/source/JsMaterialX/CMakeLists.txt
+++ b/source/JsMaterialX/CMakeLists.txt
@@ -87,7 +87,7 @@ else()
endif()
-set(JS_LINK_FLAGS_GENSHADER "${JS_LINK_FLAGS_CORE} --preload-file ${PROJECT_SOURCE_DIR}/libraries@libraries ")
+set(JS_LINK_FLAGS_GENSHADER "${JS_LINK_FLAGS_CORE} --preload-file ${PROJECT_BINARY_DIR}/libraries/DataLibraryBuild@libraries ")
add_executable(JsMaterialXCore MaterialXLib.cpp
${CORE_DEPS}
@@ -132,6 +132,8 @@ set_target_properties(JsMaterialXGenShader
INSTALL_RPATH "${MATERIALX_UP_TWO_RPATH}"
SOVERSION "${MATERIALX_MAJOR_VERSION}")
+add_dependencies(JsMaterialXGenShader MaterialXBuildData)
+
target_link_libraries(JsMaterialXCore
PUBLIC MaterialXCore
PUBLIC MaterialXFormat
diff --git a/source/MaterialXBuildTools/CMakeLists.txt b/source/MaterialXBuildTools/CMakeLists.txt
new file mode 100644
index 0000000000..b1302ccaa4
--- /dev/null
+++ b/source/MaterialXBuildTools/CMakeLists.txt
@@ -0,0 +1,20 @@
+# This subproject is necessary to support building the build tools on the host machine
+# where they may be used when cross compiling for another target
+
+cmake_minimum_required(VERSION 3.26)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+set(CMAKE_MACOSX_RPATH ON)
+
+project(MaterialXBuildTools)
+
+list(APPEND CMAKE_MODULE_PATH
+ ${PROJECT_SOURCE_DIR}/../../cmake/macros)
+
+include(Public)
+
+add_subdirectory(../MaterialXCore MaterialXCore)
+add_subdirectory(../MaterialXFormat MaterialXFormat)
+
+add_subdirectory(buildLibrary)
+add_subdirectory(buildDocs)
diff --git a/source/MaterialXBuildTools/buildDocs/CMakeLists.txt b/source/MaterialXBuildTools/buildDocs/CMakeLists.txt
new file mode 100644
index 0000000000..e8ce306ce3
--- /dev/null
+++ b/source/MaterialXBuildTools/buildDocs/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(TARGET_NAME MaterialXBuildDocs)
+
+file(GLOB materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
+file(GLOB materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*")
+
+add_executable(${TARGET_NAME} ${materialx_source} ${materialx_headers})
+
+target_link_libraries(${TARGET_NAME} PRIVATE
+ MaterialXFormat
+ MaterialXCore)
diff --git a/source/MaterialXBuildTools/buildDocs/Main.cpp b/source/MaterialXBuildTools/buildDocs/Main.cpp
new file mode 100644
index 0000000000..8fdb5a7338
--- /dev/null
+++ b/source/MaterialXBuildTools/buildDocs/Main.cpp
@@ -0,0 +1,554 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+#include
+#include
+#include
+
+namespace mx = MaterialX;
+
+#include
+#include
+#include
+#include
+
+const std::string cmdLineOptions =
+ " Options: \n"
+ " --sourceLibraryRoot [FILEPATH] Directory containing the source data library files.\n"
+ " --destDocRoot [FILEPATH] Directory to write the modified data library files.\n"
+ " --sourceMDRoot [FILEPATH] Directory to write the modified data library files.\n"
+ " --manifestFile [FILEPATH] Directory to write the modified data library files.\n"
+ " --help Display the complete list of command-line options\n";
+
+template void parseToken(std::string token, std::string type, T& res)
+{
+ if (token.empty())
+ {
+ return;
+ }
+
+ mx::ValuePtr value = mx::Value::createValueFromStrings(token, type);
+ if (!value)
+ {
+ std::cout << "Unable to parse token " << token << " as type " << type << std::endl;
+ return;
+ }
+
+ res = value->asA();
+}
+
+mx::FilePathVec getMaterialXFiles(const mx::FilePath& libraryRoot)
+{
+ mx::FilePathVec result;
+
+ auto subDirs = libraryRoot.getSubDirectories();
+ for (const auto& subDir : subDirs)
+ {
+ for (const auto& file : subDir.getFilesInDirectory("mtlx"))
+ {
+ auto absFile = subDir / file;
+ std::string absFileStr = absFile.asString();
+ std::string relFileStr = absFileStr.substr(libraryRoot.asString().size()+1);
+ result.emplace_back(mx::FilePath(relFileStr));
+ }
+ }
+ return result;
+}
+
+
+mx::FilePathVec getMarkdownFiles(const mx::FilePath& libraryRoot)
+{
+ mx::FilePathVec result;
+
+ auto subDirs = libraryRoot.getSubDirectories();
+ for (const auto& subDir : subDirs)
+ {
+ for (const auto& file : subDir.getFilesInDirectory("md"))
+ {
+ auto absFile = subDir / file;
+ std::string absFileStr = absFile.asString();
+ std::string relFileStr = absFileStr.substr(libraryRoot.asString().size()+1);
+ result.emplace_back(mx::FilePath(relFileStr));
+ }
+ }
+ return result;
+}
+
+
+void makeDir(const mx::FilePath& dir, bool recursive = true)
+{
+ if (recursive && !dir.getParentPath().isDirectory())
+ {
+ makeDir(dir.getParentPath(), recursive);
+ }
+ dir.createDirectory();
+}
+
+bool writeFile(const mx::FilePath& file, const std::string fileContents, bool createDirectories = true)
+{
+ if (createDirectories && !file.getParentPath().isDirectory())
+ {
+ makeDir(file.getParentPath(), true);
+ }
+
+ std::string filename = file.asString();
+ std::ofstream file_out(filename);
+ if (!file_out.is_open()) {
+ std::cerr << "Error opening file for writing - " << filename << std::endl;
+ return false;
+ }
+
+ file_out << fileContents;
+ file_out.close();
+ return true;
+}
+
+std::string readTextFile(const mx::FilePath& file)
+{
+ std::string filename = file.asString();
+
+ std::ifstream file_in(filename);
+ if (!file_in.is_open()) {
+ std::cerr << "Error opening file for reading - " << filename << std::endl;
+ return "";
+ }
+
+ std::string contents;
+ std::string line;
+ while (std::getline(file_in, line)) {
+ contents += line +"\n";
+ }
+ file_in.close();
+
+ return contents;
+}
+
+class MDTable
+{
+public:
+
+ using Row = std::vector;
+
+ MDTable(Row headings) : _headings(headings) {}
+
+ void addRow(const Row& row)
+ {
+ if (row.size() != _headings.size())
+ throw std::runtime_error("Incorrect row size)");
+ _rows.emplace_back(row);
+ }
+
+ std::vector columnWidths() const
+ {
+ std::vector widths;
+ for (const auto& heading : _headings)
+ {
+ widths.emplace_back(heading.size());
+ }
+ for (const auto& row : _rows)
+ {
+ for (unsigned int i = 0, n = row.size(); i < n; ++i)
+ {
+ widths[i] = std::max(widths[i], row[i].size());
+ }
+ }
+ return widths;
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& os, const MDTable& table);
+
+ static void outputRow(const Row& row, const std::vector colSizes, std::ostream& os, char padChar = ' ');
+private:
+ const Row _headings;
+ std::vector _rows;
+
+};
+
+std::ostream& operator<<(
+ std::ostream& os, const MDTable& table
+) {
+
+ os << std::endl;
+ auto colWidths = table.columnWidths();
+
+ MDTable::outputRow(table._headings, colWidths, os);
+ MDTable::outputRow(std::vector(colWidths.size(), ""), colWidths,os, '-');
+ for (const auto& row : table._rows)
+ {
+ MDTable::outputRow(row, colWidths, os);
+ }
+
+ os << std::endl;
+
+ return os;
+}
+
+void MDTable::outputRow(const Row& row, const std::vector colSizes, std::ostream& os, char padChar)
+{
+
+ os << "|";
+ for (unsigned int i = 0, n = row.size(); i < n; ++i)
+ {
+ const auto& entry = row[i];
+ auto numSpaces = colSizes[i] - entry.size();
+ os << entry << std::string(numSpaces, padChar) << "|";
+ }
+ os << std::endl;
+}
+
+int main(int argc, char* const argv[])
+{
+ std::vector tokens;
+ for (int i = 1; i < argc; i++)
+ {
+ tokens.emplace_back(argv[i]);
+ }
+
+ std::string sourceLibraryRootStr = "";
+ std::string destDocRootStr = "";
+ std::string sourceMDRootStr = "";
+ std::string destMDRootStr = "";
+ std::string manifestFileStr = "";
+
+ for (size_t i = 0; i < tokens.size(); i++)
+ {
+ const std::string& token = tokens[i];
+ const std::string& nextToken = i + 1 < tokens.size() ? tokens[i + 1] : mx::EMPTY_STRING;
+
+ if (token == "--sourceLibraryRoot")
+ {
+ sourceLibraryRootStr = nextToken;
+ }
+ else if (token == "--destDocRoot")
+ {
+ destDocRootStr = nextToken;
+ }
+ else if (token == "--sourceMDRoot")
+ {
+ sourceMDRootStr = nextToken;
+ }
+ else if (token == "--destMDRoot")
+ {
+ destMDRootStr = nextToken;
+ }
+ else if (token == "--manifestFile")
+ {
+ manifestFileStr = nextToken;
+ }
+ else if (token == "--help")
+ {
+ std::cout << " MaterialXBuildLibrary version " << mx::getVersionString() << std::endl;
+ std::cout << cmdLineOptions << std::endl;
+ return 0;
+ }
+ else
+ {
+ std::cout << "Unrecognized command-line option: " << token << std::endl;
+ std::cout << "Launch the viewer with '--help' for a complete list of supported options." << std::endl;
+ continue;
+ }
+
+ if (nextToken.empty())
+ {
+ std::cout << "Expected another token following command-line option: " << token << std::endl;
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ mx::FilePath sourceLibraryRoot = mx::FilePath(sourceLibraryRootStr);
+ mx::FilePath destDocRoot = mx::FilePath(destDocRootStr);
+ mx::FilePath sourceMDRoot = mx::FilePath(sourceMDRootStr);
+ mx::FilePath destMDRoot = mx::FilePath(destMDRootStr);
+
+ if (!sourceLibraryRoot.isDirectory())
+ {
+ std::cerr << "Source Library Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ if (!destDocRoot.isDirectory())
+ {
+ std::cerr << "Destination Library Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ if (!sourceMDRoot.isDirectory())
+ {
+ std::cerr << "Destination MD Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ if (!destMDRoot.isDirectory())
+ {
+ std::cerr << "Destination MD Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ mx::FilePathVec mtlxFiles = getMaterialXFiles(sourceLibraryRoot);
+ mx::FilePathVec mdFiles = getMarkdownFiles(sourceMDRoot);
+
+ mx::XmlReadOptions readOptions;
+ readOptions.readComments = true;
+ readOptions.readNewlines = true;
+ readOptions.expandTemplateElems = false;
+
+ mx::XmlWriteOptions writeOptions;
+ writeOptions.createDirectories = true;
+
+ const MDTable::Row headings = {"Port", "Description", "Type", "Default", "Accepted Values"};
+ std::unordered_map nodeTables;
+
+ auto processNodeDef = [&](const mx::NodeDefPtr nodedef, std::string matchString="#NOT_MATCHING#", std::string optionsTypeStr = "")
+ {
+
+ std::string firstPortMatchingType = "";
+ auto ports = nodedef->getChildrenOfType();
+
+ bool doAcceptedValues = false;
+ for (const auto& port: ports)
+ {
+ std::string portAcceptedValues = port->getAttribute("spec_acceptedvalues");
+ if (!portAcceptedValues.empty())
+ {
+ doAcceptedValues = true;
+ break;
+ }
+ }
+
+ MDTable table = doAcceptedValues ? MDTable({"Port", "Description", "Type", "Default", "Accepted Values"}) : MDTable({"Port", "Description", "Type", "Default"});
+ for (const auto& port : ports)
+ {
+ std::string portName = port->getName();
+ std::string portDesc = port->getAttribute("spec_desc");
+ std::string portType = port->getType();
+ std::string portDefault = port->getValueString();
+
+ if (auto output = port->asA())
+ {
+ portDefault = port->getAttribute("default");
+ }
+
+ if (portDefault.empty())
+ {
+ portDefault = port->getAttribute("defaultinput");
+ if (portDefault.empty())
+ {
+ portDefault = port->getAttribute("defaultgeomprop");
+ if (!portDefault.empty())
+ {
+ portDefault = std::string("_") + portDefault + "_";
+ }
+ else if (portType == "string" || portType == "filename")
+ {
+ portDefault = "";
+ }
+ }
+ else
+ {
+ portDefault = std::string("`") + portDefault + "`";
+ }
+ }
+
+ if (portType == "string" || portType == "filename")
+ {
+ portDefault = std::string("\"") + portDefault + "\"";
+ }
+
+ std::string portAcceptedValues = port->getAttribute("spec_acceptedvalues");
+ if (portAcceptedValues.empty())
+ {
+ portAcceptedValues = port->getAttribute("enum");
+ }
+
+ portDefault = mx::replaceSubstrings(portDefault, {{"Value:",""}});
+ if (portDefault == "zero" || portDefault == "one" || portDefault == "half")
+ {
+ portDefault = std::string("__") + portDefault + "__";
+ }
+
+ if (portType == matchString)
+ {
+ if (firstPortMatchingType.empty())
+ {
+ firstPortMatchingType = portName;
+ portType = optionsTypeStr;
+ }
+ else
+ {
+ portType = "Same as `"+firstPortMatchingType+"`";
+ }
+ }
+
+ if (auto output = port->asA())
+ {
+ if (portDesc.empty())
+ portDesc = "Output";
+ else
+ portDesc = "Output: " + portDesc;
+ }
+
+
+ auto row = MDTable::Row();
+
+ row.emplace_back(std::string("`") + portName + "`");
+ row.emplace_back(portDesc);
+ row.emplace_back(portType);
+ row.emplace_back(portDefault);
+
+ if (doAcceptedValues)
+ {
+ row.emplace_back(portAcceptedValues);
+ }
+
+ table.addRow(row);
+ }
+
+
+ std::stringstream sss;
+ sss << table;
+
+ const std::string key = "@MX_TABLE_"+nodedef->getNodeString()+"@";
+
+ nodeTables[key] += sss.str();
+ };
+
+
+ for (const auto& mtlxFile : mtlxFiles)
+ {
+ mx::FilePath sourceFile = sourceLibraryRoot / mtlxFile;
+ mx::DocumentPtr doc = mx::createDocument();
+ mx::readFromXmlFile(doc, sourceFile, mx::FileSearchPath(), &readOptions);
+
+ for (const auto& child : doc->getChildren())
+ {
+ const auto& childCategory = child->getCategory();
+
+ if (childCategory == "nodedef")
+ {
+ auto nodedef = child->asA();
+ processNodeDef(nodedef);
+ }
+ else if (childCategory == "template")
+ {
+ if (!child->hasAttribute("varName") || !child->hasAttribute("options"))
+ throw std::runtime_error(" must have both varName and options attributes");
+
+ const auto varName = child->getAttribute("varName");
+ const auto options = child->getAttribute("options");
+
+ std::string optionsTypeStr = options;
+
+ static const std::vector preferredOptionOrder = {
+ "float",
+ "colorN", "color3", "color4",
+ "vectorN", "vector2", "vector3", "vector4",
+ "matrixNN", "matrix33", "matrix44",
+ "boolean",
+ "integer",
+ "string", "filename",
+ "surfaceshader", "displacementshader", "volumeshader", "lightshader"
+ };
+
+ auto contains = [&](const std::vector& array, const std::string& str) -> bool {
+ return std::find(array.begin(), array.end(), str) != array.end();
+ };
+ auto remove = [&](std::vector& array, const std::string& str) -> void {
+ array.erase(std::remove(array.begin(), array.end(), str));
+ };
+
+ {
+ // create optionsTypeStr from options
+ // consolidate color3,color4 -> colorN
+ // consolidate matrix33,matrix44 -> matrixNN
+ // consolidate vector2,vector3,vector4 -> vectorN
+
+ auto parts = mx::splitString(options,",");
+ for (auto& part : parts)
+ {
+ part = mx::trimSpaces(part);
+ }
+
+ if (contains(parts, "vector2") && contains(parts, "vector3") && contains(parts, "vector4"))
+ {
+ remove(parts, "vector2");
+ remove(parts, "vector3");
+ remove(parts, "vector4");
+ parts.emplace_back("vectorN");
+ }
+ if (contains(parts, "color3") && contains(parts, "color4"))
+ {
+ remove(parts, "color3");
+ remove(parts, "color4");
+ parts.emplace_back("colorN");
+ }
+ if (contains(parts, "matrix33") && contains(parts, "matrix44"))
+ {
+ remove(parts, "matrix33");
+ remove(parts, "matrix44");
+ parts.emplace_back("matrixNN");
+ }
+
+ std::vector res;
+ for (const auto& part : preferredOptionOrder)
+ {
+ if (contains(parts, part))
+ {
+ remove(parts, part);
+ res.emplace_back(part);
+ }
+ }
+
+ if (parts.size())
+ {
+ std::stringstream ss;
+ for (const auto& part : parts)
+ {
+ ss << "\""<getChildrenOfType();
+ for (const auto& nodedef : childNodeDefs)
+ {
+ processNodeDef(nodedef, matchString, optionsTypeStr);
+ }
+ }
+ }
+ }
+
+ std::string manifestStr = "";
+ for (const auto& it : nodeTables)
+ {
+ mx::FilePath destFile = destDocRoot / (it.first + std::string("_doc.md"));
+ writeFile(destFile, it.second);
+ manifestStr += destFile.asString() + "\n";
+ }
+ writeFile(mx::FilePath(manifestFileStr), manifestStr);
+
+ for (const auto& mdFile : mdFiles)
+ {
+ mx::FilePath sourceFile = sourceMDRoot / mdFile;
+ mx::FilePath destFile = destMDRoot / mdFile;
+
+ auto contents = readTextFile(sourceFile);
+ auto newContents = mx::replaceSubstrings(contents, nodeTables);
+
+ writeFile(destFile, newContents);
+ }
+
+ return 0;
+}
diff --git a/source/MaterialXBuildTools/buildLibrary/CMakeLists.txt b/source/MaterialXBuildTools/buildLibrary/CMakeLists.txt
new file mode 100644
index 0000000000..bc7183aa3c
--- /dev/null
+++ b/source/MaterialXBuildTools/buildLibrary/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(TARGET_NAME MaterialXBuildLibrary)
+
+file(GLOB materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
+file(GLOB materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*")
+
+add_executable(${TARGET_NAME} ${materialx_source} ${materialx_headers})
+
+target_link_libraries(${TARGET_NAME} PRIVATE
+ MaterialXFormat
+ MaterialXCore)
diff --git a/source/MaterialXBuildTools/buildLibrary/Main.cpp b/source/MaterialXBuildTools/buildLibrary/Main.cpp
new file mode 100644
index 0000000000..3e4a6ba7fe
--- /dev/null
+++ b/source/MaterialXBuildTools/buildLibrary/Main.cpp
@@ -0,0 +1,212 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+#include
+#include
+#include
+
+namespace mx = MaterialX;
+
+#include
+
+const std::string cmdLineOptions =
+ " Options: \n"
+ " --sourceLibraryRoot [FILEPATH] Directory containing the source data library files.\n"
+ " --destLibraryRoot [FILEPATH] Directory to write the modified data library files.\n"
+ " --help Display the complete list of command-line options\n";
+
+template void parseToken(std::string token, std::string type, T& res)
+{
+ if (token.empty())
+ {
+ return;
+ }
+
+ mx::ValuePtr value = mx::Value::createValueFromStrings(token, type);
+ if (!value)
+ {
+ std::cout << "Unable to parse token " << token << " as type " << type << std::endl;
+ return;
+ }
+
+ res = value->asA();
+}
+
+mx::FilePathVec getMaterialXFiles(const mx::FilePath& libraryRoot)
+{
+ mx::FilePathVec result;
+
+ auto subDirs = libraryRoot.getSubDirectories();
+ for (const auto& subDir : subDirs)
+ {
+ for (const auto& file : subDir.getFilesInDirectory("mtlx"))
+ {
+ auto absFile = subDir / file;
+ std::string absFileStr = absFile.asString();
+ std::string relFileStr = absFileStr.substr(libraryRoot.asString().size()+1);
+ result.emplace_back(mx::FilePath(relFileStr));
+ }
+ }
+ return result;
+}
+
+void replaceNamedValues(mx::DocumentPtr doc, mx::ConstDocumentPtr stdlib)
+{
+ const std::string typeValuePrefix = "Value:";
+
+ // replace named value "Value:" strings with concrete values
+ for (auto elem : doc->traverseTree())
+ {
+ auto port = elem->asA();
+ if (!port)
+ {
+ continue;
+ }
+
+ mx::StringVec const& attributeNames = port->getAttributeNames();
+ for (std::string const& attrName: attributeNames) {
+ std::string const& valueStr = port->getAttribute(attrName);
+
+ if (mx::stringStartsWith(valueStr, typeValuePrefix))
+ {
+ auto typeDef = stdlib->getTypeDef(port->getType());
+ if (!typeDef)
+ {
+ throw mx::Exception("Unable to find typeDef '"+port->getType()+"'");
+ }
+
+ auto valueNameStr = valueStr.substr(typeValuePrefix.size());
+ if (!typeDef->hasAttribute(valueNameStr))
+ {
+ throw mx::Exception("Unable to find named value '"+valueNameStr+"' for type '"+typeDef->getName()+"'");
+ }
+
+ port->setAttribute(attrName, typeDef->getAttribute(valueNameStr));
+ }
+ }
+ }
+}
+
+int main(int argc, char* const argv[])
+{
+ std::vector tokens;
+ for (int i = 1; i < argc; i++)
+ {
+ tokens.emplace_back(argv[i]);
+ }
+
+ std::string sourceLibraryRootStr = "";
+ std::string destLibraryRootStr = "";
+ bool bakeNamedValues = false;
+ bool expandTemplateElems = false;
+
+ for (size_t i = 0; i < tokens.size(); i++)
+ {
+ const std::string& token = tokens[i];
+ const std::string& nextToken = i + 1 < tokens.size() ? tokens[i + 1] : mx::EMPTY_STRING;
+
+ if (token == "--sourceLibraryRoot")
+ {
+ sourceLibraryRootStr = nextToken;
+ }
+ else if (token == "--destLibraryRoot")
+ {
+ destLibraryRootStr = nextToken;
+ }
+ else if (token == "--bakeNamedValues")
+ {
+ bakeNamedValues = true;
+ continue;
+ }
+ else if (token == "--expandTemplateElems")
+ {
+ expandTemplateElems = true;
+ continue;
+ }
+ else if (token == "--help")
+ {
+ std::cout << " MaterialXBuildLibrary version " << mx::getVersionString() << std::endl;
+ std::cout << cmdLineOptions << std::endl;
+ return 0;
+ }
+ else
+ {
+ std::cout << "Unrecognized command-line option: " << token << std::endl;
+ std::cout << "Launch the viewer with '--help' for a complete list of supported options." << std::endl;
+ continue;
+ }
+
+ if (nextToken.empty())
+ {
+ std::cout << "Expected another token following command-line option: " << token << std::endl;
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ mx::FilePath sourceLibraryRoot = mx::FilePath(sourceLibraryRootStr);
+ mx::FilePath destLibrarayRoot = mx::FilePath(destLibraryRootStr);
+
+ if (!sourceLibraryRoot.isDirectory())
+ {
+ std::cerr << "Source Library Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ if (!destLibrarayRoot.isDirectory())
+ {
+ std::cerr << "Destination Library Root is not a directory" << std::endl;
+ return 1;
+ }
+
+ mx::DocumentPtr stdlib = mx::createDocument();
+ mx::loadLibraries({}, mx::FileSearchPath(sourceLibraryRootStr), stdlib);
+
+ mx::FilePathVec mtlxFiles = getMaterialXFiles(sourceLibraryRoot);
+
+ mx::XmlReadOptions readOptions;
+ readOptions.readComments = true;
+ readOptions.readNewlines = true;
+ readOptions.expandTemplateElems = expandTemplateElems;
+
+ mx::XmlWriteOptions writeOptions;
+ writeOptions.createDirectories = true;
+
+ for (const auto& mtlxFile : mtlxFiles)
+ {
+ mx::DocumentPtr doc = mx::createDocument();
+
+ mx::FilePath sourceFile = sourceLibraryRoot / mtlxFile;
+ mx::FilePath destFile = destLibrarayRoot / mtlxFile;
+
+ mx::readFromXmlFile(doc, sourceFile, mx::FileSearchPath(), &readOptions);
+
+ if (bakeNamedValues)
+ {
+ replaceNamedValues(doc, stdlib);
+ }
+
+ {
+ // remove "specification" attributes all start with "spec_"
+ for (auto elem: doc->traverseTree())
+ {
+ mx::StringVec const& attributeNames = elem->getAttributeNames();
+ for (std::string const& attrName: attributeNames) {
+ if (mx::stringStartsWith(attrName, "spec_")) {
+ elem->removeAttribute(attrName);
+ }
+ }
+ }
+ }
+
+ mx::writeToXmlFile(doc, destFile, &writeOptions);
+ }
+
+ return 0;
+}
diff --git a/source/MaterialXCore/CMakeLists.txt b/source/MaterialXCore/CMakeLists.txt
index c3c845afda..53a3b4c7b0 100644
--- a/source/MaterialXCore/CMakeLists.txt
+++ b/source/MaterialXCore/CMakeLists.txt
@@ -15,3 +15,9 @@ mx_add_library(MaterialXCore
target_include_directories(${TARGET_NAME}
PUBLIC
$)
+
+# This define controls if the named "Value:" are dynamically evaluated at runtime
+target_compile_definitions(${TARGET_NAME}
+ PRIVATE
+ MATERIALX_BUILD_BAKE_NAMED_VALUES=$
+)
diff --git a/source/MaterialXCore/Element.cpp b/source/MaterialXCore/Element.cpp
index e36a6fcbdf..595aa0787d 100644
--- a/source/MaterialXCore/Element.cpp
+++ b/source/MaterialXCore/Element.cpp
@@ -644,6 +644,36 @@ TypeDefPtr TypedElement::getTypeDef() const
//
// ValueElement methods
//
+/// Get the value string of a element.
+const string ValueElement::getValueString() const
+{
+#if MATERIALX_BUILD_BAKE_NAMED_VALUES
+ return getAttribute(VALUE_ATTRIBUTE);
+#else
+ const string typeValuePrefix = "Value:";
+
+ auto valueStr = getAttribute(VALUE_ATTRIBUTE);
+ if (!stringStartsWith(valueStr, typeValuePrefix))
+ {
+ return valueStr;
+ }
+
+ auto typeDef = getTypeDef();
+ if (!typeDef)
+ {
+ throw Exception("Unable to find typeDef '"+getType()+"'");
+ }
+
+ auto valueNameStr = valueStr.substr(typeValuePrefix.size());
+ if (!typeDef->hasAttribute(valueNameStr))
+ {
+ throw Exception("Unable to find named value '"+valueNameStr+"' for type '"+typeDef->getName()+"'");
+ }
+
+ return typeDef->getAttribute(valueNameStr);
+#endif
+}
+
string ValueElement::getResolvedValueString(StringResolverPtr resolver) const
{
diff --git a/source/MaterialXCore/Element.h b/source/MaterialXCore/Element.h
index 89f5c87152..7c7125bf0c 100644
--- a/source/MaterialXCore/Element.h
+++ b/source/MaterialXCore/Element.h
@@ -954,10 +954,7 @@ class MX_CORE_API ValueElement : public TypedElement
}
/// Get the value string of a element.
- const string& getValueString() const
- {
- return getAttribute(VALUE_ATTRIBUTE);
- }
+ const string getValueString() const;
/// Return the resolved value string of an element, applying any string
/// substitutions that are defined at the element's scope.
diff --git a/source/MaterialXFormat/XmlIo.cpp b/source/MaterialXFormat/XmlIo.cpp
index 83e4721dc1..5a20f9a896 100644
--- a/source/MaterialXFormat/XmlIo.cpp
+++ b/source/MaterialXFormat/XmlIo.cpp
@@ -167,6 +167,70 @@ void elementFromXml(const xml_node& xmlNode, ElementPtr elem, const XmlReadOptio
}
}
+void recursivelyReplaceStrings(ElementPtr elem, const StringMap& strReplaceMapping)
+{
+ for (const auto& attrName : elem->getAttributeNames())
+ {
+ auto attrValue = elem->getAttribute(attrName);
+ auto newAttrValue = replaceSubstrings(attrValue, strReplaceMapping);
+ elem->setAttribute(attrName, newAttrValue);
+ }
+
+ for (auto childElem : elem->getChildren())
+ {
+ recursivelyReplaceStrings(childElem, strReplaceMapping);
+ }
+}
+
+void expandXMLTemplateElems(DocumentPtr doc)
+{
+ // replace node definitions that use a TypeList
+ for (auto elem : doc->traverseTree())
+ {
+ if (elem->getCategory() != "template")
+ continue;
+
+ if (!elem->hasAttribute("varName")) {
+ // std::cerr << " without 'varName' attribute found" << std::endl;
+ continue;
+ }
+
+ if (!elem->hasAttribute("options")) {
+ // std::cerr << " without 'options' attribute found" << std::endl;
+ continue;
+ }
+
+ const auto typeNameAttr = elem->getAttribute("varName");
+ const auto optionsAttr = elem->getAttribute("options");
+
+ const auto options = splitString(optionsAttr, ARRAY_VALID_SEPARATORS);
+
+ const auto childElems = elem->getChildren();
+
+ auto parentElem = elem->getParent();
+ const auto origIndex = parentElem->getChildIndex(elem->getName());
+
+ for (const auto& childElem : childElems)
+ {
+ auto index = origIndex;
+
+ for (const auto& option : options)
+ {
+ StringMap strReplaceMapping = {{"@"+typeNameAttr+"@", option}};
+
+ string newName = replaceSubstrings(childElem->getName(), strReplaceMapping);
+
+ auto newChildElem = parentElem->addChildOfCategory(childElem->getCategory(), newName);
+ parentElem->setChildIndex(newChildElem->getName(), index++);
+ newChildElem->copyContentFrom(childElem);
+
+ recursivelyReplaceStrings(newChildElem, strReplaceMapping);
+ }
+ parentElem->removeChild(elem->getName());
+ }
+ }
+}
+
void documentFromXml(DocumentPtr doc, const xml_document& xmlDoc, const FileSearchPath& searchPath, const XmlReadOptions* readOptions)
{
xml_node xmlRoot = xmlDoc.child(Document::CATEGORY.c_str());
@@ -211,6 +275,11 @@ void documentFromXml(DocumentPtr doc, const xml_document& xmlDoc, const FileSear
// Build the element tree.
elementFromXml(xmlRoot, doc, readOptions);
+ if (!readOptions || readOptions->expandTemplateElems)
+ {
+ expandXMLTemplateElems(doc);
+ }
+
// Upgrade version if requested.
if (!readOptions || readOptions->upgradeVersion)
{
@@ -268,9 +337,6 @@ unsigned int getParseOptions(const XmlReadOptions* readOptions)
//
XmlReadOptions::XmlReadOptions() :
- readComments(false),
- readNewlines(false),
- upgradeVersion(true),
readXIncludeFunction(readFromXmlFile)
{
}
@@ -279,8 +345,7 @@ XmlReadOptions::XmlReadOptions() :
// XmlWriteOptions methods
//
-XmlWriteOptions::XmlWriteOptions() :
- writeXIncludeEnable(true)
+XmlWriteOptions::XmlWriteOptions()
{
}
@@ -355,6 +420,13 @@ void writeToXmlStream(DocumentPtr doc, std::ostream& stream, const XmlWriteOptio
void writeToXmlFile(DocumentPtr doc, const FilePath& filename, const XmlWriteOptions* writeOptions)
{
+ if (writeOptions && writeOptions->createDirectories)
+ {
+ if (!filename.getParentPath().isDirectory())
+ {
+ filename.getParentPath().createDirectory();
+ }
+ }
std::ofstream ofs(filename.asString());
writeToXmlStream(doc, ofs, writeOptions);
}
diff --git a/source/MaterialXFormat/XmlIo.h b/source/MaterialXFormat/XmlIo.h
index 5c80ae8005..f971043730 100644
--- a/source/MaterialXFormat/XmlIo.h
+++ b/source/MaterialXFormat/XmlIo.h
@@ -39,15 +39,15 @@ class MX_FORMAT_API XmlReadOptions
/// If true, then XML comments will be read into documents as comment elements.
/// Defaults to false.
- bool readComments;
+ bool readComments = false;
/// If true, then XML newlines will be read into documents as newline elements.
/// Defaults to false.
- bool readNewlines;
+ bool readNewlines = false;
/// If true, then documents from earlier versions of MaterialX will be upgraded
/// to the current version. Defaults to true.
- bool upgradeVersion;
+ bool upgradeVersion = true;
/// If provided, this function will be invoked when an XInclude reference
/// needs to be read into a document. Defaults to readFromXmlFile.
@@ -56,6 +56,9 @@ class MX_FORMAT_API XmlReadOptions
/// The vector of parent XIncludes at the scope of the current document.
/// Defaults to an empty vector.
StringVec parentXIncludes;
+
+ /// Expand any tags present in the document
+ bool expandTemplateElems = true;
};
/// @class XmlWriteOptions
@@ -68,11 +71,15 @@ class MX_FORMAT_API XmlWriteOptions
/// If true, elements with source file markings will be written as
/// XIncludes rather than explicit data. Defaults to true.
- bool writeXIncludeEnable;
+ bool writeXIncludeEnable = true;
/// If provided, this function will be used to exclude specific elements
/// (those returning false) from the write operation. Defaults to nullptr.
ElementPredicate elementPredicate;
+
+ /// If true, any necessary directories will be created to write the
+ /// file.
+ bool createDirectories = false;
};
/// @class ExceptionParseError