Polyline Edit Tool
環境:Houdini 20.0.751

複数のポリラインに対応した編集ツール
仕様
V:ポリラインを分割する Ctrl:モードの切り替え
Parameterの構成

Folder Type: Multiparm Block (tabs) Name: prims Label: Primitives First Instanceを0に設定する
Folder Type: Multiparm Block (list) Name: points_# Label: Points First Instanceを0に設定する
Name: pt_#_# Label: Prim # Point # Type: Float Vector 3
Viewer Stateのコード
#
# Polyline Edit Tool 20250321
#
import hou
import viewerstate.utils as su
from enum import Enum
import uuid
import math
# Enumでモード切り替え
class Mode(Enum):
SELECT = 0
EDIT = 1
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": "Polyline Edit", "desc": "tool", "icon": "SOP_subnet",
"rows": [
{"id": "mode", "label": "Mode", "value": "SELECT"},
{"id": "mode_g", "type": "choicegraph", "count": 2, "value": 0},
{"id": "switch_act", "label": "Switch Mode", "key": "Ctrl"},
{"id": "add_act", "label": "Add/Select Prim", "key": "LMB"},
{"id": "del_act", "label": "Delete Prim", "key": "Del"},
{"id": "merge_act", "label": "Merge Prims", "key": "m"},
{"id": "focus_act", "label": "Camera Focus", "key": "f"},
]
}
# テンプレートを使用してHUDを設定します。通常では、これはステートが有効な間に一回実行します。
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
self.mode = Mode.SELECT
self.viewport = None
self.pressed = False # マウス左ボタン押下フラグ
self.node = None # ノード
self.drag = False # ドラッグ中のフラグ
self.dragIndex = 0 # ドラッグ中のポイントインデックス
self.isPressedShift = False # SHIFTキーの押下判定フラグ
self.selected_prim = [] # 選択中のプリミティブ番号
self.intersect_prim = -1 # 交差中のプリミティブ番号
self.selected_point = -1 # 選択中のポイント番号
self.isCollisionExist = False
self.colli_geometry = None # コリジョン用ジオメトリ
self.gen_curve_geometry = 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)
# エッジ(点線)
selecting_line = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Line, "line",
params = {
"color1": (1.0,0.7,0.1,1.0),
"style": hou.drawableGeometryLineStyle.Dash1,
"line_width": 3 }
)
self.guideSelectingLine = hou.GeometryDrawableGroup("poly_selecting_line")
self.guideSelectingLine.addDrawable(selecting_line)
# エッジ(実線)
selected_line = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Line, "line",
params = {
"color1": (1.0,0.6,0.1,1.0),
"style": hou.drawableGeometryLineStyle.Plain,
"line_width": 3 }
)
self.guideSelectedLine = hou.GeometryDrawableGroup("poly_selectied")
self.guideSelectedLine.addDrawable(selected_line)
# UIのMultiparmフォルダからポイント数を取得する
def pointCount(self):
try:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm = self.node.parm("points_%d" % (multiparm_index))
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[0]にジオメトリが入力されている場合はジオメトリを設定する)
inputs = self.node.inputs()
if inputs and inputs[0]:
self.colli_geometry = inputs[0].geometry()
self.isCollisionExist = True
else:
self.isCollisionExist = False
self.log("isCollisionExist: " + str(self.isCollisionExist))
# ガイドのジオメトリ
hda_node = self.scene_viewer.currentNode()
hda_node.cook(force=True)
if hda_node:
hda_node.node('wrangle1').cook(force=True) # 強制的にクック
self.gen_curve_geometry = hda_node.node('wrangle1').geometry()
if self.isCollisionExist:
pass
#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)
else:
self.drawableGeo = None
if self.mode == Mode.SELECT:
self.guideSelectingLine.show(True)
self.guideSelectedLine.show(True)
else:
self.guideEditPoint.show(True)
self.guideSelectedPoint.show(True)
# Stateが中断された時に呼び出される
def onInterrupt(self,kwargs):
self.finish()
self.guideEditPoint.show(False)
self.guideSelectedPoint.show(False)
self.guideSelectingLine.show(False)
self.guideSelectedLine.show(False)
# Stateの中断が再開された時に呼び出される
def onResume(self, kwargs):
self.scene_viewer.setPromptMessage( State.MSG )
# 選択中ポイントDrawableの更新処理
def updateGuideEditPoint(self):
# UIのジオメトリを描画する
if self.pointCount() > 0:
poly_geo = hou.Geometry()
poly = poly_geo.createPolygon()
for i in range(self.pointCount()):
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval()
point = poly_geo.createPoint()
point.setPosition(parm_pos)
poly.addVertex(point)
# update the drawable
self.guideEditPoint.setGeometry(poly_geo)
# 選択済みポイントDrawableの更新処理
def updateGuideSelectedPoint(self):
poly_geo = hou.Geometry()
poly = poly_geo.createPolygon()
if self.selected_point != -1:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, self.selected_point)).eval()
point = poly_geo.createPoint()
point.setPosition(parm_pos)
poly.addVertex(point)
self.guideSelectedPoint.setGeometry(poly_geo)
# 選択中ラインDrawableの更新処理
def updateGuideSelectingLine(self):
poly_geo = hou.Geometry()
if self.intersect_prim != -1:
poly_points = self.gen_curve_geometry.prim(self.intersect_prim).points()
# Construct a new geometry
poly = poly_geo.createPolygon(False) # hou.Polygonクラスが生成される
for i in range(len(poly_points)):
point = poly_geo.createPoint()
point.setPosition(poly_points[i].position())
poly.addVertex(point)
# update the drawable
self.guideSelectingLine.setGeometry(poly_geo)
# 選択済みラインDrawableの更新処理
def updateGuideSelectedLine(self):
poly_geo = hou.Geometry()
if len(self.selected_prim) > 0:
for n in range(len(self.selected_prim)):
poly_points = self.gen_curve_geometry.prim(self.selected_prim[n]).points()
# Construct a new geometry
poly = poly_geo.createPolygon(False) # hou.Polygonクラスが生成される
for i in range(len(poly_points)):
point = poly_geo.createPoint()
point.setPosition(poly_points[i].position())
poly.addVertex(point)
self.guideSelectedLine.setGeometry(poly_geo)
# モード切替時
def modeChange(self):
self.selected_point = -1
if self.mode == Mode.SELECT:
self.updateGuideSelectingLine()
self.guideSelectingLine.show(True)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
self.guideEditPoint.show(False)
self.guideSelectedPoint.show(False)
HUD_TEMPLATE = {
"title": "Polyline Edit", "desc": "tool", "icon": "SOP_subnet",
"rows": [
{"id": "mode", "label": "Mode", "value": "SELECT"},
{"id": "mode_g", "type": "choicegraph", "count": 2, "value": 0},
{"id": "switch_act", "label": "Switch Mode", "key": "Ctrl"},
{"id": "add_act", "label": "Add/Select Prim", "key": "LMB"},
{"id": "del_act", "label": "Delete Prim", "key": "Del"},
{"id": "merge_act", "label": "Merge Prims", "key": "m"},
{"id": "focus_act", "label": "Camera Focus", "key": "f"},
]
}
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
else:
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.guideSelectingLine.show(False)
self.guideSelectedLine.show(False)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
HUD_TEMPLATE = {
"title": "Polyline Edit", "desc": "tool", "icon": "SOP_subnet",
"rows": [
{"id": "mode", "label": "Mode", "value": "EDIT"},
{"id": "mode_g", "type": "choicegraph", "count": 2, "value": 1},
{"id": "switch_act", "label": "Switch Mode", "key": "Ctrl"},
{"id": "add_act", "label": "Add/Select Point", "key": "LMB"},
{"id": "del_act", "label": "Delete Point", "key": "Del"},
{"id": "split_act", "label": "Split Point", "key": "v"},
{"id": "focus_act", "label": "Camera Focus", "key": "f"},
]
}
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
self.log(self.mode.name)
#
# マウスポインタとポイントの交差判定(交差していたらインデックス番号を、していなかったら-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
#
# ポリラインとマウスポインタの交差判定(引数はhou.Primクラスのリスト。交差していたらポリラインのインデックス番号を、していなかったら-1を返す)
#
def isIntersectPolylines(self, origin, direction, viewport, prims, radius):
# エッジとマウスポインタの交差判定
closeIndex = -1
closeDist = 1000000
# 入力ポリラインごとの処理
for i in range(len(prims)):
list_points = prims[i].points() # プリミティブからポイントを取得
# エッジごとの判定処理
# マウスポインタからの線分と各エッジを最短で結ぶ直線と交点を計算している
A = origin
B = origin + direction
ab = (B-A).normalized()
for j in range(len(list_points) - 1):
C = list_points[j].position()
D = list_points[j+1].position()
cd = (D-C).normalized()
ac = (C-A)
#d0 = (ab.dot(ac)-cd.dot(ac)*ab.dot(cd))/(1-(ab.dot(cd)*ab.dot(cd)))
d1 = (ab.dot(ac)*ab.dot(cd)-cd.dot(ac))/(1-(ab.dot(cd)*ab.dot(cd)))
#c0 = A + ab * d0
c0 = A
c1 = hou.Vector3()
if d1 >= 0 and pow(d1, 2) <= (D-C).lengthSquared(): # 線分の範囲内
c1 = C + cd * d1
elif d1 < 0: # 始点より前
c1 = C
elif pow(d1, 2) > (D-C).lengthSquared(): # 終点より後
c1 = D
dist = (viewport.mapToScreen(c1) - viewport.mapToScreen(c0)).lengthSquared()
if dist < pow(radius, 2) and dist < closeDist:
closeIndex = i
break
else:
continue
break
return closeIndex
#
# ポリラインとマウスポインタの交差判定(引数は座標リスト。一番近い座標と、エッジの最初のインデックス番号を返す)
#
def getClosePosPolyline(self, origin, direction, viewport, positions, radius):
closeIndex = -1
closeDist = 1000000
closePos = hou.Vector3()
A = origin
B = origin + direction
ab = (B-A).normalized()
for i in range(len(positions)-1):
C = positions[i]
D = positions[i+1]
cd = (D-C).normalized()
ac = (C-A)
#d0 = (ab.dot(ac)-cd.dot(ac)*ab.dot(cd))/(1-(ab.dot(cd)*ab.dot(cd)))
d1 = (ab.dot(ac)*ab.dot(cd)-cd.dot(ac))/(1-(ab.dot(cd)*ab.dot(cd)))
# 線分の範囲内か判別
if d1 >= 0 and pow(d1, 2) < (D-C).lengthSquared():
#c0 = A + ab * d0
c0 = A
c1 = C + cd * d1
dist = (viewport.mapToScreen(c1) - viewport.mapToScreen(c0)).lengthSquared()
if dist < pow(radius, 2) and dist < closeDist:
closeDist = dist
closeIndex = i
closePos = c1
return closeIndex, closePos
# IDからMultiparmのインデックス番号を返す
def getMultiparmIndexFromId(self, id):
index = -1
for i in range(self.node.parm("prims").evalAsInt()):
id2 = self.node.parm("id_%d" % i).eval()
#self.log(uuid)
if id == id2:
index = i
break
return index
# PrimitiveのインデックスからMultiparmのインデックス番号を返す
def getMultiparmIndexFromPrim(self, prim_index):
id = self.gen_curve_geometry.prim(prim_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.isCollisionExist:
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)
#
# MODE == SELECT
#
if self.mode == Mode.SELECT:
# エッジとマウスポインタの交差判定
intersect_prim = self.isIntersectPolylines(origin, direction, self.viewport, self.gen_curve_geometry.prims(), self.edge_range)
# 左ボタンクリック
if device.isLeftButton():
# ポリラインと交差してない場合
if intersect_prim == -1:
# 選択しているポリラインがあれば選択解除する
if len(self.selected_prim) > 0:
self.selected_prim.clear()
# 何も選択していなければ新規ポリライン追加
else:
# 新しくPrimのMultiparmを追加して、self.selected_primを設定する
multiparm = self.node.parm("prims")
index = multiparm.evalAsInt()
multiparm.insertMultiParmInstance(index)
self.selected_prim.clear()
self.selected_prim.append(index)
hou.ui.setMultiParmTabInEditors(multiparm, index)
# UUID
new_id = str(uuid.uuid4())[:8]
self.node.parm("id_%d" % (index)).set(new_id) # uuidをセット
# ポイントを追加
self.start()
pt_index = self.pointCount()
multiparm = self.node.parm("points_" + str(index))
multiparm.insertMultiParmInstance(pt_index)
self.node.parmTuple("pt_%d_%d" % (index, pt_index)).set(position) # 座標をセット
self.mode = Mode.EDIT
self.modeChange()
# ポリラインと交差している
else:
# 該当するポリラインがすでに選択済みか調べる
flagContain = False
if len(self.selected_prim) > 0:
for n in range(len(self.selected_prim)):
if self.selected_prim[n] == intersect_prim:
# SHIFTキーが押されていたら該当するポリラインを選択解除する
if self.isPressedShift == True:
del self.selected_prim[n]
if len(self.selected_prim) > 0:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm = self.node.parm("prims")
hou.ui.setMultiParmTabInEditors(multiparm, multiparm_index)
# SHIFTキーが押されてなかったら全て選択解除
else:
self.selected_prim.clear()
flagContain = True
break
if flagContain == False:
# SHIFTキーが押されている
if self.isPressedShift == True:
# 追加する
self.selected_prim.append(intersect_prim)
else:
# 新しく選択し直す
self.selected_prim.clear()
self.selected_prim.append(intersect_prim)
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm = self.node.parm("prims")
hou.ui.setMultiParmTabInEditors(multiparm, multiparm_index)
# Drawablesジオメトリの更新
if intersect_prim != -1:
self.intersect_prim = intersect_prim
self.updateGuideSelectingLine()
self.guideSelectingLine.show(True)
else:
self.selecting_prim = -1
self.guideSelectingLine.show(False)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
#
# EDIT MODE
#
else:
# 編集対象が存在する
if len(self.selected_prim) > 0:
# 左ボタンの押下
if device.isLeftButton():
# ポイントをドラッグ中
if self.drag == True:
# スナップをいれる
closeIndex = -1
snapFlag = False
isSnapToPoint = self.node.parm("snapToPoint").eval()
if isSnapToPoint == 1:
# ポリラインごとの処理
for i in range(len(self.gen_curve_geometry.prims())):
list_points = self.gen_curve_geometry.prim(i).points() # プリミティブからポイントを取得
list_pos = []
for j in range(len(list_points)):
if i == self.selected_prim[0] and j == self.selected_point:
pass
else:
pos = list_points[j].position()
list_pos.append(pos)
closeIndex = self.isIntersectPoints(origin, self.viewport, list_pos, self.point_range)
# ポイントを削除する
if closeIndex != -1:
snapFlag = True
break
if self.isPressedShift == False:
if snapFlag == True:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
self.node.parmTuple("pt_%d_%d" % (multiparm_index, self.dragIndex)).set(list_pos[closeIndex]) # 座標をセット
else:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
self.node.parmTuple("pt_%d_%d" % (multiparm_index, self.dragIndex)).set(position) # 座標をセット
# SHIFT押しでY軸のみ
else:
# 画面に対して平面方向にドラッグする
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, self.dragIndex)).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座標だけ変更したい場合はこの変数を使う
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
self.node.parmTuple("pt_%d_%d" % (multiparm_index, self.dragIndex)).set(newPos)
# ドラッグしてない場合
else:
# ポイントの移動
# マウスがクリックしたポイントの番号を距離から探す
list_pos = []
for i in range(self.pointCount()):
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval() # parmから値を取得するにはeval()を使う
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:
self.dragIndex = closeIndex
self.drag = True
self.selected_point = closeIndex
self.start()
# エッジの近くをクリックしたら中間点を挿入する
else:
# エッジごとの判定処理
# マウスポインタからの線分と各エッジを最短で結ぶ直線と交点を計算している
closeIndex, closePos = self.getClosePosPolyline(origin, direction, self.viewport, list_pos, self.point_range)
# エッジの近くがクリックされていたら、エッジの中間点にポイントを挿入する
if closeIndex != -1:
self.start()
closeIndex += 1
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm = self.node.parm("points_" + str(multiparm_index))
multiparm.insertMultiParmInstance(closeIndex)
self.node.parmTuple("pt_%d_%d" % (multiparm_index, closeIndex)).set(closePos) # 座標をセット
self.selected_point = closeIndex
# どこにもあてはまらなければ、新しいポイントを生成する
else:
self.start()
pt_index = self.pointCount()
if self.isPressedShift == True:
pt_index = 0
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm = self.node.parm("points_" + str(multiparm_index))
multiparm.insertMultiParmInstance(pt_index)
self.node.parmTuple("pt_%d_%d" % (multiparm_index, pt_index)).set(position) # 座標をセット
self.log("create new point")
self.selected_point = 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"):
# SELECT MODE
if self.mode == Mode.SELECT:
if len(self.selected_prim) > 0:
# インデックスが詰まらないように後ろから処理していく
self.selected_prim = sorted(self.selected_prim)
multiparm = self.node.parm("prims")
for n in reversed(range(len(self.selected_prim))):
multiparm.removeMultiParmInstance(self.selected_prim[n])
self.selected_prim.clear()
self.intersect_prim = -1
self.updateGuideSelectingLine()
self.guideSelectingLine.show(True)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
# EDIT MODE
else:
# ポイントを削除する
if self.selected_point != -1:
multiparm = self.node.parm("points_" + str(self.selected_prim[0]))
multiparm.removeMultiParmInstance(self.selected_point)
self.selected_point = -1
# もし最後のポイントだったらプリミティブも消す
if multiparm.evalAsInt() == 0:
multiparm = self.node.parm("prims")
multiparm.removeMultiParmInstance(self.selected_prim[0])
self.selected_prim.clear()
self.mode = Mode.SELECT
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
return True
# ポリラインの分割処理
if self.key_pressed in ("v"):
if self.selected_point != -1 and self.selected_point < self.pointCount()-1:
# 座標を保存
positions = []
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm_point = self.node.parm("points_" + str(multiparm_index))
for i in range(self.selected_point, multiparm_point.evalAsInt()):
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval()
pos = hou.Vector3((parm_pos[0],parm_pos[1],parm_pos[2]))
positions.append(pos)
# uuid
id = str(uuid.uuid4())[:8]
self.node.parm("id_%d" % self.selected_prim[0]).set(id) # uuidをセット
# ポイントを削除(選択インデックスから最後まで)
for i in range(self.selected_point+1, multiparm_point.evalAsInt()):
multiparm_point.removeMultiParmInstance(self.selected_point+1)
# 新しくポリラインを生成
index = self.node.parm("prims").evalAsInt()
multiparm = self.node.parm("prims")
multiparm.insertMultiParmInstance(index)
multiparm_point = self.node.parm("points_" + str(index))
for i in range(len(positions)):
multiparm_point.insertMultiParmInstance(i)
self.node.parmTuple("pt_%d_%d" % (index, i)).set(positions[i])
# uuid
id = str(uuid.uuid4())[:8]
self.node.parm("id_%d" % index).set(id) # uuidをセット
return True
# ポリラインの結合
if self.key_pressed in ("m"):
if len(self.selected_prim) == 2:
positions0 = []
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm_point = self.node.parm("points_%d" % (multiparm_index))
for i in range(multiparm_point.evalAsInt()):
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval()
pos = hou.Vector3((parm_pos[0],parm_pos[1],parm_pos[2]))
positions0.append(pos)
positions1 = []
multiparm_index2 = self.getMultiparmIndexFromPrim(self.selected_prim[1])
multiparm_point2 = self.node.parm("points_%d" % (multiparm_index2))
for i in range(multiparm_point2.evalAsInt()):
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index2, i)).eval()
pos = hou.Vector3((parm_pos[0],parm_pos[1],parm_pos[2]))
positions1.append(pos)
# 4通りの始点終点の組み合わせから距離の短いものを選ぶ
dist = []
dist.append((positions0[-1] - positions1[0]).length())
dist.append((positions0[-1] - positions1[-1]).length())
dist.append((positions0[0] - positions1[0]).length())
dist.append((positions0[0] - positions1[-1]).length())
# 一番近い組み合わせを探す
index = 0
minDist = dist[index]
for i in range(len(dist)):
if(dist[i] < minDist):
minDist = dist[i]
index = i
# 組み合わせで座標の順番を逆転させる
if index != -1:
if index == 1:
positions1.reverse()
elif index == 2:
positions0.reverse()
elif index == 3:
positions0.reverse()
positions1.reverse()
newPosistions = positions0 + positions1
#self.log(newPosistions)
# 座標をすべて削除して新しい座標を設定する
multiparm_point = self.node.parm("points_%d" % (multiparm_index))
multiparm_point.set(0)
for i in range(len(newPosistions)):
multiparm_point.insertMultiParmInstance(i)
self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).set(newPosistions[i])
# 2つめのポリラインを削除
multiparm = self.node.parm("prims")
multiparm.removeMultiParmInstance(multiparm_index2)
#temp = self.selected_prim[0]
self.selected_prim.clear()
#self.selected_prim.append(temp)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
# カメラのピボットを選択しているポイント座標にする
if self.key_pressed in ("f"):
if len(self.selected_prim) > 0 or self.selected_point != -1:
ptPos = hou.Vector3()
if self.mode == Mode.SELECT:
center = hou.Vector3()
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm_point = self.node.parm("points_" + str(multiparm_index))
for i in range(multiparm_point.evalAsInt()):
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval()
center += hou.Vector3(parm_pos)
center /= float(multiparm_point.evalAsInt())
ptPos = center
else:
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
parm_pos = self.node.parmTuple("pt_%d_%d" % (multiparm_index, 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"]
# Ctrlキー
if ui_event.device().isCtrlKey() == 1:
if self.mode == Mode.SELECT:
if len(self.selected_prim) > 0:
self.mode = Mode.EDIT
self.modeChange()
elif self.mode == Mode.EDIT:
# ポイントが1つだったら削除する
multiparm_index = self.getMultiparmIndexFromPrim(self.selected_prim[0])
multiparm_points = self.node.parm("points_%d" % (multiparm_index))
if multiparm_points.evalAsInt() == 1:
multiparm = self.node.parm("prims")
multiparm.removeMultiParmInstance(self.selected_prim)
self.selected_prim.clear()
self.mode = Mode.SELECT
self.modeChange()
# 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)
self.guideSelectingLine.draw(handle)
self.guideSelectedLine.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 = "ViewerStatePolylineTool::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
Python Callback Script
選択しているプリミティブのタブにカメラがフォーカスするようにする。
def onCamerafocusOnPoint(kwargs):
node = kwargs['node']
# Multiparmタブの選択されているインデックスを取得
parm_editor = hou.ui.paneTabOfType(hou.paneTabType.Parm)
multiparm_index = parm_editor.multiParmTab('prims')
# Multiparmの座標から中心座標を計算する
center = hou.Vector3()
multiparm_point = node.parm("points_" + str(multiparm_index))
for i in range(multiparm_point.evalAsInt()):
parm_pos = node.parmTuple("pt_%d_%d" % (multiparm_index, i)).eval()
center += hou.Vector3(parm_pos)
center /= float(multiparm_point.evalAsInt())
ptPos = center
# カメラの行列を取得
scene_viewer = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer)
viewport = scene_viewer.curViewport()
view_transform = viewport.viewTransform()
tuples = view_transform.asTuple()
worldCamPos = hou.Vector3(tuples[12], tuples[13], tuples[14])
# カメラのピボットを変更する
cam = viewport.defaultCamera() # hou.GeometryViewportCamera
camPivotPos = hou.Vector3(cam.pivot())
dist = (worldCamPos - camPivotPos).length()
cam.setPivot(ptPos)
cam.setTranslation(ptPos + hou.Vector3(0, 0, dist))
ノードをクックする
ノードにアクセスする前に確実に計算処理を終わらせるようにする。そうしないとエラーメッセージが入る。
hda_node = self.scene_viewer.currentNode()
hda_node.cook(force=True)
if hda_node:
hda_node.node('wrangle1').cook(force=True) # 強制的にクック
self.select_geometry = hda_node.node('wrangle1').geometry()
Detail WrangleをHDA内に配置する

パラメータのMultiparm Blockをポリライン化していく。
int numprims = `chs("../prims")`;
for(int i = 0; i < numprims; i++)
{
int isActive = chi(sprintf("../isActive_%g", i));
if(isActive)
{
// ポリラインを生成する
int prim = addprim(0, "polyline");
string uuid = chs(sprintf("../id_%g", i));
setprimattrib(0, "id", prim, uuid);
string roadName = chs(sprintf("../roadName_%g", i));
setprimattrib(0, "roadName", prim, roadName);
int roadType = chi(sprintf("../roadType_%g", i));
setprimattrib(0, "roadType", prim, roadType);
int laneLeft = chi(sprintf("../laneLeft_%g", i));
setprimattrib(0, "laneLeft", prim, laneLeft);
int laneRight = chi(sprintf("../laneRight_%g", i));
setprimattrib(0, "laneRight", prim, laneRight);
float laneWidth = chf(sprintf("../laneWidth_%g", i));
setprimattrib(0, "laneWidth", prim, laneWidth);
// Bank Angle Properties
float designSpeed = chf(sprintf("../designSpeed_%g", i));
setprimattrib(0, "designSpeed", prim, designSpeed);
float friction = chf(sprintf("../friction_%g", i));
setprimattrib(0, "friction", prim, friction);
int point_count = chi(sprintf("../points_%g", i));
for(int j = 0; j < point_count; j++)
{
vector p = chv(sprintf("../pt_%g_%g", i, j));
int pt = addpoint(0, p);
addvertex(0, prim, pt);
}
}
}