Adobe Substances - techartorg/TAO-Wiki GitHub Wiki
Designer snippets
Update default_configuration.sbscfg projectfiles
A lot of the time we will need to add our own project files into designer - this can be run at the startup of designer to inject our own .sbsprj file path into the default so users don't have to.
from xml.etree import ElementTree as ET
def update_project_files(xml_path:str, expected_path:str):
tree = ET.parse(xml_path)
root = tree.getroot()
projectfiles = root.find(".//projects/projectfiles")
if projectfiles is None:
# Create projectfiles if it doesn't exist - this shouldn't happen unless something went very very wrong
projects = root.find(".//projects")
if projects is None:
projects = ET.SubElement(root, "projects")
projectfiles = ET.SubElement(projects, "projectfiles")
size_elem = ET.SubElement(projectfiles, "size")
size_elem.text = "0"
else:
size_elem = projectfiles.find("size")
if size_elem is None or not size_elem.text.isdigit():
return
size = int(size_elem.text)
elements = [e for e in projectfiles if e.tag.startswith("_")]
# Check if expected_path already exists
for elem in elements:
path_elem = elem.find("path")
if path_elem is not None and path_elem.text == expected_path:
return
# if we haven't found our path, add it
size += 1
size_elem.text = str(size)
# Create new element
new_element = ET.SubElement(projectfiles, f"_{size}")
new_element.set("prefix", "_")
path_elem = ET.SubElement(new_element, "path")
path_elem.text = expected_path
# Save changes
tree.write(xml_path, encoding="UTF-8", xml_declaration=True)
Find "Dead" Nodes
While there is a "Clean up" function in the GUI of designer there doesn't seem to be a api function call for it. This allows you to find things that should be either:
- Input with no connected output
- Output with no connected input
- Node with either no input or no output
- Because of this one, there are cases where it might not be a dead node but will be flagged as one due to the use of the node
import sd
from sd.api.sdproperty import SDPropertyCategory
def __is_source_node__(node):
input_props = node.getProperties(SDPropertyCategory.Input)
for prop in input_props:
if node.getPropertyConnections(prop):
return False
return True
def __has_real_connections__(node) -> tuple[bool, bool]:
# Check that the connection is actually connected to another node and not just a phantom
input = False
output = False
input_props = node.getProperties(SDPropertyCategory.Input)
for prop in input_props:
for conn in node.getPropertyConnections(prop):
if conn.getOutputPropertyNode():
input = True
break
output_props = node.getProperties(SDPropertyCategory.Output)
for prop in output_props:
for conn in node.getPropertyConnections(prop):
if conn.getInputPropertyNode():
output = True
return input, output
def __get_forward_reachable_nodes__(graph):
# Start from all source-like nodes (no inputs)
connected = set()
print(f"Node Count: {len(graph.getNodes())}")
for node in graph.getNodes():
input_props = node.getProperties(SDPropertyCategory.Input)
has_inputs = False
has_outputs = False
if node.getDefinition().getId() == "sbs::compositing::output":
# we only need to check inputs since this is an output node
for prop in input_props:
conns = node.getPropertyConnections(prop)
if conns.getSize() > 0:
has_inputs = True
break
if has_inputs:
connected.add(node)
else:
has_inputs, has_outputs = __has_real_connections__(node)
if (has_outputs and has_inputs) or (has_outputs and __is_source_node__(node)):
connected.add(node)
return connected
def find_unconnected_nodes():
graph = app.getUIMgr().getCurrentGraph()
print(f"Looking at Graph: {graph.getIdentifier()}")
all_nodes = list(graph.getNodes())
live_nodes = [x for x in __get_forward_reachable_nodes__(graph)]
live_ids = [x.getIdentifier() for x in live_nodes]
dead_nodes = [
(x, x.getIdentifier(), x.getDefinition().getLabel()) for x in all_nodes if x.getIdentifier() not in live_ids
]
return list(dead_nodes)