C#ATIA

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

凸包に挑んでみる4

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

面倒なため非公開ですが、前回のコードを視覚的に確認出来るように修正し
実際に四面体を見ると
f:id:kandennti:20180920112611p:plain
そこそこ良さそうなのですが、何度試してみても点群に対して薄い形状に
なりやすいんです。気のせいかも知れないのですが・・・。

そこで、知恵が無いなりにもう少し良さそうな4点を選択できないものか?
考えてみました。(イラストはあくまでイメージです)

ピンクが点群です。この点群に対して重心を求めます。(白い点)
f:id:kandennti:20180920112723p:plain

点群の中から、重心から一番遠い点を選ぶ(点1)
f:id:kandennti:20180920112731p:plain

点群の中から、点1から一番遠い点を選ぶ(点2)
f:id:kandennti:20180920112740p:plain

点群の中から、点1-点2ラインに対して一番角度のある点を選ぶ(点3)
結果的に線に対しては一番遠い・・・はず。
f:id:kandennti:20180920112746p:plain
(線の色、変わらんで欲しいのに)

点1,点2,点3を通過する仮平面を考え、平面から一番遠い点を選ぶ(点4)
f:id:kandennti:20180920112754p:plain

これであれば、極端に平べったい四面体にならないような
気がしたのですが、どうでしょうか?

#FusionAPI_python test2
#Author-kantoku
#Description-新たなスケッチを作成し、ランダムに3Dな点を作成し、ユニークな4点を取得

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

def run(context):
    ui = None
    
    #作成するランダムな点の数
    pointcount = 30
    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)
        
        #4面体頂点取得
        pnts = [p.geometry for p in skt.sketchPoints]
        uniq_pnts = GetUnique4Points(pnts)
        if uniq_pnts is None: return
        
        #パッチ
        skt = root.sketches.add(root.xYConstructionPlane)
        [CreateSketchLine(skt,p1,p2) for p1,p2 in itertools.combinations(uniq_pnts,2)]
        CreatePatchs(root,skt)
        
    except:
        if ui:
            ui.messageBox('エラー:\n{}'.format(traceback.format_exc()))



#ユニークな4点取得
def GetUnique4Points(pnts):
    if len(pnts) < 4:
        return None
    
    #重心
    cog = GetCog(pnts)
    
    #重心から一番遠い点
    p1 = max(pnts,key=lambda p:p.distanceTo(cog))
    
    #p1から一番遠い点
    p2 = max(pnts,key=lambda p:p.distanceTo(p1))
    
    #p1-p2ベクトル
    vec1_2 = p1.vectorTo(p2)
    
    #p1から一番角度の大きい点
    p3 = max(pnts,key=lambda p:vec1_2.angleTo(p1.vectorTo(p)))
    
    #p1,p2,p3を仮平面した際の法線
    normal = vec1_2.crossProduct(p1.vectorTo(p3))
    normal.normalize()
    
    #平面から一番遠い点
    p4 = max(pnts,key=lambda p:abs(normal.dotProduct(p1.vectorTo(p))))
    
    return [p1,p2,p3,p4]

#点群中心
def GetCog(points3D):
    bbox = adsk.core.BoundingBox3D.create(InitZeroPoint3D(),InitZeroPoint3D())
    [bbox.expand(p) for p in points3D]
    
    return InitMidPoint3D(bbox.maxPoint,bbox.minPoint)

#中間点
def InitMidPoint3D(p1,p2):
    ary1 = p1.asArray()
    ary2 = p2.asArray()
    pos = [(a1+a2)*0.5 for a1,a2 in zip(ary1,ary2)]
    
    return adsk.core.Point3D.create(pos[0],pos[1],pos[2])

#ゼロ点
def InitZeroPoint3D():
    return adsk.core.Point3D.create(0,0,0)

#ランダムな点の作成
def InitRandomPoint(skt, low, upp, count):
    pnts = [adsk.core.Point3D.create(
            random.uniform(low,upp),random.uniform(low,upp),random.uniform(low,upp)) 
            for dmy in range(count)]
        
    skt_Pnts = skt.sketchPoints
    [skt_Pnts.add(pnt) for pnt in pnts]
    
#2点間線分
def CreateSketchLine(skt,p1,p2):
    lines = skt.sketchCurves.sketchLines
    lines.addByTwoPoints(p1,p2)

#パッチ
def CreatePatchs(root,skt): 
    profs = lst2objCollection(skt.profiles)
    patches = root.features.patchFeatures
    newBodyFeatureOpe = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    patches.add(patches.createInput(profs,newBodyFeatureOpe))
    
#リスト→objCollection
def lst2objCollection(lst):
    ents = adsk.core.ObjectCollection.create()
    [ents.add(ent) for ent in lst]    
    return ents

#開発用
def dumpPoint(p):
    print("{:.3f},{:.3f},{:.3f}".format(p.x *10,p.y*10,p.z *10))
    
def dumpPoints(ps):
    [dumpPoint(p) for p in ps]

同じ点群に対して前回と今回のもので処理した結果です。
f:id:kandennti:20180920112808p:plain
黄色と体積の左は前回、緑と右が今回です。
2倍弱ぐらいにはなりました。

よく考えたら、遠い点ばかり選択するから平べったくなるのかも知れない。