Scene Summary
Export a summary of your Houdini scene as JSON
About
This Python script exports a summary of your Houdini scene as a JSON file, including all nodes, parameters, and code snippets.
Usage
To run this script, open Houdini and go to Windows > Python Shell. Then enter:
exec(open(r"path\ o\script\scene_summary.py").read())
Replace path\to\script
with the folder path where you saved the script.
Script
# scene_summary.py — select HIP → load → write <hipname>_scene_summary.json next to it
import os, json, time, re
import hou
FILEY_HINTS = re.compile(r"(file|path|picture|output|cache)", re.I)
CODEY_HINTS = ("snippet", "vex", "code", "python", "script")
# ---------- JSON sanitizers for Houdini types ----------
def _ramp_to_dict(rp: hou.Ramp):
try:
n = rp.getKeyCount()
except Exception:
return {"error": "unreadable ramp"}
keys = []
for i in range(n):
try:
pos = rp.getKeyPosition(i)
val = rp.getKeyValue(i)
# val can be float or tuple-like (e.g., color ramps)
try:
val = list(val) # if vector-like
except TypeError:
pass
interp = str(rp.getKeyInterpolation(i))
keys.append({"pos": float(pos), "val": val, "interp": interp})
except Exception as e:
keys.append({"error": str(e)})
return {"keys": keys}
def _sanitize(obj):
# Primitive JSON types pass through
if obj is None or isinstance(obj, (bool, int, float, str)):
return obj
# Common Houdini math types → lists
try:
import numbers
# hou.Vector2/3/4 behave like sequences
if hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, dict)):
return [_sanitize(x) for x in obj]
except Exception:
pass
# hou.Ramp
if isinstance(obj, hou.Ramp):
return _ramp_to_dict(obj)
# hou.Matrix, hou.Parm, hou.Node, etc. → string fallback
try:
return str(obj)
except Exception:
return "<unserializable>"
# ---------- helpers ----------
def _has_expression(parm):
try:
return bool(parm.isExpression())
except Exception:
try:
_ = parm.expression()
return True
except Exception:
return False
def _get_expression(parm):
try:
return parm.expression()
except Exception:
return None
def _maybe_ramp_dict(parm):
# Detect ramp parm and return a dict; else None
try:
pt = parm.parmTemplate()
if pt and pt.type() == hou.parmTemplateType.Ramp:
try:
rp = parm.evalAsRamp()
return _ramp_to_dict(rp)
except Exception:
# Some ramp parms can be read via parm.eval() and will be hou.Ramp
try:
v = parm.eval()
if isinstance(v, hou.Ramp):
return _ramp_to_dict(v)
except Exception:
pass
except Exception:
pass
return None
def _parm_value(p):
rec = {"name": p.name()}
# value (sanitized)
try:
v = p.eval()
rec["value"] = _sanitize(v)
except Exception as e:
rec["value_error"] = str(e)
# ramp (explicit)
rd = _maybe_ramp_dict(p)
if rd is not None:
rec["ramp"] = rd
# expression
if _has_expression(p):
rec["expression"] = _get_expression(p)
try:
rec["language"] = str(p.expressionLanguage())
except Exception:
pass
# time dependency
try:
rec["is_time_dependent"] = p.isTimeDependent()
except Exception:
pass
# keyframes
try:
kfs = []
for k in p.keyframes():
try:
kfs.append({"frame": k.frame(), "value": _sanitize(p.evalAtFrame(k.frame()))})
except Exception:
kfs.append({"frame": k.frame()})
if kfs:
rec["keyframes"] = kfs
except Exception:
pass
# heuristic file-like flag
if FILEY_HINTS.search(p.name().lower()):
rec["hint_file_param"] = True
return rec
def _collect_node(n):
# code-like parms (wrangles, python, scripts)
code = {}
for p in n.parms():
nm = p.name().lower()
if any(h in nm for h in CODEY_HINTS):
try:
v = p.eval()
if isinstance(v, str) and v.strip():
code[p.name()] = v
except Exception:
pass
return {
"path": n.path(),
"type": n.type().nameWithCategory(),
"inputs": [i.path() if i else None for i in n.inputs()],
"outputs": [o.path() for o in n.outputs()],
"parms": [_parm_value(p) for p in n.parms()],
"code": code or None,
}
# ---------- File dialog ----------
hip_path = hou.ui.selectFile(
title="Select HIP to summarize",
file_type=hou.fileType.Hip,
pattern="*.hip *.hiplc *.hipnc",
multiple_select=False
)
if not hip_path:
hou.ui.displayMessage("No file selected."); raise SystemExit
try:
hip_path = hou.text.expandString(hip_path) # new API
except Exception:
hip_path = hou.expandString(hip_path) # fallback (deprecated)
hip_path = os.path.normpath(hip_path)
if not os.path.isfile(hip_path):
hou.ui.displayMessage("Invalid file:\n" + hip_path); raise SystemExit
# ---------- Open HIP (replaces session) ----------
hou.hipFile.load(hip_path)
# ---------- Gather ----------
root = hou.node("/")
nodes = [root] + list(root.allSubChildren())
data = {
"hipfile": hou.hipFile.path(),
"houdini_version": hou.applicationVersionString(),
"saved_time": time.ctime(),
"node_count": len(nodes),
"nodes": [_collect_node(n) for n in nodes],
}
# ---------- Write JSON next to HIP ----------
out_path = os.path.splitext(hip_path)[0] + "_scene_summary.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
hou.ui.displayMessage("Scene summary written:\n" + out_path)
print("Wrote:", out_path)