【IJCAD】ブロック定義をプレビューする
ブロック定義をプレビューしてみる
AutoCAD DevBlog の記事で紹介されているサンプルや 、adndevblog.typepad.com
Teigha の SDK に含まれるサンプルを参考にし、GraphicsSystem
名前空間のクラスを使用して、ブロック定義のプレビューを表示させるダイアログを作成してみました。
プレビュー画像の表示と違う点
INSERT コマンドを実行した時にで表示されるダイアログを違い、ブロック定義のプレビューイメージを表示させているのではありません。
ブロック定義の BlockTableRecord
描画可能なオブジェクトとして、ダイアログ上に描画させています。
描画させているコントロールにマウスのイベントを追加して、ズームやドリーといった簡易的な操作もできるようにしてみました。
サンプルのプロジェクト一式をグーグルドライブ上で公開していますので、ダウンロードして確認してみてください。
でも結局のところ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
クラスの代替とは言えません。
また、AutoCAD の TransientManager
クラスを使用したグラフィックの表示でもいえますが、一時的に作成したオブジェクトが必要がなくなったときは、確実にオブジェクトを解放しておかないとメモリリークの原因になってしまうので注意してください。
【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点
VIEW コマンドでビュー管理ダイアログを表示して確認してみると、作成したカメラのビューがモデルビューに追加されています。 +60点
カメラを選択してみると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 API の ObjectSnapModes
列挙体に実装されていないのでこのように引数として渡しています。
ですが、この指定方法でも特に問題なく、ポリラインの図心にスナップされたときの、スナップ点の座標を取得できます。