Skip to content

Instantly share code, notes, and snippets.

@kickbase
Last active June 16, 2026 14:42
Show Gist options
  • Select an option

  • Save kickbase/ca7b9a38d6a1fec6e3d06e774ab70d94 to your computer and use it in GitHub Desktop.

Select an option

Save kickbase/ca7b9a38d6a1fec6e3d06e774ab70d94 to your computer and use it in GitHub Desktop.
[Houdini] [Python] Customize menus
<?xml version="1.0" encoding="UTF-8"?>
<menuDocument>
<menu>
<separatorItem/>
<scriptItem id="h.pane.wsheet.reset_node_color">
<label>Reset color to default</label>
<scriptCode><![CDATA[
import hou
with hou.undos.group("Reset color to default"):
for node in hou.selectedNodes():
node.setGenericFlag(hou.nodeFlag.ColorDefault, True)
]]></scriptCode>
</scriptItem>
<scriptItem id="h.pane.wsheet.focus_display_flag">
<label>Focus Display Flag</label>
<expression><![CDATA[
pane = hou.ui.paneTabUnderCursor()
if pane is None or pane.type() != hou.paneTabType.NetworkEditor:
return False
pwd = pane.pwd()
if pwd is None or pwd.childTypeCategory() != hou.sopNodeTypeCategory():
return False
return pwd.displayNode() is not None
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
clicked_node = kwargs.get("node")
def network_editor_for_pwd(pwd):
pane = hou.ui.paneTabUnderCursor()
if (pane is not None
and pane.type() == hou.paneTabType.NetworkEditor
and pane.pwd() == pwd):
return pane
for candidate in hou.ui.paneTabs():
if (candidate.type() == hou.paneTabType.NetworkEditor
and candidate.pwd() == pwd):
return candidate
return None
def focus_display_sop(pane, pwd):
display_node = pwd.displayNode()
if display_node is None:
return
original_selected = list(pwd.selectedItems())
original_current = pane.currentNode()
try:
display_node.setSelected(True, clear_all_selected=True)
pane.homeToSelection()
finally:
if original_selected:
for i, item in enumerate(original_selected):
item.setSelected(True, clear_all_selected=(i == 0))
else:
display_node.setSelected(False)
if original_current is not None and original_current.parent() == pwd:
pane.setCurrentNode(original_current)
def run():
if clicked_node is None:
return
pwd = clicked_node.parent()
if pwd is None or pwd.childTypeCategory() != hou.sopNodeTypeCategory():
return
if pwd.displayNode() is None:
return
pane = network_editor_for_pwd(pwd)
if pane is None:
return
focus_display_sop(pane, pwd)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
<separatorItem/>
<scriptItem id="h.pane.wsheet.sop_to_geo">
<label>SOP to GEO</label>
<expression><![CDATA[
return any(
n.type().category() == hou.sopNodeTypeCategory()
for n in hou.selectedNodes()
)
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
def obj_container(node):
"""Walk up to the /obj-level container (geo, subnet, etc.)."""
obj = hou.node("/obj")
container = node
while container is not None and container.parent() != obj:
container = container.parent()
if container is not None and container.parent() == obj:
return container
return None
def run():
sop_nodes = [
n for n in hou.selectedNodes()
if n.type().category() == hou.sopNodeTypeCategory()
]
if not sop_nodes:
hou.ui.displayMessage(
"Select one or more SOP nodes.",
severity=hou.severityType.Message,
)
return
obj = hou.node("/obj")
num = 0
last_geo = None
with hou.undos.group("SOP to GEO"):
for node in sop_nodes:
num += 1
geo_node = obj.createNode("geo", node.name(), run_init_scripts=False)
obj_merge = geo_node.createNode("object_merge", "object_merge1")
obj_merge.parm("xformtype").set(1) # Into This Object
obj_merge.parm("objpath1").set(node.path())
obj_merge.setDisplayFlag(True)
obj_merge.setRenderFlag(True)
container = obj_container(node)
if container is not None:
geo_node.setPosition(container.position() + hou.Vector2(0, -num))
else:
geo_node.setPosition(hou.Vector2(num, 0))
last_geo = geo_node
if last_geo is not None:
desktop = hou.ui.curDesktop()
pane = desktop.paneTabOfType(hou.paneTabType.NetworkEditor) if desktop else None
if pane is None:
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if pane is not None:
pane.setPwd(obj)
pane.setCurrentNode(last_geo)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
<scriptItem id="h.pane.wsheet.sop_to_rop">
<label>SOP to ROP</label>
<expression><![CDATA[
return any(
n.type().category() == hou.sopNodeTypeCategory()
for n in hou.selectedNodes()
)
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
PARM_PREFIX = "_reference"
def create_geometry_rop(node, position=None):
"""Create a geometry ROP and file SOP wired to export the given SOP."""
name = node.name()
if name.startswith("OUT_"):
name = name[4:]
out_ctx = hou.node("/out")
rop = out_ctx.createNode("geometry", name)
if position is not None:
rop.setPosition(position)
else:
rop.moveToGoodPosition()
parent = node.parent()
file_node = parent.createNode("file")
for out_node in node.outputs():
for i, in_node in enumerate(out_node.inputs()):
if in_node == node:
out_node.setInput(i, file_node)
rop.parm("soppath").set(node.path())
rop_output = '`chs("{}/sopoutput")`'.format(rop.path())
rop.parm("postrender").set("opupdate()")
rop.parm("lpostrender").set("hscript")
file_node.parm("file").set(rop_output)
file_node.parm("missingframe").set(1)
file_node.setInput(0, node)
file_node.setPosition(node.position() + hou.Vector2(0, -1))
return rop, file_node
def sync_reference_parms(node):
"""Refresh spare NodeReference parms for nodes that depend on node."""
group = node.parmTemplateGroup()
for entry in list(group.entries()):
if entry.name().startswith(PARM_PREFIX):
group.remove(entry)
node.setParmTemplateGroup(group)
for i, dep_node in enumerate(node.dependents()):
parm_name = "{}{}".format(PARM_PREFIX, i)
parm = hou.StringParmTemplate(
parm_name,
"",
1,
string_type=hou.stringParmType.NodeReference,
)
node.addSpareParmTuple(parm)
node.parm(parm_name).set(dep_node.path())
def run():
sop_nodes = [
n for n in hou.selectedNodes()
if n.type().category() == hou.sopNodeTypeCategory()
]
if not sop_nodes:
hou.ui.displayMessage(
"Select one or more SOP nodes.",
severity=hou.severityType.Message,
)
return
created = []
last_rop_pos = None
with hou.undos.group("SOP to ROP"):
for node in sop_nodes:
position = None
if last_rop_pos is not None:
position = last_rop_pos + hou.Vector2(0, -1)
rop, file_node = create_geometry_rop(node, position=position)
last_rop_pos = rop.position()
sync_reference_parms(node)
created.append((rop, file_node))
if not created:
return
if len(created) == 1:
rop, file_node = created[0]
msg = 'Created "{}" and "{}"'.format(rop.path(), file_node.path())
else:
msg = "Created {} geometry ROP(s).".format(len(created))
hou.ui.setStatusMessage(msg, severity=hou.severityType.ImportantMessage)
last_rop = created[-1][0]
desktop = hou.ui.curDesktop()
pane = desktop.paneTabOfType(hou.paneTabType.NetworkEditor) if desktop else None
if pane is None:
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if pane is not None:
pane.setPwd(hou.node("/out"))
pane.setCurrentNode(last_rop)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
<scriptItem id="h.pane.wsheet.sop_to_lop">
<label>SOP to LOP</label>
<expression><![CDATA[
return any(
n.type().category() == hou.sopNodeTypeCategory()
for n in hou.selectedNodes()
)
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
PARM_PREFIX = "_reference"
def sanitize_node_name(name):
# Houdini node names are fairly permissive, but spaces can cause confusion.
return name.replace(" ", "_")
def create_sop_import(sop_node, position=None):
stage = hou.node("/stage")
if stage is None:
raise RuntimeError('Cannot find "/stage".')
base = sop_node.name()
if base.startswith("OUT_"):
base = base[4:]
base = sanitize_node_name(base)
desired_name = "sopimport_{}".format(base)
name = desired_name
if stage.node(name) is not None:
i = 1
while stage.node("{}_{}".format(name, i)) is not None:
i += 1
name = "{}_{}".format(name, i)
sop_import = stage.createNode("sopimport", name)
sop_import.parm("soppath").set(sop_node.path())
if position is not None:
sop_import.setPosition(position)
else:
sop_import.moveToGoodPosition()
# Mirror the original shelf tool behavior:
# update _reference* spare parms for nodes that depend on the SOP.
group = sop_node.parmTemplateGroup()
for entry in list(group.entries()):
if entry.name().startswith(PARM_PREFIX):
group.remove(entry)
sop_node.setParmTemplateGroup(group)
for i, dep_node in enumerate(sop_node.dependents()):
parm_name = "{}{}".format(PARM_PREFIX, i)
parm = hou.StringParmTemplate(
parm_name,
"",
1,
string_type=hou.stringParmType.NodeReference,
)
sop_node.addSpareParmTuple(parm)
sop_node.parm(parm_name).set(dep_node.path())
return sop_import
def run():
sop_nodes = [
n for n in hou.selectedNodes()
if n.type().category() == hou.sopNodeTypeCategory()
]
if not sop_nodes:
hou.ui.displayMessage(
"Select one or more SOP nodes.",
severity=hou.severityType.Message,
)
return
stage = hou.node("/stage")
if stage is None:
hou.ui.displayMessage(
"Cannot find /stage.",
severity=hou.severityType.Error,
)
return
created = []
errors = []
last_position = None
with hou.undos.group("SOP to LOP"):
for node in sop_nodes:
try:
position = None
if last_position is not None:
position = last_position + hou.Vector2(0, -1)
sop_import = create_sop_import(node, position=position)
last_position = sop_import.position()
created.append(sop_import)
except Exception as e:
errors.append((node.path(), str(e)))
if created:
last_import = created[-1]
if len(created) == 1:
hou.ui.setStatusMessage(
'Created "{}".'.format(last_import.path()),
severity=hou.severityType.ImportantMessage,
)
else:
hou.ui.setStatusMessage(
"Created {} SOP import(s).".format(len(created)),
severity=hou.severityType.ImportantMessage,
)
# Switch the network editor to /stage so the user lands in LOP context.
desktop = hou.ui.curDesktop()
pane = desktop.paneTabOfType(hou.paneTabType.NetworkEditor) if desktop else None
if pane is None:
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if pane is not None:
pane.setPwd(stage)
pane.setCurrentNode(last_import)
pane.homeToSelection()
if errors:
max_show = 5
lines = [
"SOP to LOP completed with errors:"
]
for p, err in errors[:max_show]:
lines.append("- {}: {}".format(p, err))
if len(errors) > max_show:
lines.append("- ... ({} more)".format(len(errors) - max_show))
hou.ui.displayMessage("\n".join(lines), severity=hou.severityType.Error)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
<separatorItem/>
<scriptItem id="h.pane.wsheet.karma_fetch">
<label>Karma Fetch</label>
<expression><![CDATA[
return any(
n.type().category() == hou.ropNodeTypeCategory()
and n.type().name() == "karma"
for n in hou.selectedNodes()
)
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
TRANGE_LABELS = [
"Render Current Frame",
"Render Frame Range",
"Render Frame Range Only(Strict)",
]
FETCH_COLOR = hou.Color(0.976, 0.78, 0.263)
def add_fetch_parms(node):
group = node.parmTemplateGroup()
parm_trange = hou.MenuParmTemplate(
"trange",
"Valid Frame Range",
["off", "normal", "on"],
menu_labels=TRANGE_LABELS,
default_value=0,
)
parm_f = hou.FloatParmTemplate(
"f",
"Start/End/Inc",
3,
disable_when="{trange == off}",
naming_scheme=hou.parmNamingScheme.Base1,
)
parm_output = hou.StringParmTemplate(
"ropoutput",
"Output File",
1,
string_type=hou.stringParmType.FileReference,
)
group.insertAfter("source", parm_output)
group.insertAfter("source", parm_f)
group.insertAfter("source", parm_trange)
node.setParmTemplateGroup(group)
def find_promoted_parm(karma_node, name):
"""Return a parm only from the Karma ROP's own interface (not locked internals)."""
return karma_node.parm(name)
def find_promoted_parm_tuple(karma_node, name):
return karma_node.parmTuple(name)
def safe_set_expression(parm, expression):
if parm is None:
return False, "missing"
if parm.isLocked():
return False, "locked"
try:
parm.setExpression(expression)
return True, None
except hou.PermissionError:
return False, "permission"
except hou.OperationFailed as err:
return False, str(err)
def safe_set(parm, value):
if parm is None:
return False, "missing"
if parm.isLocked():
return False, "locked"
try:
parm.set(value)
return True, None
except hou.PermissionError:
return False, "permission"
except hou.OperationFailed as err:
return False, str(err)
def link_karma_to_fetch(karma_node, fetch_path):
"""Link promoted Karma ROP parms to the fetch ROP."""
linked = []
missing = []
denied = []
def ch(suffix):
return 'ch("{}")'.format(fetch_path + "/" + suffix)
def chs(suffix):
return 'chs("{}")'.format(fetch_path + "/" + suffix)
def record(name, ok, reason):
if ok:
linked.append(name)
elif reason == "missing":
missing.append(name)
else:
denied.append("{} ({})".format(name, reason))
trange = find_promoted_parm(karma_node, "trange")
ok, reason = safe_set_expression(trange, ch("trange"))
record("trange", ok, reason)
f1 = find_promoted_parm(karma_node, "f1")
f2 = find_promoted_parm(karma_node, "f2")
f3 = find_promoted_parm(karma_node, "f3")
if f1 is not None and f2 is not None and f3 is not None:
ok1, r1 = safe_set_expression(f1, ch("f1"))
ok2, r2 = safe_set_expression(f2, ch("f2"))
ok3, r3 = safe_set_expression(f3, ch("f3"))
if ok1 and ok2 and ok3:
linked.extend(["f1", "f2", "f3"])
else:
for name, ok, reason in (
("f1", ok1, r1),
("f2", ok2, r2),
("f3", ok3, r3),
):
record(name, ok, reason)
else:
f_tuple = find_promoted_parm_tuple(karma_node, "f")
if f_tuple is not None and len(f_tuple) >= 3:
ok1, r1 = safe_set_expression(f_tuple[0], ch("f1"))
ok2, r2 = safe_set_expression(f_tuple[1], ch("f2"))
ok3, r3 = safe_set_expression(f_tuple[2], ch("f3"))
if ok1 and ok2 and ok3:
linked.append("f")
else:
for name, ok, reason in (
("f1", ok1, r1),
("f2", ok2, r2),
("f3", ok3, r3),
):
record(name, ok, reason)
else:
missing.append("f1/f2/f3")
output_linked = False
for name in (
"outputimage",
"picture",
"vm_picture",
"outputfile",
"soho_outputimage",
):
output_parm = find_promoted_parm(karma_node, name)
if output_parm is None:
continue
ok, reason = safe_set_expression(output_parm, chs("ropoutput"))
if ok:
linked.append(name)
output_linked = True
break
denied.append("{} ({})".format(name, reason))
if not output_linked and not any(
item.startswith("output") or item.startswith("picture")
for item in denied
):
missing.append("outputimage")
postrender = find_promoted_parm(karma_node, "postrender")
ok, reason = safe_set(postrender, "opupdate()")
record("postrender", ok, reason)
return linked, missing, denied
def create_karma_fetch(karma_node):
out_ctx = hou.node("/out")
fetch = out_ctx.createNode("fetch", "karma_{}".format(karma_node.name()))
fetch.setColor(FETCH_COLOR)
add_fetch_parms(fetch)
fetch.parm("f1").setExpression("$FSTART")
fetch.parm("f2").setExpression("$FEND")
fetch.parm("f3").set(1)
fetch.parm("source").set(karma_node.path())
fetch.parm("ropoutput").set("$HIP/render/$OS.$F4.exr")
fetch.setPosition(karma_node.position() + hou.Vector2(0, -1))
fetch_path = fetch.path()
linked, missing, denied = link_karma_to_fetch(karma_node, fetch_path)
return fetch, missing, denied
def run():
karma_nodes = [
n for n in hou.selectedNodes()
if n.type().category() == hou.ropNodeTypeCategory()
and n.type().name() == "karma"
]
if not karma_nodes:
hou.ui.displayMessage(
"Select one or more Karma ROP nodes.",
severity=hou.severityType.Message,
)
return
created = []
link_warnings = []
with hou.undos.group("Karma Fetch"):
for node in karma_nodes:
fetch, missing, denied = create_karma_fetch(node)
created.append(fetch)
if missing or denied:
link_warnings.append((node.path(), missing, denied))
if not created:
return
if len(created) == 1:
msg = 'Created "{}".'.format(created[0].path())
else:
msg = "Created {} fetch ROP(s).".format(len(created))
hou.ui.setStatusMessage(msg, severity=hou.severityType.ImportantMessage)
last_fetch = created[-1]
desktop = hou.ui.curDesktop()
pane = desktop.paneTabOfType(hou.paneTabType.NetworkEditor) if desktop else None
if pane is None:
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if pane is not None:
pane.setPwd(hou.node("/out"))
pane.setCurrentNode(last_fetch)
if link_warnings:
max_show = 5
lines = [
"Karma Fetch created fetch node(s), but some Karma parms could not be linked:"
]
for path, missing, denied in link_warnings[:max_show]:
parts = []
if missing:
parts.append("not found: {}".format(", ".join(missing)))
if denied:
parts.append("denied: {}".format(", ".join(denied)))
lines.append("- {} ({})".format(path, "; ".join(parts)))
if len(link_warnings) > max_show:
lines.append("- ... ({} more)".format(len(link_warnings) - max_show))
lines.append("")
lines.append(
"Edit frame range and output on the fetch ROP, then render the fetch node."
)
hou.ui.displayMessage("\n".join(lines), severity=hou.severityType.Warning)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
<scriptItem id="h.pane.wsheet.usd_fetch">
<label>USD Fetch</label>
<expression><![CDATA[
return any(
n.type().category() == hou.sopNodeTypeCategory()
for n in hou.selectedNodes()
)
]]></expression>
<scriptCode><![CDATA[
import hou
import hdefereval
TRANGE_LABELS = [
"Render Current Frame",
"Render Frame Range",
"Render Frame Range Only(Strict)",
]
USD_FETCH_COLOR = hou.Color(0.584, 0.776, 1)
def sanitize_node_name(name):
if name.startswith("OUT_"):
name = name[4:]
return name.replace(" ", "_")
def unique_node_name(parent, base_name):
name = base_name
if parent.node(name) is not None:
i = 1
while parent.node("{}_{}".format(base_name, i)) is not None:
i += 1
name = "{}_{}".format(base_name, i)
return name
def add_usd_fetch_parms(node):
group = node.parmTemplateGroup()
parm_ref = hou.StringParmTemplate(
"ref_node",
"reference node",
1,
string_type=hou.stringParmType.NodeReference,
)
parm_trange = hou.MenuParmTemplate(
"trange",
"Valid Frame Range",
["off", "normal", "on"],
menu_labels=TRANGE_LABELS,
default_value=0,
)
parm_f = hou.FloatParmTemplate(
"f",
"Start/End/Inc",
3,
disable_when="{trange == off}",
naming_scheme=hou.parmNamingScheme.Base1,
)
parm_output = hou.StringParmTemplate(
"ropoutput",
"Output File",
1,
string_type=hou.stringParmType.FileReference,
)
parm_fileperframe = hou.ToggleParmTemplate(
"fileperframe",
"Separate File Per Frame",
)
group.insertAfter("source", parm_fileperframe)
group.insertAfter("source", parm_output)
group.insertAfter("source", parm_f)
group.insertAfter("source", parm_trange)
group.insertAfter("source", parm_ref)
node.setParmTemplateGroup(group)
def safe_set_expression(parm, expression):
if parm is None:
return False, "missing"
if parm.isLocked():
return False, "locked"
try:
parm.setExpression(expression)
return True, None
except hou.PermissionError:
return False, "permission"
except hou.OperationFailed as err:
return False, str(err)
def safe_set(parm, value):
if parm is None:
return False, "missing"
if parm.isLocked():
return False, "locked"
try:
parm.set(value)
return True, None
except hou.PermissionError:
return False, "permission"
except hou.OperationFailed as err:
return False, str(err)
def usd_fetch_source_path(usdexport):
for rel_path in ("lopnet_EXPORT/USD_OUT", "USD_OUT", "lopnet/USD_OUT"):
child = usdexport.node(rel_path)
if child is not None:
return child.path()
return usdexport.path() + "/lopnet_EXPORT/USD_OUT"
def rewire_to_usdexport(sop_node, usdexport, skip_nodes=()):
skip = set(skip_nodes)
skip.add(usdexport)
for out_node in sop_node.outputs():
if out_node in skip:
continue
for i, in_node in enumerate(out_node.inputs()):
if in_node == sop_node:
out_node.setInput(i, usdexport)
def link_usdexport_to_fetch(usdexport, fetch_path):
linked = []
missing = []
denied = []
def ch(suffix):
return 'ch("{}")'.format(fetch_path + "/" + suffix)
def chs(suffix):
return 'chs("{}")'.format(fetch_path + "/" + suffix)
def record(name, ok, reason):
if ok:
linked.append(name)
elif reason == "missing":
missing.append(name)
else:
denied.append("{} ({})".format(name, reason))
for name, expr in (
("trange", ch("trange")),
("f1", ch("f1")),
("f2", ch("f2")),
("f3", ch("f3")),
("lopoutput", chs("ropoutput")),
("fileperframe", ch("fileperframe")),
):
ok, reason = safe_set_expression(usdexport.parm(name), expr)
record(name, ok, reason)
ok, reason = safe_set(usdexport.parm("postrender"), "opupdate()")
record("postrender", ok, reason)
return linked, missing, denied
def create_usd_fetch(sop_node, fetch_position=None, ref_position=None):
out_ctx = hou.node("/out")
stage = hou.node("/stage")
if out_ctx is None:
raise RuntimeError('Cannot find "/out".')
name = sanitize_node_name(sop_node.name())
parent = sop_node.parent()
usdexport = parent.createNode("usdexport", unique_node_name(parent, name))
rewire_to_usdexport(sop_node, usdexport)
usdexport.setInput(0, sop_node)
usdexport.setPosition(sop_node.position() + hou.Vector2(0, -1))
fetch = out_ctx.createNode("fetch", unique_node_name(out_ctx, name))
fetch.setColor(USD_FETCH_COLOR)
add_usd_fetch_parms(fetch)
fetch.parm("f1").setExpression("$FSTART")
fetch.parm("f2").setExpression("$FEND")
fetch.parm("f3").set(1)
fetch.parm("source").set(usd_fetch_source_path(usdexport))
fetch.parm("ref_node").set(usdexport.path())
fetch.parm("fileperframe").set(0)
fetch.parm("ropoutput").set("$CACHE/`$OS`/v001/`$OS`.usd")
if fetch_position is not None:
fetch.setPosition(fetch_position)
else:
fetch.moveToGoodPosition()
reference = None
if stage is not None:
reference = stage.createNode(
"reference",
unique_node_name(stage, name),
)
reference.parm("filepath1").set(
'`chs("{}/ropoutput")`'.format(fetch.path())
)
if ref_position is not None:
reference.setPosition(ref_position)
else:
reference.moveToGoodPosition()
linked, missing, denied = link_usdexport_to_fetch(usdexport, fetch.path())
safe_set(usdexport.parm("enable_attribs"), 1)
safe_set(usdexport.parm("enable_subsetgroups"), 1)
safe_set(usdexport.parm("subsetgroups"), "*")
usdimport = parent.createNode("usdimport")
usdimport.parm("filepath1").setExpression(
'chs("{}/ropoutput")'.format(fetch.path())
)
usdimport.setPosition(usdexport.position() + hou.Vector2(0, -1))
return {
"fetch": fetch,
"reference": reference,
"usdexport": usdexport,
"usdimport": usdimport,
"linked": linked,
"missing": missing,
"denied": denied,
}
def run():
sop_nodes = [
n for n in hou.selectedNodes()
if n.type().category() == hou.sopNodeTypeCategory()
]
if not sop_nodes:
hou.ui.displayMessage(
"Select one or more SOP nodes.",
severity=hou.severityType.Message,
)
return
created = []
link_warnings = []
errors = []
last_fetch_pos = None
last_ref_pos = None
with hou.undos.group("USD Fetch"):
for node in sop_nodes:
try:
fetch_position = None
if last_fetch_pos is not None:
fetch_position = last_fetch_pos + hou.Vector2(0, -1)
ref_position = None
if last_ref_pos is not None:
ref_position = last_ref_pos + hou.Vector2(0, -1)
result = create_usd_fetch(
node,
fetch_position=fetch_position,
ref_position=ref_position,
)
last_fetch_pos = result["fetch"].position()
if result["reference"] is not None:
last_ref_pos = result["reference"].position()
created.append(result)
if result["missing"] or result["denied"]:
link_warnings.append(
(node.path(), result["missing"], result["denied"])
)
except Exception as err:
errors.append((node.path(), str(err)))
if not created:
if errors:
hou.ui.displayMessage(
"\n".join("- {}: {}".format(p, e) for p, e in errors[:5]),
severity=hou.severityType.Error,
)
return
last = created[-1]
if len(created) == 1:
msg = 'Created "{}" and related nodes.'.format(last["fetch"].path())
else:
msg = "Created {} USD fetch setup(s).".format(len(created))
hou.ui.setStatusMessage(msg, severity=hou.severityType.ImportantMessage)
desktop = hou.ui.curDesktop()
pane = desktop.paneTabOfType(hou.paneTabType.NetworkEditor) if desktop else None
if pane is None:
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if pane is not None:
pane.setPwd(hou.node("/out"))
pane.setCurrentNode(last["fetch"])
if link_warnings:
max_show = 5
lines = [
"USD Fetch created nodes, but some usdexport parms could not be linked:"
]
for path, missing, denied in link_warnings[:max_show]:
parts = []
if missing:
parts.append("not found: {}".format(", ".join(missing)))
if denied:
parts.append("denied: {}".format(", ".join(denied)))
lines.append("- {} ({})".format(path, "; ".join(parts)))
if len(link_warnings) > max_show:
lines.append("- ... ({} more)".format(len(link_warnings) - max_show))
hou.ui.displayMessage("\n".join(lines), severity=hou.severityType.Warning)
if errors:
max_show = 5
lines = ["USD Fetch completed with errors:"]
for path, err in errors[:max_show]:
lines.append("- {}: {}".format(path, err))
if len(errors) > max_show:
lines.append("- ... ({} more)".format(len(errors) - max_show))
hou.ui.displayMessage("\n".join(lines), severity=hou.severityType.Error)
hdefereval.executeDeferred(run)
]]></scriptCode>
</scriptItem>
</menu>
</menuDocument>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment