C#ATIA

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

三次元ベクトルの内積と長さ

三次元ベクトルを扱っていると外積やら内積やら単位化やら必要に
なってきますよね?

自分だったらベクトルクラス作って、メソッドにそれらを実装します。
外積内積や単位化は、ベクトルの為の関数なので、ベクトルクラスに
責任持ってもらうのが筋だと思いませんか?

ですが、訳あって関数で必要になりそうなので、切り出したのですが
今まで気が付きませんでした・・・。
内積と長さを求める関数です。

'三次元ベクトルの内積
'param: vec1_array(double)-ベクトル
'param: vec2_array(double)-ベクトル
'return: スカラー
Private Function dot_3d( _
        ByVal vec1 As Variant, _
        ByVal vec2 As Valiant) As Double

    dot_3d = _
        vec1(0) * vec2(0) + _
        vec1(1) * vec2(1) + _
        vec1(2) * vec2(2)

End Function


'三次元ベクトルの長さ
'param: vec_array(double)-ベクトル
'return: 長さ
Private Function get_length_3d( _
        ByVal vec As Variant) As Double

    get_length_3d = Sqr( _
        vec(0) * vec(0) + _
        vec(1) * vec(1) + _
        vec(2) * vec(2) _
    )

End Function

長さの計算の際に二乗で計算させると気が付きにくいのですが
長さの計算式って、自身同士の内積平方根なんですね。
だからこんな風に関数を作っても良さそう。

'三次元ベクトルの長さ
'param: vec_array(double)-ベクトル
'return: 長さ
Private Function get_length_3d( _
        ByVal vec As Variant) As Double

    get_length_3d = Sqr( _
        dot_3d(vec, vec) _
    )

End Function

無関係だけど、外積はこちら

'三次元ベクトルの外積
'param: vec1_array(double)-ベクトル
'param: vec2_array(double)-ベクトル
'return: array(double)-ベクトル
Private Function cross_3d( _
        ByVal vec1 As Variant, _
        ByVal vec2 As Valiant) As Variant

    cross_3d = Array( _
        vec1(1) * vec2(2) - vec1(2) * vec2(1), _
        vec1(2) * vec2(0) - vec1(0) * vec2(2), _
        vec1(0) * vec2(1) - vec1(1) * vec2(0) _
    )

End Function

Xの成分を求める計算はお互いのYZの利用して・・・
って感じで不思議な事は印象に残ってます。
(計算式はいつまでたっても覚えない)

複数のボディをカットする

ちょっとボリュームありそうだと思いつつ、こちらに挑戦しました。
Cutting multiple bodies with single tool body - Autodesk Community

ボディ(ソリッド)の結合の切り取りを大量のボディで行いたいようです。
本来の機能の場合は、

赤のターゲットボディは1個で、緑のツールボディは複数個指定が可能なのですが、
望んでいるのはその逆だと判断しました。

APIフォーラムに投げていますが、こちらでも転載しておきます。
(ネタが無いので)

# Fusion360API Python script

import traceback
import adsk
import adsk.core as core
import adsk.fusion as fusion
import sys

_app: core.Application = None
_ui: core.UserInterface = None
_handlers = []

CMD_INFO = {
    "id": "kantoku_cutting_multiple_bodies_test",
    "name": "cutting multiple bodies test",
    "tooltip": "cutting multiple bodies test"
}

_targetOccIpt: core.SelectionCommandInput = None
_toolBodyIpt: core.SelectionCommandInput = None

class MyCommandCreatedHandler(core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: core.CommandCreatedEventArgs):
        try:
            global _handlers
            cmd: core.Command = core.Command.cast(args.command)
            inputs: core.CommandInputs = cmd.commandInputs

            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            onValidateInputs = MyValidateInputsHandler()
            cmd.validateInputs.add(onValidateInputs)
            _handlers.append(onValidateInputs)

            onExecutePreview = MyExecutePreviewHandler()
            cmd.executePreview.add(onExecutePreview)
            _handlers.append(onExecutePreview)

            global _targetOccIpt
            _targetOccIpt = inputs.addSelectionInput(
                "_targetOccIptId",
                "Occurrences",
                "Select Target Occcurrence"
            )
            _targetOccIpt.addSelectionFilter("Occurrences")
            _targetOccIpt.setSelectionLimits(1,)

            global _toolBodyIpt
            _toolBodyIpt = inputs.addSelectionInput(
                "_toolBodyIptId",
                "Tool Body",
                "Select Tool Body"
            )
            _toolBodyIpt.addSelectionFilter("SolidBodies")

        except:
            _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))


class MyExecutePreviewHandler(core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args: core.CommandEventArgs):
        global _targetOccIpt
        occs = []
        for idx in range(_targetOccIpt.selectionCount):
            occs.append(_targetOccIpt.selection(idx).entity)

        global _toolBodyIpt
        exec_combines(
            occs,
            _toolBodyIpt.selection(0).entity
        )

        args.isValidResult = True


def exec_combines(
        targetOccs: list[fusion.Occurrence],
        toolBody: fusion.BRepBody,
) -> None:
    try:
        bodies = get_occurrences_has_bodies(
            targetOccs,
            get_show_bodies(),
        )
        if len(bodies) < 1: return

        exec_combine_diff(
            bodies,
            toolBody,
        )
    except:
        _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))


def exec_combine_diff(
        bodies: list[fusion.BRepBody],
        toolBody: fusion.BRepBody,
) -> None:
    try:
        global _app
        des: fusion.Design = _app.activeProduct
        root: fusion.Component = des.rootComponent

        combFeats: fusion.CombineFeatures = root.features.combineFeatures
        for body in bodies:
            if body == toolBody: continue

            combIpt: fusion.CombineFeatureInput = combFeats.createInput(
                body,
                core.ObjectCollection.createWithArray(
                    [toolBody]
                ),
            )
            combIpt.operation = fusion.FeatureOperations.CutFeatureOperation
            combIpt.isKeepToolBodies = True

            combFeats.add(combIpt)

        toolBody.isLightBulbOn = False

    except:
        _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))

def get_occurrences_has_bodies(
        targetOccs: list[fusion.Occurrence],
        bodies: list[fusion.BRepBody],
) -> list[fusion.BRepBody]:
    
    bodyLst = []
    for body in bodies:
        if body.assemblyContext in targetOccs:
            bodyLst.append(body)

    return bodyLst


def get_show_bodies() -> list[fusion.BRepBody]:
    global _app
    des: fusion.Design = _app.activeProduct
    root: fusion.Component = des.rootComponent

    showBodies = root.findBRepUsingPoint(
        core.Point3D.create(0,0,0),
        fusion.BRepEntityTypes.BRepBodyEntityType,
        sys.float_info.max,
        True,
    )

    return list(showBodies)


class MyValidateInputsHandler(core.ValidateInputsEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args: core.ValidateInputsEventArgs):
        global _targetOccIpt
        if _targetOccIpt.selectionCount < 1:
            args.areInputsValid = False
            return

        global _toolBodyIpt
        if _toolBodyIpt.selectionCount < 1:
            args.areInputsValid = False
            return


class MyCommandDestroyHandler(core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: core.CommandEventArgs):
        adsk.terminate()


def run(context):
    try:
        global _app, _ui
        _app = core.Application.get()
        _ui = _app.userInterface

        cmdDef: core.CommandDefinition = _ui.commandDefinitions.itemById(
            CMD_INFO["id"]
        )

        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition(
                CMD_INFO["id"],
                CMD_INFO["name"],
                CMD_INFO["tooltip"]
            )

        global _handlers
        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        cmdDef.execute()

        adsk.autoTerminate(False)

    except:
        if _ui:
            _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))

気持ち薄々なので、厳密な処理はしていないのですが(外部コンポーネントのチェック等)
ソコソコ機能するとは思っています。

Fusion360APIでnumpy

過去に何度か書いているけど、忘れる。
アドインのフォルダに入れる形で一つだけ使ったけど、Updateで今は動かない。

これが一番良い方法っぽい。
(実行時にDLさせている。起動時にちょっと時間がかかるよって事らしい)
Solved: Re: How to use modules like numpy, scipy etc. by conda in Fusion 360 scripts? - Autodesk Community

Fusion360APIのpythonって、仮想環境なんじゃないのかな?

表示されているボディ毎にセットアップを作る

最近はがんばってAPIフォーラムで答えています。

こちらで回答したものをそのまま転載ですが、表示されている
ボディ毎にセットアップとNCプログラムを作成します。

# Fusion360API Python script

import traceback
import adsk.core as core
import adsk.fusion as fusion
import adsk.cam as cam
import sys

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

        camObj: cam.CAM = get_cam_product()

        rootOcc: fusion.Occurrence = camObj.designRootOccurrence
        bodies = get_show_bodies(rootOcc.component)
        if len(bodies) < 1: return

        for body in bodies:
            create_nc_program(
                camObj,
                create_setup(
                    camObj,
                    body
                )
            )

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


def create_nc_program(
        camObj: cam.CAM,
        targetSetup: cam.Setup,
) -> cam.NCProgram:
    
    ncPrograms: cam.NCPrograms = camObj.ncPrograms
    ncIpt: cam.NCProgramInput = ncPrograms.createInput()
    ncIpt.displayName = targetSetup.name

    set_cam_parameter(
        ncIpt,
        "nc_program_filename",
        targetSetup.name,
    )

    set_cam_parameter(
        ncIpt,
        "nc_program_openInEditor",
        True,
    )

    ncIpt.operations = [targetSetup]
    ncPrograms.add(ncIpt)


def get_show_bodies(
        root: fusion.Component
) -> list[fusion.BRepBody]:
    
    bodies = root.findBRepUsingPoint(
        core.Point3D.create(0,0,0),
        fusion.BRepEntityTypes.BRepBodyEntityType,
        sys.float_info.max,
        True,
    )

    return list(bodies)


def create_setup(
        camObj: cam.CAM,
        targetBody: fusion.BRepBody,
) -> cam.Setup:

    setups: cam.Setups = camObj.setups
    setupIpt: cam.SetupInput = setups.createInput(
        cam.OperationTypes.MillingOperation
    )
    setupIpt.models = [targetBody]
    setup: cam.Setup = setups.add(setupIpt)
    setup.name = targetBody.name

    set_cam_parameter(
        setup,
        'job_stockMode',
        'default',
    )

    set_cam_parameter(
        setup,
        'job_stockOffsetMode',
        'keep',
    )

    return setup


def get_cam_parameter(
        camEntity,
        name: str,
) -> cam.CAMParameter:
    try:
        prm: cam.CAMParameter = camEntity.parameters.itemByName(
            name
        )
        if not prm: return None

        return prm.value.value

    except:
        return None


def set_cam_parameter(
        camEntity,
        name: str,
        value: str,
) -> bool:
    
    try:
        prm: cam.CAMParameter = camEntity.parameters.itemByName(
            name
        )
        if not prm: return False

        prm.value.value = value

        return True

    except:
        return False


def get_cam_product() -> cam.CAM:

    app: core.Application = core.Application.get()
    activete_cam_env()

    return app.activeProduct


def activete_cam_env() -> None:

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

    camWS: core.Workspace = ui.workspaces.itemById('CAMEnvironment') 
    camWS.activate()

これはデザインから実行しても、製造に切り替えて作ります。

まぁ正直に書くと、本当に基本的な事しか行っていない為、
色々と設定が不足しています。

段々思い出してきましたよ。

Fusion360からVSCodeが起動できない時の対策

どうも、VSCodeのms_python拡張のバージョニング問題らしいの
ですが、「編集」からVSCodeが起動しないっぽいです。

自分は普通に起動しますが、確認してみると・・・

結構古い。

こちらに対処方法が記載されていました。
Solved: Cannot launch VSCode by "Scripts and Add-ins" -> "Edit" - Autodesk Community

うっかりバージョン上げちゃうので、覚書です。

addTransparentメソッド

少し前のUpdateでパレットを作成するメソッドにaddTransparentメソッドが
追加されていました。
Fusion Help

パレットの背景を透明に出来るようです。

今までだと、ボディ上にダイアログを持ってくると、当然ボディが見えなく
なります。

addTransparentメソッドを使用するとこんな感じです。

ダイアログ自体が見にくい気もしますが、自分の様にモニターが小さい場合は
画面の見える範囲を狭めないので、案外良いかもしれません。
完全に透明では無くて、透明率が指定出来て曇りガラスっぽく出来ると
もっと良いんですけどね。

オペレーション名に工具径を追記する

久々にAPIフォーラムの質問?要望?に挑戦してみました。

最初は要望の意味が分からず・・・苦戦した上で作ったものが
こちらに添付したものです。

Solved: Re: fast rename dont work properly - Page 2 - Autodesk Community

選択した”ドリル”(オペレーション)の名前に工具の直径を
追記/削除します。

”ドリルだけ”が中々理解出来ませんでしたよ。