development of

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

オブジェクトスナップをカスタマイズする

オブジェクトスナップを追加する

.NET API によるカスタマイズの一つとして、CustomObjectSnapMode クラスを使用することで、独自のオブジェクトスナップを追加することができます。

今回は、円オブジェクトに対して特定の角度の円周上点にスナップされる、カスタムオブジェクトスナップを追加してみました。

IJCAD にも CustomObjectSnapMode クラスは 存在していますが、IJCAD ではうまくスナップ機能が有効になりませんでしたので、今回のサンプルは AutoCAD 向けです。
ただ ObjectGRX を使用した場合は、 IJCAD でもオブジェクトスナップのカスタマイズは可能でしたので、そのうち .NET API でも AutoCAD と同じように .NET API でもカスタマイズができるようになると思います。

サンプルコード

using System;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(CustomOSnapSample.CustomOSnap))]
[assembly: ExtensionApplication(typeof(CustomOSnapSample.CustomOSnap))]

namespace CustomOSnapSample
{
    public class CustomOSnap : IExtensionApplication
    {
        /// <summary>スナップされる角度</summary>
        private static double Angle { get; set; }
        /// <summary>グリフの角度</summary>
        private static Vector3d Dir { get; set; }
        /// <summary>表示されるグリフ</summary>
        private static SampleGlyph Glyph { get; set; }
        /// <summary>カスタムオブジェクトスナップの情報</summary>
        private static SampleOSnapInfo Info { get; set; }
        /// <summary>カスタムオブジェクトスナップモード</summary>
        private static CustomObjectSnapMode Mode { get; set; }

        // 初期化処理
        public void Initialize()
        {
            Angle = Math.PI / 4.0;
            Dir = Vector3d.XAxis;
            Glyph = new SampleGlyph();
            Info = new SampleOSnapInfo();
            Mode = new CustomObjectSnapMode("SAMPLEOSNAP", "SAMPLEOSNAP", "Custom object snap sample", Glyph);

            // 円オブジェクトに新しいコールバックを適用する
            Mode.ApplyToEntityType(RXClass.GetClass(typeof(Circle)), new AddObjectSnapInfo(Info.SnapInfoCircle));
        }

        // 終了処理
        public void Terminate()
        {
        }

        // カスタムオブジェクトスナップの有効/無効を切り替える
        [CommandMethod("CUSTOMOSNAP")]
        public void CustomObjectSnap()
        {
            // カスタムオブジェクトスナップが有効かどうか
            if (CustomObjectSnapMode.IsActive("SAMPLEOSNAP"))
            {
                // 有効の場合無効にする
                CustomObjectSnapMode.Deactivate("SAMPLEOSNAP");
            }
            else
            {
                // 無効の場合有効にする
                CustomObjectSnapMode.Activate("SAMPLEOSNAP");
            }
        }

        // スナップされる角度を設定する
        [CommandMethod("SNAPANGLE")]
        public void SnapAngle()
        {
            var ed = Application.DocumentManager.MdiActiveDocument.Editor;
            var opt = new PromptAngleOptions("\n角度を入力");
            opt.DefaultValue = Angle;
            opt.UseDefaultValue = true;
            var res = ed.GetAngle(opt);
            if (res.Status != PromptStatus.OK) return;
            Angle = res.Value;
        }

        /// <summary>カスタムオブジェクトスナップのグリフ</summary>
        public class SampleGlyph : Glyph
        {
            /// <summary>グリフが表示される基準点</summary>
            private Point3d Pt { get; set; }

            public override void SetLocation(Point3d point)
            {
                // 基準点としてスナップ点を取得
                Pt = point;
            }

            protected override void SubViewportDraw(ViewportDraw vd)
            {
                // ビューポートを取得
                var vport = vd.Viewport;
                // 1平方メートルあたりのピクセル数を取得
                var pixels = vport.GetNumPixelsInUnitSquare(Pt);
                // グリフサイズとピクセル数からカメラの高さに合ったグリフの高さを計算
                var glyphHeight = (CustomObjectSnapMode.GlyphSize / pixels.Y) * 2.0;
                // 基準点をWCSからDCSに変換
                var pos = Pt.TransformBy(vport.EyeToWorldTransform);
                // フォントの特性
                var font = new FontDescriptor("Meiryo", false, false, 0, 0);
                // テキストスタイルを作成
                var style = new TextStyle();
                style.Font = font;
                style.TextSize = glyphHeight;
                // テキストのグリフの位置調整用ベクトル
                var vec = (Dir.GetNormal() * glyphHeight / 2.0) + (Dir.GetPerpendicularVector().Negate().GetNormal() * glyphHeight / 2.0);
                // テキストのグリフを表示
                vd.Geometry.Text((pos + vec).TransformBy(vport.EyeToWorldTransform), vport.ViewDirection, Dir, "☜", true, style);
                // 塗りつぶし設定を変更
                vd.SubEntityTraits.FillType = FillType.FillAlways;
                // スナップ位置に黒丸のグリフを表示
                vd.Geometry.Circle(pos, glyphHeight / 4, vport.ViewDirection);
            }
        }

        /// <summary>コールバック用のクラス</summary>
        public class SampleOSnapInfo
        {
            // 円にスナップ
            public void SnapInfoCircle(ObjectSnapContext context, ObjectSnapInfo result)
            {
                // ピックされているオブジェクトを取得
                var circle = context.PickedObject as Circle;
                // オブジェクトが円かどうか
                if (circle == null) return;
                // スナップ点を取得
                var snapPt = circle.GetPointAtParameter(Angle);
                // リザルトにスナップ点を追加
                result.SnapPoints.Add(snapPt);
                // グリフの方向を更新
                Dir = circle.Center.GetVectorTo(snapPt);
            }
        }
    }
}

実行結果


AutoCAD CustomObjectSnap

.NET アセンブリをロードして、ステータスバーから追加したカスタムオブジェクトスナップを有効にすると、円に対して独自のオブジェクトスナップが使用できるようになります。

【IJCAD】ダイナミックブロックのプロパティを変更する

ダイナミックブロックのプロパティを変更する

前回の記事ではダイナミックブロックに設定されているプロパティの取得方法を紹介しましたが、
tknmt.hatenablog.com

今回は、ダイナミックブロックのプロパティの値を変更する方法について紹介しようと思います。 といっても前回の記事で値を取得する時に使用したValueプロパティに値を割り当てるだけですが…

サンプルコード

[CommandMethod("SAMPLECMD")]
public void SampleCommand()
{
    var db = Application.DocumentManager.MdiActiveDocument.Database;
    var ed = Application.DocumentManager.MdiActiveDocument.Editor;
    var res1 = ed.GetEntity("\nダイナミックブロックを選択");
    if (res1.Status != PromptStatus.OK) return;

    // トランザクションを開始
    using (var tr = db.TransactionManager.StartTransaction())
    {
        // 選択したダイナミックブロックを開く
        using (var blockRef = tr.GetObject(res1.ObjectId, OpenMode.ForWrite) as BlockReference)
        {
            // ブロック参照が開かれたかどうか確認
            if (blockRef == null) return;
            
            // ダイナミックブロックかどうか確認
            if(blockRef.IsDynamicBlock)
            {
                // ダイナミックブロックに含まれるプロパティを取得
                var properties = blockRef.DynamicBlockReferencePropertyCollection;
                foreach(DynamicBlockReferenceProperty prop in properties)
                {
                    // 距離1プロパティかどうか確認
                    if(prop.PropertyName == "距離1")
                    {
                        // プロパティが読み書き可能かどうか確認
                        if(prop.ReadOnly == false)
                        {
                            var res2 = ed.GetDistance("\n距離を入力");
                            if(res2.Status == PromptStatus.OK)
                            {
                                // 入力した距離を設定
                                prop.Value = res2.Value;
                            }
                        }
                    }
                }
            }
        }
        //トランザクションをコミット
        tr.Commit();
    }
}

サンプル図面

ダイナミックブロック.dwg - Google ドライブ

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

サンプル図面のダイナミックブロックには、直線状パラメータによるストレッチしか設定されていないので、サンプルコードでは入力された距離の値を割り当てています。
ただ、値を割り当てる際に注意点が必要で、直線状パラメータであれば距離を示す値を、回転パラメータであれば角度を示す値を、可視性のパラメータであれば現在の可視性の状態を示す値を設定しなければなりません。

実行結果

サンプル図面に対してサンプルコードのコマンドを実行すると、次のような結果になります。 f:id:tknmt:20190520162928g:plain

【IJCAD】ダイナミックブロックのプロパティを取得する

ダイナミックブロックのプロパティを取得する

IJCAD の .NET API を使用して、ダイナミックブロックに設定されているプロパティを取得したいと思います。 f:id:tknmt:20190523144126p:plain

ダイナミックブロックのプロパティとは、ダイナミックブロックに設定されているパラメータセットの名前や、現在設定されている値などです。

サンプルコード

[CommandMethod("SAMPLECMD")]
public void SampleCommand()
{
    var db = Application.DocumentManager.MdiActiveDocument.Database;
    var ed = Application.DocumentManager.MdiActiveDocument.Editor;

    var res = ed.GetEntity("\nダイナミックブロックを選択");
    if (res.Status != PromptStatus.OK) return;

    // トランザクションを開始
    using (var tr = db.TransactionManager.StartTransaction())
    {
        // 選択したダイナミックブロックを開く
        using (var blockRef = tr.GetObject(res.ObjectId, OpenMode.ForRead) as BlockReference)
        {
            // ブロック参照が開かれたかどうか確認
            if (blockRef == null) return;
            
            // ダイナミックブロックかどうか確認
            if(blockRef.IsDynamicBlock)
            {
                // ダイナミックブロックに含まれるプロパティを取得
                var properties = blockRef.DynamicBlockReferencePropertyCollection;
                foreach(DynamicBlockReferenceProperty prop in properties)
                {
                    // プロパティ名を出力
                    ed.WriteMessage($"\nプロパティ名 : {prop.PropertyName}");
                    // プロパティの値を出力
                    ed.WriteMessage($"\n値 : {prop.Value}");
                }
            }
        }
    }
}

サンプル図面

ダイナミックブロック.dwg - Google ドライブ

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

プロパティ名を取得するには、DynamicBlockReferenceProperty クラスの PropertyName プロパティを使用して、プロパティの値を取得するには Value プロパティを使用します。

実行結果

サンプル図面に対してサンプルコードのコマンドを実行すると、次のようにIJCADのコマンドラインにプロパティの情報が出力されます。

f:id:tknmt:20190520153956p:plain

距離1 がダイナミックブロックに設定されている直線状パラメータで、現在の値は 50 とコマンドラインに出力されています。 Origin というのが上記の直線状パラメータの原点なのですが、値がありません。
AutoCAD では、直線状パラメータの原点の座標値をここで取得できるのですが、IJCAD で取得できないのは API のバグだと思います。

【IJCAD】データベース内のオブジェクトを選択する

ObjectGRX の強み

前回の記事ではカスタマイズするなら .NET API を使用すべきと主張しましたが、今回のは ObjectGRX のあるクラスを使用したオブジェクト選択のサンプルを紹介します。

データベースから選択セットを作成する

IJCAD の ObjectGRX には OdDbSelectionSet というクラスが存在していまして、このクラスを使用することで、データベースから選択セットを作成できます。

OdDbSelectionSet クラスは Teigha という IJCAD の DWG を読み書きするために採用しているエンジンの、ソフトウェアライブラリ特有のクラスなので 、AutoCAD の ObjectARX には存在しないと思います。

OdResBuf クラスは resbuf 構造体と同じような機能を持ったクラスで、OdDbSelectionSet::select メソッドで選択セットフィルタを設定する際に使用します。このクラスも ObjectARX には存在しないと思います。

サンプルコード

オブジェクトを全て選択するが、円以外はフィルタリングして、円のみを選択して色を変更しています。

#include "DbSSet.h"
void SampleCommand()
{
    OdDbDatabase* db = oddbHostApplicationServices()->workingDatabase();

    OdResBufPtr filter = OdResBuf::newRb(OdDb::kDxfStart, _T("CIRCLE"));
    OdDbSelectionSetPtr sset = OdDbSelectionSet::select(db, filter);
    OdDbObjectIdArray ids = sset->objectIdArray();

    for each (OdDbObjectId id in ids)
    {
        OdDbEntity* ent;
        oddbOpenOdDbEntity(ent, id, OdDb::kForWrite);
        ent->setColorIndex(1);
        ent->close();
    }
}

実行結果

サンプルコードの処理を実行したら次のような感じになります。 f:id:tknmt:20190517162951g:plain

例えば readDwgFile メソッドを使用して、メモリ上にロードしたデータベース内に対して、特定のオブジェクトのみ選択したいときに役に立つかもしれません。

IJCADのアプリケーション開発は.NET APIでやるべき

IJCADのカスタマイズするために、LISP、COM、ObjectGRX、.NET API の 4 つが API が主に使用されています。

ちょっとしたマクロのようなカスタマイズを行うのであれば、LISP や COM も選択肢に入りますが、新しくIJCAD用のアプリケーション開発を始める場合や、AutoCADや他の互換CAD製品からIJCADに乗り換える際にアプリケーションの移植を場合は、元のAPIが何であれ .NET APIを使用して開発を進めるべきです。

まず IJCAD の、各 API についてのメリットとデメリットを簡単に紹介しますが、ここで挙げた内容はあくまで個人的な見解です。

 

LISP(AutoLISP、VisualLISP、DCL)

  • メリット
    • メモ帳があれば開発が進められコンパイル作業が必要無い
    • バージョンアップ毎に再開発する必要が無い
    • AutoCADや他の互換CADでそのままLISPファイルが使用できたりする
  • デメリット
    • エラーの検出が実行するまでわからない
    • デバッグできないのでエラーチェックに手間がかかる
    • IJCADの外部へのアクセスは限定的
    • LISP関数で提供されていない部分のカスタマイズはできない

■ COM(ActiveX オートメーション)

  • メリット
    • 初心者でも扱いやすい VBA で開発を進められる
    • 他のCOMを利用したソフトウェアと連携できる
    • 例外処理を使用できる
  • デメリット
    • 大量のデータを扱う場合は処理が遅くなる
    • IJCADの64bit版では使えない
    • プロジェクトファイルはAutoCADや他の互換CADと互換性が無い

■ ObjectGRX

  • メリット
    • 事実上IJCADで可能なことは全ての部分でカスタマイズが可能
    • MFCMicrosoft Foundation Class)が利用できる
    • 処理が最も高速>
    • カスタムオブジェクトなど機能自体の拡張が可能
    • とにかく、やろうと思えば何でもできる
  • デメリット
    • 使用する開発言語(C++)の難易度や学習コストが高い
    • ポインタを扱うためバグがあるとIJCADが異常終了する
    • Visual Stadio 2010 が必要
    • コンパイルが必要

.NET API

  • メリット
    • 使用する開発言語(C# または VB.NET)の学習コストが比較的低い
    • .NET Frameworkが持つ豊富な機能を利用することができる
    • IJCADのほぼすべての機能をカスタマイズ可能
    • .NET APIを使用したカスタマイズに関するドキュメントが豊富
    • 開発環境となるVisualStadioバージョンに依存しなくなる
  • デメリット
    • 現時点手は他の開発言語よりも不具合が目立つ
    • コーディング次第でメモリリークが発生してしまうリスクがある
    • コンパイルが必要

 

.NET API のメリットとデメリットは、もう少し掘り下げていきたいと思います。

 

使用する開発言語(C# または VB.NET)の学習コストが比較的低い

.NET APIで使用する開発言語は C# または VB.NET を使用します。 C#VB.NET はどちらも、初心者向けの学習参考書や、ネットのドキュメントが多く存在していて、初めてプログラミング言語にさわる方でも取り組みやすい開発環境です。

また VB.NETC# は業務システム開発や、各種プラットフォーム上のアプリケーション開発にも使用される開発言語なので、決して習得して損のない開発言語です。

NET Frameworkが持つ豊富な機能を利用することができる

コマンド内で表示させるダイアログなどは、.NET Frameworkに含まれるグラフィカルユーザーインターフェイスAPIWindows Forms)を利用することができ、C# ではさらにWPFWindows Presentation Foundation)も利用することができます。

他にもジェネリックLINQ動的言語ランタイムなど、.NET Framework の機能を活用することができます。

IJCADのほぼすべての機能をカスタマイズ可能

VBALISP では IJCAD 上でカスタマイズできる範囲が限定されていていますが、カスタムオブジェクトの定義などを除き、ほぼすべての範囲をカスタマイズ可能です。

ObjectGRX であれば、IJCAD の全ての範囲のカスタマイズが可能になりますが、やはりC++C#VB.NET よりも、開発言語としての学習コストや難易度が高いため、IJCAD 自体を拡張するようなカスタマイズでない限りは、.NET API のカバー範囲で十分開発を進めていけます。

.NET APIによるカスタマイズに関するドキュメントが豊富

IJCAD の開発者向けのドキュメントはまだまだ不足していますが、それでも VBALISP などの他の開発言語と比べて、.NET API のドキュメントの方が多く用意されています。

また、IJCAD の .NET APIAutoCAD .NET API と高い互換性をもっている為、AutoCAD 向けにコーディングされたサンプルコードが IJCAD のコーディングの参考になります。

開発環境となる Visual Stadio バージョンに依存しなくなる

アセンブリの開発に必要な .NET Framework のバージョンが4.0と記されていますが、実際には4.0以降のバージョンであれば問題なく開発が行えるます。

その為、.NET Framework 4.0 をサポートした Visual Stadio 2010 以降であれば、どのVisual Stadio のバージョンを使用しても開発を進めることができます。

 

他の開発言語よりも不具合が目立つ

.NET API によるカスタマイズが IJCAD に導入されたのは IJCAD 2015 からで、比較的新しい開発言語のため、他の開発言語よりも API に不具合が目立つ傾向にあります。

ですが、バージョンのアップデート毎に確実にAPIの品質は良くなっていると思われます。

コーディング次第でメモリリークが発生してしまうリスクがある

.NET API による開発でも、一部のクラスの扱いを間違えるとメモリリーク発生します。

これは、.NET API のクラスは ObjectGRX のクラスををラップして公開している為、CLR(Common Language Runtime)の管理外の部分もあり、一部のクラスは適切に解放処理を行わない場合メモリリークが発生して、Windowsが終了するまでメモリを確保した状態になってしまいます。

コンパイルが必要

VBALISPと違い、コーディングが完了したら即実行というわけにはいかず、プロジェクトのビルドを実行してソースコードコンパイルする必要があります。

設計やコーディングにミスがあり修正を行った場合はその都度コンパイルしなければなりません。

 

■ 総括

IJCAD の .NET API には不具合が目立つというデメリットがありますが、デメリットが気にならなくなるほどにメリットが多く、これからIJCAD 用のアプリケーションの開発や移植を行う場合は、.NET API を使用するべきです。

LISP は覚えてしまえばちょっとしたことが楽にできますが、やはりエラーチェックがとにかく面倒で、VBA は 64bit 版の IJCAD で使用できない時点で選択する余地は無いです。

※ただし、C++ を習得済み、またはC++を挫折せず習得する自信があるのであれば、ObjectGRX を使用した開発が最適解です。

ただ今後のメンテナンスの事を考えると、引き継がせる相手はそれなりに優秀な人材であることと、否が応でも C++ を叩き込む必要があります。

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