{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Shader Generation\n", "\n", "This book will examine how to set up MaterialX for code generation. This book covers the \n", "generateshader.py script provided as part of the core distribution.\n", "\n", "Topics covered:\n", "1. Shading language 'target's\n", "2. Module / library organization\n", "3. Setting up generators and generation contexts \n", "5. Real world units and color management \n", "6. Discovering \"renderable\" items, and generating code\n", "7. Extracting source code\n", "\n", "Details behind code generation, generation options, introspection / reflection,\n", "and input binding is covered as part of rendering. \n", "\n", "Background on code generation can be found here." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Code Generation Modules / Libraries\n", "\n", "For the Python distribution, each code generator resides in a separate module in the MaterialX package. \n", "\n", "The name of each module is of the form:\n", "```\n", " PyMaterialXGen\n", "``` \n", "where target is the code generation target written in camel-case. \n", "\n", "All target names start with `gen` and then the shading language name:\n", "```\n", " gen\n", "``` \n", "\n", "For example, the target for the OSL shading language is `genosl`, with the module's postfix string being `GenOsl`.\n", "The variants for GLSL include: `genglsl` and `genessl`, which reside in a single module with postfix string `GenGlsl`.\n", "\n", "The C++ library equivalent to the module is named:\n", "```\n", " MaterialXGen\n", "```\n", "This is basically the same as the Python module but without the `Py` prefix.\n", "\n", "The module `PyMaterialxShader` contains the base support for all code generators. In the code below this module as well as modules for all targets are imported." ] }, { "cell_type": "code", "execution_count": 193, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Have required version for MSL\n" ] } ], "source": [ "import sys, os, subprocess\n", "import MaterialX as mx\n", "import MaterialX.PyMaterialXGenShader as mx_gen_shader\n", "import MaterialX.PyMaterialXGenGlsl as mx_gen_glsl\n", "import MaterialX.PyMaterialXGenOsl as mx_gen_osl\n", "import MaterialX.PyMaterialXGenMdl as mx_gen_mdl\n", "\n", "# Version check\n", "from mtlxutils.mxbase import *\n", "supportsMSL = haveVersion(1, 38, 7)\n", "if supportsMSL:\n", " import MaterialX.PyMaterialXGenMsl as mx_gen_msl\n", " print('Have required version for MSL')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "The basic setup requires that a document is created, the standard libraries are loaded, and the document containing the elements to generate code for to be present.\n", "\n", "> For the purposes of showing formatted results we use the `IPython` package to be able to output `Markdown` from Python code." ] }, { "cell_type": "code", "execution_count": 194, "metadata": {}, "outputs": [], "source": [ "from IPython.display import display_markdown" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Additional modules can be imported to support functionality such as code validation.\n", "\n", "### Code Validation\n", "\n", "For `GLSL`, `ESSL`, and `Vulkan` languages glslangValidator can be used for syntax and compilation validation. It is installed using `vcpkg` and is run as part of the CI process. For OSL and MDL: `olsc` and `mdlc` compilers are used respectively.\n", "\n", "The `generateshader.py` script supports passing in a external program as an argument. The source code passed to this program for validation.\n", "\n", "The utility function from that script has been extracted out and is included below as an example." ] }, { "cell_type": "code", "execution_count": 195, "metadata": {}, "outputs": [], "source": [ "\n", "def validateCode(sourceCodeFile, codevalidator, codevalidatorArgs):\n", " if codevalidator:\n", " cmd = codevalidator + ' ' + sourceCodeFile \n", " if codevalidatorArgs:\n", " cmd += ' ' + codevalidatorArgs\n", " print('----- Run Validator: '+ cmd)\n", " try:\n", " output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)\n", " result = output.decode(encoding='utf-8')\n", " except subprocess.CalledProcessError as out: \n", " return (out.output.decode(encoding='utf-8'))\n", " return \"\"\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Exception Handling\n", "\n", "In the following code, a document is first created and then a sample file which defines a \"marble\" material is read in.\n", "\n", "Note that MaterialX throws exceptions when encountering errors instead of keeping track of status code.\n", "There are some specific exceptions which provide additional information beyond the regular exception information.\n", "\n", "It is always prudent to catch exceptions including checking of custom exceptions \n", "provided for file I/O, code generation and rendering.\n", "\n", "In this example a \"file missing\" exception may be returned if the file cannot be read.\n", "\n", " The possible `Exception` types are defined in the API documentation. In Python, the exception name is the same as the C++ class name." ] }, { "cell_type": "code", "execution_count": 196, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Read in valid file \"data/standard_surface_marble_solid.mtlx\" for code generation.\n" ] } ], "source": [ "# Read in MaterialX file\n", "#\n", "inputFilename = 'data/standard_surface_marble_solid.mtlx'\n", "doc = mx.createDocument()\n", "try:\n", " mx.readFromXmlFile(doc, inputFilename) \n", " \n", " valid, msg = doc.validate()\n", " if not valid:\n", " raise mx.Exception('Document is invalid')\n", "\n", " print('Read in valid file \"'\"%s\"'\" for code generation.' % inputFilename)\n", "\n", "except mx.ExceptionMissing as err:\n", " print('File %s could not be loaded: \"' % inputFilename, err, '\"')\n", "except mx.Exception as err:\n", " print('File %s fail to load properly: \"' % inputFilename, err, '\"')\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Implementations\n", "\n", "The standard library includes both definitions as well as implementations for each shading language. \n", "The `stdlib`, `stdlib`, and `bxdf` folders contain definitions and any corresponding node graph and\n", "source code implementations.\n", "\n", "The sub-folders starting with `gen` contain per-target source code implementations.\n" ] }, { "cell_type": "code", "execution_count": 197, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

Library Folders:

\n",
       "+--bxdf\n",
       "|\t+--lama\n",
       "|\t+--translation\n",
       "+--cmlib\n",
       "+--lights\n",
       "|\t+--genglsl <-- e.g. target genglsl implementations reside here\n",
       "|\t+--genmsl\n",
       "+--nprlib\n",
       "|\t+--genglsl <-- e.g. target genglsl implementations reside here\n",
       "|\t+--genmdl\n",
       "|\t+--genmsl\n",
       "|\t+--genosl\n",
       "+--pbrlib\n",
       "|\t+--genglsl <-- e.g. target genglsl implementations reside here\n",
       "|\t \t+--lib\n",
       "|\t+--genmdl\n",
       "|\t+--genmsl\n",
       "|\t+--genosl\n",
       "|\t \t+--legacy\n",
       "|\t \t+--lib\n",
       "+--stdlib\n",
       "|\t+--genglsl <-- e.g. target genglsl implementations reside here\n",
       "|\t \t+--lib\n",
       "|\t+--genmdl\n",
       "|\t+--genmsl\n",
       "|\t \t+--lib\n",
       "|\t+--genosl\n",
       "|\t \t+--include\n",
       "|\t \t+--lib\n",
       "+--targets\n",
       "\n",
       "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import pkg_resources\n", "\n", "def getLibraryFoldersString(root='libraries', showMTLXFiles=True, showSourceFiles=False):\n", " '''\n", " Scan the MaterialX library folder and print out the folder structure\n", " '''\n", " folderString = ''\n", " files = pkg_resources.resource_listdir('MaterialX', root)\n", " for f in files:\n", " if not mx.FilePath(f).getExtension():\n", " folderString += '+--%s\\n' % f\n", " fpath = root+'/'+f\n", " if pkg_resources.resource_isdir('MaterialX', fpath):\n", " subfiles = pkg_resources.resource_listdir('MaterialX', fpath)\n", " for sf in subfiles:\n", " sfPath = mx.FilePath(fpath+'/'+sf)\n", " extension = sfPath.getExtension()\n", " if extension and showMTLXFiles:\n", " folderString += '|\\t|--%s\\n' % sf\n", " elif not extension:\n", " if sf == 'genglsl':\n", " folderString += '|\\t+--%s <-- e.g. target genglsl implementations reside here\\n' % sf\n", " else:\n", " folderString += '|\\t+--%s\\n' % sf\n", "\n", " sfpath = fpath+'/'+sf\n", " if pkg_resources.resource_isdir('MaterialX', sfpath):\n", " subsubfiles = pkg_resources.resource_listdir('MaterialX', sfpath)\n", " for ssf in subsubfiles:\n", " extension = mx.FilePath(ssf).getExtension()\n", " if extension and showSourceFiles:\n", " print('show source : ssf')\n", " folderString += '|\\t \\t+--%s\\n' % ssf\n", " elif not extension:\n", " folderString += '|\\t \\t+--%s\\n' % ssf\n", "\n", " # If not last file, add a line break\n", " #if f != files[-1]:\n", " # folderString += '|\\n'\n", " #folderString += '|\\n'\n", "\n", " return folderString\n", "\n", "folderString = getLibraryFoldersString('libraries', False, False)\n", "folderString = '

Library Folders:

\\n' + folderString + \"\\n
\"\n", "display_markdown(folderString, raw=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As this code is required for code generation to occur, the standard `libraries` folder must be read in. \n", "\n", "The `libraries` folder can be examined as part of the Python package as of 1.38.7. Below is a simple utility to traverse and print out the folders found. This starts where the site packages are located (as returned using `site.getsitepackages()`). This works either whether called from a virtual environment or not." ] }, { "cell_type": "code", "execution_count": 198, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "- libraries\n", " - bxdf\n", " - lama\n", " - translation\n", " - cmlib\n", " - lights\n", " - genglsl ( _Root of target specific implementations_ )\n", " - genmsl ( _Root of target specific implementations_ )\n", " - nprlib\n", " - genglsl ( _Root of target specific implementations_ )\n", " - genmdl ( _Root of target specific implementations_ )\n", " - genmsl ( _Root of target specific implementations_ )\n", " - genosl ( _Root of target specific implementations_ )\n", " - pbrlib\n", " - genglsl ( _Root of target specific implementations_ )\n", " - lib\n", " - genmdl ( _Root of target specific implementations_ )\n", " - genmsl ( _Root of target specific implementations_ )\n", " - genosl ( _Root of target specific implementations_ )\n", " - legacy\n", " - lib\n", " - stdlib\n", " - genglsl ( _Root of target specific implementations_ )\n", " - lib\n", " - genmdl ( _Root of target specific implementations_ )\n", " - genmsl ( _Root of target specific implementations_ )\n", " - lib\n", " - genosl ( _Root of target specific implementations_ )\n", " - include\n", " - lib\n", " - targets\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import site\n", " \n", "def printPaths(rootPath):\n", " \"Print a 'tree' of paths given a root path\"\n", " outputStrings = []\n", " for dirpath, dirs, files in os.walk(rootPath):\n", " testpath = dirpath.removeprefix(rootPath)\n", " path = testpath.split(os.sep)\n", " comment = ''\n", " if len(path) > 1:\n", " if path[len(path)-1].startswith('gen'):\n", " comment = ' ( _Root of target specific implementations_ )' \n", " indent = (len(path)-1)*' '\n", " outputString = indent + '- ' + os.path.basename(dirpath) + comment + '\\n'\n", " outputStrings.append(outputString) \n", " display_markdown(outputStrings, raw=True)\n", "\n", "packages = site.getsitepackages()\n", "for package in packages:\n", " libraryPath = mx.FilePath(package + '/MaterialX/libraries')\n", " if os.path.exists(libraryPath.asString()):\n", " printPaths(libraryPath.asString())\n", " break" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Implementations are of the type `Implementation`." ] }, { "cell_type": "code", "execution_count": 199, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Version: 1.39.0. Loaded 750 standard library definitions\n" ] } ], "source": [ "# Load in standard libraries, and include an definitions local to the input file\n", "stdlib = mx.createDocument()\n", "searchPath = mx.getDefaultDataSearchPath()\n", "searchPath.append(os.path.dirname(inputFilename)) \n", "libraryFolders = mx.getDefaultDataLibraryFolders()\n", "try:\n", " libFiles = mx.loadLibraries(libraryFolders, searchPath, stdlib)\n", " doc.importLibrary(stdlib)\n", " print('Version: %s. Loaded %s standard library definitions' % (mx.getVersionString(), len(doc.getNodeDefs())))\n", "except mx.Exception as err:\n", " print('Failed to load standard library definitions: \"', err, '\"')\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The `getImplementations()` API is used to get a list of `Implementation` references. Even though the total number\n", "of implementations seem large, only the source code for a specific generator are used at any given time." ] }, { "cell_type": "code", "execution_count": 200, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Read in 2217 implementations\n" ] } ], "source": [ "# Get list of all implementations\n", "implmentations = doc.getImplementations()\n", "if implmentations:\n", " print('Read in %d implementations' % len(implmentations))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation Targets\n", "\n", "Every non-nodegraph implementation must specify a `target` that it supports. \n", "\n", "A `target` name is used to identify shading languages and their variants. The naming convention is:\n", "```\n", " gen\n", "```\n", "These are represented as a `TargetDef`. \n", "The target identifiers are loaded in as part of the standard library, and these can be queried by\n", "looking for elements of category `targetdef`. For convenience, a list of available targets can be retrieved from a \n", "document using the `getTargetDefs()` API.\n", "\n", "However, at time of writing, this is missing from the Python API. Thus a simple utility function is provided here." ] }, { "cell_type": "code", "execution_count": 201, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found target identifier: essl with 552 source implementations.\n", "Found target identifier: genglsl with 552 source implementations.\n", "Found target identifier: genmdl with 561 source implementations.\n", "Found target identifier: genmsl with 551 source implementations.\n", "Found target identifier: genosl with 549 source implementations.\n" ] } ], "source": [ "# The targetdef type and support API does not currently exist so cannot be used.\n", "#doc.getTargetDefs()\n", "#doc.getChildOfType(mx.TargetDef)\n", "\n", "# Utility that basically does what doc.getTargetDefs() does.\n", "# Matching the category can be used in lieu to testing for the class type.\n", "def getTargetDefs(doc):\n", " targets = []\n", " for element in doc.getChildren():\n", " if element.getCategory() == 'targetdef':\n", " targets.append(element.getName())\n", " return targets\n", "\n", "foundTargets = getTargetDefs(doc)\n", "for target in foundTargets:\n", " implcount = 0\n", " # Find out how many implementations we have \n", " for impl in implmentations:\n", " testtarget = target\n", " if target == 'essl':\n", " testtarget = 'genglsl'\n", " if impl.getTarget() == testtarget:\n", " implcount = implcount + 1\n", " print('Found target identifier:', target, 'with', implcount, 'source implementations.') " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Code Generators\n", "\n", "### Generator Creation\n", "\n", "Every code generator must have a `target` identifier to indicate which shading langauge / variant it supports.\n", "A language version can be used to distinguish a variant if appropriate (e.g. ESSL is distinguishable this way)\n", "\n", "It is recommended that all new generators have a unique target name. \n", "\n", "Currently there is no \"registry\" for generators by target so the user must know before hand which generators exist and\n", "go through all generators to find one with the appropriate `target` to use.\n", "\n", "Targets themselves can be \"inherited\" which is reflected in the inheritance hierarchy for generators.\n", "For example the `essl` (ESSL) target inherits from the `genglsl` (GLSL) target as does the corresponding generators. \n", "Inheritance is generally used to specialize code generation to handle shading language variations. \n", "\n", "For a list of generators and their derivations see documentation for the base class ShaderGenerator\n", "\n", "\n", "\n", "Note that Vulkan has the same target as `genglsl`, but has it's own generator. Also that the Metal generator will only show up\n", "in the Mac build of documentation.\n", "\n", "Integrations are free to create custom generators. Some notable existing generators include those used to support USD HDStorm, VEX, and Arnold OSL.\n", "\n", "Any such generator can be instantiated and use the same generation process as described here.\n", "\n", "For this example, we will show how all the the generators can be created, but will only produce OSL code via an `OslShaderGenerator` generator. This can be found in the `PyMaterialXGenOsl` Python submodule and corresponding `MaterialXGenOsl` library in C++. " ] }, { "cell_type": "code", "execution_count": 202, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_keys(['genosl', 'genmdl', 'essl', 'genglsl', 'genmsl'])\n", "Use code generator for target: genmdl for language: mdl\n" ] } ], "source": [ "\n", "# Create all generators\n", "generators = []\n", "generators.append(mx_gen_osl.OslShaderGenerator.create())\n", "generators.append(mx_gen_mdl.MdlShaderGenerator.create())\n", "generators.append(mx_gen_glsl.EsslShaderGenerator.create())\n", "generators.append(mx_gen_glsl.VkShaderGenerator.create())\n", "if supportsMSL:\n", " generators.append(mx_gen_msl.MslShaderGenerator.create())\n", "\n", "# Create a dictionary based on target identifier\n", "generatordict = {}\n", "for gen in generators:\n", " generatordict[gen.getTarget()] = gen\n", "\n", "# Choose generator to use based on target identifier\n", "language = 'mdl'\n", "target = 'genmdl'\n", "if language == 'osl':\n", " target = 'genosl'\n", "elif language == 'mdl':\n", " target = 'genmdl'\n", "elif language == 'essl':\n", " target = 'essl'\n", "elif language == 'msl':\n", " target = 'genmsl'\n", "elif language in ['glsl', 'vulkan']:\n", " target = 'genglsl'\n", "\n", "print(generatordict.keys())\n", "\n", "#test_language = 'mdl'\n", "test_shadergen = generatordict['genmdl']\n", "#print('Find code generator for target:', test_shadergen.getTarget(), ' for language:', test_language, \". Version:\", test_shadergen.getVersion())\n", "\n", "shadergen = generatordict[target]\n", "print('Use code generator for target:', shadergen.getTarget(), ' for language: ', language)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Generator Contexts\n", "\n", "A \"generation context\" is required to be created for each generator instance. This is represented by a \n", "`GenContext` structure. \n", "\n", "This context provides a number of settings and options to be used for code generation. \n", "\n", "For simplicity, we will only point out the minimal requirements. This includes providing a search path to where source code implementations can be found. Any number of paths can be added using the `registerSourceCodeSearchPath()` function, on the context. The search order is first to last path added.\n", "\n", "Adding the path to the `libraries` folder is sufficient to find the source code for standard library definitions found in sub-folders. If the user has custom definitions in other locations, the root of those locations should be added. " ] }, { "cell_type": "code", "execution_count": 203, "metadata": {}, "outputs": [], "source": [ " # Create a context for a generator\n", "context = mx_gen_shader.GenContext(shadergen)\n", "\n", "# Register a path to where implmentations can be found.\n", "context.registerSourceCodeSearchPath(searchPath)\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Color Management\n", "\n", "Color management is used to ensure that input colors are interpreted properly via shader code.\n", "\n", "A \"color management system\" cab be created and specified to be used by a shader generator. \n", "During code generation, additional logic is emitted into the shader source code via the system.\n", "\n", "Usage of such as system during code generation is optional, as some renderers perform color management on input values\n", "and images before binding them to shading code.\n", "\n", "Color management systems need to be derived from the base API interface `ColorManagementSystem`). A \"default\" system is provided\n", "as part of the MaterialX distribution.\n", "\n", "It is necessary to indicate which `target` shading language code when instantiating the color management system. Naturally specifying a non-matching target will inject incompatible code.\n", "\n", "The setup steps are:\n", "\n", "1. Create the system. In this example the \"default\" system is created with the `target` being specified at creation time.\n", "2. Setting where the library of definitions exists for the system. In this case the main document which contains the standard library is specified.\n", "3. Setting the color management system on the generator. If it is not set or cleared then no color management will occur during code generation." ] }, { "cell_type": "code", "execution_count": 204, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Set up CMS: default_cms for target: genmdl\n" ] } ], "source": [ "# Create default CMS\n", "cms = mx_gen_shader.DefaultColorManagementSystem.create(shadergen.getTarget()) \n", "# Indicate to the CMS where definitions can be found\n", "cms.loadLibrary(doc)\n", "# Indicate to the code generator to use this CMS\n", "shadergen.setColorManagementSystem(cms)\n", "\n", "cms = shadergen.getColorManagementSystem()\n", "if cms:\n", " print('Set up CMS: %s for target: %s' \n", " % (cms.getName(), shadergen.getTarget()))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Real World Units\n", "\n", "To handle real-world unit specifiers a \"unit system\" should be instantiated and associated with the generator.\n", "The API interface is a UnitSystem which definitions. \n", "\n", "By default a unit system does not know how to perform any conversions. This is provided by a \n", "`UnitConverterRegistry` which contains a list of convertors. \n", "\n", "Currently MaterialX supports convertors for converting linear units: distance, and angle.\n", "The corresponding API interface is `LinearUnitConverter`.\n" ] }, { "cell_type": "code", "execution_count": 205, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created unit converter registry\n" ] } ], "source": [ "# Create unit registry\n", "registry = mx.UnitConverterRegistry.create()\n", "if registry:\n", " # Get distance and angle unit type definitions and create a linear converter for each\n", " distanceTypeDef = doc.getUnitTypeDef('distance')\n", " if distanceTypeDef:\n", " registry.addUnitConverter(distanceTypeDef, mx.LinearUnitConverter.create(distanceTypeDef))\n", " angleTypeDef = doc.getUnitTypeDef('angle')\n", " if angleTypeDef:\n", " registry.addUnitConverter(angleTypeDef, mx.LinearUnitConverter.create(angleTypeDef))\n", " print('Created unit converter registry')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "As with a color management system the location of implementations and the registry need to be set on a unit system. \n", "The unit system can then be set on the generator." ] }, { "cell_type": "code", "execution_count": 206, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Set unit system on code generator\n" ] } ], "source": [ "# Create unit system, set where definitions come from, and\n", "# set up what registry to use\n", "unitsystem = mx_gen_shader.UnitSystem.create(shadergen.getTarget())\n", "unitsystem.loadLibrary(stdlib)\n", "unitsystem.setUnitConverterRegistry(registry)\n", "\n", "if unitsystem:\n", " print('Set unit system on code generator')\n", " shadergen.setUnitSystem(unitsystem)\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "This sets up how to perform unit conversions, but does not specify what unis the scene geometry is using.\n", "This can be specified as in an \"options\" structure found on the context.\n", "\n", "The API interface is: GenOptions." ] }, { "cell_type": "code", "execution_count": 207, "metadata": {}, "outputs": [], "source": [ "# Set the target scene unit to be `meter` on the context options\n", "genoptions = context.getOptions()\n", "genoptions.targetDistanceUnit = 'meter'" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Finding Elements to Render\n", "\n", "There are a few utilities which are included to find elements which are \"renderable\":\n", "\n", "1. The findRenderableElement() utility can be used in general to find these. \n", "2. Another possible utility is to find only material nodes using `getMaterialNodes()` or \n", "2. Shader nodes by looking for nodes of type `SURFACE_SHADER_TYPE_STRING` in a document.\n", "\n", "For this example, the first \"renderable\" found is used.\n", "\n" ] }, { "cell_type": "code", "execution_count": 208, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found: 1 renderables\n", "Found node to render: Marble_3D\n" ] } ], "source": [ "\n", "# Look for renderable nodes\n", "nodes = mx_gen_shader.findRenderableElements(doc, False)\n", "print('Found: %d renderables' % len(nodes))\n", "if not nodes:\n", " nodes = doc.getMaterialNodes()\n", " if not nodes:\n", " nodes = doc.getNodesOfType(mx.SURFACE_SHADER_TYPE_STRING)\n", "\n", "node = None \n", "if nodes:\n", " node = nodes[0]\n", " print('Found node to render: ', node.getName())\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Generating Code\n", "\n", "After all of this setup, code can now be generated.\n", "1. First a `createValidName()` utility is called to ensure that the shader name produced is valid. \n", "2. Then the generator's generate() interface is called with this name, the \"renderable\" element, and the generation context. Note that derived classes override `generate()` to perform custom generation.\n", "\n", "Upon success a new Shader instance is created. Note that this is a special interface used to keep track of an entire shader. This instance can be inspected to extract information required for rendering." ] }, { "cell_type": "code", "execution_count": 209, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Succeeded in generating code for shader \"Marble_3D\" code from node \"Marble_3D\"\n" ] } ], "source": [ "shader = None\n", "nodeName = node.getName() if node else ''\n", "if nodeName:\n", " shaderName = mx.createValidName(nodeName)\n", " try:\n", " genoptions = context.getOptions()\n", " genoptions.shaderInterfaceType = mx_gen_shader.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE\n", " shader = shadergen.generate(shaderName, node, context)\n", " except mx.Exception as err:\n", " print('Shader generation errors:', err)\n", "\n", "if shader:\n", " print('Succeeded in generating code for shader \"%s\" code from node \"%s\"' % (shaderName, nodeName)) \n", "else:\n", " print('Failed to generate code for shader \"%s\" code from node \"%s\"' % (shaderName, nodeName)) " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Generated Code\n", "\n", "For hardware languages like GLSL, vertex, and pixel shader code is generated. OSL and MDL only produce\n", "pixel shader code. To complete this example the pixel shader code is queried from the Shader and\n", "shown below.\n", "\n", "Code can be queried via the getSourceCode() interface with an argument indicating which code to return. The code returned can be directly compiled and used by a renderer.\n", "\n", "It is at this point in the `generateshader.py` script that validation is performed. (This will not be shown here.) " ] }, { "cell_type": "code", "execution_count": 210, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "
Pixel Shader For: \"Marble_3D\"\n", "\n", "```cpp\n", "mdl 1.8;\n", "\n", "import ::df::*;\n", "import ::base::*;\n", "import ::math::*;\n", "import ::state::*;\n", "import ::anno::*;\n", "import ::tex::*;\n", "using ::materialx::core import *;\n", "using ::materialx::sampling import *;\n", "using ::materialx::stdlib_1_8 import *;\n", "using ::materialx::pbrlib_1_8 import *;\n", "\n", "material NG_standard_surface_surfaceshader_100\n", "(\n", " float base = 0.800000,\n", " color base_color = color(1.000000, 1.000000, 1.000000),\n", " float diffuse_roughness = 0.000000,\n", " float metalness = 0.000000,\n", " float specular = 1.000000,\n", " color specular_color = color(1.000000, 1.000000, 1.000000),\n", " float specular_roughness = 0.200000,\n", " uniform float specular_IOR = 1.500000,\n", " float specular_anisotropy = 0.000000,\n", " float specular_rotation = 0.000000,\n", " float transmission = 0.000000,\n", " color transmission_color = color(1.000000, 1.000000, 1.000000),\n", " float transmission_depth = 0.000000,\n", " color transmission_scatter = color(0.000000, 0.000000, 0.000000),\n", " float transmission_scatter_anisotropy = 0.000000,\n", " float transmission_dispersion = 0.000000,\n", " float transmission_extra_roughness = 0.000000,\n", " float subsurface = 0.000000,\n", " color subsurface_color = color(1.000000, 1.000000, 1.000000),\n", " color subsurface_radius = color(1.000000, 1.000000, 1.000000),\n", " float subsurface_scale = 1.000000,\n", " float subsurface_anisotropy = 0.000000,\n", " float sheen = 0.000000,\n", " color sheen_color = color(1.000000, 1.000000, 1.000000),\n", " float sheen_roughness = 0.300000,\n", " float coat = 0.000000,\n", " color coat_color = color(1.000000, 1.000000, 1.000000),\n", " float coat_roughness = 0.100000,\n", " float coat_anisotropy = 0.000000,\n", " float coat_rotation = 0.000000,\n", " uniform float coat_IOR = 1.500000,\n", " float3 coat_normal = state::transform_normal(state::coordinate_internal, state::coordinate_world, state::normal()),\n", " float coat_affect_color = 0.000000,\n", " float coat_affect_roughness = 0.000000,\n", " float thin_film_thickness = 0.000000,\n", " float thin_film_IOR = 1.500000,\n", " float emission = 0.000000,\n", " color emission_color = color(1.000000, 1.000000, 1.000000),\n", " color opacity = color(1.000000, 1.000000, 1.000000),\n", " bool thin_walled = false,\n", " float3 normal = state::transform_normal(state::coordinate_internal, state::coordinate_world, state::normal()),\n", " float3 tangent = state::transform_vector(state::coordinate_internal, state::coordinate_world, state::texture_tangent_u(0))\n", ")\n", " = let\n", "{\n", " float2 coat_roughness_vector_out = materialx::pbrlib_1_8::mx_roughness_anisotropy(mxp_roughness:coat_roughness, mxp_anisotropy:coat_anisotropy);\n", " float coat_tangent_rotate_degree_out = coat_rotation * 360.000000;\n", " color metal_reflectivity_out = base_color * base;\n", " color metal_edgecolor_out = specular_color * specular;\n", " float coat_affect_roughness_multiply1_out = coat_affect_roughness * coat;\n", " float tangent_rotate_degree_out = specular_rotation * 360.000000;\n", " float transmission_roughness_add_out = specular_roughness + transmission_extra_roughness;\n", " color subsurface_color_nonnegative_out = math::max(subsurface_color, 0.000000);\n", " float coat_clamped_out = math::clamp(coat, 0.000000, 1.000000);\n", " float3 subsurface_radius_vector_out = float3(subsurface_radius);\n", " float subsurface_selector_out = float(thin_walled);\n", " color base_color_nonnegative_out = math::max(base_color, 0.000000);\n", " color coat_attenuation_out = math::lerp(color(1.000000, 1.000000, 1.000000), coat_color, coat);\n", " float one_minus_coat_ior_out = 1.000000 - coat_IOR;\n", " float one_plus_coat_ior_out = 1.000000 + coat_IOR;\n", " color emission_weight_out = emission_color * emission;\n", " color opacity_luminance_out = materialx::stdlib_1_8::mx_luminance_color3(opacity);\n", " float3 coat_tangent_rotate_out = materialx::stdlib_1_8::mx_rotate3d_vector3(mxp_in:tangent, mxp_amount:coat_tangent_rotate_degree_out, mxp_axis:coat_normal);\n", " materialx::pbrlib_1_8::mx_artistic_ior__result artistic_ior_result = materialx::pbrlib_1_8::mx_artistic_ior(mxp_reflectivity:metal_reflectivity_out, mxp_edge_color:metal_edgecolor_out);\n", " float coat_affect_roughness_multiply2_out = coat_affect_roughness_multiply1_out * coat_roughness;\n", " float3 tangent_rotate_out = materialx::stdlib_1_8::mx_rotate3d_vector3(mxp_in:tangent, mxp_amount:tangent_rotate_degree_out, mxp_axis:normal);\n", " float transmission_roughness_clamped_out = math::clamp(transmission_roughness_add_out, 0.000000, 1.000000);\n", " float coat_gamma_multiply_out = coat_clamped_out * coat_affect_color;\n", " float3 subsurface_radius_scaled_out = subsurface_radius_vector_out * subsurface_scale;\n", " float coat_ior_to_F0_sqrt_out = one_minus_coat_ior_out / one_plus_coat_ior_out;\n", " float swizzle2_out = materialx::stdlib_1_8::mx_extract_color3(opacity_luminance_out, 0);\n", " float3 coat_tangent_rotate_normalize_out = math::normalize(coat_tangent_rotate_out);\n", " float coat_affected_roughness_out = math::lerp(specular_roughness, 1.000000, coat_affect_roughness_multiply2_out);\n", " float3 tangent_rotate_normalize_out = math::normalize(tangent_rotate_out);\n", " float coat_affected_transmission_roughness_out = math::lerp(transmission_roughness_clamped_out, 1.000000, coat_affect_roughness_multiply2_out);\n", " float coat_gamma_out = coat_gamma_multiply_out + 1.000000;\n", " float coat_ior_to_F0_out = coat_ior_to_F0_sqrt_out * coat_ior_to_F0_sqrt_out;\n", " float3 coat_tangent_out = materialx::stdlib_1_8::mx_ifgreater_vector3(coat_anisotropy, 0.000000, coat_tangent_rotate_normalize_out, tangent);\n", " float2 main_roughness_out = materialx::pbrlib_1_8::mx_roughness_anisotropy(mxp_roughness:coat_affected_roughness_out, mxp_anisotropy:specular_anisotropy);\n", " float3 main_tangent_out = materialx::stdlib_1_8::mx_ifgreater_vector3(specular_anisotropy, 0.000000, tangent_rotate_normalize_out, tangent);\n", " float2 transmission_roughness_out = materialx::pbrlib_1_8::mx_roughness_anisotropy(mxp_roughness:coat_affected_transmission_roughness_out, mxp_anisotropy:specular_anisotropy);\n", " color coat_affected_subsurface_color_out = math::pow(subsurface_color_nonnegative_out, coat_gamma_out);\n", " color coat_affected_diffuse_color_out = math::pow(base_color_nonnegative_out, coat_gamma_out);\n", " float one_minus_coat_ior_to_F0_out = 1.000000 - coat_ior_to_F0_out;\n", " color swizzle_out = color(one_minus_coat_ior_to_F0_out,one_minus_coat_ior_to_F0_out,one_minus_coat_ior_to_F0_out);\n", " material metal_bsdf_out = materialx::pbrlib_1_8::mx_conductor_bsdf(mxp_weight:1.000000, mxp_ior:artistic_ior_result.mxp_ior, mxp_extinction:artistic_ior_result.mxp_extinction, mxp_roughness:main_roughness_out, mxp_thinfilm_thickness:thin_film_thickness, mxp_thinfilm_ior:thin_film_IOR, mxp_normal:normal, mxp_tangent:main_tangent_out, mxp_distribution:mx_distribution_type_ggx);\n", " material transmission_bsdf_out = materialx::pbrlib_1_8::mx_dielectric_bsdf(mxp_weight:1.000000, mxp_tint:transmission_color, mxp_ior:specular_IOR, mxp_roughness:transmission_roughness_out, mxp_thinfilm_thickness:0.000000, mxp_thinfilm_ior:1.500000, mxp_normal:normal, mxp_tangent:main_tangent_out, mxp_distribution:mx_distribution_type_ggx, mxp_scatter_mode:mx_scatter_mode_T, mxp_base:material());\n", " material translucent_bsdf_out = materialx::pbrlib_1_8::mx_translucent_bsdf(mxp_weight:1.000000, mxp_color:coat_affected_subsurface_color_out, mxp_normal:normal);\n", " material subsurface_bsdf_out = materialx::pbrlib_1_8::mx_subsurface_bsdf(mxp_weight:1.000000, mxp_color:coat_affected_subsurface_color_out, mxp_radius:subsurface_radius_scaled_out, mxp_anisotropy:subsurface_anisotropy, mxp_normal:normal);\n", " material selected_subsurface_bsdf_out = materialx::pbrlib_1_8::mx_mix_bsdf(mxp_fg:translucent_bsdf_out, mxp_bg:subsurface_bsdf_out, mxp_mix:subsurface_selector_out);\n", " material diffuse_bsdf_out = materialx::pbrlib_1_8::mx_oren_nayar_diffuse_bsdf(mxp_weight:base, mxp_color:coat_affected_diffuse_color_out, mxp_roughness:diffuse_roughness, mxp_normal:normal);\n", " material subsurface_mix_out = materialx::pbrlib_1_8::mx_mix_bsdf(mxp_fg:selected_subsurface_bsdf_out, mxp_bg:diffuse_bsdf_out, mxp_mix:subsurface);\n", " material sheen_layer_out = materialx::pbrlib_1_8::mx_sheen_bsdf(mxp_weight:sheen, mxp_color:sheen_color, mxp_roughness:sheen_roughness, mxp_normal:normal, mxp_base:subsurface_mix_out);\n", " material transmission_mix_out = materialx::pbrlib_1_8::mx_mix_bsdf(mxp_fg:transmission_bsdf_out, mxp_bg:sheen_layer_out, mxp_mix:transmission);\n", " material specular_layer_out = materialx::pbrlib_1_8::mx_dielectric_bsdf(mxp_weight:specular, mxp_tint:specular_color, mxp_ior:specular_IOR, mxp_roughness:main_roughness_out, mxp_thinfilm_thickness:thin_film_thickness, mxp_thinfilm_ior:thin_film_IOR, mxp_normal:normal, mxp_tangent:main_tangent_out, mxp_distribution:mx_distribution_type_ggx, mxp_scatter_mode:mx_scatter_mode_R, mxp_base:transmission_mix_out);\n", " material metalness_mix_out = materialx::pbrlib_1_8::mx_mix_bsdf(mxp_fg:metal_bsdf_out, mxp_bg:specular_layer_out, mxp_mix:metalness);\n", " material thin_film_layer_attenuated_out = materialx::pbrlib_1_8::mx_multiply_bsdf_color3(mxp_in1:metalness_mix_out, mxp_in2:coat_attenuation_out);\n", " material coat_layer_out = materialx::pbrlib_1_8::mx_dielectric_bsdf(mxp_weight:coat, mxp_tint:color(1.000000, 1.000000, 1.000000), mxp_ior:coat_IOR, mxp_roughness:coat_roughness_vector_out, mxp_thinfilm_thickness:0.000000, mxp_thinfilm_ior:1.500000, mxp_normal:coat_normal, mxp_tangent:coat_tangent_out, mxp_distribution:mx_distribution_type_ggx, mxp_scatter_mode:mx_scatter_mode_R, mxp_base:thin_film_layer_attenuated_out);\n", " material emission_edf_out = materialx::pbrlib_1_8::mx_uniform_edf(mxp_color:emission_weight_out);\n", " material coat_tinted_emission_edf_out = materialx::pbrlib_1_8::mx_multiply_edf_color3(mxp_in1:emission_edf_out, mxp_in2:coat_color);\n", " material coat_emission_edf_out = materialx::pbrlib_1_8::mx_generalized_schlick_edf(mxp_color0:swizzle_out, mxp_color90:color(0.000000, 0.000000, 0.000000), mxp_exponent:5.000000, mxp_base:coat_tinted_emission_edf_out);\n", " material blended_coat_emission_edf_out = materialx::pbrlib_1_8::mx_mix_edf(mxp_fg:coat_emission_edf_out, mxp_bg:emission_edf_out, mxp_mix:coat);\n", " material shader_constructor_out = materialx::pbrlib_1_8::mx_surface(coat_layer_out, blended_coat_emission_edf_out, swizzle2_out, false, specular_IOR);\n", "}\n", "in material(shader_constructor_out);\n", "\n", "export material Marble_3D\n", "(\n", " material backsurfaceshader = material()\n", " [[\n", " \tmaterialx::core::origin(\"\")\n", " ]],\n", " material displacementshader = material()\n", " [[\n", " \tmaterialx::core::origin(\"\")\n", " ]],\n", " uniform mx_coordinatespace_type geomprop_Nworld_space = mx_coordinatespace_type_world\n", " [[\n", " \tmaterialx::core::origin(\"Nworld\")\n", " ]],\n", " uniform mx_coordinatespace_type geomprop_Tworld_space = mx_coordinatespace_type_world\n", " [[\n", " \tmaterialx::core::origin(\"Tworld\")\n", " ]],\n", " uniform int geomprop_Tworld_index = 0\n", " [[\n", " \tmaterialx::core::origin(\"Tworld\")\n", " ]],\n", " uniform mx_coordinatespace_type obj_pos_space = mx_coordinatespace_type_object\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/obj_pos/space\")\n", " ]],\n", " float3 add_xyz_in2 = float3(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/add_xyz/in2\")\n", " ]],\n", " float scale_pos_in2 = 4.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise_scale_2\")\n", " ]],\n", " float scale_xyz_in2 = 6.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise_scale_1\")\n", " ]],\n", " float noise_amplitude = 1.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise/amplitude\")\n", " ]],\n", " int noise_octaves = 3\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise_octaves\")\n", " ]],\n", " float noise_lacunarity = 2.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise/lacunarity\")\n", " ]],\n", " float noise_diminish = 0.500000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise/diminish\")\n", " ]],\n", " float scale_noise_in2 = 3.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/scale_noise/in2\")\n", " ]],\n", " float scale_in2 = 0.500000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/scale/in2\")\n", " ]],\n", " float bias_in2 = 0.500000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/bias/in2\")\n", " ]],\n", " float power_in2 = 3.000000\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/noise_power\")\n", " ]],\n", " color color_mix_fg = color(0.100000, 0.100000, 0.300000)\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/base_color_2\")\n", " ]],\n", " color color_mix_bg = color(0.800000, 0.800000, 0.800000)\n", " [[\n", " \tmaterialx::core::origin(\"NG_marble1/base_color_1\")\n", " ]],\n", " float SR_marble1_base = 1.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/base\")\n", " ]],\n", " float SR_marble1_diffuse_roughness = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/diffuse_roughness\")\n", " ]],\n", " float SR_marble1_metalness = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/metalness\")\n", " ]],\n", " float SR_marble1_specular = 1.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular\")\n", " ]],\n", " color SR_marble1_specular_color = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular_color\")\n", " ]],\n", " float SR_marble1_specular_roughness = 0.100000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular_roughness\")\n", " ]],\n", " uniform float SR_marble1_specular_IOR = 1.500000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular_IOR\")\n", " ]],\n", " float SR_marble1_specular_anisotropy = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular_anisotropy\")\n", " ]],\n", " float SR_marble1_specular_rotation = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/specular_rotation\")\n", " ]],\n", " float SR_marble1_transmission = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission\")\n", " ]],\n", " color SR_marble1_transmission_color = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_color\")\n", " ]],\n", " float SR_marble1_transmission_depth = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_depth\")\n", " ]],\n", " color SR_marble1_transmission_scatter = color(0.000000, 0.000000, 0.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_scatter\")\n", " ]],\n", " float SR_marble1_transmission_scatter_anisotropy = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_scatter_anisotropy\")\n", " ]],\n", " float SR_marble1_transmission_dispersion = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_dispersion\")\n", " ]],\n", " float SR_marble1_transmission_extra_roughness = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/transmission_extra_roughness\")\n", " ]],\n", " float SR_marble1_subsurface = 0.400000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/subsurface\")\n", " ]],\n", " color SR_marble1_subsurface_radius = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/subsurface_radius\")\n", " ]],\n", " float SR_marble1_subsurface_scale = 1.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/subsurface_scale\")\n", " ]],\n", " float SR_marble1_subsurface_anisotropy = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/subsurface_anisotropy\")\n", " ]],\n", " float SR_marble1_sheen = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/sheen\")\n", " ]],\n", " color SR_marble1_sheen_color = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/sheen_color\")\n", " ]],\n", " float SR_marble1_sheen_roughness = 0.300000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/sheen_roughness\")\n", " ]],\n", " float SR_marble1_coat = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat\")\n", " ]],\n", " color SR_marble1_coat_color = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_color\")\n", " ]],\n", " float SR_marble1_coat_roughness = 0.100000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_roughness\")\n", " ]],\n", " float SR_marble1_coat_anisotropy = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_anisotropy\")\n", " ]],\n", " float SR_marble1_coat_rotation = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_rotation\")\n", " ]],\n", " uniform float SR_marble1_coat_IOR = 1.500000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_IOR\")\n", " ]],\n", " float SR_marble1_coat_affect_color = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_affect_color\")\n", " ]],\n", " float SR_marble1_coat_affect_roughness = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/coat_affect_roughness\")\n", " ]],\n", " float SR_marble1_thin_film_thickness = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/thin_film_thickness\")\n", " ]],\n", " float SR_marble1_thin_film_IOR = 1.500000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/thin_film_IOR\")\n", " ]],\n", " float SR_marble1_emission = 0.000000\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/emission\")\n", " ]],\n", " color SR_marble1_emission_color = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/emission_color\")\n", " ]],\n", " color SR_marble1_opacity = color(1.000000, 1.000000, 1.000000)\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/opacity\")\n", " ]],\n", " bool SR_marble1_thin_walled = false\n", " [[\n", " \tmaterialx::core::origin(\"SR_marble1/thin_walled\")\n", " ]]\n", ")\n", "= let\n", "{\n", " float3 geomprop_Nworld_out1 = materialx::stdlib_1_8::mx_normal_vector3(mxp_space:geomprop_Nworld_space);\n", " float3 geomprop_Tworld_out1 = materialx::stdlib_1_8::mx_tangent_vector3(mxp_space:geomprop_Tworld_space, mxp_index:geomprop_Tworld_index);\n", " float3 obj_pos_out = materialx::stdlib_1_8::mx_position_vector3(mxp_space:obj_pos_space);\n", " float add_xyz_out = math::dot(obj_pos_out, add_xyz_in2);\n", " float3 scale_pos_out = obj_pos_out * scale_pos_in2;\n", " float scale_xyz_out = add_xyz_out * scale_xyz_in2;\n", " float noise_out = materialx::stdlib_1_8::mx_fractal3d_float(mxp_amplitude:noise_amplitude, mxp_octaves:noise_octaves, mxp_lacunarity:noise_lacunarity, mxp_diminish:noise_diminish, mxp_position:scale_pos_out);\n", " float scale_noise_out = noise_out * scale_noise_in2;\n", " float sum_out = scale_xyz_out + scale_noise_out;\n", " float sin_out = math::sin(sum_out);\n", " float scale_out = sin_out * scale_in2;\n", " float bias_out = scale_out + bias_in2;\n", " float power_out = math::pow(bias_out, power_in2);\n", " color color_mix_out = math::lerp(color_mix_bg, color_mix_fg, power_out);\n", " material SR_marble1_out = NG_standard_surface_surfaceshader_100(SR_marble1_base, color_mix_out, SR_marble1_diffuse_roughness, SR_marble1_metalness, SR_marble1_specular, SR_marble1_specular_color, SR_marble1_specular_roughness, SR_marble1_specular_IOR, SR_marble1_specular_anisotropy, SR_marble1_specular_rotation, SR_marble1_transmission, SR_marble1_transmission_color, SR_marble1_transmission_depth, SR_marble1_transmission_scatter, SR_marble1_transmission_scatter_anisotropy, SR_marble1_transmission_dispersion, SR_marble1_transmission_extra_roughness, SR_marble1_subsurface, color_mix_out, SR_marble1_subsurface_radius, SR_marble1_subsurface_scale, SR_marble1_subsurface_anisotropy, SR_marble1_sheen, SR_marble1_sheen_color, SR_marble1_sheen_roughness, SR_marble1_coat, SR_marble1_coat_color, SR_marble1_coat_roughness, SR_marble1_coat_anisotropy, SR_marble1_coat_rotation, SR_marble1_coat_IOR, geomprop_Nworld_out1, SR_marble1_coat_affect_color, SR_marble1_coat_affect_roughness, SR_marble1_thin_film_thickness, SR_marble1_thin_film_IOR, SR_marble1_emission, SR_marble1_emission_color, SR_marble1_opacity, SR_marble1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1);\n", " material Marble_3D_out = materialx::stdlib_1_8::mx_surfacematerial(mxp_surfaceshader: SR_marble1_out, mxp_backsurfaceshader: backsurfaceshader, mxp_displacementshader: displacementshader);\n", " material finalOutput__ = Marble_3D_out;\n", "}\n", "in material(finalOutput__);\n", "```\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pixelSource = ''\n", "vertexSource = ''\n", "if shader:\n", " errors = ''\n", "\n", " # Use extension of .vert and .frag as it's type is\n", " # recognized by glslangValidator\n", " if language in ['glsl', 'essl', 'vulkan']:\n", " vertexSource = shader.getSourceCode(mx_gen_shader.VERTEX_STAGE)\n", " text = '
Vertex Shader For: \"' + nodeName + '\"\\n\\n' + '```cpp\\n' + vertexSource + '```\\n' + '
\\n' \n", " display_markdown(text , raw=True)\n", "\n", " pixelSource = shader.getSourceCode(mx_gen_shader.PIXEL_STAGE)\n", " text = '
Pixel Shader For: \"' + nodeName + '\"\\n\\n' + '```cpp\\n' + pixelSource + '```\\n' + '
\\n' \n", " display_markdown(text , raw=True)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Shader Refection\n", "\n", "It is often required to be able to get information about the shader uniforms / arguments. These uniforms are organized into \"blocks\" such that each block's uniforms has a corresponding `ShaderPort` which can be inspected. The general term for provide additional information is **shader refection**. \n", "\n", "The following utility function is used to extract information about the shader uniforms. Integrations can customize to create their own reflection structures." ] }, { "cell_type": "code", "execution_count": 211, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Get Stage: pixel\n" ] } ], "source": [ "def getPortPath(inputPath, doc):\n", " '''\n", " Find any upstream interface input which maps to a given path.\n", " Note: This is only required pre version 1.38.9 where interface inputs traversed when\n", " reflecting the MaterialX path on the shader port.\n", " '''\n", " if not inputPath:\n", " return inputPath, None\n", " \n", " input = doc.getDescendant(inputPath)\n", " if input:\n", " # Redirect to interface input if it exists.\n", " interfaceInput = input.getInterfaceInput()\n", " if interfaceInput:\n", " input = interfaceInput\n", " return input.getNamePath(), interfaceInput\n", "\n", " return inputPath, None\n", "\n", "def reflectStage(shader, doc, filterStage='pixel', filterBlock='Public'):\n", " '''\n", " Scan through each stage of a shader and get the uniform blocks for each stage.\n", " For each block, extract out some desired information\n", " '''\n", " reflectionStages = []\n", "\n", " if not shader:\n", " return\n", "\n", " for i in range(0, shader.numStages()):\n", " stage = shader.getStage(i)\n", " if stage:\n", " print('Get Stage:', stage.getName())\n", " if filterStage and filterStage not in stage.getName():\n", " continue\n", "\n", " stageName = stage.getName() \n", " if len(stageName) == 0:\n", " continue\n", "\n", " reflectionStage = dict()\n", "\n", " theBlocks = stage.getUniformBlocks()\n", " if len(theBlocks) == 0:\n", " print(f'stage: {stageName} has no uniform blocks') \n", "\n", " for blockName in stage.getUniformBlocks():\n", " block = stage.getUniformBlock(blockName)\n", " print('Scan block: ', blockName, block)\n", " if filterBlock and filterBlock not in block.getName():\n", " #print('--------- skip block: ', block.getName())\n", " continue \n", "\n", " if not block.getName() in reflectionStage:\n", " reflectionStage[block.getName()] = [] \n", " reflectionBlock = reflectionStage[block.getName()]\n", "\n", " for shaderPort in block:\n", " variable = shaderPort.getVariable()\n", " value = shaderPort.getValue().getValueString() if shaderPort.getValue() else ''\n", " origPath = shaderPort.getPath()\n", " path, interfaceInput = getPortPath(shaderPort.getPath(), doc) \n", " if not path:\n", " path = ''\n", " else:\n", " if path != origPath:\n", " path = origPath + ' --> ' + path\n", " type = shaderPort.getType().getName()\n", "\n", " unit = shaderPort.getUnit()\n", " colorspace = ''\n", " if interfaceInput:\n", " colorspace = interfaceInput.getColorSpace()\n", " else:\n", " colorspace = shaderPort.getColorSpace() \n", "\n", " #print('add uniform: ', variable, value, type, path, unit, colorspace)\n", " portEntry = [ variable, value, type, path, unit, colorspace ]\n", "\n", " #print('add port to block: ', portEntry)\n", " reflectionBlock.append(portEntry)\n", "\n", " if len(reflectionBlock) > 0:\n", " reflectionStage[block.getName()] = reflectionBlock \n", " \n", " if len(reflectionStage) > 0:\n", " reflectionStages.append((stageName, reflectionStage)) \n", "\n", " return reflectionStages\n", "\n", "if shader:\n", " # Examine public uniforms first\n", " stages = reflectStage(shader, doc, 'pixel', 'Public')\n", " if stages:\n", " for stage in stages:\n", " for block in stage[1]:\n", " log = '

Stage \"%s\". Block: \"%s\"

\\n\\n' % (stage[0], block)\n", " log += '| Variable | Value | Type | Path | Unit | Colorspace |\\n'\n", " log += '| --- | --- | --- | --- | --- | --- |\\n'\n", " for entry in stage[1][block]:\n", " log += '| %s | %s | %s | %s | %s | %s |\\n' % (entry[0], entry[1], entry[2], entry[3], entry[4], entry[5])\n", " log += '\\n'\n", "\n", " display_markdown(log, raw=True)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Downstream Renderables\n", "\n", "`getDownstreamPorts()` can be used to traverse downstream from a given `Node`. With the current release this does not work with `NodeGraphs` so custom logic is used instead which calls into `getMatchingPorts()` on a document. " ] }, { "cell_type": "code", "execution_count": 212, "metadata": {}, "outputs": [], "source": [ "def getDownstreamPorts(nodeName):\n", " downstreamPorts = []\n", " for port in doc.getMatchingPorts(nodeName):\n", " #print('- check port:', port)\n", " #print('- Compare: ', port.getConnectedNode().getName(), ' vs ', nodeName)\n", " #if port.getConnectedNode().getName() == nodeName:\n", " downstreamPorts.append(port)\n", " return downstreamPorts" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "`getMatchingPorts()` should return all ports in the document which reference a given node. Again there is an issue with the current release that this will not find any matches when a `nodegraph` is referenced by a port. **For this example a custom build was used which addresses this issue.**\n", "\n", "A wrapper utility called `getDownStreamNodes()` is written to perform downstream traversal starting from a node. It returns ports and corresponding nodes as well as what is considered to be \"renderable\". This is akin logic found in `findRenderableElements()` but instead will only look at nodes connected downstream from a node. \n", "\"Renderable\" is considered to be \n", "- Unconnected `output` ports\n", "- Shaders (`surfaceshader` and `volumeshader` nodes) and \n", "- Materials (`material` node)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": 213, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "Common downstream:" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ " - Common downstream ports: [ NG_marble1/add_xyz/in1, NG_marble1/scale_pos/in1, NG_marble1/scale_xyz/in1, NG_marble1/noise/position, NG_marble1/sum/in1, NG_marble1/scale_noise/in1, NG_marble1/sin/in, NG_marble1/sum/in2, NG_marble1/scale/in1, NG_marble1/bias/in1, NG_marble1/power/in1, NG_marble1/color_mix/mix, NG_marble1/out, SR_marble1/base_color, Marble_3D/surfaceshader ]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ " - Common downstream nodes: [ NG_marble1/add_xyz, NG_marble1/scale_pos, NG_marble1/scale_xyz, NG_marble1/noise, NG_marble1/sum, NG_marble1/scale_noise, NG_marble1/sin, NG_marble1/scale, NG_marble1/bias, NG_marble1/power, NG_marble1/color_mix, NG_marble1, SR_marble1, Marble_3D ]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ " - Common renderable elements: [ NG_marble1/out, SR_marble1/base_color, Marble_3D/surfaceshader ]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from collections import OrderedDict\n", "\n", "def getDownstreamNodes(node, foundPorts, foundNodes, renderableElements, \n", " renderableTypes = ['material', 'surfaceshader', 'volumeshader']):\n", " \"\"\"\n", " For a given \"node\", traverse downstream connections until there are none to be found.\n", " Along the way collect a list of ports and corresponding nodes visited (in order), and\n", " a list of \"renderable\" elements. \n", " \"\"\"\n", " testPaths = set()\n", " testPaths.add(node.getNamePath())\n", "\n", " while testPaths:\n", " nextPaths = set()\n", " for path in testPaths:\n", " testNode = doc.getDescendant(path)\n", " #print('test node:', testNode.getName())\n", " ports = []\n", " if testNode.isA(mx.Node):\n", " ports = testNode.getDownstreamPorts()\n", " else:\n", " ports = getDownstreamPorts(testNode.getName())\n", " for port in ports:\n", " downNode = port.getParent()\n", " downNodePath = downNode.getNamePath()\n", " if downNode and downNodePath not in nextPaths: #and downNode.isA(mx.Node):\n", " foundPorts.append(port.getNamePath())\n", " if port.isA(mx.Output):\n", " renderableElements.append(port.getNamePath())\n", " nodedef = downNode.getNodeDef()\n", " if nodedef:\n", " nodetype = nodedef.getType()\n", " if nodetype in renderableTypes:\n", " renderableElements.append(port.getNamePath())\n", " foundNodes.append(downNode.getNamePath())\n", " nextPaths.add(downNodePath)\n", "\n", " testPaths = nextPaths \n", "\n", "def examineNodes(nodes):\n", " \"\"\"\n", " Traverse downstream for a set of nodes to find information\n", " Returns the set of common ports, nodes, and renderables found \n", " \"\"\"\n", " commonPorts = []\n", " commonNodes = []\n", " commonRenderables = []\n", " for node in nodes:\n", " foundPorts = []\n", " foundNodes = []\n", " renderableElements = []\n", " getDownstreamNodes(node, foundPorts, foundNodes, renderableElements)\n", "\n", " foundPorts = list(OrderedDict.fromkeys(foundPorts))\n", " foundNodes = list(OrderedDict.fromkeys(foundNodes))\n", " renderableElements = list(OrderedDict.fromkeys(renderableElements))\n", " #print('Traverse downstream from node: ', node.getNamePath())\n", " #print('- Downstream ports:', ', '.join(foundPorts))\n", " #print('- Downstream nodes:', ', '.join(foundNodes))\n", " #print('- Renderable elements:', ', '.join(renderableElements))\n", " commonPorts.extend(foundPorts)\n", " commonNodes.extend(foundNodes)\n", " commonRenderables.extend(renderableElements)\n", "\n", " commonPorts = list(OrderedDict.fromkeys(commonPorts))\n", " commonNodes = list(OrderedDict.fromkeys(commonNodes))\n", " commonRenderables = list(OrderedDict.fromkeys(commonRenderables))\n", "\n", " return commonPorts, commonNodes, commonRenderables\n", "\n", "\n", "nodegraph = doc.getChild('NG_marble1')\n", "nodes = [nodegraph.getChild('obj_pos'), nodegraph.getChild('scale_pos')]\n", "\n", "commonPorts, commonNodes, commonRenderables = examineNodes(nodes)\n", "display_markdown('Common downstream:', raw=True)\n", "display_markdown(' - Common downstream ports: [ ' + ', '.join(commonPorts) + ' ]', raw=True)\n", "display_markdown(' - Common downstream nodes: [ ' + ', '.join(commonNodes) + ' ]', raw=True)\n", "display_markdown(' - Common renderable elements: [ ' + ', '.join(commonRenderables) + ' ]', raw=True)\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "*The Marble graph is shown below for reference:*\n", "\n", "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Sampling Graph Nodes\n", "\n", "Given the ability to traverse downstream from a node, it is possible to produce code just a given node or the upstream subgraph rooted at a given node.\n", "If a non-surface shader or material node is used to generate from then only that nodes could will be considered.\n", "\n", "To generate code for the entire upstream graph the node needs to have a downstream root which is either a shader or a material.\n", "As of version 1.38.7, the easiest way to do this is to create a temporary `convert` node to route the output type to a downstream surface shader.\n", "For example a `convert` from color to `surfaceshader` can be used " ] }, { "cell_type": "code", "execution_count": 214, "metadata": {}, "outputs": [ { "ename": "IndentationError", "evalue": "expected an indented block after function definition on line 247 (mxnodegraph.py, line 249)", "output_type": "error", "traceback": [ "Traceback \u001b[1;36m(most recent call last)\u001b[0m:\n", "\u001b[0m File \u001b[0;32mc:\\Users\\home\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3577\u001b[0m in \u001b[0;35mrun_code\u001b[0m\n exec(code_obj, self.user_global_ns, self.user_ns)\u001b[0m\n", "\u001b[1;36m Cell \u001b[1;32mIn[214], line 1\u001b[1;36m\n\u001b[1;33m import mtlxutils.mxnodegraph as mxg\u001b[1;36m\n", "\u001b[1;36m File \u001b[1;32md:\\Work\\materialx\\MaterialX_Learn_Private\\pymaterialx\\mtlxutils\\mxnodegraph.py:249\u001b[1;36m\u001b[0m\n\u001b[1;33m connectedInputs = []\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mIndentationError\u001b[0m\u001b[1;31m:\u001b[0m expected an indented block after function definition on line 247\n" ] } ], "source": [ "import mtlxutils.mxnodegraph as mxg\n", "\n", "for nodePath in commonNodes:\n", " node = doc.getDescendant(nodePath)\n", " if not node:\n", " continue\n", "\n", " if node.isA(mx.NodeGraph):\n", " outputs = node.getOutputs()\n", " for output in outputs:\n", " outputPath = output.getNamePath()\n", " if outputPath in commonRenderables:\n", " node = output\n", " nodePath = outputPath\n", " else:\n", " convertNode = None\n", " #parent = node.getParent()\n", " #convertNode = mxg.MtlxNodeGraph.addNode(parent, 'ND_convert_' + node.getType() + '_surfaceshader', 'convert_' + node.getName())\n", " #if convertNode:\n", " # mxg.MtlxNodeGraph.connectNodeToNode(convertNode, 'in', node, '')\n", "\n", " shaderName = mx.createValidName(nodePath)\n", " try:\n", " shader = shadergen.generate(shaderName, node, context)\n", " except mx.Exception as err:\n", " print('Shader generation errors:', err)\n", "\n", " if shader:\n", " pixelSource = shader.getSourceCode(mx_gen_shader.PIXEL_STAGE)\n", " text = '
Code For: \"' + nodePath + '\"\\n\\n' + '```cpp\\n' + pixelSource + '```\\n' + '
\\n' \n", " display_markdown(text , raw=True)\n", " else:\n", " print('Failed to generate code for shader \"%s\" code from node \"%s\"' % (shaderName, nodeName)) \n", "\n", "\n", " if convertNode:\n", " nodePath = convertNode.getNamePath()\n", " shaderName = mx.createValidName(nodePath)\n", " try:\n", " shader = shadergen.generate(shaderName, convertNode, context)\n", " except mx.Exception as err:\n", " print('Shader generation errors:', err)\n", "\n", " if shader:\n", " pixelSource = shader.getSourceCode(mx_gen_shader.PIXEL_STAGE)\n", " text = '
Convert Code For: \"' + nodePath + '\"\\n\\n' + '```cpp\\n' + pixelSource + '```\\n' + '
\\n' \n", " display_markdown(text , raw=True)\n", " else:\n", " print('Failed to generate code for shader \"%s\" code from node \"%s\"' % (shaderName, nodeName)) \n", " " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Building A Custom Traversal Cache\n", "\n", "* Build a cache with node/nodegraph names -> list of ports referencing them\n", "* Only interested in port mappings for non-implementations so cache can be much smaller than when considering every\n", "port element in the standard data library ! " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Node: NG_marble1/obj_pos\n", " used by: NG_marble1/add_xyz/in1 \n", " used by: NG_marble1/scale_pos/in1 \n", "Node: NG_marble1/add_xyz\n", " used by: NG_marble1/scale_xyz/in1 \n", "Node: NG_marble1/scale_pos\n", " used by: NG_marble1/noise/position \n", "Node: NG_marble1/noise\n", " used by: NG_marble1/scale_noise/in1 \n", "Node: NG_marble1/scale_xyz\n", " used by: NG_marble1/sum/in1 \n", "Node: NG_marble1/scale_noise\n", " used by: NG_marble1/sum/in2 \n", "Node: NG_marble1/sum\n", " used by: NG_marble1/sin/in \n", "Node: NG_marble1/sin\n", " used by: NG_marble1/scale/in1 \n", "Node: NG_marble1/scale\n", " used by: NG_marble1/bias/in1 \n", "Node: NG_marble1/bias\n", " used by: NG_marble1/power/in1 \n", "Node: NG_marble1/power\n", " used by: NG_marble1/color_mix/mix \n", "Node: NG_marble1/color_mix\n", " used by: NG_marble1/out \n", "Node: /NG_marble1\n", " used by: SR_marble1/base_color out:out\n", " used by: SR_marble1/subsurface_color out:out\n", "Node: /SR_marble1\n", " used by: Marble_3D/surfaceshader \n" ] } ], "source": [ "# getAncestorOfType not in Python API.\n", "\n", "def elementInDefinition(elem):\n", " parent = elem.getParent()\n", " while parent:\n", " if parent.isA(mx.NodeGraph):\n", " if parent.getNodeDef(): \n", " #print('Skip elem: ', elem.getNamePath())\n", " return True\n", " return False\n", " else:\n", " parent = parent.getParent()\n", " return False\n", "\n", "\n", "def getParentGraph(elem):\n", " parent = elem.getParent()\n", " while parent:\n", " if parent.isA(mx.NodeGraph):\n", " return parent\n", " else:\n", " parent = parent.getParent()\n", " return None\n", "\n", "portElementMap = dict()\n", "\n", "for elem in doc.traverseTree():\n", " if not elem.isA(mx.PortElement):\n", " continue\n", "\n", " graph = getParentGraph(elem)\n", " graphName = ''\n", " if graph:\n", " if graph.getNodeDef():\n", " continue\n", " graphName = graph.getNamePath()\n", "\n", " nd = elem.getAttribute('nodename')\n", " if nd:\n", " qn = graphName + '/' + elem.getQualifiedName(nd)\n", " if qn not in portElementMap:\n", " portElementMap[qn] = [ elem ]\n", " else:\n", " portElementMap[qn].append( elem )\n", "\n", " ng = elem.getAttribute(\"nodegraph\")\n", " if ng:\n", " qng = graphName + '/' + elem.getQualifiedName(ng)\n", " if qng not in portElementMap:\n", " portElementMap[qng] = [ elem ]\n", " else:\n", " portElementMap[qng].append( elem )\n", "\n", "for k in portElementMap:\n", " print('Node:', k)\n", " for p in portElementMap[k]:\n", " print(' used by:', p.getNamePath(), 'out:' + p.getAttribute('output') if p.getAttribute('output') else '')" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "b729d0e20edc59430665dadd095c679f1a6a6ae416a8655f956120c3270c9bf6" } } }, "nbformat": 4, "nbformat_minor": 2 }