C#ATIA

↑タイトル詐欺 主にCATIA V5 の VBA(最近はPMillマクロとFusion360APIが多い)

凸包に挑んでみる2

こちらの続きです。
凸包に挑んでみる1 - C#ATIA

昨日のイマイチな処理、使用すべきベクトルが間違っていたと言うお粗末な結末。
ある程度修正したので、利用価値があるものかどうか不明ながら公開。

#FusionAPI_python ConvexHull2D
#Author-kantoku
#Description-新たなスケッチを作成し、ランダムに2Dな点を作成し、凸包する

import adsk.core, adsk.fusion, traceback
import random

def run(context):
    ui = None
    
    #作成するランダムな点の数
    pointcount = 100

    try:
        #準備
        app = adsk.core.Application.get()
        ui = app.userInterface
        des = app.activeProduct
        root = des.rootComponent
        
        #スケッチと点の作成
        skt = root.sketches.add(root.xYConstructionPlane)
        InitRandomPoint(skt, -10.0, 10.0, pointcount)
        
        #時間測定開始
        import time
        t = time.time()
        print('-- start --')
        
        #凸包
        ConvexHullEdges = ExeConvexHull(skt.sketchPoints)
        [print("({:.3f},{:.3f})-({:.3f},{:.3f})"
            .format(b[0].x *10 ,b[0].y*10,b[1].x *10 ,b[1].y*10)) for b in ConvexHullEdges]
        
        [CreateSketchLine(skt,Vec2dtoPnt3d(v1) ,Vec2dtoPnt3d(v2)) for v1,v2 in ConvexHullEdges]
        
        #終了
        t = time.time()- t
        ui.messageBox(' Points Count:{}\n ConvexHull Edges Count:{}\n time:{:.2f}s'
            .format(pointcount,len(ConvexHullEdges),t))
        
    except:
        if ui:
            ui.messageBox('エラー:\n{}'.format(traceback.format_exc()))

#凸包処理
def ExeConvexHull(points):
    #スケッチ点群をVector2D化
    vecs = [adsk.core.Vector2D.create(p.geometry.x, p.geometry.y)
            for p in points]
                
    #X方向のMinMax取得
    vec_min = min(vecs,key=lambda v:v.x)
    vec_max = max(vecs,key=lambda v:v.x)
    
    #半分づつ処理
    boundarys = []
    boundarys = GetConvexList(vec_min,vec_max,vecs,boundarys)
    boundarys = GetConvexList(vec_max,vec_min,vecs,boundarys)
    
    return boundarys

#凸包となる点リスト作成 - 再帰やめたい
def GetConvexList(v1, v2, vecs, boundarys):
    #始点終点あh除外
    if v1 in vecs:
        vecs.remove(v1)
    if v2 in vecs:
        vecs.remove(v2)
    
    #v1, v2の直交ベクトル
    vec1_2 = v2.copy()
    vec1_2.subtract(v1)
    vec_crs = adsk.core.Vector2D.create(vec1_2.y * -1, vec1_2.x)
    
    #v1, v2の外側検索
    check_lst = []
    for v in vecs:
        vv = v.copy()
        vv.subtract(v1)
        if vec_crs.dotProduct(vv) < 0:
            check_lst.append((v,vec1_2.angleTo(vv)))

    if len(check_lst) < 1:
        #v1, v2の組み合わせでOK
        boundarys.append((v1, v2))
        return boundarys
    
    #一番外の点検索
    max_pnt,ang = max(check_lst,key=lambda p:p[1])
    
    vecs = [v for (v,a) in check_lst]
    boundarys = GetConvexList(v1,max_pnt,vecs,boundarys)
    boundarys = GetConvexList(max_pnt,v2,vecs,boundarys)
    
    return boundarys

#Point2D又はVecter2DからZ0のPoint3Dを作成
def Vec2dtoPnt3d(v):
    pnt = adsk.core.Point3D.create(v.x,v.y,0)
    return pnt

#2点間戦分
def CreateSketchLine(skt,p1,p2):
    lines = skt.sketchCurves.sketchLines
    line = lines.addByTwoPoints(p1,p2)

#ランダムな点の作成
def InitRandomPoint(skt, low, upp, count):
    pnts = [adsk.core.Point3D.create(
            random.uniform(low,upp),random.uniform(low,upp),0) 
            for dmy in range(count)]
        
    skt_Pnts = skt.sketchPoints
    [skt_Pnts.add(pnt) for pnt in pnts]
    return

凸包となる点を検索するアルゴリズムは、こちらの "ギフト包装法" では無く
https://ja.wikipedia.org/wiki/%E3%82%AE%E3%83%95%E3%83%88%E5%8C%85%E8%A3%85%E6%B3%95

こちらで説明されている "Quickhull" にしたつもりです。(直感的に速そうだったので)
http://asura.iaigiri.com/OpenGL/gl50.html

Fusion360のスケッチは2Dでは無く3Dの為、どうしようかと悩みました。
処理上、内外積を使う必要があり、内積はまぁ構わないのですが
外積は次元によって答えが変わってきます。(2Dと3Dしか知らないですが・・・)
結局、効率無視で2Dとして処理し、最後に3Dの線(スケッチの線)を作成しました。

Fusion360APIは、Vector3DとVector2Dの両方用意されているのですが、
Vector3Dには外積(crossProductメソッド)が用意されているのに
Help

Vector2Dには無いんです・・・。何でだろう?
Help


ついでに愚痴なのですが、Vector3D(2Dも同様)のメソッドが破壊的か?非破壊的か?が、
かなり迷いました。
・破壊的 - ベクトルそのものが変化してしまうもの
 add(和),subtract(差),normalize(単位化),scaleBy(スケール),transformBy(変換)
・非破壊的 - 戻り値で新たなベクトルが返ってくるもの
 copy(複製 ←深いよ),crossProduct(外積)
こうして見ると外積だけか・・・個人的には全て非破壊的にして欲しい。