AutoDesk Fusion APIからビューポート(表示されている画面)を回転させる方法(SpaceMouse Driver Making)

<この記事のgitHubリンク>
https://github.com/kamoshika9999/https---github.com-kamoshika9999-Orbion_3D_Space_Mouse.git

下記リンク先のスペースマウスを使いたいが動作としてはマウスエミュレーションで左手デバイスとしては致命的な難点となっている。これを解消する為デバイスドライバを作成することが最終目標である。

https://github.com/FaqT0tum/Orbion_3D_Space_Mouse

ドライバ制作のプロセスとしては味見をした所以下の手順を踏めばできると思われる。
①Fusion APIにてビューポートを制御する方法を確立する
②APIからUSB経由のシリアル通信をポーリングする方法を確立する
③ORBIONに搭載されているArduino Pro Microのファームを変更する。
　変更ポイントとしては、mouseイベントの送信部分をシリアル出力へ置き換える事
 現状のファームではHIDデバイスとして認識されるので、これをCOMポートとして認識させる方法も確率する必要がる。
 
今回は①の部分の一部が判明しAPIから制御可能と確信を得た。
アクティブコンポーネントの中心で回転させる方法も判明した。(2024/02/29)


ポイントとしては、
adsk.core.Application.activeViewpot.camera
このオブジェクトのコピーを取得しプロパティを変更
変更したcameraオブジェクトを、このプロパティに代入すると代入した瞬間に適用され表示が更新される。
adsk.core.Cameraオブジェクトのeye,upVector,Targetを同時に同じ回転行列を用い変換してやるとアクティブコンポーネントを中心にした回転が実現できた。
３つのパラメータを同時に回転させたときの数学的な関係性の不明点は残っているが、実用上これで問題無いとし③のORBIONマウスのファームを作っていくことにする

※テストする場合はスクリプトとしてインポートすること
~~~
#Author-KamoShikaCamper
#Description-
"""
転載禁止
商用利用不可
"""

import adsk.core, adsk.fusion, adsk.cam, traceback
import threading
import math
import time

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

        # アクティブなビューポートを取得
        vp:adsk.core.Viewport = app.activeViewport
        #カメラのコピーを取得
        MyCamera:adsk.core.Camera = vp.camera
        #カメラ更新時スムーズ移動を抑制
        MyCamera.isSmoothTransition = False
        #カメラパラメータ取得
        eye: adsk.core.Point3D = MyCamera.eye
        vec: adsk.core.Vector3D = MyCamera.upVector
        target:adsk.core.Point3D = MyCamera.target
        #ループ用変数
        angle:int = 0
        #回転角度のステップ
        angle_step:float =5.0
        #回転行列設定 テストコードなのでY軸中心に回転
        rotMatrix:adsk.core.Matrix3D = adsk.core.Matrix3D.create()
        rotMatrix.setToRotation( math.radians(angle_step),
                        adsk.core.Vector3D.create(0,1,0),
                        adsk.core.Point3D.create(0,0,0))
        

        while angle <360/angle_step:
            eye.transformBy(rotMatrix)
            vec.transformBy(rotMatrix)
            target.transformBy(rotMatrix)

            MyCamera.eye = eye
            MyCamera.upVector = vec
            MyCamera.target = target
            #変更した視点のカメラの割り当て
            vp.camera = MyCamera
            #画面強制更新
            vp.refresh()
            angle += 1
            #待ち時間 10mSec
            time.sleep(0.01)
            #Fusionのメッセージ処理　フリーズ防止
            adsk.doEvents()

        print("完了")

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

~~~

---
2024/03/03追記
シリアル通信による動作が可能となった為、GitHub上へフォークしておく。
https://github.com/kamoshika9999/https---github.com-kamoshika9999-Orbion_3D_Space_Mouse.git

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672650/b41b29cd-0906-0b27-43f0-e2ff6500b18f.png)
FusionAPI_rotate.pyの使い方だが、シリアル通信を行う必要がある関係上pyserialをFusionAPIで使用しているPythonへインストールする必要がある。他サイトで説明されている為割愛しリンクを貼っておく。
https://qiita.com/JotaroS/items/3f878cd2a2ce839e6d38
https://qiita.com/kosystem/items/0023cfee941fdf099087

注意点があり、pyserialをインストールする前にシステム環境変数にPytho.exeとsite-packagesのパスを通しておく必要がある。
（例)
PATH=
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672650/1f3852d0-cbd7-ad9e-e083-888d9c87d170.png)

フォークしたコードの変更点だが、main.cppのみでシリアル通信の設定を追加した
オリジナルのOrbion_FW_v0.26.5.zipのmain.cppを上書きすれば変更出来る。
OrbionSpaceMouseの設定に入り、JoyModeをSerialに変更すれば適用完了

Fusion上でスクリプトとしてFusionAPI_rotate.pyを実行すれば良いのだが、COMポートは環境に合わせて変更する必要があるので注意(下記部分のCOM3を変更すること）
~~~
def run(context):
    ui:adsk.core.UserInterface = None
    app:adsk.core.Application = adsk.core.Application.get()
    ui  = app.userInterface
    x_rotMatrix:adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    y_rotMatrix:adsk.core.Matrix3D = adsk.core.Matrix3D.create()

    try:
        ser = serial.Serial("COM3", 9600, timeout=1)
        print(ser.name)
        print("開始")
~~~
独立した回転はするが、まだ回転軸が不自然で２D座標上の軸で回転せずビューポートのX/Y軸で回転してしまう。未解決

---
2024/03/04　追記
コンポーネントの回転軸の指定方法が判明した。但し、コンポーネントがワールド座標の中心にある場合のみ正常に機能する。
非常に回りくどい方法しか思いつかずすっきりはしていない。
X方向の回転軸はadsk.core.viewport.cmera.upVectorのVector3DにてビューポートのY軸を取得できた。
問題はY方向の回転、まずeye.vectorTo(MyCamera.target)にてビューポートの奥行方向へのVector3Dを取得しsetToRotationにてビューポートのX軸へ一致させる為90度回転させる回転行列を作成
回転行列を使用しコンポーネントをY方向へ回転させる。
このeye.vectorTo(MyCamera.target)のアイデアは公式のAutodeskCommunityサイとで見つけ試行錯誤した
https://forums.autodesk.com/t5/fusion-ri-ben-yu/byukyubude90du-yi-waino-hui-zhuanwosasetai/td-p/10762584

ノブを押し込むことでスクリプトを終了させシリアル通信を切断できる
次回は、拡大/縮小を作っていく。
その後、ファームの設定を変更する為のユーティリティを作る予定

GitHubへフォークしているファームはボタン１～３とノブの回転と押し込みボタンまでもシリアル通信での通知となっているが、そのコードの一部を書き換えればオリジナルと同様のマウス/キーボードエミュレーションをその部分のみ復活させることはできる。完全な左手デバイスとしての動作とは異なる

~~~
            #回転行列設定 
            x_rotMatrix.setToRotation( math.radians(x_angle_step),
                            vec,
                            adsk.core.Point3D.create(0,0,0))
                        
            shaft_rotMatrix.setToRotation(math.radians(90),
                            eye.vectorTo(MyCamera.target),
                            adsk.core.Point3D.create(0,0,0))

            
            shaft_vec.transformBy(shaft_rotMatrix)
            y_rotMatrix.setToRotation( math.radians(y_angle_step),
                            shaft_vec,                            
                            adsk.core.Point3D.create(0,0,0))
~~~


2024/03/06　追記
コンポーネントがワールド座標の中心になくとも、コンポーネントの中心を正しく得る方法が判明しコードを修正・GitHubへアップロードしておいた。ファームも微修正を加えている
ノブの回転による拡大縮小にも対応。こちらはカメラのエクセントを変更することが簡単に実装できた
ボタンのショートカットはまだ未対応
~~~
        while flg2:
            des:adsk.fusion.Design = app.activeProduct
            actComp = des.activeComponent
            actTarget:adsk.core.Point3D = adsk.core.Point3D.create(
                (actComp.boundingBox.maxPoint.x + actComp.boundingBox.minPoint.x)/2,
                (actComp.boundingBox.maxPoint.y + actComp.boundingBox.minPoint.y)/2,
                (actComp.boundingBox.maxPoint.z + actComp.boundingBox.minPoint.z)/2)
~~~
①アクティブコンポーネントのバウンディングボックスを中心を計算
これがワールド座標上のアクティブコンポーネントの座標となる
複数のタブを開いているときに対応できるように毎回直前にactiveProductを取得している
Fusionを複数プロセスで開いている時の動作検証はできていない
~~~
            #回転行列設定 テストコードなのでY軸中心に回転
            x_rotMatrix.setToRotation( math.radians(x_angle_step),
                            vec,
                            actTarget)
            
            
            shaft_rotMatrix.setToRotation(math.radians(90),
                            eye.vectorTo(MyCamera.target),
                            adsk.core.Point3D.create(0,0,0))

            
            shaft_vec.transformBy(shaft_rotMatrix)
            y_rotMatrix.setToRotation( math.radians(y_angle_step),
                            shaft_vec,                            
                            actTarget)
~~~
②回転行列の生成に①で取得したPoint3Dを設定する

2024/03/10 追記
AutoDesk Fusion用のアドインとしてコードを修正しGitHubへアップロード
シリアルポートが見つからず動作しない場合はコードの
~~~
        for port in ports:
            description=port.description
            if 'Arduino' in description:
                print("Arduino Leonardo = ",port.device)
                ser = serial.Serial(port.device, 115200, timeout=10)
                break
~~~
この部分を削除し
~~~
ser = serial.Serial("COM4などのポート文字列を記述", 115200, timeout=10)
~~~
に修正してください。
シリアルポートの名前かPID&VIDを固定できれば解決するのだが...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672650/e7d2c7db-3d9d-2cdb-41ec-02c1da2ee82d.png)