Autodesk Inventor API Hacking (だいたい一度はハマること)

# 0. はじめに
[Freeradical](https://www.freeradical.jp)の中の人、[yamarah](https://qiita.com/yamarah)です。
一度は誰もがハマる失敗を、まとめてみました。今後もハマる度に、追加していきます。
明らかに直ぐに判明するbugではなく、ある時突然発病する系のものが中心です。

# 1. SelectSet
通常はActiveEditDocumentを起点にdocumentにアクセスしますが、SelectSetだけは例外です。
ActiveDocument.SelectSetにアクセスしないと、失敗します。
だいたい、インプレース編集するまで気づきません。

# 2. Proxy
これは奥が深いので、簡単にしか触れませんが、AssemblyDocument内で、Sub Assemblyをインプレース編集中にoccurrenceにアクセスする時は、注意が必要です。
テスト項目に、インプレース編集中の動作は必ず入れるべきです。

# 3. ユーザーは自由に選択する
例えば、次のようなコマンドを開発するとします。

* 2Dスケッチ環境で
* 選択されたSketchEntityの
* 色を変える。

この場合、ユーザーがアクティブではないスケッチのSketchEntityを選択する可能性を考慮する必要があります。

# 4. 何もない場合がある

* 開いているDocumentがない。(ActiveEditDocument == null)
* PartDocumentにFeatureが1つもない。
* AssemblyDocumentにOccuurenceが1つもない。
* DrawingDocumentにViewが1つもない。
* DrawingDocumentに図面枠、表題欄がない。

# 5. オブジェクト編集中
インプレース編集中でない時にしか動作させないコマンドは、現在インプレース編集中でないかを判断してから実行する必要があります。
PartDocument, AssemblyDocumentでは、document.ActivatedObject == nullかどうか、
DrawingDocumentでは、document.ActivatedObject is Sheetかどうかで判断します。

# 6. コマンド実行中にDocumentをスイッチ
コマンドでインタラクティブな操作を実行中に、ユーザーがActiveEditDocumetを変更する可能性があります。
場合によっては、コンテキスト(データ)をDoument毎に用意する必要があります。

# 7. コマンド実行中にコマンドを実行
コマンドを実行中に、ユーザーにコマンドを起動されることがあります。
OnExecute()で、いわゆる再入が発生します。

# 8. インタラクティブな操作をする時は、一旦OnExecute()を抜けるべき
インタラクティブな操作をする時は、InteractionEvents.Start()して、即座にOnExecute()を終了すべきです。
OnExecute()から抜けずに処理していると、色々と微妙な問題が発生します。

# 9. 新規ドキュメント
新規に作成されたDocumentで、一度も保存されていないものは、一部のPropertyにアクセスすると例外を発生します。(ファイルの実体が無いので)

# 10. 解決していないドキュメント
Inventorは柔軟なので、一部のDocumentファイルがなくても、親Documentを開くことが出来ます。
その場合、リンク切れのOccurrenceの一部のPropertyにアクセスすると、例外が発生します。

# 11. BrowserNode.NativeObjectに触れると例外発生
例えばDrawingDocumentで、BrowserNode.NativeObjectに触れると例外が発生することがあります。
他には、インポートしたソリッドなども同様です。
ですので、以下のcodeは、そのうち例外を送出します。

```C#
BrowserNodesEnumerator browserNodes = document.BrowserPanes["PmDefault"].TopNode.BrowserNodes;
foreach (BrowserNode node in browserNodes)
{
    if (node.NativeObject is EndOfFeatures)
    {
        break;
    }
    //
    // 何らかの処理
    //
}

```

# 12. ファイル名問題
これはInventorのAddIn開発に限った話ではなく、Windows全般の問題ですが、あるpath(ファイル名)が、他のpathと同じファイルを指しているかどうかを判断するのが、非常に難しいです。

* 大文字、小文字
* ネットワークドライブ
* Symbolic link
* などなど・・・

path文字列同士の比較では、恐らく判断しきれないので、わざと実行時例外を発生させるようなcodeを書いて判断するしかないのかなと思います。
大文字・小文字問題はToUpper()すればいいじゃないか、という人もいますが、それ、本当に全言語のWindowsで成り立ちますか?

# 13. SelectEvents.RemoveFromSelectedEntities()してますか?
これはやれば気付く部類ですが、SelectEventsを実行しっぱしで、選択したものを次々処理するような場合は、処理が終わったobjectは`RemoveFromSelectedEntities()`で`SelectEvents`から除去する必要があります。
(でないと、HighLightしたままになる)

# 14. readonlyとstatic
例えば、次のようなコードがあるとします。

```C#
public class StandardAddInServer : Inventor.ApplicationAddInServer
{
    readonly List<Document> list1 = new List<Document>();
    static   List<Document> list2 = new List<Document>();

```
これらは、AddInを開発する上では、ほとんど同じに思えます。
(まず間違いなく、StandardAddInServerの実体は1つしか作られないのですから)
しかし、決定的な違いが出る場合があります。それは、`アドイン マネージャ`から`ロード解除` → 再`ロード`した場合です。
この場合、StandardAddInServerの実体は破棄されて再構築されますが、staticな要素はそのままです。
つまり、list1は必ず空になりますが、list2は前回のセッションの内容を残すということです。
安易にグローバル変数の代替にstaticを使うと、はまります。(自戒)

# 15. interopのプロパティ設定

下記項目は、全てFalseにしておかないと、環境によって動いたり動かなかったりします。
![interop.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424028/993ac11e-a047-e067-5da3-3b476a3d0fb4.png)
* ローカルにコピー
* 相互運用型の埋め込み
* 特定バージョン

自分の環境では動くのに、配布先で動かないという場合は、相手のInventorのversion確認は当然として、この設定も確認してみてください。

# 16. DocumentをDirtyにする可能性があるなら、InteractionEvents.Start()すべき
長くなるので、[別記事](https://qiita.com/yamarah/items/5cba5a3772df76c67086)に纏めました。
特殊な条件下で、Inventorをcrashさせる可能性があります。

# 17. SketchLineにStartSketchPoint, EndSketchPointがあるとは限らない
例えば作業線を投影した直線は、長さを持ちません。
ですので、画面上では線分ですが扱いは(数学的な)直線であり、両端がありません。

# 18. Virtual Componentに注意しろ
Virtual ComponentのOccurrenceの`DefinitionDocumentType`には、`kAssemblyDocumentObject`が設定されています。
例えば名前を参照するだけならAssemblyとして扱っても良いのかもしれませんが、経験上はだいたいハマります。Virtual Componentに出会うまで発病しないパターンです。
Virtual Componentかどうかを`occurrence.Definition is VirtualComponentDefinition`で切り分けて、特別な処理をするcodeを書くのが良いでしょう。
BOMを辿るときも、同様な注意が必要です。

# 19. 画面上のBrowserに存在しないBrowserNodeがある
`DrawingDocument`において、例えば断面ビューではビューに存在しない`Occurrence`は、画面上のBrowserにも表示されません。
しかし、内部的には`BrowserNode`は存在しており、Topから辿っていくと到達することが出来ます。
この、「存在しているのに表示されていない」`BrowserNode`にアクセスすると、例外が送出されることがあります。

```C#
BrowserNode node; // 存在しているのに表示されていないBrowserNode
// 中略
node.DoSelect();       // 何も選択されない
_ = node.Expanded;     // 例外にならない
node.Expanded = false; // 例外が送出される!!
```
# 20. 図面環境で「カスタムビューの方向」を選択すると、特殊なビューが作られる
タイトル通り、「カスタムビューの方向」を選択すると、特殊なビューが作られます。
このビューが表示中は、いくつかの機能が制限されます。(例 : `ファイル` → `開く`が使えない)
また、このビューが表示中は・・・

* ActiveDocumentがnullになる
* ActiveDocumentTypeがkNoDocumentになる
* ActiveViewの幾つかのメンバー(例えば、Parent)に触れるだけで例外が発生する

などの、通常と異なった振る舞いをします。
常駐系のAddInを開発する場合は、この状況で誤動作しないように注意が必要です。
下記codeで「カスタムビューの方向」が実行中かどうか、判断できます。

```c#
if (InventorApplication.ActiveEnvironment.InternalName == "DLxDrawingCustomViewEnvironment")
{
    // Drawing環境で、カスタムビューを実行中
    // Errorを表示するなり、処理を終了するなり・・・
}
```

# 21. InteractionEvents実行中にWinFormを作るとInventor終了時にエラーが起きる
`InteractionEvents`とDialog(WinForm)を同時に使うと、ごくまれにInventor終了時にアプリケーションエラーが発生します。(Inventor 2020にて確認)
Dialogがmodelでもmodelessでも関係ない、というか、`new`していれば表示しなくてもエラーが起きます。
かなり再現性が低いのですが、ゼロではありません。
~~残念ながら、回避する方法はなさそうなので、こういう構成のアプリケーションは作成してはいけない、ということです。~~

19.11.01 追記
回避策を見つけました。
`InteractionEvents`中で登録したEvent(例えば、`InteractionEvents.SelectEvents.OnPreSelect`)を、`InteractionEvents.OnTerminate`などで登録解除すると、エラーが出なくなりました。

# 22. SelectEvent中にAddByProjectingEntityするなら、先にRemoveFromSelectedEntitiesしろ
これはタイトルの通りで、SelectEventで要素を選択して、それを投影する場合は、投影するより先に選択表示を解除する必要があります。
後で解除すると、不安定な`OnPreSelect`や`OnSelect`が発行されることがあります。
また、選択表示解除しないと、見た目は選択が解除されているが内部的には選択表示のままになりることがあります。

# 23. Sketches.Addが迷子のSketchを返すことがある
AssemblyDocument中のPartDocumentをIn-Place編集中に、AssemblyのWorkplaneを引数に`Sketches.Add(workplane)`すると、しれっとPlanarSketchが返ってくるのですが、このSketchはどのDocumentにも属していない迷子のSketchです。
編集しようとすると、Inventorごと落ちることがあります。
手動でこの操作をすると、AdaptiveなWorkplaneが自動的に作られ、そこにSketchが作成されます。ですので、APIでも同様に動作するか、もしくはいっそ例外を投げて欲しいところです。

# 24. スケッチブロック編集
スケッチ編集中に動作するAddInを作る場合は、それがスケッチブロックを編集中でも動作するか確認すべきです。
残念ながら、2020年5月6日時点では、APIでスケッチブロック内に拘束を付与することが出来ないようなので([4. SketchBlockDefinition内に拘束付与できない](https://qiita.com/yamarah/items/373e3a8b4169312b1749#4-sketchblockdefinition%E5%86%85%E3%81%AB%E6%8B%98%E6%9D%9F%E4%BB%98%E4%B8%8E%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84))、スケッチ拘束を操作するAddInはスケッチブロック編集中に動作しないようロックするなどの措置が必要です。
また、`SketchBlockDefinition`は`PlanarSketch`を継承しているので、条件分岐のコードを書く場合は順序に注意が必要です。
(具体的には、先に`SketchBlockDefinition`かどうかを確認すべき。)

# 25. Model State(モデル状態)に気を付けろ
Inventor2022からModel State(モデル状態)が導入されました。これにより、同じDocumentだとしても、形状やiPropertyが違う可能性が出てきました。
情報をキャッシュしている場合や、同じDocumentのOccurrenceなら内容も同じと決め打ちしている場合は、正常に動作しない可能性があります。

# 26. SheetMetalに気を付けろ
SheetMetalは普通のパーツと違うところがあるのですが、Documentとしては、`PartDocument`です。DocumentのFeatureをなめて操作するToolだと、SheetMetalはFeatureが"曲げモデル"というFolderに入っているので、専用の処理が必要になります。
次のような拡張メソッドを定義するのが良いでしょう。

```C#
public static BrowserNodesEnumerator GetFeatureNodes(this PartDocument PartDocument)
{
    BrowserNodesEnumerator browserNodes = PartDocument.BrowserPanes["PmDefault"].TopNode.BrowserNodes;
    if (PartDocument.ComponentDefinition is not SheetMetalComponentDefinition)
    {
        return browserNodes;
    }
    foreach (BrowserNode node in browserNodes)
    {
        try
        {
            if (node.NativeObject is SheetMetalComponentDefinition)
            {
                return node.BrowserNodes;
            }
        }
        catch { }
    }
    throw new InvalidOperationException();
}
```
# 99. 親の記事に戻る
[Autodesk Inventor API Hacking (概略)](https://qiita.com/yamarah/items/cee08eb8f2b7aecbda83)