development of

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

【IJCAD】MSTRETCHコマンド

MSTRETCH コマンドとは

AutoCAD では Express Tools をインストールすることで、いくつかの生産向上につながるコマンドが追加され、MSTRETCH コマンドも追加されるコマンドの一つです。

MSTRECH コマンドは LISP で作成されたコマンドなので、LISP が扱える DWG 互換 CAD 製品であれば、 MSTRECH コマンドを実行させてしまう事が可能らしいです。

IJCAD はどうなの?

IJCAD も LISP は扱えます。ただ MSTRECH コマンドで使用されているいくつかの LISP 関数が IJCAD には存在しません。その為入力中の処理を実行することができませんでした。
※BricsCAD では普通に動くらしいですね…

LISP 関数がどんな処理をするのか理解できれば、代わりのLISP関数を用意することも可能ですが、わざわざ LISP 関数を自作で用意する位なら、同じようなコマンドを最初から全部自分で作っちゃえばいいんじゃないかと思います。

そんなこんなで

いつの間にか手元にそれらしいプロジェクトがありましたので公開します。

drive.google.com

AutoCAD 上で MSTRECH コマンドの動作を確認しながら大体の動きを再現させてみたらしいです。ストレッチの処理は次の記事の方法でした。

tknmt.hatenablog.com

実のところ

標準の STRECH コマンドもオプションを活用することで、複数のオブジェクトを対象にしたストレッチはできるので、そもそも Autodesk も MSTRETCH コマンドなんてもの何故作ったのか不思議ですね。
昔から CAD の仕事をしている人なら、その辺知っているのかもしれません。

【IJCAD】選択オブジェクトから情報を取得する

IJCAD 2018 以前では…

以前のバージョンの IJCAD では、SelectedObject オブジェクトを、 PickPointSelectedObject クラスや、CrossingOrWindowSelectedObject クラスにキャストできないという、なんとも言えない問題がありました。

as 演算子 を使用した型変換をすると null が返される、括弧を使用してキャストをすると System.InvalidCastException の例外がスローされてしまうといった具合です。

この問題のおかげで IJCAD 2018 以前では、Editor.GetSelection メソッドを使用したユーザ入力などで、オブジェクトがどのような状況で選択されたのかの詳細を知る術がありませんでした。

IJCAD 2019 にて

この仕様もない問題ですが、どうやら IJCAD 2019 にてやっと解決したようで、どのようにしてオブジェクトが選択されたのかの詳細が取得することができました。

サンプルコード

var db = Application.DocumentManager.MdiActiveDocument.Database;
var ed = Application.DocumentManager.MdiActiveDocument.Editor;

// ユーザ選択による選択セットを作成
var res = ed.GetSelection();
if (res.Status != PromptStatus.OK) return;

// 選択セットを取得
var ss = res.Value;

foreach(SelectedObject selobj in ss)
{
    var id = selobj.ObjectId;
    var dxfName = id.ObjectClass.DxfName;

    // どのように選択されたか確認
    switch(selobj.SelectionMethod)
    {
        // 非グラフィカルな方法で選択された場合
        case SelectionMethod.NonGraphical:

            ed.WriteMessage($"\n{dxfName}{id}は、非グラフィカルな方法(すべて(ALL)や最後(L)等)で選択されました。");
            break;

        // オブジェクトをクリックして選択された場合
        case SelectionMethod.PickPoint:

            // SelectedObjectをPickPointSelectedObjectにキャスト
            var pickSelObj = (PickPointSelectedObject)selobj;
            // 選択時の要約を取得
            var pickPoint = pickSelObj.PickPoint;

            ed.WriteMessage($"\n{dxfName}{id}は、座標{pickPoint.PointOnLine}をクリックして選択しました。");
            break;

        // 窓またはポリゴン窓で選択された場合
        case SelectionMethod.Window:

            // SelectedObjectをCrossingOrWindowSelectedObjectにキャスト
            var windowSelObj = (CrossingOrWindowSelectedObject)selobj;
            // 選択時の要約を取得
            var pickPointDescriptors = windowSelObj.GetPickPoints();

            ed.WriteMessage($"\n{dxfName}{id}を窓またはポリゴン窓で選択しました。");
            ed.WriteMessage("\n窓またはポリゴン窓は、次の座標点で構成されています。");
            for(var i = 0; i < pickPointDescriptors.Count(); i++)
            {
                if (i != 0) ed.WriteMessage(", ");
                ed.WriteMessage($"\n  点{i + 1}:{pickPointDescriptors[i].PointOnLine}");
            }
            break;

        // 交差またはポリゴン交差で選択された場合
        case SelectionMethod.Crossing:

            // SelectedObjectをCrossingOrWindowSelectedObjectにキャスト
            var crossingSelObj = (CrossingOrWindowSelectedObject)selobj;
            // 選択時の要約を取得
            pickPointDescriptors = crossingSelObj.GetPickPoints();

            ed.WriteMessage($"\n{dxfName}{id}を交差またはポリゴン交差で選択しました。");
            ed.WriteMessage("\n交差またはポリゴン交差は、次の座標点で構成されています。");
            for (var i = 0; i < pickPointDescriptors.Count(); i++)
            {
                if (i != 0) ed.WriteMessage(", ");
                ed.WriteMessage($"\n  点{i + 1}:{pickPointDescriptors[i].PointOnLine}");
            }
            break;

        // フェンスで選択された場合
        case SelectionMethod.Fence:

            // SelectedObjectをFenceSelectedObjectにキャスト
            var fanceSelObj = (FenceSelectedObject)selobj;
            // 選択時の要約を取得
            pickPointDescriptors = fanceSelObj.GetIntersectionPoints();

            ed.WriteMessage($"\n{dxfName}{id}をフェンスで選択しました。");
            ed.WriteMessage("\n入力したフェンスとは、次の座標点で交差しています。");
            for (var i = 0; i < pickPointDescriptors.Count(); i++)
            {
                if (i != 0) ed.WriteMessage(", ");
                ed.WriteMessage($"\n  点{i + 1}:{pickPointDescriptors[i].PointOnLine}");
            }
            break;

        // その他の方法で選択された場合
        default:
            ed.WriteMessage($"\n{dxfName}{id}は不明な方法で選択されました。");
            break;
    }
}

SelectedObject オブジェクトから、選択時のさまざまな情報を取得できるようになりましたので、例えばオブジェクトをクリックして選択されたときの座標や、窓や交差による選択範囲がどの座標を指定したかを、やっと確認できるようになりました。

【IJCAD】ブロック定義をプレビューする

ブロック定義をプレビューしてみる

AutoCAD DevBlog の記事で紹介されているサンプルや 、adndevblog.typepad.com

Teigha の SDK に含まれるサンプルを参考にし、GraphicsSystem 名前空間のクラスを使用して、ブロック定義のプレビューを表示させるダイアログを作成してみました。


BlockInsertSample

プレビュー画像の表示と違う点

INSERT コマンドを実行した時にで表示されるダイアログを違い、ブロック定義のプレビューイメージを表示させているのではありません。
ブロック定義の BlockTableRecord 描画可能なオブジェクトとして、ダイアログ上に描画させています。
描画させているコントロールにマウスのイベントを追加して、ズームやドリーといった簡易的な操作もできるようにしてみました。

サンプルのプロジェクト一式をグーグルドライブ上で公開していますので、ダウンロードして確認してみてください。

drive.google.com

でも結局のところIJCADでは...

※サンプルを確認するにあたっていくつか注意点があります。

グラフィックシステムを管理している Manager オブジェクトを、Document.GraphicsManager プロパティから取得すると、IJCAD 終了時に異常終了を警告するダイアログが表示されてしまうので、GraphicsSystem 名前空間のクラスにはリスクがあります。

【IJCAD】一時的なグラフィックを表示する

AutoCAD で一時的なグラフィックを表示するには

AutoCAD で一時的なグラフィックを表示させるには、Editor.DrawVector メソッドを使った方法と、TransientManager クラスを使った方法があります。

Editor.DrawVector メソッドは、指定したベクトルを描画します。
TransientManager クラスは一時的なものを管理するクラスで、TransientManager.AddTransient メソッドで一時的に表示したいオブジェクトを追加することで、ビューポート上に一時的なグラフィックが表示されます。

IJCAD で一時的なグラフィックを表示するには

IJCAD には、Editor.DrawVector メソッドは存在しますが、残念ながら TransientManager クラスは実装されていませんでした。

直線や矩形の一時的なグラフィックを表示するには、 Editor.DrawVector メソッドでも事足りますが、円や楕円の一時グラフィックを表示するとなると、曲線をベクトルで表現するのは手間がかかってしまいます。

そこで多少 IJCAD に負荷をかけることになりますが、PointMonitor イベント内で Geometry.Draw メソッド使用して、一時的なグラフィックを表示させてみます。 

サンプルコード

private static List<Entity> _entities = new List<Entity>();
private bool _pointMonitor = false;

[CommandMethod("TEMPOBJ")]
public void CreateTemporaryObject()
{
    PointMonitorON();

    var ed = Application.DocumentManager.MdiActiveDocument.Editor;
    while (true)
    {
        var res = ed.GetPoint("\n円の中心を指定");
        if (res.Status != PromptStatus.OK) break;
        _entities.Add(new Circle(res.Value, Vector3d.ZAxis, 10.0));
    }
}

[CommandMethod("TEMPOBJCLEAR")]
public void ClearTemporaryObject()
{
    _entities.ForEach(ent => ent.Dispose());
    _entities.Clear();

    PointMonitorOFF();
}

[CommandMethod("PMON")]
public void PointMonitorON()
{
    if(!_pointMonitor)
    {
        var ed = Application.DocumentManager.MdiActiveDocument.Editor;
        ed.PointMonitor += Ed_PointMonitor;
        _pointMonitor = true;
    }
}

[CommandMethod("PMOFF")]
public void PointMonitorOFF()
{
    if (_pointMonitor)
    {
        var ed = Application.DocumentManager.MdiActiveDocument.Editor;
        ed.PointMonitor -= Ed_PointMonitor;
        _pointMonitor = false;
    }
}

private static void Ed_PointMonitor(object sender, PointMonitorEventArgs e)
{
    if (_entities.Count == 0) return;
    var geo = e.Context.DrawContext.Geometry;
    _entities.ForEach(ent => geo.Draw(ent));
}

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

PointMonitorEventArgs.Context プロパティから順に、DrawContext プロパティ、Geometry プロパティと進み、ViewportGeometry オブジェクトを取得します。

後は、Geometry.Draw メソッドを使用して、一時的なグラフィックがビューポートに表示されます。

PointMonitor イベントを使用した方法では、ビューポートのリフレッシュが完了したタイミングでマウスカーソルの動きを止めると、一時的なグラフィックが表示されていない状態になったりするなど、完全な TransientManager クラスの代替とは言えません。

また、AutoCADTransientManager クラスを使用したグラフィックの表示でもいえますが、一時的に作成したオブジェクトが必要がなくなったときは、確実にオブジェクトを解放しておかないとメモリリークの原因になってしまうので注意してください。

【IJCAD】カメラを作成する

IJCAD ではカメラを設置できない?

IJCAD には 3D ビューカメラを作成するための CAMERA [カメラ] コマンドが存在しません。
それなのに何故かカメラオブジェクトに関する、 CAMERADISPLAY 等のシステム変数は存在してます。 まぁこのシステム変数は、AutoCADとの互換性を取るために存在しているのかもしれません。

本当にIJCADではカメラを設置できないのか??

コマンドが存在しないので IJCAD ではカメラは作成できないと普通は思いますが、API を使用することでカメラを作成することはできちゃったりします。
ただ、IJCAD が 3D ビューには対応していない節があるので、無理やりカメラオブジェクトを作成して設置することは何とかできるといった感じです。

サンプルコード

var db = Application.DocumentManager.MdiActiveDocument.Database;
var ed = Application.DocumentManager.MdiActiveDocument.Editor;

using (var tr = db.TransactionManager.StartTransaction())
{
    var vt = tr.GetObject(db.ViewTableId, OpenMode.ForWrite) as ViewTable;
    var vtr = new ViewTableRecord();
    vtr.Name = "TESTCAM";
    vt.Add(vtr);
    tr.AddNewlyCreatedDBObject(vtr, true);

    vtr.Target = new Point3d(50, 50, 50);
    vtr.ViewDirection = new Vector3d(0, -50, 0);
    vtr.PerspectiveEnabled = true;
    vtr.Height = 50;
    vtr.CenterPoint = Point2d.Origin;
    vtr.LensLength = 30;

    var vsDict = tr.GetObject(db.VisualStyleDictionaryId, OpenMode.ForRead) as DBDictionary;
    vtr.VisualStyleId = vsDict.GetAt("Realistic");

    db.CameraDisplay = true;

    tr.Commit();
}

実行結果

IJCADでサンプルコードを実行するとモデル空間上にカメラが配置され、カメラオブジェクトが表示されています。 +30点

f:id:tknmt:20190419143621p:plain

VIEW コマンドでビュー管理ダイアログを表示して確認してみると、作成したカメラのビューがモデルビューに追加されています。 +60点

f:id:tknmt:20190419143655p:plain

カメラを選択してみるとIJCADは異常終了します。-10000点

結論

今後リリースされる IJCAD に、 3D ビューカメラ機能が実装されるまでは、カメラを設置しない方が無難ですね。

カメラを作成した後、図面を保存して再度 IJCAD で開くと、カメラはどこにも表示されていませんでした。
ただ、保存した図面をAutoCADなどで開くとカメラはちゃんとありましたので、データベース内には問題なく追加されているのかもしれません。

【AutoCAD Mechanical】パーツ参照のデータを取得する

パーツ参照のデータを取得するには

パーツ参照のコンポーネントのプロパティの各値を取得するには、 AcmBOMManager::getPartData メソッドを使用します。 

サンプルコード

// オブジェクトを選択
ads_name entname;
ads_point pickPoint;
if (acedEntSel(_T("\nパーツ参照を選択"), entname, pickPoint) != RTNORM) return;

AcDbObjectId partrefId;
if (acdbGetObjectId(partrefId, entname) != Acad::eOk) return;

AcDbObject *pObj;
if (acdbOpenObject(pObj, partrefId, AcDb::kForRead) != Acad::eOk) return;
CString className = pObj->isA()->name();
pObj->close();

// パーツ参照を選択したかどうかを確認
if (className != _T("AcmPartRef"))
{
    acutPrintf(_T("\nパーツ参照が選択されませんでした。"));
    return;
}

// パーツ参照のプロパティを取得
CMapStringToString valueMap;
if (getAcmBomMgr()->getPartData(partrefId, valueMap) != Acad::eOk) return;

POSITION pos = valueMap.GetStartPosition();
while (pos != NULL)
{
    CString key, value;
    valueMap.GetNextAssoc(pos, key, value);
    acutPrintf(_T("\n  %s : %s"), key, value);
}

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

getPartData メソッドにパーツ参照の ID と、値を格納する CMapStringToString のみを渡していますが、あわせて Adesk::UInt32 型の numOfItems を渡すことで、設定されているパーツ参照の数量も取得できます。

【AutoCAD Mechanical】部品表を作成する

部品表を作成する

部品表を API で作成するには、AcmBOMManager::createBomTable メソッドまたは、AcmBOMManager::createBorderBomTable メソッドを使用します。

AcmBOMManager::createBomTable メソッドでは図面全体の部品表や、ストラクチャ内のアセンブリの部品表を作成できます。

AcmBOMManager::createBorderBomTable メソッドでは、図面枠の部品表を作成できます。

サンプルコード

ads_name entname;
ads_point pickPoint;

// 部品表を作成する図面枠を選択
int result = acedEntSel(
    _T("\n部品表を作成する図面枠を選択 <Enter を押した場合は MAIN を作成>"),
    entname,
    pickPoint);

AcDbObjectId bomId;
Acad::ErrorStatus es = Acad::eOk;
if (result == RTNORM)
{
    // 図面枠のObjectIdを取得
    AcDbObjectId targetId;
    acdbGetObjectId(targetId, entname);

    // 作成する部品表の名前を入力
    ACHAR name[133];
    if (acedGetString(1, _T("\n部品表の名前を入力"), name) != RTNORM) return;

    // 入力した名前の部品表が既に存在するか確認
    es = acmBomMgr->existBomTable((LPCTSTR)name);
    if (es != Acad::eOk)
    {
        // 部品表を作成
        es = acmBomMgr->createBorderBomTable(bomId, targetId, (LPCTSTR)name);
    }
    else
    {
        acutPrintf(_T("\n部品表[%s]は作成済です。"), (LPCTSTR)name);
        return;
    }
}
else if (result == RTERROR)
{
    // MAINが既に存在するか確認
    es = acmBomMgr->existBomTable(_T("MAIN"));
    if (es != Acad::eOk)
    {
        // 部品表を作成
        es = acmBomMgr->createBomTable(bomId);
    }
    else
    {
        acutPrintf(_T("\n部品表[MAIN]は作成済です。"));
        return;
    }
}
if (es != Acad::eOk)
{
    acutPrintf(_T("\n部品表の作成に失敗しました。 エラーコード : %d"), es);
}

CMapStringToString valueMap;
CString bomName;
es = acmBomMgr->getBomData(bomId, bomName, valueMap);
acutPrintf(_T("\n部品表 [%s] を作成しました。"), (LPCTSTR)bomName);

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

図面枠のブロック参照を選択した場合は、createBorderBomTable メソッドを使用してボーダー部品表を作成し、オブジェクトを選択せず Enter を入力した場合は、createBomTable メソッドを使用して図面全体の部品表を作成しています。

アセンブリ部品表を作成する場合は、createBomTable メソッドに、アセンブリのブロックテーブルレコードの ObjectId と、部品表の名前を渡します。