LoginSignup
0
0

More than 3 years have passed since last update.

Autodesk Inventor API Hacking (Transaction処理)

Posted at

0. はじめに

少し複雑な処理をすると、Transactionを使ってオブジェクト操作履歴を1つに纏めたくなるでしょう。
しかし、例外やnullチェックでの早期リターンで、確実にAbort(もしくはEnd)出来ていないbugを内包する可能性があります。
Autodeskではこの対策としてChangeProcessorを推奨しているようですが、必ずしも使いやすいとは言えないので、何か良い方法がないか模索してみました。

1. usingを使おう

C#には言語仕様でusingがあります。
usingが何かを簡単に解説すると、CやC++では、ローカルオブジェクトはスコープを抜けるタイミングでデストラクターが呼ばれる保証がありますが、C#ではガーベージコレクションのタイミングまで存続するので、その欠点を補うものです。

InventorのTransaction自体がIDisposableを継承していれば話は簡単なのですが、そうではないので、wrapper classを作りました。
普段使う機能しか実装していないので、名付けてTinyTransactionです。

2. TinyTransactionの使い方

想定される典型的な使い方。

void foo(void)
{
    Document doc;
    bool isChanged = false;

    //
    // docの初期化など
    //
    using (var transaction = new TinyTransaction(doc, "処理の名前"))
    {
        if (something == null) {
            // この場合は、Abort()される。
            return;
        }
        try {
            //
            // いろいろとオブジェクトを操作する
            //
        } catch (Exception ex) {
            // 異常時はAbortする
            transaction.Abort();
            throw;
        }
        finally {
            if (isChanged) {
                // 変更した場合のみ、End()する。
                // 例外のcatchで先にAbort()していた場合は、このEnd()は無視される。
                transaction.End();
            }
        }
    }
}

要点としては、

  • End()しない場合は、Abort()される。
  • End()/Abort()後にEnd()もしくはAbort()しても無視される。(つまり、早い者勝ち)

ということです。
tryとusingのどちらを外側にするかですが、私が組んでいる限りは例の通りusingが外側の方が多いですね。

3. TinyTransactionのソース

例えばPartDocument型はDocument型に自動変換されないため、コンストラクターを各Document型用に用意しました。

using Inventor;
using System;

namespace InvAddIn
{
    /// <summary>
    /// InventorのTransactionをラップするclassです。
    /// Dispose()でAbort()します。
    /// </summary>
    internal class TinyTransaction : IDisposable
    {
        /// <summary>
        /// Inventorのトランザクションオブジェクトです。
        /// </summary>
        Transaction transaction;
        /// <summary>
        /// Inventorのアプリケーションオブジェクトです。
        /// </summary>
        readonly Application application;
        /// <summary>
        /// トランザクションオブジェクトの生成に必要なdocumentです。
        /// </summary>
        readonly Document document;
        /// <summary>
        /// トランザクション名です。
        /// </summary>
        readonly string transactionName;
        /// <summary>
        /// Dispose()が実行されるとtrueになります。
        /// </summary>
        bool disposed = false;

        /// <summary>
        /// TinyTransactionのコンストラクターです。
        /// </summary>
        /// <param name="inventor">Inventorのアプリケーションオブジェクトです。</param>
        /// <param name="_document">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(Application inventor, Document _document, string name, bool deferredStart = false)
        {
            application = inventor;
            document = _document;
            transactionName = name;
            if (deferredStart == false)
            {
                Start();
            }
            else
            {
                transaction = null;
            }
        }

        /// <summary>
        /// デストラクターです。トランザクションが実行中であればAbort()してリソースを破棄します。
        /// </summary>
        ~TinyTransaction()
        {
            Dispose(false);
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、documentから得ます。
        /// </summary>
        /// <param name="_document">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(Document document, string name, bool deferredStart = false) : this((Inventor.Application)document.DocumentEvents.Application, document, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、partDocumentから得ます。
        /// </summary>
        /// <param name="partDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(PartDocument partDocument, string name, bool deferredStart = false) : this((Document)partDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、assemblyDocumentから得ます。
        /// </summary>
        /// <param name="assemblyDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(AssemblyDocument assemblyDocument, string name, bool deferredStart = false) : this((Document)assemblyDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、drawingDocumentから得ます。
        /// </summary>
        /// <param name="drawingDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(DrawingDocument drawingDocument, string name, bool deferredStart = false) : this((Document)drawingDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// トランザクションを開始します。
        /// </summary>
        public void Start()
        {
            if (disposed)
            {
                throw new ObjectDisposedException(GetType().FullName);
            }
            if (transaction != null)
            {
                throw new InvalidOperationException();
            }
            transaction = application.TransactionManager.StartTransaction((_Document)document, transactionName);
        }

        /// <summary>
        /// トランザクションが実行中であれば、tureを返します。
        /// </summary>
        /// <returns>実行中であればtrue。</returns>
        public bool IsRunning()
        {
            if (disposed)
            {
                throw new ObjectDisposedException(GetType().FullName);
            }
            return transaction != null;
        }

        /// <summary>
        /// リソースを破棄します。
        /// トランザクションが実行中であれば、Abort()します。
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                Dispose(true);
                GC.SuppressFinalize(this);
                disposed = true;
            }
        }

        /// <summary>
        /// リソース破棄の内部処理です。
        /// </summary>
        /// <param name="disposing">Dispose()から呼ぶ場合は、True。デストラクターから呼ぶ場合はfalse。</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // managedリソースの破棄
            }
            // unmanagedリソースの破棄
            Abort();
        }

        /// <summary>
        /// トランザクションを中止します。
        /// </summary>
        public void Abort()
        {
            if (transaction != null)
            {
                transaction.Abort();
                transaction = null;
            }
        }

        /// <summary>
        /// トランザクションを終了します。
        /// </summary>
        public void End()
        {
            if (transaction != null)
            {
                transaction.End();
                transaction = null;
            }
        }
    }
}

99. 親の記事に戻る

Autodesk Inventor API Hacking (概略)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0