SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
developer_panel: visualizers in a tabbed multiparm
The per-visualizer tabs were spare parms appended to each instance by
an Add button, with a Python module that reindexed them on removal and
a folder-index lookup to relabel tabs. They are now instances of a
tabbed multiparm; every instance parm routes to one sync() callback
that pushes the instance onto its visualizer, and the block's own
callback rebuilds the set when instances are added or removed. A
Rebuild button recreates the visualizers from the parms, and
adopt_visualizers() fills the multiparm from the visualizers an old
instance already carries, since Houdini persists those itself.
The Attribute fields now list the input's point attributes instead of
a fixed mag0..dir3 menu. Dead ramp-preset code pointing at
~/src/developer/scripts is gone, as is the visualizer rename:
ViewportVisualizer.setName() segfaulted Houdini 22.0.429 from a
callback, and the name is cosmetic.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
otls/sop_lsgalante.developer_panel.1.0.hdalc | Bin 24039 -> 16795 bytes
tools/check.py | 31 +++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/otls/sop_lsgalante.developer_panel.1.0.hdalc b/otls/sop_lsgalante.developer_panel.1.0.hdalc
index bbde53c..2d51194 100755
Binary files a/otls/sop_lsgalante.developer_panel.1.0.hdalc and b/otls/sop_lsgalante.developer_panel.1.0.hdalc differ
diff --git a/tools/check.py b/tools/check.py
index 618ec9e..7af4c1b 100644
--- a/tools/check.py
+++ b/tools/check.py
@@ -621,6 +621,36 @@ def check_open_recent():
check("recentHipFiles() only lists existing files", recent_files_exist)
+def check_developer_panel():
+ """The Developer Panel HDA's visualizers live in a tabbed multiparm
+ whose instance parms all route to one sync() callback."""
+ print("developer panel")
+ import os
+ path = ROOT / "otls" / "sop_lsgalante.developer_panel.1.0.hdalc"
+
+ def multiparm_interface():
+ hou.hda.installFile(str(path))
+ d = [x for x in hou.hda.definitionsInFile(str(path))][0]
+ ptg = d.parmTemplateGroup()
+ block = ptg.find("visualizers")
+ assert isinstance(block, hou.FolderParmTemplate), "no visualizers folder"
+ assert block.folderType() == hou.folderType.TabbedMultiparmBlock, block.folderType()
+ names = [pt.name() for pt in block.parmTemplates()]
+ for expected in ("attr#", "type#", "coloring#", "coloring_attr#", "bounds#", "ramp#", "color#", "len#", "state#"):
+ assert expected in names, f"{expected} missing from {names}"
+ for pt in block.parmTemplates():
+ assert pt.scriptCallback() == "hou.phm().sync(kwargs)", (pt.name(), pt.scriptCallback())
+ assert block.scriptCallback() == "hou.phm().count_changed(kwargs)"
+ module = d.sections()["PythonModule"].contents()
+ compile(module, "PythonModule", "exec")
+ for fn in ("sync", "rebuild", "count_changed", "attr_menu", "adopt_visualizers"):
+ assert f"def {fn}(" in module, fn
+ assert ".setName(" not in module, "setName segfaults Houdini 22.0.429 from a callback"
+ return f"{len(names)} instance parms, module compiles"
+
+ check("tabbed multiparm, one sync callback", multiparm_interface)
+
+
def check_status_circle():
"""The status circle's menu is built from the main-menu XML files with
everything Python cannot run filtered out."""
@@ -1775,6 +1805,7 @@ def main():
check_split_handles()
check_nodegraph_hooks()
check_open_recent()
+ check_developer_panel()
check_status_circle()
check_geometry()
check_node_ops()