development of

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

【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 と、部品表の名前を渡します。

【IJCAD】オブジェクトをストレッチさせる

.NET API でオブジェクトを変換させる方法とは

.NET API でオブジェクトの変換には、本来は Entity クラスの TransformBy メソッドを使用します。
TransformBy メソッドは、オブジェクトの移動、尺度変更、回転、鏡像化などができますが、オブジェクトのストレッチは残念ながらできません。

ストレッチとは?

STRECH コマンドを実行した時の動きは、交差窓やポリゴン交差で選択されたオブジェクトの一部をストレッチさせます。
TransformBy メソッドは変換行列(Matrix3dオブジェクト)を使用して移動、尺度変更、回転、鏡像化などオブジェクトの変換を行いますが、オブジェクトの一部をストレッチさせるという変換行列がありえないので、TransformBy メソッドではストレッチはできないという事になります。

ストレッチさせるにはどうすればいいのか?

オブジェクトをストレッチさせるには、自力でオブジェクト選択時の情報からストレッチの対象となるオブジェクトの一部を計算して、選択されたオブジェクトを個々に変更していくことでストレッチを模倣した処理を作成することも可能です。
ただ、そのような面倒なことをしなくても、Entity クラスの MoveStretchPointsAt メソッドを使用することで、ストレッチコマンドのような動きを再現できます。 

サンプルコード

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

// オプション
var opt1 = new PromptSelectionOptions();
opt1.MessageForAdding = "\nストレッチするオブジェクトを交差窓 または ポリゴン交差窓で選択";
opt1.MessageForRemoval = "\n除外するオブジェクトを選択";

// オブジェクトを選択
var res1 = ed.GetSelection();
if (res1.Status != PromptStatus.OK) return;

// 基点と目的点を入力
var res2 = ed.GetPoint("\n基点を指定");
if (res2.Status != PromptStatus.OK) return;
var opt3 = new PromptPointOptions("\n目的点を指定");
opt3.UseBasePoint = true;
opt3.BasePoint = res2.Value;
var res3 = ed.GetPoint(opt3);
if (res3.Status != PromptStatus.OK) return;

// オフセットの値
var offset = res2.Value.GetVectorTo(res3.Value);

using (var tr = db.TransactionManager.StartTransaction())
{
    // 選択オブジェクトの数だけループ
    foreach(SelectedObject obj in res1.Value)
    {
        // 選択モードをチェック
        if(obj.SelectionMethod == SelectionMethod.Crossing)
        {
            // 交差窓またはポリゴン交差窓で選択された場合

            // 選択オブジェクトの型をCrossingOrWindowSelectedObject型に変更する
            var selobj = (CrossingOrWindowSelectedObject)obj;

            // 選択時に使用されたピックポイントを取得
            var pickPoints = new Point3dCollection();
            foreach (var pickPt in selobj.GetPickPoints())
            {
                pickPoints.Add(pickPt.PointOnLine);
            }
            pickPoints.Add(selobj.GetPickPoints()[0].PointOnLine);

            // 選択されたオブジェクトを開く
            using (var ent = tr.GetObject(selobj.ObjectId, OpenMode.ForWrite) as Entity)
            {
                // オブジェクトのストレッチポイントを取得
                var stretchPoints = new Point3dCollection();
                ent.GetStretchPoints(stretchPoints);

                // 選択時に選択範囲に含まれるストレッチポイントのインデックスを取得
                var indices = GetIndices(pickPoints, stretchPoints);
                if (indices.Count > 0)
                {
                    // オブジェクトをストレッチする
                    ent.MoveStretchPointsAt(indices, offset);
                }
            }
        }
        else
        {
            // その他の方法で選択された場合

            // 選択されたオブジェクトを開く
            using (var ent = tr.GetObject(obj.ObjectId, OpenMode.ForWrite) as Entity)
            {
                // その他の方法で選択された場合は全体をストレッチする = 移動させる
                var transform = Matrix3d.Displacement(offset);
                ent.TransformBy(transform);
            }
        }
    }
    tr.Commit();
}
/// <summary>
/// 選択時に選択範囲に含まれるストレッチポイントのインデックスを取得
/// </summary>
/// <param name="pickPoints">選択時に使用されたピックポイントのコレクション</param>
/// <param name="stretchPoints">ストレッチポイントのコレクション</param>
/// <returns></returns>
public IntegerCollection GetIndices(Point3dCollection pickPoints, Point3dCollection stretchPoints)
{
    var indices = new IntegerCollection();

    foreach(Point3d stretchPt in stretchPoints)
    {
        var ray = new Ray3d(stretchPt, pickPoints[0].GetVectorTo(pickPoints[1]));
        var count = 0;
        for(var idx = 0; idx < pickPoints.Count -1; idx++)
        {
            var line3d = new LineSegment3d(pickPoints[idx], pickPoints[idx + 1]);
            var result = line3d.IntersectWith(ray);
            if (result != null) count++;
        }
        if(count % 2 == 1)
        {
            indices.Add(stretchPoints.IndexOf(stretchPt));
        }
    }

    return indices;
}

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

サンプルコードでは交差またポリゴン交差で選択したオブジェクトを、入力した基点と目的点を基にしたベクトルでストレッチしています。それ以外の方法で選択したオブジェクトは全体をストレッチさせるという事で移動させています。

MoveStretchPointsAt メソッドには、ストレッチさせるオブジェクトのストレッチポイントのインデックスを渡す必要があり、サンプルでは Crossing Number Algorithmというある互換で内外判定を行っていますが、これは IJCAD の .NET API は色々と足りていないので、仕方なく自前で計算しています。

また、このサンプルでは平面図かつ UCS が WCS であることを前提としていますので、それ以外の条件ではインデックスの取得に失敗します。

【IJCAD】ポリラインの図心座標を取得する

AutoCADでの図心の取得方法

領域を持つポリラインの図心座標を取得する方法として、広く認知されているのはポリラインからリージョンを作成して、Region.AreaPropertiesメソッドを使用してRegionAreaPropertiesオブジェクトを取得して、リージョンの中心を取得する方法があります。

ただ、残念ながら IJCAD の Region クラスには、AreaProperties メソッドが実装されていません。(ObjectGRX には OdDbRegion::getAreaProp メソッドがあります)
そのため、IJCAD では自力でポリラインの各頂点から重心を計算する必要がありますが、ポリラインなら別の方法でも図心座標を取得できたりします。

IJCADではオブジェクトスナップを活用しよう

Entity クラスには GetObjectSnapPoints というメソッドが存在します。
このメソッドを使用することで、指定したオブジェクトスナップモードと入力座標点で、エンティティにスナップされたときのスナップ点の座標を取得できます。
つまり、ポリラインに対して図心のオブジェクトスナップが有効な時の座標を取得できるということです。

サンプルコード

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

var opt = new PromptEntityOptions("\nポリラインを選択");
opt.SetRejectMessage("\n選択したエンティティはポリラインではありません。");
opt.AddAllowedClass(typeof(Polyline), true);
opt.AddAllowedClass(typeof(Polyline2d), true);
opt.AddAllowedClass(typeof(Polyline3d), true);
var res = ed.GetEntity(opt);
if (res.Status != PromptStatus.OK) return;

using (var tr = db.TransactionManager.StartTransaction())
{
    using (var pline = tr.GetObject(res.ObjectId, OpenMode.ForRead) as Entity)
    {
        var snapPts = new Point3dCollection();
        var geoIds = new IntegerCollection();
        pline.GetObjectSnapPoints((ObjectSnapModes)11, 0, res.PickedPoint, Point3d.Origin, Matrix3d.Identity, snapPts, geoIds);

        ed.WriteMessage($"\nポリライン[{pline.Handle}]の図心は、{snapPts[0]}です。");
    }
}

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

snapMode には、ObjectSnapModes 列挙体の列挙子を指定します。
例えば、 ObjectSnapModes.ModeCenter の場合は円や円弧の中心、ObjectSnapModes.ModeEnd の場合はエンティティの端点を探します。

ObjectSnapModes 列挙体の詳細は次の表の通りです。

メンバー 対象
ModeEnd 終点
ModeMid 中点
ModeCenter 中心
ModeNode ノード
ModeQuad 四半円点
ModeIntersection 交差点
ModeIns 挿入点
ModePerpendicular 垂線
ModeTangent 接線
ModeNear 近接点
ModeApparentIntersection 仮想交点
ModeParallel 平行
ModeStartpoint 開始点

ここで気になると思われるのが、サンプルコードでは上記の表に存在しない値を使用しています。これは、AcDb::OsnapMode::kOsModeCentroid に該当する値が、 .NET APIObjectSnapModes 列挙体に実装されていないのでこのように引数として渡しています。
ですが、この指定方法でも特に問題なく、ポリラインの図心にスナップされたときの、スナップ点の座標を取得できます。