在3D内容生产的快节奏环境中,Houdini艺术家和技术TD们常常需要处理重复性高、复杂度大的任务。掌握Python脚本能力,就像获得了一把瑞士军刀——它能帮你批量创建节点、自动连接管线、快速修改参数,甚至实现自定义工具开发。本文将分享5个经过实战检验的脚本技巧,每个都附带可直接复用的代码片段,助你从"会写脚本"进阶到"高效生产"。
传统手动创建节点的方式在面对复杂场景时效率低下。通过Python脚本,我们可以实现节点批量化生成,并结合智能命名规则保持场景整洁。
python复制import hou
def create_nodes_with_naming(parent_path, node_types, prefix="auto"):
parent_node = hou.node(parent_path)
created_nodes = []
for i, node_type in enumerate(node_types):
# 自动生成带序号和类型的节点名
node_name = f"{prefix}_{node_type}_{i+1}"
new_node = parent_node.createNode(node_type, node_name)
created_nodes.append(new_node)
# 自动设置常见参数默认值
if node_type == "attribwrangle":
new_node.parm("snippet").set("v@P.y += sin(@Time);")
# 自动布局节点避免重叠
hou.ui.autoLayoutNodes(created_nodes)
return created_nodes
# 示例:在/obj/geo1下批量创建常用节点
node_types = ["box", "sphere", "attribwrangle", "null"]
create_nodes_with_naming("/obj/geo1", node_types, "demo")
关键优势:
提示:可以通过扩展
createNode方法的第二个参数实现更复杂的命名规则,如结合时间戳或场景上下文信息。
当需要同时修改多个节点的相同参数时,手动操作既耗时又容易出错。以下脚本展示如何精准定位并批量修改参数:
python复制def batch_update_parameters(root_path, parm_name, new_value, node_filter=None):
root_node = hou.node(root_path)
nodes_to_process = root_node.allSubChildren()
for node in nodes_to_process:
# 可选节点类型过滤
if node_filter and not node.type().name().startswith(node_filter):
continue
parm = node.parm(parm_name)
if parm:
# 智能判断参数类型并设置值
if parm.parmTemplate().type() == hou.parmTemplateType.Toggle:
parm.set(new_value != 0)
elif isinstance(new_value, (tuple, list)):
parm.set(new_value)
else:
parm.set(new_value)
# 自动设置关键帧(如果参数可动画)
if parm.isKeyframable():
parm.setKeyframe(hou.Keyframe(new_value))
# 示例:修改所有SOP节点下的"scale"参数为1.5
batch_update_parameters("/obj/geo1", "scale", 1.5, "Sop")
进阶技巧:
allSubChildren()递归获取所有子节点parmTemplate().type()判断参数类型,确保值设置安全复杂场景中节点间的连接关系往往错综复杂。这个脚本不仅能自动连接节点,还能分析依赖关系:
python复制def auto_connect_nodes(source_path, target_paths, input_index=0):
source_node = hou.node(source_path)
if not source_node:
raise ValueError(f"源节点不存在: {source_path}")
connections = []
for target_path in target_paths:
target_node = hou.node(target_path)
if not target_node:
print(f"警告:目标节点不存在 {target_path}")
continue
# 检查输入槽是否可用
if input_index >= target_node.inputs():
print(f"警告:{target_path} 没有输入槽 {input_index}")
continue
# 建立连接前检查循环依赖
if is_dependency_loop(source_node, target_node):
print(f"警告:检测到循环依赖,跳过 {source_path} -> {target_path}")
continue
target_node.setInput(input_index, source_node)
connections.append((source_node, target_node))
return connections
def is_dependency_loop(node_a, node_b):
# 检查node_a是否直接或间接依赖node_b
def check_depends_on(node, target):
if node == target:
return True
for input_node in node.inputs():
if input_node and check_depends_on(input_node, target):
return True
return False
return check_depends_on(node_a, node_b) or check_depends_on(node_b, node_a)
# 示例:将/obj/geo1/box1连接到多个节点
target_nodes = ["/obj/geo1/merge1", "/obj/geo1/attribvop1"]
auto_connect_nodes("/obj/geo1/box1", target_nodes)
安全机制:
将常用操作封装为自定义工具可以极大提升团队效率。以下脚本自动生成HDA(Houdini Digital Asset)并设置参数界面:
python复制def create_custom_tool(output_path, tool_name, parameters, script_callback=None):
# 创建基础几何节点作为工具容器
tool_node = hou.node("/obj").createNode("geo", tool_name)
# 转换为数字资产
hda_node = tool_node.createDigitalAsset(
name=tool_name,
save_to_hip_file=False,
hda_file_name=output_path
)
# 获取定义器以添加参数
definition = hda_node.type().definition()
parm_template_group = definition.parmTemplateGroup()
# 添加自定义参数
for parm_info in parameters:
parm_template = None
if parm_info["type"] == "float":
parm_template = hou.FloatParmTemplate(
name=parm_info["name"],
label=parm_info.get("label", parm_info["name"]),
num_components=1,
default_value=(parm_info.get("default", 0),)
)
elif parm_info["type"] == "toggle":
parm_template = hou.ToggleParmTemplate(
name=parm_info["name"],
label=parm_info.get("label", parm_info["name"]),
default_value=parm_info.get("default", False)
)
if parm_template:
# 设置参数范围(如果指定)
if "min" in parm_info and "max" in parm_info:
parm_template.setMinValue(parm_info["min"])
parm_template.setMaxValue(parm_info["max"])
parm_template_group.append(parm_template)
# 应用参数组
definition.setParmTemplateGroup(parm_template_group)
# 添加Python回调脚本(如果提供)
if script_callback:
definition.addScript(hou.scriptLanguage.Python, "OnCreated", script_callback)
definition.save()
return hda_node
# 示例:创建一个简单的散射工具
tool_params = [
{"name": "count", "type": "int", "label": "Instance Count", "default": 10, "min": 1, "max": 100},
{"name": "scale_variation", "type": "float", "label": "Scale Variation", "default": 0.2, "min": 0, "max": 1},
{"name": "randomize_rotation", "type": "toggle", "label": "Random Rotation", "default": True}
]
python_callback = """
import random
node = kwargs["node"]
count = node.parm("count").eval()
scale_var = node.parm("scale_variation").eval()
use_rotation = node.parm("randomize_rotation").eval()
# 创建点用于散射
geo = node.node("OUT").geometry()
for i in range(count):
pt = geo.createPoint()
pt.setPosition((random.uniform(-1,1), 0, random.uniform(-1,1)))
# 应用随机缩放
if scale_var > 0:
scale = 1 + random.uniform(-scale_var, scale_var)
pt.setAttribValue("scale", (scale, scale, scale))
# 应用随机旋转
if use_rotation:
rot = random.uniform(0, 360)
pt.setAttribValue("rot", (0, rot, 0))
"""
create_custom_tool("$HIP/scatter_tool.hda", "scatter_tool", tool_params, python_callback)
功能亮点:
大型场景中,了解资源使用情况至关重要。这个脚本分析场景并生成详细报告:
python复制def generate_scene_report(root_path="/obj", output_file=None):
root_node = hou.node(root_path)
if not root_node:
raise ValueError(f"无效的根路径: {root_path}")
report_data = {
"total_nodes": 0,
"node_types": {},
"parameter_stats": {},
"network_depths": {}
}
def analyze_node(node, depth=0):
report_data["total_nodes"] += 1
# 统计节点类型
node_type = node.type().name()
report_data["node_types"][node_type] = report_data["node_types"].get(node_type, 0) + 1
# 记录网络深度
report_data["network_depths"][node.path()] = depth
# 分析参数
for parm in node.parms():
parm_type = parm.parmTemplate().type().name()
report_data["parameter_stats"][parm_type] = report_data["parameter_stats"].get(parm_type, 0) + 1
# 递归分析子节点
for child in node.children():
analyze_node(child, depth + 1)
analyze_node(root_node)
# 生成报告文本
report_lines = [
f"Houdini场景分析报告 - {hou.hipFile.path()}",
f"生成时间: {hou.time()}",
"="*50,
f"总节点数: {report_data['total_nodes']}",
"\n节点类型统计:",
*[f"- {k}: {v}" for k, v in sorted(report_data['node_types'].items(), key=lambda x: -x[1])],
"\n参数类型统计:",
*[f"- {k}: {v}" for k, v in sorted(report_data['parameter_stats'].items(), key=lambda x: -x[1])],
"\n最深节点路径:",
*[f"- {path} (深度: {depth})" for path, depth in sorted(report_data['network_depths'].items(), key=lambda x: -x[1])][:5]
]
report_text = "\n".join(report_lines)
# 输出到文件或返回字符串
if output_file:
with open(output_file, "w") as f:
f.write(report_text)
else:
return report_text
# 示例:生成报告并保存到桌面
report = generate_scene_report()
print(report)
分析维度:
将这些脚本整合到日常工作中,你会发现Houdini生产流程变得前所未有的高效。真正的Python脚本高手不是记住所有API,而是培养将重复劳动转化为自动化解决方案的思维模式。