Point Place Tool
環境:Houdini 20.0.751

ポイントを配置していくツール
Parameterの構成

Folder Type: Multiparm Block (tabs) Name: points Label: Points First Instanceを0に設定する
Name: position_# Label: Position Type: Float Vector 3
Name: id_# Label: ID Type: String

Viewer Stateのコード
#
# Point Placement Tool 20250321
# コリジョン上にポイントを配置するツール
#
import hou
import viewerstate.utils as su
from enum import Enum
import uuid
import math
class State(object):
MSG = "Select the polyline and press Ctrl to edit the points."
def __init__(self, state_name, scene_viewer):
self.state_name = state_name
self.scene_viewer = scene_viewer
# UIのテンプレート
HUD_TEMPLATE = {
"title": "Point Place", "desc": "tool", "icon": "SOP_subnet",
"rows": [
{"id": "add_act", "label": "Add/Select Point", "key": "LMB"},
{"id": "del_act", "label": "Delete Point", "key": "Del"},
{"id": "focus_act", "label": "Camera Focus", "key": "f"},
]
}
# テンプレートを使用してHUDを設定します。通常では、これはステートが有効な間に一回実行します。
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
self.viewport = None
self.pressed = False # マウス左ボタン押下フラグ
self.node = None # ノード
self.drag = False # ドラッグ中のフラグ
self.dragIndex = 0 # ドラッグ中のポイントインデックス
self.isPressedShift = False # SHIFTキーの押下判定フラグ
self.selected_point = -1 # 選択中のプリミティブ番号
self.intersect_point = -1 # 交差中のプリミティブ番号
self.colli_geometry = None # コリジョン用ジオメトリ
self.select_geometry = None # 結果のジオメトリ
self.drawableGeo = None # 入力されたポリラインのジオメトリ(表示用)
# ポイントとエッジの判定距離(ピクセル)
self.point_range = 16
self.edge_range = 8
# Construct a geometry drawable group
# ポイント
edit_point = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Point, "point",
params = {
"num_rings": 2,
"radius": 3,
"color1": (1.0,0.7,0.1,1.0),
"style" : hou.drawableGeometryPointStyle.LinearSquare}
)
self.guideEditPoint = hou.GeometryDrawableGroup("poly_edit")
self.guideEditPoint.addDrawable(edit_point)
# ポイント(選択中)
selected_point = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Point, "point",
params = {
"num_rings": 2,
"radius": 5,
"color1": (1.0,0.6,0.1,1.0),
"style" : hou.drawableGeometryPointStyle.LinearSquare}
)
self.guideSelectedPoint = hou.GeometryDrawableGroup("poly_selected_point")
self.guideSelectedPoint.addDrawable(selected_point)
# UIのMultiparmフォルダからプリミティブ数を取得する
def pointCount(self):
try:
multiparm = self.node.parm("points")
return multiparm.evalAsInt()
except:
return 0
# マウス左ボタンが押された時に呼ばれる処理
def start(self):
if not self.pressed:
self.scene_viewer.beginStateUndo("Add point")
self.pressed = True
# マウス左ボタンが放された時に呼ばれる処理
def finish(self):
# 前フレームで押下されていたら
if self.pressed:
self.scene_viewer.endStateUndo()
self.pressed = False
# ユーザーがStateに入ると呼び出される(エンターキーを押すと入る)
def onEnter(self, kwargs):
self.node = kwargs["node"]
if not self.node:
raise
self.scene_viewer.setPromptMessage( State.MSG )
# コリジョンのジオメトリ(input[1]にジオメトリが入力されている場合はジオメトリを設定する)
inputs = self.node.inputs()
if inputs and inputs[0]:
self.colli_geometry = inputs[0].geometry()
else:
self.colli_geometry = None
# ガイドのジオメトリ
hda_node = self.scene_viewer.currentNode()
hda_node.cook(force=True)
if hda_node:
self.select_geometry = hda_node.node('generate_points').geometry()
if inputs and inputs[0]:
#hda_node.node('geo_draw').cook(force=True) # 強制的にクック
self.drawableGeo = hou.SimpleDrawable(self.scene_viewer, hda_node.node('geo_draw').geometry(), "drawable_geometry")
self.drawableGeo.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
self.drawableGeo.enable(True)
self.drawableGeo.show(True)
self.guideEditPoint.show(True)
self.guideSelectedPoint.show(True)
# Stateが中断された時に呼び出される
def onInterrupt(self,kwargs):
self.finish()
#self.drawableGeo.show(False)
self.guideEditPoint.show(False)
self.guideSelectedPoint.show(False)
# Stateの中断が再開された時に呼び出される
def onResume(self, kwargs):
self.scene_viewer.setPromptMessage( State.MSG )
# 編集ポイントdrawbleの更新
def updateGuideEditPoint(self):
# UIのジオメトリを描画する
if self.pointCount() > 0:
poly_geo = hou.Geometry()
poly = poly_geo.createPolygon()
# ジオメトリから座標を取得する
poly_points = self.select_geometry.points()
for i in range(len(poly_points)):
parm_pos = poly_points[i].position()
point = poly_geo.createPoint()
point.setPosition(parm_pos)
poly.addVertex(point)
# update the drawable
self.guideEditPoint.setGeometry(poly_geo)
# 選択済みポイントdrawbleの更新
def updateGuideSelectedPoint(self):
poly_geo = hou.Geometry()
poly = poly_geo.createPolygon()
if self.selected_point != -1:
poly_points = self.select_geometry.points()
parm_pos = poly_points[self.selected_point].position()
point = poly_geo.createPoint()
point.setPosition(parm_pos)
poly.addVertex(point)
self.guideSelectedPoint.setGeometry(poly_geo)
#
# マウスポインタとポイントの交差判定(交差していたらインデックス番号を、していなかったら-1を返す)
#
def isIntersectPoints(self, origin, viewport, positions, radius):
closeIndex = -1
closeDist = 1000000
for i in range(len(positions)):
A = origin
P = positions[i]
dist = (viewport.mapToScreen(P) - viewport.mapToScreen(A)).lengthSquared()
if dist < pow(radius, 2) and dist < closeDist:
closeDist = dist
closeIndex = i
return closeIndex
# IDからMultiparmのインデックス番号を返す
def getMultiparmIndexFromId(self, id):
index = -1
for i in range(self.node.parm("points").evalAsInt()):
uuid = self.node.parm("id_%d" % i).eval()
#self.log(uuid)
if id == uuid:
index = i
break
return index
# PrimitiveのインデックスからMultiparmのインデックス番号を返す
def getMultiparmIndexFromPoint(self, point_index):
id = self.select_geometry.point(point_index).attribValue("id")
multiparm_index = self.getMultiparmIndexFromId(id)
return multiparm_index
#
# マウスイベント
#
def onMouseEvent(self, kwargs):
ui_event = kwargs["ui_event"]
device = ui_event.device()
origin, direction = ui_event.ray()
self.viewport = self.scene_viewer.curViewport() # hou.GeometryViewportクラス
if self.colli_geometry:
hit, position, norm, uvw = su.sopGeometryIntersection(self.colli_geometry, origin, direction)
# どことも交差がなければコンストラクション平面に交差させる
if hit == -1:
position = su.cplaneIntersection(self.scene_viewer, origin, direction)
else:
position = su.cplaneIntersection(self.scene_viewer, origin, direction)
# 左ボタンの押下
if device.isLeftButton():
# ポイントをドラッグ中
if self.drag == True:
# スナップをいれる
closeIndex = -1
snapFlag = False
if self.isPressedShift == False:
self.node.parmTuple("position_%d" % self.selected_point).set(position) # 座標をセット
else:
# 画面に対して平面方向にドラッグする
parm_pos = self.node.parmTuple("position_%d" % self.selected_point).eval() # parmから値を取得するにはeval()を使う
P = hou.Vector3(parm_pos[0],parm_pos[1],parm_pos[2])
planeNormal = direction
flagHorizontal = False
angle = math.acos(direction.dot(hou.Vector3(0, 1, 0)))
if math.radians(45) < angle and angle < math.radians(135):
flagHorizontal = True
if snapFlag == True:
if flagHorizontal:
newPos = P.x(), list_pos[closeIndex][1], P.z() # Y座標だけ変更したい場合はこの変数を使う
else:
newPos = list_pos[closeIndex][0], P.y(), list_pos[closeIndex][2]
else:
d = -(planeNormal.x() * P.x() + planeNormal.y() * P.y() + planeNormal.z() * P.z())
t = -(planeNormal.x() * origin.x() + planeNormal.y() * origin.y() + planeNormal.z() * origin.z() + d) / (planeNormal.x() * direction.x() + planeNormal.y() * direction.y() + planeNormal.z() * direction.z());
cross = origin.x() + direction.x() * t, origin.y() + direction.y() * t, origin.z() + direction.z() * t # Vector3ではなくタプル
if flagHorizontal:
newPos = P.x(), cross[1], P.z() # Y座標だけ変更したい場合はこの変数を使う
else:
newPos = cross[0], P.y(), cross[2] # Y座標だけ変更したい場合はこの変数を使う
self.node.parmTuple("position_%d" % self.selected_point).set(newPos)
# ドラッグしてない場合
else:
# ポイントの移動
# マウスがクリックしたポイントの番号を距離から探す
list_pos = []
# ジオメトリから座標を取得する
poly_points = self.select_geometry.points()
for i in range(len(poly_points)):
parm_pos = poly_points[i].position()
list_pos.append(hou.Vector3((parm_pos[0],parm_pos[1],parm_pos[2])))
closeIndex = self.isIntersectPoints(origin, self.viewport, list_pos, self.point_range)
# ポイントをクリックしていたらドラッグ(移動)フラグを立てる
if closeIndex != -1:
# 選択中のポイントだったら選択解除する
if self.selected_point == closeIndex:
self.selected_point = -1
else:
self.dragIndex = closeIndex
self.drag = True
self.selected_point = closeIndex
self.start()
# タブを選択する
multiparm = self.node.parm("points")
multiparm_index = self.getMultiparmIndexFromPoint(self.selected_point)
hou.ui.setMultiParmTabInEditors(multiparm, multiparm_index)
# ポイントを新規追加する
else:
# 選択中のポイントがあれば選択解除する
if self.selected_point != -1:
self.selected_point = -1
else:
self.start()
pt_index = self.pointCount()
multiparm = self.node.parm("points")
multiparm.insertMultiParmInstance(pt_index)
# UUID
id = str(uuid.uuid4())[:8]
self.node.parm("id_%d" % pt_index).set(id) # uuidをセット
# Position
self.node.parmTuple("position_%d" % pt_index).set(position) # 座標をセット
self.log("create new point")
self.selected_point = -1
# タブを選択する
hou.ui.setMultiParmTabInEditors(multiparm, pt_index)
else:
self.finish()
self.drag = False
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
return True
# キーボード入力
def onKeyEvent(self,kwargs):
ui_event = kwargs["ui_event"]
self.key_pressed = ui_event.device().keyString()
# Delキーが押されたら削除する
if self.key_pressed in ("Del"):
if self.selected_point != -1:
multiparm = self.node.parm("points")
multiparm.removeMultiParmInstance(self.selected_point)
self.selected_point = -1
self.intersect_point = -1
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
return True
# カメラのピボットを選択しているポイント座標にする
if self.key_pressed in ("f"):
if self.selected_point != -1:
parm_pos = self.node.parmTuple("position_%d" % (self.selected_point)).eval()
ptPos = hou.Vector3((parm_pos[0],parm_pos[1],parm_pos[2]))
viewTransform = self.viewport.viewTransform()
tuples = viewTransform.asTuple()
worldCamPos = hou.Vector3(tuples[12], tuples[13], tuples[14])
# カメラのピボットを変更する
cam = self.viewport.defaultCamera() # hou.GeometryViewportCamera
camPivotPos = hou.Vector3(cam.pivot())
dist = (worldCamPos - camPivotPos).length()
cam.setPivot(ptPos)
cam.setTranslation(ptPos + hou.Vector3(0, 0, dist))
return True
self.key_pressed = None
return False
# 修飾キーのイベント
def onKeyTransitEvent(self, kwargs):
ui_event = kwargs["ui_event"]
# SHIFTキー
if ui_event.device().isShiftKey() == 1:
self.isPressedShift = True
else:
self.isPressedShift = False
return False
def onDraw( self, kwargs ):
""" This callback is used for rendering the drawables """
handle = kwargs["draw_handle"]
self.guideEditPoint.draw(handle)
self.guideSelectedPoint.draw(handle)
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "ViewerStatePointPlaceTool::1.0"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
HDA内のWrangleコード

int numpoints = `chs("../points")`;
for(int i = 0; i < numpoints; i++)
{
int point_count = chi("../points_" + itoa(i));
vector p = chv(sprintf("../position_%g", i));
int pt = addpoint(0, p);
}