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);
        }
    }
}

← 記事一覧へ