Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Script of a BezierSpline Houdini Digital Asset
# BezierSpline
# (Houdini Digital Asset Module)
# Shared under the terms of the MIT License
# Copyright (c) 2019 Elie Michel
# This is a wip, expect more docstring eventually
from __future__ import print_function
# Utils
def getPoint(coords, idx):
"""Extract point coordinates as a list of floats from a coord field"""
if type(coords) == str:
coords = coords.split()
if idx >= 0 and idx < len(coords):
pt = coords[idx].split(',')
p = []
for c in pt:
try:
c = float(c)
except ValueError as e:
c = 0
p.append(c)
return hou.Vector3(p)
else:
return None
def getHandleType(types, idx):
idx = int(idx/3)
if idx >= 0 and idx < len(types):
return types[idx]
else:
return 'f'
def getActiveTool(node):
parm = node.parm("active_tool")
return parm.menuItems()[parm.eval()]
# HDA Module
def syncCorrectedCoords(node):
coords = node.parm("coords").eval().split()
node.parm("corrected_coords").set(' '.join(coords[1:-1]))
def syncCurrentToCoords(node):
coords_parm = node.parm("coords")
coords = coords_parm.eval().split()
idx = node.parm("current_index").eval()
p = hou.Vector3([node.parm("current_pos"+c).eval() for c in 'xyz'])
t = getHandleType(node.parm("types").eval(), idx)
if idx >= 0 and idx < len(coords):
main_idx = int(idx/3)*3+1
before_p = getPoint(coords, main_idx-1)
main_p = getPoint(coords, main_idx)
after_p = getPoint(coords, main_idx+1)
if idx == main_idx:
delta = p - main_p
main_p = p
before_p = before_p + delta
after_p = after_p + delta
elif idx == main_idx - 1:
before_p = p
if t == 's':
after_p = 2 * main_p - before_p
elif t == 'a':
d = after_p.distanceTo(main_p)
after_p = main_p + d * (main_p - before_p).normalized()
elif idx == main_idx + 1:
after_p = p
if t == 's':
before_p = [2 * a - b for a, b in zip(main_p, after_p)]
elif t == 'a':
d = before_p.distanceTo(main_p)
before_p = main_p + d * (main_p - after_p).normalized()
coords[main_idx-1] = "{},{},{}".format(*before_p)
coords[main_idx+0] = "{},{},{}".format(*main_p)
coords[main_idx+1] = "{},{},{}".format(*after_p)
coords_parm.set(' '.join(coords))
syncCorrectedCoords(node)
def syncCoordsToCurrent(node):
coords = node.parm("coords").eval().split()
idx = node.parm("current_index").eval()
if idx >= 0 and idx < len(coords):
pt = coords[idx].split(',')
for i,c in enumerate("xyz"):
node.parm("current_pos"+c).set(pt[i])
def syncCurrentToTypes(node):
types_parm = node.parm("types")
types = types_parm.eval()
idx = int(node.parm("current_index").eval()/3)
if idx >= 0 and idx < len(types):
parm = node.parm("current_type")
t = parm.menuItems()[parm.eval()]
types = types[:idx] + t + types[idx+1:]
types_parm.set(types)
def syncTypesToCurrent(node):
types = node.parm("types").eval()
idx = int(node.parm("current_index").eval()/3)
if idx >= 0 and idx < len(types):
t = types[idx]
node.parm("current_type").set(t)
def onCurrentIndexChanged(node):
syncCoordsToCurrent(node)
syncTypesToCurrent(node)
def onActiveToolChanged(node):
pass
# Viewer State
class SplineState(object):
def __init__(self, state_name, scene_viewer):
self.state_name = state_name
self.scene_viewer = scene_viewer
def onMouseEvent(self, kwargs):
ui_event = kwargs["ui_event"]
node = kwargs["node"]
dev = ui_event.device()
reason = ui_event.reason()
coords_parm = node.parm("coords")
types_parm = node.parm("types")
corrected_coords_parm = node.parm("corrected_coords")
coords = coords_parm.eval().split()
types = types_parm.eval()
handles_parm = node.parm("handles")
if getActiveTool(node) not in ['add', 'all']:
return
if dev.isLeftButton():
origin, direction = ui_event.ray()
p = origin - (origin.y()/direction.y()) * direction
pt = "{},0,{}".format(p.x(), p.z())
if reason == hou.uiEventReason.Start or reason == hou.uiEventReason.Picked:
coords.append(pt)
coords.append(pt)
coords.append(pt)
types += 's'
elif coords:
coords[-1] = pt
c = hou.Vector3([float(x) for x in coords[-2].split(',')])
sp = 2 * c - p
spt = "{},0,{}".format(sp.x(), sp.z())
coords[-3] = spt
coords_parm.set(' '.join(coords))
types_parm.set(types)
syncCorrectedCoords(node)
def onHandleToState(self, kwargs):
node = kwargs["node"]
handle_name = kwargs["handle"]
parms = kwargs["parms"]
tool = getActiveTool(node)
if handle_name == "current_translate":
if tool in ['edit', 'all']:
for c in "xyz":
node.parm("current_pos"+c).set(parms["t"+c])
syncCurrentToCoords(node)
elif handle_name == "translate_points":
if tool in ['add', 'all']:
node.parm("coords").set(parms["pointlist"])
syncCorrectedCoords(node)
def onStateToHandle(self, kwargs):
node = kwargs["node"]
handle_name = kwargs["handle"]
parms = kwargs["parms"]
tool = getActiveTool(node)
if handle_name == "current_translate":
if tool in ['edit', 'all']:
for c in "xyz":
parms["t"+c] = node.parm("current_pos"+c).eval()
else:
parms["tx"] = 9999999.9 # hack deactivation
elif handle_name == "translate_points":
if tool in ['add', 'all']:
parms["pointlist"] = node.parm("coords").eval()
else:
parms["pointlist"] = ''
nodetype = kwargs['type']
def createViewerStateTemplate():
state_name = nodetype.name() + ".pystate"
label = nodetype.description()
category = nodetype.category()
template = hou.ViewerStateTemplate(state_name, label, category)
template.bindFactory(SplineState)
template.bindHandle("curvepoint", "translate_points")
template.bindHandle("translate", "current_translate")
return template
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment