Point on Polyline Tool

公開 更新

環境:Houdini 20.0.751

ポリライン上にポイントを打っていくツール。

Viewer Stateのコード

#
# Point on Polyline Tool 20240318
#
import hou
import viewerstate.utils as su
from enum import Enum
import math

# 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": "Point on Polyline", "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"},
            ]
        }
        
        # テンプレートを使用して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    # 選択中のポイントインデックス(Multiparm順の)
        self.selected_prim = -1     # 選択中の親プリミティブのインデックス
        self.intersect_prim = -1    # 交差中の親プリミティブのインデックス
        
        self.node = None
        self.drawable_geometry = None         # 入力されたポリラインのジオメトリ(表示用)
        self.src_curve_geometry = None     # 入力されたポリラインのジオメトリ
        self.src_curve_bsphere = None     # 入力されたポリラインのバウンディングスフィア

        # 入力用のフラグ
        self.mouse_pressed = False
        self.old_mouse_pressed = False
        self.drag = False   # ドラッグ中のフラグ
        
        # ポイントとエッジの判定距離(ピクセル)
        self.point_range = 16.0
        self.edge_range = 8.0
        self.edge_selecting_range = 96.0

        # ポイント
        drawable_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_point")
        self.guideEditPoint.addDrawable(drawable_edit_point)

        # ポイント(選択中:オレンジ)
        drawable_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("guideSelectedPoint")
        self.guideSelectedPoint.addDrawable(drawable_selected_point)

        # エッジ(点線)
        drawable_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(drawable_selecting_line)
        
        # エッジ(実線)
        drawable_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(drawable_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('src_bsphere').cook(force=True)
            self.src_curve_bsphere = hda_node.node('src_bsphere').geometry()
            
            # オーバーレイで描画する入力ポリライン
            hda_node.node('geo_src_curve').cook(force=True)
            colli = hda_node.node('geo_src_curve').geometry()
            hda_node.node('geo_draw').cook(force=True)
            geo_draw = hda_node.node('geo_draw').geometry()
            self.drawable_geometry = hou.SimpleDrawable(self.scene_viewer, geo_draw, "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.node.parm("onEnter").set(1)
            
    # Stateが中断された時に呼び出される
    def onInterrupt(self,kwargs):
        #self.showGeometry(False)
        #self.guideEditPoint.show(False)
        #self.guideSelectedPoint.show(False)
        self.guideSelectingLine.show(False)
        self.guideSelectedLine.show(False)
        
    # Stateの中断が再開された時に呼び出される
    def onResume(self, kwargs):
        self.drawable_geometry.show(True)
        
    # stateを終了させた時に呼び出される
    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 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)
                
        self.guideSelectingLine.setGeometry(poly_geo)   # update the drawable

    # 選択済みラインDrawableの更新処理
    def updateGuideSelectedLine(self):
        poly_geo = hou.Geometry()
        #self.log(self.selected_prim)
        if self.selected_prim != -1:
            poly_points = self.src_curve_geometry.prim(self.selected_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)
                
        self.guideSelectedLine.setGeometry(poly_geo)

    # 編集ポイントdrawableの更新処理(ポイントはUIパラメータから再構成する)
    def updateGuideEditPoint(self):
        # UIのジオメトリを描画する
        poly_geo = hou.Geometry()
        poly = poly_geo.createPolygon()

        id = self.getIdFromParentPrimIndex(self.selected_prim)
        index = self.getMultiparmIndexFromId(id)
        
        # Multiparmから点を描画する
        multiparm = self.node.parm("points_" + str(index))
        for i in range(multiparm.evalAsInt()):
            list_pos = self.getPointPositionsFromMultiparm(index)
            for j in range(len(list_pos)):
                point = poly_geo.createPoint()
                point.setPosition(list_pos[j])
                poly.addVertex(point)

        self.guideEditPoint.setGeometry(poly_geo)  # update the drawable

    # 選択済みポイントDrawableの更新処理
    def updateGuideSelectedPoint(self):
        # UIのジオメトリを描画する
        poly_geo = hou.Geometry()
        
        if self.selected_point != -1:
            poly = poly_geo.createPolygon()

            id = self.getIdFromParentPrimIndex(self.selected_prim)
            multiparm_prim_index = self.getMultiparmIndexFromId(id)
            
            parent_id = self.node.parm("parent_id_%d" % (multiparm_prim_index)).eval()
            parent = self.getParentPrimIndexFromId(parent_id)
            
            param_u = self.node.parm("u_coord_%d_%d" % (multiparm_prim_index, self.selected_point)).eval()
            pos = self.calcPosFromParametricUV(self.src_curve_geometry.prim(parent), param_u)

            point = poly_geo.createPoint()
            point.setPosition(pos)
            poly.addVertex(point)

        self.guideSelectedPoint.setGeometry(poly_geo)  # update the drawable

    # モード切替時
    def modeChange(self):
        self.selected_point = -1

        # SELECT MODE
        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": "Point on Polyline", "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)

        # EDIT MODE
        else:
            self.updateGuideEditPoint()
            self.guideEditPoint.show(True)
            self.guideSelectingLine.show(False)
            self.guideSelectedLine.show(False)
            self.updateGuideSelectedPoint()
            self.guideSelectedPoint.show(True)

            HUD_TEMPLATE = {
                "title": "Point on Polyline", "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": "Select/Add Point", "key": "LMB"},
                    {"id": "add_act", "label": "Delete Point", "key": "Del"},
                    {"id": "focus_act", "label": "Camera Focus", "key": "f"},
                ]
            }
            self.scene_viewer.hudInfo(hud_template=HUD_TEMPLATE)
        self.log(self.mode.name)

    #
    # Multiparmのポイントを描画する
    #
    def getPointPositionsFromMultiparm(self, index):
        list_position = []

        multiparm = self.node.parm("points_" + str(index))
        id = self.node.parm("parent_id_%d" % (index)).eval()
        parent = self.getParentPrimIndexFromId(id)
        for i in range(multiparm.evalAsInt()):
            if parent != -1:
                param_u = self.node.parm("u_coord_%d_%d" % (index, i)).eval()
                pos = self.calcPosFromParametricUV(self.src_curve_geometry.prim(parent), 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())):
            id2 = self.src_curve_geometry.prim(i).attribValue("id")
            if id == id2:
                prim_index = i
                break

        return prim_index

    # 親ポリラインのインデックスからIDを返す関数
    def getIdFromParentPrimIndex(self, src_prim_index):
        return self.src_curve_geometry.prim(src_prim_index).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
        
    #
    # マウスポインタとポイントが交差していたらインデックス番号を、していなかったら-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とワールド座標を求める
    # 
    def calcParametricPos(self, origin, direction, viewport, prim, radius):
        isIntersect = False    
    
        closeDist = 1000000
        closePos = hou.Vector3()
        
        # 入力ポリラインからポイントを取得
        list_points = prim.points()
        
        perimeter = 0   # Polylineの全長
        pos = 0         # Polylineでの位置

        # エッジごとの判定処理
        # マウスポインタからの線分と各エッジを最短で結ぶ直線と交点を計算している
        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
            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:
                closeDist = dist
                closePos = c1
                pos = perimeter + d1
                isIntersect = True

            perimeter += (D-C).length()

        parametric_u = pos / perimeter

        if parametric_u > 1.0:
            parametric_u = 1.0
        elif parametric_u < 0:
            parametric_u = 0.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

    #
    # マウスイベント
    #
    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_prim != -1:
                        self.selected_prim = -1
                # ポリラインと交差している
                else:
                    # 同じインデックスなら選択解除する
                    if intersect_prim == self.selected_prim:
                        self.selected_prim = -1
                    else:
                        self.selected_prim = intersect_prim
                        # 選択しているMultiparmを先頭にする(なければ生成する)
                        multiparm = self.node.parm("prims")
                        id = self.getIdFromParentPrimIndex(self.selected_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
        #
        else:
            # 左ボタンクリック
            if device.isLeftButton():
                # ドラッグ中
                if self.drag == True:
                    isIntersect, param_u, pos = self.calcParametricPos(self.origin, self.direction, self.viewport, self.src_curve_geometry.prim(self.selected_prim), self.edge_selecting_range)

                    id = self.getIdFromParentPrimIndex(self.selected_prim)
                    multiparm_prim_index = self.getMultiparmIndexFromId(id)
                    
                    self.node.parm("u_coord_%d_%d" % (multiparm_prim_index, self.selected_point)).set(param_u) # 座標をセット

                # ドラッグしていない
                else:
                    intersect_point = -1
                    #
                    # ポイントとの交差をチェックする
                    #
                    list_positions = [] # 座標を格納
                    list_indices = []   # 親のプリミティブインデックスを格納
        
                    # Multiparmから座標リストをつくり交差を調べる
                    id = self.getIdFromParentPrimIndex(self.selected_prim)
                    multiparm_prim_index = self.getMultiparmIndexFromId(id)
                    
                    multiparm = self.node.parm("points_" + str(multiparm_prim_index))
                    if multiparm.evalAsInt() > 0:
                        id = self.node.parm("parent_id_%d" % (multiparm_prim_index)).eval()
                        parent_index = self.getParentPrimIndexFromId(id)
                        for i in range(multiparm.evalAsInt()):
                            if parent_index != -1:
                                param_u = self.node.parm("u_coord_%d_%d" % (multiparm_prim_index, i)).evalAsFloat()
                                pos = self.calcPosFromParametricUV(self.src_curve_geometry.prim(parent_index), param_u)
                                list_positions.append(pos)
                                list_indices.append(i)
        
                        intersect_point = self.isIntersectPoints(self.origin, self.viewport, list_positions, self.point_range)

                    #self.log("intersect_point:" + str(intersect_point))

                    # ポイントと交差している
                    if intersect_point != -1:
                        if intersect_point == self.selected_point:
                            self.selected_point = -1
                        else:
                            self.start()
                            self.drag = True
    
                            # タブを選択状態にする
                            hou.ui.setMultiParmTabInEditors(multiparm, intersect_point)
                            self.selected_point = intersect_point

                    # ポイントと交差していない
                    else:
                        # 押下した瞬間
                        self.start()
                        if self.mouse_pressed != self.old_mouse_pressed:
                            # 親のポリラインとの交差していたら新規ポイントを生成する
                            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 intersect_src_prim == self.selected_prim:
                                isIntersect, parametric_u, pos = self.calcParametricPos(self.origin, self.direction, self.viewport, self.src_curve_geometry.prim(intersect_src_prim), self.edge_range)
                                
                                multiparm = self.node.parm("points_" + str(multiparm_prim_index))
                                index = multiparm.evalAsInt()
                                multiparm.insertMultiParmInstance(index)
                                
                                self.node.parm("u_coord_%d_%d" % (multiparm_prim_index, index)).set(parametric_u) # 座標をセット

                                # タブを選択状態にする
                                hou.ui.setMultiParmTabInEditors(multiparm, index)
                                self.selected_point = index
                            
                            # 選択解除
                            else:
                                self.selected_point = -1
            else:
                self.drag = False
                self.finish()

            # Drawablesジオメトリの更新
            
            # 選択されている場合
            if self.selected_point != -1:
                self.updateGuideSelectedPoint()
                self.guideSelectedPoint.show(True)
            else:
                self.guideSelectedPoint.show(False)
            
            # 常時表示する
            self.updateGuideEditPoint()
            self.guideEditPoint.show(True)

        #if device.isMouse():
        #    print("マウスが画面内にあります")
        #else:
        #    print("マウスは画面外です")

        self.old_mouse_pressed = self.mouse_pressed

        return True
        
    #  
    # キーボードイベント
    #
    def onKeyEvent(self,kwargs):
        ui_event = kwargs["ui_event"]
        self.key_pressed = ui_event.device().keyString()

        #Delキーが押されたら削除する
        if self.key_pressed in ("Del"):
            if self.mode == Mode.EDIT:
                if self.selected_point != -1:
                    id = self.getIdFromParentPrimIndex(self.selected_prim)
                    multiparm_prim_index = self.getMultiparmIndexFromId(id)
                
                    multiparm = self.node.parm("points_" + str(multiparm_prim_index))
                    multiparm.removeMultiParmInstance(self.selected_point)
                    self.selected_point = -1

                self.updateGuideSelectedPoint()
                self.guideSelectedPoint.show(True)
                self.updateGuideEditPoint()
                self.guideEditPoint.show(True)

            return True

        # カメラのピボットを選択しているポイント座標にする
        if self.key_pressed in ("f"):
            flag = False
            centroid = hou.Vector3()
            # SELECT MODE
            if self.mode == Mode.SELECT:
                if self.selected_prim != -1:
                    points = self.src_curve_geometry.prim(self.selected_prim).points()
                    centroid = sum((p.position() for p in points), hou.Vector3()) / len(points)
                    flag = True
            # EDIT MODE
            else:
                if self.selected_point != -1:
                    id = self.getIdFromParentPrimIndex(self.selected_prim)
                    multiparm_prim_index = self.getMultiparmIndexFromId(id)

                    param_u = self.node.parm("u_coord_%d_%d" % (multiparm_prim_index, self.selected_point)).eval()
                    centroid = self.calcPosFromParametricUV(self.src_curve_geometry.prim(self.selected_prim), param_u)
                    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(centroid)
                cam.setTranslation(centroid + 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 self.selected_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.guideEditPoint.draw(handle)
        self.guideSelectedPoint.draw(handle)
        self.guideSelectingLine.draw(handle) 
        self.guideSelectedLine.draw(handle) 

def createViewerStateTemplate():
    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "ViewerStatePointOnPolylineTool::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

Detail WrangleをHDA内に配置する

//
// パラメータからポリラインを生成する(スナップ)
// Run Over: Primitives
//

int isSnap = chi("../snap");

int numprims = `chs("../prims")`;
for(int i = 0; i < numprims; i++)
{

    // idからPrimのインデックスを探す
    string id = chs(sprintf("../parent_id_%g", i));
    
    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 numPoints = chi(sprintf("../points_%g", i));
        
        if(numPoints > 0)
        {
            float u_pos = chf(sprintf("../pt_%g_%g", i, 0));
            
            float curveLength = primintrinsic(1, "measuredperimeter", parent_index);
            float sum = 0;
            
            float pos = curveLength * u_pos;
            
            int pts[] = primpoints(1, parent_index);
            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 <= pos && pos < sum + length)
                {
                    vector newPos = p0;
                    if(isSnap)
                    {
                        float l0 = pos - sum;
                        float l1 = length(p1 - p0) - l0;
                        
                        if(l0 > l1)
                            newPos = p1;
                    }
                    else
                    {
                        float l0 = pos - sum;
                        newPos = p0 + normalize(p1 - p0) * l0;
                    }
                    
                    int pt = addpoint(0, newPos);
                    setpointattrib(0, "parent_id", pt, id);
                    setpointattrib(0, "u_pos", pt, u_pos);

                    break;
                }

                // 何もなければ加算
                sum += length;
            }
        }
    }
}

← 記事一覧へ