development of

Tieghaに関する備忘録とおまけ

【ODA】ペーパー空間の印刷マージンを点線にする

AutoCADなどのペーパー空間には印刷マージンを点線で表示していますが、単純にペーパー空間のビューを作成しても直線で表示されてしまいます。 この枠を点線にする方法について見当違いな部分をひたすら調査して時間がとられてしまったが、実際にはとても簡単な方法で変更できたのでそれについてのメモを残しておきます。 f:id:tknmt:20191210111057j:plain

当初はレイアウトの設定値でこの枠線を点線に変更しているのかと思い、ビュー周りやドキュメント周りを調査していましたが、実際にはOdDbLayoutPaperPEクラスを使用してOdDbLayoutクラスの表示を拡張しているだけでした。

OdDbLayoutPaperPEを継承したクラスを作成し、drawMarginsメソッド内で直線ではなく点線になるように描画の処理を行い、addXメソッドでOdDbLayoutクラスを拡張することで、ペーパー空間の印刷マージンの枠線が点線で表示されました。

f:id:tknmt:20191210111751j:plain

【ODA】OdaMfcAppでアンチエイリアスを有効にする

ODAライブラリにサンプルとして含まれているOdaMfcAppアプリケーションでロードした図面を、 WinDirectX.txvを使用してビューをDirectXで描画させる際にアンチエイリアスを有効にする方法。

ODAライブラリのサンプルを参考にしてDWGのビューを作成するサンプルを作成してみたが、 OdGsDeviceのプロパティをいくら調整しても、線分や円などエンティティの描画に対してアンチエイリアを有効にできなかったので、 WinDirectX.txvモジュール自体に手を入れてみたら無事アンチエイリアスを有効にできたのでその方法を忘れないためにメモ。

  1. WinDireccXプロジェクトを開く
  2. ExGsDirectXDevice.cppのコードを表示させる
  3. IDirect3DDeviceのインスタンス作成後にSetRenderState関数でD3DRS_ANTIALIASEDLINEENABLEを有効にする

ただ、どうやらWinDirectX.txvはDirectX9を使用しているようなので、このモジュールを一から作り直したほうが良さそうな気もしない…

【AutoCAD Mechanical】全ての部品表を取得する

全ての部品表を取得するには

図面内に存在する全ての部品表を取得するには、AcmBOMManager::getAllBomTables メソッドを使用します。
このメソッドを使用すると、図面内のすべての部品表のオブジェクトIDを取得できます。

サンプルコード

void GetAllBom()
{
    AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();

    AcDbObjectIdArray bomIds;

    // 全ての部品表のObjectIdを取得
    acmBomMgr->getAllBomTables(bomIds);

    for each (AcDbObjectId bomId in bomIds)
    {
        CMapStringToString valueMap;
        CString bomName;

        // 部品表のデータを取得
        acmBomMgr->getBomData(bomId, bomName, valueMap);
        acutPrintf(_T("\n部品表 [%s] の対象は、"), (LPCTSTR)bomName);

        bool isStructuralyExpanded;
        bool isUsingAssmPath;
        CString structSeparator;
        AcDbObjectIdArray assocLayoutIds;
        AcDbObjectId targetId;

        // 部品表のプロパティを取得
        acmBomMgr->getBomProperties(
            bomId,
            isStructuralyExpanded,
            isUsingAssmPath,
            structSeparator,
            assocLayoutIds,
            targetId);

        AcDbObject *obj;

        // 部品表の対象オブジェクトを取得
        acdbOpenAcDbObject(obj, targetId, AcDb::kForRead);

        // 対象オブジェクトが何なのか確認
        if (obj->isKindOf(AcDbBlockReference::desc()))
        {
            // ブロック参照の場合

            AcDbBlockReference *insert = AcDbBlockReference::cast(obj);
            AcDbObjectId blockId = insert->blockTableRecord();
            AcDbBlockTableRecord *block;
            acdbOpenObject(block, blockId, AcDb::kForRead);
            ACHAR *name;
            block->getName(name);
            acutPrintf(_T("図面枠 [%s] です。"), name);
            block->close();
        }
        else if (obj->isKindOf(AcDbBlockTableRecord::desc()))
        {
            // ブロックテーブルレコードの場合

            AcDbBlockTableRecord *block = AcDbBlockTableRecord::cast(obj);
            ACHAR *name;
            block->getName(name);
            if (acdbSymUtil()->isBlockModelSpaceName(name))
            {
                acutPrintf(_T("図面全体です。"));
            }
            else
            {
                acutPrintf(_T("不明のオブジェクトです。"));
            }
        }
        else
        {
            // その他のオブジェクトの場合

            CString cmpdef = _T("ACAMCOMPDEF");
            // オブジェクトのDXF名を確認
            if (cmpdef.Compare(obj->isA()->dxfName()) == 0)
            {
                // DXF名がコンポーネント定義の場合はストラクチャと判断しています
                acutPrintf(_T("ストラクチャです。"));
            }
            else
            {
                acutPrintf(_T("不明のオブジェクトです。"));
            }
        }
        obj->close();
    }
}

※サンプルコードの補足

サンプルコードでは図面全体の部品表、図面枠の部品表、ストラクチャの部品表かどうかをチェックしていて、ブロック参照を対象としている部品表は図面枠の部品表、モデル空間のブロックテーブルレコードを対象としている部品表は図面全体の部品表、その他のオブジェクトの中で "ACAMCOMPDEF" という DXF 名をもつオブジェクトの場合はストラクチャと判断しています。

【AutoCAD Mechanical】部品表にデータを読み込む

部品表にデータを読み込むには

部品表 ダイアログボックス から部品表にデータを読み込ませることができますが、前回の記事と同じように API を使用して部品表にデータを読み込ませることが可能です。

【AutoCAD Mechanical】部品表のデータを書き出す - development of

部品表の読み込みには、AcmBOMManager::importBOM メソッドを使用します。

サンプルコード

#define IMPORT_EXT _T("Microsoft Access(*.mdb)|*.mdb|Microsoft Excel 97(*.xls)|*.xls|Microsoft Excel 95(*.xls)|*.xls|dBase 5(*.dbf)|*.dbf|dBase IV(*.dbf)| *.dbf|dBase III(*.dbf)|*.dbf|タブ区切りのテキスト形式(*.txt)|*.txt|セミコロン区切りのテキスト形式(*.csv)|*.csv|HTML書き出し(*.html)|*.html||")
void ImportBOM()
{
    // ファイルダイアログを表示
    CFileDialog dlg(
        TRUE,
        _T("mdb"),
        NULL, OFN_CREATEPROMPT,
        IMPORT_EXT);
    if (dlg.DoModal() != IDOK) return;

    AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
    AcDbObjectId bomId;
    CString bomTableName;

    // 部品表[MAIN]を取得
    acmBomMgr->getBomTable(bomId, bomTableName, pDb->currentSpaceId());

    // 部品表に選択したデータを読み込む
    if (acmBomMgr->importBOM(
        (AcmBOMManager::ImportBOMType)(dlg.m_ofn.nFilterIndex > 6 ? dlg.m_ofn.nFilterIndex + 3 : dlg.m_ofn.nFilterIndex),
        bomId, 
        dlg.GetPathName(), 
        bomTableName, 
        true) == Acad::eOk)
    {
        acutPrintf(_T("\n部品表[%s]の内容を[%s]に基づいて更新しました。"), bomTableName, dlg.GetFileName());
    }
    else
    {
        acutPrintf(_T("\n部品表[%s]の内容のロードに失敗しました。"), bomTableName);
    }
}

【AutoCAD Mechanical】部品表のデータを書き出す

部品表のデータを書き出すには

一般的には部品表 ダイアログ ボックス から部品表の内容を書き出すと思いますが、 f:id:tknmt:20190611173802p:plain API を使用して指定したファイル形式で書き出すことも可能です。

部品表の書き出しには、AcmBOMManager::exportBOM メソッドを使用します。
書き出せるファイル形式は、Microsoft Access (.mdb)Microsoft Excel (.xls)、テキスト形式 (*.txt) などがあります。

サンプルコード

#define EXPORT_EXT _T("Microsoft Access(*.mdb)|*.mdb|Microsoft Access 97(*.mdb)|*.mdb|Microsoft Excel 97(*.xls)|*.xls|Microsoft Excel 95(*.xls)|*.xls|dBase 5(*.dbf)|*.dbf|dBase IV(*.dbf)| *.dbf|dBase III(*.dbf)|*.dbf|タブ区切りのテキスト形式(*.txt)|*.txt|セミコロン区切りのテキスト形式(*.csv)|*.csv|HTML書き出し(*.html)|*.html||")
void ExportBOM()
{
    // ファイルダイアログを表示
    CFileDialog dlg(
        FALSE, 
        _T("mdb"),
        NULL, OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT,
        EXPORT_EXT);
    if (dlg.DoModal() != IDOK) return;
    
    AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
    AcDbObjectId bomId;
    CString bomTableName;

    // 部品表[MAIN]を取得
    acmBomMgr->getBomTable(bomId, bomTableName, pDb->currentSpaceId());

    // 部品表を書き出す
    if (acmBomMgr->exportBOM(
        (AcmBOMManager::ExportBOMType)(dlg.m_ofn.nFilterIndex > 7 ? dlg.m_ofn.nFilterIndex + 3 : dlg.m_ofn.nFilterIndex),
        bomId, 
        dlg.GetPathName(),
        bomTableName, 
        true) == Acad::eOk)
    {
        acutPrintf(_T("\n部品表[%s]の内容を[%s]に保存しました。"), bomTableName, dlg.GetFileName());
    }
    else
    {
        acutPrintf(_T("\n部品表[%s]の内容の保存に失敗しました。"), bomTableName);
    }
}

※サンプルコードについての補足

AcmBOMManager::exportBOM メソッドに渡す書き出すファイルの形式として、AcmBOMManager::ExportBOMType 列挙体を使用して指定します。

列挙子 ファイル形式
eEXPORT_MDB 1 Microsoft Access
eEXPORT_MDB97 2 Microsoft Access 97
eEXPORT_EXCEL97 3 Microsoft Excel 97
eEXPORT_EXCEL95 4 Microsoft Excel 95
eEXPORT_DB5 5 dBASE 5
eEXPORT_DBIV 6 dBASE IV
eEXPORT_DBIII 7 dBASE III
eEXPORT_FOXVIS 8 Microsoft Visual Foxpro 3.0
eEXPORT_FOX26 9 Microsoft Foxpro 2.6
eEXPORT_FOX25 10 Microsoft Foxpro 2.5
eEXPORT_TXT 11 タブ区切りのテキスト形式
eEXPORT_CSV 12 セミコロン区切りのテキスト形式
eEXPORT_HTML 13 HTML形式
eEXPORT_ALLFILE 14

サンプルコードでは Foxpro 形式以外の形式で書き出せるようにしています。

【IJCAD】コマンド実行コンテキストについて

コマンド実行コンテキストについて

.NET API でコマンドを定義するときに、IJCAD でも AutoCAD と同じようにコマンド実行コンテキストについて考慮する必要があります。

2つの実行コンテキスト

実行コンテキストには、ドキュメント実行コンテキスト アプリケーション実行コンテキストの2種類があります。

CommandFlags.Modal を指定したコマンドはドキュメント実行コンテキストで、CommandFlags.Session を指定したコマンドはアプリケーション実行コンテキストです。

ドキュメント実行コンテキストのコマンドは、基本的に現在アクティブな図面に対して実行されるコマンドで、コマンドの開始時と終了時は同一の図面がアクティブな状態です。

アプリケーション実行コンテキストのコマンドは、コマンドの処理内でアクティブな図面が変更され、ドキュメントを跨って処理が実行されるコマンドで、コマンドの開始時と終了時でアクティブな図面が異なる場合もあります。

実行コンテキストの使い分け

通常コマンドの実行は、現在アクティブな図面に対して処理を行うために実行されるので、基本的にドキュメント実行コンテキストでコマンドを定義します。

NEW コマンドや OPEN コマンドのように、新しいドキュメントを追加する時や、開いている複数の図面に対して一括に処理を行う時などは、アプリケーション実行コンテキストでコマンドを定義します。

また IJCAD に限った話ですが、Window Forms 内の処理にブレークポイントを追加しても、うまくデバッグできないという問題があります。 f:id:tknmt:20190529105632p:plain この場合にアプリケーション実行コンテキストでコマンドを定義すると、ブレークポイントで処理が止まるようになり、変数の中身などを確認できるようになります。

アプリケーション実行コンテキストの問題点

IJCAD のアプリケーション実行コンテキストにはいくつかの問題点があります。

AutoCAD では、アプリケーション実行コンテキストのコマンドで、図面のデータベースを編集する場合は、明示的にドキュメントをロックしないと例外が発生してしまいますが、IJCAD ではドキュメントをロックしていなくてもデータベースの編集ができてしまいます。

また、ユーザ入力で表示したメッセージなどがコマンド終了時にも表示されたままになったりします。

Window Forms 内のブレークポイントが止まらないというのは、開発を進めるにあたっての障害になりますが、開発が完了しアプリケーションをリリースする際には、可能な範囲でドキュメント実行コンテキストに変更したほうが、品質の高いアプリケーションを世に出せると思います。

【IJCAD】COMの機能をもっと楽に活用する

COM 相互運用機能とは

.NET API で開発を進めるときに、GrxCAD.Interop.dll を参照することで、COM の機能を使用することができるようになります。

COM 相互運用機能を使用することで、過去に VBA を使用して開発を進めていたときのコードを、一部を書き換えるだけで簡単に .NET API へ移植することができます。

COM の機能を活用する

.NET API ではオブジェクト範囲にズームを実装するのが多少面倒なんですが、COM には標準の関数として ZoomExtents メソッドが存在しています。

現在アクティブな空間のオブジェクト範囲を取得して、アクティブなビューをオブジェクト範囲から計算した幅と高さに変更するという処理を実装する位なら、COM の ZoomExtents メソッドを呼び出す方が楽です。

ただ、COM 相互運用機能を使用するために参照する GrxCAD.Interop.dll ですが、このライブラリは 32bit 用と 64bit 用で分けられていて、使用する IJCAD に合わせて参照設定を変更する必要があります。

もっと楽にCOMの機能を使用する

.NET Framework 4.0 から動的言語ランタイムが追加されました。これによって GrxCAD.Interop.dll を参照しなくても、簡単に COM のメソッドを呼び出せるようになりました。

例えば ZoomExtents メソッドを呼び出すには次のようなコードになります。

((dynamic)Application.AcadApplication).ZoomExtents();

Application.AcadApplication プロパティの値を dynamic にキャストして ZoomExtents メソッドを呼び出しています。