C#ATIA

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

Fusion360APIのVecter3Dの外積を確認2

こちらの続きです。
Fusion360APIのVecter3Dの外積を確認1 - C#ATIA
もう一つ違う方法で確認する事にしました。

適当なボディをクリックすると慣性モーメントを利用して、
重心やら軸やらを取得して、そこからマトリックスを作成し
可視化しました。
f:id:kandennti:20211008143450p:plain

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core

def run(context):
    ui: adsk.core.UserInterface = None
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        des: adsk.fusion.Design = app.activeProduct
        root: adsk.fusion.Component = des.rootComponent

        msg: str = 'Select'
        selFiltter: str = 'Bodies'
        sel: adsk.core.Selection = selectEnt(msg, selFiltter)
        if not sel:
            return

        # BRepBody
        body: adsk.fusion.BRepBody = sel.entity

        # Principal cog Axes
        physicalProp: adsk.fusion.PhysicalProperties = body.getPhysicalProperties()
        cog: adsk.core.Point3D = physicalProp.centerOfMass
        _, xAxis, yAxis, zAxis = physicalProp.getPrincipalAxes()

        mat1: adsk.core.Matrix3D = initMatrix3D_fromXZ(xAxis, zAxis, cog)
        dumpMatrix(root, mat1, 'mat1')

        mat2: adsk.core.Matrix3D = adsk.core.Matrix3D.create()
        mat2.setWithCoordinateSystem(cog, xAxis, yAxis, zAxis)
        dumpMatrix(root, mat2, 'mat2')

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

# 長さ X10Cm Y15Cm Z20Cm
def dumpMatrix(
    comp: adsk.fusion.Component,
    mat: adsk.core.Matrix3D,
    name: str = ''):

    skt: adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)
    if name != '':
        skt.name = name

    pnt, vx, vy, vz = mat.getAsCoordinateSystem()
    vy.scaleBy(1.5)
    vz.scaleBy(2)
    for v in [vx, vy, vz]:
        p = pnt.copy()
        p.translateBy(v)
        skt.sketchCurves.sketchLines.addByTwoPoints(
            pnt, p
        )

def initMatrix3D_fromXZ(
    vecX: adsk.core.Vector3D,
    vecZ: adsk.core.Vector3D,
    cog: adsk.core.Point3D = None) -> adsk.core.Matrix3D:

    vecY: adsk.core.Vector3D = vecZ.crossProduct(vecX)
    vecX = vecZ.crossProduct(vecY)

    vecX.normalize()
    vecY.normalize()
    vecZ.normalize()

    if not cog:
        cog = adsk.core.Point3D.create(0, 0, 0)

    mat: adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    mat.setWithCoordinateSystem(
        cog,
        vecX,
        vecY,
        vecZ
    )

    return mat

def selectEnt(
        msg: str,
        filtterStr: str) -> adsk.core.Selection:

    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        sel = ui.selectEntity(msg, filtterStr)
        return sel
    except:
        return None

こちらを実行すると、
f:id:kandennti:20211008143622p:plain
mat1側は僕が作った関数から作ったマトリックス
mat2側は慣性モーメントから素直に作ったマトリックスです。

mat1側が左手系です。・・・犯人は僕です。


仕方が無いので、関数を修正しました。

def initMatrix3D_fromXZ(
    vecX: adsk.core.Vector3D,
    vecZ: adsk.core.Vector3D,
    cog: adsk.core.Point3D = None) -> adsk.core.Matrix3D:

    vecY: adsk.core.Vector3D = vecZ.crossProduct(vecX)
    vecX = vecZ.crossProduct(vecY)
    if vecX.crossProduct(vecY).dotProduct(vecZ) < 0:
        vecX.scaleBy(-1)

    vecX.normalize()
    vecY.normalize()
    vecZ.normalize()

    if not cog:
        cog = adsk.core.Point3D.create(0, 0, 0)

    mat: adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    mat.setWithCoordinateSystem(
        cog,
        vecX,
        vecY,
        vecZ
    )

    return mat

内積を利用して、1軸(X軸)だけ逆にしました。
恐らくこれでOKでしょう。
本題に戻れそう。

Fusion360APIのVecter3Dの外積を確認1

こちらを確認!
メッシュの六角形分割に挑む11 - C#ATIA

外積を視覚的に確認したいのでテストしました。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core

def run(context):
    ui: adsk.core.UserInterface = None
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        des: adsk.fusion.Design = app.activeProduct
        root: adsk.fusion.Component = des.rootComponent
        
        mat = initMatrix3D_fromXZ(
            adsk.core.Vector3D.create(1, 2, 1),
            adsk.core.Vector3D.create(-1, 2, 2)
        )

        dumpMatrix(root, mat, 'Fusion API')

        mat = initMatrix3D_fromXZ(
            adsk.core.Vector3D.create(1, 2, 1),
            adsk.core.Vector3D.create(-1, 2, 2),
            cross3D
        )

        dumpMatrix(root, mat, 'non Fusion API')

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

def initMatrix3D_fromXZ(
    vecX: adsk.core.Vector3D,
    vecZ: adsk.core.Vector3D,
    crossFanc = None) -> adsk.core.Matrix3D:

    vecY: adsk.core.Vector3D 
    if crossFanc:
        vecY = cross3D(vecZ, vecX)
        vecX = cross3D(vecZ, vecY)
    else:
        vecY = vecZ.crossProduct(vecX)
        vecX = vecZ.crossProduct(vecY)

    vecX.normalize
    vecY.normalize
    vecZ.normalize

    mat: adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    mat.setWithCoordinateSystem(
        adsk.core.Point3D.create(0, 0, 0),
        vecX,
        vecY,
        vecZ
    )

    return mat

def cross3D(
    vec1: adsk.core.Vector3D,
    vec2: adsk.core.Vector3D) -> adsk.core.Vector3D:

    x = vec1.y * vec2.z - vec1.z * vec2.y
    y = vec1.z * vec2.x - vec1.x * vec2.z
    z = vec1.x * vec2.y - vec1.y * vec2.x

    return adsk.core.Vector3D.create(x, y, z)

# 長さ X10Cm Y15Cm Z20Cm
def dumpMatrix(
    comp: adsk.fusion.Component,
    mat: adsk.core.Matrix3D,
    name: str):

    skt: adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)
    skt.name = name

    pnt, vx, vy, vz = mat.getAsCoordinateSystem()
    vy.scaleBy(1.5)
    vz.scaleBy(2)
    for v in [vx, vy, vz]:
        p = pnt.copy()
        p.translateBy(v)
        skt.sketchCurves.sketchLines.addByTwoPoints(
            pnt, p
        )

2つのベクトルは、疑問に思った時のベクトルと同じです。
結果、
f:id:kandennti:20211008113944p:plain
ちゃんと右手系で返してます。
と言う事は、あちらで僕が何かを間違えてます・・・。

メッシュの六角形分割に挑む11

こちらの続きです。
メッシュの六角形分割に挑む10 - C#ATIA

六角柱を配置する為の中心点を求めているのですが、進まなく
なってます・・・。ベクトルやらマトリックスの数学的な部分で。

f:id:kandennti:20211008103618p:plain


メッシュボディに対して、バウンダリボックスの取得は以前作ったもので
機能しています。
それに対して、六角柱の配置はバウンダリボックスよりちょっと
大きいぐらいに配置したいのですが、なんだかイマイチです。
(最初はもっとひどかったのです。)

どうしても思うような結果にならない為、原因を探っていたのですが、
マトリックスを作成する際、三次元ベクトルの外積を行っています。
別に難しいことじゃなくて、Fusion360APIでは用意されています。
help.autodesk.com
僕の中では、
"並行しない2個の三次元ベクトルの外積は、直交するベクトルが得られる"
と思ってます。(合ってますよね?)

この直交するベクトルは、三次元CADの場合は常に右手系で返してくれると
思っているのですが、左手系で返って来ている事が発覚。
外積に左手系ってあるの?” と思ったら有るらしい。
右手系3次元座標とベクトルの外積の定義と公式 - Irohabook

CATIAの時は、三次元ベクトルの外積(自作関数)は常に右手系で返してくれる
ので、座標系が右手か左手かを判断出来たので・・・どうやって判断すれば良いのかな?
挫折しそう。

メッシュの六角形分割に挑む10

こちらの続きです。
メッシュの六角形分割に挑む9 - C#ATIA

六角柱をBRepBodyで作成します。
後の事を考慮して任意の位置に任意のサイズで作ります。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core
import math


def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface

        if app.activeDocument.design.designType != adsk.fusion.DesignTypes.DirectDesignType:
            ui.messageBox('履歴がキャプチャされている為、中止します')
            return

        root: adsk.fusion.Component = app.activeProduct.rootComponent
        initHexagon(
            root,
            adsk.core.Point3D.create(1, 2, 3),
            adsk.core.Vector3D.create(3, 2, 1),
            adsk.core.Vector3D.create(-2, 2, 5),
            5,
            20
        )

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


def initHexagon(
        comp: adsk.fusion.Component,
        center: adsk.core.Point3D,
        vecX: adsk.core.Vector3D,
        vecY: adsk.core.Vector3D,
        length: float,
        height: float):

    def initHexagonProfile(
        skt: adsk.fusion.Sketch,
        center: adsk.core.Point3D,
        vecX: adsk.core.Vector3D,
        vecY: adsk.core.Vector3D,
        length: float
    ) -> adsk.fusion.Profile:

        # points
        pnts = []
        for idx in range(6):
            theta = math.radians(30 + 60 * idx)
            pnts.append(
                adsk.core.Point3D.create(
                    math.cos(theta) * length,
                    math.sin(theta) * length)
            )

        # lines
        pnts.append(pnts[0])
        for p1, p2 in zip(pnts, pnts[1:]):
            skt.sketchCurves.sketchLines.addByTwoPoints(p1, p2)

        return skt.profiles[0]

    def initExtrude(
            profile: adsk.fusion.Profile,
            height: float) -> adsk.fusion.ExtrudeFeature:

        comp: adsk.fusion.Component = profile.parentSketch.parentComponent
        exts: adsk.fusion.ExtrudeFeatures = comp.features.extrudeFeatures
        extIpt: adsk.fusion.ExtrudeFeatureInput = exts.createInput(
            profile,
            adsk.fusion.FeatureOperations.NewBodyFeatureOperation
        )
        extIpt.setSymmetricExtent(
            adsk.core.ValueInput.createByReal(height),
            True
        )

        return exts.add(extIpt)

    def initCloneTransform(
            body: adsk.fusion.BRepBody,
            mat: adsk.core.Matrix3D) -> adsk.fusion.BRepBody:

        tmpMgr: adsk.fusion.TemporaryBRepManager = adsk.fusion.TemporaryBRepManager.get()
        clone: adsk.fusion.BRepBody = tmpMgr.copy(body)

        tmpMgr.transform(clone, mat)

        comp: adsk.fusion.Component = body.parentComponent
        return comp.bRepBodies.add(clone)

    # ----------
    # vecter
    vecZ: adsk.core.Vector3D = vecX.crossProduct(vecY)
    vecY = vecX.crossProduct(vecZ)
    vecX.normalize()
    vecY.normalize()
    vecZ.normalize()

    # sketch
    skt: adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)

    # profile
    pro: adsk.fusion.Profile = initHexagonProfile(
        skt, center, vecX, vecY, length)

    # extrude
    extFeat: adsk.fusion.ExtrudeFeature = initExtrude(pro, height)

    # matrix
    mat: adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    mat.setWithCoordinateSystem(center, vecX, vecY, vecZ)

    # Clone Transform
    body: adsk.fusion.BRepBody = initCloneTransform(
        extFeat.bodies[0],
        mat
    )

    # remove
    extFeat.bodies[0].deleteMe()
    extFeat.dissolve()
    skt.deleteMe()

f:id:kandennti:20211007142720p:plain
ダイレクトモードでのみ動きます。
又、途中の要らないものは削除しています。

メッシュの六角形分割に挑む9

こちらの続きです。
メッシュの六角形分割に挑む8 - C#ATIA
何時まで続くのか・・・。

新たな作成に欠かせない、メッシュの結合です。
f:id:kandennti:20211006145817p:plain
以前の結合は、結合でしたが今度は交差です。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core

def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        root: adsk.fusion.Component = app.activeProduct.rootComponent

        execMeshCombine_Intersect(
            root.meshBodies[1],
            [root.meshBodies[0]]
        )

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


def execMeshCombine_Intersect(
        target: adsk.fusion.MeshBody,
        tools: list):

    app: adsk.core.Application = adsk.core.Application.get()
    ui: adsk.core.UserInterface = app.userInterface
    sels: adsk.core.Selections = ui.activeSelections

    sels.clear()
    app.executeTextCommand(u'Commands.Start ParaMeshCombineCommand')

    app.executeTextCommand(u'UI.EnableCommandInput infoTargetBody')
    sels.add(target)

    for tool in tools:
        app.executeTextCommand(u'UI.EnableCommandInput infoToolBody')
        sels.add(tool)

    cmds = [
        u'Commands.SetString infoType infoTypeIntersect',
        u'Commands.SetBool newComponent 0',
        u'Commands.SetBool infoKeepToolBodies 1',
        u'Commands.SetBool infoPreview 0',
        u'NuCommands.CommitCmd'
    ]

    [app.executeTextCommand(cmd) for cmd in cmds]

オプションにしても良かったのですが、ツール側は残る設定になってます。

処理を行いたい六角柱側をターゲットにし、目的のメッシュを
ツール側にしてます。
f:id:kandennti:20211006150314p:plain

これで実行するとこんな感じ。
f:id:kandennti:20211006150552p:plain

処理は間違いなく出来るのですが、処理時間が長い。
ひょっとしたら何十個も処理する必要が出て来そうなので
恐ろしい事になりそう。

Meshmixerにもマクロがあるようなのですが、ドキュメントが
無い状態の様です。それは無理だな。

メッシュの六角形分割に挑む8

こちらの続きです。
メッシュの六角形分割に挑む7 - C#ATIA

MeshBodyの移動(Translate)が無いので、そちらを作成しました。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core


def meshTranslateBy(
        mesh: adsk.fusion.MeshBody,
        vec: adsk.core.Vector3D,
        copyIs: bool = False):

    app: adsk.core.Application = adsk.core.Application.get()
    ui: adsk.core.UserInterface = app.userInterface
    sels: adsk.core.Selections = ui.activeSelections

    sels.clear()
    sels.add(mesh)

    cmds = [
        u'Commands.Start FusionMoveCommand',
        u'Commands.SetString infoMoveMethodType infoMoveMethodTranslate',
        u'Commands.SetString infoMoveDirectionType infoMoveDirectionComponentXYZ',
        u'Commands.SetDouble infoMoveTranslateX {}'.format(vec.x),
        u'Commands.SetDouble infoMoveTranslateY {}'.format(vec.y),
        u'Commands.SetDouble infoMoveTranslateZ {}'.format(vec.z),
        u'Commands.SetBool infoCreateCopy {}'.format(1 if copyIs else 0),
        u'NuCommands.CommitCmd',
    ]

    [app.executeTextCommand(cmd) for cmd in cmds]

    res = adsk.fusion.MeshBody.cast(None)
    if copyIs:
        res = mesh.parentComponent.meshBodies[-1]

    return res

def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        root: adsk.fusion.Component = app.activeProduct.rootComponent

        clone = meshTranslateBy(
            root.meshBodies[0],
            adsk.core.Vector3D.create(1, 2, 3),
            True
        )

        meshTranslateBy(
            clone,
            adsk.core.Vector3D.create(1, 2, 3),
        )

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

MeshBodyの六角形は1個のみ作成し、移動コピーで済ませたいので。
Meshに関しては、APIで調べる為の機能は備わっていますが、
形状を変更したりするメソッド類が提供されていないので、
地味なものばかり作成しなきゃならないですね・・・。

メッシュの六角形分割に挑む7

こちらの続きです。
メッシュの六角形分割に挑む6 - C#ATIA

内容が薄いまま続けます。
六角柱MeshBodyが必要になります。STLフォーマット調べて作る方法も
あるかもしれませんが、サイズ変更も必要な為BRepBodyをMeshBodyへ
変換したいところです。

これもAPIでは提供されていないっぽいので。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core

def brep2Mesh(
        brep: adsk.fusion.BRepBody) -> adsk.fusion.MeshBody:

    app: adsk.core.Application = adsk.core.Application.get()
    ui = app.userInterface

    sels: adsk.core.Selections = ui.activeSelections
    sels.clear()
    sels.add(brep)

    app.executeTextCommand(f'Commands.Start ParaMeshTessellateCommand')
    app.executeTextCommand(u'NuCommands.CommitCmd')

    meshs: adsk.fusion.MeshBodies = brep.parentComponent.meshBodies

    return meshs[-1]


def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        root: adsk.fusion.Component = app.activeProduct.rootComponent

        mesh: adsk.fusion.MeshBody = brep2Mesh(root.bRepBodies[0])

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

こんな感じです。
f:id:kandennti:20211005152751p:plain