worn_painted_metal
21 nodes, 23 wires — 9 from the library, 12 built in
- colour
- grayscale
- either
- value
- built-in filter
- library node
The same network, for Designer
Open the graph you want it in, then Windows › Python Editor, paste, Run.
# Generated from a node-graph spec -- worn_painted_metal.
#
# Open the graph you want these nodes in, then: Windows > Python Editor, paste, Run.
# Nodes are added below anything already in the graph; nothing existing is touched.
import os
import sys
import sd
from sd.api.sdbasetypes import ColorRGBA, float2, float4
from sd.api.sdapplication import SDApplicationPath
from sd.api.sdvaluebool import SDValueBool
from sd.api.sdvaluecolorrgba import SDValueColorRGBA
from sd.api.sdvalueenum import SDValueEnum
from sd.api.sdvaluefloat import SDValueFloat
from sd.api.sdvaluefloat4 import SDValueFloat4
from sd.api.sdvalueint import SDValueInt
from sd.api.sdvaluestring import SDValueString
from sd.api.sdvaluearray import SDValueArray
from sd.api.sdtypeusage import SDTypeUsage
from sd.api.sdusage import SDUsage
from sd.api.sdvalueusage import SDValueUsage
def refuse(app, headline, detail):
"""Stop, and make sure it was seen.
Two channels, because one alone is missable in a different way. stderr first and always:
the Python Editor tags it [ERR] rather than [MSG], which is why a crash traceback stands out
from ordinary output, and unlike a dialog it is still there after it has been dismissed.
Then a modal box, which is the half that interrupts rather than waits to be read.
The dialog is the optional half. PySide6 6.5 ships in the interpreter's own site-packages
and sd.api wraps Designer's main window into a QMainWindow for it, so it is available from
a pasted script -- but anything that goes wrong with it goes wrong after the console line
has already been written."""
sys.stderr.write(headline + '\n' + detail + '\n')
sys.stderr.flush()
try:
from PySide6 import QtWidgets
from sd.api.qtforpythonuimgrwrapper import QtForPythonUIMgrWrapper
box = QtWidgets.QMessageBox(QtForPythonUIMgrWrapper(app.getUIMgr()).getMainWindow())
box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
box.setWindowTitle('Nothing was built')
box.setText(headline)
box.setInformativeText(detail)
box.exec()
except Exception:
pass
def resources_dir(app):
"""Where the shipped .sbs packages live. Asking the application beats hardcoding an install
path, but which of these the answer points at is not worth guessing -- try them in turn."""
resources = app.getPath(SDApplicationPath.DefaultResourcesDir)
install = app.getPath(SDApplicationPath.InstallationDir)
return [
os.path.join(resources, 'packages'),
resources,
os.path.join(install, 'resources', 'packages'),
]
def find_resource(app, package_name, identifier, explicit_path=None):
"""Library nodes are graphs inside .sbs packages and no definition manager lists them.
Match on identifier, never on the resource url -- its ?dependency=NNNN is a per-session uid.
explicit_path is set for a graph the project itself owns: it sits in the project's alias
folder rather than the install. This script is regenerated per machine, so an absolute
path here is written fresh each build rather than committed."""
package_mgr = app.getPackageMgr()
filename = package_name + '.sbs'
for package in package_mgr.getPackages():
path = (package.getFilePath() or '').replace('\\', '/')
if path.endswith('/' + filename):
for resource in package.getChildrenResources(True):
if resource.getIdentifier() == identifier:
return resource
search = ([explicit_path] if explicit_path else [])
search += [os.path.join(directory, filename) for directory in resources_dir(app)]
for candidate in search:
if not os.path.isfile(candidate):
continue
package = package_mgr.loadUserPackage(candidate)
for resource in package.getChildrenResources(True):
if resource.getIdentifier() == identifier:
return resource
raise RuntimeError('cannot find {0} in {1}.sbs'.format(identifier, package_name))
def free_offset(graph):
"""Drop the new network clear of whatever is already in the graph."""
lowest = None
for node in graph.getNodes():
position = node.getPosition()
lowest = position.y if lowest is None else max(lowest, position.y)
return 0.0 if lowest is None else lowest + 192.0
def main():
app = sd.getContext().getSDApplication()
graph = app.getUIMgr().getCurrentGraph()
if graph is None:
refuse(app, 'No compositing graph is open.',
'These nodes go into whatever graph you are looking at, and there is nothing '
'to look at. Open the graph you want them in, then run again.')
return
offset = free_offset(graph)
nodes = {}
# brushed -- anisotropic_noise_v2
nodes['brushed'] = graph.newInstanceNode(find_resource(app, 'noise_anisotropic_noise_v2', 'anisotropic_noise_v2'))
nodes['brushed'].setPosition(float2(0.0, offset + 0.0))
nodes['brushed'].setInputPropertyValueFromId('X_Amount', SDValueInt.sNew(4))
nodes['brushed'].setInputPropertyValueFromId('Y_Amount', SDValueInt.sNew(64))
nodes['brushed'].setInputPropertyValueFromId('disorder', SDValueFloat.sNew(0.12))
nodes['brushed'].setInputPropertyValueFromId('smoothness', SDValueFloat.sNew(1.5))
# panels -- tile_generator
nodes['panels'] = graph.newInstanceNode(find_resource(app, 'pattern_tile_generator', 'tile_generator'))
nodes['panels'].setPosition(float2(0.0, offset + 144.0))
nodes['panels'].setInputPropertyValueFromId('x_amount', SDValueInt.sNew(2))
nodes['panels'].setInputPropertyValueFromId('y_amount', SDValueInt.sNew(3))
nodes['panels'].setInputPropertyValueFromId('scale', SDValueFloat.sNew(0.97))
# plate -- bevel
nodes['plate'] = graph.newInstanceNode(find_resource(app, 'bevel', 'bevel'))
nodes['plate'].setPosition(float2(192.0, offset + 360.0))
nodes['plate'].setInputPropertyValueFromId('distance', SDValueFloat.sNew(0.01))
nodes['plate'].setInputPropertyValueFromId('smoothing', SDValueFloat.sNew(4.0))
nodes['plate'].setInputPropertyValueFromId('normal_intensity', SDValueFloat.sNew(1.0))
# seams -- height_blend
nodes['seams'] = graph.newInstanceNode(find_resource(app, 'height_blend', 'height_blend'))
nodes['seams'].setPosition(float2(384.0, offset + 360.0))
nodes['seams'].setInputPropertyValueFromId('height_position', SDValueFloat.sNew(0.5))
nodes['seams'].setInputPropertyValueFromId('contrast', SDValueFloat.sNew(0.4))
nodes['seams'].setInputPropertyValueFromId('opacity', SDValueFloat.sNew(0.65))
# scratch -- scratches_generator
nodes['scratch'] = graph.newInstanceNode(find_resource(app, 'pattern_scratches_generator', 'scratches_generator'))
nodes['scratch'].setPosition(float2(0.0, offset + 288.0))
nodes['scratch'].setInputPropertyValueFromId('spline_number', SDValueInt.sNew(48))
nodes['scratch'].setInputPropertyValueFromId('spline_width', SDValueFloat.sNew(0.1))
nodes['scratch'].setInputPropertyValueFromId('spline_distortion', SDValueFloat.sNew(0.3))
# carve -- blend
nodes['carve'] = graph.newNode('sbs::compositing::blend')
nodes['carve'].setPosition(float2(576.0, offset + 360.0))
nodes['carve'].setInputPropertyValueFromId('blendingmode', SDValueEnum.sFromValue('sbs::compositing::blendingmode', 2))
nodes['carve'].setInputPropertyValueFromId('opacitymult', SDValueFloat.sNew(0.3))
# erode -- slope_blur_grayscale_2
nodes['erode'] = graph.newInstanceNode(find_resource(app, 'slope_blur', 'slope_blur_grayscale_2'))
nodes['erode'].setPosition(float2(768.0, offset + 360.0))
nodes['erode'].setInputPropertyValueFromId('Samples', SDValueInt.sNew(16))
nodes['erode'].setInputPropertyValueFromId('Intensity', SDValueFloat.sNew(0.35))
# tonormal -- normal
nodes['tonormal'] = graph.newNode('sbs::compositing::normal')
nodes['tonormal'].setPosition(float2(960.0, offset + 360.0))
nodes['tonormal'].setInputPropertyValueFromId('intensity', SDValueFloat.sNew(6.0))
# height -- output
nodes['height'] = graph.newNode('sbs::compositing::output')
nodes['height'].setPosition(float2(960.0, offset + 504.0))
nodes['height'].setAnnotationPropertyValueFromId('identifier', SDValueString.sNew('height'))
usages = SDValueArray.sNew(SDTypeUsage.sNew(), 0)
usages.pushBack(SDValueUsage.sNew(SDUsage.sNew('height', 'RGBA', '')))
nodes['height'].setAnnotationPropertyValueFromId('usages', usages)
# normal -- output
nodes['normal'] = graph.newNode('sbs::compositing::output')
nodes['normal'].setPosition(float2(1152.0, offset + 432.0))
nodes['normal'].setAnnotationPropertyValueFromId('identifier', SDValueString.sNew('normal'))
usages = SDValueArray.sNew(SDTypeUsage.sNew(), 0)
usages.pushBack(SDValueUsage.sNew(SDUsage.sNew('normal', 'RGBA', '')))
nodes['normal'].setAnnotationPropertyValueFromId('usages', usages)
# curv -- curvature_smooth_v2
nodes['curv'] = graph.newInstanceNode(find_resource(app, 'curvature_smooth_v2', 'curvature_smooth_v2'))
nodes['curv'].setPosition(float2(1152.0, offset + 288.0))
nodes['curv'].setInputPropertyValueFromId('radius', SDValueFloat.sNew(1.5))
# edges -- histogram_scan
nodes['edges'] = graph.newInstanceNode(find_resource(app, 'histogram_scan', 'histogram_scan'))
nodes['edges'].setPosition(float2(1344.0, offset + 360.0))
nodes['edges'].setInputPropertyValueFromId('Position', SDValueFloat.sNew(0.62))
nodes['edges'].setInputPropertyValueFromId('Contrast', SDValueFloat.sNew(0.8))
# grunge -- grunge_scratches_rough
nodes['grunge'] = graph.newInstanceNode(find_resource(app, 'grunge_scratches_rough', 'grunge_scratches_rough'))
nodes['grunge'].setPosition(float2(0.0, offset + 432.0))
nodes['grunge'].setInputPropertyValueFromId('scratch_quantity', SDValueFloat.sNew(0.45))
nodes['grunge'].setInputPropertyValueFromId('Contrast', SDValueFloat.sNew(0.3))
# chip -- blend
nodes['chip'] = graph.newNode('sbs::compositing::blend')
nodes['chip'].setPosition(float2(1536.0, offset + 360.0))
nodes['chip'].setInputPropertyValueFromId('blendingmode', SDValueEnum.sFromValue('sbs::compositing::blendingmode', 5))
nodes['chip'].setInputPropertyValueFromId('opacitymult', SDValueFloat.sNew(0.75))
# paint -- uniform
nodes['paint'] = graph.newNode('sbs::compositing::uniform')
nodes['paint'].setPosition(float2(0.0, offset + 576.0))
nodes['paint'].setInputPropertyValueFromId('colorswitch', SDValueBool.sNew(True))
nodes['paint'].setInputPropertyValueFromId('outputcolor', SDValueColorRGBA.sNew(ColorRGBA(0.09, 0.25, 0.28, 1.0)))
# steel -- uniform
nodes['steel'] = graph.newNode('sbs::compositing::uniform')
nodes['steel'].setPosition(float2(0.0, offset + 720.0))
nodes['steel'].setInputPropertyValueFromId('colorswitch', SDValueBool.sNew(True))
nodes['steel'].setInputPropertyValueFromId('outputcolor', SDValueColorRGBA.sNew(ColorRGBA(0.44, 0.42, 0.4, 1.0)))
# albedo -- blend
nodes['albedo'] = graph.newNode('sbs::compositing::blend')
nodes['albedo'].setPosition(float2(1728.0, offset + 288.0))
# basecolor -- output
nodes['basecolor'] = graph.newNode('sbs::compositing::output')
nodes['basecolor'].setPosition(float2(1920.0, offset + 288.0))
nodes['basecolor'].setAnnotationPropertyValueFromId('identifier', SDValueString.sNew('basecolor'))
usages = SDValueArray.sNew(SDTypeUsage.sNew(), 0)
usages.pushBack(SDValueUsage.sNew(SDUsage.sNew('baseColor', 'RGBA', '')))
nodes['basecolor'].setAnnotationPropertyValueFromId('usages', usages)
# roughbase -- levels
nodes['roughbase'] = graph.newNode('sbs::compositing::levels')
nodes['roughbase'].setPosition(float2(960.0, offset + 216.0))
nodes['roughbase'].setInputPropertyValueFromId('levelinlow', SDValueFloat4.sNew(float4(0.3, 0.3, 0.3, 0.0)))
nodes['roughbase'].setInputPropertyValueFromId('levelinhigh', SDValueFloat4.sNew(float4(0.75, 0.75, 0.75, 1.0)))
# roughmix -- blend
nodes['roughmix'] = graph.newNode('sbs::compositing::blend')
nodes['roughmix'].setPosition(float2(1728.0, offset + 432.0))
nodes['roughmix'].setInputPropertyValueFromId('opacitymult', SDValueFloat.sNew(0.9))
# roughness -- output
nodes['roughness'] = graph.newNode('sbs::compositing::output')
nodes['roughness'].setPosition(float2(1920.0, offset + 432.0))
nodes['roughness'].setAnnotationPropertyValueFromId('identifier', SDValueString.sNew('roughness'))
usages = SDValueArray.sNew(SDTypeUsage.sNew(), 0)
usages.pushBack(SDValueUsage.sNew(SDUsage.sNew('roughness', 'RGBA', '')))
nodes['roughness'].setAnnotationPropertyValueFromId('usages', usages)
nodes['panels'].newPropertyConnectionFromId('output', nodes['plate'], 'input')
nodes['plate'].newPropertyConnectionFromId('height', nodes['seams'], 'height_bottom')
nodes['brushed'].newPropertyConnectionFromId('output', nodes['seams'], 'height_top')
nodes['seams'].newPropertyConnectionFromId('blended_height', nodes['carve'], 'destination')
nodes['scratch'].newPropertyConnectionFromId('output', nodes['carve'], 'source')
nodes['carve'].newPropertyConnectionFromId('unique_filter_output', nodes['erode'], 'Source')
nodes['scratch'].newPropertyConnectionFromId('output', nodes['erode'], 'Effect')
nodes['erode'].newPropertyConnectionFromId('Slope_Blur', nodes['height'], 'inputNodeOutput')
nodes['erode'].newPropertyConnectionFromId('Slope_Blur', nodes['tonormal'], 'input1')
nodes['tonormal'].newPropertyConnectionFromId('unique_filter_output', nodes['normal'], 'inputNodeOutput')
nodes['tonormal'].newPropertyConnectionFromId('unique_filter_output', nodes['curv'], 'input')
nodes['curv'].newPropertyConnectionFromId('curvature', nodes['edges'], 'Input_1')
nodes['edges'].newPropertyConnectionFromId('Output', nodes['chip'], 'destination')
nodes['grunge'].newPropertyConnectionFromId('output', nodes['chip'], 'source')
nodes['paint'].newPropertyConnectionFromId('unique_filter_output', nodes['albedo'], 'destination')
nodes['steel'].newPropertyConnectionFromId('unique_filter_output', nodes['albedo'], 'source')
nodes['chip'].newPropertyConnectionFromId('unique_filter_output', nodes['albedo'], 'opacity')
nodes['albedo'].newPropertyConnectionFromId('unique_filter_output', nodes['basecolor'], 'inputNodeOutput')
nodes['erode'].newPropertyConnectionFromId('Slope_Blur', nodes['roughbase'], 'input1')
nodes['roughbase'].newPropertyConnectionFromId('unique_filter_output', nodes['roughmix'], 'destination')
nodes['grunge'].newPropertyConnectionFromId('output', nodes['roughmix'], 'source')
nodes['chip'].newPropertyConnectionFromId('unique_filter_output', nodes['roughmix'], 'opacity')
nodes['roughmix'].newPropertyConnectionFromId('unique_filter_output', nodes['roughness'], 'inputNodeOutput')
print('added {0} nodes and {1} wires'.format(21, 23))
main()
Notes
Pin colours are this page’s, not Designer’s — the canvas palette lives in the application binary, not in any readable resource. They separate what a pin carries: a colour texture, a grayscale one, either, or a plain number.
Built-in filters accept whichever of colour or grayscale the graph is already carrying, so their pins are drawn as either rather than guessed at.