demos | Substance 3D Automation ToolKit

Note

Documentation for Substance Automation Toolkit is now included in the SAT package. You can access the documentation by opening html-doc.zip inside your downloaded SAT package.

demos

Module demos provides samples of usage of Pysbs.

demos.demoBakingParameters(aContext, aFileAbsPath='', aDestFileAbsPath='')

Demonstrates the creation and edition of the baking parameters

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoBakingParameters:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Parse the .sbs file and provide the object structure of the entire substance 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    if sbsDoc.parseDoc(): 
        # Add a new Scene resource to the document 
        aRelPath = sbsDoc.buildAbsPathFromRelToMePath(aRelPathFromPackage='./Models/m41_low.fbx') 
        aNewResource = sbsDoc.createLinkedResource(aIdentifier='LowResMesh', 
                                                   aResourcePath=aRelPath, 
                                                   aResourceTypeEnum=sbsenum.ResourceTypeEnum.SCENE) 
 
        # Create BakingParameters for this resource 
        aBakingParams = aNewResource.createBakingParameters() 
 
        # Add a high poly mesh from a file path 
        aHighPolyFilePath = sbsDoc.buildAbsPathFromRelToMePath('./Models/m41_high.fbx') 
        aBakingParams.addHighDefinitionMeshFromFile(aHighPolyFilePath) 
 
        # Set default Antialiasing value 
        aBakingParams.setParameterValue(sbsbakers.ConverterParamEnum.DEFAULT__SUB_SAMPLING, sbsbakers.BakerFromMeshSubSamplingEnum.SUBSAMPLING_4x4) 
 
        # Add a Bent Normal From Mesh baker 
        BN_baker = aBakingParams.addBaker(sbsbakers.BakerEnum.BENT_NORMALS_FROM_MESH) 
 
        # Add a Normal Map From Mesh baker 
        NM_baker = aBakingParams.addBaker(sbsbakers.BakerEnum.NORMAL_MAP_FROM_MESH) 
 
        # Set Antialiasing value specifically to NormalMap baker 
        NM_baker.setParameterValue(aParameter=sbsbakers.ConverterParamEnum.DETAIL__SUB_SAMPLING, 
                                   aParamValue=sbsbakers.BakerFromMeshSubSamplingEnum.SUBSAMPLING_2x2) 
 
        # Add an Ambient Occlusion baker which uses the resulting map of the Normal Map From Mesh baker 
        AO_baker = aBakingParams.addBaker(sbsbakers.BakerEnum.AMBIENT_OCCLUSION) 
        AO_baker.setFileParameterValueFromPreviousBaker(aParameter=sbsbakers.ConverterParamEnum.ADDITIONAL__NORMAL_MAP, 
                                                        aPreviousBaker=NM_baker) 
 
        # Set back the baking parameters into the options of the second resource 
        aNewResource.setBakingParameters(aBakingParams) 
 
        # Write back the document structure into the destination .sbs file 
        sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
        log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
        return True 
 
except BaseException as error: 
    log.error("!!! [demoBakingParameters] Failed to edit the baking parameters") 
    raise error
demos.demoBuildSBSFromPainterBitmaps(aContext, aDestFileAbsPath='')

Create a graph with as many Bitmap & Output node as picture files in the Painter folder

Parameters:
  • aContext (context.Context) – Execution context
  • aDestFileAbsPath (str) – The absolute path of the resulting SBS file
Returns:

Nothing

Here is the code of function demoBuildSBSFromPainterBitmaps:

if aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aDestFileAbsPath") 
    return False 
 
xOffset  = [150, 0, 0] 
yOffset  = [0, 150, 0] 
initPos  = [0,0, 0] 
 
try: 
    # Create a new SBSDocument from scratch 
    sbsDoc = sbsgenerator.createSBSDocument(aContext, 
                            aFileAbsPath = aDestFileAbsPath, 
                            aGraphIdentifier = 'PainterFilter', 
                            aParameters  = {sbsenum.CompNodeParamEnum.OUTPUT_SIZE:[sbsenum.OutputSizeEnum.SIZE_1024,sbsenum.OutputSizeEnum.SIZE_1024], 
                                            sbsenum.CompNodeParamEnum.OUTPUT_FORMAT:sbsenum.OutputFormatEnum.FORMAT_16BITS}, 
                            aInheritance = {sbsenum.CompNodeParamEnum.OUTPUT_SIZE:sbsenum.ParamInheritanceEnum.ABSOLUTE, 
                                            sbsenum.CompNodeParamEnum.OUTPUT_FORMAT:sbsenum.ParamInheritanceEnum.ABSOLUTE}) 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier = 'PainterFilter') 
 
    # Retrieve bitmaps 
    aPathOutputs = sbsDoc.mDirAbsPath + os.path.sep 
    aListOutputs = glob.glob(aPathOutputs+"*.png") 
    log.info(len(aListOutputs)) 
 
    for x in range(0,len(aListOutputs)): 
 
        log.info(aListOutputs[x]) 
        # Create a Bitmap node 
        aBitmapNode   = aGraph.createBitmapNode(aSBSDocument = sbsDoc, 
                            aResourcePath  = aListOutputs[x], 
                            aGUIPos        = [y * x for y in yOffset], 
                            aParameters    = {sbsenum.CompNodeParamEnum.COLOR_MODE:True}, 
                            aCookedFormat  = sbsenum.BitmapFormatEnum.JPG, 
                            aCookedQuality = 1) 
 
        aId = aListOutputs[x][aListOutputs[x].rfind(os.path.sep)+1:-4] 
 
        # Create a Output color node 
        aOutputNode = aGraph.createOutputNode(aIdentifier = aId, 
                            aGUIPos        = map(sum, zip([y * x for y in yOffset], xOffset)), 
                            aOutputFormat  = sbsenum.TextureFormatEnum.DEFAULT_FORMAT, 
                            aAttributes    = {sbsenum.AttributesEnum.Description: 'Exported from painter: %s' % aId}, 
                            aMipmaps       = sbsenum.MipmapEnum.LEVELS_12, 
                            aUsages        = {sbsenum.UsageEnum.DIFFUSE: {sbsenum.UsageDataEnum.COMPONENTS:sbsenum.ComponentsEnum.RGBA}, 
                                              sbsenum.UsageEnum.HEIGHT: {sbsenum.UsageDataEnum.COMPONENTS:sbsenum.ComponentsEnum.R}}) 
 
        aGraph.connectNodes(aLeftNode  = aBitmapNode, aRightNode = aOutputNode, 
                            aRightNodeInput = sbsenum.InputEnum.INPUT_NODE_OUTPUT ) 
 
        # Update node position 
        initPos += yOffset 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc() 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoBuildSBSFromPainterBitmaps] Failed to create the new package") 
    raise error
demos.demoCreation(aContext, aDestFileAbsPath='')
Demonstrates all what is possible to create using this API:
  • A new substance from scratch
  • Compositing graph (all filters, instance of graphs, inputs and outputs…)
  • Function graph (all functions, instance of functions, …)
  • Definition of input parameters for graphs and functions
  • Definition of a dynamic parameter
Parameters:
  • aContext (context.Context) – Execution context
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoCreation:

if aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Create a new package 
    sbsDoc = sbsgenerator.createSBSDocument(aContext, aDestFileAbsPath) 
 
    aGraph = sbsDoc.createGraph(aGraphIdentifier = 'MyGraph', 
                                aParameters = {sbsenum.CompNodeParamEnum.OUTPUT_FORMAT:sbsenum.OutputFormatEnum.FORMAT_16BITS}, 
                                aInheritance= {sbsenum.CompNodeParamEnum.OUTPUT_FORMAT:sbsenum.ParamInheritanceEnum.ABSOLUTE}) 
    aSubGraph = sbsDoc.createGraph(aGraphIdentifier = 'MySubGraph') 
    aFunction = sbsDoc.createFunction(aFunctionIdentifier = 'MyFunction') 
    aResourceGroup = sbsDoc.createGroup( aParentFolder = 'MyResources', aGroupIdentifier = 'Bitmaps' ) 
 
    startPos = [48,  48,  0] 
    xOffset  = [192,  0,  0] 
    yOffset  = [0,  192,  0] 
    xyOffset = [192, 96,  0] 
 
    # ------------------------------------------------------------------------------------------ 
    # Create the graph 'MySubGraph' 
    # ------------------------------------------------------------------------------------------ 
 
    # Create Input parameters for MySubGraph 
    # - Parameter InputColor(RGBA) 
    aSubGraph.addInputParameter(aIdentifier   = 'InputColor', 
                        aWidget       = sbsenum.WidgetEnum.COLOR_FLOAT4, 
                        aDefaultValue = [1,1,1,1], 
                        aLabel        = 'Input Color') 
 
    # - Parameter Blending(DropDown list) 
    aParam = aSubGraph.addInputParameter(aIdentifier = 'Blending', 
                        aWidget = sbsenum.WidgetEnum.DROPDOWN_INT1, 
                        aLabel  = 'Blending') 
    aParam.setDropDownList(aValueMap={sbsenum.BlendBlendingModeEnum.MULTIPLY: 'Multiply', 
                                      sbsenum.BlendBlendingModeEnum.OVERLAY: 'Overlay', 
                                      sbsenum.BlendBlendingModeEnum.SOFT_LIGHT: 'Soft Light'}) 
    aParam.setDefaultValue(sbsenum.BlendBlendingModeEnum.MULTIPLY) 
 
    # Create the content of the graph MySubGraph 
    # - Uniform color filter 
    aUniformColor = aSubGraph.createCompFilterNode(aFilter = sbsenum.FilterEnum.UNIFORM, 
                        aParameters = {sbsenum.CompNodeParamEnum.COLOR_MODE: sbsenum.ColorModeEnum.COLOR, 
                                       sbsenum.CompNodeParamEnum.OUTPUT_COLOR: [0.54,0.063,0,1]}, 
                        aGUIPos=startPos) 
 
    # - Input node color 
    aInputNode = aSubGraph.createInputNode(aIdentifier = 'MyInput', 
                        aColorMode   = sbsenum.ColorModeEnum.COLOR, 
                        aGUIPos      = aUniformColor.getOffsetPosition(yOffset), 
                        aAttributes  = {sbsenum.AttributesEnum.Label: 'My Input'}, 
                        aUsages      = {sbsenum.UsageEnum.BASECOLOR:{sbsenum.UsageDataEnum.COMPONENTS:sbsenum.ComponentsEnum.RGB}}) 
 
    # - Blend filter 
    aBlendNode = aSubGraph.createCompFilterNode(aFilter = sbsenum.FilterEnum.BLEND, 
                        aGUIPos      = aUniformColor.getOffsetPosition(xyOffset), 
                        aParameters  = {sbsenum.CompNodeParamEnum.BLENDING_MODE: sbsenum.BlendBlendingModeEnum.MULTIPLY, 
                                        sbsenum.CompNodeParamEnum.OPACITY: 0.67}, 
                        aInheritance = {sbsenum.CompNodeParamEnum.OUTPUT_SIZE: sbsenum.ParamInheritanceEnum.PARENT}) 
 
    # - Output node 
    aOutputNode = aSubGraph.createOutputNode(aIdentifier = 'MyOutput', 
                        aGUIPos       = aBlendNode.getOffsetPosition(xOffset), 
                        aOutputFormat = sbsenum.TextureFormatEnum.DEFAULT_FORMAT, 
                        aAttributes   = {sbsenum.AttributesEnum.Description: 'SubGraph Output'}, 
                        aUsages       = {sbsenum.UsageEnum.BASECOLOR: {sbsenum.UsageDataEnum.COMPONENTS:sbsenum.ComponentsEnum.RGBA}}) 
 
    # Connect the nodes 
    aSubGraph.connectNodes(aLeftNode  = aInputNode, aRightNode = aBlendNode, 
                        aRightNodeInput = sbsenum.InputEnum.DESTINATION) 
    aSubGraph.connectNodes(aLeftNode  = aUniformColor, aRightNode = aBlendNode, 
                        aRightNodeInput = sbsenum.InputEnum.SOURCE) 
    aSubGraph.connectNodes(aLeftNode  = aBlendNode, aRightNode = aOutputNode, 
                        aRightNodeInput = sbsenum.InputEnum.INPUT_NODE_OUTPUT) 
 
    # Define some dynamic parameters, handled by the input parameters defined in SubGraph: 
    aDynFunction = aUniformColor.setDynamicParameter(sbsenum.CompNodeParamEnum.OUTPUT_COLOR) 
    aDynFunction.setToInputParam(aParentGraph = aSubGraph, aInputParamIdentifier = 'InputColor') 
 
    aDynFunction = aBlendNode.setDynamicParameter(sbsenum.CompNodeParamEnum.BLENDING_MODE) 
    aDynFunction.setToInputParam(aParentGraph = aSubGraph, aInputParamIdentifier = 'Blending') 
 
 
    # ------------------------------------------------------------------------------------------ 
    # Create the function 'MyFunction' 
    # ------------------------------------------------------------------------------------------ 
    aFunction.initFunction() 
 
    # - Definition of the function input parameter: BlurIntensity 
    aParamBlur = 'BlurIntensity' 
    aFunction.addInputParameter(aIdentifier = aParamBlur, 
                        aWidget = sbsenum.WidgetEnum.SLIDER_FLOAT1) 
 
    # - Function node Get_Float(BlurIntensity) 
    aNodeGet = aFunction.createFunctionNode(aFunction = sbsenum.FunctionEnum.GET_FLOAT1, 
                        aParameters = {sbsenum.FunctionEnum.GET_FLOAT1: aParamBlur}, 
                        aGUIPos=startPos) 
 
    # - Function node Pi (instance included in package sbs://functions.sbs) 
    aNodePi = aFunction.createFunctionInstanceNodeFromPath(aSBSDocument = sbsDoc, 
                        aPath = 'sbs://functions.sbs/Functions/Math/Pi', 
                        aGUIPos = aNodeGet.getOffsetPosition(yOffset)) 
 
    # - Function node Mul 
    aNodeMult = aFunction.createFunctionNode(aFunction = sbsenum.FunctionEnum.MUL, 
                        aGUIPos = aNodeGet.getOffsetPosition(xyOffset)) 
 
    # Connect the nodes 
    aFunction.connectNodes(aLeftNode = aNodeGet, aRightNode= aNodeMult, aRightNodeInput = sbsenum.FunctionInputEnum.A) 
    aFunction.connectNodes(aLeftNode = aNodePi, aRightNode = aNodeMult, aRightNodeInput = sbsenum.FunctionInputEnum.B) 
    aFunction.setOutputNode(aNodeMult) 
 
 
    # ------------------------------------------------------------------------------------------ 
    # Create the graph 'MyGraph' 
    # ------------------------------------------------------------------------------------------ 
 
    # Create Input parameters for MyGraph 
    aParam = aGraph.addInputParameter(aIdentifier = aParamBlur, 
                        aWidget = sbsenum.WidgetEnum.SLIDER_FLOAT1, 
                        aLabel  = 'Blur Intensity') 
    aParam.setDefaultValue(0.5) 
    aParam.setMaxValue(3) 
 
    # Create the content of MyGraph 
 
    # - FxMap Node 
    aFxMapNode = aGraph.createCompFilterNode(aFilter=sbsenum.FilterEnum.FXMAPS, 
                        aParameters={sbsenum.CompNodeParamEnum.COLOR_MODE: sbsenum.ColorModeEnum.GRAYSCALE, 
                                     sbsenum.CompNodeParamEnum.RANDOM_SEED: 5}, 
                        aGUIPos=startPos) 
 
    #  - Creation of the FxMap graph: 
    aFxMapGraph = aFxMapNode.getFxMapGraph() 
 
    #   - First Quadrant node (root) 
    aQuadrant1 = aFxMapGraph.createFxMapNode(aFxMapNode = sbsenum.FxMapNodeEnum.QUADRANT) 
    aFxMapGraph.setRootNode(aQuadrant1) 
 
    #   - Second Quadrant node 
    aQuadrant2 = aFxMapGraph.createFxMapNode(aFxMapNode = sbsenum.FxMapNodeEnum.QUADRANT, 
                        aGUIPos = aQuadrant1.getOffsetPosition(yOffset)) 
 
    #   - Third Quadrant node 
    aQuadrant3 = aFxMapGraph.createFxMapNode(aFxMapNode = sbsenum.FxMapNodeEnum.QUADRANT, 
                        aGUIPos = aQuadrant2.getOffsetPosition(yOffset), 
                        aParameters = {sbsenum.CompNodeParamEnum.FX_PATTERN_TYPE: sbsenum.FX_PatternType.PYRAMID}) 
 
    #   - Define several functions for the third quadrant node 
    #    => Random luminosity (Float1 between 0 and 1) 
    aFuncLum = aQuadrant3.setDynamicParameter(sbsenum.CompNodeParamEnum.FX_COLOR_LUM) 
    aFloatNode = aFuncLum.createFunctionNode(aFunction = sbsenum.FunctionEnum.CONST_FLOAT, 
                        aParameters = {sbsenum.FunctionEnum.CONST_FLOAT: 1}) 
    aRandNode = aFuncLum.createFunctionNode(aFunction = sbsenum.FunctionEnum.RAND, 
                        aGUIPos = aFloatNode.getOffsetPosition(xOffset)) 
 
    aFuncLum.connectNodes(aLeftNode=aFloatNode, aRightNode=aRandNode) 
    aFuncLum.setOutputNode(aRandNode) 
 
    #    => Random pattern rotation (Float1 between 0 and 1) 
    aFuncRotation = aFuncLum.copy() 
    aQuadrant3.setParameterValue(sbsenum.CompNodeParamEnum.FX_PATTERN_ROTATION, aFuncRotation) 
 
    #    => Random pattern offset (Float2 between -1 and 1) 
    aFuncOffset = aQuadrant3.setDynamicParameter(sbsenum.CompNodeParamEnum.FX_PATTERN_OFFSET) 
    aRandNode = aFuncOffset.createFunctionInstanceNodeFromPath(aSBSDocument=sbsDoc, 
                        aPath   = 'sbs://functions.sbs/Functions/Random/Uniform_[-1,1[') 
    aRandNode2 = aFuncOffset.createFunctionInstanceNodeFromPath(aSBSDocument=sbsDoc, 
                        aPath   = 'sbs://functions.sbs/Functions/Random/Uniform_[-1,1[', 
                        aGUIPos = aRandNode.getOffsetPosition(yOffset)) 
 
    aVec2Node = aFuncOffset.createFunctionNode(aFunction = sbsenum.FunctionEnum.VECTOR2, 
                        aGUIPos = aRandNode.getOffsetPosition(xyOffset)) 
 
    aFuncOffset.connectNodes(aLeftNode = aRandNode, aRightNode = aVec2Node, 
                        aRightNodeInput = sbsenum.FunctionInputEnum.COMPONENTS_IN) 
    aFuncOffset.connectNodes(aLeftNode = aRandNode2, aRightNode = aVec2Node, 
                        aRightNodeInput = sbsenum.FunctionInputEnum.COMPONENTS_LAST) 
    aFuncOffset.setOutputNode(aVec2Node) 
 
    #    => Random pattern size (Float2 between 0.5 and 2) 
    aFuncSize = aQuadrant3.setDynamicParameter(sbsenum.CompNodeParamEnum.FX_PATTERN_SIZE) 
    aFloatNode1 = aFuncSize.createFunctionNode(aFunction = sbsenum.FunctionEnum.CONST_FLOAT, 
                        aParameters = {sbsenum.FunctionEnum.CONST_FLOAT: 0.5}) 
    aFloatNode2 = aFuncSize.createFunctionNode(aFunction = sbsenum.FunctionEnum.CONST_FLOAT, 
                        aParameters = {sbsenum.FunctionEnum.CONST_FLOAT: 2}, 
                        aGUIPos = aFloatNode1.getOffsetPosition(yOffset)) 
    aRandNode = aFuncSize.createFunctionInstanceNodeFromPath(aSBSDocument=sbsDoc, 
                        aPath   = 'sbs://functions.sbs/Functions/Random/Uniform_[A,B[', 
                        aGUIPos = aFloatNode1.getOffsetPosition(xyOffset)) 
    aVec2Node = aFuncSize.createFunctionNode(aFunction = sbsenum.FunctionEnum.VECTOR2, 
                        aGUIPos = aRandNode.getOffsetPosition(xOffset)) 
 
    aFuncSize.connectNodes(aLeftNode = aFloatNode1, aRightNode = aRandNode, aRightNodeInput = 'A') 
    aFuncSize.connectNodes(aLeftNode = aFloatNode2, aRightNode = aRandNode, aRightNodeInput = 'B') 
    aFuncSize.connectNodes(aLeftNode = aRandNode, aRightNode = aVec2Node, aRightNodeInput = sbsenum.FunctionInputEnum.COMPONENTS_IN) 
    aFuncSize.connectNodes(aLeftNode = aRandNode, aRightNode = aVec2Node, aRightNodeInput = sbsenum.FunctionInputEnum.COMPONENTS_LAST) 
    aFuncSize.setOutputNode(aVec2Node) 
 
    #   - Fourth and Fifth Quadrant nodes, copies of the third one 
    aQuadrant4 = aFxMapGraph.duplicateNode(aQuadrant3, aGUIOffset = yOffset) 
    aQuadrant5 = aFxMapGraph.duplicateNode(aQuadrant4, aGUIOffset = yOffset) 
 
    #   - Connections of the quadrant nodes 
    aFxMapGraph.connectNodes(aTopNode = aQuadrant1, aBottomNode = aQuadrant2) 
    aFxMapGraph.connectNodes(aTopNode = aQuadrant2, aBottomNode = aQuadrant3) 
    aFxMapGraph.connectNodes(aTopNode = aQuadrant3, aBottomNode = aQuadrant4) 
    aFxMapGraph.connectNodes(aTopNode = aQuadrant4, aBottomNode = aQuadrant5) 
 
 
    # - Gradient Filter with key values 
    gradientKeyValues = [sbslibrary.GradientKey(aPosition = 0,     aValue = [0,0,0,1]), 
                         sbslibrary.GradientKey(aPosition = 0.330, aValue = [1,1,1,1])] 
    aGradientNode = aGraph.createGradientMapNode(aGUIPos = aFxMapNode.getOffsetPosition(xOffset), 
                                                 aKeyValues = gradientKeyValues, 
                                                 aParameters= {sbsenum.CompNodeParamEnum.GRADIENT_ADDRESSING: sbsenum.GradientAddressingEnum.REPEAT}) 
 
    # - Instance of graph SubGraph created previously 
    aInstanceNode = aGraph.createCompInstanceNode(aSBSDocument = sbsDoc, 
                        aGraph  = aSubGraph, 
                        aGUIPos = aGradientNode.getOffsetPosition(xOffset)) 
 
    # - Instance of substance Blur_Hq included in the default package sbs:// 
    aBlurHQNode = aGraph.createCompInstanceNodeFromPath(aSBSDocument= sbsDoc, 
                        aPath       = 'sbs://blur_hq.sbs/blur_hq', 
                        aGUIPos     = aInstanceNode.getOffsetPosition(xOffset), 
                        aParameters = {'Intensity':2.8}) 
 
    #   - Definition of a dynamic parameter for the Blur_Hq Intensity parameter: 
    aBlurFunction = aBlurHQNode.setDynamicParameter(aParameter = 'Intensity') 
    aGetFloatNode = aBlurFunction.createFunctionNode(aFunction = sbsenum.FunctionEnum.GET_FLOAT1, 
                        aParameters = {sbsenum.FunctionEnum.GET_FLOAT1: aParamBlur}) 
 
    #   - Creation of an instance of function MyFunction defined previously: 
    aFctInstanceNode = aBlurFunction.createFunctionInstanceNode(aSBSDocument = sbsDoc, 
                        aFunction = aFunction, 
                        aGUIPos   = aGetFloatNode.getOffsetPosition(xOffset)) 
 
    #   - Connect the nodes 
    aBlurFunction.connectNodes(aLeftNode = aGetFloatNode, aRightNode = aFctInstanceNode) 
    aBlurFunction.setOutputNode(aFctInstanceNode) 
 
    # - SVG node 
    aSvgPath = sbsDoc.buildAbsPathFromRelToMePath('Bitmaps/SD_Icon_Color.svg' ) 
    aSVGNode = aGraph.createSvgNode(aSBSDocument = sbsDoc, 
                        aResourcePath  = aSvgPath, 
                        aResourceGroup = aResourceGroup, 
                        aGUIPos        = aBlurHQNode.getOffsetPosition(xOffset), 
                        aParameters    = {sbsenum.CompNodeParamEnum.COLOR_MODE:sbsenum.ColorModeEnum.COLOR, 
                                          sbsenum.CompNodeParamEnum.OUTPUT_SIZE:[sbsenum.OutputSizeEnum.SIZE_1024,sbsenum.OutputSizeEnum.SIZE_1024], 
                                          sbsenum.CompNodeParamEnum.TILING_MODE:sbsenum.TilingEnum.NO_TILING}, 
                        aInheritance   = {sbsenum.CompNodeParamEnum.OUTPUT_SIZE:sbsenum.ParamInheritanceEnum.ABSOLUTE, 
                                          sbsenum.CompNodeParamEnum.TILING_MODE:sbsenum.ParamInheritanceEnum.INPUT}, 
                        aCookedQuality = 0.5) 
 
    # - Output node 
    aOutputNode = aGraph.createOutputNode(aIdentifier = 'MainOutput', 
                        aGUIPos       = aSVGNode.getOffsetPosition(xOffset), 
                        aOutputFormat = sbsenum.TextureFormatEnum.DEFAULT_FORMAT, 
                        aAttributes   = {sbsenum.AttributesEnum.Description: 'Main Output'}, 
                        aMipmaps      = sbsenum.MipmapEnum.LEVELS_12, 
                        aUsages       = {sbsenum.UsageEnum.BASECOLOR: {sbsenum.UsageDataEnum.COMPONENTS:sbsenum.ComponentsEnum.RGBA}}) 
 
    # Connect the nodes 
    aGraph.connectNodes(aLeftNode = aFxMapNode,    aRightNode = aGradientNode) 
    aGraph.connectNodes(aLeftNode = aGradientNode, aRightNode = aInstanceNode, aRightNodeInput = 'MyInput') 
    aGraph.connectNodes(aLeftNode = aInstanceNode, aRightNode = aBlurHQNode) 
    aGraph.connectNodes(aLeftNode = aBlurHQNode,   aRightNode = aSVGNode) 
    aGraph.connectNodes(aLeftNode = aSVGNode,      aRightNode = aOutputNode) 
 
    # Tweak parameters 
    aInstanceNode.getParameterValue('InputColor') 
    aInstanceNode.setParameterValue('InputColor', [1, 0, 0, 1]) 
    aInstanceNode.setParameterValue('Blending', sbsenum.BlendBlendingModeEnum.OVERLAY) 
 
    # Set graph attributes and icon 
    aGraph.setAttribute(aAttributeIdentifier = sbsenum.AttributesEnum.Author, aAttributeValue = 'Substance Designer API') 
    aGraph.setIcon(aIconAbsPath = sbsDoc.buildAbsPathFromRelToMePath('Bitmaps/graphIcon.jpg')) 
 
    # Write the document structure into the destination .sbs file 
    sbsDoc.writeDoc() 
 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoCreation] Failed to create the new package") 
    raise error
demos.demoCreationMDL(aContext, aDestFileAbsPath='')
Demonstrates the MDL graph creation using this API:
  • MDL Graph
  • MDL Nodes of any kind (constants, native mdl nodes, mdl graph instances, …)
  • Declaring the input of the graph
  • Setting parameters and annotations
  • Create resources dedicated to MDL graphs (light profile or bsdf measurement)
Parameters:
  • aContext (context.Context) – Execution context
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoCreationMDL:

if aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    startPos = [48,  48,  0] 
    xOffset  = [192,  0,  0] 
    yOffset  = [0,  192,  0] 
    xyOffset = [192, 96,  0] 
 
    # Create a new package and a MDL graph 
    sbsDoc = sbsgenerator.createSBSDocument(aContext, aDestFileAbsPath) 
    mdlGraph = sbsDoc.createMDLGraph(aGraphIdentifier='DemoMDLGraph', aCreateOutputNode=True) 
 
    # Create a new Substance graph, which will be used in the MDL graph 
    sbsGraph = sbsDoc.createGraph(aGraphIdentifier='SBSGraph') 
    aNode = sbsGraph.createCompFilterNode(aFilter=sbsenum.FilterEnum.UNIFORM, 
                                        aParameters={sbsenum.CompNodeParamEnum.OUTPUT_COLOR:[0,0,1,1]}, 
                                        aGUIPos=startPos) 
    aOutput = sbsGraph.createOutputNode(aIdentifier='Output', 
                                        aGUIPos=aNode.getOffsetPosition(xOffset)) 
    sbsGraph.connectNodes(aLeftNode=aNode, aRightNode=aOutput) 
 
 
    # Create the microfacet ggx branch 
    inputRoughness = mdlGraph.createMDLNodeConst(aName='roughness', aConstTypePath='mdl::float', aExposed=True, 
                                        aValue=0.5, 
                                        aAnnotations={mdl.mdlenum.MDLAnnotationEnum.DISPLAY_NAME:'Roughness', 
                                                      mdl.mdlenum.MDLAnnotationEnum.GAMMA_TYPE:1, 
                                                      mdl.mdlenum.MDLAnnotationEnum.SAMPLER_USAGE:'roughness', 
                                                      mdl.mdlenum.MDLAnnotationEnum.HARD_RANGE:[0,1]}, 
                                        aGUIPos=startPos) 
    sbsInstance = mdlGraph.createMDLNodeSBSGraphInstance(aSBSDocument=sbsDoc, 
                                                         aGraph=sbsGraph, 
                                                         aGUIPos=inputRoughness.getOffsetPosition(yOffset)) 
 
    mulNode = mdlGraph.createMDLNodeInstance(aPath='mdl::operator*(float,float)', 
                                             aGUIPos=inputRoughness.getOffsetPosition(xOffset)) 
 
    ggxNode = mdlGraph.createMDLNodeInstance(aPath='mdl::df::microfacet_ggx_smith_bsdf$1.5(float,float,color,float3,::df::scatter_mode,string)', 
                                             aGUIPos=mulNode.getOffsetPosition(xyOffset)) 
    mdlGraph.connectNodes(aLeftNode=inputRoughness, aRightNode=mulNode, aRightNodeInput='x') 
    mdlGraph.connectNodes(aLeftNode=inputRoughness, aRightNode=mulNode, aRightNodeInput='y') 
    mdlGraph.connectNodes(aLeftNode=mulNode, aRightNode=ggxNode, aRightNodeInput='roughness_u') 
    mdlGraph.connectNodes(aLeftNode=mulNode, aRightNode=ggxNode, aRightNodeInput='roughness_v') 
    mdlGraph.connectNodes(aLeftNode=sbsInstance, aRightNode=ggxNode, aRightNodeInput='tint') 
 
 
    # Create a new light profile resource 
    aPath = sbsDoc.buildAbsPathFromRelToMePath('Bitmaps/Comet.IES') 
    aRes = sbsDoc.createLinkedResource(aResourcePath=aPath,aResourceTypeEnum=sbsenum.ResourceTypeEnum.LIGHT_PROFILE) 
 
    # Create a IES profile node that uses this resource and generate a 'material_emission' 
    iesNode = mdlGraph.createMDLNodeInstance(aPath='mdl::light_profile(string)', 
                                             aGUIPos=sbsInstance.getOffsetPosition(yOffset)) 
    iesNode.setParameterValue(aParameter='name', aParamValue=aRes.getPkgResourcePath()) 
 
    edfNode = mdlGraph.createMDLNodeInstance(aPath='mdl::df::measured_edf(light_profile,float,bool,float3x3,float3,string)', 
                                             aParameters={'multiplier':0.01}, 
                                             aGUIPos=iesNode.getOffsetPosition(xOffset)) 
    matEmissionNode = mdlGraph.createMDLNodeInstance(aPath='mdl::material_emission(edf,color,intensity_mode)', 
                                                     aParameters={'intensity':[0.000619,0.000387,0.000077]}, 
                                                     aGUIPos=edfNode.getOffsetPosition(xOffset)) 
 
    mdlGraph.connectNodes(aLeftNode=iesNode, aRightNode=edfNode) 
    mdlGraph.connectNodes(aLeftNode=edfNode, aRightNode=matEmissionNode) 
 
    # Handle the material_geometry 
    stateNormal = mdlGraph.createMDLNodeInstance(aPath='mdl::state::normal()', 
                                                 aGUIPos=iesNode.getOffsetPosition(yOffset)) 
 
    inputNormal =  mdlGraph.createMDLNodeConst(aName='normal', aConstTypePath='mdl::float3', aExposed=True, 
                                        aAnnotations={mdl.mdlenum.MDLAnnotationEnum.DISPLAY_NAME:'Normal', 
                                                      mdl.mdlenum.MDLAnnotationEnum.GAMMA_TYPE:1, 
                                                      mdl.mdlenum.MDLAnnotationEnum.SAMPLER_USAGE:'normal'}, 
                                        aGUIPos=stateNormal.getOffsetPosition(xOffset)) 
 
    matGeometryNode = mdlGraph.createMDLNodeInstance(aPath='mdl::material_geometry(float3,float,float3)', 
                                                     aGUIPos=inputNormal.getOffsetPosition(xOffset)) 
 
    mdlGraph.connectNodes(aLeftNode=stateNormal, aRightNode=inputNormal) 
    mdlGraph.connectNodes(aLeftNode=inputNormal, aRightNode=matGeometryNode, aRightNodeInput='normal') 
 
    # Create the material_surface 
    matSurfaceNode = mdlGraph.createMDLNodeInstance(aPath='mdl::material_surface(bsdf,material_emission)', 
                                                    aGUIPos=ggxNode.getOffsetPosition(xyOffset)) 
    matSurfaceNode.setPinVisibilityForParameter(aParameter='emission', aVisible=True) 
 
    mdlGraph.connectNodes(aLeftNode=ggxNode, aRightNode=matSurfaceNode) 
    mdlGraph.connectNodes(aLeftNode=matEmissionNode, aRightNode=matSurfaceNode) 
 
    # Get the output node 
    outputNode = mdlGraph.getGraphOutput() 
    outputNode.setPosition(matSurfaceNode.getOffsetPosition(xyOffset)) 
 
    mdlGraph.connectNodes(aLeftNode=matSurfaceNode, aRightNode=outputNode) 
    mdlGraph.connectNodes(aLeftNode=matGeometryNode, aRightNode=outputNode) 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc(aNewFileAbsPath=aDestFileAbsPath) 
 
    del sbsDoc 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoCreationMDL] Failed to modify package") 
    raise error
demos.demoExportWithDependencies(aContext, aFileAbsPath='', aDestFolderAbsPath='')

Export the given package into the given destination folder

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the package
  • aDestFolderAbsPath (str) – The absolute folder where to export the package
Returns:

Nothing

Here is the code of function demoExportWithDependencies:

if aFileAbsPath == '' or aDestFolderAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFolderAbsPath") 
    return False 
 
try: 
    python_helpers.createFolderIfNotExists(aDestFolderAbsPath) 
 
    # Parse the document to export 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Create an exporter 
    aExporter = sbsexporter.SBSExporter() 
 
    # Export the package into an archived self-contained package, in the folder aDestFolderAbsPath 
    # The resources and dependencies of this package will be included in the archive, including the ones referred by the alias sbs:// 
    aResultingArchive = aExporter.export(aSBSDocument = sbsDoc, aExportFolder = aDestFolderAbsPath, 
                                         aBuildArchive = True, aAliasesToExport = ['sbs']) 
    log.info("=> Archive created at %s" % aResultingArchive) 
 
    # Same as before, without archiving the resulting folder 
    aResultingPath = aExporter.export(aSBSDocument = sbsDoc, aExportFolder = aDestFolderAbsPath, 
                                      aAliasesToExport = ['sbs']) 
    log.info("=> Substance exported with its dependencies at %s" % aResultingPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoExportWithDependencies] Failed to export the package") 
    raise error
demos.demoIteration(aContext, aFileAbsPath='', aDestFileAbsPath='')

Demonstrates the iteration creation of a single node and of a complete pattern.

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoIteration:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Parse the .sbs file and provide the object structure of the entire substance 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Duplicate 5 times a single node 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier = 'DemoIterationSubGraph') 
    aGraph.createIterationOnNode(aNbIteration = 5, 
                                 aNodeUID = '1255032103') 
 
    # Duplicate 3 times the node (test the automatic detection of compatible inputs / outputs) 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier = 'DemoIterationSubGraphDouble') 
    aGraph.createIterationOnNode(aNbIteration = 3, 
                                 aNodeUID = '1255523774') 
 
    # Duplicate 3 times the pattern of nodes (test the automatic detection of compatible inputs / outputs) 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier='DemoIterationPattern') 
    aGraph.createIterationOnPattern(aNbIteration=3, 
                                    aNodeUIDs=['1255034408', '1255026224', '1255029181', '1255029884', 
                                                   '1255029987', '1255029994', '1255029049']) 
 
    # Duplicate 3 times the pattern of nodes, specifying way to connect two successive patterns 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier = 'DemoIterationVerticalPattern') 
    aGraph.createIterationOnPattern(aNbIteration = 3, 
                                    aNodeUIDs = ['1262168894', '1262168896'], 
                                    aNodeUIDs_NextPattern = ['1262169024', '1262168960'], 
                                    aGUIOffset    = [0, 120]) 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
 
    del sbsDoc 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoIteration] Failed to modify package") 
    raise error
demos.demoIterationFlame(aContext, aFileAbsPath='', aDestFileAbsPath='')

Demonstrates the iteration creation of a pattern inside a Function

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoIterationFlame:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Parse the .sbs file and provide the object structure of the entire substance 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Duplicate 63 times the pattern inside function RayMarch 
    aFunction = sbsDoc.getSBSFunction(aFunctionIdentifier= 'RayMarch') 
    createdNodes = aFunction.createIterationOnPattern(aNbIteration = 63, 
                        aGUIOffset            = [0, 200], 
                        aNodeUIDs             = ['1262101431', '1262101516'], 
                        aNodeUIDs_NextPattern = ['1262101533', '1262101527']) 
 
    # Connect the last created node with the end of the function 
    aEndNode = aFunction.getNode('1262095290') 
    aFunction.connectNodes(aLeftNode = createdNodes[-1], aRightNode = aEndNode, 
                           aRightNodeInput = sbsenum.FunctionInputEnum.SEQUENCE_IN) 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoIterationFlame] Failed to create the iterations") 
    raise error
demos.demoIterationPixProc(aContext, aFileAbsPath='', aDestFileAbsPath='')

Demonstrates the iteration inside a pixel processor.

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoIterationPixProc:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    pass 
 
try: 
    # Parse the .sbs file and provide the object structure of the entire substance 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Get the graph 'TerrainMultiFractal' 
    aGraph = sbsDoc.getSBSGraph(aGraphIdentifier = 'TerrainMultiFractal') 
 
    # Get the pixel processor node where the iteration will be created, and its dynamic function 
    aPixProcNode = aGraph.getNode('1260898088') 
    aDynFct = aPixProcNode.getPixProcFunction() 
 
    # Duplicate 10 times the pattern, indicating the way to reconnect to the next pattern 
    createdNodes = aDynFct.createIterationOnPattern(aNbIteration = 10, 
                        aGUIOffset = [0, 200], 
                        aNodeUIDs = ['1263052178', '1263052114' ], 
                        aNodeUIDs_NextPattern = ['1263052279', '1263052278']) 
 
    # Connect the last node of the iteration to the end part of the pixel processor function 
    aDynFct.connectNodes(aLeftNode=createdNodes[-1], aRightNode='1257621570') 
    aDynFct.connectNodes(aLeftNode=createdNodes[-1], aRightNode='1257624564') 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
 
    del sbsDoc 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoIteration] Failed to modify package") 
    raise error
demos.demoMassiveModification(aContext, aFileAbsPath='', aDestFileAbsPath='')

Demonstrates the massive modification of a Substance. In this sample, a substance containing 3 graph with lots of Input nodes will be modified so that all Graphs and Input nodes pixel format are set to 16 bits per channel, without inheritance from parent.

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoMassiveModification:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Parse the .sbs file and provide the object structure of the entire substance 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Parse all graphs 
    for aGraph in sbsDoc.getSBSGraphList(): 
 
        # Set pixel format and output size of the graph 
        aGraph.setBaseParameterValue(aParameter  = sbsenum.CompNodeParamEnum.OUTPUT_FORMAT, 
                                     aParamValue = sbsenum.OutputFormatEnum.FORMAT_16BITS, 
                                     aRelativeTo = sbsenum.ParamInheritanceEnum.ABSOLUTE) 
        aGraph.setBaseParameterValue(aParameter  = sbsenum.CompNodeParamEnum.OUTPUT_SIZE, 
                                     aParamValue = [sbsenum.OutputSizeEnum.SIZE_1024,sbsenum.OutputSizeEnum.SIZE_1024], 
                                     aRelativeTo = sbsenum.ParamInheritanceEnum.ABSOLUTE) 
 
        # Set pixel format for all input nodes 
        inputNodes = aGraph.getAllInputNodes() 
        for inputNode in inputNodes: 
            inputNode.setParameterValue(aParameter  = sbsenum.CompNodeParamEnum.OUTPUT_FORMAT, 
                                        aParamValue = sbsenum.OutputFormatEnum.FORMAT_16BITS, 
                                        aRelativeTo = sbsenum.ParamInheritanceEnum.ABSOLUTE) 
 
    # Write back the document structure into the destination .sbs file 
    sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
 
    del sbsDoc 
    log.info("=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoMassiveModification] Failed to modify package") 
    raise error
demos.demoReadWriteSBS(aContext, aFileAbsPath='', aDestFileAbsPath='')

Allow to validate the deserialization and serialization of a .sbs without doing any modification on it.

Parameters:
  • aContext (context.Context) – Execution context
  • aFileAbsPath (str) – The absolute path of the file
  • aDestFileAbsPath (str) – The absolute path of the resulting file. Can be the same as aFileAbsPath
Returns:

Nothing

Here is the code of function demoReadWriteSBS:

if aFileAbsPath == '' or aDestFileAbsPath == '': 
    log.error("Please provide the appropriate arguments: aFileAbsPath and aDestFileAbsPath") 
    return False 
 
try: 
    # Parse document 
    sbsDoc = substance.SBSDocument(aContext, aFileAbsPath) 
    sbsDoc.parseDoc() 
 
    # Display its dependencies and resources 
    log.info("Dependencies: ") 
    for s in sbsDoc.getSBSDependencyList(): 
        log.info(s.mFileAbsPath) 
    log.info("nResources: ") 
    for s in sbsDoc.getSBSResourceList(): 
        log.info(s.getResolvedFilePath()) 
        log.info(s.mFileAbsPath) 
 
    # Write the document 
    sbsDoc.writeDoc(aNewFileAbsPath = aDestFileAbsPath) 
    log.info("n=> Resulting substance saved at %s" % aDestFileAbsPath) 
    return True 
 
except BaseException as error: 
    log.error("!!! [demoHelloWorld] Failed to create the new package") 
    raise error

Adobe, Inc.

Get help faster and easier

New user?