C#ATIA

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

スケッチ曲線を円弧近似化する

以前こちらで作成したCATIAのマクロをFusion360用にしてみました。
曲線と戦ってみる9 - C#ATIA

#FusionAPI_python
#Author-kantoku
#Description-ArcApproximation
#スケッチ曲線の円弧近似化

import adsk.core, adsk.fusion, adsk.cam, traceback

def run(context):
    ui = None
    
    #円弧近似化トレランス 単位はCm!!
    tolerance = 0.001
    
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        #面選択
        selFilters = 'SketchCurves'
        sel = Sel('カーブを選択/ESC-中止', selFilters )
        if sel is None: return
        crv = sel.entity        
        
        #点群
        points = InitPointsOnCurve(crv.geometry, tolerance)
        
        #円弧近似
        threeArcs = []
        threeArcs = InitThreePointArc(0, len(points)-1, tolerance, points, threeArcs)
        
        if len(threeArcs) < 1:
            ui.messageBox('円弧近似化できませんでした')
            return
        
        #スケッチ作成
        des = adsk.fusion.Design.cast(app.activeProduct)
        comp = des.rootComponent
        skt = comp.sketches.add(comp.xYConstructionPlane)
        skt.name = "ArcApproximation"
        
        #円弧作成
        skt_arcs = skt.sketchCurves.sketchArcs
        [skt_arcs.addByThreePoints(p1,p2,p3) for (p1,p2,p3) in threeArcs]
        
        ui.messageBox('{}個の円弧を作成しました'.format(len(threeArcs)))
        
    except:
        if ui:
           ui.messageBox('エラー\n{}'.format(traceback.format_exc()))

#外心円
def GetCircumCircleOFTraiangle(p1,p2,p3):
    #3点ベクトル取得
    vec1_2 = adsk.core.Vector3D.cast(p2.asVector())
    vec1_2.subtract(p1.asVector())
    vec1_2.normalize()
    
    vec2_3 = adsk.core.Vector3D.cast(p3.asVector())
    vec2_3.subtract(p2.asVector())
    vec2_3.normalize()
    
    #平行チェック
    if vec1_2.isParallelTo(vec2_3):
        return None
    
    #P2_P3間 オイラー線
    eulerLine = vec2_3.crossProduct(vec1_2.crossProduct(vec2_3))
    eulerLine.normalize()
    
    #中間点
    p1_2 = GetMidPoint3D(p1,p2)
    p2_3 = GetMidPoint3D(p2,p3)

    #不明
    nv = vec1_2.dotProduct(eulerLine)
    t = (vec1_2.dotProduct(p1_2.asVector()) - vec1_2.dotProduct(p2_3.asVector())) / nv
    
    #外心
    center = GetCircumCenter(p2_3,eulerLine,t)
    
    #半径
    radius = center.distanceTo(p1)
    
    return (center, radius)
    
#外心
def GetCircumCenter(p,v,l):
    pos = (p.x + v.x * l,
           p.y + v.y * l,
           p.z + v.z * l)    
    return adsk.core.Point3D.create(pos[0],pos[1],pos[2])

#中間点
def GetMidPoint3D(p1,p2):
    pos = ((p1.x + p2.x) * 0.5,
           (p1.y + p2.y) * 0.5,
           (p1.z + p2.z) * 0.5)
    return adsk.core.Point3D.create(pos[0],pos[1],pos[2])

def IsInTolerance(center, radius, tol, points):
    dist_lst = [radius - center.distanceTo(p) for p in points]
    res = [d for d in dist_lst if d > tol]
    
    return True if len(res) < 1 else False

#3点円弧 再帰
def InitThreePointArc(startIdx, endIdx, tol, points, threeArcs):
    
    if 2 > (endIdx - startIdx):
        return threeArcs
    
    #中間idx
    midIdx = int((startIdx + endIdx) * 0.5)
    arc = GetCircumCircleOFTraiangle(points[startIdx],
                                     points[midIdx], 
                                     points[endIdx])
    if arc is None:
        return threeArcs
    
    #円弧評価
    if IsInTolerance(arc[0], arc[1], 0.001, points[startIdx+1:endIdx-1]):
        threeArcs.append([points[startIdx],points[midIdx],points[endIdx]])
    else:
        threeArcs = InitThreePointArc(startIdx, midIdx, tol, points, threeArcs)
        threeArcs = InitThreePointArc(midIdx, endIdx, tol, points, threeArcs)
        
    return threeArcs
    
#トレランス以内の曲線上の点群
def InitPointsOnCurve(geo, tol):
    #evaluator
    eva = geo.evaluator
    
    #始点終点
    (returnValue, startPoint, endPoint) = eva.getEndPoints()
    (returnValue, startPram) = eva.getParameterAtPoint(startPoint)
    (returnValue, endPram) = eva.getParameterAtPoint(endPoint)
    
    #トレランス以内の点群
    (returnValue, pnts) = eva.getStrokes(startPram, endPram, tol)
    return pnts
    
#選択
def Sel(msg, selFilter):
    app = adsk.core.Application.get()
    ui  = app.userInterface
    try:
        return ui.selectEntity(msg, selFilter)
    except:
        return None

役に立つものかどうかは、かなり謎です。
CATIAに比べ、Fusion360APIはベクトルのクラスが既存であり
ベクトル演算が簡単に行えるのはかなり楽です。(破壊的・非破壊的メソッドの違いには迷う)
又、折れ線近似化する事が可能なトレランス以内で点群を取得できるメソッド(getStrokes)が
備わっているのもかなり楽です。

処理自体もCATIAより軽い気もしますね。

困惑するオフセット平面を作成

タイトルだけでは伝わりにくいのですが、先日行った作業用対策のマクロです。
(他人には役に立たないと思います)

結果的にはオフセット平面を作成するだけなのですが、何といいますか面倒なんです。
f:id:kandennti:20180914193019p:plain
赤色はXY平面、黄色はZX平面で紫色のBodyの面はZX平面と平行です。
紫色と同じ位置に、ZX平面からのオフセット平面を作成したいのです。

手動で行うのであれば・・・ ” 平面からオフセット " でZX平面を指定し
"オフセット" 部分のコンテキストメニューで "2要素間を測定..." を選択。
f:id:kandennti:20180914193027p:plain
ZX平面と紫色の面を測定し、リンクさせたくない為(あくまで数値だけ欲しい)
"測定を保持" のチェックを外してOKです。
f:id:kandennti:20180914193034p:plain
更に、ZX平面のプラス側だったため、"Y+28.296mm” とリネーム。
が、一番操作が速そうな気がしてます、が面倒なんです。

で、作ったのがこちらのマクロです。
想像以上に大規模になってしまいました・・・・。

'vba OffsetPlane_ver0.0.1  using-'KCL0.0.12'  by Kantoku
Option Explicit

'基準平面優先モード
'True-基準平面優先 False-優先無し
Private Const PRIORITY_ORIGINPLANE = True ' False

'作成したオフセット平面を入れる形状セット名
Private Const TARGET_HBBODY_NAME = "Planes"

Sub CATMain()
    'ドキュメントのチェック
    If Not CanExecute("PartDocument") Then Exit Sub
    
    'モロモロ
    Dim doc As PartDocument
    Set doc = CATIA.ActiveDocument
    
    Dim pt As PART
    Set pt = doc.PART
    
    
    '平面リスト
    Dim planes As Collection
    Set planes = GetPlanes(doc)
    
    '選択
    Dim msg As String
    Dim refPlane As Plane
    
    Dim hbdy As HybridBody
    Set hbdy = Nothing
    
    Do
        '初期化
        Set refPlane = Nothing
        
        'ボディ面選択
        msg = "ボディ面を選択してください : ESCキー 終了"
        Dim face As AnyObject
        Set face = KCL.SelectItem(msg, "PlanarFace")
        
        If face Is Nothing Then Exit Do
        
        If Not IsBodyFace(face) Then
            msg = "対象はボディ面のみです!!"
            MsgBox msg, vbExclamation
        End If
        
        '基準平面と比較
        CATIA.HSOSynchronized = False
        
        If PRIORITY_ORIGINPLANE Then
            Set refPlane = GetParallelOriginPlane(pt, face)
        End If
        
        '全ての平面と比較
        If refPlane Is Nothing Then
            Set refPlane = GetParallelOtherPlane(pt, face, planes)
        End If
        
        '参照平面の指定無し
        If refPlane Is Nothing Then
            msg = "平行な平面が存在しない(又は指定していない)為、" & vbCrLf & _
                  "再度 面を指定して下さい"
            MsgBox msg, vbExclamation
            GoTo continue
        End If
        
        '平面作成
        Dim pln  As HybridShapePlaneOffset
        Set pln = InitPlane(pt, face, refPlane)
        If pln Is Nothing Then GoTo continue
        
        '形状セットへ
        If hbdy Is Nothing Then
            Set hbdy = GetHBBody(doc, TARGET_HBBODY_NAME)
        End If
        hbdy.AppendHybridShape pln
        
        '平面リストに追加
        planes.Add pln
        
        CATIA.HSOSynchronized = True
        
        MsgBox "平面を作成しました(" & pln.name & ")"
continue:
    Loop
    
    CATIA.HSOSynchronized = True
    
    MsgBox "Done"
End Sub

'ボディ面判断
Private Function IsBodyFace(ByVal face As AnyObject) As Boolean
    IsBodyFace = IIf(KCL.GetParent_Of_T(face, "Body") Is Nothing, _
                 False, True)
End Function

'形状セット取得・作成
Private Function GetHBBody(ByVal doc As PartDocument, _
                           ByVal hbdy_name As String) As HybridBody
    Dim sel As Selection
    Set sel = doc.Selection
    
    sel.Search "(Name='" & hbdy_name & "' & CATPrtSearch.OpenBodyFeature),all"
    
    If sel.Count2 > 0 Then
        Set GetHBBody = sel.Item2(1).Value
        Exit Function
    End If
    
    Dim pt As PART
    Set pt = doc.PART
    
    Dim hbdy As HybridBody
    Set hbdy = pt.hybridBodies.Add()
    hbdy.name = hbdy_name
    
    Set GetHBBody = hbdy
End Function

'平面情報なりにオフセット平面作成
Private Function InitPlane(ByVal pt As PART, ByVal face As PlanarFace, _
                           ByVal refpln As Plane) As HybridShapePlaneOffset
    Dim face_info As Variant
    face_info = GetPlaneInfo(pt, face)
    
    Dim refpln_info As Variant
    refpln_info = GetPlaneInfo(pt, refpln)
    
    Dim dist As Double
    dist = GetDistance(pt, face, refpln)
    
    Dim p2p_vec As Variant
    p2p_vec = ToVecter(face_info(0), refpln_info(0))
    
    Dim rev As Double
    rev = IIf(Dot(refpln_info(1), p2p_vec) < 0, 1, -1)
    
    Dim pln
    Set pln = InitPlaneOffset(pt, refpln, dist * rev)
    
    Set InitPlane = pln
End Function

'PlaneOffset
Private Function InitPlaneOffset(ByVal pt As PART, ByVal ref As Reference, _
                                 ByVal dist As Double) As HybridShapePlaneOffset
    Dim fact As HybridShapeFactory
    Set fact = pt.HybridShapeFactory
    
    Dim pln As HybridShapePlaneOffset
    Set pln = fact.AddNewPlaneOffset(ref, dist, False)
    pln.name = GetPlaneNewName(ref, Round(dist, 3))
    pt.UpdateObject pln
    
    Set InitPlaneOffset = pln
End Function

'それっぽい名前取得
Private Function GetPlaneNewName(ByVal ref As Reference, ByVal dist As Double) As String
    Dim txt As String
    
    Select Case KCL.GetInternalName(ref)
        Case "xy-plane"
            txt = "Z"
        Case "yz-plane"
            txt = "X"
        Case "zx-plane"
            txt = "Y"
        Case Else
            txt = ref.name
    End Select
    
    If dist < 0 Then
        txt = txt & CStr(dist) & "mm"
    Else
        txt = txt & "+" & CStr(dist) & "mm"
    End If
    
    GetPlaneNewName = txt
End Function

'指定平面と平行なものを取得
Private Function GetParallelOtherPlane(ByVal pt As PART, _
                                       ByVal face As AnyObject, _
                                       ByVal planes As Collection) As Plane
    Set GetParallelOtherPlane = Nothing
    
    Dim lst As Collection
    Set lst = New Collection
    
    Dim pln As Plane
    For Each pln In planes
        If IsParallel(pt, face, pln) Then
            lst.Add pln
        End If
    Next
    
    Dim msg As String
    Select Case lst.count
        Case Is < 1
            '平行平面無し
        Case 1
            Set GetParallelOtherPlane = lst(1)
        Case Else
            msg = "指定面と平行な平面が複数存在します。" & vbCrLf & _
                  "参照する面を指定して下さい"
            MsgBox msg, vbInformation
            '再度選択
            Set GetParallelOtherPlane = SelectRefPlane(pt, face, lst)
    End Select
    
End Function

'参照平面選択
Private Function SelectRefPlane(ByVal pt As PART, _
                                ByVal face As AnyObject, _
                                ByVal planes As Collection) As Plane
    Set SelectRefPlane = Nothing
    
    'グラフィックバックアップ
    Dim infos As Variant
    infos = GetGraphicsInfoList(planes)
    
    '候補平面のグラフィック変更
    Call SetGraphicsInfoList(infos, Array(0, 0, 255, 0, 3, 0))
    
    '選択
    Dim refpln As Plane
    Dim msg As String
    Do
        '初期化
        Set refpln = Nothing
        
        '平面選択
        msg = "参照する平面を選択してください : ESCキー 終了"
        Set refpln = KCL.SelectItem(msg, "Plane")
        If refpln Is Nothing Then Exit Do
        
        '平行OK
        If IsParallel(pt, face, refpln) Then Exit Do
        
        '繰り返し
        msg = "平行な平面を選択してください!"
        MsgBox msg, vbExclamation
    Loop
    
    'グラフィック戻し
    Call SetGraphicsInfoList(infos)
    
    Set SelectRefPlane = refpln
End Function


'コレクション内平面名の文字列化
Private Function GetPlanesName(ByVal plns As Collection) As String
    Dim ary() As Variant
    ReDim ary(plns.count - 1)
    
    Dim i As Long
    For i = 0 To plns.count - 1
        ary(i) = plns(i + 1).name
    Next
    
    GetPlanesName = Join(ary, vbCrLf)
End Function


'----- 平面関連 -----
'平行?
Private Function IsParallel(ByVal pt As PART, ByVal p1, ByVal p2) As Boolean
    IsParallel = IIf(GetAngle(pt, p1, p2) < 0.001, True, False)
End Function

'平面情報
'return 0-Origin 1-Direction As Plane
Private Function GetPlaneInfo(ByVal pt As PART, ByVal pln) As Variant
    Dim pln_vari As Variant
    Set pln_vari = pln
    
    Dim info(1) As Variant
    
    Dim ori(2)
    pln_vari.GetOrigin ori
    info(0) = ori
    
    info(1) = GetPlaneDirection(pln_vari)
    GetPlaneInfo = info
End Function

'平面法線
Private Function GetPlaneDirection(ByVal pln_vari As Variant) As Variant
    Dim vec1(2)
    pln_vari.GetFirstAxis vec1
    
    Dim vec2(2)
    pln_vari.GetSecondAxis vec2
    
    GetPlaneDirection = Cross(vec1, vec2)
End Function

'平面全て取得
Private Function GetPlanes(ByVal doc As Document) As Collection
    Dim lst As Collection
    Set lst = New Collection
    Set GetPlanes = lst
    
    Dim sel As Selection
    Set sel = doc.Selection
    
    CATIA.HSOSynchronized = False
    
    sel.Search "CATPrtSearch.Plane,all"
    
    If sel.Count2 < 1 Then Exit Function
    
    Dim i As Long
    For i = 1 To sel.Count2
        lst.Add sel.Item2(i).Value
    Next
    sel.Clear
    
    CATIA.HSOSynchronized = True
End Function

'平行な基本平面を取得
Private Function GetParallelOriginPlane(ByVal pt As PART, _
                                        ByVal face As AnyObject) As Plane
    Set GetParallelOriginPlane = Nothing
    
    With pt.OriginElements
        Select Case True
            Case IsParallel(pt, face, .PlaneXY)
                Set GetParallelOriginPlane = .PlaneXY
            Case IsParallel(pt, face, .PlaneYZ)
                Set GetParallelOriginPlane = .PlaneYZ
            Case IsParallel(pt, face, .PlaneZX)
                Set GetParallelOriginPlane = .PlaneZX
        End Select
    End With
End Function


'----- 測定関連 -----
'Measurable
Private Function GetMeasurable(ByVal pt As PART, ByVal p1) 'As Measurable
    Dim wb, meas As Measurable
    Set wb = pt.Parent.GetWorkbench("SPAWorkbench")
    Set GetMeasurable = wb.GetMeasurable(p1)
End Function

'角度
Private Function GetAngle(ByVal pt As PART, ByVal p1, ByVal p2) As Double
    Dim meas As Measurable
    Set meas = GetMeasurable(pt, p1)
    
    GetAngle = meas.GetAngleBetween(p2)
End Function

'距離
Private Function GetDistance(ByVal pt As PART, ByVal p1, ByVal p2) As Variant
    Dim meas As Measurable
    Set meas = GetMeasurable(pt, p1)
    
    GetDistance = meas.GetMinimumDistance(p2)
End Function


'----- グラフィック関連 -----
'グラフィック情報取得
'0-show , 1-Color R , 2-ColorG , 3-ColorB , 4-Width , 5-Pick
Private Function GetGraphicsInfo(ByVal itm As AnyObject) As Variant
    Dim doc As Document
    Set doc = KCL.GetParent_Of_T(itm, "PartDocument")
    
    Dim sel As Selection
    Set sel = doc.Selection
    
    sel.Clear
    sel.Add itm
    
    Dim info(5) As Variant
    Dim vis As Variant 'VisPropertySet
    Set vis = sel.VisProperties
    
    vis.GetShow info(0)
    vis.GetRealColor info(1), info(2), info(3)
    vis.GetVisibleWidth info(4)
    vis.GetPick info(5)
    
    sel.Clear
    GetGraphicsInfo = info
End Function

'グラフィック情報設定
'0-show , 1-Color R , 2-ColorG , 3-ColorB , 4-Width , 5-Pick
Private Sub SetGraphicsInfo(ByVal itm As AnyObject, ByVal info As Variant)
    Dim doc As Document
    Set doc = KCL.GetParent_Of_T(itm, "PartDocument")
    
    Dim sel As Selection
    Set sel = doc.Selection
    
    sel.Clear
    sel.Add itm
    
    Dim vis As VisPropertySet
    Set vis = sel.VisProperties
    
    vis.SetShow info(0)
    vis.SetRealColor info(1), info(2), info(3), 1
    vis.SetVisibleWidth info(4), 1
    vis.SetPick info(5)
    
    sel.Clear
End Sub

'リスト内グラフィック情報取得
Private Function GetGraphicsInfoList(ByVal plns As Collection) As Variant
    Dim ary() As Variant
    ReDim ary(plns.count - 1)
    
    Dim i As Long
    For i = 0 To plns.count - 1
        ary(i) = Array(plns(i + 1), GetGraphicsInfo(plns(i + 1)))
    Next
    GetGraphicsInfoList = ary
End Function

'リスト内グラフィック情報設定
'Optional allinfo 指定時は全てその設定
Private Sub SetGraphicsInfoList(ByVal graps As Variant, _
                                Optional ByVal allinfo = Empty)
    Dim i As Long
    
    If IsEmpty(allinfo) Then
        For i = 0 To UBound(graps)
            Call SetGraphicsInfo(graps(i)(0), graps(i)(1))
        Next
    Else
        For i = 0 To UBound(graps)
            Call SetGraphicsInfo(graps(i)(0), allinfo)
        Next
    End If
End Sub


'----- ベクトル関連 -----
'内積3D
Private Function Dot(ByVal v1 As Variant, ByVal v2 As Variant) As Double
    Dot = v1(0) * v2(0) + v1(1) * v2(1) + v1(2) * v2(2)
End Function

'外積3D
Private Function Cross(ByVal v1 As Variant, ByVal v2 As Variant) As Variant
    Dim vec(2) As Double
    vec(0) = v1(1) * v2(2) - v1(2) * v2(1)
    vec(1) = v1(2) * v2(0) - v1(0) * v2(2)
    vec(2) = v1(0) * v2(1) - v1(1) * v2(0)
    Cross = vec
End Function

'2点間ベクトル
Private Function ToVecter(ByVal p1 As Variant, ByVal p2 As Variant) As Variant
    Dim vec(2) As Double, i As Long
    For i = 0 To UBound(vec)
        vec(i) = p2(i) - p1(i)
    Next
    ToVecter = vec
End Function

要は平らなボディ面を選択するだけで、先程手動操作で作成したオフセット平面と
同等のものをジャンジャン作成します。
f:id:kandennti:20180914193053p:plain
先頭付近の定数

Private Const PRIORITY_ORIGINPLANE = True

と設定していると、基本平面(XY,YZ,ZX)と平行な平面の場合は勝手に
平面を作成・リネームを行います。参照平面が
XY平面:Z + オフセット長さ
YZ平面:X + オフセット長さ
ZX平面:Y + オフセット長さ
の名称です。

基本平面と平行では無いBody面を指定した場合、Partファイル内に
存在する平面から平行な平面を探し出します。
平行な平面が1個しかない場合は、勝手に平面を作成し
参照平面名 + オフセット長さ
でリネームします。
f:id:kandennti:20180914193103p:plain
緑色が面取りで、"平面.1" が面取りと平行な45°面です。
緑色の面を選択すると、他に平行な平面が存在しない為、
"平面.1+44.989mm" が作成されます。

平行な平面が複数存在している場合は、参照する平面を指定する
必要が有り、この様なダイアログが表示されます。
f:id:kandennti:20180914193109p:plain
面がわかりやすいように、平行な平面は黄緑の太線表示に
切り替えます。(選択後は元に戻ります)
f:id:kandennti:20180914193115p:plain

又、選択した面に平行な平面がPartファイル内に存在しない場合は
f:id:kandennti:20180914193122p:plain
のダイアログが出現し、平面を作成することが出来ません。


他にもイロイロと問題もあるのですが、それ以上にそもそもこの平面に
価値があるものかどうかが疑問なんです。
この平面は、偶々ボディと同じ位置にある平面なので、当然ボディを
修正したら全く無関係の平面となってしまいます。
又、平面のオフセット量を変更しても、平面をリネームする必要が有り
非常に手間です。
(それっぽい名前で無ければオフセット量を調べますが、修正しリネームを
忘れた際は、誤った数値で捉えてしまう可能性があります)

一ヶ所の変更で、他の部分が変更されないようなモデリングルールにする
気持ちもわからなくは無いのですが、修正した際は関連する部分も
連携しているような作り方にしておかないと、手間ばかりで
「2D設計から3D設計に切り替えたが、効果が上がらず むしろ
工数が増えるばかり」の典型例に感じてしょうがないんですよ。

ボディ毎にコピペすることが大前提のルールなんですよね。マクロと同じで
コピペより関数化。CATIAにはパワーコピーと言う素晴らしい機能が
あるのだから利用した方が良いですよ。
(同等の機能は他の3DCADにあるのかな?)

比較的最近まで知らなかった操作の数々

ここ一ヶ月ぐらいあまりやらないモデリングをたっぷり行いました。
恥ずかしながら、比較的最近まで知らなかった操作の数々です。

・ドラフト - ニュートラル面による選択
こんな感じの97角形(100以下の最大素数)に、勾配付ける場合有りますよね?
f:id:kandennti:20180913124724p:plain
(無いです)以前であれば、1/97の形状を作って勾配つけて円形パターンして
ましたが(恐らく式を使って行わないと、綺麗に一周しないはず)天面指定する
だけOKで楽です。
f:id:kandennti:20180913124031p:plain

・シェル - 外側の厚み
黄色のBodyをパッドで作った上で、パッドが延長したような
青色のBodyを黄色のBodyと連動するような形で作りたい時がシバシバあります。
f:id:kandennti:20180913124042p:plain
以前は黄色のBodyを "リンクの結果" でコピーし平面を作成。厚みを付けて
平面でカットしてました。
f:id:kandennti:20180913124051p:plain
シェルを利用すると、内側を0で外側を任意の厚みにし、除去するフェースを
延長する方向以外の面を指定すると同様の連動したBodyが出来ます。
良くこんな方法を思いつくなぁ・・・。

・ポケット - サイドを反転
具体的な例があまり良くないのですが、これはかなり以前から知っていました。
こんな感じのXY平面からもZX平面からも三角形の形状を作るような場合です。
(要はパッド一発で作れない形状です)
f:id:kandennti:20180913124102p:plain
以前であれば、両方の形状を個別のBodyでパッド作成しブーリアンの積で作って
いました。
ポケットを利用すると、どちらかの形状をパッドで作成した後ポケットを "サイドを反転"
させることで作成出来ます。
f:id:kandennti:20180913124134p:plain
タイプを "最後まで" にすると、修正した際にも必ず連動してくれるので
根拠の無い大きな数値を利用しなくて済むメリットは大きいように感じています。
"ソリッドを合成" 使うと一発なんですが、そこは何故か使わない・・・。

・スケッチ拘束 - 半径/直径
あまり図面からモデルを作る事が無いのですが、左のような図面に対して
右のようなスケッチを作るのは、個人的に好きじゃないんです。
f:id:kandennti:20180913124143p:plain
直径で指定されている寸法を、勝手に半径として拘束しているのが
イヤなんです。(数字を見て判断してしまうので見間違える)

以前は、ムリムリ式を作ったりしていたのですが、軸を利用すると
簡単に直径で指定できます。
拘束を作成する際、軸と外形線を選択し決定する前にコンテキストメニューを
表示させ "半径/直径" をクリックすることで "D" 付きの拘束になります。
(右側の D 40 のような感じ)
f:id:kandennti:20180913124150p:plain
軸があれば、シャフトを行う際も勝手に軸を判断するので楽です。
・・・丸いものじゃなく、対称形状のようなものでも使っちゃってます。

・スケッチ拘束 - 直径時 素直に式が作れない
こんな感じのスケッチ拘束があります。
f:id:kandennti:20180913124159p:plain
"長さ.9" の拘束を編集するために定義(又はダブルクリック)して
ダイアログを表示させ、値部分でコンテキストメニューを表示させると
一番上に "式の編集..." が表示され式が利用できます。
f:id:kandennti:20180913124205p:plain
半径指定している "半径.10" も同様です。
f:id:kandennti:20180913124213p:plain
ところが、直径指定している "半径.11" や、先程書いた軸を利用した
"オフセット.13" は "式の編集..." が表示されないんです。
f:id:kandennti:20180913124219p:plain
この前は "直径は式が利用できないのかぁ" と諦めちゃったのですが
ダイアログを表示させずに直接拘束のコンテキストメニューの深いところに
"式を編集" がある事に今気が付きました。
f:id:kandennti:20180913135133p:plain
但し、数値としては半径の値となる為、注意が必要そうです。
(マクロ的には半径管理されているのは、薄っすら知ってます)

・スケッチ - ユーザーパターンのスケッチの "形状セット変更" が出来ない
これはリリース依存している感じですが、R2015(R25)でのお話です。
ユーザーパターン結構便利なので使うのですが(ここなさんは使わないって書いてました)
こんな感じの場合です。
f:id:kandennti:20180913124317p:plain
"スケッチ.1" は配置する為の点のみスケッチで、"スケッチ.2" は形状の為の
スケッチです。モデリングルールとして "スケッチは形状セットに入れておく" と
なっている場合、"スケッチ.2" はコンテキストメニューで "形状セット変更" を
利用して形状セットに入れる事が出来ます。
f:id:kandennti:20180913124322p:plain
ところが、ユーザーパターンで使用した "スケッチ.1" は "形状セット変更" が
出てきません。
f:id:kandennti:20180913124331p:plain
古いリリースでは出来ませんが、無理やりD&Dで形状セット入れる方法
でも "スケッチ.2" はできますが、 "スケッチ.1" はコピーされて新たなスケッチが
作成されてしまいます。(使わない理由はこの辺かも)
f:id:kandennti:20180913124419p:plain
結局上手く出来る方法が見つからず、置換で対応するしか方法が
無さそうです。

念のため、R2017(そういえばR2018が届いていないような・・・)で
試した所、Treeの状態が異なりました。
f:id:kandennti:20180913124427p:plain
こちらではD&Dで移動可能でした。

・面取り - 境界エレメントが無い
あまり面取りを使わない(面取りとしてではなく、別のことでは使いますが)
ので、気が付きませんでした。
フィレットは任意の稜線の途中まで範囲を指定して作る事が出来ます。
f:id:kandennti:20180913124436p:plain
面取りもフィレットと大差無い機能だと思い込んでいたのですが、
”境界エレメント” が無いんですね。
f:id:kandennti:20180913124442p:plain
これも簡単に出来そうな方法が見つからず、遠回りな方法で
行いました。付けてくれても良さそうな感じするのですが・・・。

GetDirectionが上手く行かない2

こちらの続きです。
GetDirectionが上手く行かない - C#ATIA

imihitoさんからのアドバイスを頂き、こんな風にしてみました。
(これだけじゃ動きません)

'vba
'平面法線
Private Function GetPlaneDirection(ByVal pln As Plane) As Variant
    Dim pln_vari As Variant
    Set pln_vari = pln
    
    Dim vec1(2)
    pln_vari.GetFirstAxis vec1
    
    Dim vec2(2)
    pln_vari.GetSecondAxis vec2
    
    GetPlaneDirection = Cross(vec1, vec2)
End Function

'外積3D
Private Function Cross(ByVal v1 As Variant, ByVal v2 As Variant) As Variant
    Dim vec(2) As Double
    vec(0) = v1(1) * v2(2) - v1(2) * v2(1)
    vec(1) = v1(2) * v2(0) - v1(0) * v2(2)
    vec(2) = v1(0) * v2(1) - v1(1) * v2(0)
    Cross = vec
End Function

外積の関数、持ってました・・・。
GetPlaneDirectionにPlane型を投げると、x,y,zのVariant型(中身はDouble)の配列を
返します。

・・・そういえば、何処かで新入社員教育で ”ダブル型の変数を用意して下さい”
と指示したら

Dim W

と書かれた。 と言うネタを思い出しました。(意味は無いです)

GetDirectionが上手く行かない

平面の向きを取得したいだけなのですが、SPAWorkbenchのGetDirectionが
エラーになり上手く行かないです。 確かに使った事無いんですが・・・。

'vba これは一部なのでこれだけじゃ動かないです

'平面情報
'return 0-Distance 1-p1_Direction 2-p2_Direction
Private Function GetPlaneInfo(ByVal pt As PART, ByVal p1, ByVal p2) As Variant
    Dim meas 'As Measurable
    Set meas = GetMeasurable(pt, p1)
    
    Dim info(2) As Variant
    info(0) = meas.GetMinimumDistance(p2)
    
    Dim vec(2) 'As Variant
    meas.GetDirection vec 'ここでエラー
    info(1) = vec
    
    Set meas = GetMeasurable(pt, p2)
    meas.GetDirection vec
    info(2) = vec
    
    GetPlaneInfo = info
End Function

Private Function GetMeasurable(ByVal pt As PART, ByVal p1) 'As Measurable
    Dim wb, meas As Measurable
    Set wb = pt.Parent.GetWorkbench("SPAWorkbench")
    Set GetMeasurable = wb.GetMeasurable(p1)
End Function

この手は型指定するとエラーになるのはわかっているので、型指定無しです。

代案が無くも無いのですが、面倒なので直接平面の向きを取得したいのですが・・・。

スケッチにランダムな点を作成する

これ自体は無意味なコードですが、後で使うような気がしたので
作ってみました。

#FusionAPI_python
#Author-kantoku
#Description-新たなスケッチを作成しランダムに点を作成

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

def run(context):
    ui = None
    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, 200)

    except:
        if ui:
            ui.messageBox('エラー:\n{}'.format(traceback.format_exc()))

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

他人には、役に立たないと思います。

参照かつ外部とのスケッチ拘束を削除する

自分が作業して欲しくなったので突貫で作りました。

こんなスケッチ1を作ります。
f:id:kandennti:20180905155613p:plain
続いて、こんなスケッチ2を作ります。
f:id:kandennti:20180905155620p:plain
基本的にスケッチ1が変更されても、スケッチ2の形状は影響は有りません。
但し、"オフセット.20" はスケッチ1に対して参照の拘束が付いています。
まぁクリアランスを確認したい とかです。

ここで問題になるのは、スケッチ1が削除される場合です。
こんな風になりますよね?
f:id:kandennti:20180905155627p:plain
”すべての子を削除” のチェックを外し、実行してしまうと
f:id:kandennti:20180905155633p:plain
まぁエラーになります。形状には影響無いので無視したいところ
なのですが。

要は
・参照の拘束
・外部との拘束
と言うことです。

で、この様なスケッチ拘束を削除するマクロです。

'vba Part_RemoveExternalElmConst Ver0.0.1  using-'KCL0.0.12'  by Kantoku

Option Explicit

Private Const EXTERNAL_ELEMENT_NAME = "CATIAGeometry"

Sub CATMain()

    'ドキュメントのチェック
    If Not CanExecute(Array("PartDocument")) Then Exit Sub

    Dim doc As Document
    Set doc = CATIA.ActiveDocument
    
    'スケッチ取得
    Dim skts As Collection
    Set skts = GetSketchList(doc)
    If skts.count < 1 Then
        MsgBox "スケッチがありません!", vbExclamation
        Exit Sub
    End If
    
    '処理確認
    Dim msg As String
    msg = "個のスケッチに対して処理します。宜しいですか?"
    If MsgBox(skts.count & msg, vbYesNo + vbQuestion) = vbNo Then Exit Sub
    
    '削除対象の拘束取得
    Dim del_lst As Collection
    Set del_lst = GetRemoveConstraintList(skts)
    
    If del_lst.count < 1 Then
        MsgBox "削除すべきスケッチ拘束が有りません!", vbExclamation
        Exit Sub
    End If
    
    '削除
    Dim cnt As Long
    cnt = RemoveConstraint(del_lst)
    
    'done
    doc.Part.Update
    MsgBox cnt & "個のスケッチ拘束を削除しました"

End Sub

'リスト内拘束の削除
Private Function RemoveConstraint(ByVal cons As Collection) As Long
    RemoveConstraint = cons.count
    
    Dim sel As Selection
    Set sel = KCL.GetParent_Of_T(cons(1), "PartDocument").Selection
    
    CATIA.HSOSynchronized = False
    
    sel.Clear
    
    Dim con As Constraint
    For Each con In cons
        sel.Add con
    Next
    sel.Delete
    sel.Clear
    
    CATIA.HSOSynchronized = True
End Function

'削除対象拘束リスト
'条件:参照チェックON & 外部要素との拘束
Private Function GetRemoveConstraintList(ByVal skts As Collection) As Collection
    Dim lst As Collection
    Set lst = New Collection
    
    Dim skt As Sketch
    Dim con As Constraint
    For Each skt In skts
        For Each con In skt.Constraints
            Debug.Print con.Name & " : " & CStr(con.Mode)
            If con.Mode = catCstModeDrivingDimension Then GoTo continue
            If Not HasExternalElementConstraint(con) Then GoTo continue
            
            lst.Add con
continue:
        Next
    Next
    
    Set GetRemoveConstraintList = lst
End Function

'ドキュメント内のスケッチリスト
Private Function GetSketchList(ByVal doc As Document) As Collection
    Dim sel As Selection
    Set sel = doc.Selection
    
    CATIA.HSOSynchronized = False
    With sel
        .Clear
        .Search "CATPrtSearch.Sketch,all"
    End With
    
    Dim lst As Collection
    Set lst = New Collection
    
    Dim i As Long
    For i = 1 To sel.Count2
        lst.Add sel.Item2(i).Value
    Next
    
    sel.Clear
    CATIA.HSOSynchronized = True
    
    Set GetSketchList = lst
End Function

'自身のスケッチ要素以外との拘束か?
Private Function HasExternalElementConstraint(ByVal con As Constraint) As Boolean
    HasExternalElementConstraint = True
    
    Dim elm As AnyObject
    Dim idx As Long
    idx = 0
    
    On Error Resume Next
    
    Do While Err.Number = 0
        Err.Number = 0
        idx = idx + 1
        Set elm = con.GetConstraintElement(idx)
        If InStr(elm.Name, EXTERNAL_ELEMENT_NAME) > 0 Then
            Exit Function
            
        End If
    Loop
    HasExternalElementConstraint = False
End Function

同一スケッチ内の参照拘束は、確認の意味もあるだろうと思う為、削除しません。
(残っていても、ダメージは少ないだろうと思ってます)

最近は細々したものばかり・・・。