SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
hc: add Reset Node Colors, the way back into the setting
Set Node Colors drops the hc_custom_color tag, so a node colored by hand
is never touched by updateNodeColors() again -- which was a one-way door:
nothing could put the node back under the configured default.
Reset Node Colors restores the default color on the selection and
re-records the tag, so the node follows the setting once more. It takes
the whole selection as one undo, which Set Node Colors still does not.
tools/check.py drives the round trip -- opt a node out, confirm the next
pass leaves it alone, opt it back in, confirm the pass maintains it.
Co-Authored-By: Claude Opus 5 <[email protected]>
python3.13libs/hc/hcnetworkeditor.py | 25 +++++++++++++++++++++++++
tools/check.py | 30 ++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/python3.13libs/hc/hcnetworkeditor.py b/python3.13libs/hc/hcnetworkeditor.py
index 0bab9fd..fbab051 100644
--- a/python3.13libs/hc/hcnetworkeditor.py
+++ b/python3.13libs/hc/hcnetworkeditor.py
@@ -1245,6 +1245,31 @@ class HCNetworkEditor(HCPathTab):
# would otherwise revert it to the default on the next hip load.
node.destroyUserData("hc_custom_color", must_exist=False)
+ @command("Reset Node Colors")
+ def resetNodeColors(self):
+ """Put selected nodes back under the configured default color.
+
+ Set Node Colors deliberately drops the `hc_custom_color` tag, so a node
+ colored by hand is never touched by updateNodeColors() again. This is
+ the way back: it restores the default color and re-records the tag, so
+ the node follows the setting once more. Without it, opting a node out
+ was a one-way door.
+ """
+ nodes = list(self.hou_tab.pwd().selectedChildren())
+ if not nodes:
+ hou.ui.setStatusMessage("No selected nodes to reset",
+ hou.severityType.Warning)
+ return
+ settings = HCSettings()
+ color = settings.nodeColor()
+ color_hex = settings.nodeColorHex()
+ # One undo for the whole selection, not one per node.
+ with hou.undos.group("Reset Node Colors"):
+ for node in nodes:
+ node.setColor(color)
+ node.setUserData("hc_custom_color", color_hex)
+ hou.ui.setStatusMessage(f"Reset {len(nodes)} node(s) to {color_hex}")
+
@command("Set Node Shapes")
def setNodeShapes(self):
shape = HCSettings().nodeShape()
diff --git a/tools/check.py b/tools/check.py
index fc99a9b..771c398 100644
--- a/tools/check.py
+++ b/tools/check.py
@@ -733,6 +733,36 @@ def check_node_colors():
check("updateNodeColors is idempotent", second_pass_writes_nothing)
+ def opting_out_is_reversible():
+ """Set Node Colors / Reset Node Colors, minus the UI.
+
+ Dropping the tag has to be undoable by hand, or coloring one node is a
+ one-way door out of the setting.
+ """
+ default = settings.nodeColor()
+ default_hex = settings.nodeColorHex()
+ geo = hou.node("/obj").createNode("geo")
+ node = geo.createNode("box")
+ session = blank(HCSession)
+
+ # Set Node Colors: an explicit color, tag dropped.
+ node.setColor(RED)
+ node.destroyUserData("hc_custom_color", must_exist=False)
+ session.updateNodeColors()
+ assert colorsMatch(node.color(), RED), "opting out did not stick"
+
+ # Reset Node Colors: default color, tag re-recorded.
+ node.setColor(default)
+ node.setUserData("hc_custom_color", default_hex)
+ session.updateNodeColors()
+ assert colorsMatch(node.color(), default), "reset node was not maintained"
+ assert node.userData("hc_custom_color") == default_hex, "reset lost the tag"
+
+ geo.destroy()
+ return "opt out, then opt back in"
+
+ check("resetNodeColors round trip", opting_out_is_reversible)
+
def main():
check_settings()