Polyline Path Tool
環境:Houdini 20.0.751

ポリライン上に沿ってポリラインを生成するツール。
元々存在するポリラインを「親ポリライン」とし、そこに沿って作られるポリラインを「生成ポリライン」とする。
Parameterの構成
Folder Type: Multiparm Block (tabs) Name: prims Label: Primitives
Folder Type: Multiparm Block (list) Name: points_# Label: Points
Name: pt_#_# Label: Prim # Point # Type: Float
Viewer Stateのコード
#
# Polyline Path Tool 20250319
#
import hou
import viewerstate.utils as su
import math
from enum import Enum
# Enumでモード切り替え
class Mode(Enum):
SELECT = 0
EDIT = 1
class State(object):
def __init__(self, state_name, scene_viewer):
self.state_name = state_name
self.scene_viewer = scene_viewer
# UIのテンプレート
HUD_TEMPLATE = {
"title": "Polyline Path", "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": "Select / Add Prim", "key": "LMB"},
{"id": "del_act", "label": "Delete Prim", "key": "Del"},
{"id": "focus_act", "label": "Camera Focus", "key": "F"},
]
}
# テンプレートを使用してHUDを設定します。通常では、これはステートが有効な間に一回実行します。
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
self.mode = Mode.SELECT # モード
self.origin = None
self.direction = None
self.viewport = None
self.selected_point = -1 # 選択ポイントのローカルインデックス
self.selected_gen_prim = -1 # 選択中のプリミティブ番号
self.selected_src_prim = -1 # 選択プリミティブの親のインデックス
self.intersect_gen_prim = -1 # 交差中のプリミティブ番号(ポイントもプリミティブとして扱う)
self.intersect_src_prim = -1
self.node = None
self.drawable_geometry = None # 入力されたポリラインのジオメトリ(表示用)
self.src_curve_geometry = None # 入力されたポリラインのジオメトリ
self.gen_curve_geometry = None # 生成されたポリライン
self.src_curve_bsphere = None # 入力されたポリラインのバウンディングスフィア
self.gen_curve_bsphere = None # 生成されたポリラインのバウンディングスフィア
# 入力用のフラグ
self.mouse_pressed = False
self.old_mouse_pressed = False
self.drag = False # ドラッグ中のフラグ
self.dragIndex = -1 # ドラッグ中のポイントインデックス
# ポイントとエッジの判定距離(ピクセル)
self.point_range = 16.0
self.edge_range = 8.0
self.edge_selecting_range = 96.0
# 表示ポイント(編集中:オレンジ:大)
edit_point = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Point, "point",
params = {
"num_rings": 2,
"radius": 3,
"color1": (1.0,0.6,0.1,1.0),
"style" : hou.drawableGeometryPointStyle.LinearSquare}
)
self.guideEditPoint = hou.GeometryDrawableGroup("poly_edit_point")
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)
# エッジ(選択中:実線:オレンジ)
curve_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": 1 }
)
self.guideCurveLine = hou.GeometryDrawableGroup("poly_curve_line")
self.guideCurveLine.addDrawable(curve_line)
# エッジ(選択済み:実線:オレンジ)
selected_curve_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.guideSelectedCurveLine = hou.GeometryDrawableGroup("poly_selected_curve_line")
self.guideSelectedCurveLine.addDrawable(selected_curve_line)
# エッジ(点線)
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)
#
# ユーザーがStateに入ると呼び出される
#
def onEnter(self, kwargs):
self.node = kwargs["node"]
# ガイドのジオメトリ
hda_node = self.scene_viewer.currentNode()
hda_node.cook(force=True) # 強制クック
if hda_node:
hda_node.node('geo_src_curve').cook(force=True)
self.src_curve_geometry = hda_node.node('geo_src_curve').geometry()
hda_node.node('geo_new_curve').cook(force=True)
self.gen_curve_geometry = hda_node.node('geo_new_curve').geometry()
hda_node.node('src_bsphere').cook(force=True)
self.src_curve_bsphere = hda_node.node('src_bsphere').geometry()
hda_node.node('new_bsphere').cook(force=True)
self.gen_curve_bsphere = hda_node.node('new_bsphere').geometry()
# オーバーレイで描画する入力ポリライン
colli = hda_node.node('geo_src_curve').geometry()
self.drawable_geometry = hou.SimpleDrawable(self.scene_viewer, colli, "drawable_geometry")
self.drawable_geometry.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
#self.drawable_geometry.setDisplayMode(hou.drawableDisplayMode.CurrentViewportMode)
self.drawable_geometry.enable(True)
self.drawable_geometry.show(True)
self.modeChange()
self.node.parm("onEnter").set(1)
# Stateが中断された時に呼び出される
def onInterrupt(self,kwargs):
#self.drawable_geometry.show(False)
#self.guideSelectedPoint.show(False)
#self.guideEditPoint.show(False)
#self.guideSelectingLine.show(False)
#self.guideSelectedLine.show(False)
pass
# Stateの中断が再開された時に呼び出される
def onResume(self, kwargs):
self.drawable_geometry.show(True)
def onExit(self, kwargs):
self.node.parm("onEnter").set(0)
# マウス左ボタンが押された時に呼ばれる処理
def start(self):
if not self.mouse_pressed:
self.scene_viewer.beginStateUndo("Mouse Pressed")
self.mouse_pressed = True
# マウス左ボタンが放された時に呼ばれる処理
def finish(self):
# 前フレームで押下されていたら
if self.mouse_pressed:
self.scene_viewer.endStateUndo()
self.mouse_pressed = False
# 選択済みポイントDrawableの更新処理
def updateGuideEditPoint(self):
# UIのジオメトリを描画する
poly_geo = hou.Geometry()
if self.selected_src_prim != -1:
poly = poly_geo.createPolygon()
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
list_pos = self.getPointPositionsFromMultiparm(multiparm_prim_index)
for i in range(len(list_pos)):
point = poly_geo.createPoint()
point.setPosition(list_pos[i])
poly.addVertex(point)
self.guideEditPoint.setGeometry(poly_geo) # update the drawable
# 選択中ポイントDrawableの更新処理
def updateGuideSelectedPoint(self):
# UIのジオメトリを描画する
poly_geo = hou.Geometry()
#self.log("self.selected_point:" + str(self.selected_point))
if self.selected_point != -1 and self.selected_src_prim != -1:
poly = poly_geo.createPolygon()
list_pos = self.getPointPositionsFromMultiparm(self.selected_src_prim)
point = poly_geo.createPoint()
point.setPosition(list_pos[self.selected_point])
poly.addVertex(point)
self.guideSelectedPoint.setGeometry(poly_geo) # update the drawable
# 生成ラインDrawableの更新処理
def updateGuideCurveLine(self):
poly_geo = hou.Geometry()
poly_prims = self.gen_curve_geometry.prims()
for i in range(len(poly_prims)):
poly_points = self.gen_curve_geometry.prim(i).points()
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.guideCurveLine.setGeometry(poly_geo)
# 選択済みの生成ラインDrawableの更新処理
def updateGuideSelectedCurveLine(self):
poly_geo = hou.Geometry()
if self.selected_gen_prim != -1:
#poly_prims = self.gen_curve_geometry.prims()
poly_points = self.gen_curve_geometry.prim(self.selected_gen_prim).points()
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.guideSelectedCurveLine.setGeometry(poly_geo)
# 選択中ラインDrawableの更新処理
def updateGuideSelectingLine(self):
poly_geo = hou.Geometry()
if self.intersect_prim != -1:
poly_points = self.src_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 self.selected_src_prim != -1:
# 入力されたポリラインからポイント情報を取得する
poly_points = self.src_curve_geometry.prim(self.selected_src_prim).points()
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)
#
# Multiparmのポイントを描画する
#
def getPointPositionsFromMultiparm(self, selected_src_prim):
list_position = []
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_curve = self.node.parm("curves_" + str(multiparm_prim_index))
for i in range(multiparm_curve.evalAsInt()):
multiparm_point = self.node.parm("points_%d_%d" % (multiparm_prim_index, i))
for j in range(multiparm_point.evalAsInt()):
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, i, j)).eval()
pos = self.calcPosFromParametricUV(self.src_curve_geometry.prim(self.selected_src_prim), param_u)
list_position.append(pos)
return list_position
# IDから親ポリラインのインデックス番号を返す関数
def getParentPrimIndexFromId(self, id):
prim_index = -1
for i in range(len(self.src_curve_geometry.prims())):
uuid = self.src_curve_geometry.prim(i).attribValue("id")
if id == uuid:
prim_index = i
break
return prim_index
# 親ポリラインのインデックスからIDを返す関数
def getIdFromParentPrimIndex(self, src_prim_id):
return self.src_curve_geometry.prim(src_prim_id).attribValue("id")
# IDからMultiparmのインデックス番号を返す
def getMultiparmIndexFromId(self, id):
index = -1
for i in range(self.node.parm("prims").evalAsInt()):
parent_id = self.node.parm("parent_id_%d" % i).eval()
if id == parent_id:
index = i
break
return index
# PrimitiveのインデックスからMultiparmのインデックス番号を返す
def getMultiparmIndexFromPrim(self, prim_index):
id = self.src_curve_geometry.prim(prim_index).attribValue("id")
multiparm_index = self.getMultiparmIndexFromId(id)
return multiparm_index
#
# マウスポインタとポイントが交差していたらインデックス番号を、していなかったら-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, bspheres, radius):
# エッジとマウスポインタの交差判定
closeIndex = -1
closeDist = 1000000
# 球と直線の交差判定
for i in range(len(prims)):
b_radius = bspheres[i].attribValue("radius")
center = hou.Vector3(bspheres[i].attribValue("center"))
p0 = origin - center
p1 = (origin + direction) - center
v = (p1 - p0).normalized()
a = v.x() * v.x() + v.y() * v.y() + v.z() * v.z()
b = 2 * (p0.x() * v.x() + p0.y() * v.y() + p0.z() * v.z())
c = p0.x() * p0.x() + p0.y() * p0.y() + p0.z() * p0.z() - b_radius * b_radius
# 条件をクリアしたら直線と線分の交差判定
if b * b - 4 * a * c >= 0:
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
#
# マウスポインタとポリラインの交差判定からパラメトリックUVとワールド座標を求める
# 線分AB:マウスからの光線、線分CD:ポリラインのひと区間
#
def calcParametricPos(self, origin, direction, viewport, prim, radius):
isIntersect = False
closeDist = 1000000
closePos = hou.Vector3()
# 入力ポリラインからポイントを取得
list_points = prim.points()
perimeter = 0 # Polylineの全長
pos_coord = 0 # Polylineでの位置
# エッジごとの判定処理
# マウスポインタからの線分と各エッジを最短で結ぶ直線と交点を計算している
A = origin
B = origin + direction
ab = (B-A).normalized()
mouse_screen_pos = viewport.mapToScreen(A)
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)))
# 線分の範囲内か判別
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) - mouse_screen_pos).lengthSquared()
if dist < closeDist and dist < pow(radius, 2):
closeDist = dist
closePos = c1
pos_coord = perimeter + d1
isIntersect = True
perimeter += (D-C).length()
parametric_u = pos_coord / perimeter
parametric_u = max(0.0, min(parametric_u, 1.0))
return isIntersect, parametric_u, closePos
#
# パラメトリック座標からワールド座標を計算する
#
def calcPosFromParametricUV(self, prim, param_u):
# 入力ポリラインからポイントを取得
list_points = prim.points()
# ポリラインの全長を計算
perimeter = 0
for j in range(len(list_points)-1):
p0 = list_points[j].position()
p1 = list_points[j+1].position()
dist = (p1-p0).length()
perimeter += dist
pos = perimeter * param_u # 位置を計算
result = hou.Vector3()
sum = 0
for j in range(len(list_points)-1):
p0 = list_points[j].position()
p1 = list_points[j+1].position()
dist = (p1-p0).length()
if sum + dist >= pos:
rest = pos - sum
result = p0 + (p1 - p0).normalized() * rest
break
sum += dist
return result
#
# パラメトリックUVの前後を反転する
#
def swapParametricUV(self):
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
curve_index = int(self.selected_point / 2)
point_index = int(self.selected_point % 2)
multiparm_points = self.node.parm("points_%d_%d" % (multiparm_prim_index, curve_index))
value = []
for i in range(multiparm_points.evalAsInt()):
value.append(self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, i)).evalAsFloat())
if value[0] > value[1]:
value.sort()
for i in range(multiparm_points.evalAsInt()):
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, i)).set(value[i])
# 選択ポイントの入れ替え
if point_index%2 == 0:
self.selected_point += 1
else:
self.selected_point -= 1
#
# マウスイベント
#
def onMouseEvent(self, kwargs):
ui_event = kwargs["ui_event"]
device = ui_event.device()
self.origin, self.direction = ui_event.ray()
self.viewport = self.scene_viewer.curViewport()
#
# Mode == SELECT
#
if self.mode == Mode.SELECT:
# エッジとマウスポインタの交差判定
intersect_prim = self.isIntersectPolylines(self.origin, self.direction, self.viewport, self.src_curve_geometry.prims(), self.src_curve_bsphere.prims(), self.edge_range)
# 左ボタンクリック
if device.isLeftButton():
# ポリラインと交差してない場合
if intersect_prim == -1:
# 選択しているポリラインがあれば選択解除する
if self.selected_src_prim != -1:
self.selected_src_prim = -1
# ポリラインと交差している
else:
# 同じインデックスなら選択解除する
if intersect_prim == self.selected_src_prim:
self.selected_src_prim = -1
else:
self.selected_src_prim = intersect_prim
# 選択しているMultiparmを先頭にする(なければ生成する)
multiparm = self.node.parm("prims")
id = self.getIdFromParentPrimIndex(self.selected_src_prim)
index = -1
for i in range(multiparm.evalAsInt()):
parent_id = self.node.parm("parent_id_%d" % i).eval()
if id == parent_id:
index = i
break
if index == -1:
# 新しくMultiparm項目をつくる
index = multiparm.evalAsInt()
multiparm.insertMultiParmInstance(index)
self.node.parm("parent_id_%d" % index).set(id) # uuidをセット
hou.ui.setMultiParmTabInEditors(multiparm, index)
else:
hou.ui.setMultiParmTabInEditors(multiparm, index)
# 現在選択してるタブのインデックスを記録する
parm_editor = hou.ui.paneTabOfType(hou.paneTabType.Parm)
selected_tab_index = parm_editor.multiParmTab('prims')
self.node.parm("tab_index").set(selected_tab_index)
# Drawablesジオメトリの更新
if intersect_prim != -1:
self.intersect_prim = intersect_prim
self.updateGuideSelectingLine()
self.guideSelectingLine.show(True)
else:
self.intersect_prim = -1
self.guideSelectingLine.show(False)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
#
# Mode == EDIT
#
elif self.mode == Mode.EDIT:
# 左ボタンクリック
if device.isLeftButton():
# ドラッグ中
if self.drag == True:
# スナップをいれる
snapFlag = False
isSnapPoint = self.node.parm("snap").eval()
if isSnapPoint:
# Multiparmからポイントリストをつくる
parm_pos = self.getPointPositionsFromMultiparm(self.selected_src_prim)
for i in range(len(parm_pos)):
if i != self.selected_point: # ドラッグしているポイント番号は対象外に
closeIndex = self.isIntersectPoints(self.origin, self.viewport, [parm_pos[i]], self.point_range)
if closeIndex != -1:
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
curve_index = int(i / 2)
point_index = int(i % 2)
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, point_index)).eval()
snapFlag = True
break
if snapFlag == True:
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
curve_index = int(self.dragIndex / 2)
point_index = int(self.dragIndex % 2)
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, point_index)).set(param_u) # 座標をセット
else:
# カーブとの交差を計算し、座標を取得する
isIntersect, param_u, pos = self.calcParametricPos(self.origin, self.direction, self.viewport, self.src_curve_geometry.prim(self.selected_src_prim), self.edge_selecting_range)
if isIntersect:
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
curve_index = int(self.dragIndex / 2)
point_index = int(self.dragIndex % 2)
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, point_index)).set(param_u) # 座標をセット
# ドラッグしてない場合
else:
# マウスポインタと生成カーブの交差判定
intersect_curve = self.isIntersectPolylines(self.origin, self.direction, self.viewport, self.gen_curve_geometry.prims(), self.gen_curve_bsphere.prims(), self.edge_range)
self.log("intersect_curve:" + str(intersect_curve))
# 生成カーブと交差していたら
if intersect_curve != -1:
#
if self.selected_gen_prim == intersect_curve:
self.selected_gen_prim = -1
else:
self.selected_gen_prim = intersect_curve
self.selected_point = -1 # 選択ポイントは解除する
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_curves = self.node.parm("curves_" + str(multiparm_prim_index))
hou.ui.setMultiParmTabInEditors(multiparm_curves, intersect_curve)
else:
self.selected_gen_prim = -1
# マウスポインタと生成ポイントの交差判定
list_pos = self.getPointPositionsFromMultiparm(self.selected_src_prim)
intersect_point = self.isIntersectPoints(self.origin, self.viewport, list_pos, self.point_range)
# マウスポインタがポイントと交差していたらドラッグ状態へ遷移
if intersect_point != -1:
self.start()
#self.log(intersect_point)
self.dragIndex = intersect_point
self.drag = True
self.selected_point = intersect_point
curve_index = int(intersect_point/2)
#point_index = int(intersect_point%2)
self.selected_gen_prim = curve_index
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_curves = self.node.parm("curves_" + str(multiparm_prim_index))
hou.ui.setMultiParmTabInEditors(multiparm_curves, curve_index)
else:
#
if intersect_curve == -1:
self.intersect_src_prim = self.isIntersectPolylines(self.origin, self.direction, self.viewport, self.src_curve_geometry.prims(), self.src_curve_bsphere.prims(), self.edge_range)
# 親のポリラインとの交差
if self.intersect_src_prim == self.selected_src_prim:
# 新しくポイントを生成し、ポリラインを生成する
isIntersect, parametric_u, pos = self.calcParametricPos(self.origin, self.direction, self.viewport, self.src_curve_geometry.prim(self.intersect_src_prim), self.edge_range)
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_curves = self.node.parm("curves_%d" % (multiparm_prim_index))
curve_index = multiparm_curves.evalAsInt()
multiparm_curves.insertMultiParmInstance(curve_index)
#self.log("multiparm_prim_index:" + str(multiparm_prim_index))
#self.log("curve_index:" + str(curve_index))
multiparm_points = self.node.parm("points_%d_%d" % (multiparm_prim_index, curve_index))
multiparm_points.insertMultiParmInstance(0)
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 0)).set(parametric_u-0.01) # 座標をセット
multiparm_points.insertMultiParmInstance(1)
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 1)).set(parametric_u+0.01)
self.selected_point = -1
self.selected_gen_prim = -1
# 親のポリラインと交差していない
else:
# SELECTモードへ
#self.mode = Mode.SELECT
#self.modeChange()
#return
self.selected_point = -1
self.selected_gen_prim = -1
else:
self.drag = False
self.finish()
if self.mouse_pressed == False and self.mouse_pressed != self.old_mouse_pressed:
if self.selected_point != -1:
self.swapParametricUV() # 順序反転
# Drawablesジオメトリの更新
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
self.updateGuideCurveLine()
self.guideCurveLine.show(True)
self.updateGuideSelectedCurveLine()
self.guideSelectedCurveLine.show(True)
self.old_mouse_pressed = self.mouse_pressed
return True
#
# モード切替時
#
def modeChange(self):
self.selected_point = -1
self.intersect_gen_prim = -1
if self.mode == Mode.SELECT:
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
self.guideCurveLine.show(False)
self.guideEditPoint.show(False)
self.guideSelectedPoint.show(False)
HUD_TEMPLATE = {
"title": "Polyline Path", "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": "Select Prim", "key": "LMB"},
{"id": "focus_act", "label": "Camera Focus", "key": "F"},
]
}
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
elif self.mode == Mode.EDIT:
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.guideSelectingLine.show(False)
self.guideSelectedLine.show(False)
HUD_TEMPLATE = {
"title": "Polyline Path", "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 Point", "key": "LMB"},
{"id": "del_act", "label": "Delete Point", "key": "Del"},
{"id": "max_act", "label": "Maximize Length", "key": "A"},
#{"id": "split_act", "label": "Split Prim", "key": "V"},
{"id": "focus_act", "label": "Camera Focus", "key": "F"},
]
}
self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
self.log(self.mode.name)
#
# キーボードイベント
#
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.mode == Mode.SELECT:
if self.selected_src_prim != -1:
multiparm_prim = self.node.parm("prims")
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_prim.removeMultiParmInstance(multiparm_prim_index)
self.selected_src_prim = -1
self.selected_point = -1
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
# EDIT Mode
elif self.mode == Mode.EDIT:
# 選択ポイントのカーブを削除
if self.selected_src_prim != -1:
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
multiparm_curves = self.node.parm("curves_%d" % multiparm_prim_index)
if self.selected_point != -1:
curve_index = int(self.selected_point / 2)
multiparm_curves.removeMultiParmInstance(curve_index)
self.selected_point = -1
elif self.selected_gen_prim != -1:
multiparm_curves.removeMultiParmInstance(self.selected_gen_prim)
self.selected_gen_prim = -1
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideCurveLine()
self.guideCurveLine.show(True)
self.updateGuideSelectedCurveLine()
self.guideSelectedCurveLine.show(True)
return True
'''
# ポリラインの分割処理
if self.key_pressed in ("v"):
if self.mode == Mode.SELECT:
# 選択ポリラインとマウスポインタから、分割する座標(パラメトリック座標)を取得する
if self.selected_gen_prim != -1:
intersect_src_prim = self.isIntersectPolylines(self.origin, self.direction, self.viewport, self.gen_curve_geometry.prims(), self.gen_curve_bsphere.prims(), self.edge_range)
# 選択ポリラインとマウスポインタの交差からパラメトリック座標を取得する
if intersect_src_prim == self.selected_gen_prim:
# 親のポリラインを探す
id = self.gen_curve_geometry.prim(self.selected_gen_prim).attribValue("parent_id")
parent_index = self.getParentPrimIndexFromId(id)
isIntersect, param_u, pos = self.calcParametricPos(self.origin, self.direction, self.viewport, self.src_curve_geometry.prim(parent_index), self.edge_range)
param_end = self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 1)).evalAsFloat()
self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 1)).set(param_u) # 座標をセット
# 新規ポイント(プリミティブ)を生成する
multiparm = self.node.parm("prims")
index = multiparm.evalAsInt()
multiparm.insertMultiParmInstance(index)
multiparm_points = self.node.parm("points_" + str(index))
multiparm_points.insertMultiParmInstance(0)
multiparm_points.insertMultiParmInstance(1)
self.node.parm("parent_id_%d" % index).set(id) # 親のUUIDを設定
self.node.parm("pt_%d_%d" % (index, 0)).set(param_u) # 座標をセット
self.node.parm("pt_%d_%d" % (index, 1)).set(param_end) # 座標をセット
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
return True
'''
# 最大の長さのカーブに変更する
if self.key_pressed in ("a"):
if self.mode == Mode.EDIT:
if self.selected_point != -1:
list_pos = []
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
curve_index = int(self.selected_point / 2)
multiparm_curve = self.node.parm("curves_" + str(multiparm_prim_index))
for i in range(multiparm_curve.evalAsInt()):
if i != curve_index:
multiparm_point = self.node.parm("points_%d_%d" % (multiparm_prim_index, i))
for j in range(multiparm_point.evalAsInt()):
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, i, j)).eval()
list_pos.append(param_u)
list_pos.append(0.0)
list_pos.append(1.0)
list_pos.sort()
# 始点
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 0)).eval()
for i in range(len(list_pos)-1):
if(list_pos[i] < param_u and param_u < list_pos[i+1]):
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 0)).set(list_pos[i])
break
# 終点
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 1)).eval()
for i in range(len(list_pos)-1):
if(list_pos[i] < param_u and param_u < list_pos[i+1]):
self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, 1)).set(list_pos[i+1])
break
self.updateGuideEditPoint()
self.guideEditPoint.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
self.updateGuideCurveLine()
self.guideCurveLine.show(True)
return True
'''
# オーバーラップの解消
if self.key_pressed in ("c"):
if self.mode == Mode.SELECT:
if self.selected_gen_prim != -1:
if self.isPrimPolyline(self.selected_gen_prim) == 1:
list_indices = []
selectedParentPrim = self.getParentPrimIndexFromSelectedGenPrim()
multiparm = self.node.parm("prims")
for i in range(multiparm.evalAsInt()):
if i != self.selected_gen_prim:
id = self.node.parm("parent_id_%d" % i).eval()
parent = self.getParentPrimIndexFromId(id)
if parent == selectedParentPrim:
list_indices.append(i)
# 始点
pos = self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 0)).eval()
farPos = pos
for i in range(len(list_indices)):
start = self.node.parm("pt_%d_%d" % (list_indices[i], 0)).eval()
end = self.node.parm("pt_%d_%d" % (list_indices[i], 1)).eval()
if start < pos and pos < end:
if end > farPos:
farPos = end
self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 0)).set(farPos)
# 終点
pos = self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 1)).eval()
nearPos = pos
for i in range(len(list_indices)):
start = self.node.parm("pt_%d_%d" % (list_indices[i], 0)).eval()
end = self.node.parm("pt_%d_%d" % (list_indices[i], 1)).eval()
if start < pos and pos < end:
if start < nearPos:
nearPos = start
self.node.parm("pt_%d_%d" % (self.selected_gen_prim, 1)).set(nearPos)
self.updateGuideSelectedLine()
self.guideSelectedLine.show(True)
self.updateGuideSelectedPoint()
self.guideSelectedPoint.show(True)
return True
'''
# カメラのピボットを選択しているポイント座標にする
if self.key_pressed in ("f"):
#self.log(self.selected_point)
center = hou.Vector3()
flag = False
if self.mode == Mode.SELECT:
if self.selected_src_prim != -1:
points = self.src_curve_geometry.prim(self.selected_src_prim).points()
center = sum((p.position() for p in points), hou.Vector3()) / len(points)
flag = True
elif self.mode == Mode.EDIT:
multiparm_prim_index = self.getMultiparmIndexFromPrim(self.selected_src_prim)
if self.selected_point != -1:
curve_index = int(self.selected_point / 2)
point_index = int(self.selected_point % 2)
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, curve_index, point_index)).eval()
center = self.calcPosFromParametricUV(self.src_curve_geometry.prim(self.selected_src_prim), param_u)
flag = True
elif self.selected_gen_prim != -1:
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, self.selected_gen_prim, 0)).eval()
pos0 = self.calcPosFromParametricUV(self.src_curve_geometry.prim(self.selected_src_prim), param_u)
param_u = self.node.parm("pt_%d_%d_%d" % (multiparm_prim_index, self.selected_gen_prim, 1)).eval()
pos1 = self.calcPosFromParametricUV(self.src_curve_geometry.prim(self.selected_src_prim), param_u)
center = (pos0 + pos1)/2
flag = True
if flag:
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(center)
cam.setTranslation(center + hou.Vector3(0, 0, dist))
return True
# デバッグ用
if self.key_pressed in ("z"):
return True
self.key_pressed = None
return False
#
# 修飾キーのイベント
#
def onKeyTransitEvent(self, kwargs):
ui_event = kwargs["ui_event"]
if ui_event.device().isCtrlKey() == 1:
if self.mode == Mode.SELECT:
if self.selected_src_prim != -1:
self.mode = Mode.EDIT
self.modeChange()
elif self.mode == Mode.EDIT:
self.mode = Mode.SELECT
self.modeChange()
return False
def onDraw( self, kwargs ):
""" This callback is used for rendering the drawables """
handle = kwargs["draw_handle"]
self.guideSelectedPoint.draw(handle)
self.guideEditPoint.draw(handle)
self.guideSelectingLine.draw(handle)
self.guideSelectedLine.draw(handle)
self.guideCurveLine.draw(handle)
self.guideSelectedCurveLine.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 = "ViewerStatePolylinePathTool::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
関数
getIdFromParentPrimIndex()
getIdFromParentPrimIndex(int index) 返り値:string
ポリラインからIDを取得する。親のポリライン対象。
getParentPrimIndexFromId()
getParentPrimIndexFromId(string id) 返り値:int
IDから一致するプリミティブのインデックスを返す。親のポリライン対象。
getPointPositionsFromMultiparm()
getPointPositionsFromMultiparm(int index) 返り値:hou.Vector3[]
Multiparmの番号を引数に入れ、ポイント座標2つ(または1つ)を取得する。
calcPosFromParametricUV()
calcPosFromParametricUV(int prim_index; float param_u) 返り値:hou.Vector3
ポリライン上のパラメトリック座標からワールド座標を計算して返す。
calcParametricPos()
calcParametricPos(hou.Vector3 origin; hou.Vector3 direction; hou.GeometryViewport viewport; int prim; float radius) 返り値:bool isIntersect, float parametric_u, hou.Vector3 closePos
ビューポート上のマウスポインタ位置から近くのポリライン上の座標を返す。
isIntersectPoints()
isIntersectPoints(hou.Vector3 origin; hou.GeometryViewport viewport; hou.Vector3[] positions; float radius) 返り値:int
マウスポインタとポイントが交差しているかチェックする。交差していたらインデックスを返す。なければ-1が返る。
isIntersectPolylines()
isIntersectPolylines(hou.Vector3 origin; hou.Vector3 direction; hou.GeometryViewport viewport; hou.Prim[] prims; hou.Prim[] bspheres; float radius) 返り値:int
交差するポリラインのインデックスを返す。
Detail WrangleをHDA内に配置する
パラメトリックUVからポリラインを生成している。
//
// Multiparmのパラメータからプリミティブを生成する
// Run Over: Detail
//
int numprims = `chs("../prims")`;
for(int i = 0; i < numprims; i++)
{
int prim = addprim(0, "polyline");
}
wrangle1というノード名にしておき、Pythonコードから参照するようになっている。ここで生成されたポリラインがHDAの結果の出力となる。
//
// パラメータからポリラインを生成する
// Run Over: Primitives
//
// idからPrimのインデックスを探す
string id = chs(sprintf("../parent_id_%g", @primnum));
int parent_index = -1;
for(int j = 0; j < nprimitives(1); j++)
{
string uuid = prim(1, "id", j);
if(uuid == id)
{
parent_index = j;
break;
}
}
if(parent_index != -1)
{
int points = chi(sprintf("../points_%g", @primnum));
if(points == 2)
{
float start = chf(sprintf("../pt_%g_%g", @primnum, 0));
float end = chf(sprintf("../pt_%g_%g", @primnum, 1));
setprimattrib(0, "parent_id", @primnum, id);
setprimattrib(0, "u_start", @primnum, start);
setprimattrib(0, "u_end", @primnum, end);
/*
// 順序入れ替え
if(start > end)
{
float temp = start;
start = end;
end = temp;
}*/
int pts[] = primpoints(1, parent_index);
float curveLength = primintrinsic(1, "measuredperimeter", parent_index);
float sum = 0;
float startPos = curveLength * start;
float endPos = curveLength * end;
int flagStart = 0; // 通過したらフラグを立てる
int flagEnd = 0;
for(int j = 0; j < len(pts)-1; j++)
{
vector p0 = point(1, "P", pts[j]);
vector p1 = point(1, "P", pts[j+1]);
float length = length(p1-p0);
// 最初のポイントのエリアを発見
if(sum + length > startPos && flagStart == 0)
{
float rest = startPos - sum;
vector newPos = p0 + normalize(p1 - p0) * rest;
int pt = addpoint(0, newPos);
addvertex(0, @primnum, pt);
flagStart = 1;
}
// 終点ポイントのエリアを発見
if(sum + length > endPos && flagEnd == 0)
{
float rest = endPos - sum;
vector newPos = p0 + normalize(p1 - p0) * rest;
int pt = addpoint(0, newPos);
addvertex(0, @primnum, pt);
flagEnd = 1;
break;
}
// 一番最後まで到達した場合
else if(flagStart == 1)
{
int pt = addpoint(0, p1);
addvertex(0, @primnum, pt);
}
// 何もなければ加算
sum += length;
}
}
}
else
{
removeprim(0, @primnum, 1);
}
メモ
IDを介して親のポリラインと生成ポリライン、マルチパラメータ(UI)の相互のやりとりがあるためややこしくなっている。
生成ポリラインから親のポリラインのインデックスを探す
id = self.gen_curve_geometry.prim(self.selected_gen_prim).attribValue("parent_id")
parent_index = self.getParentPrimIndexFromId(id)
wrangleで生成したポリラインに割り振ったIDから探している。