Volumetric Graphs and Channels

This topic explains how to build and manipulate volumetric graphs in code. For the API object model, see Introduction to the Volumetric Modeling API. For implicit modeling concepts and how output channels combine into a levelset, see Advanced topics.

A VolumetricModel is defined by two Graph objects: the PrimaryGraph and the CellGraph. Each graph is a directed acyclic graph (DAG) of GraphNode objects connected through pin-to-pin links. You add nodes, set properties, connect output pins to channel input pins, and validate the result before meshing or sampling.

Graphs

Graphs represent the overall structure of the implicit modeling process, comprising nodes and connections that perform the mathematical operations necessary to define a volumetric field. Each model has two graphs:

Nodes, Properties, and Pins

Nodes are the fundamental building blocks of a graph. Each node has:

Use GraphNodes.add, GraphNodes.outputNode, and GraphNodePin.connect to build the graph programmatically. See the node reference for available node type strings and property types.

Connections

Connections link an output pin on one node to an input pin on another by calling GraphNodePin.connect. Data flows from outputs to inputs, enabling chained evaluation. Pin indices are zero-based. Inspect existing wiring with Graph.connectors or GraphNodePin.connectors.

Output Channels

Output channels are special nodes with one input pin and no output pins. They are the sinks of the graph — the paths through which evaluated data reaches the volumetric model. Retrieve a channel node with GraphNodes.outputNode and a GraphOutputNodeTypes value.

GraphOutputNodeTypes is an enumeration in adsk.volume that identifies which output channel to access on a graph. Primary-graph channels control boundary shape, lattice and texture coordinates and densities, and color. Cell-graph channels define the repeating lattice and texture unit-cell shapes. The table below maps each channel to its enum member, graph, input type, and role in evaluation.

Channel GraphOutputNodeTypes Graph Input type Role
BoundarySDF BoundarySDFOutputNodeType Primary Scalar Limits geometry to the desired boundary. Combined with mesostructures using max.
Color ColorOutputNodeType Primary Color Defines color anywhere in the field. Evaluated independently of the levelset.
LatticeDensity LatticeDensityOutputNodeType Primary Scalar Modulates lattice cell shape; subtracted from lattice shape to form lattice mesostructure.
LatticeCoordinates LatticeCoordinatesOutputNodeType Primary Vector Maps global coordinates to cell coordinates for the lattice pattern.
TextureDensity TextureDensityOutputNodeType Primary Scalar Modulates texture near the boundary only. Multiplied with texture shape.
TextureCoordinates TextureCoordinatesOutputNodeType Primary Vector Maps global coordinates to cell coordinates for the texture pattern.
CellLatticeShape CellLatticeShapeOutputNodeType Cell Scalar SDF of the repeating lattice unit cell, evaluated in lattice cell coordinates.
CellTextureShape CellTextureShapeOutputNodeType Cell Scalar SDF of the surface texture unit cell, evaluated in texture cell coordinates.

Directed Acyclic Graphs (DAG)

Graphs must be directed acyclic graphs: no path may form a loop. This prevents infinite evaluation cycles and ensures a well-defined evaluation order from inputs to output channels.

Pin Data Types

Graph pins use three data types:

Getting Started — Minimal Graph

The smallest useful volumetric feature uses a solid boundary body, relies on the default BoundarySDF channel (created automatically), optionally sets a constant lattice density, and converts to mesh with default settings. Use this to verify your toolchain before building more complex graphs.

import adsk.core, adsk.fusion, adsk.volume

app = adsk.core.Application.get()
design = adsk.fusion.Design.cast(app.activeProduct)
component = design.rootComponent

# Assume body is a solid BRep body to use as the boundary
features = component.features
customFeatures = features.volumetricCustomFeatures
customInput = customFeatures.createInput(body)
customFeature = customFeatures.add(customInput)
volumetricModel = adsk.volume.VolumetricModel.cast(customFeature.volumetricModel)

# Optional: set constant lattice density on the default graph
primaryGraph = volumetricModel.getGraph(adsk.volume.GraphTypes.PrimaryGraphType)
densityNode = primaryGraph.nodes.add("Density", "ConstantScalar")
densityNode.properties.itemByName("value").value = 0.5
densityChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.LatticeDensityOutputNodeType)
densityNode.outputPins.item(0).connect(densityChannel.inputPins.item(0))

# Convert to mesh with defaults
meshFeatures = features.volumetricModelToMeshFeatures
meshInput = meshFeatures.createInput(volumetricModel)
meshFeatures.add(meshInput)

Graph Validation

Before relying on evaluation results, check that the graph is well-formed:

Basic Lattice Example

A minimal lattice wires constant density, a coordinate transform, and a gyroid cell shape:

import adsk.core, adsk.fusion, adsk.volume

primaryGraph = volumetricModel.getGraph(adsk.volume.GraphTypes.PrimaryGraphType)
cellGraph = volumetricModel.getGraph(adsk.volume.GraphTypes.CellGraphType)

# Constant lattice density
densityNode = primaryGraph.nodes.add("ConstantDensity", "ConstantScalar")
densityNode.properties.itemByName("value").value = 0.5
densityChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.LatticeDensityOutputNodeType)
densityNode.outputPins.item(0).connect(densityChannel.inputPins.item(0))

# Lattice coordinate transform (asymmetric scale)
coordNode = primaryGraph.nodes.add("LatticeTransform", "TransformCoords")
coordNode.properties.itemByName("scaling").value = adsk.core.Vector3D.create(1, 2, 3)
coordChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.LatticeCoordinatesOutputNodeType)
coordNode.outputPins.item(0).connect(coordChannel.inputPins.item(0))

# Gyroid cell shape
gyroidNode = cellGraph.nodes.add("gyroid", "FunctionVectorToScalar")
gyroidNode.properties.itemByName("function").value = (
    "(sin(x*pi2)*cos(y*pi2)+sin(y*pi2)*cos(z*pi2)+sin(z*pi2)*cos(x*pi2))*0.333333333+0.5"
)
shapeChannel = cellGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.CellLatticeShapeOutputNodeType)
gyroidNode.outputPins.item(0).connect(shapeChannel.inputPins.item(0))

Lattice with Control Point Map

Use a ControlPointMapScalarToScalar node to vary lattice density as a function of position:

# Map a scalar input (e.g. height) to a density range
mapNode = primaryGraph.nodes.add("DensityMap", "ControlPointMapScalarToScalar")
# Configure control points on the node's ScalarControlPointMapGraphNodeProperty, then connect:
densityChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.LatticeDensityOutputNodeType)
mapNode.outputPins.item(0).connect(densityChannel.inputPins.item(0))

Control point map properties use ScalarControlPointMapGraphNodeProperty (addPoint, iterable count/item). For color ramps, use ControlPointMapScalarToColor with ColorControlPointMapGraphNodeProperty.

LatticeCoordinates Transforms

The TransformCoords, TwistCoords, and HomogenousTransformCoords nodes map global coordinates to cell coordinates. The example below applies non-uniform scale and a simple offset:

transform = primaryGraph.nodes.add("CellMapping", "TransformCoords")
transform.properties.itemByName("scaling").value = adsk.core.Vector3D.create(2, 1, 1)
transform.properties.itemByName("translation").value = adsk.core.Vector3D.create(0.1, 0, 0)

coordChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.LatticeCoordinatesOutputNodeType)
transform.outputPins.item(0).connect(coordChannel.inputPins.item(0))

Basic Texture Example

Surface texture uses TextureCoordinates, TextureDensity, and CellTextureShape in the Cell graph:

# Texture coordinate mapping on the primary graph
texCoord = primaryGraph.nodes.add("TexCoords", "TransformCoords")
texCoordChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.TextureCoordinatesOutputNodeType)
texCoord.outputPins.item(0).connect(texCoordChannel.inputPins.item(0))

# Constant texture density near the boundary
texDensity = primaryGraph.nodes.add("TexDensity", "ConstantScalar")
texDensity.properties.itemByName("value").value = 0.3
texDensityChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.TextureDensityOutputNodeType)
texDensity.outputPins.item(0).connect(texDensityChannel.inputPins.item(0))

# Texture cell shape in the cell graph
texShape = cellGraph.nodes.add("surfacePattern", "FunctionVectorToScalar")
texShape.properties.itemByName("function").value = "sin(x*pi2)*0.5+0.5"
texShapeChannel = cellGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.CellTextureShapeOutputNodeType)
texShape.outputPins.item(0).connect(texShapeChannel.inputPins.item(0))

Basic Color Example

Wire a constant or computed color to the Color output channel:

colorNode = primaryGraph.nodes.add("BaseColor", "ConstantColor")
colorNode.properties.itemByName("value").value = adsk.core.Color.create(255, 128, 0, 255)

colorChannel = primaryGraph.nodes.outputNode(
    adsk.volume.GraphOutputNodeTypes.ColorOutputNodeType)
colorNode.outputPins.item(0).connect(colorChannel.inputPins.item(0))

Combining Lattice and Texture

When both lattice and texture mesostructures are connected, the evaluator combines channels using the fixed order described in Graph evaluation. Wire all lattice and texture channels before meshing:

# BoundarySDF is created by default when the volumetric model is created.
# Ensure lattice channels (LatticeCoordinates, LatticeDensity, CellLatticeShape)
# and texture channels (TextureCoordinates, TextureDensity, CellTextureShape)
# are each fully connected. The evaluator combines them automatically:
#   levelset = max(latticeMeso, boundarySDF + textureMeso)

Function expression syntax for cell-shape nodes is covered in Volumetric Function Expression Syntax.

Related topics