2026年3月8日日曜日

3点を通る球体~Dynamo

回転球体法

避雷針の保護範囲を計測する方法として回転球体法(JIS Z 9290‑3)があります。様々なパターンがありますが、3つの避雷針で保護される範囲はそれぞれの先端に接する半径R(レベルによる)の下側ということになります。

このような「3点A,B,Cに接する半径Rの球」をDynamoで作成してみましょう。

3点に接する半径Rの球を作成するには?

幾何学的には案外単純

3点A,B,Cを通る半径Rの球体の中心点は、△ABCの外接円の中心を、面の法線方向に伸ばした線上にあります。幾何学的には割と単純な話ですがこれをDynamoで解くとなると一工夫必要です。

Dynamoの新規グラフを作成する

[管理]タブ>[ビジュアルプログラミング]パネル>[Dynamo]をクリックし[新規作成]を選択

プログラムの実行モードは[手動]に切り替えておきましょう。


Dynamoでは点が指定できない

現在のDynamoの仕様ではユーザーが指定した点を取得することはできません。そこで要素のエッジを選択してその両端のうちZの値が大きいほうを選ぶようにしてみます。以下はその部分を示すグラフです。

選択したエッジの両端のうちZが大きいほうを選ぶ

ここで使用しているノードは次の通りです。

  • SelectEdge : ユーザーにエッジを選択を促す
  • Curve.StartPoint/EndPoint : エッジの始点と終点
  • List.Create : エッジの始点と終点をリストにする
  • List.MaximumItemByKey : リストのうちPoint.Zの値が最大のアイテムを返す

これを複製して3つ追加します。

3点を選ぶために複写

この状態でSelectEdgeの選択ボタンを押して、3つの避雷針のエッジを選択しておきます。

半径を入力

半径を入力するのですが、入力自体はm単位で、計算は基本的にmmなのでメートルからミリに変換します。1000を掛ければいいのですが、今回は単位変換ノードを使用します。

半径を取得
ここで使用しているノードは次の通りです。

  • Number : 半径をユーザーが入力
  • Convert By Unit : メートルをミリメートルに変換

3点を通り半径Rの球体の中心点を求める

幾何学的には簡単、と申し上げましたがDynamoのノードだけでこれを行うのはなかなか大変です。そこでPythonスクリプトを使って求めることにします。

まず、Puthon Scriptノードを追加し+ボタンをおして、入力端子をIN[0]~[3]まで作成します。


次にノードを右クリックして[編集]を選択してエディタを開きます。そして既存のコードを削除して、以下を丸ごと張り付けてください。


# Python 標準ライブラリおよび DesignScript ライブラリをロード
# Dynamo Python Node
# IN[0]=p1, IN[1]=p2, IN[2]=p3 : Geometry.Point
# IN[3]=r : radius (same unit as points)
# OUT = c1 : Geometry.Point (higher Z)
import math
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import Point, Vector
# このノードへの入力は、リスト形式で IN 変数に格納されます。
p1 = IN[0]
p2 = IN[1]
p3 = IN[2]
r  = float(IN[3])
# この行の下にコードを配置します
def circumcenter(p1, p2, p3, eps=1e-12):
    a = Vector.ByTwoPoints(p1, p2)
    b = Vector.ByTwoPoints(p1, p3)
    n0 = a.Cross(b)
       
    if n0.Length < eps:
        raise Exception("3 points are nearly collinear")
      
    aLen2 = a.Dot(a)
    bLen2 = b.Dot(b)
    n0Len2 = n0.Dot(n0)
    term1 = b.Cross(n0).Scale(aLen2)
    term2 = n0.Cross(a).Scale(bLen2)
    c_vec = term1.Add(term2).Scale(1.0 / (2.0 * n0Len2))
    c = p1.Add(c_vec)
    n = n0.Normalized()
    return c, n
    
def sphere_center_z_higher(p1, p2, p3, r):
    
    c, n = circumcenter(p1, p2, p3)
    rho = c.DistanceTo(p1)
    if r < rho:
        raise Exception("r is smaller than circumradius")
    h = math.sqrt(r*r - rho*rho)
    cA = c.Add(n.Scale(h))
    cB = c.Add(n.Scale(-h))
    return cA if cA.Z >= cB.Z else cB
# 出力を OUT 変数に割り当てます。
OUT = sphere_center_z_higher(p1, p2, p3, r)


ここでは詳細な説明は省きますが、コピーするときは一度メモ帳などに貼り付けて書式を解除したうえで貼り付けてください。

球体の中心は三角形ABC(p1-p2-p3)の上側と下側の両方にあるので、ここでは上側の中心点のみをOUTのポートに返すようにしています。

[保存して実行]を押して閉じたらIN[0]~[2]に指定した3点を、IN[3]に半径を接続します。

Python Scriptノードに入力を接続

球体をダイレクトシェイプで作成

中心がわかったのでダイレクトシェイプを使って球体を作成します。まずはSOLIDで球体を作成し、それをもとにダイレクトシェイプを作成します。

ダイレクトシェイプの作成

ここで使用しているノードは

  • Sphere.ByCenterPointRadius : 中心点と半径で球体SOLIDを作成
  • DirectShape.ByGeometry : ジオメトリとカテゴリを指定してダイレクトシェイプを作成
  • Categories : カテゴリを選択


Sphere.ByCenterPointRadiusのcenterPonintに球体の中心点を、Radiusに半径を接続します。

中心点と半径を接続


これで実行してみましょう。球体の大きさが避雷針の互いの距離よりも短かければ球体が作成されるはずです。

3点を通る半径Rの球体