C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

凸包に挑んでみる7

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

先日のスピードテストを反映させて
・処理時間の短縮
・実行後、ソリッド化
を修正しました。

#FusionAPI_python test_ConvexHull3D ver0.0.2
#Author-kantoku
#Description-新たなスケッチを作成し、ランダムに3Dな点を作成し、3Dな凸包を作成

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.

import adsk.core, adsk.fusion, traceback
import random, os, sys
this_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(this_dir)

#thx-varkenvarken
#chttps://michelanders.blogspot.com/2012/02/3d-convex-hull-in-python.html
#https://github.com/varkenvarken/blenderaddons/blob/master/chull.py
import chull  

def run(context):
    #Create Point Count
    p_count = 100
    
    ui = None
    try:
        #extension
        chull.Vertex.toPnt = ToPoint3d
        chull.Face.toSkt = ToSketch
        chull.Face.cog = InitCOGPoint
        adsk.core.Point3D.toVec = ToCHullVector
        adsk.fusion.Sketch.initSurf = InitSurface
        
        #preparation
        app = adsk.core.Application.get()
        ui = app.userInterface
        des = app.activeProduct
        root = des.rootComponent
        
        #RandomPoint
        skt = root.sketches.add(root.xYConstructionPlane)
        skt.name = 'points'
        CreateRandomPoint(skt, -10.0, 10.0, p_count)
        
        #time
        import time
        t = time.time()
        
        #coordinate array
        pnts = [p.geometry.asArray() for p in skt.sketchPoints]
        del pnts[0] #remove origin point
        
        #edges sketch
        skt = root.sketches.add(root.xYConstructionPlane)
        skt.name = 'edges'
        
        
        #ConvexHull
        face_count = InitConvexHull(pnts,skt)
        
        #finish
        ui.messageBox('Point Count:{}\nface Count:{}\ntime:{:.2f}s'
            .format(p_count,face_count,time.time()- t))
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

#ConvexHull
def InitConvexHull(point3d_list,target_sketch):
    if len(point3d_list) < 4: return
    
    #hull
    pnts = [chull.Vector(ary[0],ary[1],ary[2]) for ary in point3d_list]
    hull = chull.Hull(pnts)
    
    #edges
    QuickOn(target_sketch)
    [f.toSkt(target_sketch) for f in hull.faces]
    QuickOff(target_sketch)
    
    #profile
    profs = GetUseProfiles(target_sketch,hull.faces,0.001)
    
    #faces
    target_sketch.initSurf(profs)
    
    return len(hull.faces)

#有効なプロファイルのみ選択 重心距離で検索
def GetUseProfiles(skt,faces,tol):
    profs = skt.profiles
    if len(profs) == len(faces):
        return list(profs)

    cogs = [face.cog() for face in faces]
    
    lst = []
    for cog in cogs:
        hit = [prof for prof in profs
                if prof.areaProperties().centroid.distanceTo(cog) < tol]
        lst.extend((filter(lambda x:not x in lst, hit)))
    return lst

def QuickOn(skt):
    skt.isComputeDeferred = True
    skt.areProfilesShown = False
    skt.areDimensionsShown = False

def QuickOff(skt):    
    skt.areDimensionsShown = True
    skt.areProfilesShown = True
    skt.isComputeDeferred = False    

#RandomPoint
def CreateRandomPoint(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
    QuickOn(skt)
    [skt_Pnts.add(pnt) for pnt in pnts]
    QuickOff(skt)

# --- extension method ---
#chull.Vertex
def ToPoint3d(self):
    return adsk.core.Point3D.create(
        self.v.x,self.v.y,self.v.z)

#chull.Face
def ToSketch(self, skt):
    #Vertex
    pnts = skt.sketchPoints
    ps = [pnts.add(p.toPnt()) for p in self.vertex]
    
    #edge
    lins = skt.sketchCurves.sketchLines
    edges =[(ps[0],ps[1]),(ps[1],ps[2]),(ps[2],ps[0])]
    [lins.addByTwoPoints(p0,p1) for p0,p1 in edges]

#chull.Face
def InitCOGPoint(self):
    return adsk.core.Point3D.create(
        (self.vertex[0].v.x+self.vertex[1].v.x+self.vertex[2].v.x)/3,
        (self.vertex[0].v.y+self.vertex[1].v.y+self.vertex[2].v.y)/3,
        (self.vertex[0].v.z+self.vertex[1].v.z+self.vertex[2].v.z)/3)

#adsk.core.Point3D
def ToCHullVector(self):
    ary = self.asArray()
    return chull.Vector(ary[0],ary[1],ary[2])
    
#adsk.fusion.Sketch
def InitSurface(self,profs,tolerance = 0.001):
    if len(profs) < 1:
        return
        
    newBodyOpe = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    
    comp = self.parentComponent
    feats = comp.features
    
    objs = adsk.core.ObjectCollection.create()
    [objs.add(v) for v in profs]

    patches = feats.patchFeatures
    pats = patches.add(patches.createInput(objs,newBodyOpe))
    
    objs.clear()
    [objs.add(v) for v in pats.bodies]
    
    tol = adsk.core.ValueInput.createByReal(tolerance)
    stitches = feats.stitchFeatures
    stitches.add(stitches.createInput(objs, tol, newBodyOpe))

結局、全ての三角形は1つのスケッチに描く今までの方法にしました。
ライブラリから得られた三角形の重心と同じ位置にある
プロファイルのみを選択し、パッチを作成するようにしたところ
100%ソリッド化出来るようになりました。

こちらに点100個と1000個のサンプルをUpしました。
3D CAD Model Collection | GrabCAD Community Library
10000個も挑戦したのですが、時間がかかりすぎたため断念。
となると、ボディから凸包を作成するのはちょっと厳しそうです。