development of

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

【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 であることを前提としていますので、それ以外の条件ではインデックスの取得に失敗します。