C#ATIA

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

オフセット面の作成場所2

こちらの続きです。
オフセット面の作成場所 - C#ATIA

前回の最後に記載した
コンポーネントからオカレンスをどうやったら取得できるのか?”
のテストです。

ややこしいのですが、こんな状態のデータでテストします。
f:id:kandennti:20181002130440p:plain
青部分がルートコンポーネント
赤Bodyを最初に選択してテスト(root - コンポーネント1:1 のBody)
緑Bodyを後で選択してテスト(root - コンポ1:1 - コンポ2:1 - コンポ3:1 のBody)

このようなコードを用意しました。

#FusionAPI_python test_offset
#Author-kantoku
#Description-オフセット面の作成

import adsk.core, adsk.fusion, traceback
 
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        #選択
        sel = Sel('Select Base Body','SolidBodies')
        if sel is None:
            return
        
        #選択したボディ
        bd = sel.entity
        
        #ボディからコンポーネント
        comp = bd.parentComponent
        print('--start--')
        print('// 選択した ボディ名 - コンポ名 //')
        print('{}-{}'.format(bd.name,comp.name))
        
        #作業中のデザイン(作業中のファイルみたいなもの)
        des = adsk.fusion.Design.cast(app.activeProduct)
        
        #ルートコンポーネント(全体のコンポーネント)
        root = des.rootComponent
        
        #コンポーネントのメソッド・プロパティ
        comps = root.allOccurrencesByComponent(comp)
        print('// allOccurrencesByComponent //')
        [DumpOccInfo(o) for o in comps]
        
        comps = root.occurrencesByComponent(comp)
        print('// occurrencesByComponent //')
        [DumpOccInfo(o) for o in comps]        
        
        comps = comp.occurrences
        print('// occurrences //')
        [DumpOccInfo(o) for o in comps]  

        print('--done--')     
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

#オカレンス情報
def DumpOccInfo(occ):
    print('--occ info --\nocc name : {}\n子Occ数 : {}\nコンポ名 : {}\n'
        .format(occ.name,len(occ.childOccurrences),occ.component.name))

#選択
def Sel(msg, selFilter):
    app = adsk.core.Application.get()
    ui  = app.userInterface
    try:
        return ui.selectEntity(msg, selFilter)
    except:
        return None

コンポーネントクラスにはオカレンスが関連しそうなメソッド・プロパティが
3個ありました。
・allOccurrencesByComponent(メソッド)
・occurrencesByComponent(メソッド)
・occurrences(プロパティ)
正直なところ、APIのHelp読んでも意味がわからないのでテストした
ってことです。 通常 "プロパティでしょ" とは思っています。


赤Bodyを選択した結果はこちら

--start--
// 選択した ボディ名 - コンポ名 //
Body1-コンポーネント1
// allOccurrencesByComponent //
--occ info --
occ name : コンポーネント1:1
子Occ数 : 2
コンポ名 : コンポーネント1

// occurrencesByComponent //
--occ info --
occ name : コンポーネント1:1
子Occ数 : 2
コンポ名 : コンポーネント1

// occurrences //
--occ info --
occ name : コンポーネント2:1
子Occ数 : 1
コンポ名 : コンポーネント2

--occ info --
occ name : コンポーネント4:1
子Occ数 : 0
コンポ名 : コンポーネント4

--done--

赤Bodyを選択した際に欲しいのは、"コンポーネント1:1" です。
occurrences(プロパティ)で得られるのは、そのコンポーネント
ぶら下がっているコンポーネント(子コンポーネント)です。

"コンポーネント3:1" が得られていないので、孫以下は見ていないです。
※ならば、ChildrenOccurrenceのような名称の方がわかりやすい!!


緑Bodyを選択した結果はこちら

--start--
// 選択した ボディ名 - コンポ名 //
Body1-コンポーネント3
// allOccurrencesByComponent //
--occ info --
occ name : コンポーネント3:1
子Occ数 : 0
コンポ名 : コンポーネント3

// occurrencesByComponent //
// occurrences //
--done--

occurrences(プロパティ)については、子供のオカレンスが無い為
出力無しは納得です。

occurrencesByComponent(メソッド)の出力無しについては
ルートコンポーネントから見て、緑Bodyの入っているオカレンスが
ひ孫の位置にあるため得られないため、出力無しとなるのだろうと
思います。(あくまで対象は子供のオカレンスのみ)

allOccurrencesByComponent(メソッド)は "all" が付いている
だけあって呼び出したコンポーネント(今回はルートコンポーネント
以下のオカレンスであれば取得できるようです。
※よく見たらallOccurrences(プロパティ)もありましたが、
 動作は察しが付きますし、目的のものでも無さそうです。


で、探した限り直接コンポーネントから該当するプロパティは
無さそうな気がしています。
その為、拡張メソッド用の関数を用意し昨日のオフセットスプリクトを
修正しました。

#FusionAPI_python test_offset2
#Author-kantoku
#Description-オフセット面の作成

import adsk.core, adsk.fusion, traceback
 
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        des = adsk.fusion.Design.cast(app.activeProduct)
        
         #拡張
        adsk.fusion.Component.toOcc = toOccurrenc
        
        sel = Sel('Select Solid Face','SolidFaces')
        if sel is None:
            return
            
        face = sel.entity
        CreateZeroOffset(face)
        
        ui.messageBox('Done')
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
            
def Sel(msg, selFilter):
    app = adsk.core.Application.get()
    ui  = app.userInterface
    try:
        return ui.selectEntity(msg, selFilter)
    except:
        return None
    
def CreateZeroOffset(face):
    zero = adsk.core.ValueInput.createByString('0 cm')
    newBody = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    
    #選択された面のあるボディのコンポーネント取得
    comp = face.body.parentComponent
    
    #コンポーネントのフューチャーのオフセットを取得
    offsets = comp.features.offsetFeatures
    
    objs = adsk.core.ObjectCollection.create()
    objs.add(face)
    
    occ = comp.toOcc()
    if not occ is None:
        occ.activate()
    offsets.add(offsets.createInput(objs, zero, newBody))
    
#adsk.fusion.Component 拡張メソッド
def toOccurrenc(self):
    root = self.parentDesign.rootComponent
    if self == root:
        return None
        
    occs = [occ
            for occ in root.allOccurrencesByComponent(self)
            if occ.component == self]
    return occs[0]

意図した位置にオフセット面が作成出来るようになりました。
本来であれば実行前のコンポーネントをアクティブにして終了したいの
ですが、ルートではオカレンスが取得できない為、オフセット面を
作った状態で終了と、雑な処理になっています。


Fusion360APIの場合、コンポーネントとオカレンスは密接な関係だと
思っていますが、特にオカレンスに関してはわかりにくいのが本音です。
知っていることを含めまとめると

・ルートコンポーネントにはオカレンスが無い。
 (ルート自体は固定されていて、移動等出来ない為だと思う)

・Treeに "コンポーネント" と書かれているものは実はオカレンス
 nameプロパティを取得した感じだと、こんなイメージ。
f:id:kandennti:20181002130518p:plain
 名前が異なる為、「itemByName」では取得できない。

・"コンポーネント" は各コンポーネントを作業しているような状態で
 ルートから見た際(移動等位置決めしている場合)オカレンスとして
 扱う必要がある。

・オカレンスは重要なのにBRep~(例えばBRepBody)には、直接
 オカレンスを取得する方法が無い。(と思う)
 parentComponentメソッドはあるけど、parentOccurrenceは無い。

・手動の "コンポーネントのアクティブ化" はAPIの場合、
 Occurrence.activate() になるようですが、ルートコンポーネント
 オカレンスが無いのでどうやれば良いのだろうか?

Helpも "コンポーネント" と説明している部分が圧倒的多数なのですが
実際はオカレンスを説明している部分もありそうな感じです。

ローコマンド

以前こちらにも書いたものに近いです。
スイープコマンドのロー - C#ATIA

こちらを見て感心しました。
Developed 2D to folded surface - DASSAULT: CATIA products - Eng-Tips
確かめていませんが、Lawコマンド使えば出来そう。

これをFusion360で行う為のスプリクトも考えているのですが、
知識が足らずに挫折中。

オフセット面の作成場所

サーフェスのオフセット面を作りたいのですが、
思うように出来なくて悩んでます。

f:id:kandennti:20181001183859p:plain
こんな感じの状態で、Body1の選択された青い面のオフセット面を
作成したいのです。但し、Body1はComponent1:1に有り、
それとは異なるRootのコンポーネントがアクティブな状態です。
そして、Body1と同じコンポーネントにオフセット面を作成したいんです。

こんなコードにしました。

#FusionAPI_python test_offset
#Author-kantoku
#Description-オフセット面の作成
import adsk.core, adsk.fusion, traceback
 
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        sel = Sel('Select Solid Face','SolidFaces')
        if sel is None:
            return
            
        face = sel.entity
        CreateZeroOffset(face)
        
        ui.messageBox('Done')
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
            
def Sel(msg, selFilter):
    app = adsk.core.Application.get()
    ui  = app.userInterface
    try:
        return ui.selectEntity(msg, selFilter)
    except:
        return None
    
def CreateZeroOffset(face):
    zero = adsk.core.ValueInput.createByString('0 cm')
    newBody = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    
    #選択された面のあるボディのコンポーネント取得
    comp = face.body.parentComponent
    print(comp.name)

    #コンポーネントのフューチャーのオフセットを取得
    offsets = comp.features.offsetFeatures
    
    objs = adsk.core.ObjectCollection.create()
    objs.add(face)

    offsets.add(offsets.createInput(objs, zero, newBody))

これを上記の状態で実行すると
f:id:kandennti:20181001183948p:plain
アクティブなコンポーネント(今回はRoot)にオフセット面が
出来ています。

手動で行った感覚であれば
”まぁ当然アクティブなコンポーネントに出来るでしょ”
と思うのですが、CreateZeroOffset関数内の

    #選択された面のあるボディのコンポーネント取得
    comp = face.body.parentComponent 
  ↑選択面の存在するコンポーネント
    print(comp.name)
  ↑確認 選択面のコンポーネント名を出力

    #コンポーネントのフューチャーのオフセットを取得
    offsets = comp.features.offsetFeatures 
       ↑上のコンポーネント内のオフセットフューチャー
         オブジェクトを取得

と言う手順でオフセット面を作成している為、選択面の存在する
コンポーネントにオフセット面が作成されるのが当然のように
感じています。

何となく納得できなかったので、フォーラムで検索すると
こちらをHit。
https://forums.autodesk.com/t5/fusion-360-api-and-scripts/activate-component-with-code/m-p/7470207
Ekinsさんは、僕に近い感覚の事を書いています。
(アクティブなコンポーネントに出来るわけじゃないと ・・・多分)

質問者さんは納得しなかったようで、やっぱりアクティブにしたい
ような雰囲気です。
(僕も、常にアクティブなコンポーネントに作成されている
 実感はあります。)

納得は出来ないものの目的地にたどり着く為には、やっぱり
目的のコンポーネントをアクティブにするしかない と思っている
のですが、コンポーネントからオカレンスをどうやったら
取得できるのか? を探し中。

迷路を作る2

こちらの続きです。
迷路を作る - C#ATIA

先日のスピードテストを反映させて、処理時間の短縮させたつもりなのですが
まだまだ遅いです。

念の為、コードを公開。
迷路自体は、こちらのライブラリを利用しています。
GitHub - boppreh/maze: Simple maze generator in Python
'maze.py' だけを利用しているので、スプリクトと同一フォルダ内に
置いて下さい。

#FusionAPI_python test_Maze ver0.0.2
#Author-kantoku
#Description-迷路ボディの作成

'''
The MIT License (MIT)

Copyright (c) 2014 BoppreH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''

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

#thx-boppreh
#https://github.com/boppreh/maze
import maze

def run(context):
    row_num = 10
    column_num = 10
    
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        NewDoc(app)
        root = app.activeProduct.rootComponent
        
        #拡張
        maze.Maze.exp = expWallMap
        
        #time
        t = time.time()
        
        #迷路取得
        mz = maze.Maze.generate(row_num,column_num)
        wall = mz.exp()
        print('迷路取得:{:.2f}s'.format(time.time()- t))
        
        #スケッチ作成
        unit = 2.0
        
        skt = root.sketches.add(root.xYConstructionPlane)
        
        QuickOn(skt)
        for j,y in enumerate(wall):
            for i,x in enumerate(y):
                if x:
                    initBox(skt,(i*unit,j*unit,0),((i+1)*unit,(j+1)*unit,0))
        QuickOff(skt)
        print('スケッチ:{:.2f}s'.format(time.time()- t))
        
        #パッド
        joinBody =  adsk.fusion.FeatureOperations.JoinFeatureOperation
        height = adsk.core.ValueInput.createByReal(unit)
        exts = root.features.extrudeFeatures
        
        objs = lst2objs([prof for prof in skt.profiles if IsQuad(prof)])
        exts.addSimple(objs, height, joinBody)
        print('パッド:{:.2f}s'.format(time.time()- t))
        
        #finish
        ui.messageBox('done\ntime:{:.2f}s'.format(time.time()- t))

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
            
def NewDoc(app):
    return app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)
    
def IsQuad(prof):
    profCurves = prof.profileLoops.item(0).profileCurves
    return True if profCurves.count == 4 else False

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

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

def initBox(skt,ary1,ary2):
    lines = skt.sketchCurves.sketchLines
    box = lines.addTwoPointRectangle(
        adsk.core.Point3D.create(ary1[0],ary1[1],ary1[2]),
        adsk.core.Point3D.create(ary2[0],ary2[1],ary2[2]))
    return box
    
def lst2objs(lst):
    objs = adsk.core.ObjectCollection.create()
    [objs.add(obj) for obj in lst]    
    return objs
    
#maze.Maze
def expWallMap(self):
    return [convIsWallLst(v) for v in self._to_str_matrix()[::-1]]

def convIsWallLst(lst):
    return [True if v == 'O' else False for v in lst]

面倒なため、こちらにまとめてデータをUpしました。
3D CAD Model Collection | GrabCAD Community Library

設定を変えれば、こんな感じのものが出来ます。
f:id:kandennti:20181001114648p:plain
でも、5分ぐらい時間がかかります。

処理時間を調べると
迷路取得:0.01s
スケッチ:254.86s
パッド:280.70s
こんな感じで、スケッチの処理時間が予想外に多かったです。
1マス毎にスケッチを描いているのが無駄なのは、気が付いて
いるのですが、どの様にすれば効率良くなるのだろう?
(グラフ構造っぽいことをしなきゃならないのかな・・・)

凸包に挑んでみる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個も挑戦したのですが、時間がかかりすぎたため断念。
となると、ボディから凸包を作成するのはちょっと厳しそうです。

押し出しのスピードテスト2

こちらの続きです。
押し出しのスピードテスト1 - C#ATIA

前回、押し出しの最速処理の "テスト3" の問題点についてです。

少し前に作成した3D凸包(パッチ)や
凸包に挑んでみる6 - C#ATIA
迷路(押し出し)
迷路を作る - C#ATIA
に取り組むまで気が付きませんでした。


3D凸包については "テスト3" の方法でパッチを作成しています。
その時は全く問題点に気が付いていませんでした。
念のため、サンプルデータをこちらにUpしています。
3D CAD Model Collection | GrabCAD Community Library
(ConvexHull3D_NG.f3d)
処理は、履歴で見るとわかりやすいのですが、こんな感じの事をしています。
f:id:kandennti:20180928130631p:plain
実は最後はソリッド化させているつもりなのですが、実際はソリッドに
なっていません。
Fusion360サーフェスでも閉じた瞬間にソリッドになります)
何度やってもソリッド化される事の方が "まれ" です。

ステッチコマンドを見てみると
f:id:kandennti:20180928130643p:plain
赤印部分でエッジの情報が表示されていますが、全156本のうち
ステッチされるのは150本だけなんです。
左側の赤い三角形部分にステッチされないエッジがあります。
(もう一ヶ所フリーの三角形があり3本x2ヶ所=6本ステッチされない)

原因が良くわからず、「多分このライブラリの処理が間違っているんだな」
ぐらいに受け止め、他人のせいにしてました。(我ながら、酷い・・・)


迷路に取り組んだ際、最初は同様の方法(テスト3)で取り組みました。
ライブラリからのデータを元にこんな感じのスケッチを作成します。

f:id:kandennti:20180928130652p:plain

各セルがブロックなのか?通路なのか?が返ってくる為、ブロック部分
だけ押し出せば完成です。頭の中ではこんなイメージです。

f:id:kandennti:20180928130659p:plain

ところが実際に出来上がるボディはこんな感じです。

f:id:kandennti:20180928130705p:plain

通路となる部分にはスケッチを作ったつもりではなかったのですが、
最終的に閉じた状態になってしまう為、通路部分もプロファイルと
なってしまいました。
過去に書いたこちらをすっかり忘れていましたよ。
プロファイルの考え方の違い - C#ATIA
恐らく、これが3D凸包でボディ化出来ない原因だろうと思われます。

結果的に良い方法がわからず、迷路を作成する際前回の "テスト1"
の一番遅い方法で作った末、異常に遅いものとなりました。

一番良いと思える方法は、一つのスケッチに全て描き必要なプロファイル
だけを抜き出して一発で押し出せると処理が速く、正しい結果を得られると
思っているのですが、"必要なプロファイル" を抜き出す為のアルゴリズム
イマイチわかっていないです。
・・・少しだけ思いつくのですが、どうだろう?

押し出しのスピードテスト1

タイトルが異なりますが、こちらの続きです。
スケッチのスピードテスト - C#ATIA

前回のスケッチに関しては、ある程度知っていたので、
本当はこちらの "押し出し" についてテストしたかったんです。

前回同様、スケッチに100個の四角を作り押し出します。
テスト内容は、作成方法を変更し各5回です。
f:id:kandennti:20180928111935p:plain

ベースにするコードはこちら。

#FusionAPI_python 押し出しの速度テスト
#Author-kantoku

import adsk.core, adsk.fusion, traceback
import time, inspect, itertools
        
DEBUG = False
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        unit_size = 2.0
        lst = [((v*3, 0.0, 0.0),(unit_size+v*3, unit_size, 0.0)) for v in range(100)]
        
        #test
        for i in range(5):
            doc = NewDoc(app)
            root = app.activeProduct.rootComponent
            
            t = time.time()
            
            funcname = test1(root,lst,unit_size)#ここ書き換え
            
            msg='{}-time:{:.2f}s'.format(funcname,time.time()- t)
            print(msg)
            
            if not DEBUG:
                doc.close(False)
                
        if DEBUG:
            ui.messageBox(msg)
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def NewDoc(app):
    return app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)

def initBox(skt,ary1,ary2):
    lines = skt.sketchCurves.sketchLines
    p1 = adsk.core.Point3D.create(ary1[0],ary1[1],ary1[2])
    p2 = adsk.core.Point3D.create(ary2[0],ary2[1],ary2[2])
    box = lines.addTwoPointRectangle(p1,p2)
    return box

def lst2objs(lst):
    objs = adsk.core.ObjectCollection.create()
    [objs.add(obj) for obj in lst]    
    return objs

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

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

QuickOn・QuickOffについては前回のテストからの産物です。

"押し出し" は、プロファイルかボディやパッチの平坦な面を元に
高さを付けてボディ化します。
前回のテストでプロファイルを非表示化することで処理時間を
短縮する効果が有ることがわかったのですが、テストを行っている
うちに、押し出しを行う為のプロファイルを選択するタイミングでは
プロファイルが表示されている必要が有ることがわかりました。
(非表示のままではボディが作成されません)


○テスト1
オーソドックスに、四角1個に対しスケッチを作成→押し出しです。

#test1 1個毎に スケッチ-押し出し
def test1(comp,pnts,height):
    
    exts = comp.features.extrudeFeatures
    joinBody =  adsk.fusion.FeatureOperations.JoinFeatureOperation
    h = adsk.core.ValueInput.createByReal(height)
    
    for (a1,a2) in pnts:
        #sketch
        skt = comp.sketches.add(comp.xYConstructionPlane)
        QuickOn(skt)
        initBox(skt,a1,a2)
        QuickOff(skt)
        
        #extrude
        exts.addSimple(skt.profiles[0], h, joinBody)
        
    return inspect.getframeinfo(inspect.currentframe())[2]

比較すべきものが無いのでこれが基準です。

test1-time:7.68s
test1-time:7.60s
test1-time:7.61s
test1-time:7.71s
test1-time:7.73s


○テスト2
1個のスケッチに全ての四角を作成し、プロファイル毎に押し出しです。

#test2 まとめてスケッチしプロファイル毎に押し出し
def test2(comp,pnts,height):
    #sketch
    skt = comp.sketches.add(comp.xYConstructionPlane)
    QuickOn(skt)
    [initBox(skt,a1,a2) for (a1,a2) in pnts]
    QuickOff(skt)
    
    #enums
    joinBody =  adsk.fusion.FeatureOperations.JoinFeatureOperation
    
    #extrude
    h = adsk.core.ValueInput.createByReal(height)
    exts = comp.features.extrudeFeatures
    
    [exts.addSimple(prof, h, joinBody) for prof in skt.profiles]
    
    return inspect.getframeinfo(inspect.currentframe())[2]

半分程の処理時間となります。スケッチを作成数が少ない分効果が大きいです。

test2-time:3.30s
test2-time:3.28s
test2-time:3.28s
test2-time:3.27s
test2-time:3.28s


○テスト3
1個のスケッチに全ての四角を作成し、全てのプロファイルを一度に押し出しです。

#test3 まとめてスケッチし、まとめて押し出し
def test3(comp,pnts,height):
    #sketch
    skt = comp.sketches.add(comp.xYConstructionPlane)
    QuickOn(skt)
    [initBox(skt,a1,a2) for (a1,a2) in pnts]
    QuickOff(skt)
    
    #enums
    joinBody =  adsk.fusion.FeatureOperations.JoinFeatureOperation
    
    #extrude
    h = adsk.core.ValueInput.createByReal(height)
    profs = lst2objs(skt.profiles)
    exts = comp.features.extrudeFeatures
    
    exts.addSimple(profs, h, joinBody)
    
    return inspect.getframeinfo(inspect.currentframe())[2]

現状知っている方法の中では最速だと思っています。
但し問題も抱えています。

test3-time:1.11s
test3-time:1.14s
test3-time:1.10s
test3-time:1.09s
test3-time:1.10s


○テスト4
四角1個に対しスケッチを作成しプロファイルをストック。
押し出しは1度に行います。

#test4 1個毎にスケッチを作りプロファイルをストック、まとめて押し出し
def test4(comp,pnts,height):
    #sketch
    objs = adsk.core.ObjectCollection.create()
    
    for (a1,a2) in pnts:
        skt = comp.sketches.add(comp.xYConstructionPlane)
        QuickOn(skt)
        initBox(skt,a1,a2)
        skt.areProfilesShown = True
        [objs.add(prof) for prof in skt.profiles]
        QuickOn(skt)
    
    #extrude
    exts = comp.features.extrudeFeatures
    joinBody =  adsk.fusion.FeatureOperations.JoinFeatureOperation
    h = adsk.core.ValueInput.createByReal(height)    
    
    exts.addSimple(objs, h, joinBody)
        
    return inspect.getframeinfo(inspect.currentframe())[2]

自分の中では、テスト3の問題を解決する為には現状これしか
思いつかないなぁと言う方法です。スケッチを多く作成している為
処理時間が劣っています。

test4-time:2.76s
test4-time:2.74s
test4-time:2.74s
test4-time:2.72s
test4-time:2.75s


○テスト5
基本的にはテスト4と同様の考え方で、処理の手順を少し変えました。

#test5 1個毎にスケッチを作りプロファイルをストック、まとめて押し出し
def test5(comp,pnts,height):
    #sketch
    skts = comp.sketches
    xy = comp.xYConstructionPlane
    
    skts = [skts.add(xy) for _ in pnts]
    [QuickOn(skt) for skt in skts]
    [initBox(skt,a1,a2) for ((a1,a2),skt) in zip(pnts,skts)]
    [QuickOff(skt) for skt in skts]
    
    objs = adsk.core.ObjectCollection.create()
    profs = [skt.profiles for skt in skts]
    [objs.add(prof) for prof in list(itertools.chain.from_iterable(profs))]
    [QuickOn(skt) for skt in skts]
    
    #extrude
    exts = comp.features.extrudeFeatures
    joinBody = adsk.fusion.FeatureOperations.JoinFeatureOperation
    h = adsk.core.ValueInput.createByReal(height)    
    
    exts.addSimple(objs, h, joinBody)
        
    return inspect.getframeinfo(inspect.currentframe())[2]

若干遅くなってしまいました。

test5-time:3.01s
test5-time:2.95s
test5-time:2.95s
test5-time:2.95s
test5-time:2.97s