C#ATIA

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

スタート時BrowserCommandInputに初期値を渡す

以前からどうやって良いのかわからなかった処理が、やっと
昨夜分かりましたので、記載しておきます。
(上手く行った際、思わず声が出た・・・)


元ネタはこちらです。
パレットに苦しむ2 - C#ATIA
パレットの中身はブラウザなのですが、旧タイプはCEFコンポーネント
(Chromium Embedded Framework ...chromeとかedgeに使われている奴)
ですが、新タイプはQtWebブラウザーコンポーネントです。
そして再三、旧タイプは無くなってしまうので切り替えるように
アナウンスされています。

先程の元ネタでは旧タイプを使用しているのですが、あのまま新タイプに
してしまうと、初期値をパレットに反映することが出来ませんでした。
(python -> javascriptのデータの受け渡しが出来なかったです)

その為、旧タイプから新タイプに切り替えることが出来ずに放置して
いました。・・・当然、後々のことを考えれば良くないです。


BrowserCommandInputも中身はブラウザなのですが、かなり後から
実装されたため旧タイプが無く、新タイプのQtWebブラウザー
コンポーネントです。 つまり初期値の受け渡し方法が分からなく
ここ一週間ほど、悩んでいました。


何せFusion360APIの内容な為、検索してもそれらしきサンプルが
見つからず、自分で試すしか方法がありません。
又、起動時の処理はデバッグが非常に行いにくく、無い知恵絞って
試すのですが、何れも上手く行かず途方に暮れていました。

解決のきっかけは、公式ドキュメントに記載されていました。
(真ん中より少し下に記載あり。ちゃんと読むべき)
Fusion 360 Help
CEFコンポーネントは同期処理されるが、QtWebブラウザーコンポーネント
は非同期処理されるとの事。 最初は "フーン" 程度で完全には理解
出来ませんでした。と言うか、どの様に対処知れば良いのかが
分かりませんでした。

javasprictを理解しきっていないのですが、どうやらコールバック関数や
thenを使えって事の様です。


と言う事で、必要最低限の出来上がったサンプルです。
スクリプト起動時に、アクティブなドキュメント名をBrowserCommandInput
で表示させています。

こちらは、python側です。まぁこちらはこれと言って問題無かったです。

# Fusion360API Python script

import adsk.core
import adsk.fusion
import traceback

_app: adsk.core.Application = None
_ui: adsk.core.UserInterface  = None

_cmdId = 'browserInputTest'
_handlers = []


class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            cmd = adsk.core.Command.cast(args.command)

            # inputs
            inputs: adsk.core.CommandInputs = cmd.commandInputs

            # global _browserIpt
            inputs.addBrowserCommandInput(
                'browserIptId',
                '',
                'index.html',
                300,
                100
            )

            # event
            onDestroy = MyDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            onIncomingFromHTML = MyIncomingFromHTMLHandler()
            cmd.incomingFromHTML.add(onIncomingFromHTML)
            _handlers.append(onIncomingFromHTML)

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


class MyIncomingFromHTMLHandler(adsk.core.HTMLEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args: adsk.core.HTMLEventArgs):
        if args.action == 'DOMContentLoaded':
            args.returnData = getActDocName()


class MyDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.CommandEventArgs.cast(args)

        adsk.terminate() 

def getActDocName() -> str:
    global _app
    return _app.activeDocument.name


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

        global _cmdId
        cmdDef = _ui.commandDefinitions.itemById(_cmdId)
        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition(
                _cmdId,
                'Browser Input Test',
                'Browser Input Test'
            )

        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()))

続いて、html側で "index.html" と言うファイル名にしています。
上記のpython(.py)ファイルと同一フォルダに配置しておきます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Qt Web Browser Test</title>
  </head>
  <body>
    <p id="xxx">** BrowserInput Test **</p>
  </body>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      let adskWaiter = setInterval(() => {
        console.log("DOMContentLoaded");
        if (window.adsk) {
          console.log("adsk ok");
          clearInterval(adskWaiter);

          let element = document.getElementById("xxx");
          adsk
            .fusionSendData("DOMContentLoaded", "{}")
            .then((data) =>
              element.insertAdjacentHTML("afterend", "<p>" + data + "</p>")
            );
        }
      }, 100);
    });
  </script>
</html>

実行するとこんな無意味なダイアログが表示されます。

赤矢印はhtmlにベタに記載していますが、青矢印はイベントで
後から追記させたアクティブなドキュメント名です。


細々とした(幼児レベルの)覚書です。

・元ネタJeromeBriotさんの物は、"window.onload" でイベントを
拾っていますが、あの方法はあまり良くないとの記述を見つけました。
window.onloadの利用はお勧めしない その理由
その為、addEventListenerを利用するようにしました。

・"addEventListener" する際、 "load" "DomContentLoaded" の
どちらかのイベントを利用するのですが、呼び出しタイミングの
早い "DomContentLoaded" にしました。

元ネタでは "window.adsk" の記載でしたが、"window” は
省略可能だと知っていました。

今回の例では

if (adsk) {

でも上手く処理出来るのですが、上手く行かない時がある為

if (window.adsk) {

とする方が良い事が分かりました。

・元ネタでは

var adskWaiter = setInterval(function () {

になっていますが、アロー関数を利用すると

let adskWaiter = setInterval(() => {

と書けることを知っていました。
(varも良くない!)

・恐らく一発では "adsk" (オブジェクト?) を見つける事が
出来ないのだと思うので、setIntervalでadskを探し出し
発見次第にclearIntervalで止めていると理解しました。

・非同期のpromiseオブジェクトの場合、thenを利用した
メソッドチェーンで処理することがお作法だと教わりました。
【ES6】 JavaScript初心者でもわかるPromise講座 - Qiita



あぁやっと進める。 時間出来たら他のアドインも書き換えなきゃ。