tag:blogger.com,1999:blog-9933938445161839902024-03-16T11:51:11.100-07:00Drive AutoCAD with CodeNorman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.comBlogger137125tag:blogger.com,1999:blog-993393844516183990.post-72891193875340600542024-03-05T07:47:00.000-08:002024-03-16T11:49:17.052-07:00Showing Intersecting Polygon in Jig's Dragging<a href="https://forums.autodesk.com/t5/net/the-right-way-to-jig-hatches-from-list-of-regions-which/m-p/12589443#M80804" target="_blank">A recent discussion</a> in the AutoCAD .NET forum raises question about how to show a dynamic area visually during jig's dragging. <div><br /><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjlwhyULG5jPaEReZldKu0rE7bfj_CyX1iJX-QmCpTb-NWBaG64WdyqUygO_C88pipD3FXUvLkf3pO7kyOTz7f6hUW8TW2BPl7WlojCUfh0j6suH7Hv9HZpuHfCXH76tm0BkUDgWX24LOxLpxbh_GeUn0QgD519cW_yvJN23NNhmwNsbkeuTD00I4jWwVY" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="428" data-original-width="600" height="228" src="https://blogger.googleusercontent.com/img/a/AVvXsEjlwhyULG5jPaEReZldKu0rE7bfj_CyX1iJX-QmCpTb-NWBaG64WdyqUygO_C88pipD3FXUvLkf3pO7kyOTz7f6hUW8TW2BPl7WlojCUfh0j6suH7Hv9HZpuHfCXH76tm0BkUDgWX24LOxLpxbh_GeUn0QgD519cW_yvJN23NNhmwNsbkeuTD00I4jWwVY" width="320" /></a></div><br /></div><div>The OP tried to use Hatch to present the dynamic area. However, creating Hatch entity could only be completed by appending necessary HatchLoops, which require database-residing entities playing the role of the loop. So, it is imagine-able that using Hatch to show a dynamic area during Jig's dragging operation, AutoCAD needs to adding HatchLoop entity or entities into database and then erase it repeatedly in high rate (depending how fast the user drags the mouse), therefore the operation would be sticky, as expected, or even not practically usable. This reminds me one of <a href="https://drive-cad-with-code.blogspot.com/2011/01/mimicking-autocads-area-command-with_20.html" target="_blank">my old article</a> about a custom "AREA" command, in that article I also used Hatch to show dynamic area in a "Jig" style.</div><div><br /></div><div>In the case of the OP, the Jig would drag a closed curve, or a region, entity against a collection of regions (or simply a collection of closed curves), during the dragging, the dynamically identifiable intersecting areas should be visually presented. Obviously, region entity would be used inside the Jig's code for the boolean-intersecting calculation in order to identify the intersecting areas.</div><div><br /></div><div>For simplicity, I create a DrawJig with the inputs of a closed polyline (as the moving area) and a collection of regions (as the target areas that could be intersecting the moving area).</div><div><br /></div><div>Here is the class <i>PolygonIntersectingJig</i>:</div><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.GraphicsInterface;
namespace AreaIntersectionJig
{
public class PolygonIntersectingJig : DrawJig, IDisposable
{
private CadDb.Polyline _polygon;
private List<Region> _regions;
private Region _movingRegion;
private List<Region> _intersectingRegions = new List<Region>();
private Point3d _basePoint;
private Point3d _currPoint;
public PolygonIntersectingJig(
CadDb.Polyline polygon, Point3d basePoint, List<Region> regions)
{
_polygon = polygon;
_regions= regions;
_movingRegion = CreateRegionFromPolyline(polygon);
_basePoint= basePoint;
_currPoint= basePoint;
}
public Point3d JigPoint => _currPoint;
public void Dispose()
{
ClearIntersectingAreas();
_movingRegion.Dispose();
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
var opt = new JigPromptPointOptions("\nMove to:");
opt.UseBasePoint = true;
opt.BasePoint = _basePoint;
opt.Cursor = CursorType.RubberBand;
var res = prompts.AcquirePoint(opt);
if (res.Status==PromptStatus.OK)
{
if (res.Value.Equals(_currPoint))
{
return SamplerStatus.NoChange;
}
else
{
var mt = Matrix3d.Displacement(_currPoint.GetVectorTo(res.Value));
_movingRegion.TransformBy(mt);
_currPoint = res.Value;
GenerateIntersectingAreas();
return SamplerStatus.OK;
}
}
else
{
return SamplerStatus.Cancel;
}
}
protected override bool WorldDraw(WorldDraw draw)
{
draw.Geometry.Draw(_movingRegion);
if (_intersectingRegions.Count > 0)
{
foreach (var item in _intersectingRegions)
{
draw.Geometry.Draw(item);
}
}
return true;
}
private Region CreateRegionFromPolyline(CadDb.Polyline poly)
{
var dbCol = new DBObjectCollection() { poly };
var regs = Region.CreateFromCurves(dbCol);
if (regs.Count>0)
{
var region = (Region)regs[0];
region.ColorIndex = 2;
return region;
}
else
{
throw new ArgumentException(
"Selected polyline cannot form a Region object.");
}
}
private void ClearIntersectingAreas()
{
if (_intersectingRegions.Count > 0)
{
foreach (var item in _intersectingRegions) item.Dispose();
}
}
private void GenerateIntersectingAreas()
{
ClearIntersectingAreas();
foreach (var r in _regions)
{
using (var tempRegion = _movingRegion.Clone() as Region)
{
var intersectingRegion = r.Clone() as Region;
intersectingRegion.BooleanOperation(
BooleanOperationType.BoolIntersect, tempRegion);
if (!intersectingRegion.IsNull)
{
intersectingRegion.ColorIndex = 1;
_intersectingRegions.Add(intersectingRegion);
}
}
}
CadApp.UpdateScreen();
}
private Curve FindIntersectingArea(Region region1, Region region2)
{
Curve areaCurve = null;
region1.BooleanOperation(BooleanOperationType.BoolIntersect, region2);
if (!region1.IsNull)
{
areaCurve = region1.ToBoundaryPolyline();
}
return areaCurve;
}
}
}
</pre><div>Here is the CommandMethod that runs the PolygonIntersectingJig:</div><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[assembly: CommandClass(typeof(AreaIntersectionJig.MyCommands))]
namespace AreaIntersectionJig
{
public class MyCommands
{
[CommandMethod("MyJig")]
public static void RunMyCommand()
{
var dwg = CadApp.DocumentManager.MdiActiveDocument;
var editor = dwg.Editor;
var poly = SelectPolygon(editor);
if (poly.entId.IsNull)
{
editor.WriteMessage("\n*Cancel*\n");
return;
}
var regionIds = SelectRegions(editor);
if (regionIds==null)
{
editor.WriteMessage("\n*Cancel*\n");
return;
}
using (var tran = dwg.TransactionManager.StartTransaction())
{
var movingPolygon = (Polyline)tran.GetObject(poly.entId, OpenMode.ForWrite);
var regions = GetRegions(regionIds, tran);
var jigOk = false;
using (var jig = new PolygonIntersectingJig(movingPolygon, poly.basePt, regions))
{
var res = editor.Drag(jig);
if (res.Status == PromptStatus.OK)
{
var mt = Matrix3d.Displacement(poly.basePt.GetVectorTo(jig.JigPoint));
movingPolygon.TransformBy(mt);
jigOk = true;
}
}
if (jigOk)
{
tran.Commit();
}
else
{
tran.Abort();
}
}
editor.UpdateScreen();
}
private static (ObjectId entId, Point3d basePt) SelectPolygon(Editor ed)
{
var opt = new PromptEntityOptions("\nSelect a closed polyline:");
opt.SetRejectMessage("\nInvalid: not a polyline.");
opt.AddAllowedClass(typeof(Polyline), true);
var res = ed.GetEntity(opt);
if (res.Status== PromptStatus.OK)
{
var pRes = ed.GetPoint("\nSelect base point for moving:");
if (pRes.Status == PromptStatus.OK)
{
return (res.ObjectId, pRes.Value);
}
else
{
return (ObjectId.Null, new Point3d());
}
}
else
{
return (ObjectId.Null, new Point3d());
}
}
private static IEnumerable<ObjectId> SelectRegions(Editor ed)
{
var vals = new TypedValue[] { new TypedValue((int)DxfCode.Start, "REGION") };
var filter = new SelectionFilter(vals);
var opt = new PromptSelectionOptions();
opt.MessageForAdding = "\nSelect regions:";
var res = ed.GetSelection(opt, filter);
if (res.Status== PromptStatus.OK)
{
return res.Value.GetObjectIds();
}
else
{
return null;
}
}
private static List<Region> GetRegions(IEnumerable<ObjectId> regionIds, Transaction tran)
{
var regions= new List<Region>();
foreach (var regionId in regionIds)
{
var region = (Region)tran.GetObject(regionId, OpenMode.ForRead);
regions.Add(region);
}
return regions;
}
}
}
</pre><div>See the following video clip showing how the dynamic intersecting areas are visually presented during the jig's dragging:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzt6FAGplfPuhvkqNa2Zk2hNEZ8T0u2c2AcWAzgMT7dK9bCD9iQFT1Y9BpjJEPIFVxBWbSAeglWbsnRX30sQQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><div><br /></div><div>Without using Hatch to show dynamic intersecting areas, the Jig's dragging is responsive quite well. However, the presented dynamic areas might not be as eye-catching "hatched" area, fore sure. But I thought a proper color of the region would be visually satisfying in most cases. One possible solution I could try is to use MPolygon for the visual presentation.</div><div><br /></div><div>The code here is only meant for showing how to presenting dynamic intersecting areas during Jig's dragging. As for the practical use of it, there could be a few enhancements to make it useful, such as dynamically showing the intersecting areas' area value.</div><div><br /></div><div><b><span style="color: red;">UPDATE</span></b></div><div>I forgot to include an extension method that returns a Polyline as a region's boundary (Thank you, CubeK, for pointed out in your comment). Here is the code:</div><div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
using System.Collections.Generic;
namespace AreaIntersectionJig
{
// Following code is mainly based on the code from Mr. Gilles Chanteau,
// posted in Autodesk's AutoCAD .NET API discussion forum
// https://forums.autodesk.com/t5/net/create-the-outermost-border-for-the-curves-boundary/td-p/12164598
// I modified the code slightly to make it an Extension Method to be used in my article
public static class RegionExtension
{
private struct Segment
{
public Point2d StartPt { get; set; }
public Point2d EndPt { get; set; }
public double Bulge { get; set; }
}
public static Polyline ToBoundaryPolyline(this Region reg)
{
var segments = new DBObjectCollection();
reg.Explode(segments);
var segs = new List<Segment>();
var plane = new Plane(Point3d.Origin, reg.Normal);
for (int i = 0; i < segments.Count; i++)
{
if (segments[i] is Region r)
{
r.Explode(segments);
continue;
}
Curve crv = (Curve)segments[i];
Point3d start = crv.StartPoint;
Point3d end = crv.EndPoint;
double bulge = 0.0;
if (crv is Arc arc)
{
double angle = arc.Center.GetVectorTo(start).
GetAngleTo(arc.Center.GetVectorTo(end), arc.Normal);
bulge = Math.Tan(angle / 4.0);
}
segs.Add(
new Segment
{
StartPt = start.Convert2d(plane),
EndPt = end.Convert2d(plane),
Bulge = bulge
});
}
foreach (DBObject o in segments) o.Dispose();
var pline = new Polyline();
pline.AddVertexAt(0, segs[0].StartPt, segs[0].Bulge, 0.0, 0.0);
Point2d pt = segs[0].EndPt;
segs.RemoveAt(0);
int vtx = 1;
while (true)
{
int i = segs.FindIndex((s) => s.StartPt.IsEqualTo(pt) || s.EndPt.IsEqualTo(pt));
if (i < 0) break;
Segment seg = segs[i];
if (seg.EndPt.IsEqualTo(pt))
seg = new Segment { StartPt = seg.EndPt, EndPt = seg.StartPt, Bulge = -seg.Bulge };
pline.AddVertexAt(vtx, seg.StartPt, seg.Bulge, 0.0, 0.0);
pt = seg.EndPt;
segs.RemoveAt(i);
vtx++;
}
pline.Closed = true;
return pline;
}
}
}
</pre></div>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com2tag:blogger.com,1999:blog-993393844516183990.post-60213287610612188202023-11-05T16:21:00.000-08:002023-11-05T16:21:40.031-08:00Preventing "Explodable" Property of a Block Definition from Being Changed<p>A recent <a href="https://forums.autodesk.com/t5/net/preventing-the-ability-to-explode-a-block/td-p/12331969" target="_blank">thread in the AutoCAD .NET API forum</a> discussed a scenario of how to prevent user to use Block Editor to change the "Explodable" property of a block definition (the OP of that discussion thread wants to keep the block as non-explodable). In my reply to that question, I suggested to use SystemVariableChanged even handler to monitor the system variable "BlockEditor" to detect if user opens or close Block Editor.</p><p>Well, after digging in deeper, I found I was wrong: when system variable "BlockEditor" changes its value (0 or 1, when the Block Editor is opened, or closed), the SystemVariableChange event is not triggered. According to AutoCAD .NET API, it is not guaranteed that SystemVariableChanged event is triggered when these system variables' value is changed by certain commands. Unfortunately, system variable "BlockEditor" is one of them.</p><p>On the other handle, using code to test whether a block definition's "Explodable" property is true/false and change it is rather easy. So the real issue here is to decide when to run the code to detect the change and reverse it back if necessary. In fact, one can trigger the code running with many events that occur with AutoCAD application, document, or database, for example, Application.Idle event, or Document.CommandEnded event. The only issue is, with these event being fired, the chance of "Explodable" property being changed are quite low, so the code would run with nothing being done in most cases. While it is mostly harmless, it would be better to only run code when use does something, in which the "Explodable" property is likely being changed. Obviously, if user opens Block Editor, the chance of "Explodable" property being changed is higher.</p><p>With this in mind, I stick with the approach of watching SystemVariableChange events to see what happen when I open and close BlockEditor. Here are what I found:</p><p>1. When user opens Block Editor (selecting a block, right-clicking to show context menu and selecting "Block Editor...", or simply entering command "BEDIT"), SystemVariableChanged events fire against following system variables:</p><p>USCNAME, CLAYER, VIEWDIR</p><p>2. When user closes Block Editor, SystemVariableChanged events fire against following system variables:</p><p>UCSNAME, CLAYER, EXTMIN, EXTMAX, CANNOSCALE</p><p>So, I thought I can detect if Block Editor is opened or closed when these 2 group of system variables are changed. When Block Editor is detected being opened, I can safely assume its close will be detected, unless the user shuts down AutoCAD without closing it (then even use indeed changes a block's "Explodable" property, it will not take effect unless the Block Editor is closed properly). Therefore, as long as I detected Block Editor is opened and then closed, there is chance the user has changed the "Explodable" property of a block definition, thus, I need to run the code to make sure the change is reversed back after the Block Editor is closed.</p><p>The code is rather simple, as shown here:</p><p>
</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">namespace</span> StopBlockExplosion
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockExplosionGuard</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> IEnumerable<<span style="color: blue;">string</span>> _blockNames;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _enabled = <span style="color: blue;">false</span>;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _blockEditorOn = <span style="color: blue;">false</span>;
<span style="color: blue;">public</span> <span style="color: #066555;">BlockExplosionGuard</span>(IEnumerable<<span style="color: blue;">string</span>> <span style="color: #1f377f;">targetBlockNames</span>)
{
_blockNames = targetBlockNames;
}
<span style="color: #5b5b5b;">#region</span> public methods
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> IsEnabled=>_enabled;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Enable</span>(<span style="color: blue;">bool</span> <span style="color: #1f377f;">enable</span>)
{
_enabled= enable;
<span style="color: #8f08c4;">if</span> (_enabled)
{
CadApp.SystemVariableChanged += CadApp_SystemVariableChanged;
}
<span style="color: #8f08c4;">else</span>
{
CadApp.SystemVariableChanged -= CadApp_SystemVariableChanged;
}
}
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: #5b5b5b;">#region</span> private method: reverse changed "Explodable" property of the BlockTableRecord
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">VerifyNonExplodableBlocksInCurrentDwg</span>(Document <span style="color: #1f377f;">dwg</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkTable</span> = (BlockTable)tran.GetObject(
dwg.Database.BlockTableId, OpenMode.ForRead);
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blkName</span> <span style="color: #8f08c4;">in</span> _blockNames)
{
<span style="color: #8f08c4;">if</span> (!blkTable.Has(blkName)) <span style="color: #8f08c4;">continue</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = (BlockTableRecord)tran.GetObject(
blkTable[blkName], OpenMode.ForRead);
<span style="color: #8f08c4;">if</span> (blk.Explodable)
{
CadApp.ShowAlertDialog(
<span style="color: #a31515;">$"Block \"</span>{blk.Name}<span style="color: #a31515;"> has been accidently changed to EXPLODABLE!\n\n"</span> +
<span style="color: #a31515;">"Our CAD standard now forces it back to NON-EXPLODABLE!"</span>);
blk.UpgradeOpen();
blk.Explodable = <span style="color: blue;">false</span>;
}
}
tran.Commit();
}
}
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CadApp_SystemVariableChanged</span>(
<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, Autodesk.AutoCAD.ApplicationServices.SystemVariableChangedEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">vName</span> = e.Name.ToUpper();
<span style="color: #8f08c4;">if</span> (!_blockEditorOn)
{
<span style="color: #066555;">// detect Block Editor is turned on</span>
<span style="color: #8f08c4;">if</span> (vName == <span style="color: #a31515;">"UCSNAME"</span> || vName == <span style="color: #a31515;">"CLAYER"</span> || vName == <span style="color: #a31515;">"VIEWDIR"</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">val</span> = (<span style="color: blue;">short</span>)CadApp.GetSystemVariable(<span style="color: #a31515;">"BLOCKEDITOR"</span>);
<span style="color: #8f08c4;">if</span> (val == 1)
{
_blockEditorOn = <span style="color: blue;">true</span>;
}
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">if</span> (vName == <span style="color: #a31515;">"UCSNAME"</span> || vName == <span style="color: #a31515;">"CLAYER"</span> ||
vName == <span style="color: #a31515;">"EXTMIN"</span> || vName ==<span style="color: #a31515;">"EXTMAX"</span> || vName==<span style="color: #a31515;">"CANNOSCALE"</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">val</span> = (<span style="color: blue;">short</span>)CadApp.GetSystemVariable(<span style="color: #a31515;">"BLOCKEDITOR"</span>);
<span style="color: #8f08c4;">if</span> (val == 0)
{
_blockEditorOn = <span style="color: blue;">false</span>;
CadApp.Idle += CadApp_Idle;
}
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CadApp_Idle</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, EventArgs <span style="color: #1f377f;">e</span>)
{
CadApp.Idle -= CadApp_Idle;
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">using</span> (dwg.LockDocument())
{
VerifyNonExplodableBlocksInCurrentDwg(dwg);
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><p></p><p>To place the code into work:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(StopBlockExplosion.MyCommands))]
<span style="color: blue;">namespace</span> StopBlockExplosion
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">static</span> BlockExplosionGuard _blkExplosionGuard = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"NoBlkExp"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunMyCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">editor</span> = dwg.Editor;
<span style="color: #8f08c4;">if</span> (_blkExplosionGuard==<span style="color: blue;">null</span>)
{
_blkExplosionGuard = <span style="color: blue;">new</span> BlockExplosionGuard(
<span style="color: blue;">new</span>[] { <span style="color: #a31515;">"TestBlk1"</span>, <span style="color: #a31515;">"TestBlk2"</span> });
}
<span style="color: #8f08c4;">if</span> (!_blkExplosionGuard.IsEnabled)
{
_blkExplosionGuard.Enable(<span style="color: blue;">true</span>);
editor.WriteMessage(
<span style="color: #a31515;">"\nBlockExplosionGuard is enabled."</span>);
}
<span style="color: #8f08c4;">else</span>
{
_blkExplosionGuard.Enable(<span style="color: blue;">false</span>);
editor.WriteMessage(
<span style="color: #a31515;">"\nBlockExplosionGuard is disabled."</span>);
}
}
}
}</pre><p>The video clip below showing the code in action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='512' height='425' src='https://www.blogger.com/video.g?token=AD6v5dxCJ1C54avsBrhSsfnXqG7pQ-mwlwOUofvBet-GZhFgLs3nU6nzWzNL_uTwKxGSnV-eUeyLZcpA_tFJhlTzXw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-17492855506220172212023-09-30T07:50:00.000-07:002023-09-30T07:50:57.690-07:00Find Tangential Touch Points of Entities (Beyond ARC/Circle)<p>A while ago, I post <a href="https://drive-cad-with-code.blogspot.com/2022/02/where-tangent-point-is-show-it-when.html" target="_blank">an article</a> on finding tangent points on a Circle or an Arc. Recently, a question was posted in the AutoCAD .NET API discussion, asking how to find 2 edge points if a ray projected from a point towards an entity (in the original question, the entity is a BlockReference, but in general, it could be any entity, Line, Circle, Polyline, Text, Hatch...),as the picture is shown below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha9e09JS4jYEVARmsbQacWOl0QGAmKFDEtrbBQeRBmny7kHq9RiFiOYt2FrnM1Ami4pMXUNXBQjK-IDe_QKcMIpH4Jc3vVU99OlsMVI06_HbRrwFgqZG_rK83XnhMtWW7I2PTS7V-8oz06J5Bz2e-yvoZjkJlVAGRD3JDwsG0jof3ZPDF6d6Cu2yjqGNk/s600/TangentialPoints.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="414" data-original-width="600" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha9e09JS4jYEVARmsbQacWOl0QGAmKFDEtrbBQeRBmny7kHq9RiFiOYt2FrnM1Ami4pMXUNXBQjK-IDe_QKcMIpH4Jc3vVU99OlsMVI06_HbRrwFgqZG_rK83XnhMtWW7I2PTS7V-8oz06J5Bz2e-yvoZjkJlVAGRD3JDwsG0jof3ZPDF6d6Cu2yjqGNk/s320/TangentialPoints.png" width="320" /></a></div><br /><p>The case is similar to finding tangent point on Circle, but only the target entity could be in any shape (Circle and Arc could be included, of course), so, let me call them tangential points of an Entity from a point away from it. </p><p>When I read the question from the forum, I gave it some thought, but did not find time to solve it with code then, but finally, I have some code running that gives fairly satisfying result. Because of my limited time available, I limited the target entities only to be ARC/CIRCLE/LINE/POLYLINE.</p><p>Before diving into the code, here is the video clip showing the result of the code, so that my reader could see if they are interested in the scenarios shown in the video, if yes, then going deep into the code.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dylpIO4p-EJy5QgEAnh0IkJnwqmJKVookZ3ZtWk8mAFtrs1gGf4yljUqSUwhodCmjjW3EPfHBv2sL4lDKLkJw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>After watching the video, the code would be much easier to follow, as shown below.</p><p>First, the help class that actually does the work of finding tangential points:</p><p>
</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">namespace</span> FindTangentialPoints
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">CadUtils</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> (Point3d? tanPt1, Point3d? tanPt2) <span style="color: #74531f;">GetTangentialPoints</span>(
Curve <span style="color: #1f377f;">curve</span>, Point3d <span style="color: #1f377f;">tanLineOrigin</span>)
{
List<Point3d> <span style="color: #1f377f;">points</span> = <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">try</span>
{
<span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Line)
{
points = GetTangentPointsOnLine(
curve <span style="color: blue;">as</span> Line, tanLineOrigin);
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Arc)
{
points = GetTangentPointsOnArc(
curve <span style="color: blue;">as</span> Arc, tanLineOrigin);
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Circle)
{
points = GetTangentPointsOnCircle(
curve <span style="color: blue;">as</span> Circle, tanLineOrigin);
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Polyline)
{
points = GetTangentPointsOnPolyline(
curve <span style="color: blue;">as</span> Polyline, tanLineOrigin);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">throw</span> <span style="color: blue;">new</span> NotImplementedException(
<span style="color: #a31515;">"The target entity must be LINE/ARC/CIRCLE/POLYLINE!"</span>);
}
}
<span style="color: #8f08c4;">catch</span>
{
points = <span style="color: blue;">null</span>;
}
<span style="color: #8f08c4;">if</span> (points != <span style="color: blue;">null</span> && points.Count == 2)
{
<span style="color: #8f08c4;">return</span> (points[0], points[1]);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> (<span style="color: blue;">null</span>, <span style="color: blue;">null</span>);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetTangentPointsOnArc</span>(
Arc <span style="color: #1f377f;">arc</span>, Point3d <span style="color: #1f377f;">originPoint</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">geArc</span> = arc.GetGeCurve() <span style="color: blue;">as</span> CircularArc3d;
<span style="color: #8f08c4;">return</span> GetTangentPointsOnArc(geArc, originPoint);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetTangentPointsOnArc</span>(
CircularArc3d <span style="color: #1f377f;">geArc</span>, Point3d <span style="color: #1f377f;">originPoint</span>)
{
CalculateTangentPointOfCircularArc(
geArc, originPoint, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tan1</span>, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tan2</span>);
<span style="color: #8f08c4;">if</span> (tan1.HasValue && tan2.HasValue)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { tan1.Value, tan2.Value };
}
<span style="color: #8f08c4;">if</span> (!tan1.HasValue && !tan2.HasValue)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { geArc.StartPoint, geArc.EndPoint };
}
<span style="color: #8f08c4;">else</span>
{
Point3d <span style="color: #1f377f;">pt</span> = tan1.HasValue ? tan1.Value : tan2.Value;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">curve</span> = Curve.CreateFromGeCurve(geArc))
{
<span style="color: #8f08c4;">if</span> (IsTangetiallyTouched(curve, geArc.StartPoint, originPoint))
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { pt, geArc.StartPoint };
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { pt, geArc.EndPoint };
}
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetTangentPointsOnCircle</span>(
Circle <span style="color: #1f377f;">circle</span>, Point3d <span style="color: #1f377f;">originPoint</span>)
{
<span style="color: #8f08c4;">if</span> (IsInside(circle, originPoint)) <span style="color: #8f08c4;">return</span> <span style="color: blue;">null</span>;
CalculateTangentPointOfCircularArc(
circle.GetGeCurve() <span style="color: blue;">as</span> CircularArc3d,
originPoint, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tan1</span>, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tan2</span>);
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { tan1.Value, tan2.Value };
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetTangentPointsOnLine</span>(
Line <span style="color: #1f377f;">line</span>, Point3d <span style="color: #1f377f;">originPoint</span>)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { line.StartPoint, line.EndPoint };
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetTangentPointsOnPolyline</span>(
Polyline <span style="color: #1f377f;">polyline</span>, Point3d <span style="color: #1f377f;">originPoint</span>)
{
<span style="color: #8f08c4;">if</span> (polyline.Closed)
{
<span style="color: #8f08c4;">if</span> (IsInside(polyline, originPoint)) <span style="color: #8f08c4;">return</span> <span style="color: blue;">null</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #066555;">// If the point is inside the polyline's area</span>
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = polyline.Clone() <span style="color: blue;">as</span> Polyline)
{
poly.Closed = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">if</span> (IsInside(poly, originPoint))
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d>
{
polyline.StartPoint,
polyline.EndPoint
};
}
}
}
<span style="color: #066555;">// If the point is beyond the polyline's area</span>
Point3d? <span style="color: #1f377f;">tan1</span> = <span style="color: blue;">null</span>;
Point3d? <span style="color: #1f377f;">tan2</span> = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">points</span>=GetPossibleTangentialPoints(polyline, originPoint);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = polyline.Clone() <span style="color: blue;">as</span> Polyline)
{
<span style="color: #8f08c4;">if</span> (!poly.Closed) poly.Closed = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">point</span> <span style="color: #8f08c4;">in</span> points)
{
<span style="color: #8f08c4;">if</span> (IsTangetiallyTouched(poly, point, originPoint))
{
<span style="color: #8f08c4;">if</span> (!tan1.HasValue)
{
tan1 = point;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">if</span> (!IsTheSamePoint(point, tan1.Value))
{
tan2 = point;
}
}
}
<span style="color: #8f08c4;">if</span> (tan1.HasValue && tan2.HasValue) <span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">if</span> (tan1.HasValue && tan2.HasValue)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> List<Point3d> { tan1.Value, tan2.Value };
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">null</span>;
}
}
<span style="color: #5b5b5b;">#region</span> private methods:
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsTheSamePoint</span>(Point3d <span style="color: #1f377f;">p1</span>, Point3d <span style="color: #1f377f;">p2</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dist</span>=p1.DistanceTo(p2);
<span style="color: #8f08c4;">return</span> dist <= Tolerance.Global.EqualPoint;
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> List<Point3d> <span style="color: #74531f;">GetPossibleTangentialPoints</span>(
Polyline <span style="color: #1f377f;">poly</span>, Point3d <span style="color: #1f377f;">originPt</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">points</span>=<span style="color: blue;">new</span> List<Point3d>();
DBObjectCollection <span style="color: #1f377f;">segs</span>= <span style="color: blue;">new</span> DBObjectCollection();
poly.Explode(segs);
<span style="color: #8f08c4;">foreach</span> (DBObject <span style="color: #1f377f;">seg</span> <span style="color: #8f08c4;">in</span> segs)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = seg <span style="color: blue;">as</span> Line;
<span style="color: #8f08c4;">if</span> (line!=<span style="color: blue;">null</span>)
{
points.Add(line.StartPoint);
points.Add(line.EndPoint);
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">arc</span> = seg <span style="color: blue;">as</span> Arc;
<span style="color: #8f08c4;">if</span> (arc!=<span style="color: blue;">null</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">tanPts</span> = GetTangentPointsOnArc(arc, originPt);
<span style="color: #8f08c4;">if</span> (tanPts!=<span style="color: blue;">null</span>)
{
points.AddRange(tanPts);
}
}
}
<span style="color: #8f08c4;">return</span> points;
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CalculateTangentPointOfCircularArc</span>(
CircularArc3d <span style="color: #1f377f;">arc</span>, Point3d <span style="color: #1f377f;">point</span>,
<span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tangent1</span>, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tangent2</span>)
{
tangent1 = <span style="color: blue;">null</span>;
tangent2 = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">dist</span> = point.DistanceTo(arc.Center);
<span style="color: #8f08c4;">if</span> (dist < arc.Radius) <span style="color: #8f08c4;">return</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle</span> = Math.Acos(arc.Radius / dist);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = <span style="color: blue;">new</span> Line(arc.Center, point))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle1</span> = line.Angle + angle;
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle2</span> = line.Angle - angle;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">circle</span> = <span style="color: blue;">new</span> Circle(arc.Center, arc.Normal, arc.Radius))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">arcLen</span> = angle1 * arc.Radius;
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = circle.GetPointAtDist(arcLen);
<span style="color: #8f08c4;">if</span> (arc.IsOn(pt))
{
tangent1 = pt;
}
arcLen = angle2 * arc.Radius;
pt = circle.GetPointAtDist(arcLen);
<span style="color: #8f08c4;">if</span> (arc.IsOn(pt))
{
tangent2 = pt;
}
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsTangetiallyTouched</span>(
Curve <span style="color: #1f377f;">curve</span>, Point3d <span style="color: #1f377f;">ptOnCurve</span>, Point3d <span style="color: #1f377f;">ptOrigin</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">pts</span> = <span style="color: blue;">new</span> Point3dCollection();
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">ray</span> = <span style="color: blue;">new</span> Ray())
{
ray.BasePoint = ptOrigin;
ray.SecondPoint = ptOnCurve;
curve.IntersectWith(
ray, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
}
<span style="color: #8f08c4;">return</span> pts.Count == 1;
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsInside</span>(Curve <span style="color: #1f377f;">curve</span>, Point3d <span style="color: #1f377f;">pt</span>)
{
<span style="color: #8f08c4;">if</span> (!curve.Closed) <span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">inside</span> = <span style="color: blue;">false</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">ray</span>=<span style="color: blue;">new</span> Ray())
{
ray.BasePoint = pt;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ext</span> = curve.GeometricExtents;
<span style="color: blue;">var</span> <span style="color: #1f377f;">h</span> = ext.MaxPoint.Y - ext.MinPoint.Y;
<span style="color: blue;">var</span> <span style="color: #1f377f;">w</span>=ext.MaxPoint.X-ext.MinPoint.X;
<span style="color: blue;">var</span> <span style="color: #1f377f;">l</span> = Math.Max(h, w);
ray.SecondPoint=<span style="color: blue;">new</span> Point3d(pt.X+2*l, pt.Y+2*l, pt.Z);
<span style="color: blue;">var</span> <span style="color: #1f377f;">pts</span> = <span style="color: blue;">new</span> Point3dCollection();
curve.IntersectWith(
ray, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
<span style="color: #8f08c4;">if</span> (pts.Count > 0)
{
inside = pts.Count == 1 ? <span style="color: blue;">true</span> : pts.Count % 2 != 0;
}
}
<span style="color: #8f08c4;">return</span> inside;
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><p>Then the code that does the fancy jig-style work of dynamically drawing the rays passing through the tangential points:</p><p>
</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> FindTangentialPoints
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">TangentialLines</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> TransientManager _tsManager =
TransientManager.CurrentTransientManager;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Database _db;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> Ray _tanLine1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Ray _tanLine2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBPoint _tanPt1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBPoint _tanPt2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Curve _curve = <span style="color: blue;">null</span>;
<span style="color: blue;">public</span> <span style="color: #066555;">TangentialLines</span>()
{
_dwg = Application.DocumentManager.MdiActiveDocument;
_db = _dwg.Database;
_ed = _dwg.Editor;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DrawTangentialLines</span>(ObjectId <span style="color: #1f377f;">curveId</span>)
{
<span style="color: blue;">short</span> <span style="color: #1f377f;">ptMode</span> = (<span style="color: blue;">short</span>)Application.GetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>);
Application.SetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>, 34);
<span style="color: #8f08c4;">try</span>
{
_ed.PointMonitor += Editor_PointMonitor;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _db.TransactionManager.StartTransaction())
{
_curve = (Curve)tran.GetObject(curveId, OpenMode.ForRead);
_curve.Highlight();
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetPoint(<span style="color: #a31515;">"\nSelect tangential line's origin:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: #8f08c4;">if</span> (_tanPt1 != <span style="color: blue;">null</span> && _tanPt2 != <span style="color: blue;">null</span>)
{
CreateTangentialLines(
res.Value, _tanPt1.Position, _tanPt2.Position, tran);
}
}
_curve.Unhighlight();
tran.Commit();
}
_ed.UpdateScreen();
}
<span style="color: #8f08c4;">finally</span>
{
_ed.PointMonitor -= Editor_PointMonitor;
ClearTransients();
Application.SetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>, ptMode);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
ClearTransients();
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearTransients</span>()
{
<span style="color: #8f08c4;">if</span> (_tanLine1!=<span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(_tanLine1, <span style="color: blue;">new</span> IntegerCollection());
_tanLine1.Dispose();
}
<span style="color: #8f08c4;">if</span> (_tanLine2 != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(_tanLine2, <span style="color: blue;">new</span> IntegerCollection());
_tanLine2.Dispose();
}
<span style="color: #8f08c4;">if</span> (_tanPt1 != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(_tanPt1, <span style="color: blue;">new</span> IntegerCollection());
_tanPt1.Dispose();
}
<span style="color: #8f08c4;">if</span> (_tanPt2 != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(_tanPt2, <span style="color: blue;">new</span> IntegerCollection());
_tanPt2.Dispose();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateTransients</span>(Point3d <span style="color: #1f377f;">origin</span>, Point3d <span style="color: #1f377f;">pt1</span>, Point3d <span style="color: #1f377f;">pt2</span>)
{
<span style="color: #8f08c4;">if</span> (_tanPt1 == <span style="color: blue;">null</span> || _tanPt1.IsDisposed)
{
_tanPt1 = <span style="color: blue;">new</span> DBPoint(pt1);
_tanPt1.ColorIndex = 1;
_tsManager.AddTransient(
_tanPt1,
TransientDrawingMode.DirectShortTerm,
128, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">else</span>
{
_tanPt1.Position = pt1;
_tsManager.UpdateTransient(_tanPt1, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">if</span> (_tanPt2 == <span style="color: blue;">null</span> || _tanPt2.IsDisposed)
{
_tanPt2 = <span style="color: blue;">new</span> DBPoint(pt2);
_tanPt2.ColorIndex = 1;
_tsManager.AddTransient(
_tanPt2,
TransientDrawingMode.DirectShortTerm,
128, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">else</span>
{
_tanPt2.Position = pt2;
_tsManager.UpdateTransient(_tanPt2, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">if</span> (_tanLine1 == <span style="color: blue;">null</span> || _tanLine1.IsDisposed)
{
_tanLine1 = <span style="color: blue;">new</span> Ray();
_tanLine1.BasePoint = origin;
_tanLine1.SecondPoint = pt1;
_tanLine1.ColorIndex = 2;
_tsManager.AddTransient(
_tanLine1,
TransientDrawingMode.DirectShortTerm,
128, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">else</span>
{
_tanLine1.BasePoint = origin;
_tanLine1.SecondPoint = pt1;
_tsManager.UpdateTransient(_tanLine1, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">if</span> (_tanLine2 == <span style="color: blue;">null</span> || _tanLine2.IsDisposed)
{
_tanLine2 = <span style="color: blue;">new</span> Ray();
_tanLine2.BasePoint = origin;
_tanLine2.SecondPoint = pt2;
_tanLine2.ColorIndex = 2;
_tsManager.AddTransient(
_tanLine2,
TransientDrawingMode.DirectShortTerm,
128, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: #8f08c4;">else</span>
{
_tanLine2.BasePoint = origin;
_tanLine2.SecondPoint = pt2;
_tsManager.UpdateTransient(_tanLine2, <span style="color: blue;">new</span> IntegerCollection());
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateTangentialLines</span>(
Point3d <span style="color: #1f377f;">origin</span>, Point3d <span style="color: #1f377f;">tanPt1</span>, Point3d <span style="color: #1f377f;">tanPt2</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(
_db.CurrentSpaceId, OpenMode.ForWrite);
<span style="color: blue;">var</span> <span style="color: #1f377f;">line1</span> = <span style="color: blue;">new</span> Line(origin, tanPt1);
line1.SetDatabaseDefaults();
line1.ColorIndex = 3;
space.AppendEntity(line1);
tran.AddNewlyCreatedDBObject(line1, <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">line2</span> = <span style="color: blue;">new</span> Line(origin, tanPt2);
line2.SetDatabaseDefaults();
line2.ColorIndex = 3;
space.AppendEntity(line2);
tran.AddNewlyCreatedDBObject(line2, <span style="color: blue;">true</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Editor_PointMonitor</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PointMonitorEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">originPt</span> = e.Context.RawPoint;
<span style="color: blue;">var</span> <span style="color: #1f377f;">tanPoints</span> = CadUtils.GetTangentialPoints(_curve, originPt);
<span style="color: #8f08c4;">if</span> (tanPoints.tanPt1.HasValue && tanPoints.tanPt2.HasValue)
{
UpdateTransients(
originPt, tanPoints.tanPt1.Value, tanPoints.tanPt2.Value);
_ed.UpdateScreen();
}
<span style="color: #8f08c4;">else</span>
{
ClearTransients();
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><p>Finally, the commands I used to demonstrate the code action:</p><p>
</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(FindTangentialPoints.MyCommands))]
<span style="color: blue;">namespace</span> FindTangentialPoints
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"DynamicTans"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunMyCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">editor</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">selected</span> = SelectCurve(editor);
<span style="color: #8f08c4;">if</span> (selected.IsNull)
{
editor.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tanLines</span> = <span style="color: blue;">new</span> TangentialLines())
{
tanLines.DrawTangentialLines(selected);
}
}
[CommandMethod(<span style="color: #a31515;">"PickTans"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">PickPointForTangentialLines</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">editor</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">selected</span> = SelectCurve(editor);
<span style="color: #8f08c4;">if</span> (selected.IsNull)
{
editor.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = editor.GetPoint(<span style="color: #a31515;">"\nSelect point:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">curve</span> = (Curve)tran.GetObject(selected, OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">tanPoints</span> = CadUtils.GetTangentialPoints(curve, res.Value);
<span style="color: #8f08c4;">if</span> (tanPoints.tanPt1.HasValue && tanPoints.tanPt2.HasValue)
{
DrawingTangentLines(
res.Value,
tanPoints.tanPt1.Value,
tanPoints.tanPt2.Value,
dwg.Database.CurrentSpaceId, tran);
}
<span style="color: #8f08c4;">else</span>
{
editor.WriteMessage(<span style="color: #a31515;">"\nCannot find tangentail points."</span>);
}
tran.Commit();
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> ObjectId <span style="color: #74531f;">SelectCurve</span>(Editor <span style="color: #1f377f;">ed</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect an entity (ARC/CIRCLE/LINE/POLYLINE):"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid: must be ARC/CIRCLE/LINE/POLYLINE."</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Arc), <span style="color: blue;">true</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Circle), <span style="color: blue;">true</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Line), <span style="color: blue;">true</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Polyline), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: #8f08c4;">return</span> res.ObjectId;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> ObjectId.Null;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DrawingTangentLines</span>(
Point3d <span style="color: #1f377f;">origin</span>, Point3d <span style="color: #1f377f;">pt1</span>, Point3d <span style="color: #1f377f;">pt2</span>, ObjectId <span style="color: #1f377f;">spaceId</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(spaceId, OpenMode.ForWrite);
<span style="color: blue;">var</span> <span style="color: #1f377f;">line1</span> = <span style="color: blue;">new</span> Line(origin, pt1);
line1.SetDatabaseDefaults();
line1.ColorIndex = 2;
space.AppendEntity(line1);
tran.AddNewlyCreatedDBObject(line1, <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">line2</span> = <span style="color: blue;">new</span> Line(origin, pt2);
line2.SetDatabaseDefaults();
line2.ColorIndex = 2;
space.AppendEntity(line2);
tran.AddNewlyCreatedDBObject(line2, <span style="color: blue;">true</span>);
}
}
}</pre><p>Some Points to Explore:</p><p>As mentioned, I choose to limited the target "obstacle" entities to ARC/CIRCLE/LINE/POLYLINE to simplify my effort. If the target entity is other types, I'd use get the entity's shape as one of the basic entities for the tangential point calculation. Such as:</p><p>1. DBText/MText. Use its bounding box to generate a rectangle polyline. Note, if the text entity is rotated, the rectangle bounding polyline should be generated by rotating the text entity to 0 degree and then transform the rectangle to the text entity's rotation angle.</p><p>2. Hatch. Find its outer loop as a Polyline.</p><p>3. A group of entities of mixed types. The possible approach would be</p><p>a. find tangential points of each entity in the group and place all the points in a collection, say a List<Point3d>;</p><p>b. randomly choose one and create a ray from the origin point;</p><p>c. create a ray with each of the other points, get the angle between this ray and the first ray to determine the largest angles clockwise and counterclockwise to eventually decide the outmost 2 tangential points of these entities in the group.</p><p>4. BlockReference. Explode the block reference and do the tangential point calculation on individual elements, then do as described in 3.</p><p>The source is available for download <a href="https://drive.google.com/file/d/1VuecLWio48Ghyy333Qbsv-wGPTKRL-3x/view?usp=sharing">here</a>.</p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com5tag:blogger.com,1999:blog-993393844516183990.post-11817585525401695592023-07-24T19:09:00.000-07:002023-07-24T19:09:03.898-07:00Selecting Split Point of a Line with Visual Aid<p><b><span style="font-size: medium;">Background</span></b></p><p>This article is inspired by <a href="https://forums.autodesk.com/t5/net/jigging-splitting-polyline/td-p/12118902" target="_blank">a question</a> asked in Autodesk .NET API discussion forum. The OP expect to use a JIG as visual aid for user to select a point on a Polyline so that the Polyline could be split into 2. Obviously, selecting a point on Polyline itself is really simple, especially if OSNAP is enabled. But to select a point accurately that makes business sense, it would be better for the command to provide sufficient visual hints about where the expected point should be and make the accurate pick easy. In the case of the posted question, the OP wants to show the length of potentially split 2 segments where user moves the mouse to pick point.</p><p>While this surely can be done with custom Jig class (likely derived from DrawJig), I thought it would be as simple as wrap the Editor.GetPoint() in between with add/removing Editor.PointMonitor event handler. After all, the nature of the operation is to get an accurate split point on the Polyline. Once an expected split point is obtained, use would not have to worry how the Polyline gets split.</p><p><span style="font-size: x-small;">Note: the OP not only wants to split the Polyline into 2 segments, but also makes the 2 segments overlap to each other in a given range. However, for simplicity, in this article, I ignore the "overlap" requirement and only focus on how to make helpful, user-friendly visual aid while user is trying to select the split point</span>.</p><p><b><span style="font-size: medium;">Design of The Code</span></b></p><p>Firstly, I make some assumptions to make the code sample simple:</p><p></p><ul style="text-align: left;"><li>the entity to be split is a Line</li><li>user intends to select the split point based on the center point of the Line</li><li>the desired/possible split point would be apart from center point with given gap incrementally </li></ul><div>Therefore, the goals of my code are to achieve are:</div><div><ul style="text-align: left;"><li>Ask user to select the target Line</li><li>Once a Line entity is selected, the center point of the Line and potential split points along the Line, before and after the center point is visually shown</li><li>While user moved the mouse, one the potential split point that is closest to the mouse cursor would be visually prompted</li><li>When the user clicks the mouse, the mouse cursor does not need to be accurately at the prompted split point, thus the point selecting is really user-friendly</li></ul><div><span style="font-size: medium;"><b>The Code</b></span></div></div><div><br /></div><div>The class <i>LineSplite</i>r:</div><div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">namespace</span> DragToSplitLine
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">LineSplitter</span> : IDisposable
{
<span style="color: blue;">private</span> Document _dwg;
<span style="color: blue;">private</span> Editor _ed;
<span style="color: blue;">private</span> ObjectId _lineId = ObjectId.Null;
<span style="color: blue;">private</span> Line _line1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Line _line2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> List<DBPoint> _incrementPoints = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBText _text1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBText _text2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Point3d _splitPoint = Point3d.Origin;
<span style="color: blue;">private</span> <span style="color: blue;">double</span> _increment = 0.0;
<span style="color: blue;">private</span> <span style="color: blue;">double</span> _textHeight = 1.0;
<span style="color: blue;">private</span> TransientManager _tsManager = TransientManager.CurrentTransientManager;
<span style="color: blue;">public</span> <span style="color: #066555;">LineSplitter</span>(Document <span style="color: #1f377f;">dwg</span>, ObjectId <span style="color: #1f377f;">lineId</span>)
{
_dwg= dwg;
_ed = _dwg.Editor;
_lineId= lineId;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
ClearTransientDrawables();
}
<span style="color: blue;">public</span> Point3d? <span style="color: #74531f;">DragSplitter</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ok</span> = <span style="color: blue;">false</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">originalPdMode</span> = Convert.ToInt32(Application.GetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>));
<span style="color: blue;">var</span> <span style="color: #1f377f;">originalPdSize</span> = Convert.ToDouble(Application.GetSystemVariable(<span style="color: #a31515;">"PDSIZE"</span>));
<span style="color: #8f08c4;">try</span>
{
<span style="color: #066555;">// Set DBPoint display mode</span>
Application.SetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>, 2);
Application.SetSystemVariable(<span style="color: #a31515;">"PDSIZE"</span>, -1.0);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = (Line)tran.GetObject(_lineId, OpenMode.ForRead);
<span style="color: #066555;">// Get initial increment, which could be any value</span>
_increment = (Math.Floor(line.Length / 10) * 10) / 20;
_textHeight = line.Length / 100;
CreateTransientDrawables(line);
<span style="color: #8f08c4;">do</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">changeIncrement</span> = <span style="color: blue;">false</span>;
<span style="color: #8f08c4;">try</span>
{
AddTransientDrawables();
_ed.PointMonitor += Editor_PointMonitor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptPointOptions(
<span style="color: #a31515;">"\nSelect split point on selected line, or change increment:"</span>);
opt.AllowNone = <span style="color: blue;">true</span>;
opt.Keywords.Add(<span style="color: #a31515;">"Increment"</span>);
opt.Keywords.Default = <span style="color: #a31515;">"Increment"</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetPoint(opt);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
ok = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">break</span>;
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.Keyword)
{
changeIncrement = <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">finally</span>
{
ClearTransientDrawables();
_ed.PointMonitor -= Editor_PointMonitor;
}
<span style="color: #8f08c4;">if</span> (changeIncrement)
{
<span style="color: #8f08c4;">if</span> (ChangeIncrement(<span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">inc</span>))
{
_increment = inc;
ClearTransientDrawables();
CreateTransientDrawables(line);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
} <span style="color: #8f08c4;">while</span> (<span style="color: blue;">true</span>);
tran.Commit();
}
}
<span style="color: #8f08c4;">finally</span>
{
<span style="color: #066555;">// Restore DBPoint diaplay mode</span>
Application.SetSystemVariable(<span style="color: #a31515;">"PDMODE"</span>, originalPdMode);
Application.SetSystemVariable(<span style="color: #a31515;">"PDSIZE"</span>, originalPdSize);
_ed.UpdateScreen();
}
<span style="color: #8f08c4;">if</span> (ok)
{
<span style="color: #8f08c4;">return</span> _splitPoint;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">null</span>;
}
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Editor_PointMonitor</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PointMonitorEventArgs <span style="color: #1f377f;">e</span>)
{
_splitPoint = GetSplitPoint(e.Context.RawPoint);
SetLines();
SetLengthTexts();
UpdateTransientDrawables();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateTransientDrawables</span>(Line <span style="color: #1f377f;">line</span>)
{
_incrementPoints = <span style="color: blue;">new</span> List<DBPoint>();
<span style="color: #066555;">// Get center point</span>
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = line.GetPointAtDist(line.Length / 2.0);
<span style="color: blue;">var</span> <span style="color: #1f377f;">dbPt</span> = <span style="color: blue;">new</span> DBPoint(pt);
dbPt.ColorIndex = 1;
_incrementPoints.Add(dbPt);
_splitPoint = pt;
<span style="color: #066555;">// Add DBPoints on first half of the line</span>
<span style="color: blue;">var</span> <span style="color: #1f377f;">l</span> = line.Length / 2.0 - _increment;
<span style="color: #8f08c4;">while</span> (l > 0)
{
pt = line.GetPointAtDist(l);
dbPt = <span style="color: blue;">new</span> DBPoint(pt);
dbPt.ColorIndex = 2;
_incrementPoints.Insert(0, dbPt);
l -= _increment;
}
<span style="color: #066555;">// Add DBPoint on second half of the line</span>
l = line.Length / 2.0 + _increment;
<span style="color: #8f08c4;">while</span> (l < line.Length)
{
pt = line.GetPointAtDist(l);
dbPt = <span style="color: blue;">new</span> DBPoint(pt);
dbPt.ColorIndex = 2;
_incrementPoints.Add(dbPt);
l += _increment;
}
<span style="color: #066555;">// add 2 lines</span>
_line1 = <span style="color: blue;">new</span> Line(line.StartPoint, _splitPoint);
_line1.ColorIndex = 5;
_line2=<span style="color: blue;">new</span> Line(line.EndPoint, _splitPoint);
_line2.ColorIndex = 6;
<span style="color: #066555;">// add 2 DBTexts</span>
_text1 = CreateLineLengthText(_line1);
_text2 = CreateLineLengthText(_line2);
}
<span style="color: blue;">private</span> DBText <span style="color: #74531f;">CreateLineLengthText</span>(Line <span style="color: #1f377f;">line</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">txt</span> = <span style="color: blue;">new</span> DBText();
txt.TextString = line.Length.ToString(<span style="color: #a31515;">"######0.0"</span>);
txt.ColorIndex = 7;
txt.Height = _textHeight;
txt.Rotation = line.Angle;
txt.Position = line.GetPointAtDist(line.Length / 2.0);
txt.Justify = AttachmentPoint.BottomCenter;
txt.AdjustAlignment(_dwg.Database);
<span style="color: #8f08c4;">if</span> (txt.Rotation >= Math.PI && txt.Rotation < Math.PI * 2.0)
{
txt.TransformBy(
Matrix3d.Rotation(Math.PI, Vector3d.ZAxis, txt.AlignmentPoint));
}
<span style="color: #8f08c4;">return</span> txt;
}
<span style="color: blue;">private</span> Point3d <span style="color: #74531f;">GetSplitPoint</span>(Point3d <span style="color: #1f377f;">movePoint</span>)
{
<span style="color: #066555;">// Reset DBPoints' color</span>
<span style="color: #8f08c4;">for</span> (<span style="color: blue;">int</span> <span style="color: #1f377f;">i</span>=0; i<_incrementPoints.Count; i++)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span>= _incrementPoints[i];
<span style="color: #8f08c4;">if</span> (i == (_incrementPoints.Count - 1) / 2)
{
pt.ColorIndex = 1;
}
<span style="color: #8f08c4;">else</span>
{
pt.ColorIndex = 2;
}
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">dbPt</span> = (<span style="color: blue;">from</span> p <span style="color: blue;">in</span> _incrementPoints
<span style="color: blue;">orderby</span> p.Position.DistanceTo(movePoint) <span style="color: blue;">ascending</span>
<span style="color: blue;">select</span> p).First();
dbPt.ColorIndex = 4;
<span style="color: #8f08c4;">return</span> dbPt.Position;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">SetLines</span>()
{
_line1.EndPoint = _splitPoint;
_line2.EndPoint = _splitPoint;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">SetLengthTexts</span>()
{
_text1.TextString= _line1.Length.ToString(<span style="color: #a31515;">"######0.0"</span>);
_text1.Position = _line1.GetPointAtDist(_line1.Length / 2.0);
_text2.TextString = _line2.Length.ToString(<span style="color: #a31515;">"######0.0"</span>);
_text2.Position = _line2.GetPointAtDist(_line2.Length / 2.0);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddTransientDrawables</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">dbPt</span> <span style="color: #8f08c4;">in</span> _incrementPoints)
{
_tsManager.AddTransient(
dbPt, TransientDrawingMode.DirectTopmost, 128, <span style="color: blue;">new</span> IntegerCollection());
}
_tsManager.AddTransient(
_line1, TransientDrawingMode.DirectShortTerm, 128, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.AddTransient(
_line2, TransientDrawingMode.DirectShortTerm, 128, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.AddTransient(
_text1, TransientDrawingMode.DirectShortTerm, 128, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.AddTransient(
_text2, TransientDrawingMode.DirectShortTerm, 128, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateTransientDrawables</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">dbPt</span> <span style="color: #8f08c4;">in</span> _incrementPoints)
{
_tsManager.UpdateTransient(dbPt, <span style="color: blue;">new</span> IntegerCollection());
}
_tsManager.UpdateTransient(_line1, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.UpdateTransient(_line2, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.UpdateTransient(_text1, <span style="color: blue;">new</span> IntegerCollection());
_tsManager.UpdateTransient(_text2, <span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearTransientDrawables</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">dbPt</span> <span style="color: #8f08c4;">in</span> _incrementPoints)
{
<span style="color: #8f08c4;">if</span> (!dbPt.IsDisposed)
{
_tsManager.EraseTransient(dbPt, <span style="color: blue;">new</span> IntegerCollection());
dbPt.Dispose();
}
}
_incrementPoints.Clear();
<span style="color: #8f08c4;">if</span> (_line1 != <span style="color: blue;">null</span> && !_line1.IsDisposed)
{
_tsManager.EraseTransient(_line1, <span style="color: blue;">new</span> IntegerCollection());
_line1.Dispose();
}
<span style="color: #8f08c4;">if</span> (_line2 != <span style="color: blue;">null</span> && !_line2.IsDisposed)
{
_tsManager.EraseTransient(_line2, <span style="color: blue;">new</span> IntegerCollection());
_line2.Dispose();
}
<span style="color: #8f08c4;">if</span> (_text1 != <span style="color: blue;">null</span> && !_text1.IsDisposed)
{
_tsManager.EraseTransient(_text1, <span style="color: blue;">new</span> IntegerCollection());
_text1.Dispose();
}
<span style="color: #8f08c4;">if</span> (_text2 != <span style="color: blue;">null</span> && !_text2.IsDisposed)
{
_tsManager.EraseTransient(_text2, <span style="color: blue;">new</span> IntegerCollection());
_text2.Dispose();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">ChangeIncrement</span>(<span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">increment</span>)
{
increment = _increment;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptDoubleOptions(
<span style="color: #a31515;">"\nEnter increment value:"</span>);
opt.AllowZero = <span style="color: blue;">false</span>;
opt.AllowNegative = <span style="color: blue;">false</span>;
opt.DefaultValue = _increment;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span>=_ed.GetDouble(opt);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
increment = res.Value;
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre></div><div><br /></div><div>The command method that runs the <i>LineSpliter</i>:</div><div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(DragToSplitLine.MyCommands))]
<span style="color: blue;">namespace</span> DragToSplitLine
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"SplitLine"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(<span style="color: #a31515;">"\nSelect a line:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid selection: must be a line."</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Line), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status!= PromptStatus.OK)
{
ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
Point3d? <span style="color: #1f377f;">splitPoint</span> = <span style="color: blue;">null</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">splitter</span> = <span style="color: blue;">new</span> LineSplitter(dwg, res.ObjectId))
{
splitPoint = splitter.DragSplitter();
}
<span style="color: #8f08c4;">if</span> (splitPoint.HasValue)
{
<span style="color: #066555;">// Do whatever is needed to the selected line</span>
ed.WriteMessage(<span style="color: #a31515;">$"\nSplit Point: </span>{splitPoint.Value}<span style="color: #a31515;">\n"</span>);
}
<span style="color: #8f08c4;">else</span>
{
ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
}
}
}
}</pre></div><p></p><p><span style="font-size: medium;"><b>The Video Clip Showing The Code Running Effect:</b></span></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dywMnEPrNjv7nCz9zCof-3a0pnB-MypkeMd268mYXC09KJrXAUczrIrAY5ot83aAiMU54uM-dNj5nthF2qeRw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p><b><span style="font-size: medium;">Point of Interests</span></b></p><p></p><ul style="text-align: left;"><li>As I mentioned the article is only meant to show the techniques to adding visual aids for the simply task of selecting point, thus I choose to target a Line entity for simplicity. It can be easily enhanced to target other types of entity (Arc, Polyline, or in general Curve). One only needs to show the potential split points in the same way showed here, and then use Curve.GetSplitCurves() to generate 2 visual curves</li><li>The length DBText entities could have been displayed more appropriately by moving it above the split visual segments</li></ul><p></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-87824107013948821192023-06-21T08:10:00.000-07:002023-06-21T08:10:45.463-07:00Auto-Rotating Annotating Entities in Viewport with DrawableOverrule<p>When annotating entities (Text, MText, or Block with Attributes) are used in ModelSpace as if they are labels, they often are made horizonal in terms of their textual presentation (or all with certain rotation, for that matter). However, CAD users would face an annoying issue, if the drawing contents have to be presented in various layout viewports, which may have different twist angles, depending how the drawing contents in the ModelSpace is to be presented in the viewport. CAD users may have to spent time to rotate the annotating entities that visible in a viewport, so that they appear in the viewport as horizontal (or vertical). </p><p>In the past, I had written custom command at my work to make label-like entities being rotated to horizontal/vertical based on which viewport they appeared. While this worked OK in many cases, but it would obviously not work well, if an annotating entity was visible in multiple viewports, which may have different twist angle.</p><p>AutoCAD Civil 3D users know that C3D labels automatically appear horizontally in layout viewports, regardless their twist angles. So, can we achieve the same or similar effect with plain AutoCAD (e.g. making annotating entities - DBText, MText, BlockReference with Attributes - to appear always horizontally in layout viewports)? The answer is "yes". This article demonstrates how to use DrawableOrverule to do it.</p><p>A custom DrawableOverrule overrides either its WorldDraw(), or ViewportDraw(), or both, to make the target entity visually different from its built-in appearance. In most cases overridden WorldDraw() is used. For example, we can make a curve (Line, Polyline...) looks like a 3D pipe, a DBPoint looks like a ball. Because of the topic of this article, while the main focus is to override ViewportDraw() method, so that the annotating entities would be appeared horizontally in layout viewports, the WorldDraw() must(!) also be overridden (I placed comments in the code). The code is following.</p><p>Class <i>LabelDrawableOverrule</i>:</p><p></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">namespace</span> LabelTextDrawableOverrule
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">LabelDrawableOverrule</span> : DrawableOverrule
{
<span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> BLOCK_NAME = <span style="color: #a31515;">"TESTLABEL"</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> LabelDrawableOverrule _instance;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _isEnabled = <span style="color: blue;">false</span>;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _originalOverruling = <span style="color: blue;">false</span>;
<span style="color: #5b5b5b;">#region</span> public properties
<span style="color: blue;">public</span> <span style="color: blue;">static</span> LabelDrawableOverrule Instance
{
<span style="color: blue;">get</span>
{
<span style="color: #8f08c4;">if</span> (_instance == <span style="color: blue;">null</span>)
{
_instance = <span style="color: blue;">new</span> LabelDrawableOverrule();
}
<span style="color: #8f08c4;">return</span> _instance;
}
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> IsEnabled=>_isEnabled;
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunOverrule</span>()
{
<span style="color: #8f08c4;">if</span> (!_isEnabled)
{
EnableOverrule();
}
<span style="color: #8f08c4;">else</span>
{
DisableOverrule();
}
Application.DocumentManager.MdiActiveDocument.Editor.Regen();
}
<span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsApplicable</span>(RXObject <span style="color: #1f377f;">overruledSubject</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = overruledSubject <span style="color: blue;">as</span> BlockReference;
<span style="color: #8f08c4;">if</span> (blk!=<span style="color: blue;">null</span> && blk.Name.ToUpper()==BLOCK_NAME)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> It is important to override WorldDraw() method and</span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> bypass calling base.WorldDraw() and return false</span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"></</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">param</span> <span style="color: #5b5b5b;">name</span><span style="color: #5b5b5b;">=</span><span style="color: #5b5b5b;">"</span>drawable<span style="color: #5b5b5b;">"</span><span style="color: #5b5b5b;">></</span><span style="color: #5b5b5b;">param</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">param</span> <span style="color: #5b5b5b;">name</span><span style="color: #5b5b5b;">=</span><span style="color: #5b5b5b;">"</span>wd<span style="color: #5b5b5b;">"</span><span style="color: #5b5b5b;">></</span><span style="color: #5b5b5b;">param</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">returns</span><span style="color: #5b5b5b;">></</span><span style="color: #5b5b5b;">returns</span><span style="color: #5b5b5b;">></span>
<span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">WorldDraw</span>(Drawable <span style="color: #1f377f;">drawable</span>, WorldDraw <span style="color: #1f377f;">wd</span>)
{
<span style="color: #066555;">//base.WorldDraw(drawable, wd);</span>
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
<span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ViewportDraw</span>(Drawable <span style="color: #1f377f;">drawable</span>, ViewportDraw <span style="color: #1f377f;">vd</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = drawable <span style="color: blue;">as</span> BlockReference; <span style="color: #066555;">// because this Overrule targets BlockReference</span>
<span style="color: #8f08c4;">if</span> (ent == <span style="color: blue;">null</span>) <span style="color: #8f08c4;">return</span>;
<span style="color: #8f08c4;">if</span> (vd.ViewportObjectId.IsNull)
{
<span style="color: #066555;">// Rotating overruled entity in ModelSpace to horizontal</span>
<span style="color: #8f08c4;">if</span> (ent.Rotation != 0.0)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">clone</span> = ent.Clone() <span style="color: blue;">as</span> BlockReference)
{
clone.TransformBy(
Matrix3d.Rotation(-ent.Rotation, Vector3d.ZAxis, ent.Position));
<span style="color: blue;">base</span>.ViewportDraw(clone, vd);
<span style="color: #8f08c4;">return</span>;
}
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">if</span> (IsLabelBlockVisibleFromViewport(vd.ViewportObjectId, ent, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">tweestAngle</span>))
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">clone</span> = ent.Clone() <span style="color: blue;">as</span> BlockReference)
{
clone.TransformBy(Matrix3d.Rotation(
-(tweestAngle + ent.Rotation), Vector3d.ZAxis, ent.Position));
<span style="color: blue;">base</span>.ViewportDraw(clone, vd);
<span style="color: #8f08c4;">return</span>;
}
}
}
<span style="color: blue;">base</span>.ViewportDraw(drawable, vd);
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">EnableOverrule</span>()
{
_originalOverruling = Overrule.Overruling;
AddOverrule(RXClass.GetClass(<span style="color: blue;">typeof</span>(BlockReference)), <span style="color: blue;">this</span>, <span style="color: blue;">false</span>);
SetCustomFilter();
_isEnabled = <span style="color: blue;">true</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DisableOverrule</span>()
{
RemoveOverrule(RXClass.GetClass(<span style="color: blue;">typeof</span>(BlockReference)), <span style="color: blue;">this</span>);
_isEnabled = <span style="color: blue;">false</span>;
Overrule.Overruling=_originalOverruling;
}
<span style="color: #066555;">// for simplicity, I consider the label block is visible in the viewport</span>
<span style="color: #066555;">// if its insertion point is inside the viewport boundary projected to ModelSpace</span>
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsLabelBlockVisibleFromViewport</span>(
ObjectId <span style="color: #1f377f;">vportId</span>, BlockReference <span style="color: #1f377f;">labelBlock</span>, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">angle</span>)
{
angle = 0.0;
<span style="color: blue;">var</span> <span style="color: #1f377f;">isInside</span> = <span style="color: blue;">false</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span>=vportId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">vport</span> = (CadDb.Viewport)tran.GetObject(vportId, OpenMode.ForRead);
angle = vport.TwistAngle;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">vportBoundaryInModel</span> = GetViewportBoundaryInModelSpace(vport, tran))
{
isInside =
CadHelper.IsPointInside(labelBlock.Position, vportBoundaryInModel);
}
tran.Commit();
}
<span style="color: #8f08c4;">return</span> isInside;
}
<span style="color: blue;">private</span> CadDb.Polyline <span style="color: #74531f;">GetViewportBoundaryInModelSpace</span>(
CadDb.Viewport <span style="color: #1f377f;">vport</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
CadDb.Polyline <span style="color: #1f377f;">poly</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">mt</span> = CadHelper.PaperToModel(vport);
<span style="color: #8f08c4;">if</span> (!vport.NonRectClipEntityId.IsNull && vport.NonRectClipOn)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">clipPoly</span> = (CadDb.Polyline)tran.GetObject(
vport.NonRectClipEntityId, OpenMode.ForRead);
poly = clipPoly.Clone() <span style="color: blue;">as</span> CadDb.Polyline;
}
<span style="color: #8f08c4;">else</span>
{
poly = GetViewportBoundary(vport, mt);
}
<span style="color: #8f08c4;">return</span> poly;
}
<span style="color: blue;">private</span> CadDb.Polyline <span style="color: #74531f;">GetViewportBoundary</span>(
CadDb.Viewport <span style="color: #1f377f;">vport</span>, Matrix3d <span style="color: #1f377f;">paperToModelTransform</span>)
{
CadDb.Polyline <span style="color: #1f377f;">poly</span> = <span style="color: blue;">new</span> CadDb.Polyline();
Extents3d <span style="color: #1f377f;">ext</span> = vport.GeometricExtents;
Point3d <span style="color: #1f377f;">pt</span>;
pt=(<span style="color: blue;">new</span> Point3d(ext.MinPoint.X, ext.MinPoint.Y, 0.0)).
TransformBy(paperToModelTransform);
poly.AddVertexAt(0, <span style="color: blue;">new</span> Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
pt = (<span style="color: blue;">new</span> Point3d(ext.MinPoint.X, ext.MaxPoint.Y, 0.0)).
TransformBy(paperToModelTransform);
poly.AddVertexAt(1, <span style="color: blue;">new</span> Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
pt = (<span style="color: blue;">new</span> Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, 0.0)).
TransformBy(paperToModelTransform);
poly.AddVertexAt(2, <span style="color: blue;">new</span> Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
pt = (<span style="color: blue;">new</span> Point3d(ext.MaxPoint.X, ext.MinPoint.Y, 0.0)).
TransformBy(paperToModelTransform);
poly.AddVertexAt(3, <span style="color: blue;">new</span> Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
poly.Closed = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">return</span> poly;
}
<span style="color: blue;">private</span> CadDb.Polyline <span style="color: #74531f;">GetNonRectClipBoundary</span>(
CadDb.Polyline <span style="color: #1f377f;">polyline</span>, Matrix3d <span style="color: #1f377f;">paperToModelTransform</span>)
{
Point3dCollection <span style="color: #1f377f;">points</span> = <span style="color: blue;">new</span> Point3dCollection();
<span style="color: #8f08c4;">for</span> (<span style="color: blue;">int</span> <span style="color: #1f377f;">i</span> = 0; i<polyline.NumberOfVertices; i++)
{
points.Add(polyline.GetPoint3dAt(i).TransformBy(paperToModelTransform));
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = <span style="color: blue;">new</span> CadDb.Polyline();
<span style="color: blue;">var</span> <span style="color: #1f377f;">n</span> = 0;
<span style="color: #8f08c4;">foreach</span> (Point3d <span style="color: #1f377f;">p</span> <span style="color: #8f08c4;">in</span> points)
{
poly.AddVertexAt(n, <span style="color: blue;">new</span> Point2d(p.X, p.Y), 0.0, 0.0, 0.0);
n++;
}
poly.Closed = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">return</span> poly;
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><br /></pre><p>Class <i>CadHelper</i>, in which I referenced <i>AcMPolygonMgd.dll</i> for testing if a point is inside of closed polyline:</p><p></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> LabelTextDrawableOverrule
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">CadHelper</span>
{
<span style="color: #5b5b5b;">#region</span>
<span style="color: #066555;">//**********************************************************************</span>
<span style="color: #066555;">//Create coordinate transform matrix</span>
<span style="color: #066555;">//between modelspace and paperspace viewport</span>
<span style="color: #066555;">//The code is borrowed from</span>
<span style="color: #066555;">//http://www.theswamp.org/index.php?topic=34590.msg398539#msg398539</span>
<span style="color: #066555;">//*********************************************************************</span>
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Matrix3d <span style="color: #74531f;">PaperToModel</span>(Viewport <span style="color: #1f377f;">vp</span>)
{
Matrix3d <span style="color: #1f377f;">mx</span> = ModelToPaper(vp);
<span style="color: #8f08c4;">return</span> mx.Inverse();
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Matrix3d <span style="color: #74531f;">ModelToPaper</span>(Viewport <span style="color: #1f377f;">vp</span>)
{
Vector3d <span style="color: #1f377f;">vd</span> = vp.ViewDirection;
Point3d <span style="color: #1f377f;">vc</span> = <span style="color: blue;">new</span> Point3d(vp.ViewCenter.X, vp.ViewCenter.Y, 0);
Point3d <span style="color: #1f377f;">vt</span> = vp.ViewTarget;
Point3d <span style="color: #1f377f;">cp</span> = vp.CenterPoint;
<span style="color: blue;">double</span> <span style="color: #1f377f;">ta</span> = -vp.TwistAngle;
<span style="color: blue;">double</span> <span style="color: #1f377f;">vh</span> = vp.ViewHeight;
<span style="color: blue;">double</span> <span style="color: #1f377f;">height</span> = vp.Height;
<span style="color: blue;">double</span> <span style="color: #1f377f;">width</span> = vp.Width;
<span style="color: blue;">double</span> <span style="color: #1f377f;">scale</span> = vh / height;
<span style="color: blue;">double</span> <span style="color: #1f377f;">lensLength</span> = vp.LensLength;
Vector3d <span style="color: #1f377f;">zaxis</span> = vd.GetNormal();
Vector3d <span style="color: #1f377f;">xaxis</span> = Vector3d.ZAxis.CrossProduct(vd);
Vector3d <span style="color: #1f377f;">yaxis</span>;
<span style="color: #8f08c4;">if</span> (!xaxis.IsZeroLength())
{
xaxis = xaxis.GetNormal();
yaxis = zaxis.CrossProduct(xaxis);
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (zaxis.Z < 0)
{
xaxis = Vector3d.XAxis * -1;
yaxis = Vector3d.YAxis;
zaxis = Vector3d.ZAxis * -1;
}
<span style="color: #8f08c4;">else</span>
{
xaxis = Vector3d.XAxis;
yaxis = Vector3d.YAxis;
zaxis = Vector3d.ZAxis;
}
Matrix3d <span style="color: #1f377f;">pcsToDCS</span> = Matrix3d.Displacement(Point3d.Origin - cp);
pcsToDCS = pcsToDCS * Matrix3d.Scaling(scale, cp);
Matrix3d <span style="color: #1f377f;">dcsToWcs</span> = Matrix3d.Displacement(vc - Point3d.Origin);
Matrix3d <span style="color: #1f377f;">mxCoords</span> = Matrix3d.AlignCoordinateSystem(
Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis,
Vector3d.ZAxis, Point3d.Origin,
xaxis, yaxis, zaxis);
dcsToWcs = mxCoords * dcsToWcs;
dcsToWcs = Matrix3d.Displacement(vt - Point3d.Origin) * dcsToWcs;
dcsToWcs = Matrix3d.Rotation(ta, zaxis, vt) * dcsToWcs;
Matrix3d <span style="color: #1f377f;">perspectiveMx</span> = Matrix3d.Identity;
<span style="color: #8f08c4;">if</span> (vp.PerspectiveOn)
{
<span style="color: blue;">double</span> <span style="color: #1f377f;">vSize</span> = vh;
<span style="color: blue;">double</span> <span style="color: #1f377f;">aspectRatio</span> = width / height;
<span style="color: blue;">double</span> <span style="color: #1f377f;">adjustFactor</span> = 1.0 / 42.0;
<span style="color: blue;">double</span> <span style="color: #1f377f;">adjstLenLgth</span> = vSize * lensLength *
Math.Sqrt(1.0 + aspectRatio * aspectRatio) * adjustFactor;
<span style="color: blue;">double</span> <span style="color: #1f377f;">iDist</span> = vd.Length;
<span style="color: blue;">double</span> <span style="color: #1f377f;">lensDist</span> = iDist - adjstLenLgth;
<span style="color: blue;">double</span>[] <span style="color: #1f377f;">dataAry</span> = <span style="color: blue;">new</span> <span style="color: blue;">double</span>[]
{
1,0,0,0,0,1,0,0,0,0,
(adjstLenLgth - lensDist) / adjstLenLgth,
lensDist * (iDist - adjstLenLgth) / adjstLenLgth,
0,0,-1.0 / adjstLenLgth,iDist / adjstLenLgth
};
perspectiveMx = <span style="color: blue;">new</span> Matrix3d(dataAry);
}
Matrix3d <span style="color: #1f377f;">finalMx</span> =
pcsToDCS.Inverse() *perspectiveMx * dcsToWcs.Inverse();
<span style="color: #8f08c4;">return</span> finalMx;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsPointInside</span>(Point3d <span style="color: #1f377f;">point</span>, Polyline <span style="color: #1f377f;">boundary</span>, <span style="color: blue;">bool</span> <span style="color: #1f377f;">onEdgeAsInside</span>=<span style="color: blue;">true</span>)
{
<span style="color: blue;">using</span> (MPolygon <span style="color: #1f377f;">mPolygon</span> = MakeMPolygon(boundary))
{
<span style="color: #8f08c4;">for</span> (<span style="color: blue;">int</span> <span style="color: #1f377f;">i</span> = 0; i < mPolygon.NumMPolygonLoops; i++)
{
<span style="color: #8f08c4;">if</span> (mPolygon.IsPointOnLoopBoundary(point, i, Tolerance.Global.EqualPoint))
{
<span style="color: #8f08c4;">if</span> (onEdgeAsInside) <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
}
<span style="color: #8f08c4;">if</span> (mPolygon.IsPointInsideMPolygon(point, Tolerance.Global.EqualPoint).Count == 1)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
}
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">static</span> MPolygon <span style="color: #74531f;">MakeMPolygon</span>(Polyline <span style="color: #1f377f;">pline</span>)
{
MPolygon <span style="color: #1f377f;">mPolygon</span> = <span style="color: blue;">new</span> MPolygon();
mPolygon.AppendLoopFromBoundary(pline, <span style="color: blue;">false</span>, Tolerance.Global.EqualPoint);
mPolygon.Elevation = pline.Elevation;
mPolygon.Normal = pline.Normal;
<span style="color: #8f08c4;">return</span> mPolygon;
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre><p></p><p>And finally, the CommandClass:</p><p></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(LabelTextDrawableOverrule.MyCommands))]
<span style="color: blue;">namespace</span> LabelTextDrawableOverrule
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"LabelOverrule"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunLabelOverrule</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
LabelDrawableOverrule.Instance.RunOverrule();
<span style="color: blue;">var</span> <span style="color: #1f377f;">msg</span> = LabelDrawableOverrule.Instance.IsEnabled ?
<span style="color: #a31515;">"LabelDrawableOverrule is enabled."</span> :
<span style="color: #a31515;">"LabelDrawableOverrule is disabled."</span>;
ed.WriteMessage(<span style="color: #a31515;">$"\n</span>{msg}<span style="color: #a31515;">\n"</span>);
}
}
}</pre><p></p><p>Points of interest:</p><p></p><ol style="text-align: left;"><li>The ViewportDraw argument of the ViewportDraw() method provided ObjectId of the layout viewport, so that we can use to find out the Viewport.TwistAngle and also find out if the overruled entity is visible in the viewport. In my code here, for simplicity, I assumed the label block entity is visible in the viewport if its insertion point is inside of the viewport boundary projected in the ModelSpace.</li><li>We'll need to make sure WorldDraw() method is overridden and do not call base.WorldDraw() inside. We also must let it return false, so that the ViewportDraw() method is called by the Overrule.</li><li>I used a copy of the overruled entity and rotated it as needed and pass it to base.ViewportDraw() method. That is, the overruled entity itself remained unchanged (e.g. the block's Rotation property was unchanged), only its image in the viewport got rotated (to be horizonal). I could have rotated the overruled entity, but if the entities is visible in multiple viewports with different twist angles, it would end up with constant changing rotations.</li></ol><div>See the video clip below showing the code running result:</div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyVAzNf76XVtaUyAbWtAoRXga4YdTfjED4E6-gIOJKy4ovfpRDuWAV92qFRV3OOlxrK5Ch-v_dn4pZeHIy_4g' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><div><br /></div><div><br /></div><p></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-64137181212441715342023-03-21T17:51:00.000-07:002023-03-21T17:51:07.444-07:00Using Custom Jig To Transform A Group of Entities - Again<p> In my <a href="https://drive-cad-with-code.blogspot.com/2023/03/using-custom-jig-to-transform-group-of.html" target="_blank">previous post</a> I used Transient graphics in conjunction with Editor.PointMonitor handling to create a "DrawJig" for transforming a group of entities and mentioned that I might post code for a custom jig that is derived from DrawJig class. </p><p>In the meantime, the original question poster also asked <a href="https://forums.autodesk.com/t5/net/how-to-jig-the-multiple-entities-not-full-entity-part-portion-of/td-p/11822698" target="_blank">question again</a>, regarding my previous post, and wanted to know how to do change the connected polyline when one of its vertex is at the block's insertion point. While it is slightly out of the topic of the discussion (using Jig to change/transform a group of entities), it is really not that difficult to solve, whether the polyline's start/end point, vertex, or event anywhere, is at the block's insertion point.</p><p>Now that I now write another solution to the original question, by using custom DrawJig derived custom Jig, I decide to show the code in action with a polyline connected to the block's insertion point in different way: start/end point connected, a vertex point connected, or any point along the polyline connected. Also, for simplicity, I only do it with Polyline with straight segments.</p><p>Again, the code is rather easy to read/follow. So, no need to extra explanation, I think.</p><p>The class <i>MyBlockDrawJig</i>:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">namespace</span> CurveConnectedBlockJig
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyBlockDrawJig</span> : DrawJig, IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">class</span> <span style="color: #066555;">EndConnectedPolyline</span>
{
<span style="color: blue;">public</span> CadDb.Polyline Polyline { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> AtStart { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
}
<span style="color: blue;">private</span> <span style="color: blue;">class</span> <span style="color: #066555;">MidConnectedPolyline</span>
{
<span style="color: blue;">public</span> CadDb.Polyline Polyline { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
<span style="color: blue;">public</span> (<span style="color: blue;">int</span> index, Point3d vertex) VertexBefore { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
<span style="color: blue;">public</span> (<span style="color: blue;">int</span> index, Point3d vertex) VertexAfter { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
}
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> ObjectId _blkId;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Database _db;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> Transaction _tran = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Point3d _basePoint;
<span style="color: blue;">private</span> Point3d _prevPoint;
<span style="color: blue;">private</span> Point3d _currPoint;
<span style="color: blue;">private</span> BlockReference _block = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> List<EndConnectedPolyline> _endConnectedPolys = <span style="color: blue;">new</span> List<EndConnectedPolyline>();
<span style="color: blue;">private</span> List<MidConnectedPolyline> _midConnectedPolys=<span style="color: blue;">new</span> List<MidConnectedPolyline>();
<span style="color: blue;">private</span> <span style="color: blue;">int</span> _addedVertexIndex = -1;
<span style="color: blue;">public</span> <span style="color: #066555;">MyBlockDrawJig</span>(Document <span style="color: #1f377f;">dwg</span>, ObjectId <span style="color: #1f377f;">blkId</span>)
{
_blkId = blkId;
_dwg = dwg;
_db = dwg.Database;
_ed = dwg.Editor;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Move</span>()
{
_tran = _db.TransactionManager.StartTransaction();
_block = (BlockReference)_tran.GetObject(_blkId, OpenMode.ForWrite);
_block.Highlight();
FindConnectedPolylines();
_basePoint = _block.Position;
_prevPoint = _block.Position;
_currPoint = _block.Position;
PromptStatus <span style="color: #1f377f;">status</span> = PromptStatus.Cancel;
<span style="color: #8f08c4;">try</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.Drag(<span style="color: blue;">this</span>);
status = res.Status;
}
<span style="color: #8f08c4;">finally</span>
{
_block.Unhighlight();
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _endConnectedPolys)
{
item.Polyline.Unhighlight();
}
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _midConnectedPolys)
{
item.Polyline.Unhighlight();
}
<span style="color: #8f08c4;">if</span> (status == PromptStatus.OK)
{
_tran.Commit();
}
<span style="color: #8f08c4;">else</span>
{
_tran.Abort();
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
<span style="color: #8f08c4;">if</span> (_tran != <span style="color: blue;">null</span>)
{
_tran.Dispose();
}
}
<span style="color: #5b5b5b;">#region</span> DrawJig implementing
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> SamplerStatus <span style="color: #74531f;">Sampler</span>(JigPrompts <span style="color: #1f377f;">prompts</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> JigPromptPointOptions(
<span style="color: #a31515;">"\nSelect position to move:"</span>);
opt.UseBasePoint = <span style="color: blue;">true</span>;
opt.BasePoint = _basePoint;
opt.Cursor = CursorType.RubberBand;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = prompts.AcquirePoint(opt);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: #8f08c4;">if</span> (res.Value!=_currPoint)
{
_block.TransformBy(
Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint)));
TransformEndConnected();
TransformMidConnected();
_prevPoint = _currPoint;
_currPoint = res.Value;
<span style="color: #8f08c4;">return</span> SamplerStatus.OK;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> SamplerStatus.NoChange;
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> SamplerStatus.Cancel;
}
}
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">WorldDraw</span>(WorldDraw <span style="color: #1f377f;">draw</span>)
{
draw.Geometry.Draw(_block);
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _endConnectedPolys)
{
draw.Geometry.Draw(item.Polyline);
}
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _midConnectedPolys)
{
draw.Geometry.Draw(item.Polyline);
}
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: #5b5b5b;">#region</span> private methods: collect jigging targets
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">FindConnectedPolylines</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">model</span> = (BlockTableRecord)_tran.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(_db), OpenMode.ForRead);
<span style="color: #8f08c4;">foreach</span> (ObjectId <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> model)
{
<span style="color: #8f08c4;">if</span> (id.ObjectClass.DxfName.ToUpper() != <span style="color: #a31515;">"LWPOLYLINE"</span>) <span style="color: #8f08c4;">continue</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span>=(CadDb.Polyline)_tran.GetObject(id, OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">endConnected</span> = IsEndConnected(poly);
<span style="color: #8f08c4;">if</span> (endConnected !=<span style="color: blue;">null</span>)
{
_endConnectedPolys.Add(endConnected);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">midConnected</span> = IsMidConnected(poly);
<span style="color: #8f08c4;">if</span> (midConnected != <span style="color: blue;">null</span>)
{
_midConnectedPolys.Add(midConnected);
}
}
}
}
<span style="color: blue;">private</span> EndConnectedPolyline <span style="color: #74531f;">IsEndConnected</span>(CadDb.Polyline <span style="color: #1f377f;">poly</span>)
{
EndConnectedPolyline <span style="color: #1f377f;">endConnected</span> = <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">if</span> (poly.StartPoint.Equals(_block.Position))
{
poly.UpgradeOpen();
endConnected = <span style="color: blue;">new</span> EndConnectedPolyline()
{
Polyline = poly,
AtStart = <span style="color: blue;">true</span>
};
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (poly.EndPoint.Equals(_block.Position))
{
poly.UpgradeOpen();
endConnected = <span style="color: blue;">new</span> EndConnectedPolyline()
{
Polyline = poly,
AtStart = <span style="color: blue;">false</span>
};
}
<span style="color: #8f08c4;">if</span> (endConnected!=<span style="color: blue;">null</span>) endConnected.Polyline.Highlight();
<span style="color: #8f08c4;">return</span> endConnected;
}
<span style="color: blue;">private</span> MidConnectedPolyline <span style="color: #74531f;">IsMidConnected</span>(CadDb.Polyline <span style="color: #1f377f;">poly</span>)
{
MidConnectedPolyline <span style="color: #1f377f;">midConnected</span> = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">geCurve</span> = poly.GetGeCurve();
<span style="color: #8f08c4;">if</span> (geCurve.IsOn(_block.Position))
{
poly.UpgradeOpen();
midConnected = <span style="color: blue;">new</span> MidConnectedPolyline()
{
Polyline = poly
};
<span style="color: blue;">var</span> <span style="color: #1f377f;">dist</span>=poly.GetDistAtPoint(_block.Position);
<span style="color: #8f08c4;">for</span> (<span style="color: blue;">int</span> <span style="color: #1f377f;">i</span>=1; i<poly.NumberOfVertices; i++)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = poly.GetPoint3dAt(i);
<span style="color: blue;">var</span> <span style="color: #1f377f;">l</span> = poly.GetDistAtPoint(pt);
<span style="color: #8f08c4;">if</span> (l >= dist)
{
midConnected.VertexBefore = (i - 1, poly.GetPoint3dAt(i - 1));
<span style="color: blue;">var</span> <span style="color: #1f377f;">diff</span> = Math.Abs(dist - l);
<span style="color: #8f08c4;">if</span> (diff <= Tolerance.Global.EqualPoint)
{
midConnected.VertexAfter = (i + 1, poly.GetPoint3dAt(i + 1));
}
<span style="color: #8f08c4;">else</span>
{
midConnected.VertexAfter = (i, pt);
}
<span style="color: #8f08c4;">break</span>;
}
}
}
<span style="color: #8f08c4;">if</span> (midConnected!=<span style="color: blue;">null</span>) midConnected.Polyline.Highlight();
<span style="color: #8f08c4;">return</span> midConnected;
}
<span style="color: #5b5b5b;">#endregion</span>
<span style="color: #5b5b5b;">#region</span> private methods: transform jigged entities
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">TransformEndConnected</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _endConnectedPolys)
{
<span style="color: #8f08c4;">if</span> (item.AtStart)
{
item.Polyline.SetPointAt(0, <span style="color: blue;">new</span> Point2d(_currPoint.X, _currPoint.Y));
}
<span style="color: #8f08c4;">else</span>
{
item.Polyline.SetPointAt(
item.Polyline.NumberOfVertices - 1,
<span style="color: blue;">new</span> Point2d(_currPoint.X, _currPoint.Y));
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">TransformMidConnected</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">item</span> <span style="color: #8f08c4;">in</span> _midConnectedPolys)
{
<span style="color: #8f08c4;">if</span> (item.VertexAfter.index-item.VertexBefore.index==1)
{
<span style="color: #8f08c4;">if</span> (_addedVertexIndex<0)
{
<span style="color: #066555;">// insert a new vertex on the straight segment</span>
_addedVertexIndex = item.VertexAfter.index;
item.Polyline.AddVertexAt(
_addedVertexIndex,
<span style="color: blue;">new</span> Point2d(_currPoint.X, _currPoint.Y), 0.0, 0.0, 0.0);
}
item.Polyline.SetPointAt(
_addedVertexIndex, <span style="color: blue;">new</span> Point2d(_currPoint.X, _currPoint.Y));
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">index</span> = item.VertexAfter.index - 1;
item.Polyline.SetPointAt(
index, <span style="color: blue;">new</span> Point2d(_currPoint.X, _currPoint.Y));
}
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre>
<p><br /></p><p>The CommandMethod is almost the same as before, except for instantiating a different jig class:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">[CommandMethod(<span style="color: #a31515;">"MoveBlk2"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunBlockDrawJig</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect curve-connected block:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"Invalid: must be a block:"</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(BlockReference), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">jig</span> = <span style="color: blue;">new</span> MyBlockDrawJig(dwg, res.ObjectId))
{
jig.Move();
}
}
ed.WriteMessage(<span style="color: #a31515;">"\n"</span>);
}</pre>
<p>See the video clip below:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyq2OF1lQyT2Nsq6LEE35FAfPdGecQUKNsu6JxESJW05f61yfKKM7TK0blobcEpgqZoPSTrZEgirR1vwtqOJA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-54272107671719176352023-03-20T19:00:00.000-07:002023-03-20T19:00:24.338-07:00Using Custom Jig To Transform A Group of EntitiesThis post is in response to <a href="https://forums.autodesk.com/t5/net/how-to-jig-the-multiple-entities-not-full-entity-part-portion-of/td-p/11822698" target="_blank">a question</a> asked in Autodesk's AutoCAD .NET API discussion forum. While in my reply I suggested it can be done easily by either creating a DrawJig or doing a jig-like code with the combination of Editor.PointMonitor and Transient graphics, I went ahead to write code with the latter approach. <div><br /></div><div>The code is rather straightforward and easy to follow, assuming there is a block reference with one or more curves (Line or Polyline) connecting to it (e.g. one of the end point of the Line/Polyline) at at the block's insertion point). The code is as following.</div><div><br /></div><div>The class <i>MyBlockJig</i>:</div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">namespace</span> CurveConnectedBlockJig
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyBlockJig</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">class</span> <span style="color: #066555;">ConnectedCurve</span>
{
<span style="color: blue;">public</span> ObjectId ConnectedCurveId { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
<span style="color: blue;">public</span> Drawable Ghost { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> AtStartPoint { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }
}
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Database _db;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> ObjectId _blkId;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> TransientManager _tsMng = TransientManager.CurrentTransientManager;
<span style="color: blue;">private</span> List<ConnectedCurve> _connectedCurves = <span style="color: blue;">new</span> List<ConnectedCurve>();
<span style="color: blue;">private</span> BlockReference _blkGhost = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Point3d _prevPoint;
<span style="color: blue;">private</span> Point3d _currPoint;
<span style="color: blue;">public</span> <span style="color: #066555;">MyBlockJig</span>(Document <span style="color: #1f377f;">dwg</span>, ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: #8f08c4;">if</span> (blkId.ObjectClass.DxfName.ToUpper()!=<span style="color: #a31515;">"INSERT"</span>)
{
<span style="color: #8f08c4;">throw</span> <span style="color: blue;">new</span> InvalidOperationException(
<span style="color: #a31515;">"Not a block reference!"</span>);
}
_blkId = blkId;
_dwg=dwg;
_db = _dwg.Database;
_ed= _dwg.Editor;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Move</span>()
{
FindConnectedCurves();
_prevPoint = _blkGhost.Position;
_currPoint = _blkGhost.Position;
<span style="color: #8f08c4;">try</span>
{
AddGhosts();
_ed.PointMonitor += Editor_PointMonitor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetPoint(<span style="color: #a31515;">"\nMove block to: "</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
UpdateEntities(res.Value);
}
}
<span style="color: #8f08c4;">finally</span>
{
_ed.PointMonitor -= Editor_PointMonitor;
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
ClearGhosts();
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: #066555;">// Assume any Line/Polyline with one of its end point is at the block's insertion point</span>
<span style="color: #066555;">// is considered connected to the block reference, thus the end segment of the </span>
<span style="color: #066555;">// curve would move with the block reference during the drag; Also, if the connected curve is</span>
<span style="color: #066555;">// Polyline, assume the connected segment is straight segment</span>
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">FindConnectedCurves</span>()
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">model</span> = (BlockTableRecord)tran.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(_db), OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkRef</span> = (BlockReference)tran.GetObject(_blkId, OpenMode.ForRead);
_blkGhost = blkRef.Clone() <span style="color: blue;">as</span> BlockReference;
<span style="color: blue;">var</span> <span style="color: #1f377f;">position</span> = blkRef.Position;
<span style="color: #8f08c4;">foreach</span> (ObjectId <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> model)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">curve</span> = tran.GetObject(id, OpenMode.ForRead) <span style="color: blue;">as</span> Curve;
<span style="color: #8f08c4;">if</span> (curve == <span style="color: blue;">null</span>) <span style="color: #8f08c4;">continue</span>;
<span style="color: #8f08c4;">if</span> (!(curve <span style="color: blue;">is</span> Line) && !(curve <span style="color: blue;">is</span> CadDb.Polyline)) <span style="color: #8f08c4;">continue</span>;
<span style="color: #8f08c4;">if</span> (IsConnected(position, curve.StartPoint, curve.EndPoint))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">connectedCurve</span> = GetConnectedCurve(curve, position);
_connectedCurves.Add(connectedCurve);
}
}
tran.Commit();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsConnected</span>(Point3d <span style="color: #1f377f;">position</span>, Point3d <span style="color: #1f377f;">point1</span>, Point3d <span style="color: #1f377f;">point2</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dist</span> = position.DistanceTo(point1);
<span style="color: #8f08c4;">if</span> (dist <= Tolerance.Global.EqualPoint) <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
dist = position.DistanceTo(point2);
<span style="color: #8f08c4;">if</span> (dist <= Tolerance.Global.EqualPoint) <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> ConnectedCurve <span style="color: #74531f;">GetConnectedCurve</span>(Curve <span style="color: #1f377f;">curve</span>, Point3d <span style="color: #1f377f;">position</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">connected</span>=<span style="color: blue;">new</span> ConnectedCurve() { ConnectedCurveId = curve.Id };
Drawable <span style="color: #1f377f;">drawable</span> = <span style="color: blue;">null</span>;
Line <span style="color: #1f377f;">line</span> = <span style="color: blue;">null</span>;
Point3d <span style="color: #1f377f;">pt</span>;
<span style="color: blue;">bool</span> <span style="color: #1f377f;">atStart</span>;
<span style="color: #8f08c4;">if</span> (position.Equals(curve.StartPoint))
{
<span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Line)
{
pt = ((Line)curve).EndPoint;
}
<span style="color: #8f08c4;">else</span>
{
pt = ((CadDb.Polyline)curve).GetPoint3dAt(1);
}
atStart = <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Line)
{
pt = ((Line)curve).StartPoint;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">lastIndex</span> = ((CadDb.Polyline)curve).NumberOfVertices - 1;
pt = ((CadDb.Polyline)curve).GetPoint3dAt(lastIndex - 1);
}
atStart = <span style="color: blue;">false</span>;
}
line = <span style="color: blue;">new</span> Line(position, pt);
line.ColorIndex = 2;
drawable = line;
connected.Ghost = drawable;
connected.AtStartPoint = atStart;
<span style="color: #8f08c4;">return</span> connected;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Editor_PointMonitor</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PointMonitorEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">point</span> = e.Context.RawPoint;
<span style="color: #8f08c4;">if</span> (point.Equals(_currPoint)) <span style="color: #8f08c4;">return</span>;
UpdateGhosts(point);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddGhosts</span>()
{
_tsMng.AddTransient(_blkGhost, TransientDrawingMode.Highlight, 128, <span style="color: blue;">new</span> IntegerCollection());
<span style="color: #8f08c4;">if</span> (_connectedCurves.Count > 0 )
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">connected</span> <span style="color: #8f08c4;">in</span> _connectedCurves)
{
_tsMng.AddTransient(connected.Ghost, TransientDrawingMode.Highlight, 128, <span style="color: blue;">new</span> IntegerCollection());
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateGhosts</span>(Point3d <span style="color: #1f377f;">point</span>)
{
_prevPoint = _currPoint;
_currPoint = point;
_blkGhost.TransformBy(Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint)));
_tsMng.UpdateTransient(_blkGhost, <span style="color: blue;">new</span> IntegerCollection());
<span style="color: #8f08c4;">if</span> ( _connectedCurves.Count > 0 )
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">connected</span> <span style="color: #8f08c4;">in</span> _connectedCurves)
{
((Line)connected.Ghost).StartPoint=point;
_tsMng.UpdateTransient(connected.Ghost, <span style="color: blue;">new</span> IntegerCollection());
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearGhosts</span>()
{
<span style="color: #8f08c4;">if</span> (_blkGhost!=<span style="color: blue;">null</span>)
{
_tsMng.EraseTransient(_blkGhost, <span style="color: blue;">new</span> IntegerCollection());
_blkGhost.Dispose();
}
<span style="color: #8f08c4;">if</span> (_connectedCurves.Count > 0)
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">connected</span> <span style="color: #8f08c4;">in</span> _connectedCurves)
{
<span style="color: #8f08c4;">if</span> (connected.Ghost != <span style="color: blue;">null</span>)
{
_tsMng.EraseTransient(connected.Ghost, <span style="color: blue;">new</span> IntegerCollection());
connected.Ghost.Dispose();
}
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateEntities</span>(Point3d <span style="color: #1f377f;">point</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = (BlockReference)tran.GetObject(_blkId, OpenMode.ForWrite);
blk.TransformBy(Matrix3d.Displacement(blk.Position.GetVectorTo(point)));
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">connected</span> <span style="color: #8f08c4;">in</span> _connectedCurves)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">curve</span> = (Curve)tran.GetObject(connected.ConnectedCurveId, OpenMode.ForWrite);
UpdateConnectedCurve(curve, point, connected.AtStartPoint);
}
tran.Commit();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateConnectedCurve</span>(Curve <span style="color: #1f377f;">curve</span>, Point3d <span style="color: #1f377f;">point</span>, <span style="color: blue;">bool</span> <span style="color: #1f377f;">changeStart</span>)
{
<span style="color: #8f08c4;">if</span> (curve <span style="color: blue;">is</span> Line)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = (Line)curve;
<span style="color: #8f08c4;">if</span> (changeStart)
{
line.StartPoint = point;
}
<span style="color: #8f08c4;">else</span>
{
line.EndPoint = point;
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = (CadDb.Polyline)curve;
<span style="color: #8f08c4;">if</span> (changeStart)
{
poly.SetPointAt(0, <span style="color: blue;">new</span> Point2d(point.X, point.Y));
}
<span style="color: #8f08c4;">else</span>
{
poly.SetPointAt(poly.NumberOfVertices-1, <span style="color: blue;">new</span> Point2d(point.X,point.Y));
}
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre>
<div><br /></div><div>Then the CommandMethod to run the code:</div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(CurveConnectedBlockJig.MyCommands))]
<span style="color: blue;">namespace</span> CurveConnectedBlockJig
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"MoveBlk"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect curve-connected block:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"Invalid: must be a block:"</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(BlockReference), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">jig</span> = <span style="color: blue;">new</span> MyBlockJig(dwg, res.ObjectId))
{
jig.Move();
}
}
ed.WriteMessage(<span style="color: #a31515;">"\n"</span>);
}
}
}
</pre>
<div><br /></div><div>See the video clip below for the code in action:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwjRdymheY78bAlAfGbGqdMzt-eSjeH94Q0zCnoDCxkd01gBsslt1eb3EhNHysxESY_lXw9pOf-8Iw5BUDNlw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><div>As I mentioned, it would be equally easy to just derive a custom DrawJig for the same visual effect. If I can find extra time, I might put it up (hint: MIGHT, 😃).</div><div><br /></div><div><br /></div>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-32190696980048637032023-02-23T07:28:00.000-08:002023-02-23T07:28:02.733-08:00Prompting Area by Moving Mouse Cursor To Trace Boundary<p>AutoCAD users who work in civil engineering/land development/surveying very often want a easy way to know the area value of a enclosed area, be it by a single closed polyline, or by a boundary formed by multiple entities. AutoCAD provides "AREA" command for user to pick points to get the area value of the boundary formed by the picked points. If the area is formed by a closed polyline, or a circle, user can also use "LIST" command to see the area value.</p><p>Readers who come to my blogs often probably know that I am a fan of using Transient graphics to help AutoCAD user with visual hints. Here is another example of using Transient graphics in conjunction with Editor.PointMonitor event handle to allow user to move mouse cursor freely to get visual feedback of an enclosed area.</p><p>The code is quite simple and straightforward, so, no need for the extra explanation.</p><p>This is the class that handles Editor.PointMinitor event and trace the boundary of possible area:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> AreaPrompting
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">AreaDetector</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> TransientManager _tsManager =
TransientManager.CurrentTransientManager;
<span style="color: blue;">private</span> MPolygon _mPoly = <span style="color: blue;">null</span>;
<span style="color: blue;">public</span> <span style="color: #066555;">AreaDetector</span>()
{
_dwg = Application.DocumentManager.MdiActiveDocument;
_ed = _dwg.Editor;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
ClearTransient();
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">PromptArea</span>()
{
<span style="color: #8f08c4;">try</span>
{
_ed.PointMonitor += Editor_PointMonitor;
_ed.GetPoint(<span style="color: #a31515;">"\nMove mouse cursor into an enclosed area:"</span>);
}
<span style="color: #8f08c4;">finally</span>
{
_ed.PointMonitor-= Editor_PointMonitor;
ClearTransient();
}
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Editor_PointMonitor</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PointMonitorEventArgs <span style="color: #1f377f;">e</span>)
{
ClearTransient();
<span style="color: blue;">var</span> <span style="color: #1f377f;">polygon</span> = DetectArea(e.Context.RawPoint);
<span style="color: #8f08c4;">if</span> (polygon!=<span style="color: blue;">null</span>)
{
_mPoly = polygon;
_tsManager.AddTransient(
_mPoly, TransientDrawingMode.Highlight, 128, <span style="color: blue;">new</span> IntegerCollection());
e.AppendToolTipText(<span style="color: #a31515;">$"AREA=</span>{_mPoly.Area}<span style="color: #a31515;">"</span>);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearTransient</span>()
{
<span style="color: #8f08c4;">if</span> (_mPoly!=<span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(
_mPoly, <span style="color: blue;">new</span> Autodesk.AutoCAD.Geometry.IntegerCollection());
_mPoly.Dispose();
}
}
<span style="color: blue;">private</span> MPolygon <span style="color: #74531f;">DetectArea</span>(Point3d <span style="color: #1f377f;">point</span>)
{
MPolygon <span style="color: #1f377f;">polygon</span> = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">boundaryEnts</span> = _ed.TraceBoundary(point, <span style="color: blue;">false</span>);
<span style="color: #8f08c4;">if</span>(boundaryEnts!=<span style="color: blue;">null</span> && boundaryEnts.Count>0)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">pline</span> = boundaryEnts[0] <span style="color: blue;">as</span> CadDb.Polyline;
<span style="color: #8f08c4;">if</span> (pline!=<span style="color: blue;">null</span>)
{
polygon = <span style="color: blue;">new</span> MPolygon();
polygon.ColorIndex = 9;
polygon.SetPattern(HatchPatternType.PreDefined, <span style="color: #a31515;">"SOLID"</span>);
polygon.AppendLoopFromBoundary(
pline, <span style="color: blue;">false</span>, Tolerance.Global.EqualVector);
}
}
<span style="color: #8f08c4;">return</span> polygon;
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><p><br /></p><p>This is the CommandMethod to execute the above code:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(AreaPrompting.MyCommands))]
<span style="color: blue;">namespace</span> AreaPrompting
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"GetArea"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">detector</span> = <span style="color: blue;">new</span> AreaDetector())
{
detector.PromptArea();
}
}
}
}</pre><p>See the video clip showing the code effect:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyoIg_fMtQ4zxBT9Jm_g0V3sESA13DNQllsQnvWkC6iYSup0m6aXDM75eNVWSvTnhtGLCnNX7SdpnVqRYLgTA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>One may notice one annoying thing: when Editor.TraceBoundary() method is called (many times while mouse moves), AutoCAD shows useless/meaningless messages at command line. I guess the message was used when Autodesk's engineers implemented and tested the TraceBoundary() method, and they forgot to clean them out after they had their job done!</p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-80402301325482106392022-12-24T12:34:00.001-08:002022-12-24T12:34:31.060-08:00Getting entity's Bounding Box in UCS<p>In AutoCAD, the bounding box of an entity is a minimal orthogonal rectangle that enclose the entity. With .NET API, one can get the bounding box of an entity with Entity.GeometricExtents property, which is an Extents3d structure. With COM API, one can call AcadEntity.GetBoundingBox(minPoint, maxPoint) to obtain the bounding box's minimum point (lower-left corner) and maximum point (the upper-right corner).</p><p>There was <a href="https://forums.autodesk.com/t5/vba/lisp-code-to-vba-code-conversion/td-p/11561281" target="_blank">an interesting question </a>asked recently in the AutoCAD VBA discussion forum. The OP wanted to get the bounding box of an entity as it appears in an UCS. Obviously, depending on the transformation of an UCS from WCS (World Coordinate System), the appeared bounding box in current UCS would be different from the Bounding Box calculated with .NET API/COM API, as shown in the pictures below.</p><p>The bounding box of an Hatch entity obtained through API (Entity.GeometricExtents, or AcadEntity.GetBoundingBox()), is shown in red:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sYLp3AjYQQi8OTh4VyU0bxyxAIpWMzLHB1lcjh1PuJoE0zamz0vJO-slYrCl_fTaZ9wv1Cv9_iZOaTcPujHXjgEW1VJ8_aSqJKujPjKRbkeavwAfgS4Si6-djb8ZB-8XPM_kEfOBg_3ZBgM-U6HJCsl7YEBGeP6Z2JRq9OMowkFtSc3Q7XPvNXgi/s1243/UcsToWcs_01.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="866" data-original-width="1243" height="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sYLp3AjYQQi8OTh4VyU0bxyxAIpWMzLHB1lcjh1PuJoE0zamz0vJO-slYrCl_fTaZ9wv1Cv9_iZOaTcPujHXjgEW1VJ8_aSqJKujPjKRbkeavwAfgS4Si6-djb8ZB-8XPM_kEfOBg_3ZBgM-U6HJCsl7YEBGeP6Z2JRq9OMowkFtSc3Q7XPvNXgi/w640-h446/UcsToWcs_01.png" width="640" /></a></div><div><br /></div>When an UCS is set current, the appeared bounding box (which was what the OP of aforementioned forum discussion wanted) is shown in yellow:<div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy-1v6RNdYLwxGtlsfrVQRHLBycNGXtjRKx_qyZMmEOeEpcLZFIkqDWMeUUyxvHt27Ie1YALCpWK3nOtknlXTDJ-CA06IqTNZLv_XTHlSiMcGXO7WeJYWkApwe7rYDlW6Rorm408gRKDEgk4knDTqZP6t8DPUggCAQr1gzoJU5RsBl1TslCYFiJ7mb/s1243/UcsToWcs_02.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="866" data-original-width="1243" height="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy-1v6RNdYLwxGtlsfrVQRHLBycNGXtjRKx_qyZMmEOeEpcLZFIkqDWMeUUyxvHt27Ie1YALCpWK3nOtknlXTDJ-CA06IqTNZLv_XTHlSiMcGXO7WeJYWkApwe7rYDlW6Rorm408gRKDEgk4knDTqZP6t8DPUggCAQr1gzoJU5RsBl1TslCYFiJ7mb/w640-h446/UcsToWcs_02.png" width="640" /></a></div><br /><div>So, the question here is how to find the corner coordinates of the UCS-appeared bound box in the current UCS.</div><div><br /></div><div>It turns out, with AutoCAD .NET API support, the calculation is rather easy: simply transform the entity with inverse Matrix3d of the current UCS and then get the transformed entity's GeometricExtents. Following code shows how to (only 2 lines of code in red does the trick!):</div>
<pre style="background: white; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: black;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(UcsToWcsTest.MyCommands))]
<span style="color: blue;">namespace</span> UcsToWcsTest
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"GetUcsBox"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">entId</span> = SelectEntity(ed);
<span style="color: #8f08c4;">if</span> (entId.IsNull)
{
ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*"</span>);
<span style="color: #8f08c4;">return</span>;
}
GetUcsBoundingBox(entId, ed);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> ObjectId <span style="color: #74531f;">SelectEntity</span>(Editor <span style="color: #1f377f;">ed</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(<span style="color: #a31515;">"\nSelect an entity:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
<span style="color: #8f08c4;">return</span> res.ObjectId;
<span style="color: #8f08c4;">else</span>
<span style="color: #8f08c4;">return</span> ObjectId.Null;
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">GetUcsBoundingBox</span>(ObjectId <span style="color: #1f377f;">entId</span>, Editor <span style="color: #1f377f;">ed</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">db</span> = entId.Database;
Extents3d <span style="color: #1f377f;">bound</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(
db.CurrentSpaceId, OpenMode.ForWrite);
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(entId, OpenMode.ForRead);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">entCopy</span> = ent.Clone() <span style="color: blue;">as</span> Entity)
{
</span><span style="color: red;"><b>entCopy.TransformBy(ed.CurrentUserCoordinateSystem.Inverse());
bound = entCopy.GeometricExtents;</b></span>
}
tran.Commit();
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = bound.MinPoint;
ed.WriteMessage(<span style="color: #a31515;">$"\nFirst corner: </span>{PointToString(pt)}<span style="color: #a31515;">"</span>);
pt = <span style="color: blue;">new</span> Point3d(bound.MinPoint.X,bound.MaxPoint.Y, bound.MinPoint.Z);
ed.WriteMessage(<span style="color: #a31515;">$"\nSecond corner: </span>{PointToString(pt)}<span style="color: #a31515;">"</span>);
pt = bound.MaxPoint;
ed.WriteMessage(<span style="color: #a31515;">$"\nThird corner: </span>{PointToString(pt)}<span style="color: #a31515;">"</span>);
pt = <span style="color: blue;">new</span> Point3d(bound.MaxPoint.X, bound.MinPoint.Y, bound.MinPoint.Z);
ed.WriteMessage(<span style="color: #a31515;">$"\nFourth corner: </span>{PointToString(pt)}<span style="color: #a31515;">"</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">string</span> <span style="color: #74531f;">PointToString</span>(Point3d <span style="color: #1f377f;">pt</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">x</span> = Converter.DistanceToString(pt.X);
<span style="color: blue;">var</span> <span style="color: #1f377f;">y</span>= Converter.DistanceToString(pt.Y);
<span style="color: #8f08c4;">return</span> <span style="color: #a31515;">$"(</span>{x}<span style="color: #a31515;">, </span>{y}<span style="color: #a31515;">)"</span>;
}
}
}</pre><div><p>Noted that I used a copy of the entity for the calculation, so that the real entity is not changed/transformed. Otherwise it would need to be transformed back.</p><p>See the video click below for the effect of the code execution.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyHBpZQt99Sfo7R3WEm9HUp8TBzGz6XdSpMfeRr6qArMdyTLYXeY0_RIqOAv47joCxQN8QhhwmGZ9IiRltkYA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><div class="separator" style="clear: both; text-align: center;"><br /></div><p>Since the aforementioned question was asked in AutoCAD VBA forum, then, how can we find out the appeared bounding box in UCS with VBA code? Well, unlike AutoCAD .NET API's support to solve this problem, it is a bit complicated with VBA/AutoCAD COM API, or at least, not straightforward. Because I have posted the VBA code in the VBA discussion forum, go to here to take a look, if interested in:</p><p><a href="https://forums.autodesk.com/t5/vba/lisp-code-to-vba-code-conversion/td-p/11561281" target="_blank">https://forums.autodesk.com/t5/vba/lisp-code-to-vba-code-conversion/td-p/11561281</a><br /></p><p> </p><p><br /></p></div></div>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-10462621891219802712022-11-17T07:59:00.002-08:002024-01-06T06:07:47.785-08:00Loop Operation with SendStringToExecute()<p>While programming an AutoCAD plugin, there are many occasions we use <i>Document.SendStringToExecute()</i> method to existing command, which can be built-in commands or custom commands (including lisp-defined commands). </p><p>Using <i>SendStringToExecute()</i> could save a lot of programming effort for particular AutoCAD operations when we know there are existing commands/lisp routines that does exactly the work, especially when the existing commands/lisp routines do some work that we programmers cannot easily do with our own code, and in many case, why reinventing the wheel anyway?</p><p>However, due to its "async" nature, it cannot be used in middle of a chunk of our code and expect the code following it can operate on the result produced from the <i>SendStringToExecute()</i> call. For example, if we need to call <i>SendStringToExecute()</i> repeatedly as loop in a custom operation of our code, following code simply does not work:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DoRepeatedWork</span>(IEnumerable<ObjectId> <span style="color: #1f377f;">entIds</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = Application.DocumentManager.MdiActiveDocument;
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">entId</span> <span style="color: #8f08c4;">in</span> entIds)
{
<span style="color: #066555;">// set PickFirst selections</span>
dwg.SendStringToExecute(<span style="color: #a31515;">"SomeCommand\n"</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
<span style="color: #066555;">// Do something with the result of SendStringToExecute()</span>
}
}</pre><p>The key to be able to repeatedly call <i>SendStringToExecute()</i> is to know when the command/lisp routine executed by <i>SendStringToExecute()</i> ends. Fortunately, in the .NET API, there are <i>Document.CommandEnded/Cancelled</i> and <i>Document.LispEnded/Cancelled</i> events for us to handle, so that we know when command/lisp routine execution ends, thus, we can repeatedly call <i>SendStringToExecute()</i> in a loop.</p><p>Following code example shows how to use <i>SendStringToExecute()</i> to run an command repeatedly against a collection of entities, one at a time. I created a command "<i>ThirdPartyCmd</i>" to mimic a third party command, which we really do not know (or do not need to know) how it does its work inside. But for the sake to show the "third party" command's effect, I let the command only accept one Line entity as PickFirst selection and rotate it, change its color.</p><pre style="background: white; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: #5b5b5b;">#region</span> Handle CommandEnded for looping
<span style="color: blue;">private</span> ObjectId[] _selectedLines = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">int</span> _currentIndex = 0;
<span style="color: blue;">private</span> Document _thisDwg = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"CmdLoop", </span><span>CommandFlags.Session</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunLoopingCommand</span>()
{
_thisDwg = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = _thisDwg.Editor;
<span style="color: #066555;">// Select target entities when the command loop begins</span>
<span style="color: #8f08c4;">if</span> (_selectedLines == <span style="color: blue;">null</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">filter</span> = <span style="color: blue;">new</span> SelectionFilter(
<span style="color: blue;">new</span>[] { <span style="color: blue;">new</span> TypedValue((<span style="color: blue;">int</span>)DxfCode.Start, <span style="color: #a31515;">"LINE"</span>) });
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetSelection(filter);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
_selectedLines = res.Value.GetObjectIds();
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span>;
}
_currentIndex = 0;
}
<span style="color: #066555;">// if necessary, do something with the </span>
<span style="color: #066555;">// entity that was processed by SendStringToExecute()</span>
<span style="color: #8f08c4;">if</span> (_currentIndex>0)
{
<span style="color: #066555;">//DoSomething(_selectedLines[_currentIndex-1]);</span>
}
_thisDwg.Editor.SetImpliedSelection(<span style="color: blue;">new</span>[] { _selectedLines[_currentIndex] });
_thisDwg.CommandEnded += ThirdPartyCmd_Ended;
_thisDwg.SendStringToExecute(<span style="color: #a31515;">"ThirdPartyCmd\n"</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ThirdPartyCmd_Ended</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, CommandEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: #8f08c4;">if</span> (e.GlobalCommandName.ToUpper() != <span style="color: #a31515;">"THIRDPARTYCMD"</span>) <span style="color: #8f08c4;">return</span>;
_thisDwg.CommandEnded -= ThirdPartyCmd_Ended;
_thisDwg.Editor.WriteMessage(
<span style="color: #a31515;">$"\nLine being processed by ThirdPartyCmd: </span>{_selectedLines[_currentIndex]}<span style="color: #a31515;">"</span>);
<span style="color: #066555;">// increment index</span>
_currentIndex++;
_thisDwg.Editor.WriteMessage(
<span style="color: #a31515;">$"\nThirdPartyCmd execution count: </span>{_currentIndex}<span style="color: #a31515;">"</span>);
<span style="color: #066555;">// End the loop after last target entity is done.</span>
<span style="color: #8f08c4;">if</span> (_currentIndex >= _selectedLines.Length)
{
_selectedLines= <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">return</span>;
}
<span style="color: #066555;">//Back to original command for another loop</span>
_thisDwg.SendStringToExecute(<span style="color: #a31515;">"CmdLoop\n"</span>, <span style="color: blue;">true</span>, <span style="color: blue;">false</span>, <span style="color: blue;">true</span>);
}
[CommandMethod(<span style="color: #a31515;">"ThirdPartyCmd"</span>, CommandFlags.UsePickSet)]
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RotateEntity</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.SelectImplied();
ed.WriteMessage(<span style="color: #a31515;">"\nRunning third party command...,"</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
ed.WriteMessage(<span style="color: #a31515;">$"\nProcessing selected entity: </span>{res.Value[0].ObjectId}<span style="color: #a31515;">"</span>);
<span style="color: blue;">using</span>(<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span>=dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = (Line)tran.GetObject(res.Value[0].ObjectId, OpenMode.ForWrite);
line.ColorIndex = <span style="color: blue;">new</span> Random().Next(0, 255);
line.TransformBy(Matrix3d.Rotation(
Math.PI / 2.0, Vector3d.ZAxis, line.StartPoint));
tran.Commit();
}
}
ed.WriteMessage(<span style="color: #a31515;">"\ndone!"</span>);
}
<span style="color: #5b5b5b;">#endregion</span></pre><p>The code is quite self-explanatory. See this video clip see the code running result:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='270' src='https://www.blogger.com/video.g?token=AD6v5dxVef6Z_jB3IgJlfEiAiJ6YCqLueYH40aeR-ZPmCvHo1Mj6ipkH7r1uQrVYPrizORvGKZ_04cDeiJeXjVA1nQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p><br /></p><p>This post is actually inspired by <a href="https://forums.autodesk.com/t5/net/text-explode/td-p/11532039" target="_blank">this discussion in the .NET forum</a>. The original poster want to execute command "<i>TxtExp</i>" (exploding text, from Express Tools) repeatedly against a selection of text entities, one at a time, and between the "<i>TxtExp</i>" calls, he/she also want to do something with the exploding-generated entities. He/she also referred an article posted by famous Kean Walmsley about using "<i>TxtExp</i>". "<i>TxtExp</i>" is a lisp command from the Express Tool suite. While it is certainly possible to write our own code to do the same text exploding work, but it would be quite complicated. If we could just use it in our code to achieve our goal, we can save a lot of time/effort. So, I gave it a try, use the same approach as above code shows. </p><p>The difference is that handing <i>CommandEnded</i> event is changed to handing <i>LispEnded</i>. Also, since we know the "<i>TxtExp</i>" (one can study the lisp routine "txtexp.lsp" in "Express" folder for some inside of this command) command generating a bunch of geometry entities from the selected entities, adding them into database, and then erasing the text, we can somehow collect these "exploded" geometry entities between "<i>TxtExp</i>" calls and do something as needed. Again, following code are quite easy to follow, so no extra explanation is needed:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: #5b5b5b;">#region</span> Handle LispEnded for looping
<span style="color: blue;">private</span> ObjectId[] _selectedTexts = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> List<ObjectId> _explodedEntities = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">int</span> _index = 0;
<span style="color: blue;">private</span> Document _dwg = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"ExplodeTexts"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ExplodeTextsToGeometryEntities</span>()
{
_dwg = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = _dwg.Editor;
<span style="color: #8f08c4;">if</span> (_selectedTexts == <span style="color: blue;">null</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">filter</span> = <span style="color: blue;">new</span> SelectionFilter(<span style="color: blue;">new</span> TypedValue[]
{
<span style="color: blue;">new</span> TypedValue((<span style="color: blue;">int</span>)DxfCode.Start, <span style="color: #a31515;">"TEXT"</span>)
});
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetSelection(filter);
<span style="color: #8f08c4;">if</span> (res.Status != PromptStatus.OK) <span style="color: #8f08c4;">return</span>;
_selectedTexts = res.Value.GetObjectIds();
_index= 0;
}
<span style="color: #8f08c4;">if</span> (_index>0)
{
UpdateExplodedEntities();
}
_dwg.LispEnded += Lisp_Ended;
_dwg.Database.ObjectAppended += Entity_Appended;
_explodedEntities = <span style="color: blue;">new</span> List<ObjectId>();
ed.SetImpliedSelection(<span style="color: blue;">new</span>[] { _selectedTexts[_index] });
_dwg.SendStringToExecute(<span style="color: #a31515;">"TXTEXP\n"</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Lisp_Ended</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, EventArgs <span style="color: #1f377f;">e</span>)
{
ObjectId <span style="color: #1f377f;">id</span> = _selectedTexts[_index];
<span style="color: #8f08c4;">if</span> (id.IsErased)
{
_dwg.LispEnded -= Lisp_Ended;
_dwg.Database.ObjectAppended -= Entity_Appended;
_dwg.Editor.WriteMessage(<span style="color: #a31515;">$"\nExploding text count: </span>{_index + 1}<span style="color: #a31515;">"</span>);
_index++;
<span style="color: #066555;">// end looping</span>
<span style="color: #8f08c4;">if</span> (_index >= _selectedTexts.Length)
{
_selectedTexts = <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">return</span>;
}
<span style="color: #066555;">// Do next loop</span>
_dwg.SendStringToExecute(<span style="color: #a31515;">"ExplodeTexts\n"</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Entity_Appended</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, ObjectEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: #8f08c4;">if</span> (e.DBObject <span style="color: blue;">is</span> Entity)
{
_explodedEntities.Add(e.DBObject.ObjectId);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">UpdateExplodedEntities</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">color</span> = <span style="color: blue;">new</span> Random().Next(0, 255);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _dwg.TransactionManager.StartTransaction())
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">entId</span> <span style="color: #8f08c4;">in</span> _explodedEntities)
{
<span style="color: #8f08c4;">if</span> (!entId.IsErased)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(entId, OpenMode.ForWrite);
ent.ColorIndex = color;
}
}
tran.Commit();
}
}
<span style="color: #5b5b5b;">#endregion</span></pre><p><br /></p><p>See the video clip below showing the effect of the code running:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxGY11GhMcWFTwHwFHOo38z-avT8Q1BQv6Vw9e3Bnuu4BweaOtFEuLGZhTmv3hfF33BfRpRf412BjcdzBCGFA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>Obviously I omitted the code to handle possible <i>CommandCancelled/LispCancelled</i> event, if the command/lisp routine to be executed could be cancelled by design, we may want to handle this event if necessary.</p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-80950630928139202122022-10-05T07:14:00.000-07:002022-10-05T07:14:15.307-07:00Extract Entity Image From Side-Loaded Database<p>There have been some discussions about getting image of an entity, and code samples could be found online. Most of them do the work with the entities in a drawing opened in AutoCAD.</p><p>I published 2 articles that could prove to be useful for this discussion:</p><p></p><ul style="text-align: left;"><li><i><a href="https://drive-cad-with-code.blogspot.com/2020/12/obtaining-blocks-image.html" target="_blank">Obtain Block's Image</a></i>, published in 2020-12-16</li><li><i><a href="https://drive-cad-with-code.blogspot.com/2022/08/generating-drawing-thumbnailpreview.html" target="_blank">Generating Drawing Thumbnail/Preview Image in Side Database</a></i>, published in 2022-08-22</li></ul><div>In these 2 articles, I showed how we can use <i>Autodesk.Autodesk.Internal.Utils.GetBlockImage()</i> to generate the image of a block without having to open the drawing in AutoCAD as Document.</div><div><br /></div><div>Seeing the <a href="https://forums.autodesk.com/t5/net/how-to-export-a-single-db-object-to-a-jpeg-or-bitmap-image/td-p/11460179" target="_blank">question</a> posted in the .NET discussion forum about exporting image of single entity, I thought it should be possible to use the same approach to do the job. With a short brain storm, I came up these steps to do this job:</div><div><br /></div><div>1. As the user for drawing file name where the entity images to be extracted from;</div><div>2. Ask the user to select a folder to save the extracted images;</div><div>3. Open the drawing as side database;</div><div>4. Create an empty BlockTableRecord as a container to hold an entity from which the image is to be generated;</div><div>5. Loop through ModelSpace for each entity</div><div>a. clear entity from the container BlockTableRecord;</div><div>b. add a clone of the entity to the container BlockTableRecord;</div><div>c. Call GetBlockImage() method against the container BlockTableRecord;</div><div>d. Save the obtained imager;</div><div><br /></div><div>With these steps in mind, I went ahead to implement them in code.</div><div><br /></div><div>The class <i>EntityImageExtracter</i>:</div><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Internal;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">namespace</span> ExtractEntityImages
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">EntityImageExtracter</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _sourceDwg;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _outputFolder;
<span style="color: blue;">public</span> <span style="color: #066555;">EntityImageExtracter</span>()
{
}
<span style="color: #066555;">// Notify the calling process</span>
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<<span style="color: blue;">int</span>> ExtractionStarted;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<<span style="color: blue;">int</span>> ExtractionProgressed;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<<span style="color: blue;">int</span>> ExtractionEnded;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ExtractEntityImages</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">sourceDwg</span>, <span style="color: blue;">string</span> <span style="color: #1f377f;">outputFolder</span>)
{
_sourceDwg = sourceDwg;
_outputFolder = outputFolder;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">db</span> = <span style="color: blue;">new</span> Database(<span style="color: blue;">false</span>, <span style="color: blue;">true</span>))
{
db.ReadDwgFile(_sourceDwg, FileOpenMode.OpenForReadAndAllShare, <span style="color: blue;">false</span>, <span style="color: blue;">null</span>);
ExtractImagesFromDatabase(db);
}
}
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ExtractImagesFromDatabase</span>(Database <span style="color: #1f377f;">db</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">model</span> = (BlockTableRecord)tran.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">ids</span> = model.Cast<ObjectId>();
ExtractionStarted?.Invoke(ids.Count());
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkDef</span> = CreateNewBlockTableRecord(db.BlockTableId, tran);
<span style="color: blue;">var</span> <span style="color: #1f377f;">doneCount</span> = 0;
<span style="color: blue;">var</span> <span style="color: #1f377f;">count</span> = 0;
<span style="color: #8f08c4;">foreach</span>(var <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> ids)
{
count++;
ExtractionProgressed?.Invoke(count);
<span style="color: blue;">var</span> <span style="color: #1f377f;">img</span> = ExtractEntityImage(id, blkDef, tran);
<span style="color: #8f08c4;">if</span> (img!=<span style="color: blue;">null</span>)
{
<span style="color: #066555;">//Save the image to file</span>
<span style="color: blue;">var</span> <span style="color: #1f377f;">imgFile</span> = <span style="color: #a31515;">$"</span>{id.ObjectClass.DxfName}<span style="color: #a31515;">_</span>{id.Handle.ToString()}<span style="color: #a31515;">.png"</span>;
img.Save(System.IO.Path.Combine(_outputFolder, imgFile));
doneCount++;
img.Dispose();
}
}
ExtractionEnded?.Invoke(doneCount);
tran.Abort();
}
}
<span style="color: blue;">private</span> BlockTableRecord <span style="color: #74531f;">CreateNewBlockTableRecord</span>(ObjectId <span style="color: #1f377f;">btId</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">bt</span> = (BlockTable)tran.GetObject(btId, OpenMode.ForWrite);
<span style="color: #066555;">// make sure no duplicated block name</span>
<span style="color: blue;">var</span> <span style="color: #1f377f;">name</span> = <span style="color: #a31515;">"ExtractImage"</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">i</span> = 1;
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkName</span> = <span style="color: #a31515;">$"</span>{name}<span style="color: #a31515;">_</span>{i}<span style="color: #a31515;">"</span>;
<span style="color: #8f08c4;">while</span>(bt.Has(blkName))
{
i++;
blkName = <span style="color: #a31515;">$"</span>{name}<span style="color: #a31515;">_</span>{i}<span style="color: #a31515;">"</span>;
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = <span style="color: blue;">new</span> BlockTableRecord();
blk.Name = blkName;
blk.Origin = Point3d.Origin;
bt.Add(blk);
tran.AddNewlyCreatedDBObject(blk, <span style="color: blue;">true</span>);
<span style="color: #8f08c4;">return</span> blk;
}
<span style="color: blue;">private</span> System.Drawing.Image <span style="color: #74531f;">ExtractEntityImage</span>(
ObjectId <span style="color: #1f377f;">entId</span>, BlockTableRecord <span style="color: #1f377f;">tempBlk</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(entId, OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">clone</span> = ent.Clone() <span style="color: blue;">as</span> Entity;
<span style="color: #066555;">// Use the center of the entity's extents at the block's origin</span>
MoveCenterToOrigin(clone);
<span style="color: #8f08c4;">foreach</span> (ObjectId <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> tempBlk)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dbObj</span> = tran.GetObject(id, OpenMode.ForWrite);
dbObj.Erase();
}
tempBlk.AppendEntity(clone);
tran.AddNewlyCreatedDBObject(clone, <span style="color: blue;">true</span>);
<span style="color: #066555;">// generate the image of the block definition</span>
<span style="color: #066555;">// where only one entity exists, thus, the image of the entity</span>
<span style="color: blue;">var</span> <span style="color: #1f377f;">cl</span> = Autodesk.AutoCAD.Colors.Color.FromColor(
System.Drawing.Color.WhiteSmoke);
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkId</span> = tempBlk.ObjectId;
<span style="color: blue;">var</span> <span style="color: #1f377f;">imgPtr</span> = Utils.GetBlockImage(blkId, 300, 300, cl);
<span style="color: blue;">var</span> <span style="color: #1f377f;">image</span> = System.Drawing.Image.FromHbitmap(imgPtr);
<span style="color: #8f08c4;">return</span> image;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">MoveCenterToOrigin</span>(Entity <span style="color: #1f377f;">ent</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">exts</span> = ent.GeometricExtents;
<span style="color: blue;">var</span> <span style="color: #1f377f;">w</span> = exts.MaxPoint.X - exts.MinPoint.X;
<span style="color: blue;">var</span> <span style="color: #1f377f;">h</span>= exts.MaxPoint.Y - exts.MinPoint.Y;
<span style="color: blue;">var</span> <span style="color: #1f377f;">center</span> = <span style="color: blue;">new</span> Point3d(
exts.MinPoint.X + w / 2.0,
exts.MinPoint.Y + h / 2.0,
exts.MinPoint.Z);
<span style="color: blue;">var</span> <span style="color: #1f377f;">mt</span> = Matrix3d.Displacement(center.GetVectorTo(Point3d.Origin));
ent.TransformBy(mt);
}
<span style="color: #5b5b5b;">#endregion</span>
}
}</pre><div>The code is rather simple and straightforward. Following are the other code to make the process runnable.</div><div><br /></div><div>A helper class for user to select drawing file and output folder:</div>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System.IO;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">namespace</span> ExtractEntityImages
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">GenericHelper</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">string</span> <span style="color: #74531f;">SelectSourceDrawingFile</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">fName</span> = <span style="color: #a31515;">""</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">dlg</span> = <span style="color: blue;">new</span> OpenFileDialog())
{
dlg.Title = <span style="color: #a31515;">"Select Drawing File"</span>;
dlg.Filter = <span style="color: #a31515;">"AutoCAD Drawing (*.dwg)|*.dwg"</span>;
dlg.Multiselect = <span style="color: blue;">false</span>;
<span style="color: #8f08c4;">if</span> (dlg.ShowDialog() == DialogResult.OK)
{
fName = dlg.FileName;
}
}
<span style="color: #8f08c4;">return</span> fName;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">string</span> <span style="color: #74531f;">SelectImageOutputFolder</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">initPath</span>=<span style="color: #a31515;">""</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">folder</span> = <span style="color: #a31515;">""</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">dlg</span> = <span style="color: blue;">new</span> FolderBrowserDialog())
{
dlg.Description = <span style="color: #a31515;">"Select Image Output Folder"</span>;
<span style="color: #8f08c4;">if</span> (!<span style="color: blue;">string</span>.IsNullOrEmpty(initPath))
{
dlg.SelectedPath = initPath;
}
dlg.ShowNewFolderButton = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">if</span> (dlg.ShowDialog()== DialogResult.OK)
{
folder = dlg.SelectedPath;
}
}
<span style="color: #8f08c4;">return</span> folder;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearImageOutputFolder</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">path</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">files</span>=Directory.GetFiles(path);
<span style="color: #8f08c4;">if</span> (files.Length>0)
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">f</span> <span style="color: #8f08c4;">in</span> files)
{
File.Delete(f);
}
}
}
}
}</pre><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="font-family: "Times New Roman"; font-size: medium; white-space: normal;">The Command class to run the process:</span></pre>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(ExtractEntityImages.MyCommands))]
<span style="color: blue;">namespace</span> ExtractEntityImages
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"GetEntImages"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ExtractEntityImagedFromDrawing</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">string</span> <span style="color: #1f377f;">sourceDwg</span> = GenericHelper.SelectSourceDrawingFile();
<span style="color: #8f08c4;">if</span> (<span style="color: blue;">string</span>.IsNullOrEmpty(sourceDwg))
{
ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">initPath</span> = dwg.IsNamedDrawing ?
System.IO.Path.GetDirectoryName(dwg.Name) : <span style="color: #a31515;">""</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">outputFolder</span> = GenericHelper.SelectImageOutputFolder(initPath);
<span style="color: #8f08c4;">if</span> (<span style="color: blue;">string</span>.IsNullOrEmpty(outputFolder))
{
ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
GenericHelper.ClearImageOutputFolder(outputFolder);
ProgressMeter <span style="color: #1f377f;">meter</span> = <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">try</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">extractedCount</span> = 0;
EntityImageExtracter.ExtractionStarted = (<span style="color: #1f377f;">count</span>) =>
{
meter = <span style="color: blue;">new</span> ProgressMeter();
meter.SetLimit(count);
meter.Start(<span style="color: #a31515;">"Extracting entity images..."</span>);
};
EntityImageExtracter.ExtractionProgressed = (<span style="color: #1f377f;">index</span>) =>
{
meter.MeterProgress();
};
EntityImageExtracter.ExtractionEnded = (<span style="color: #1f377f;">totalCount</span>) =>
{
extractedCount = totalCount;
};
<span style="color: blue;">var</span> <span style="color: #1f377f;">extracter</span> = <span style="color: blue;">new</span> EntityImageExtracter();
extracter.ExtractEntityImages(sourceDwg, outputFolder);
ed.WriteMessage(
<span style="color: #a31515;">$"\nExtracted image count: </span>{extractedCount}<span style="color: #a31515;">\n"</span>);
}
<span style="color: #8f08c4;">catch</span>(System.Exception <span style="color: #1f377f;">ex</span>)
{
ed.WriteMessage(
<span style="color: #a31515;">$"\nError:\n</span>{ex.Message}<span style="color: #a31515;">\n"</span>);
}
<span style="color: #8f08c4;">finally</span>
{
<span style="color: #8f08c4;">if</span> (meter!=<span style="color: blue;">null</span>)
{
meter.Stop();
meter.Dispose();
}
}
}
}
}
</pre><div><br /></div><div>Since the entire process of generating image is done with an side-loaded database, so I did not bother to remove the container BlockTableRecord, as long as the side database is not to be saved for the changes.</div><div><br /></div><div>I only test it with simple drawing, as following video clip shows, the result satisfies me with its speed and the quality of the image. The only thing in the whole thing that might be potentially problematic is the key factor of the approach - the method GetBlockImage() - is from Autodesk.AutoCAD.Internal namespace, meaning use it at your own risk, if Autodesk wants to break it for whatever reason.</div><div><br /></div><div>See the video clip bellow:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dy0hnlhKAxvKfioT4GwFOH8kwi2v978IoISIAoPBeziYNop62X3ALhKjzaEuL3UijRvY1LdzNuf22IFVR-V8Q' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><p></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-71861332488910097982022-09-27T18:11:00.000-07:002022-09-27T18:11:11.273-07:00Selecting a Segment of a Polyline Visually<p>By arriving at New Orleans for AU2022 early I planned to explore the The Big Easy city for its unique scene and culture. But the unbearable heat (at least to me, as Canadian from far up north) forced me staying air-conditioned indoor more than I wanted. As a runner, I also planned some morning runs whenever I am in a different city, but I decided I'd better not run here to avoid unexpected heat stroke. Thus I end up having a bit more time to write some code for a question I saw in the .NET discussion forum <a href="https://forums.autodesk.com/t5/net/how-to-get-seperated-polyline-segment-in-selected-polyline/td-p/11445045" target="_blank">here</a>. While the answer to that question is fairly simple, but it still needs to be better explained with code, thus this post.</p><p>The question asked there actually involves 2 different programming tasks: identifying the selected segment of a polyline (e.g. the user should select a polyline, and the code then determine which segment of the polyline is actually clicked); and showing a visual hint to the user to indicate which segment is selected. With these 2 separate coding tasks in mind, the code then can be structured easily.</p><p>First, for identifying selected segment, I create the class <i>PolylineSegmentSelector</i>, which is really simple - if the user selected a polyline, based on where the picked point is, the segment's index is calculated:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">namespace</span> MiscTest
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">PolylineSegmentSelector</span>
{
<span style="color: blue;">private</span> Document _dwg;
<span style="color: blue;">private</span> Editor _ed;
<span style="color: blue;">private</span> Database _db;
<span style="color: blue;">public</span> ObjectId SelectedPolyLine { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; } = ObjectId.Null;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> SelectedSegment { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; } = -1;
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">SelectSegment</span>(Document <span style="color: #1f377f;">dwg</span>)
{
_dwg = dwg;
_ed = dwg.Editor;
_db = dwg.Database;
SelectedPolyLine = ObjectId.Null;
SelectedSegment = -1;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect target segment of a polyline:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid selection: not a polyline."</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(CadDb.Polyline), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status != PromptStatus.OK) <span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
SelectedPolyLine = res.ObjectId;
SelectedSegment = FindSelectedSegment(res.ObjectId, res.PickedPoint);
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">int</span> <span style="color: #74531f;">FindSelectedSegment</span>(ObjectId <span style="color: #1f377f;">polyId</span>, Point3d <span style="color: #1f377f;">pickedPt</span>)
{
<span style="color: blue;">int</span> <span style="color: #1f377f;">index</span> = -1;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
polyId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = (CadDb.Polyline)tran.GetObject(polyId, OpenMode.ForRead);
<span style="color: blue;">var</span> <span style="color: #1f377f;">ptOnPoly</span> = poly.GetClosestPointTo(pickedPt, <span style="color: blue;">false</span>);
index = Convert.ToInt32(Math.Floor(poly.GetParameterAtPoint(ptOnPoly)));
tran.Commit();
}
<span style="color: #8f08c4;">return</span> index;
}
}
}</pre><p>Then, the class <i>PolylineSegmentHighlighter</i>, which presents a temporary visual hint withTransient Graphics:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> CadDb = Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">namespace</span> MiscTest
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">PolylineSegmentHighlighter</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">class</span> <span style="color: #066555;">SegmentGhost</span>
{
<span style="color: blue;">public</span> ObjectId PolylineId { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = ObjectId.Null;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> SegmentIndex { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = 0;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> ColorIndex { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = 0;
<span style="color: blue;">public</span> Drawable Ghost { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = <span style="color: blue;">null</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> List<SegmentGhost> _ghosts = <span style="color: blue;">new</span> List<SegmentGhost>();
<span style="color: blue;">private</span> TransientManager _tsManager = TransientManager.CurrentTransientManager;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">SetSegmentHighlight</span>(ObjectId <span style="color: #1f377f;">polyId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">segIndex</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">colorIndex</span>)
{
RemoveSegmentHighlight(polyId, segIndex);
<span style="color: blue;">var</span> <span style="color: #1f377f;">newGhost</span> = CreateHighlightGhost(polyId, segIndex, colorIndex);
_ghosts.Add(newGhost);
_tsManager.AddTransient(
newGhost.Ghost,
TransientDrawingMode.DirectTopmost,
128,
<span style="color: blue;">new</span> Autodesk.AutoCAD.Geometry.IntegerCollection());
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveSegmentHighlight</span>(ObjectId <span style="color: #1f377f;">polyId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">segIndex</span>)
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">ghost</span> <span style="color: #8f08c4;">in</span> _ghosts)
{
<span style="color: #8f08c4;">if</span> (ghost.PolylineId == polyId &&
ghost.SegmentIndex == segIndex &&
ghost.Ghost!=<span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(
ghost.Ghost, <span style="color: blue;">new</span> Autodesk.AutoCAD.Geometry.IntegerCollection());
_ghosts.Remove(ghost);
ghost.Ghost.Dispose();
<span style="color: #8f08c4;">break</span>;
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">ghost</span> <span style="color: #8f08c4;">in</span> _ghosts)
{
<span style="color: #8f08c4;">if</span> (ghost.Ghost != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(
ghost.Ghost, <span style="color: blue;">new</span> Autodesk.AutoCAD.Geometry.IntegerCollection());
ghost.Ghost.Dispose();
}
}
_ghosts.Clear();
}
<span style="color: blue;">private</span> SegmentGhost <span style="color: #74531f;">CreateHighlightGhost</span>(ObjectId <span style="color: #1f377f;">polyId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">segIndex</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">colorIndex</span>)
{
Entity <span style="color: #1f377f;">ghost</span> = <span style="color: blue;">null</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
polyId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">poly</span> = (CadDb.Polyline)tran.GetObject(polyId, OpenMode.ForRead);
<span style="color: #8f08c4;">if</span> (poly.NumberOfVertices > 2)
{
DBObjectCollection <span style="color: #1f377f;">ents</span> = <span style="color: blue;">new</span> DBObjectCollection();
poly.Explode(ents);
<span style="color: #8f08c4;">for</span> (<span style="color: blue;">int</span> <span style="color: #1f377f;">i</span> = 0; i < ents.Count; i++)
{
<span style="color: #8f08c4;">if</span> (i == segIndex)
{
ghost = ents[i] <span style="color: blue;">as</span> Entity;
}
<span style="color: #8f08c4;">else</span>
{
ents[i].Dispose();
}
}
}
<span style="color: #8f08c4;">else</span>
{
ghost = poly.Clone() <span style="color: blue;">as</span> Entity;
}
tran.Commit();
}
<span style="color: #8f08c4;">if</span> (ghost != <span style="color: blue;">null</span>)
{
ghost.ColorIndex = colorIndex;
<span style="color: #8f08c4;">return</span> <span style="color: blue;">new</span> SegmentGhost()
{
PolylineId=polyId,
SegmentIndex=segIndex,
ColorIndex = colorIndex,
Ghost=ghost
};
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">null</span>;
}
}
}
}</pre><p>Following CommandMethod demonstrates how to use these 2 classes:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">[CommandMethod(<span style="color: #a31515;">"SelectPolySeg"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">SelectPolylineSegment</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">selectedPolySegs</span> = <span style="color: blue;">new</span> List<(ObjectId polyId, <span style="color: blue;">int</span> segIndex)>();
<span style="color: blue;">var</span> <span style="color: #1f377f;">selector</span> = <span style="color: blue;">new</span> PolylineSegmentSelector();
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">highlighter</span> = <span style="color: blue;">new</span> PolylineSegmentHighlighter())
{
<span style="color: blue;">int</span> <span style="color: #1f377f;">color</span> = 1;
<span style="color: #8f08c4;">while</span>(selector.SelectSegment(dwg))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">selectedPoly</span> = selector.SelectedPolyLine;
<span style="color: blue;">var</span> <span style="color: #1f377f;">segIndex</span> = selector.SelectedSegment;
selectedPolySegs.Add((selectedPoly, segIndex));
highlighter.SetSegmentHighlight(selectedPoly, segIndex, color);
CadApp.UpdateScreen();
color++;
}
}
<span style="color: #8f08c4;">if</span> (selectedPolySegs.Count>0)
{
<span style="color: #066555;">//do whatever work required for each of the selected segments</span>
}
}</pre><p>See the video clip below:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dybp578jD4Y9H8aw2kg-FOpulA-PY-bee2WxOcQ-ct0hpnP3LEZYOykDrx-_kq9wPqW3-8QcsK-hskeSh8OKQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p><br /></p><p><br /></p><p><br /></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com2tag:blogger.com,1999:blog-993393844516183990.post-2961999933704377662022-09-25T19:38:00.002-07:002022-09-29T14:36:19.457-07:00Update Entities via Data Binding of Modeless WPF Window - Update<p>I arrived at New Orleans for the AU2002 early and found myself having a bit extra time do deal with something that bothers me: in the <a href="https://drive-cad-with-code.blogspot.com/2022/09/update-entities-via-data-binding-of.html" target="_blank">article</a> I published a couple of days ago on the topic of binding entity property with WPF modeless UI, I only demonstrated how to control entity's property (Rotation property of a block reference) from UI side. As a better AutoCAD plugin UI design, I should/could have made the binding between the entities and UI work in both ways: the user can control the entity property from the UI, and the UI can also reflect changes of the entity property made by the user in the AutoCAD editor. So, I quickly reworked the previous project.</p><p>Here were two things I took into account that happen in AutoCAD editor and ought to be reflected in the modeless UI:</p><p>1. The Rotation property of a monitored block reference can be changed by the CAD user in AutoCAD editor (through "Rotate" command, or simply via the block reference's grip). When this happens, the UI should reflect the change;</p><p>2. A monitored block reference (e.g. it is selected by the user to show up in the modeless UI) can be erased from AutoCAD editor, either by the user manually, or by other custom command. When this happens, the UI should get rid of the block reference from its monitored block list.</p><p>While the code changes were quite easy because the fully workable code in the previous project, I decided to create a brand new DLL project, and copy the code over for refactoring, in which I renamed most classes.</p><p>Although quite portions of the code here are the same as the previous project, I post all the code of this updated project here for the benefit of my readers' convenience. Here it goes.</p><p>The WPF support code:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.ComponentModel;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Runtime.CompilerServices;
<span style="color: blue;">using</span> System.Text;
<span style="color: blue;">using</span> System.Threading.Tasks;
<span style="color: blue;">using</span> System.Windows.Input;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">RelayCommand</span> : ICommand
{
<span style="color: blue;">private</span> Action<<span style="color: blue;">object</span>> execute;
<span style="color: blue;">private</span> Func<<span style="color: blue;">object</span>, <span style="color: blue;">bool</span>> canExecute;
<span style="color: blue;">public</span> <span style="color: blue;">event</span> EventHandler CanExecuteChanged
{
<span style="color: blue;">add</span> { CommandManager.RequerySuggested += value; }
<span style="color: blue;">remove</span> { CommandManager.RequerySuggested -= value; }
}
<span style="color: blue;">public</span> <span style="color: #066555;">RelayCommand</span>(Action<<span style="color: blue;">object</span>> <span style="color: #1f377f;">execute</span>, Func<<span style="color: blue;">object</span>, <span style="color: blue;">bool</span>> <span style="color: #1f377f;">canExecute</span> = <span style="color: blue;">null</span>)
{
<span style="color: blue;">this</span>.execute = execute;
<span style="color: blue;">this</span>.canExecute = canExecute;
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">CanExecute</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">parameter</span>)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">this</span>.canExecute == <span style="color: blue;">null</span> || <span style="color: blue;">this</span>.canExecute(parameter);
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Execute</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">parameter</span>)
{
<span style="color: blue;">this</span>.execute(parameter);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">abstract</span> <span style="color: blue;">class</span> <span style="color: #066555;">ViewModelBase</span> : INotifyPropertyChanged
{
<span style="color: blue;">public</span> <span style="color: blue;">event</span> PropertyChangedEventHandler PropertyChanged;
<span style="color: blue;">protected</span> <span style="color: blue;">virtual</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnPropertyChanged</span>([CallerMemberName] <span style="color: blue;">string</span> <span style="color: #1f377f;">propertyName</span> = <span style="color: blue;">null</span>)
{
PropertyChanged?.Invoke(<span style="color: blue;">this</span>, <span style="color: blue;">new</span> PropertyChangedEventArgs(propertyName));
}
}
}</pre><p>The CAD operations:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Text;
<span style="color: blue;">using</span> System.Threading.Tasks;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">CadHelper</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> ObjectId <span style="color: #74531f;">SelectBlock</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = Application.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect a block:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid selection: not a block!"</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(BlockReference), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = dwg.Editor.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
{
<span style="color: #8f08c4;">return</span> res.ObjectId;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> ObjectId.Null;
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">WriteMessage</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">msg</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = Application.DocumentManager.MdiActiveDocument;
dwg.Editor.WriteMessage(<span style="color: #a31515;">$"</span>{msg}<span style="color: #a31515;">"</span>);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToEntity</span>(ObjectId <span style="color: #1f377f;">entId</span>)
{
Extents3d <span style="color: #1f377f;">ext</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
entId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(entId, OpenMode.ForRead);
ext = ent.GeometricExtents;
tran.Commit();
}
ZoomToExtents(ext, 1.0);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToExtents</span>(Extents3d <span style="color: #1f377f;">ext</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">marginRatio</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">w</span> = ext.MaxPoint.X - ext.MinPoint.X;
<span style="color: blue;">var</span> <span style="color: #1f377f;">h</span> = ext.MaxPoint.Y - ext.MinPoint.Y;
<span style="color: blue;">var</span> <span style="color: #1f377f;">delta</span> = Math.Max(w, h) * marginRatio;
<span style="color: blue;">var</span> <span style="color: #1f377f;">minPt</span> = <span style="color: blue;">new</span>[]
{
ext.MinPoint.X-delta, ext.MinPoint.Y-delta, ext.MinPoint.Z
};
<span style="color: blue;">var</span> <span style="color: #1f377f;">maxPt</span> = <span style="color: blue;">new</span>[]
{
ext.MaxPoint.X+delta, ext.MaxPoint.Y+delta, ext.MaxPoint.Z
};
dynamic <span style="color: #1f377f;">comApp</span> =
Autodesk.AutoCAD.ApplicationServices.Application.AcadApplication;
comApp.ZoomWindow(minPt, maxPt);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RotateBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: #8f08c4;">if</span> (dwg.Database.FingerprintGuid != blkId.Database.FingerprintGuid)
{
<span style="color: #8f08c4;">throw</span> <span style="color: blue;">new</span> InvalidOperationException(
<span style="color: #a31515;">"Entity is not from current drawing database!"</span>);
}
<span style="color: blue;">using</span> (dwg.LockDocument())
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
blkId.Database.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> =
(BlockReference)tran.GetObject(blkId, OpenMode.ForWrite);
blk.Rotation = Math.PI * rotation / 180.0;
tran.Commit();
}
}
CadApp.UpdateScreen();
}
}
}</pre><p>The "Model" in Model-View-ViewModel" pattern (I highlight the major code changes in red):</p><pre style="background: white; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Text;
<span style="color: blue;">using</span> System.Threading.Tasks;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: red;"><span>public</span> <span>class</span> <span>MonitoredBlock</span>
{
<span>public</span> ObjectId ObjectId { <span>get</span>; <span>set</span>; } = ObjectId.Null;
<span>public</span> <span>int</span> Rotation { <span>get</span>; <span>set</span>; } = 0;
}</span><span style="color: black;">
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MonitoredBlocks</span> : Dictionary<<span style="color: blue;">string</span>, List<MonitoredBlock>>
{
<span style="color: blue;">public</span> <span style="color: #066555;">MonitoredBlocks</span>()
{
Application.DocumentManager.DocumentToBeDestroyed += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
<span style="color: #8f08c4;">if</span> (ContainsKey(e.Document.Database.FingerprintGuid))
{
</span><span style="color: red;">RemoveDatabaseEventHandlers(e.Document.Database);</span>
Remove(e.Document.Database.FingerprintGuid);
}
};
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">int</span>> BlockAdded;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> BlockRemoved;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">int</span>> BlockRotated;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> ChangedfromUI = <span style="color: blue;">false</span>;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">db</span> = blkId.Database;
<span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span> = 0;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = db.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = tran.GetObject(blkId, OpenMode.ForRead) <span style="color: blue;">as</span> BlockReference;
<span style="color: #8f08c4;">if</span> (blk!=<span style="color: blue;">null</span>) rotation= Convert.ToInt32(blk.Rotation * 180 / Math.PI);
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = db.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (!ContainsKey(key))
{
Add(key, <span style="color: blue;">new</span> List<MonitoredBlock>());
HookUpDatabaseEventHandlers(blkId.Database);
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">mblk</span> = <span style="color: blue;">new</span> MonitoredBlock { ObjectId = blkId, Rotation = rotation };
<span style="color: blue;">this</span>[key].Add(mblk);
BlockAdded?.Invoke(blkId, rotation);
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = blkId.Database.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blks</span> = <span style="color: blue;">this</span>[key];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> blks)
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId == blkId)
{
blks.Remove(blk);
<span style="color: #8f08c4;">return</span>;
}
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsDoubleSelected</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = blkId.Database.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> <span style="color: blue;">this</span>[key])
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId == blkId) <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: red;"><span>private</span> <span>void</span> <span>HookUpDatabaseEventHandlers</span>(Database <span>db</span>)
{
db.ObjectModified += Db_ObjectModified;
db.ObjectErased += Db_ObjectErased;
}
<span>private</span> <span>void</span> <span>Db_ObjectErased</span>(<span>object</span> <span>sender</span>, ObjectErasedEventArgs <span>e</span>)
{
<span>var</span> <span>entId</span> = e.DBObject.ObjectId;
<span>if</span> (ContainsKey(entId.Database.FingerprintGuid))
{
<span>var</span> <span>blks</span> = <span>this</span>[entId.Database.FingerprintGuid];
<span>foreach</span> (var <span>blk</span> <span>in</span> blks)
{
<span>if</span> (blk.ObjectId == entId)
{
blks.Remove(blk);
BlockRemoved?.Invoke(entId);
<span>break</span>;
}
}
}
}
<span>private</span> <span>void</span> <span>Db_ObjectModified</span>(<span>object</span> <span>sender</span>, ObjectEventArgs <span>e</span>)
{
<span>if</span> (ChangedfromUI) <span>return</span>;
<span>var</span> <span>entId</span> = e.DBObject.ObjectId;
<span>if</span> (ContainsKey(entId.Database.FingerprintGuid))
{
<span>var</span> <span>blks</span> = <span>this</span>[entId.Database.FingerprintGuid];
<span>foreach</span> (var <span>blk</span> <span>in</span> blks)
{
<span>if</span> (blk.ObjectId==entId)
{
<span>var</span> <span>rotate</span> = Convert.ToInt32(((BlockReference)e.DBObject).Rotation * 180 / Math.PI);
<span>if</span> (rotate != blk.Rotation)
{
blk.Rotation= rotate;
BlockRotated?.Invoke(blk.ObjectId, rotate);
}
<span>break</span>;
}
}
}
}
<span>private</span> <span>void</span> <span>RemoveDatabaseEventHandlers</span>(Database <span>db</span>)
{
<span>try</span>
{
db.ObjectErased -= Db_ObjectErased;
db.ObjectModified -= Db_ObjectModified;
}
<span>catch</span> { }
}</span><span style="color: black;">
}
}</span></pre><p>The "View" in "Model-View-ViewModel" pattern (no major changes made to the 2 views at all, including the very few lines of code-behind remaining unchanged, except for changing the Slider's interval from 30 to 1, so that the UI can control/reflect block's rotation as small as 1 degree):</p><p>The UserControl class "BlockMonitorRowView":</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;"><</span><span style="color: #a31515;">UserControl</span><span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Class</span><span style="color: blue;">=</span><span style="color: blue;">"BlockRotationDashboard.BlockMonitorRowView"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">x</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">mc</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">d</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">local</span><span style="color: blue;">=</span><span style="color: blue;">"clr-namespace:BlockRotationDashboard"</span>
<span style="color: maroon;"> mc</span><span style="color: blue;">:</span><span style="color: maroon;">Ignorable</span><span style="color: blue;">=</span><span style="color: blue;">"d"</span>
<span style="color: maroon;"> d</span><span style="color: blue;">:</span><span style="color: maroon;">DesignHeight</span><span style="color: blue;">=</span><span style="color: blue;">"36"</span><span style="color: maroon;"> d</span><span style="color: blue;">:</span><span style="color: maroon;">DesignWidth</span><span style="color: blue;">=</span><span style="color: blue;">"650"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"36"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100*"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"70"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"70"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Handle:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;"> /></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Handle</span><span style="color: blue;">,</span><span style="color: maroon;"> Mode</span><span style="color: blue;">=</span><span style="color: blue;">OneTime</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Rotation:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;"> /></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Rotation</span><span style="color: blue;">,</span><span style="color: maroon;"> Mode</span><span style="color: blue;">=</span><span style="color: blue;">OneWay</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Slider</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"2"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Minimum</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Maximum</span><span style="color: blue;">=</span><span style="color: blue;">"360"</span>
<span style="color: maroon;"> Interval</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> IsSnapToTickEnabled</span><span style="color: blue;">=</span><span style="color: blue;">"True"</span><span style="color: maroon;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Rotation</span><span style="color: blue;">}</span><span style="color: blue;">"</span>
<span style="color: maroon;"> TickPlacement</span><span style="color: blue;">=</span><span style="color: blue;">"Both"</span><span style="color: maroon;"> LargeChange</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> SmallChange</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> TickFrequency</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"3"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Zoom"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"60"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> ZoomCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockId</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"4"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Remove"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"60"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> RemoveCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockId</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">UserControl</span><span style="color: blue;">></span></pre><p>The Window class "BlockMonitorView":</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;"><</span><span style="color: #a31515;">Window</span><span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Class</span><span style="color: blue;">=</span><span style="color: blue;">"BlockRotationDashboard.BlockMonitorView"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">x</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">mc</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">d</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">local</span><span style="color: blue;">=</span><span style="color: blue;">"clr-namespace:BlockRotationDashboard"</span>
<span style="color: maroon;"> mc</span><span style="color: blue;">:</span><span style="color: maroon;">Ignorable</span><span style="color: blue;">=</span><span style="color: blue;">"d"</span><span style="color: maroon;"> d</span><span style="color: blue;">:</span><span style="color: maroon;">DesignWidth</span><span style="color: blue;">=</span><span style="color: blue;">"800"</span>
<span style="color: maroon;"> Closing</span><span style="color: blue;">=</span><span style="color: blue;">"TheWindow_Closing"</span>
<span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Name</span><span style="color: blue;">=</span><span style="color: blue;">"TheWindow"</span>
<span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"660"</span><span style="color: maroon;"> MinHeight</span><span style="color: blue;">=</span><span style="color: blue;">"250"</span>
<span style="color: maroon;"> SizeToContent</span><span style="color: blue;">=</span><span style="color: blue;">"Height"</span><span style="color: maroon;"> ShowInTaskbar</span><span style="color: blue;">=</span><span style="color: blue;">"False"</span><span style="color: maroon;"> ResizeMode</span><span style="color: blue;">=</span><span style="color: blue;">"NoResize"</span>
<span style="color: maroon;"> Title</span><span style="color: blue;">=</span><span style="color: blue;">"Block Rotation Dashboard"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"225"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.RowDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">RowDefinition</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">RowDefinition</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"150"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.RowDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: maroon;"> Grid.Row</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"5,0,5,0"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"200*"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Monitored Blocks:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockCount</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span>
<span style="color: maroon;"> FontWeight</span><span style="color: blue;">=</span><span style="color: blue;">"DemiBold"</span><span style="color: maroon;"> Foreground</span><span style="color: blue;">=</span><span style="color: blue;">"Blue"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Add Block >"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"80"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> AddBlockCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span>
<span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> ElementName</span><span style="color: blue;">=</span><span style="color: blue;">TheWindow</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ListBox</span><span style="color: maroon;"> Grid.Row</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"5,5,5,5"</span>
<span style="color: maroon;"> ItemsSource</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Blocks</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> HorizontalContentAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Stretch"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ListBox.ItemTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">local</span><span style="color: blue;">:</span><span style="color: #a31515;">BlockMonitorRowView</span><span style="color: maroon;"> DataContext</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;"> /></span>
<span style="color: blue;"></</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">ListBox.ItemTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">ListBox</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Window</span><span style="color: blue;">></span>
</pre><p>This is the window's code-behind:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System.Windows;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> Interaction logic for BlockMonitorView.xaml</span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"></</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockMonitorView</span> : Window
{
<span style="color: blue;">public</span> <span style="color: #066555;">BlockMonitorView</span>()
{
InitializeComponent();
}
<span style="color: blue;">public</span> <span style="color: #066555;">BlockMonitorView</span>(BlockMonitorViewModel <span style="color: #1f377f;">viewModel</span>) : <span style="color: blue;">this</span>()
{
Loaded += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
DataContext = viewModel;
};
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">TheWindow_Closing</span>(
<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, System.ComponentModel.CancelEventArgs <span style="color: #1f377f;">e</span>)
{
e.Cancel = <span style="color: blue;">true</span>;
Visibility = Visibility.Hidden;
}
}
}</pre><p>The "ViewModel" of the "Model-View-ViewModel" pattern:</p><p>Firstly, the "BlockMonitorRowViewModel" class, which is the same as the one used in previous project, except for being renamed:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Text;
<span style="color: blue;">using</span> System.Threading.Tasks;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockMonitorRowViewModel</span> : ViewModelBase
{
<span style="color: blue;">private</span> <span style="color: blue;">int</span> _rotation = 0;
<span style="color: blue;">private</span> ObjectId _blkId = ObjectId.Null;
<span style="color: blue;">public</span> <span style="color: #066555;">BlockMonitorRowViewModel</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span>)
{
_blkId = blkId;
_rotation = rotation;
RemoveCommand = <span style="color: blue;">new</span> RelayCommand(
RemoveBlock, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
ZoomCommand = <span style="color: blue;">new</span> RelayCommand(
ZoomToBlock, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
}
<span style="color: blue;">public</span> <span style="color: blue;">string</span> Handle => _blkId.Handle.ToString();
<span style="color: blue;">public</span> ObjectId BlockId => _blkId;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> Rotation
{
<span style="color: blue;">get</span> => _rotation;
<span style="color: blue;">set</span>
{
_rotation = value;
OnPropertyChanged(<span style="color: #a31515;">"Rotation"</span>);
RotateBlockAction?.Invoke(_blkId, _rotation);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> RemoveBlockAction;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> ZoomAction;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">int</span>> RotateBlockAction;
<span style="color: blue;">public</span> RelayCommand RemoveCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> RelayCommand ZoomCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveBlock</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
RemoveBlockAction?.Invoke(_blkId);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToBlock</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
ZoomAction?.Invoke(_blkId);
}
}
}
</pre><p>The "BlockMonitorViewModel" class, in which I added some comments at changed code lines:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Collections.ObjectModel;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Text;
<span style="color: blue;">using</span> System.Threading.Tasks;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockMonitorViewModel</span> : ViewModelBase
{
<span style="color: blue;">private</span> MonitoredBlocks _monitoredBlocks;
<span style="color: blue;">private</span> ObservableCollection<BlockMonitorRowViewModel> _blocks;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _currentDbGuid = <span style="color: #a31515;">""</span>;
<span style="color: blue;">public</span> <span style="color: #066555;">BlockMonitorViewModel</span>(MonitoredBlocks <span style="color: #1f377f;">monitoredBlocks</span>)
{
_monitoredBlocks = monitoredBlocks;
_blocks = <span style="color: blue;">new</span> ObservableCollection<BlockMonitorRowViewModel>();
AddBlockCommand = <span style="color: blue;">new</span> RelayCommand(
AddBlockToDashboard, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
MonitoredBlocks.BlockAdded = OnBlockAdded;
<span style="color: #066555;">// The actions to be triggered when monitored blocks</span>
<span style="color: #066555;">// is rotated or erased in AutoCAD editor</span>
MonitoredBlocks.BlockRemoved = OnBlockRemoved;
MonitoredBlocks.BlockRotated = OnBlockRotated;
BlockMonitorRowViewModel.RemoveBlockAction = OnBlockRemove;
BlockMonitorRowViewModel.ZoomAction = OnBlockZoom;
BlockMonitorRowViewModel.RotateBlockAction = OnBlockRotate;
_currentDbGuid =
CadApp.DocumentManager.MdiActiveDocument.Database.FingerprintGuid;
ResetView();
CadApp.DocumentManager.DocumentActivated += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
<span style="color: #8f08c4;">if</span> (e.Document.Database.FingerprintGuid != _currentDbGuid)
{
_currentDbGuid = e.Document.Database.FingerprintGuid;
ResetView();
}
};
}
<span style="color: blue;">public</span> ObservableCollection<BlockMonitorRowViewModel> Blocks => _blocks;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> BlockCount => _blocks.Count;
<span style="color: blue;">public</span> RelayCommand AddBlockCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ResetView</span>()
{
_blocks.Clear();
<span style="color: #8f08c4;">if</span> (_monitoredBlocks.ContainsKey(_currentDbGuid))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blks</span> = _monitoredBlocks[_currentDbGuid];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> blks)
{
_blocks.Add(<span style="color: blue;">new</span> BlockMonitorRowViewModel(blk.ObjectId, blk.Rotation));
}
}
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddBlockToDashboard</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">window</span> = cmdParam <span style="color: blue;">as</span> System.Windows.Window;
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkId</span> = ObjectId.Null;
<span style="color: #8f08c4;">try</span>
{
<span style="color: #8f08c4;">if</span> (window != <span style="color: blue;">null</span>) window.Visibility = System.Windows.Visibility.Hidden;
CadApp.MainWindow.Focus();
<span style="color: #8f08c4;">while</span> (<span style="color: blue;">true</span>)
{
blkId = CadHelper.SelectBlock();
<span style="color: #8f08c4;">if</span> (!blkId.IsNull)
{
<span style="color: #8f08c4;">if</span> (_monitoredBlocks.IsDoubleSelected(blkId))
{
CadHelper.WriteMessage(
<span style="color: #a31515;">"\nThe block has already been listed in rotation dashboard!"</span>);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
}
<span style="color: #8f08c4;">finally</span>
{
<span style="color: #8f08c4;">if</span> (window != <span style="color: blue;">null</span>) window.Visibility =
System.Windows.Visibility.Visible;
}
<span style="color: #8f08c4;">if</span> (blkId == ObjectId.Null) <span style="color: #8f08c4;">return</span>;
_monitoredBlocks.AddBlock(blkId);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockAdded</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span>)
{
_blocks.Add(<span style="color: blue;">new</span> BlockMonitorRowViewModel(blkId, rotation));
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: #066555;">// This method gets called when block reference in drawing is erased</span>
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRemoved</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> _blocks)
{
<span style="color: #8f08c4;">if</span> (blk.BlockId == blkId)
{
_blocks.Remove(blk);
<span style="color: #8f08c4;">break</span>;
}
}
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: #066555;">// This method gets called when user clicks "remove" button in the UI</span>
<span style="color: #066555;">// which will stop the block's rotation being monitored</span>
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRemove</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
_monitoredBlocks.RemoveBlock(blkId);
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> _blocks)
{
<span style="color: #8f08c4;">if</span> (blk.BlockId == blkId)
{
_blocks.Remove(blk);
<span style="color: #8f08c4;">break</span>;
}
}
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockZoom</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
CadHelper.ZoomToEntity(blkId);
}
<span style="color: #066555;">// this method gets called when the monitored block is rotated in</span>
<span style="color: #066555;">// AutoCAD editor</span>
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRotated</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span>)
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">row</span> <span style="color: #8f08c4;">in</span> _blocks)
{
<span style="color: #8f08c4;">if</span> (row.BlockId==blkId)
{
row.Rotation = rotation;
<span style="color: #8f08c4;">break</span>;
}
}
}
<span style="color: #066555;">// This method gets called when user changes Slider's value in the UI</span>
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRotate</span>(ObjectId <span style="color: #1f377f;">bkId</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span>)
{
<span style="color: #8f08c4;">try</span>
{
MonitoredBlocks.ChangedfromUI = <span style="color: blue;">true</span>;
CadHelper.RotateBlock(bkId, rotation);
}
<span style="color: #8f08c4;">finally</span>
{
MonitoredBlocks.ChangedfromUI = <span style="color: blue;">false</span>;
}
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre><p>Then finally the CommandClass/Method, which is the same as the previous project:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(BlockRotationDashboard.MyCommands))]
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">static</span> MonitoredBlocks _blocks = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> BlockMonitorView _view = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> BlockMonitorViewModel _viewModel = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"MonitorBlockRotation"</span>, CommandFlags.Session)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">MonitorBlocks</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: #8f08c4;">if</span> (_blocks == <span style="color: blue;">null</span>)
{
_blocks = <span style="color: blue;">new</span> MonitoredBlocks();
}
<span style="color: #8f08c4;">if</span> (_view == <span style="color: blue;">null</span>)
{
_viewModel = <span style="color: blue;">new</span> BlockMonitorViewModel(_blocks);
_view = <span style="color: blue;">new</span> BlockMonitorView(_viewModel);
CadApp.ShowModelessWindow(_view);
}
<span style="color: #8f08c4;">else</span>
{
_view.Visibility = System.Windows.Visibility.Visible;
}
}
}
}
</pre><p>Thanks to the use of MVVM pattern, the changed "business requirement" of monitoring the blocks' change in AutoCAD editor and reflecting the changes in the modeless UI is rather easy: the UI code (XAML code and the bit of code-behind) remains unchanged at all; so is the code for AutoCAD operation in class "CadHelper". I only updated the "Model" (class MonitoredBlocks) to have it hook up with Database_ObjectModified/ObjectErase events, so that when monitored change happens, the "Model" notifies the "ViewModel" and then the "View" gets updated.</p><p>If a Win Form is used, it is likely the UI's code-behind has to be updated, unless certain design pattern is used, such as model-view-controller/presenter that are suitable to Win form. I used such pattern with Win Form before and they are no match to MVVM pattern for WPF.</p><p>See the video clip showing the updated action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxT_JLlM9nCDX8yTHDDCon_NfTEOWSWAAsfIWksyW1K53OFPOiQuX3URribOZPmWEadP9h_AoocNproXiem7Q' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>The source code can be downloaded <a href="https://drive.google.com/file/d/1HX5_Hl_qlvPNWQFbyzsesptPk1f32lOZ/view?usp=sharing" target="_blank">here</a>.</p><p><b><span style="color: red;">Bug Fixing Update - 2022-09-29</span></b></p><div style="text-align: left;">In the <a href="https://forums.autodesk.com/t5/net/control-in-wpf-modeless-for-binding-the-properties-of-an-entity/td-p/11436051" target="_blank">discussion thread</a> which inspired me for this article and the <a href="https://drive-cad-with-code.blogspot.com/2022/09/update-entities-via-data-binding-of.html" target="_blank">previous article</a>, one reader (thank you, <span style="background-color: white;"><span style="font-family: inherit;">Genésio Hanauer!</span><span style="font-family: inherit; font-size: x-small;">)</span></span> pointed out that Database.FingerpointGuid is not unique if the drawings opened in the AutoCAD session are originated from the same drawing (such as they are created from the same template drawing), thus using Database.FingerprintGuid as the key for the Dictionary to store data model in order to segregate data from drawings could result error. So, I updated the class <i>MonitoredBlocks </i>to use <i>Database.UnmanagedObject</i>, which is the type of <i>IntPtr</i>, as the Dictionary's key. Here is the updated code:</div><pre style="background: white; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: black;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">namespace</span> BlockRotationDashboard
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MonitoredBlock</span>
{
<span style="color: blue;">public</span> ObjectId ObjectId { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = ObjectId.Null;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> Rotation { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } = 0;
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MonitoredBlocks</span> : Dictionary<</span><span style="color: red;">IntPtr</span>, List<MonitoredBlock>>
{
<span style="color: blue;">public</span> <span style="color: #066555;">MonitoredBlocks</span>()
{
Application.DocumentManager.DocumentToBeDestroyed += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
<span style="color: #8f08c4;">if</span> (ContainsKey(e.Document.Database.UnmanagedObject))
{
RemoveDatabaseEventHandlers(e.Document.Database);
Remove(e.Document.Database.UnmanagedObject);
}
};
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">int</span>> BlockAdded;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> BlockRemoved;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">int</span>> BlockRotated;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> ChangedfromUI = <span style="color: blue;">false</span>;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">db</span> = blkId.Database;
<span style="color: blue;">int</span> <span style="color: #1f377f;">rotation</span> = 0;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = db.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> = tran.GetObject(blkId, OpenMode.ForRead) <span style="color: blue;">as</span> BlockReference;
<span style="color: #8f08c4;">if</span> (blk!=<span style="color: blue;">null</span>) rotation= Convert.ToInt32(blk.Rotation * 180 / Math.PI);
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = <span style="color: red;">db.UnmanagedObject</span>;
<span style="color: #8f08c4;">if</span> (!ContainsKey(key))
{
Add(key, <span style="color: blue;">new</span> List<MonitoredBlock>());
HookUpDatabaseEventHandlers(blkId.Database);
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">mblk</span> = <span style="color: blue;">new</span> MonitoredBlock { ObjectId = blkId, Rotation = rotation };
<span style="color: blue;">this</span>[key].Add(mblk);
BlockAdded?.Invoke(blkId, rotation);
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = <span style="color: red;">blkId.Database.UnmanagedObject</span>;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blks</span> = <span style="color: blue;">this</span>[key];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> blks)
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId == blkId)
{
blks.Remove(blk);
<span style="color: #8f08c4;">return</span>;
}
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsDoubleSelected</span>(
ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = <span style="color: red;">blkId.Database.UnmanagedObject</span>;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> <span style="color: blue;">this</span>[key])
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId == blkId) <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">HookUpDatabaseEventHandlers</span>(Database <span style="color: #1f377f;">db</span>)
{
db.ObjectModified += Db_ObjectModified;
db.ObjectErased += Db_ObjectErased;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Db_ObjectErased</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, ObjectErasedEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">entId</span> = e.DBObject.ObjectId;
<span style="color: #8f08c4;">if</span> (ContainsKey(<span style="color: red;">entId.Database.UnmanagedObject</span>))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blks</span> = <span style="color: blue;">this</span>[entId.Database.UnmanagedObject];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> blks)
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId == entId)
{
blks.Remove(blk);
BlockRemoved?.Invoke(entId);
<span style="color: #8f08c4;">break</span>;
}
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Db_ObjectModified</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, ObjectEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: #8f08c4;">if</span> (ChangedfromUI) <span style="color: #8f08c4;">return</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">entId</span> = e.DBObject.ObjectId;
<span style="color: #8f08c4;">if</span> (ContainsKey(<span style="color: red;">entId.Database.UnmanagedObject</span>))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blks</span> = <span style="color: blue;">this</span>[entId.Database.UnmanagedObject];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> blks)
{
<span style="color: #8f08c4;">if</span> (blk.ObjectId==entId)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">rotate</span> = Convert.ToInt32(((BlockReference)e.DBObject).Rotation * 180 / Math.PI);
<span style="color: #8f08c4;">if</span> (rotate != blk.Rotation)
{
blk.Rotation= rotate;
BlockRotated?.Invoke(blk.ObjectId, rotate);
}
<span style="color: #8f08c4;">break</span>;
}
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveDatabaseEventHandlers</span>(Database <span style="color: #1f377f;">db</span>)
{
<span style="color: #8f08c4;">try</span>
{
db.ObjectErased -= Db_ObjectErased;
db.ObjectModified -= Db_ObjectModified;
}
<span style="color: #8f08c4;">catch</span> { }
}
}
}
</pre><p>I have also updated the source that can be downloaded in the link above.</p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com4tag:blogger.com,1999:blog-993393844516183990.post-77607683183342761372022-09-23T18:19:00.002-07:002022-09-23T18:19:39.643-07:00Update Entities via Data Binding of Modeless WPF Window<p>This article is in response to a question asked in AutoCAD .NET API discussion forum. </p><p>In last a few year, I use WPF as the main UI option in my AutoCAD .NET API development, only occasionally use Win Form for very simple UI for collecting user inputs. While one can use WPF in about the same way as with Win Form (e.g. writing a lot of code behind the UI to control the behaviors of the UI), the real advantages of using WPF are both its rich UI presentation capability and its data binding. That latter allows us programmer to easily separate the UI and business options, usually through a well-known design pattern MVVM (Model-View-ViewModel). </p><p>The AutoCAD .NET project in this article does almost exactly the work as the question asked in the discussion forum:</p><p>1. Create a WPF modeless form;</p><p>2. On the form there are some WPF UserControls, which are bound to certain AutoCAD entities in drawing; In this project, the UserControls are WPF Slider controls, the entities are block refeeneces in drawing;</p><p>3. CAD user can change the slider's tick, hence the slider's value changes with a given range (0 to 360, in this case). The slider's Value property is bound to a block reference's Rotation property. Therefore, the the slider's value changes, the corresponding block reference in drawing rotates accordingly.</p><p>Let's dive into the code.</p><p>First some very basic code almost all WPF project needs: the implementation of <i>ICommand</i> and <i>INotifyPropertyChanged</i>:</p><p></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.ComponentModel;
<span style="color: blue;">using</span> System.Runtime.CompilerServices;
<span style="color: blue;">using</span> System.Windows.Input;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">RelayCommand</span> : ICommand
{
<span style="color: blue;">private</span> Action<<span style="color: blue;">object</span>> execute;
<span style="color: blue;">private</span> Func<<span style="color: blue;">object</span>, <span style="color: blue;">bool</span>> canExecute;
<span style="color: blue;">public</span> <span style="color: blue;">event</span> EventHandler CanExecuteChanged
{
<span style="color: blue;">add</span> { CommandManager.RequerySuggested += value; }
<span style="color: blue;">remove</span> { CommandManager.RequerySuggested -= value; }
}
<span style="color: blue;">public</span> <span style="color: #066555;">RelayCommand</span>(Action<<span style="color: blue;">object</span>> <span style="color: #1f377f;">execute</span>, Func<<span style="color: blue;">object</span>, <span style="color: blue;">bool</span>> <span style="color: #1f377f;">canExecute</span> = <span style="color: blue;">null</span>)
{
<span style="color: blue;">this</span>.execute = execute;
<span style="color: blue;">this</span>.canExecute = canExecute;
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">CanExecute</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">parameter</span>)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">this</span>.canExecute == <span style="color: blue;">null</span> || <span style="color: blue;">this</span>.canExecute(parameter);
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Execute</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">parameter</span>)
{
<span style="color: blue;">this</span>.execute(parameter);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">abstract</span> <span style="color: blue;">class</span> <span style="color: #066555;">ViewModelBase</span> : INotifyPropertyChanged
{
<span style="color: blue;">public</span> <span style="color: blue;">event</span> PropertyChangedEventHandler PropertyChanged;
<span style="color: blue;">protected</span> <span style="color: blue;">virtual</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnPropertyChanged</span>([CallerMemberName] <span style="color: blue;">string</span> <span style="color: #1f377f;">propertyName</span> = <span style="color: blue;">null</span>)
{
PropertyChanged?.Invoke(<span style="color: blue;">this</span>, <span style="color: blue;">new</span> PropertyChangedEventArgs(propertyName));
}
}
}</pre><p>Now the code for "Model" part of the Model-View-ViewModel pattern, class <i>ControlledBlocks</i>:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">ControlledBlocks</span> : Dictionary<<span style="color: blue;">string</span>, List<ObjectId>>
{
<span style="color: blue;">public</span> <span style="color: #066555;">ControlledBlocks</span>()
{
Application.DocumentManager.DocumentToBeDestroyed += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
<span style="color: #8f08c4;">if</span> (ContainsKey(e.Document.Database.FingerprintGuid))
{
Remove(e.Document.Database.FingerprintGuid);
}
};
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">double</span>> BlockAdded;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = blkId.Database.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (!ContainsKey(key))
{
Add(key, <span style="color: blue;">new</span> List<ObjectId>());
}
<span style="color: blue;">this</span>[key].Add(blkId);
<span style="color: blue;">var</span> <span style="color: #1f377f;">rotation</span> = 0.0;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span>=
blkId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span>=(BlockReference)tran.GetObject(blkId, OpenMode.ForRead);
rotation= blk.Rotation;
tran.Commit();
}
BlockAdded?.Invoke(blkId, rotation);
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = blkId.Database.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkIds</span> = <span style="color: blue;">this</span>[key];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> blkIds)
{
<span style="color: #8f08c4;">if</span> (id==blkId)
{
blkIds.Remove(id);
<span style="color: #8f08c4;">return</span>;
}
}
}
}
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">IsDoubleSelected</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">key</span> = blkId.Database.FingerprintGuid;
<span style="color: #8f08c4;">if</span> (ContainsKey(key))
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">this</span>[key].Contains(blkId);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
}
}</pre><p>Because the WPF UI will be modeless, the data (selected block references, which rotation will be controlled by the modeless UI) should be organized in per-document/database base. I use a <i>List<ObjectId></i> to hold selected block reference IDs and use each document's <i>Database.FingerprintGuid </i>property as the Dictionary's key. I could have used <i>Document</i>, or <i>Document.UnmanagedObject</i> as the key, but considering <i>ObjectId</i> has a property<i> Database,</i> using <i>Database.FingerprintGuid</i> is more convenient, which would become quite obvious if one looks into the code of this class. An static instance of this class is kept alive in entire AutoCAD session. So, inside the class, and event handle is hooked up to <i>DocumentCollection.DocumentToBeDestroyed</i>, so that when an open drawing is about to be closed down, the data corresponding to this document/database is cleared. One would also notice a public Action BlockAdded is exposed as a static property. If it is assigned somewhere (in the ViewModel, in this case), whenever a block reference is added into this data model, this action is invoked (i.e. a method in ViewModel, which would update the UI to show the newly added block reference). This is a simplified version of the usual event handler delegate/event argument/event approach.</p><p>Now the "View" part of the Model-View-ViewModel. The UI, as a modeless window, looks like:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9Ems_03s_q0Xo80KzRflnGoj_FYCxQLl7tFe4cLZDO1Z6kLo5yoDDSd1ZFXXWwQ8HjzBpc6gFluFms3-F683vwBXqipoPYYftFEiLmv2Ff6sDb7cuR5f3QKG4a5nISsDZSygHxgbGLLiBx19v4xv1KjIm6oFsdw-v2pcVbxZ78-JUM9CZe6Hd3rc3/s566/WfpEntityBinding.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="566" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9Ems_03s_q0Xo80KzRflnGoj_FYCxQLl7tFe4cLZDO1Z6kLo5yoDDSd1ZFXXWwQ8HjzBpc6gFluFms3-F683vwBXqipoPYYftFEiLmv2Ff6sDb7cuR5f3QKG4a5nISsDZSygHxgbGLLiBx19v4xv1KjIm6oFsdw-v2pcVbxZ78-JUM9CZe6Hd3rc3/w400-h150/WfpEntityBinding.png" width="400" /></a></div><p>The main view is <i>BlockRotationView</i> class, its XAML code is as following:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;"><</span><span style="color: #a31515;">Window</span><span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Class</span><span style="color: blue;">=</span><span style="color: blue;">"WPFBindingEntities.BlockRotationView"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">x</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">mc</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">d</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">local</span><span style="color: blue;">=</span><span style="color: blue;">"clr-namespace:WPFBindingEntities"</span>
<span style="color: maroon;"> mc</span><span style="color: blue;">:</span><span style="color: maroon;">Ignorable</span><span style="color: blue;">=</span><span style="color: blue;">"d"</span>
<span style="color: maroon;"> Closing</span><span style="color: blue;">=</span><span style="color: blue;">"TheWindow_Closing"</span>
<span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Name</span><span style="color: blue;">=</span><span style="color: blue;">"TheWindow"</span>
<span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"580"</span><span style="color: maroon;"> SizeToContent</span><span style="color: blue;">=</span><span style="color: blue;">"Height"</span><span style="color: maroon;"> ShowInTaskbar</span><span style="color: blue;">=</span><span style="color: blue;">"False"</span><span style="color: maroon;"> ResizeMode</span><span style="color: blue;">=</span><span style="color: blue;">"NoResize"</span>
<span style="color: maroon;"> Title</span><span style="color: blue;">=</span><span style="color: blue;">"Block Rotation Dashboard"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.RowDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">RowDefinition</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">RowDefinition</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"150"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.RowDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: maroon;"> Grid.Row</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"5,0,5,0"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"200*"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Monitored Blocks:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockCount</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span>
<span style="color: maroon;"> FontWeight</span><span style="color: blue;">=</span><span style="color: blue;">"DemiBold"</span><span style="color: maroon;"> Foreground</span><span style="color: blue;">=</span><span style="color: blue;">"Blue"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Add Block >"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"80"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> AddBlockCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span>
<span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> ElementName</span><span style="color: blue;">=</span><span style="color: blue;">TheWindow</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ListBox</span><span style="color: maroon;"> Grid.Row</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"5,5,5,5"</span>
<span style="color: maroon;"> ItemsSource</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Blocks</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> HorizontalContentAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Stretch"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ListBox.ItemTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">local</span><span style="color: blue;">:</span><span style="color: #a31515;">BlockRotationRowView</span><span style="color: maroon;"> DataContext</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;"> /></span>
<span style="color: blue;"></</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">ListBox.ItemTemplate</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">ListBox</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Window</span><span style="color: blue;">></span>
</pre><p>As the window's XMAL code shows, the items in the list box is custom WPF UserControls of class <i>BlockRotationRowView</i>, its XAML code is as following:<br /></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;"><</span><span style="color: #a31515;">UserControl</span><span style="color: maroon;"> x</span><span style="color: blue;">:</span><span style="color: maroon;">Class</span><span style="color: blue;">=</span><span style="color: blue;">"WPFBindingEntities.BlockRotationRowView"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">x</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">mc</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">d</span><span style="color: blue;">=</span><span style="color: blue;">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span style="color: maroon;"> xmlns</span><span style="color: blue;">:</span><span style="color: maroon;">local</span><span style="color: blue;">=</span><span style="color: blue;">"clr-namespace:WPFBindingEntities"</span>
<span style="color: maroon;"> mc</span><span style="color: blue;">:</span><span style="color: maroon;">Ignorable</span><span style="color: blue;">=</span><span style="color: blue;">"d"</span>
<span style="color: maroon;"> d</span><span style="color: blue;">:</span><span style="color: maroon;">DesignHeight</span><span style="color: blue;">=</span><span style="color: blue;">"36"</span><span style="color: maroon;"> d</span><span style="color: blue;">:</span><span style="color: maroon;">DesignWidth</span><span style="color: blue;">=</span><span style="color: blue;">"500"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"36"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"100*"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"70"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">ColumnDefinition</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"70"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid.ColumnDefinitions</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"0"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Handle:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;"> /></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Handle</span><span style="color: blue;">,</span><span style="color: maroon;"> Mode</span><span style="color: blue;">=</span><span style="color: blue;">OneTime</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"1"</span><span style="color: maroon;"> Orientation</span><span style="color: blue;">=</span><span style="color: blue;">"Horizontal"</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">=</span><span style="color: blue;">"Rotation:"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: blue;"> /></span>
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: maroon;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Rotation</span><span style="color: blue;">,</span><span style="color: maroon;"> Mode</span><span style="color: blue;">=</span><span style="color: blue;">OneWay</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Margin</span><span style="color: blue;">=</span><span style="color: blue;">"10,0,0,0"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Slider</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"2"</span><span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> Minimum</span><span style="color: blue;">=</span><span style="color: blue;">"0.0"</span><span style="color: maroon;"> Maximum</span><span style="color: blue;">=</span><span style="color: blue;">"360.0"</span>
<span style="color: maroon;"> Interval</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: maroon;"> IsSnapToTickEnabled</span><span style="color: blue;">=</span><span style="color: blue;">"True"</span><span style="color: maroon;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> Rotation</span><span style="color: blue;">}</span><span style="color: blue;">"</span>
<span style="color: maroon;"> TickPlacement</span><span style="color: blue;">=</span><span style="color: blue;">"Both"</span><span style="color: maroon;"> LargeChange</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: maroon;"> SmallChange</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: maroon;"> TickFrequency</span><span style="color: blue;">=</span><span style="color: blue;">"30"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"3"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Zoom"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"60"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> ZoomCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockId</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"><</span><span style="color: #a31515;">Button</span><span style="color: maroon;"> Grid.Column</span><span style="color: blue;">=</span><span style="color: blue;">"4"</span><span style="color: maroon;"> Content</span><span style="color: blue;">=</span><span style="color: blue;">"Remove"</span><span style="color: maroon;"> Width</span><span style="color: blue;">=</span><span style="color: blue;">"60"</span><span style="color: maroon;"> Height</span><span style="color: blue;">=</span><span style="color: blue;">"22"</span>
<span style="color: maroon;"> VerticalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Center"</span><span style="color: maroon;"> HorizontalAlignment</span><span style="color: blue;">=</span><span style="color: blue;">"Right"</span>
<span style="color: maroon;"> Command</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> RemoveCommand</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: maroon;"> CommandParameter</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: maroon;"> BlockId</span><span style="color: blue;">}</span><span style="color: blue;">"</span><span style="color: blue;">/></span>
<span style="color: blue;"></</span><span style="color: #a31515;">Grid</span><span style="color: blue;">></span>
<span style="color: blue;"></</span><span style="color: #a31515;">UserControl</span><span style="color: blue;">></span>
</pre><p>The XAML code is almost all I need here for the "view", except for only a few lines of C# code-behind:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System.Windows;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"><</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> Interaction logic for BlockRatationView.xaml</span>
<span style="color: #5b5b5b;">///</span><span style="color: #066555;"> </span><span style="color: #5b5b5b;"></</span><span style="color: #5b5b5b;">summary</span><span style="color: #5b5b5b;">></span>
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockRotationView</span> : Window
{
<span style="color: blue;">public</span> <span style="color: #066555;">BlockRotationView</span>()
{
InitializeComponent();
}
<span style="color: blue;">public</span> <span style="color: #066555;">BlockRotationView</span>(BlockRotationViewModel <span style="color: #1f377f;">viewModel</span>):<span style="color: blue;">this</span>()
{
Loaded += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
DataContext = viewModel;
};
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">TheWindow_Closing</span>(
<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, System.ComponentModel.CancelEventArgs <span style="color: #1f377f;">e</span>)
{
e.Cancel = <span style="color: blue;">true</span>;
Visibility = Visibility.Hidden;
}
}
}</pre><p>The overloaded view constructor is to make the view data-bound to the view model, while the handling of <i>TheWindow_Closing</i> event is necessary because of the window being modeless: the Closing event handle makes the window effectively behaves as singleton instance.</p><p>Now there is "ViewModel" class corresponding to the "View" in the "Model-View-ViewModel": class <i>BlockRotationViewModel</i> and <i>BlockRotationRowViewModel</i>.</p><p>This the main view model <i>BlockRotationViewModel</i> class:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System.Collections.ObjectModel;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockRotationViewModel</span> : ViewModelBase
{
<span style="color: blue;">private</span> ControlledBlocks _monitoredBlocks;
<span style="color: blue;">private</span> ObservableCollection<BlockRotationRowViewModel> _blocks;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _currentDbGuid = <span style="color: #a31515;">""</span>;
<span style="color: blue;">public</span> <span style="color: #066555;">BlockRotationViewModel</span>(ControlledBlocks <span style="color: #1f377f;">monitoredBlocks</span>)
{
_monitoredBlocks = monitoredBlocks;
_blocks = <span style="color: blue;">new</span> ObservableCollection<BlockRotationRowViewModel>();
AddBlockCommand = <span style="color: blue;">new</span> RelayCommand(
AddBlockToDashboard, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
ControlledBlocks.BlockAdded = OnBlockAdded;
BlockRotationRowViewModel.RemoveBlockAction = OnBlockRemove;
BlockRotationRowViewModel.ZoomAction = OnBlockZoom;
BlockRotationRowViewModel.RotateBlockAction = OnBlockRotate;
_currentDbGuid =
CadApp.DocumentManager.MdiActiveDocument.Database.FingerprintGuid;
ResetView();
CadApp.DocumentManager.DocumentActivated += (<span style="color: #1f377f;">o</span>, <span style="color: #1f377f;">e</span>) =>
{
<span style="color: #8f08c4;">if</span> (e.Document.Database.FingerprintGuid != _currentDbGuid)
{
_currentDbGuid = e.Document.Database.FingerprintGuid;
ResetView();
}
};
}
<span style="color: blue;">public</span> ObservableCollection<BlockRotationRowViewModel> Blocks => _blocks;
<span style="color: blue;">public</span> <span style="color: blue;">int</span> BlockCount => _blocks.Count;
<span style="color: blue;">public</span> RelayCommand AddBlockCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: #5b5b5b;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ResetView</span>()
{
_blocks.Clear();
<span style="color: #8f08c4;">if</span> (_monitoredBlocks.ContainsKey(_currentDbGuid))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkIds</span> = _monitoredBlocks[_currentDbGuid];
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">Id</span> <span style="color: #8f08c4;">in</span> blkIds)
{
_blocks.Add(<span style="color: blue;">new</span> BlockRotationRowViewModel(Id, 0.0));
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddBlockToDashboard</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">window</span> = cmdParam <span style="color: blue;">as</span> System.Windows.Window;
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkId</span> = ObjectId.Null;
<span style="color: #8f08c4;">try</span>
{
<span style="color: #8f08c4;">if</span> (window != <span style="color: blue;">null</span>) window.Visibility = System.Windows.Visibility.Hidden;
Application.MainWindow.Focus();
<span style="color: #8f08c4;">while</span> (<span style="color: blue;">true</span>)
{
blkId = CadHelper.SelectBlock();
<span style="color: #8f08c4;">if</span> (!blkId.IsNull)
{
<span style="color: #8f08c4;">if</span> (_monitoredBlocks.IsDoubleSelected(blkId))
{
CadHelper.WriteMessage(
<span style="color: #a31515;">"\nThe block has already been listed in rotation dashboard!"</span>);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
}
<span style="color: #8f08c4;">finally</span>
{
<span style="color: #8f08c4;">if</span> (window != <span style="color: blue;">null</span>) window.Visibility =
System.Windows.Visibility.Visible;
}
<span style="color: #8f08c4;">if</span> (blkId == ObjectId.Null) <span style="color: #8f08c4;">return</span>;
_monitoredBlocks.AddBlock(blkId);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockAdded</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">rotation</span>)
{
_blocks.Add(<span style="color: blue;">new</span> BlockRotationRowViewModel(blkId, rotation));
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRemove</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
_monitoredBlocks.RemoveBlock(blkId);
<span style="color: #8f08c4;">foreach</span> (var <span style="color: #1f377f;">blk</span> <span style="color: #8f08c4;">in</span> _blocks)
{
<span style="color: #8f08c4;">if</span> (blk.BlockId== blkId)
{
_blocks.Remove(blk);
<span style="color: #8f08c4;">break</span>;
}
}
OnPropertyChanged(<span style="color: #a31515;">"BlockCount"</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockZoom</span>(ObjectId <span style="color: #1f377f;">blkId</span>)
{
CadHelper.ZoomToEntity(blkId);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">OnBlockRotate</span>(ObjectId <span style="color: #1f377f;">bkId</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">rotation</span>)
{
CadHelper.RotateBlock(bkId, rotation);
}
<span style="color: #5b5b5b;">#endregion</span>
}
}
</pre><p>And this is <i>BlockRotationRowViewModel</i> class:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">BlockRotationRowViewModel</span> : ViewModelBase
{
<span style="color: blue;">private</span> <span style="color: blue;">double</span> _rotation = 0.0;
<span style="color: blue;">private</span> ObjectId _blkId = ObjectId.Null;
<span style="color: blue;">public</span> <span style="color: #066555;">BlockRotationRowViewModel</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">rotation</span>)
{
_blkId = blkId;
_rotation= rotation;
RemoveCommand = <span style="color: blue;">new</span> RelayCommand(
RemoveBlock, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
ZoomCommand = <span style="color: blue;">new</span> RelayCommand(
ZoomToBlock, (<span style="color: #1f377f;">o</span>) => { <span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>; });
}
<span style="color: blue;">public</span> <span style="color: blue;">string</span> Handle => _blkId.Handle.ToString();
<span style="color: blue;">public</span> ObjectId BlockId => _blkId;
<span style="color: blue;">public</span> <span style="color: blue;">double</span> Rotation
{
<span style="color: blue;">get</span> => _rotation;
<span style="color: blue;">set</span>
{
_rotation = value;
OnPropertyChanged(<span style="color: #a31515;">"Rotation"</span>);
RotateBlockAction?.Invoke(_blkId, _rotation);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> RemoveBlockAction;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId> ZoomAction;
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Action<ObjectId, <span style="color: blue;">double</span>> RotateBlockAction;
<span style="color: blue;">public</span> RelayCommand RemoveCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> RelayCommand ZoomCommand { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RemoveBlock</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
RemoveBlockAction?.Invoke(_blkId);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToBlock</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">cmdParam</span>)
{
ZoomAction?.Invoke(_blkId);
}
}
}
</pre><p>Of course, all the AutoCAD related operations are organized in a separate class <i>CadHelper</i>, exposed as static methods:</p><p><br /></p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">CadHelper</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> ObjectId <span style="color: #74531f;">SelectBlock</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = Application.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect a block:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid selection: not a block!"</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(BlockReference), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = dwg.Editor.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
<span style="color: #8f08c4;">return</span> res.ObjectId;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> ObjectId.Null;
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">WriteMessage</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">msg</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = Application.DocumentManager.MdiActiveDocument;
dwg.Editor.WriteMessage(<span style="color: #a31515;">$"</span>{msg}<span style="color: #a31515;">"</span>);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToEntity</span>(ObjectId <span style="color: #1f377f;">entId</span>)
{
Extents3d <span style="color: #1f377f;">ext</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
entId.Database.TransactionManager.StartOpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(entId, OpenMode.ForRead);
ext = ent.GeometricExtents;
tran.Commit();
}
ZoomToExtents(ext, 1.0);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ZoomToExtents</span>(Extents3d <span style="color: #1f377f;">ext</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">marginRatio</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">w</span> = ext.MaxPoint.X - ext.MinPoint.X;
<span style="color: blue;">var</span> <span style="color: #1f377f;">h</span>= ext.MaxPoint.Y - ext.MinPoint.Y;
<span style="color: blue;">var</span> <span style="color: #1f377f;">delta</span> = Math.Max(w, h) * marginRatio;
<span style="color: blue;">var</span> <span style="color: #1f377f;">minPt</span> = <span style="color: blue;">new</span>[]
{
ext.MinPoint.X-delta, ext.MinPoint.Y-delta, ext.MinPoint.Z
};
<span style="color: blue;">var</span> <span style="color: #1f377f;">maxPt</span> = <span style="color: blue;">new</span>[]
{
ext.MaxPoint.X+delta, ext.MaxPoint.Y+delta, ext.MaxPoint.Z
};
dynamic <span style="color: #1f377f;">comApp</span> =
Autodesk.AutoCAD.ApplicationServices.Application.AcadApplication;
comApp.ZoomWindow(minPt, maxPt);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RotateBlock</span>(ObjectId <span style="color: #1f377f;">blkId</span>, <span style="color: blue;">double</span> <span style="color: #1f377f;">rotation</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: #8f08c4;">if</span> (dwg.Database.FingerprintGuid!=blkId.Database.FingerprintGuid)
{
<span style="color: #8f08c4;">throw</span> <span style="color: blue;">new</span> InvalidOperationException(
<span style="color: #a31515;">"Entity is not from current drawing database!"</span>);
}
<span style="color: blue;">using</span> (dwg.LockDocument())
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> =
blkId.Database.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">blk</span> =
(BlockReference)tran.GetObject(blkId, OpenMode.ForWrite);
blk.Rotation = Math.PI * rotation / 180.0;
tran.Commit();
}
}
CadApp.UpdateScreen();
}
}
}
</pre><p>Finally, this the CommandClass, which keeps the data model (<i>ControlledBlocks</i> class), view (<i>BlockRotationView</i> class) and viewmodel (<i>BlockRotationViewModel</i> class) in the Application context as static instance (singleton) because of the view is required to be modeless window:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(WPFBindingEntities.MyCommands))]
<span style="color: blue;">namespace</span> WPFBindingEntities
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">MyCommands</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">static</span> ControlledBlocks _blocks = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> BlockRotationView _view = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> BlockRotationViewModel _viewModel = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"BlockRotation"</span>, CommandFlags.Session)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">MonitorBlocks</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: #8f08c4;">if</span> (_blocks==<span style="color: blue;">null</span>)
{
_blocks = <span style="color: blue;">new</span> ControlledBlocks();
}
<span style="color: #8f08c4;">if</span> (_view == <span style="color: blue;">null</span>)
{
_viewModel = <span style="color: blue;">new</span> BlockRotationViewModel(_blocks);
_view = <span style="color: blue;">new</span> BlockRotationView(_viewModel);
CadApp.ShowModelessWindow(_view);
}
<span style="color: #8f08c4;">else</span>
{
_view.Visibility = System.Windows.Visibility.Visible;
}
}
}
}
</pre><p>As one can see, doing this project with MVVM pattern, the entire operation is fairly well separated into Model, View, and ViewModel, taking advantage of WPF data binding.</p><p>As usual, see the video clip below showing the code in action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwGYAoGIx2otoUu-FPU3dMg6KBmNqj1wbvVuyXEMd1kA8yuzE7hvw1CY4TiN0Vg-i92VVmdPeFRKMnl823Eig' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>While all the code in the project are posted inside this article, the entire source code can be <a href="https://drive.google.com/file/d/1oEs1k0HCXPjpNutS4AVYX2JoIgZ7H5ux/view?usp=sharing" target="_blank">downloaded here</a>. It is VisualStudio2022 solution and tested against AutoCAD 2022, but it should work with all later versions of AutoCAD.</p><p><br /></p><p><br /></p><p><br /></p><p></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-72423225483065877252022-08-22T20:36:00.000-07:002022-08-22T20:36:09.764-07:00Generating Drawing Thumbnail/Preview Image in Side Database<p>With AutoCAD .NET API, it is quite common practice that we use side database to create a new drawing file, or update an existing drawing file. One of the issues with using side database to update drawing file is that the thumbnail/preview image in the drawing file either does not exist, or get lost when the drawing file opened as side database is saved (via Database.SaveAs() method).</p><p><a href="https://forums.autodesk.com/t5/net/capture-brand-new-thumbnail-of-dwg-on-side-database/td-p/10103404" target="_blank">A recent post </a>in the .NET API discussion forum asks how to generate drawing file's thumbnail image in side database, This led me to recall I post an <a href="https://drive-cad-with-code.blogspot.com/2020/12/obtaining-blocks-image.html" target="_blank">article here</a> a while ago about how to get the image of a block. I thought it might be used as solution for this newly raised question. So, I fired up Visual Studio and put together some code quickly to see if it is doable.</p><p>If you have read my previous article, you can see I used <i>Autodesk.AutoCAD.Internal.Utils.GetBlockImage()</i> to generate a block image. This method takes an <i>ObjectId</i> of a <i>BlockTableRecord</i>. So, I thought, if I want to generate a image of the drawing, it means the <i>BlockTableRecord</i> should be the <i>ModelSpace</i>'s ObjectId. With this in mind, here is my code that shows how to create a new drawing file with a side database. When the side database is created, some entities are added into <i>ModelSpace</i>, and then the image of the <i>ModelSpace </i>is created and assigned to <i>Database.ThumbnailBitmap</i> property before the side database is saved as drawing file.</p><p>Here is the code for using side database to create a drawing file:</p>
<p>
</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Internal;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> MiscTest
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #066555;">SideDbCreation</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateDrawFromSideDatabase</span>(
<span style="color: blue;">string</span> <span style="color: #1f377f;">fileName</span>, Action<Database> <span style="color: #1f377f;">addEntityAction</span>, <span style="color: blue;">bool</span> <span style="color: #1f377f;">createThumnail</span> = <span style="color: blue;">true</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">db</span> = <span style="color: blue;">new</span> Database(<span style="color: blue;">true</span>, <span style="color: blue;">true</span>))
{
<span style="color: #8f08c4;">if</span> (addEntityAction != <span style="color: blue;">null</span>)
{
addEntityAction(db);
<span style="color: #8f08c4;">if</span> (createThumnail)
{
CreateThumnailInSideDatabase(db);
}
}
<span style="color: #8f08c4;">if</span> (System.IO.File.Exists(fileName)) System.IO.File.Delete(fileName);
db.SaveAs(fileName,DwgVersion.Current);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddEntitiesInDatabase</span>(Database <span style="color: #1f377f;">db</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span>=db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">model</span> = (BlockTableRecord)tran.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
CreateLine(
<span style="color: blue;">new</span> Point3d(0, 0, 0), <span style="color: blue;">new</span> Point3d(100, 0, 0), model, tran);
CreateLine(
<span style="color: blue;">new</span> Point3d(100, 0, 0), <span style="color: blue;">new</span> Point3d(100, 100, 0), model, tran);
CreateLine
(<span style="color: blue;">new</span> Point3d(100, 100, 0), <span style="color: blue;">new</span> Point3d(0, 100, 0), model, tran);
CreateLine(
<span style="color: blue;">new</span> Point3d(0, 100, 0), <span style="color: blue;">new</span> Point3d(0, 0, 0), model, tran);
CreateLine(
<span style="color: blue;">new</span> Point3d(0, 0, 0), <span style="color: blue;">new</span> Point3d(100, 100, 0), model, tran);
CreateLine(
<span style="color: blue;">new</span> Point3d(0, 100, 0), <span style="color: blue;">new</span> Point3d(100, 0, 0), model, tran);
tran.Commit();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateLine</span>(
Point3d <span style="color: #1f377f;">startPt</span>, Point3d <span style="color: #1f377f;">endPt</span>, BlockTableRecord <span style="color: #1f377f;">model</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = <span style="color: blue;">new</span> Line(startPt, endPt);
line.SetDatabaseDefaults(model.Database);
model.AppendEntity(line);
tran.AddNewlyCreatedDBObject(line, <span style="color: blue;">true</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateThumnailInSideDatabase</span>(Database <span style="color: #1f377f;">db</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">cl</span> = Autodesk.AutoCAD.Colors.Color.FromColor(
System.Drawing.Color.WhiteSmoke);
<span style="color: blue;">var</span> <span style="color: #1f377f;">blkId</span> = SymbolUtilityServices.GetBlockModelSpaceId(db);
<span style="color: blue;">var</span> <span style="color: #1f377f;">imgPtr</span> = Utils.GetBlockImage(blkId, 100, 100, cl);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">image</span> = System.Drawing.Image.FromHbitmap(imgPtr))
{
db.ThumbnailBitmap = image;
}
}
}
}
</pre>
<p></p><p>This is the CommandMethod to get the work done:</p><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;">[CommandMethod(<span style="color: #a31515;">"SideDbDwg"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateDrawingFromSideDb</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: #8f08c4;">try</span>
{
<span style="color: blue;">string</span> <span style="color: #1f377f;">fileName</span> = <span style="color: maroon;">@"E:\Temp\SideDbDrawing.dwg"</span>;
SideDbCreation.CreateDrawFromSideDatabase(
fileName, SideDbCreation.AddEntitiesInDatabase, <span style="color: blue;">true</span>);
ed.WriteMessage(<span style="color: #a31515;">$"\nDrawing has been created at:\n</span>{fileName}<span style="color: #a31515;">\n"</span>);
}
<span style="color: #8f08c4;">catch</span>(System.Exception <span style="color: #1f377f;">ex</span>)
{
ed.WriteMessage(
<span style="color: #a31515;">$"\nError: </span>{ex.Message}<span style="color: #a31515;">\n"</span>);
}
}</pre><pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><br /></pre>
<p></p><p>See the video clip below:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxuP6_lp_q13oAfYa9P2MVYxFxIKLah3iixykUEgFLYqnsJB8W25WmgNNLSGVUGLcHUAkXqcjZIkdqcIg2tyA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>While I did not have time to try to use side database to update an existing drawing and then re-generate the thumbnail image, I do not see why it would not work.</p><p>Also, in this sample code I simply arbitrarily choose the image size of 100 x 100. I can be smaller or larger, I suppose. If you update existing drawing, there is likely an existing thumbnail image, so you can use the <i>Database.ThumbnailBitmap</i> property to get the size of the existing image and use the size for your new Thumbnail image. </p><p>While the code is really simple, but to be warned: the code is at the mercy of the <i>Autodesk.AutoCAD.Internal</i> namespace, which Autodesk is free to break without warning (well, the chances are very low, unless the .NET API is subject to drastic changes for particular reasons</p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-17030159582195192882022-05-20T18:04:00.002-07:002022-08-12T09:11:09.232-07:00Assign a Custom Action to a Keyboard Key by Capturing Windows' Key-Press Message<p>This article is inspired by <a href="https://forums.autodesk.com/t5/net/how-to-associate-an-event-to-the-click-of-the-quot-delete-quot/td-p/11182598" target="_blank">this question</a> recently asked in AutoCAD .NET user discussion forum. As usual, I chose to post sample code regarding the topic here for better readability.</p><p>One of the famous posts on AutoCAD .NET from Kean Walmsley (March, 2008, 14 years ago!) talked about the topic of filtering Windows message inside AutoCAD. Because the .NET API provides a way to look up the Windows message, so we, as .NET API programmer, can rather easily capture the Windows message (in this particular discussion, it is the WM_KEYDOWN message) and do things accordingly.</p><p>The Application class (Autodesk.AutoCAD.ApplicationServices.Core namespace) exposes PreTranslateMessage event with PreTranslateMessageEventArgs, which supplies a raw Windows meessage. </p><p>Following code sample does what was asked in the question from the .NET forum: using "Delete" key to erase entities. </p><p>First, <i>DeleteKeyHandler</i> class:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">namespace</span> EditorGetMethods
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">DeleteKeyHandler</span>
{
<span style="color: blue;">const</span> <span style="color: blue;">int</span> WM_KEYDOWN = 256;
<span style="color: blue;">const</span> <span style="color: blue;">int</span> DELETE_KEY_CODE = 46;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _enabled = <span style="color: blue;">false</span>;
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> Enabled => _enabled;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">EnableMyDelKey</span>(<span style="color: blue;">bool</span> <span style="color: #1f377f;">enable</span>)
{
<span style="color: #8f08c4;">if</span> (enable)
{
CadApp.PreTranslateMessage += CadApp_PreTranslateMessage;
_enabled = <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
CadApp.PreTranslateMessage -= CadApp_PreTranslateMessage;
_enabled=<span style="color: blue;">false</span>;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CadApp_PreTranslateMessage</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PreTranslateMessageEventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: #8f08c4;">if</span> (e.Message.message==WM_KEYDOWN &&
e.Message.wParam.ToInt32()==DELETE_KEY_CODE)
{
DeleteKeyPressed?.Invoke(<span style="color: blue;">this</span>, EventArgs.Empty);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">event</span> EventHandler DeleteKeyPressed;
}
}</pre><p>To use it, here is the CommandClass/Method:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">sing</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(EditorGetMethods.MyCommands))]
<span style="color: blue;">namespace</span> EditorGetMethods
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">static</span> DeleteKeyHandler _myDelKey = <span style="color: blue;">null</span>;
[CommandMethod(<span style="color: #a31515;">"MyDelKey"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">EraseEntitiesWithDelKey</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: #8f08c4;">if</span> (_myDelKey==<span style="color: blue;">null</span>)
{
_myDelKey = <span style="color: blue;">new</span> DeleteKeyHandler();
_myDelKey.DeleteKeyPressed += DelKeyPressed;
}
<span style="color: #8f08c4;">if</span> (!_myDelKey.Enabled)
{
_myDelKey.EnableMyDelKey(<span style="color: blue;">true</span>);
ed.WriteMessage(<span style="color: #a31515;">"\nDeleteKeyHandler is enabled."</span>);
}
<span style="color: #8f08c4;">else</span>
{
_myDelKey.EnableMyDelKey(<span style="color: blue;">false</span>);
ed.WriteMessage(<span style="color: #a31515;">"\nDeleteKeyHandler is disabled."</span>);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DelKeyPressed</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, EventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: #8f08c4;">if</span> (dwg == <span style="color: blue;">null</span>) <span style="color: #8f08c4;">return</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
ed.WriteMessage(<span style="color: #a31515;">"\nDELETE key was pressed."</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.SelectImplied();
<span style="color: #8f08c4;">if</span> (res.Status != PromptStatus.OK) <span style="color: #8f08c4;">return</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ids</span> = res.Value.GetObjectIds();
<span style="color: blue;">using</span> (dwg.LockDocument())
{
EraseSelectedEntities(ids);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">EraseSelectedEntities</span>(ObjectId[] <span style="color: #1f377f;">ids</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = ids[0].Database.TransactionManager.StartTransaction())
{
<span style="color: #8f08c4;">foreach</span> (ObjectId <span style="color: #1f377f;">id</span> <span style="color: #8f08c4;">in</span> ids)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = tran.GetObject(id, OpenMode.ForWrite);
ent.Erase();
}
tran.Commit();
}
}
}
}</pre>
<p>The code is rather simple and straightforward.</p><p>While the code shows how easy we can assign some keys from the keyboard to do particular things quite easily, I would be extremely cautious to do it. For example, in this particular case, it is obvious that erasing work triggered by pressing DELETE key would only work when there are entities in AutoCAD editor are preselected; when DELETE key is pressed, there could be another command is active, which may cause unexpected/unwanted result. Just think: what is the advantage here by pressing DELETE key over pressing "E" (the command ERASE)? None, not to mention the potential unwanted result.</p><p>Anyway, with the sample code showed here, we have seen how to capture Windows message sent to AutoCAD and to handle it accordingly for our business need.</p><p>Since the code is very simple and works as expected in my a few tests, so I omitted my usual companion video clip.</p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-37331895002807574432022-02-24T19:59:00.001-08:002022-02-24T20:02:07.093-08:00Where The Tangent Point Is - Show It When User Moves Mouse<p><span style="font-family: arial;">Frequent visitors here must have noticed that many of my articles talk about Transient Grphics. It is one of my favorite AutoCAD .NET API features. You see, if you develop AutoCAD application to help user interact with AutoCAD easier, more efficiently, CAD users would appreciate if your application provides as much visual assistance as possible. A while ago, <a href="https://forums.autodesk.com/t5/net/how-do-you-find-the-tangent-of-point-to-curve-in-autocad-api/td-p/10946327" target="_blank">a question posted in .NET API forum about finding tangent point of a curve from a point</a>, I though it would be an interesting exercise to write a bit of code to give user visual feedback of potential tangent point when mouse is dragged.</span></p><p><span style="font-family: arial;">The visual part, obviously by using Transient Graphics, is rather simple, as long as the tangent point is calculated correctly. The calculation can be purely done with trigonometric methods/formula. However, since I need to use AutoCAD entities to generate transient image, I took a easier way of using AutoCAD objects for the calculation. One may argue that using trigonometric math would use much less computer power then using AutoCAD object for such calculation. But considering how the code runs: while moving mouse to show the visual effect, AutoCAD is basically waiting for user to decide his/here input and doing nothing else. So, the extra computing power burden would not be an issue, IMO.</span></p><p><span style="font-family: arial;">Anyway, here is the code:</span></p>
<pre style="background: white; color: black; font-size: 13px;"><span style="font-family: courier;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">namespace</span> TangentTrail
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">TangentFinder</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> CircularArc3d _circularArc = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> TransientManager tsManager = TransientManager.CurrentTransientManager;
<span style="color: blue;">private</span> Point3d? _tangent1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Point3d? _tangent2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Line _ghostLine1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> Line _ghostLine2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBPoint _ghostPoint1 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> DBPoint _ghostPoint2 = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: blue;">int</span> _colorIndex;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _transientAdded = <span style="color: blue;">false</span>;
<span style="color: blue;">public</span> <span style="color: #2b91af;">TangentFinder</span>(Document <span style="color: #1f377f;">dwg</span>, <span style="color: blue;">int</span> <span style="color: #1f377f;">ghostColorIndex</span>=1)
{
_dwg= dwg;
_ed = _dwg.Editor;
_colorIndex = ghostColorIndex;
}
<span style="color: blue;">public</span> Point3d? TangentPoint1 => _tagent1;</span></pre><pre style="background: white; color: black; font-size: 13px;"><span style="font-family: courier;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: courier;"><span style="color: blue;"> public</span> Point3d? TangentPoint2 => _tagent2;</span></pre>
<span style="color: blue;">public</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">DragForTangent</span>()
{
<span style="color: #8f08c4;">if</span> (!SelectCurve()) <span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">pdMode</span> = _dwg.Database.Pdmode;
<span style="color: #8f08c4;">try</span>
{
_dwg.Database.Pdmode = 34;
_ed.PointMonitor += Editor_PointMonitor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetPoint(
<span style="color: #a31515;">"\nSelect point to form a tangent line:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK &&
(_tangent1.HasValue || _tangent2.HasValue))
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: #8f08c4;">finally</span>
{
_ed.PointMonitor-=Editor_PointMonitor;
_dwg.Database.Pdmode = pdMode;
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
ClearGhost();
}
<span style="color: grey;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">SelectCurve</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">opt</span> = <span style="color: blue;">new</span> PromptEntityOptions(
<span style="color: #a31515;">"\nSelect an ARC or a CIRCLE:"</span>);
opt.SetRejectMessage(<span style="color: #a31515;">"\nInvalid: must be an ARC or a CIRCLE!"</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Arc), <span style="color: blue;">true</span>);
opt.AddAllowedClass(<span style="color: blue;">typeof</span>(Circle), <span style="color: blue;">true</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetEntity(opt);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = <span style="color: blue;">new</span> OpenCloseTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">curve</span> = (Curve)tran.GetObject(res.ObjectId, OpenMode.ForRead);
_circularArc = curve.GetGeCurve() <span style="color: blue;">as</span> CircularArc3d;
}
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Editor_PointMonitor</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, PointMonitorEventArgs <span style="color: #1f377f;">e</span>)
{
ClearGhost();
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = e.Context.RawPoint;
CalculateTangentPoint(
_circularArc, pt, <span style="color: blue;">out</span> _tangent1, <span style="color: blue;">out</span> _tangent2);
<span style="color: #8f08c4;">if</span> (_tangent1.HasValue)
{
ShowGhost(_ghostLine1, _ghostPoint1, pt, _tangent1.Value);
}
<span style="color: #8f08c4;">if</span> (_tangent2.HasValue)
{
ShowGhost(_ghostLine2, _ghostPoint2, pt, _tangent2.Value);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearGhost</span>()
{
ClearGhost(_ghostLine1);
ClearGhost(_ghostLine2);
ClearGhost(_ghostPoint1);
ClearGhost(_ghostPoint2);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearGhost</span>(Drawable <span style="color: #1f377f;">ghost</span>)
{
<span style="color: #8f08c4;">if</span> (ghost != <span style="color: blue;">null</span>)
{
tsManager.EraseTransient(ghost, <span style="color: blue;">new</span> IntegerCollection());
ghost.Dispose();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ShowGhost</span>(
Line <span style="color: #1f377f;">ghostLine</span>, DBPoint <span style="color: #1f377f;">ghostPoint</span>,
Point3d <span style="color: #1f377f;">startPoint</span>, Point3d <span style="color: #1f377f;">endPoint</span>)
{
<span style="color: #8f08c4;">if</span> (ghostLine != <span style="color: blue;">null</span> && !ghostLine.IsDisposed)
{
ghostLine.Dispose();
}
ghostLine = <span style="color: blue;">null</span>;
<span style="color: #8f08c4;">if</span> (ghostPoint != <span style="color: blue;">null</span> && !ghostPoint.IsDisposed)
{
ghostPoint.Dispose();
}
ghostPoint = <span style="color: blue;">null</span>;
ghostLine = <span style="color: blue;">new</span> Line();
ghostLine.ColorIndex = _colorIndex;
ghostLine.StartPoint = startPoint;
ghostLine.EndPoint = endPoint;
tsManager.AddTransient(
ghostLine,
TransientDrawingMode.DirectTopmost,
128,
<span style="color: blue;">new</span> IntegerCollection());
ghostPoint = <span style="color: blue;">new</span> DBPoint(endPoint);
ghostPoint.ColorIndex = _colorIndex;
tsManager.AddTransient(
ghostPoint,
TransientDrawingMode.DirectTopmost,
128,
<span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CalculateTangentPoint</span>(
CircularArc3d <span style="color: #1f377f;">arc</span>, Point3d <span style="color: #1f377f;">point</span>,
<span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tangent1</span>, <span style="color: blue;">out</span> Point3d? <span style="color: #1f377f;">tangent2</span>)
{
tangent1 = <span style="color: blue;">null</span>;
tangent2 = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">dist</span> = point.DistanceTo(arc.Center);
<span style="color: #8f08c4;">if</span> (dist < arc.Radius) <span style="color: #8f08c4;">return</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle</span> = Math.Acos(arc.Radius / dist);
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">line</span> = <span style="color: blue;">new</span> Line(arc.Center, point))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle1</span> = line.Angle + angle;
<span style="color: blue;">var</span> <span style="color: #1f377f;">angle2</span> = line.Angle - angle;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">circle</span>=<span style="color: blue;">new</span> Circle(arc.Center, arc.Normal, arc.Radius))
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">arcLen</span> = angle1 * arc.Radius;
<span style="color: blue;">var</span> <span style="color: #1f377f;">pt</span> = circle.GetPointAtDist(arcLen);
<span style="color: #8f08c4;">if</span> (_circularArc.IsOn(pt))
{
tangent1 = pt;
}
arcLen = angle2 * arc.Radius;
pt = circle.GetPointAtDist(arcLen);
<span style="color: #8f08c4;">if</span> (_circularArc.IsOn(pt))
{
tangent2 = pt;
}
}
}
}
<span style="color: grey;">#endregion</span>
}
}</span></pre><p>The CommandClass/Method to run it:</p>
<pre style="background: white; color: black; font-size: 13px;"><span style="font-family: courier;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(TangentTrail.MyCommands))]
<span style="color: blue;">namespace</span> TangentTrail
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"GetTangent"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">GetTangent</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tangentTool</span> = <span style="color: blue;">new</span> TangentFinder(dwg))
{
<span style="color: #8f08c4;">if</span> (tangentTool.DragForTangent())
{
}
}
}
}
}</span></pre>
<p><br /></p><p>The video below shows the code in action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwHYFWeY_0GXJwraPAIcBiwlbj4sxOfC__4VnIR8iZ2j8oqHHaL4XDPsch925q_m0fkL-F1UQKNQ_9gdaS7Eg' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com5tag:blogger.com,1999:blog-993393844516183990.post-53949318028694022122022-02-15T15:01:00.000-08:002022-02-15T15:01:58.577-08:00Using Transient Graphics to Prompt User on Screen<p>I have been quite busy these days that I have not post an article for quite a while. Today, as I often do, based on my interaction in <a href="https://forums.autodesk.com/t5/net/change-temporary-entity-during-selection-by-using-mouse-button/td-p/10949816" target="_blank">a discussion thread in the .NET forum</a>, I put together some code quickly as the result of the discussion. To save time to repeat the topic that was discussed, please refer the link to the discussion there to see what the code is about. I only post the code and a companion video clip that shows the code execution result.</p><p>First a class <i>TransientPrompt</i>:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> System;
<span style="color: blue;">namespace</span> TransintTextPrompt
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">TransientPrompt</span> : IDisposable
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> DBText _txtEntity = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _promptText = <span style="color: #a31515;">""</span>;
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> _enabled = <span style="color: blue;">false</span>;
<span style="color: blue;">private</span> TransientManager tsManager = TransientManager.CurrentTransientManager;
<span style="color: blue;">public</span> <span style="color: #2b91af;">TransientPrompt</span>(Document <span style="color: #1f377f;">dwg</span>, <span style="color: blue;">string</span> <span style="color: #1f377f;">initialText</span>)
{
_dwg = dwg;
_promptText = initialText;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Enable</span>(<span style="color: blue;">bool</span> <span style="color: #1f377f;">enable</span>)
{
_enabled = enable;
<span style="color: #8f08c4;">if</span> (_enabled)
{
_dwg.ViewChanged += ViewChanged;
<span style="color: #8f08c4;">if</span> (_txtEntity != <span style="color: blue;">null</span>) _txtEntity.Dispose();
_txtEntity = <span style="color: blue;">new</span> DBText();
_txtEntity.SetDatabaseDefaults();
_txtEntity.HorizontalMode = TextHorizontalMode.TextCenter;
_txtEntity.VerticalMode = TextVerticalMode.TextVerticalMid;
_txtEntity.TextString = _promptText;
_txtEntity.ColorIndex = 1;
CreateTransient();
}
<span style="color: #8f08c4;">else</span>
{
_dwg.ViewChanged -= ViewChanged;
ClearTransient();
<span style="color: #8f08c4;">if</span> (_txtEntity != <span style="color: blue;">null</span>) _txtEntity.Dispose();
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">SetPromptText</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">prompt</span>)
{
_promptText = prompt;
<span style="color: #8f08c4;">if</span> (_txtEntity!=<span style="color: blue;">null</span>)
{
ClearTransient();
CreateTransient();
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Dispose</span>()
{
<span style="color: #8f08c4;">if</span> (_txtEntity != <span style="color: blue;">null</span>)
{
tsManager.EraseTransient(_txtEntity, <span style="color: blue;">new</span> IntegerCollection());
_txtEntity.Dispose();
}
}
<span style="color: grey;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ViewChanged</span>(<span style="color: blue;">object</span> <span style="color: #1f377f;">sender</span>, EventArgs <span style="color: #1f377f;">e</span>)
{
<span style="color: #8f08c4;">if</span> (!_enabled) <span style="color: #8f08c4;">return</span>;
ClearTransient();
CreateTransient();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DrawingTransient</span>()
{
<span style="color: #8f08c4;">if</span> (_txtEntity!=<span style="color: blue;">null</span>)
{
tsManager.AddTransient(_txtEntity, TransientDrawingMode.DirectShortTerm, 128, <span style="color: blue;">new</span> IntegerCollection());
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearTransient</span>()
{
<span style="color: #8f08c4;">if</span> (_txtEntity!=<span style="color: blue;">null</span>)
{
tsManager.EraseTransient(_txtEntity, <span style="color: blue;">new</span> IntegerCollection());
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">GetViewSize</span>(<span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">height</span>, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">width</span>, <span style="color: blue;">out</span> Point3d <span style="color: #1f377f;">center</span>)
{
center = (Point3d)Application.GetSystemVariable(<span style="color: #a31515;">"VIEWCTR"</span>);
height = (<span style="color: blue;">double</span>)Application.GetSystemVariable(<span style="color: #a31515;">"VIEWSIZE"</span>);
<span style="color: blue;">var</span> <span style="color: #1f377f;">screen</span> = (Point2d)Application.GetSystemVariable(<span style="color: #a31515;">"SCREENSIZE"</span>);
width = screen.X / screen.Y * height;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">GetTextLocation</span>(<span style="color: blue;">out</span> Point3d <span style="color: #1f377f;">textPosition</span>, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">textHeight</span>)
{
GetViewSize(<span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">viewHeight</span>, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">viewWidth</span>, <span style="color: blue;">out</span> Point3d <span style="color: #1f377f;">viewCenter</span>);
textPosition = <span style="color: blue;">new</span> Point3d(viewCenter.X - (viewWidth / 2.0) * .70, viewCenter.Y + (viewHeight / 2.0) * .70, 0.0);
textHeight = viewHeight / 10.0;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateTransient</span>()
{
GetTextLocation(<span style="color: blue;">out</span> Point3d <span style="color: #1f377f;">pos</span>, <span style="color: blue;">out</span> <span style="color: blue;">double</span> <span style="color: #1f377f;">h</span>);
_txtEntity.Height = h;
_txtEntity.Position = pos;
_txtEntity.TextString = _promptText;
DrawingTransient();
}
<span style="color: grey;">#endregion</span>
}
}
</pre>
<p>Then the CommandClass/Method where the <i>TransientPrompt</i> class is used:</p>
<pre style="background: white; color: black; font-family: "Cascadia Mono"; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[<span style="color: blue;">assembly</span>: CommandClass(<span style="color: blue;">typeof</span>(TransintTextPrompt.MyCommands))]
<span style="color: blue;">namespace</span> TransintTextPrompt
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"TestTransient"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">int</span> <span style="color: #1f377f;">count</span> = 0;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">transient</span>=<span style="color: blue;">new</span> TransientPrompt(dwg, count.ToString()))
{
transient.Enable(<span style="color: blue;">true</span>);
<span style="color: #8f08c4;">while</span>(<span style="color: blue;">true</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(<span style="color: #a31515;">"\nSelect an ENTITY:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
count++;
transient.SetPromptText(count.ToString());
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">break</span>;
}
}
transient.Enable(<span style="color: blue;">false</span>);
}
}
}
}
</pre>
<p>The code is fairly simple and self-descriptive. See the video clip showing the code execution effect:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwnsfh5ScSlv7buPRiPxdx4hwWSVVmL_0kuuT6kDYpFmP2BUDz7GAlbhtjBcm-UEcHn4BWiQcDabxAqR6nNiA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-29489829172442675112021-11-21T08:51:00.001-08:002021-11-22T10:54:48.153-08:00Use COM API/VBA to Open Viewport on Layout to Show Given Content in ModelSpace<p>Since I moved to AutoCAD .NET API more than 15 years ago, I dealt with AutoCAD VBA less and less, yet, still not completely left it behind. There was <a href="https://forums.autodesk.com/t5/vba/create-viewport-from-rectangle-or-block/td-p/10734474" target="_blank">a recent discussion</a> in the VBA discussion forum on the topic of opening viewport on layout to show ModelSpace content within a given area/scope. While there are a few useful replies, the OP still expects/prefers VBA code sample. According to the OP's request, the VBA code should not be too complicated, so I gave it a try. Once again, instead of posting the code sample as reply in the discussion thread, it would be better to post here with better readability.</p><p>First, let me assume the conditions of running the code:</p><p>A rectangle block is used as content boundary in the ModelSpace to be shown in a layout's viewport with given scale.</p><p>This picture shows the ModelSpace contents to be shown:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-3ZeLH34iSXo/YZpyFEbeNHI/AAAAAAAABoI/NSj-Q5YG8agkxDLL3jYckrLdpU4y-HdRwCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="276" data-original-width="780" height="226" src="https://lh3.googleusercontent.com/-3ZeLH34iSXo/YZpyFEbeNHI/AAAAAAAABoI/NSj-Q5YG8agkxDLL3jYckrLdpU4y-HdRwCLcBGAsYHQ/w640-h226/image.png" width="640" /></a></div><br />The coding process would be:<p></p><p>1. Determine the rectangle's size (width/height), rotation angle, and the coordinates of its corners;</p><p>2. Open a Viewport on a layout with the same size as the rectangle (scaled, of course);</p><p>3. Activate the Viewport in MSpace mode and zoom the view to the rectangle.</p><p>Note, ideally, based on the rectangle's size and required scale, the code could do calculation to decide what paper size is needed for the layout. But I omitted this calculation and simply manually chose a paper size for the layout ("Layout1") and choose the open the AcadPViewport at point (200, 150) as its center, which is based on the boundary block's size and a scale of 1/20 (0.05).</p><p>Here is the class <i>BorderBlock,</i> which holds all geometric information required to create the Viewport (e.g. in VBA Editor, create a class, name it as <i>BorderBlock</i>):</p>
<pre>Option Explicit
Private mBlkName As String
Private mHeight As Double
Private mWidth As Double
Private mRotation As Double
Private mScale As Double
Private mLLCorner(0 To 2) As Double
Private mLRCorner(0 To 2) As Double
Private mULCorner(0 To 2) As Double
Private mURCorner(0 To 2) As Double
Private Sub Class_Initialize()
mBlkName = "VPORT_BOUNDARY"
mScale = 0.05
End Sub
Public Property Get VPortTweestAngle() As Double
VPortTweestAngle = mRotation
End Property
Public Property Get Height() As Double
Height = mHeight
End Property
Public Property Get Width() As Double
Width = mWidth
End Property
Public Property Get VPortWidth() As Double
VPortWidth = mWidth * mScale
End Property
Public Property Get VPortHeigth() As Double
VPortHeigth = mHeight * mScale
End Property
Public Property Get VPortScale() As Double
VPortScale = mScale
End Property
Public Property Get LLCorner() As Variant
LLCorner = mLLCorner
End Property
Public Property Get URCorner() As Variant
URCorner = mURCorner
End Property
Public Sub GetViewportData(blk As AcadBlockReference)
'' Get border block's width and height
Dim oldRotation As Double
Dim minPt As Variant
Dim maxPt As Variant
If blk.Rotation <> 0 Then
oldRotation = blk.Rotation
blk.Rotation = 0#
End If
blk.GetBoundingBox minPt, maxPt
mWidth = maxPt(0) - minPt(0)
mHeight = maxPt(1) - minPt(1)
blk.Rotation = oldRotation
'' View port tweest angle
mRotation = 2 * 3.1415926 - blk.Rotation
'' Get 4 corners of the border block
mLLCorner(0) = blk.InsertionPoint(0)
mLLCorner(1) = blk.InsertionPoint(1)
mLLCorner(2) = blk.InsertionPoint(2)
Dim angle As Double
Dim nextCorner As Variant
angle = blk.Rotation
nextCorner = CalculateNextCorner(mLLCorner, angle, mWidth)
mLRCorner(0) = nextCorner(0)
mLRCorner(1) = nextCorner(1)
mLRCorner(2) = nextCorner(2)
angle = angle + 3.1415926 / 2#
nextCorner = CalculateNextCorner(mLRCorner, angle, mHeight)
mURCorner(0) = nextCorner(0)
mURCorner(1) = nextCorner(1)
mURCorner(2) = nextCorner(2)
angle = angle + 3.1415926 / 2#
nextCorner = CalculateNextCorner(mURCorner, angle, mWidth)
mULCorner(0) = nextCorner(0)
mULCorner(1) = nextCorner(1)
mULCorner(2) = nextCorner(2)
End Sub
Private Function CalculateNextCorner(corner As Variant, angle As Double, distance As Double) As Variant
Dim nextCorner(0 To 2) As Double
Dim x As Double
Dim y As Double
x = distance * Cos(angle)
x = x + corner(0)
y = distance * Sin(angle)
y = y + corner(1)
nextCorner(0) = x: nextCorner(1) = y: nextCorner(2) = corner(2)
CalculateNextCorner = nextCorner
End Function
</pre><p>There is the VBA module with a public method <i>OpenPVport()</i> as a macro:</p><p><br /></p>
<pre>
Option Explicit
Public Sub OpenPVport()
Dim border As BorderBlock
Set border = SelectBorderBlock()
If border Is Nothing Then Exit Sub
ThisDrawing.ActiveSpace = acPaperSpace
ThisDrawing.ActiveLayout = ThisDrawing.Layouts("Layout1")
CreatePVort border
End Sub
Private Function SelectBorderBlock() As BorderBlock
If ThisDrawing.ActiveSpace <> acModelSpace Then
ThisDrawing.ActiveSpace = acModelSpace
End If
Dim blk As AcadBlockReference
Dim ent As AcadEntity
Dim pt As Variant
On Error Resume Next
ThisDrawing.Utility.GetEntity ent, pt, vbCr & "Select vport boundary block:"
If ent Is Nothing Then Exit Function
If TypeOf ent Is AcadBlockReference Then
Set blk = ent
If UCase(blk.Name) <> "VPORT_BOUNDARY" Then
MsgBox "Selected wrong block: not VPORT_BOUNDARY block!"
Exit Function
End If
Else
MsgBox "Selected entity is not a block reference!"
Exit Function
End If
Dim border As BorderBlock
Set border = New BorderBlock
border.GetViewportData blk
Set SelectBorderBlock = border
End Function
Private Sub CreatePVort(border As BorderBlock)
Dim vport As AcadPViewport
Dim center(0 To 2) As Double
center(0) = 200: center(1) = 150: center(2) = 0
Set vport = ThisDrawing.PaperSpace.AddPViewport( _
center, border.VPortWidth, border.VPortHeigth)
vport.Display True
vport.TwistAngle = border.VPortTweestAngle
'' zoom properly
ThisDrawing.MSpace = True
ThisDrawing.ActivePViewport = vport
ThisDrawing.MSpace = True
ZoomWindow border.LLCorner, border.URCorner
ThisDrawing.MSpace = False
'' Lock the viewport's display
vport.DisplayLocked = True
vport.Update
End Sub
</pre>
<p>See following video clip for the action of the code:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxguWJdOVRuSjJ2Y5oefUCtcWqSrnzH2lVG7KFuIxc5qgLzc4FoPogZN_VYxlf8n0qM26XwLoflRIdwxdwgPQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p><br /></p><p><br /></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com2tag:blogger.com,1999:blog-993393844516183990.post-54078025489144573562021-09-10T07:35:00.000-07:002021-09-10T07:35:28.459-07:00Using EntityJig with Keyword Options for Copying Entity<p> <a href="https://forums.autodesk.com/t5/net/jig-multiple-entity-with-user-input/td-p/10608189" target="_blank">A question</a> was asked in Autodesk's AutoCAD .NET forum. There are actually 2 issues raised in the question: 1) during jig's dragging process, can/how we use keyword options; 2) which jig (<i>EntityJig</i> or <i>DrawJig</i>) should be used for the copying.</p><p>I could post my reply directly to offer my opinion on how I would do it. But as programmer, I thought code often provides more helpful answer than words. So, as I usually do, I quickly put some code together to demonstrate what I do in this situation and publish the code here for better readability.</p><p>Some thoughts here:</p><p></p><ul style="text-align: left;"><li>Jig is a nice way in AutoCAD to visually show the possible outcome of user input. That is, when Jig begins, AutoCAD is effectively waiting for user input, either from mouse click, or keyboard press. That is, once a user input occurs, jig ends, and a <i>PromptResult</i> object is returned. So, if you use keyword options in jig, which might change the jig's initial condition (in the case of the posted question, the OP want to change to the jig's target entity), the current jig is ended once a keyword is entered. Based on the keyword option's logic, we may want to loop back to do another round jig with new condition.</li><li>For copying, I can see both <i>DrawJig</i> and <i>EntityJig</i> can be used. But in this sample project, I choose to create a class <i>MyJigCopier</i>, which uses an <i>EntityJig</i> inside to encapsulate the entire copying process.</li></ul><div>OK, see code below.</div><div><br /></div><div>First, the class <i>MyEntityJig</i>. The purpose of using this jig is to move around the non-database-residing entity before it being added to the database, or obtain user's other inputs other than entity copy's position (i.e. cancellation, keyword selected). So, the code is pretty simple:</div><div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">namespace</span> KeywordsInJig
{
<span style="color: blue;">public</span> <span style="color: blue;">enum</span> <span style="color: #2b91af;">JigResult</span>
{
JigDone = 0,
RepickSource = 1,
RepickBasePoint = 2,
JigCancelled = 3,
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyEntityJig</span> : EntityJig
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Point3d _basePoint;
<span style="color: blue;">private</span> Point3d _currPoint;
<span style="color: blue;">private</span> Point3d _prevPoint;
<span style="color: blue;">public</span> <span style="color: #2b91af;">MyEntityJig</span>(Editor <span style="color: #1f377f;">ed</span>,Entity <span style="color: #1f377f;">ent</span>, Point3d <span style="color: #1f377f;">basePt</span>) : <span style="color: blue;">base</span>(ent)
{
_ed = ed;
_basePoint = basePt;
_currPoint = basePt;
_prevPoint = basePt;
}
<span style="color: blue;">public</span> Point3d CopyLocation { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> JigResult <span style="color: #74531f;">Drag</span>()
{
JigResult <span style="color: #1f377f;">result</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.Drag(<span style="color: blue;">this</span>);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
CopyLocation = _currPoint;
result = JigResult.JigDone;
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.Keyword)
{
<span style="color: #8f08c4;">if</span> (res.StringResult==<span style="color: #a31515;">"Source"</span>)
{
result = JigResult.RepickSource;
}
<span style="color: #8f08c4;">else</span>
{
result = JigResult.RepickBasePoint;
}
}
<span style="color: #8f08c4;">else</span>
{
result = JigResult.JigCancelled;
}
<span style="color: #8f08c4;">return</span> result;
}
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> SamplerStatus <span style="color: #74531f;">Sampler</span>(JigPrompts <span style="color: #1f377f;">prompts</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">options</span> = <span style="color: blue;">new</span> JigPromptPointOptions(
<span style="color: #a31515;">"\nSelect position"</span>);
options.UseBasePoint = <span style="color: blue;">true</span>;
options.BasePoint = _basePoint;
options.Cursor = CursorType.RubberBand;
options.Keywords.Add(<span style="color: #a31515;">"Source"</span>);
options.Keywords.Add(<span style="color: #a31515;">"Basepoint"</span>);
options.Keywords.Default = <span style="color: #a31515;">"Source"</span>;
options.UserInputControls =
UserInputControls.NullResponseAccepted |
UserInputControls.Accept3dCoordinates;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = prompts.AcquirePoint(options);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
<span style="color: #8f08c4;">if</span> (res.Value.Equals(_currPoint))
{
<span style="color: #8f08c4;">return</span> SamplerStatus.NoChange;
}
<span style="color: #8f08c4;">else</span>
{
_currPoint = res.Value;
<span style="color: #8f08c4;">return</span> SamplerStatus.OK;
}
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.Keyword)
{
<span style="color: #8f08c4;">return</span> SamplerStatus.OK;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> SamplerStatus.Cancel;
}
}
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">Update</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">mt</span> = Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint));
Entity.TransformBy(mt);
_prevPoint = _currPoint;
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
}
}
</pre>
</div><div><br /></div><div>Here is the class <i>MyJigCopier</i>, which uses <i>MyEntityJig</i> inside, performing the entity copying work. Note, for simplicity, I call <i>Entity.Clone()</i> to create a copy of the selected entity, which is used in <i>MyEntityJig</i> class. Because user could choose keyword options during jig process, a <i>while(){...}</i> loop is used to keep the jig going until either the copy location is picked, or the jig is cancelled.</div><div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">namespace</span> KeywordsInJig
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyJigCopier</span>
{
<span style="color: blue;">private</span> ObjectId _sourceEntId = ObjectId.Null;
<span style="color: blue;">private</span> Point3d _basePoint;
<span style="color: blue;">private</span> Document _dwg;
<span style="color: blue;">private</span> Database _db;
<span style="color: blue;">private</span> Editor _ed;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DoCopy</span>(Document <span style="color: #1f377f;">dwg</span>)
{
_dwg = dwg;
_db = _dwg.Database;
_ed = _dwg.Editor;
JigResult <span style="color: #1f377f;">result</span> = JigResult.JigDone;
_sourceEntId = SelectSourceEntity(_ed);
<span style="color: #8f08c4;">if</span> (_sourceEntId.IsNull)
{
_ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
<span style="color: #8f08c4;">if</span> (!PickBasePoint(_ed, <span style="color: blue;">out</span> _basePoint))
{
_ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
<span style="color: #8f08c4;">while</span> (<span style="color: blue;">true</span>)
{
<span style="color: #8f08c4;">if</span> (result == JigResult.RepickSource)
{
_sourceEntId = SelectSourceEntity(_ed);
<span style="color: #8f08c4;">if</span> (_sourceEntId.IsNull)
{
_ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
}
<span style="color: #8f08c4;">if</span> (result== JigResult.RepickBasePoint || result == JigResult.RepickSource)
{
<span style="color: #8f08c4;">if</span> (!PickBasePoint(_ed, <span style="color: blue;">out</span> _basePoint))
{
_ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">return</span>;
}
}
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">ent</span> = (Entity)tran.GetObject(_sourceEntId, OpenMode.ForRead);
ent.Highlight();
<span style="color: blue;">var</span> <span style="color: #1f377f;">clone</span> = ent.Clone() <span style="color: blue;">as</span> Entity;
<span style="color: blue;">var</span> <span style="color: #1f377f;">jig</span> = <span style="color: blue;">new</span> MyEntityJig(_ed, clone, _basePoint);
result = jig.Drag();
ent.Unhighlight();
<span style="color: #8f08c4;">switch</span>(result)
{
<span style="color: #8f08c4;">case</span> JigResult.JigDone:
AddEntityCopyToDb(clone, tran);
tran.Commit();
<span style="color: #8f08c4;">break</span>;
<span style="color: #8f08c4;">case</span> JigResult.RepickBasePoint:
<span style="color: #8f08c4;">case</span> JigResult.RepickSource:
<span style="color: green;">// go back to next loop</span>
<span style="color: #8f08c4;">break</span>;
<span style="color: #8f08c4;">default</span>:
clone.Dispose();
tran.Abort();
_ed.WriteMessage(<span style="color: #a31515;">"\n*Cancel*\n"</span>);
<span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">if</span> (result == JigResult.JigDone || result == JigResult.JigCancelled) <span style="color: #8f08c4;">return</span>;
}
}
<span style="color: grey;">#region</span> private methods
<span style="color: blue;">private</span> ObjectId <span style="color: #74531f;">SelectSourceEntity</span>(Editor <span style="color: #1f377f;">ed</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetEntity(<span style="color: #a31515;">"\nSelect source entity to copy from:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status == PromptStatus.OK)
<span style="color: #8f08c4;">return</span> res.ObjectId;
<span style="color: #8f08c4;">else</span>
<span style="color: #8f08c4;">return</span> ObjectId.Null;
}
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">PickBasePoint</span>(Editor <span style="color: #1f377f;">ed</span>, <span style="color: blue;">out</span> Point3d <span style="color: #1f377f;">pt</span>)
{
pt = <span style="color: blue;">new</span> Point3d();
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = ed.GetPoint(<span style="color: #a31515;">"\nSelect base point:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK)
{
pt = res.Value;
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">AddEntityCopyToDb</span>(Entity <span style="color: #1f377f;">ent</span>, Transaction <span style="color: #1f377f;">tran</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(_db.CurrentSpaceId, OpenMode.ForWrite);
space.AppendEntity(ent);
tran.AddNewlyCreatedDBObject(ent, <span style="color: blue;">true</span>);
}
<span style="color: grey;">#endregion</span>
}
}
</pre>
</div><div><br /></div><div>To use the class MyJigCopier is really simple:</div><div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">namespace</span> KeywordsInJig
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
[CommandMethod(<span style="color: #a31515;">"DoCopy"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">RunMyCommand</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: blue;">var</span> <span style="color: #1f377f;">copier</span> = <span style="color: blue;">new</span> MyJigCopier();
copier.DoCopy(dwg);
}
}
}</pre>
</div><div><br /></div><div>See the video clip below showing how the code works:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxMFg5YldPlupNXnMHU7BrHdJM5xc37SC93f5yZCukasiHmuk9v-qzVB2psslfPMx5wuw-uRNnIiJsZvC7LoQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><div><br /></div><p></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com2tag:blogger.com,1999:blog-993393844516183990.post-14881609939240514962021-08-14T14:25:00.001-07:002021-08-14T14:26:50.881-07:00An EntityJig For MLine Creation<p> I recently involved <a href="https://forums.autodesk.com/t5/net/problem-in-drawing-double-lines/td-p/10542674" target="_blank">a discussion in Autodesk's .NET forum</a>, which is about to create double-lines. The OP chose to create a MLine and then explode it to achieve the goal of drawing line segments continuously with double offsets. While this is an viable way of doing this, with the help of .NET API's Curve.GetOffsetCurves(), the goal of drawing double (or triple) lines is rather easier to achieve than drawing a MLine and then explode it. </p><p>But this post is not about which way is better/easier to draw double lines. The OP somehow ran in to issues that the "Draw MLine -> Explode it" code that generates extra lines after explosion. I did not spent too much time to troubleshoot the code posted. Rather I simply write some code to prove that drawing MLine and then exploding it should be fairly easy (many programmers, including myself, have less patient to fix others' code and choose to rewrite their onw for the same process). So, once my code drew the MLine and exploded it correctly (as expected, I could not see why OP ran into that issue, as posted), I just added a bit extra code to make it fully functional MLine jig, so that I can post a working code sample here for anyone who may be interested in. </p><p>This is the class <i>MLineJig</i>:</p><p>
</p><pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">namespace</span> C3DMiscTest
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MLineJig</span> : EntityJig
{
<span style="color: blue;">private</span> Point3d _currPoint;
<span style="color: blue;">private</span> Point3d _prevPoint;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Document _dwg;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> Editor _ed;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> ObjectId _styleId;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> TransientManager _tsManager =
TransientManager.CurrentTransientManager;
<span style="color: blue;">private</span> Mline _mline;
<span style="color: blue;">private</span> Mline _ghost = <span style="color: blue;">null</span>;
<span style="color: blue;">public</span> <span style="color: #2b91af;">MLineJig</span>(<span style="color: blue;">string</span> <span style="color: #1f377f;">mlineStyleName</span> = <span style="color: blue;">null</span>) : <span style="color: blue;">base</span>(<span style="color: blue;">new</span> Mline())
{
_dwg = CadApp.DocumentManager.MdiActiveDocument;
_ed = _dwg.Editor;
_styleId = GetMLineStyle(_dwg.Database, mlineStyleName);
<span style="color: #8f08c4;">if</span> (_styleId.IsNull)
{
<span style="color: #8f08c4;">throw</span> <span style="color: blue;">new</span> InvalidCastException(<span style="color: #a31515;">"No MLine style available!"</span>);
}
_mline = Entity <span style="color: blue;">as</span> Mline;
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> <span style="color: #74531f;">Draw</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetPoint(<span style="color: #a31515;">"\nSelect start point:"</span>);
<span style="color: #8f08c4;">if</span> (res.Status != PromptStatus.OK) <span style="color: #8f08c4;">return</span>;
_currPoint = res.Value;
_prevPoint = res.Value;
<span style="color: blue;">var</span> <span style="color: #1f377f;">cancelled</span> = <span style="color: blue;">false</span>;
ObjectId <span style="color: #1f377f;">explodeId</span> = ObjectId.Null;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = _dwg.TransactionManager.StartTransaction())
{
_mline.SetDatabaseDefaults(_dwg.Database);
_mline.Style = _styleId;
_mline.Justification = MlineJustification.Zero;
_mline.Normal = <span style="color: blue;">new</span> Vector3d(0, 0, 1);
_mline.AppendSegment(_currPoint);
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(
_dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
explodeId = space.AppendEntity(_mline);
tran.AddNewlyCreatedDBObject(_mline, <span style="color: blue;">true</span>);
_dwg.TransactionManager.QueueForGraphicsFlush();
<span style="color: #8f08c4;">while</span> (<span style="color: blue;">true</span>)
{
ClearGhostImage();
<span style="color: blue;">var</span> <span style="color: #1f377f;">jigRes</span> = _ed.Drag(<span style="color: blue;">this</span>);
<span style="color: #8f08c4;">if</span> (jigRes.Status == PromptStatus.OK)
{
_mline.AppendSegment(_currPoint);
_prevPoint = _currPoint;
_dwg.TransactionManager.QueueForGraphicsFlush();
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (jigRes.Status == PromptStatus.Keyword)
{
<span style="color: #8f08c4;">if</span> (jigRes.StringResult==<span style="color: #a31515;">"Cancel"</span>)
{
cancelled = <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">break</span>;
}
<span style="color: #8f08c4;">else</span>
{
cancelled = <span style="color: blue;">true</span>;
<span style="color: #8f08c4;">break</span>;
}
}
<span style="color: #8f08c4;">if</span> (!cancelled)
{
tran.Commit();
_ed.Regen();
}
<span style="color: #8f08c4;">else</span>
{
tran.Abort();
}
ClearGhostImage();
}
<span style="color: #8f08c4;">if</span> (cancelled || _mline.NumberOfVertices < 2)
{
_mline.Dispose();
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">if</span> (PromptForExplosion())
{
ExplodeMLine(explodeId);
}
}
}
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> SamplerStatus <span style="color: #74531f;">Sampler</span>(JigPrompts <span style="color: #1f377f;">prompts</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">jigOpt</span> = <span style="color: blue;">new</span> JigPromptPointOptions(
<span style="color: #a31515;">"\nSelect next point:"</span>);
jigOpt.UseBasePoint = <span style="color: blue;">true</span>;
jigOpt.BasePoint = _prevPoint;
jigOpt.Cursor = CursorType.RubberBand;
<span style="color: #8f08c4;">if</span> (_mline.NumberOfVertices >= 2)
{
jigOpt.Keywords.Add(<span style="color: #a31515;">"Done"</span>);
jigOpt.Keywords.Add(<span style="color: #a31515;">"Cancel"</span>);
jigOpt.Keywords.Default = <span style="color: #a31515;">"Done"</span>;
jigOpt.AppendKeywordsToMessage = <span style="color: blue;">true</span>;
jigOpt.UserInputControls = UserInputControls.NullResponseAccepted;
}
<span style="color: blue;">var</span> <span style="color: #1f377f;">jigRes</span> = prompts.AcquirePoint(jigOpt);
<span style="color: #8f08c4;">if</span> (jigRes.Status== PromptStatus.OK)
{
<span style="color: #8f08c4;">if</span> (jigRes.Value==_prevPoint)
{
<span style="color: #8f08c4;">return</span> SamplerStatus.NoChange;
}
<span style="color: #8f08c4;">else</span>
{
_currPoint = jigRes.Value;
<span style="color: #8f08c4;">return</span> SamplerStatus.OK;
}
}
<span style="color: #8f08c4;">else</span> <span style="color: #8f08c4;">if</span> (jigRes.Status== PromptStatus.Keyword)
{
<span style="color: #8f08c4;">return</span> jigRes.StringResult == <span style="color: #a31515;">"Done"</span> ?
SamplerStatus.NoChange : SamplerStatus.Cancel;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> SamplerStatus.Cancel;
}
}
<span style="color: blue;">protected</span> <span style="color: blue;">override</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">Update</span>()
{
CreateGhostImage();
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: blue;">private</span> ObjectId <span style="color: #74531f;">GetMLineStyle</span>(Database <span style="color: #1f377f;">db</span>, <span style="color: blue;">string</span> <span style="color: #1f377f;">styleName</span>)
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">id</span> = ObjectId.Null;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dict</span> = (DBDictionary)tran.GetObject(
db.MLStyleDictionaryId, OpenMode.ForRead);
<span style="color: #8f08c4;">if</span> (!<span style="color: blue;">string</span>.IsNullOrEmpty(styleName) &&
dict.Contains(styleName))
{
id = dict.GetAt(styleName);
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">foreach</span> (DBDictionaryEntry <span style="color: #1f377f;">entry</span> <span style="color: #8f08c4;">in</span> dict)
{
id = entry.Value;
<span style="color: #8f08c4;">break</span>;
}
}
tran.Commit();
}
<span style="color: #8f08c4;">return</span> id;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">CreateGhostImage</span>()
{
ClearGhostImage();
_ghost = <span style="color: blue;">new</span> Mline();
_ghost.SetDatabaseDefaults(_dwg.Database);
_ghost.Style = _styleId;
_ghost.Justification = MlineJustification.Zero;
_ghost.Normal = <span style="color: blue;">new</span> Vector3d(0, 0, 1);
_ghost.ColorIndex = 2;
_ghost.AppendSegment(_prevPoint);
_ghost.AppendSegment(_currPoint);
_tsManager.AddTransient(
_ghost,
TransientDrawingMode.DirectTopmost,
128,
<span style="color: blue;">new</span> IntegerCollection());
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ClearGhostImage</span>()
{
<span style="color: #8f08c4;">if</span> (_ghost != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(
_ghost, <span style="color: blue;">new</span> IntegerCollection());
_ghost.Dispose();
}
}
<span style="color: blue;">private</span> <span style="color: blue;">bool</span> <span style="color: #74531f;">PromptForExplosion</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">kOpt</span> = <span style="color: blue;">new</span> PromptKeywordOptions(
<span style="color: #a31515;">"\nDo you want to explode the MLine:"</span>);
kOpt.Keywords.Add(<span style="color: #a31515;">"Yes"</span>);
kOpt.Keywords.Add(<span style="color: #a31515;">"No"</span>);
kOpt.Keywords.Default = <span style="color: #a31515;">"No"</span>;
<span style="color: blue;">var</span> <span style="color: #1f377f;">res</span> = _ed.GetKeywords(kOpt);
<span style="color: #8f08c4;">if</span> (res.Status== PromptStatus.OK &&
res.StringResult==<span style="color: #a31515;">"Yes"</span>)
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: #8f08c4;">else</span>
{
<span style="color: #8f08c4;">return</span> <span style="color: blue;">false</span>;
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> <span style="color: #74531f;">ExplodeMLine</span>(ObjectId <span style="color: #1f377f;">entId</span>)
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> <span style="color: #1f377f;">tran</span> = entId.Database.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">entity</span> = (Entity)tran.GetObject(entId, OpenMode.ForWrite);
<span style="color: blue;">var</span> <span style="color: #1f377f;">objects</span> = <span style="color: blue;">new</span> DBObjectCollection();
entity.Explode(objects);
<span style="color: blue;">var</span> <span style="color: #1f377f;">space</span> = (BlockTableRecord)tran.GetObject(
entId.Database.CurrentSpaceId, OpenMode.ForWrite);
<span style="color: #8f08c4;">foreach</span> (DBObject <span style="color: #1f377f;">obj</span> <span style="color: #8f08c4;">in</span> objects)
{
space.AppendEntity(obj <span style="color: blue;">as</span> Entity);
tran.AddNewlyCreatedDBObject(obj, <span style="color: blue;">true</span>);
}
entity.Erase();
tran.Commit();
}
}
}
}</pre><pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="font-family: "Times New Roman"; font-size: medium; white-space: normal;">To use </span><i style="font-family: "Times New Roman"; font-size: medium; white-space: normal;">MLineJig</i><span style="font-family: "Times New Roman"; font-size: medium; white-space: normal;"> class:</span></pre>
<p></p><pre style="background: white; color: black; font-family: Consolas; font-size: 13px;">[CommandMethod(<span style="color: #a31515;">"DoubleLine"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> <span style="color: #74531f;">DrawDoubleLine</span>()
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">dwg</span> = CadApp.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> <span style="color: #1f377f;">ed</span> = dwg.Editor;
<span style="color: #8f08c4;">try</span>
{
<span style="color: blue;">var</span> <span style="color: #1f377f;">jig</span> = <span style="color: blue;">new</span> MLineJig();
jig.Draw();
}
<span style="color: #8f08c4;">catch</span>(System.Exception <span style="color: #1f377f;">ex</span>)
{
CadApp.ShowAlertDialog(<span style="color: #a31515;">$"Error:\n</span>{ex.Message}<span style="color: #a31515;">"</span>);
}
}</pre>
<p></p><p>See this video clip for the code action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dw7CGcEL9SQ1gMJJTpjJOhiIw1dAkotqZTTdO-9IwltSST5n-xZRkbj8eDye6pzRVva7LzA9l4qgixR-CFmTw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p><br /></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com1tag:blogger.com,1999:blog-993393844516183990.post-23741652494210084412021-03-15T17:03:00.001-07:002021-03-19T17:46:55.754-07:00Highlight Entity In Block - Upated: Added VB.NET Code<p> A question on <a href="https://forums.autodesk.com/t5/net/quot-highlight-quot-polyline-in-block-references/td-p/10158463" target="_blank">how to highlight Polyline in a BlockReference</a> was posted in Autodesk's .NET discussion forum. In my reply, I proposed to use <i>TransientGraphics</i> to render the highlight. Here I put together a quick project to show how easy to achieve that.</p><p>By the way, in the past I posted 2 articles on highlighting attributes in <i>BlockReference</i> <a href="https://drive-cad-with-code.blogspot.com/2015/09/highlight-attributes-in-blockreference.html" target="_blank">here</a> and <a href="https://drive-cad-with-code.blogspot.com/2016/05/highlight-attributes-in-blockreference.html" target="_blank">here</a>. However, because attribute (<i>AttributeReference</i>) is owned by <i>BlockReference</i>, while entity we see in <i>BlockReference</i> is actually part of block definition (<i>BlockTableRecord</i>), we cannot highlight it individually. That is where <i>TransientGraphics</i> comes into play: we create a clone of the target entity in block definition, transform its location to where the <i>BlockReference</i> is, and then use the cloned entity as Drawable to draw T<i>ransientGraphics</i>. </p><p>Here is the class <i>BlockNestedEntityHighlighter</i>:</p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.GraphicsInterface;
<span style="color: blue;">namespace</span> HighlightEntityInBlock
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">BlockNestedEntityHighlighter</span> : <span style="color: #2b91af;">IDisposable</span>
{
<span style="color: blue;">private</span> <span style="color: #2b91af;">Entity</span> _entClone = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: #2b91af;">TransientManager</span> _tsManager =
<span style="color: #2b91af;">TransientManager</span>.CurrentTransientManager;
<span style="color: blue;">private</span> <span style="color: blue;">int</span> _colorIndex = 2;
<span style="color: blue;">public</span> <span style="color: blue;">void</span> HighlightEntityInBlock(<span style="color: #2b91af;">ObjectId</span> nestedEntId, <span style="color: #2b91af;">Matrix3d</span> transform)
{
ClearHighlight();
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = nestedEntId.Database.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> ent = (<span style="color: #2b91af;">Entity</span>)tran.GetObject(nestedEntId, <span style="color: #2b91af;">OpenMode</span>.ForRead);
_entClone = ent.Clone() <span style="color: blue;">as</span> <span style="color: #2b91af;">Entity</span>;
tran.Commit();
}
_entClone.ColorIndex = _colorIndex;
_entClone.TransformBy(transform);
_tsManager.AddTransient(
_entClone,
<span style="color: #2b91af;">TransientDrawingMode</span>.Highlight,
128,
<span style="color: blue;">new</span> <span style="color: #2b91af;">IntegerCollection</span>());
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> Dispose()
{
ClearHighlight();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ClearHighlight()
{
<span style="color: blue;">if</span> (_entClone != <span style="color: blue;">null</span>)
{
_tsManager.EraseTransient(
_entClone, <span style="color: blue;">new</span> <span style="color: #2b91af;">IntegerCollection</span>());
_entClone.Dispose();
_entClone = <span style="color: blue;">null</span>;
}
}
}
}</pre><p>Here is the code running to highlight a selected nested entity from a <i>BlockReference</i>:</p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
[<span style="color: blue;">assembly</span>: <span style="color: #2b91af;">CommandClass</span>(<span style="color: blue;">typeof</span>(HighlightEntityInBlock.<span style="color: #2b91af;">MyCommands</span>))]
<span style="color: blue;">namespace</span> HighlightEntityInBlock
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
[<span style="color: #2b91af;">CommandMethod</span>(<span style="color: #a31515;">"HlEntInBlk"</span>)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> RunMyCommand()
{
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> ed = dwg.Editor;
<span style="color: blue;">if</span> (SelectNestedEntityInBlock(
ed, <span style="color: blue;">out</span> <span style="color: #2b91af;">ObjectId</span> nestedEntId, <span style="color: blue;">out</span> <span style="color: #2b91af;">Matrix3d</span> blkTransform))
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> highlighter = <span style="color: blue;">new</span> <span style="color: #2b91af;">BlockNestedEntityHighlighter</span>())
{
highlighter.HighlightEntityInBlock(nestedEntId, blkTransform);
ed.GetString(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Press Enter to continue..."</span>);
}
ed.PostCommandPrompt();
}
<span style="color: blue;">else</span>
{
ed.WriteMessage(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">*Cancel*</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> SelectNestedEntityInBlock(<span style="color: #2b91af;">Editor</span> ed,
<span style="color: blue;">out</span> <span style="color: #2b91af;">ObjectId</span> entId, <span style="color: blue;">out</span> <span style="color: #2b91af;">Matrix3d</span> blkTransform)
{
entId = <span style="color: #2b91af;">ObjectId</span>.Null;
blkTransform = <span style="color: #2b91af;">Matrix3d</span>.Identity;
<span style="color: blue;">var</span> res = ed.GetNestedEntity(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Pick an entity in a block:"</span>);
<span style="color: blue;">if</span> (res.Status== <span style="color: #2b91af;">PromptStatus</span>.OK)
{
entId = res.ObjectId;
blkTransform = res.Transform;
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">entId: </span>{entId}<span style="color: #a31515;">"</span>);
<span style="color: blue;">return</span> <span style="color: blue;">true</span>;
}
<span style="color: blue;">else</span>
{
<span style="color: blue;">return</span> <span style="color: blue;">false</span>;
}
}
}
}</pre><p>See this video clip for the effect:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxDNwDQ15djYUxv0PdCnlP4fywJFg0IlYFJWRZe-aq9cpUyuwkKvDpfowMUxmf7IqfYmJLvvoQq3ZYbDtOsyA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><p>For simplicity, I hard-coded the highlight color as yellow. If the color of the nested entity is yellow (whether is ByBlock, or ByLayer, or by itself) , the code could be enhanced to choose a different color automatically to make the highlight stand out; f the nested entity is a Polyline, the code could make its global width thicker; the code could also use different line type, or line weight.</p><p>Update</p><p>Since the author of the original post in Autodesk .NET discussion asked for a VB.NET version of the code shown here, I did a quick conversion and now also post it here:</p><p>Class <i>BlockNestedEntityHighlighter</i>:</p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">Imports</span> System
<span style="color: blue;">Imports</span> Autodesk.AutoCAD.DatabaseServices
<span style="color: blue;">Imports</span> Autodesk.AutoCAD.Geometry
<span style="color: blue;">Imports</span> Autodesk.AutoCAD.GraphicsInterface
<span style="color: blue;">Namespace</span> HighlightEntityInBlock
<span style="color: blue;">Public</span> <span style="color: blue;">Class</span> <span style="color: #2b91af;">BlockNestedEntityHighlighter</span>
<span style="color: blue;">Implements</span> <span style="color: #2b91af;">IDisposable</span>
<span style="color: blue;">Private</span> _entClone <span style="color: blue;">As</span> <span style="color: #2b91af;">Entity</span> = <span style="color: blue;">Nothing</span>
<span style="color: blue;">Private</span> <span style="color: blue;">ReadOnly</span> _tsManager <span style="color: blue;">As</span> <span style="color: #2b91af;">TransientManager</span> = <span style="color: #2b91af;">TransientManager</span>.CurrentTransientManager
<span style="color: blue;">Private</span> _colorIndex <span style="color: blue;">As</span> <span style="color: blue;">Integer</span> = 2
<span style="color: blue;">Public</span> <span style="color: blue;">Sub</span> HighlightEntityInBlock(<span style="color: blue;">ByVal</span> nestedEntId <span style="color: blue;">As</span> <span style="color: #2b91af;">ObjectId</span>, <span style="color: blue;">ByVal</span> transform <span style="color: blue;">As</span> <span style="color: #2b91af;">Matrix3d</span>)
ClearHighlight()
<span style="color: blue;">Using</span> tran = nestedEntId.Database.TransactionManager.StartTransaction()
<span style="color: blue;">Dim</span> ent = <span style="color: blue;">CType</span>(tran.GetObject(nestedEntId, <span style="color: #2b91af;">OpenMode</span>.ForRead), <span style="color: #2b91af;">Entity</span>)
_entClone = <span style="color: blue;">TryCast</span>(ent.Clone(), <span style="color: #2b91af;">Entity</span>)
tran.Commit()
<span style="color: blue;">End</span> <span style="color: blue;">Using</span>
_entClone.ColorIndex = _colorIndex
_entClone.TransformBy(transform)
_tsManager.AddTransient(_entClone, <span style="color: #2b91af;">TransientDrawingMode</span>.Highlight, 128, <span style="color: blue;">New</span> <span style="color: #2b91af;">IntegerCollection</span>())
<span style="color: blue;">End</span> <span style="color: blue;">Sub</span>
<span style="color: blue;">Public</span> <span style="color: blue;">Sub</span> Dispose() <span style="color: blue;">Implements</span> <span style="color: #2b91af;">IDisposable</span>.Dispose
ClearHighlight()
<span style="color: blue;">End</span> <span style="color: blue;">Sub</span>
<span style="color: blue;">Private</span> <span style="color: blue;">Sub</span> ClearHighlight()
<span style="color: blue;">If</span> _entClone <span style="color: blue;">IsNot</span> <span style="color: blue;">Nothing</span> <span style="color: blue;">Then</span>
_tsManager.EraseTransient(_entClone, <span style="color: blue;">New</span> <span style="color: #2b91af;">IntegerCollection</span>())
_entClone.Dispose()
_entClone = <span style="color: blue;">Nothing</span>
<span style="color: blue;">End</span> <span style="color: blue;">If</span>
<span style="color: blue;">End</span> <span style="color: blue;">Sub</span>
<span style="color: blue;">End</span> <span style="color: blue;">Class</span>
<span style="color: blue;">End</span> <span style="color: blue;">Namespace</span></pre><p>CommandClass <i>MyCommands</i>:</p><p></p>
<pre style="font-family:Consolas;font-size:13px;color:black;background:white;"><span style="color:blue;">Imports</span> Autodesk.AutoCAD.DatabaseServices
<span style="color:blue;">Imports</span> Autodesk.AutoCAD.EditorInput
<span style="color:blue;">Imports</span> Autodesk.AutoCAD.Geometry
<span style="color:blue;">Imports</span> Autodesk.AutoCAD.Runtime
<span style="color:blue;">Imports</span> <span style="color:#2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color:#2b91af;">Application</span>
<span style="color:blue;">Imports</span> System.Runtime.InteropServices
<<span style="color:blue;">Assembly</span>: <span style="color:#2b91af;">CommandClass</span>(<span style="color:blue;">GetType</span>(HighlightEntityInBlock.<span style="color:#2b91af;">MyCommands</span>))>
<span style="color:blue;">Namespace</span> HighlightEntityInBlock
<span style="color:blue;">Public</span> <span style="color:blue;">Class</span> <span style="color:#2b91af;">MyCommands</span>
<<span style="color:#2b91af;">CommandMethod</span>(<span style="color:#a31515;">"HlEntInBlk"</span>)>
<span style="color:blue;">Public</span> <span style="color:blue;">Shared</span> <span style="color:blue;">Sub</span> RunMyCommand()
<span style="color:blue;">Dim</span> dwg = <span style="color:#2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument
<span style="color:blue;">Dim</span> ed = dwg.Editor
<span style="color:blue;">Dim</span> nestedEntId <span style="color:blue;">As</span> <span style="color:#2b91af;">ObjectId</span> = <span style="color:blue;">Nothing</span>, blkTransform <span style="color:blue;">As</span> <span style="color:#2b91af;">Matrix3d</span> = <span style="color:blue;">Nothing</span>
<span style="color:blue;">If</span> SelectNestedEntityInBlock(ed, nestedEntId, blkTransform) <span style="color:blue;">Then</span>
<span style="color:blue;">Using</span> highlighter = <span style="color:blue;">New</span> <span style="color:#2b91af;">BlockNestedEntityHighlighter</span>()
highlighter.HighlightEntityInBlock(nestedEntId, blkTransform)
ed.GetString(vbLf & <span style="color:#a31515;">"Press Enter to continue..."</span>)
<span style="color:blue;">End</span> <span style="color:blue;">Using</span>
ed.PostCommandPrompt()
<span style="color:blue;">Else</span>
ed.WriteMessage(vbLf & <span style="color:#a31515;">"*Cancel*"</span> & vbLf)
<span style="color:blue;">End</span> <span style="color:blue;">If</span>
<span style="color:blue;">End</span> <span style="color:blue;">Sub</span>
<span style="color:blue;">Private</span> <span style="color:blue;">Shared</span> <span style="color:blue;">Function</span> SelectNestedEntityInBlock(<span style="color:blue;">ByVal</span> ed <span style="color:blue;">As</span> <span style="color:#2b91af;">Editor</span>, <<span style="color:#2b91af;">Out</span>> <span style="color:blue;">ByRef</span> entId <span style="color:blue;">As</span> <span style="color:#2b91af;">ObjectId</span>, <<span style="color:#2b91af;">Out</span>> <span style="color:blue;">ByRef</span> blkTransform <span style="color:blue;">As</span> <span style="color:#2b91af;">Matrix3d</span>) <span style="color:blue;">As</span> <span style="color:blue;">Boolean</span>
entId = <span style="color:#2b91af;">ObjectId</span>.Null
blkTransform = <span style="color:#2b91af;">Matrix3d</span>.Identity
<span style="color:blue;">Dim</span> res = ed.GetNestedEntity(vbLf & <span style="color:#a31515;">"Pick an entity in a block:"</span>)
<span style="color:blue;">If</span> res.Status = <span style="color:#2b91af;">PromptStatus</span>.OK <span style="color:blue;">Then</span>
entId = res.ObjectId
blkTransform = res.Transform
ed.WriteMessage(<span style="color:#a31515;">$"\nentId: </span>{entId}<span style="color:#a31515;">"</span>)
<span style="color:blue;">Return</span> <span style="color:blue;">True</span>
<span style="color:blue;">Else</span>
<span style="color:blue;">Return</span> <span style="color:blue;">False</span>
<span style="color:blue;">End</span> <span style="color:blue;">If</span>
<span style="color:blue;">End</span> <span style="color:blue;">Function</span>
<span style="color:blue;">End</span> <span style="color:blue;">Class</span>
<span style="color:blue;">End</span> <span style="color:blue;">Namespace</span>
</pre>
Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com3tag:blogger.com,1999:blog-993393844516183990.post-82041152129657602522021-03-11T14:23:00.000-08:002021-03-11T14:23:43.952-08:00Drag & Drop In AutoCAD (3) - Drag From PlugIn UI And Drop Onto AutoCAD<p>This is the third of this series on <span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"> </span><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">Drag & Drop operation in AutoCAD. See previous ones here:</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222; font-size: 13.2px;"><span> </span></span><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222; font-size: 13.2px;"><a href="https://drive-cad-with-code.blogspot.com/2021/02/drag-drop-in-autocad-1-scenarios.html" target="_blank">Drag & Drop In AutoCAD (1) - The Scenarios</a></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222; font-size: 13.2px;"> <a href="https://drive-cad-with-code.blogspot.com/2021/02/drag-drop-in-autocad-2-drag-from.html" target="_blank">Drag & Drop In AutoCAD (2) - Drag From External Application And Drop Onto AutoCAD</a><br /></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>In this post I look into the scenario of dragging from an AutoCAD plugin UI and dropping onto AutoCAD's editor. Typically, we may have a form/window UI in our plugin application, where some data representing entities to be created in drawing. User can click a button on the UI to trigger the creation in the drawing with data in the UI; or we can let user to drag certain portion of the UI (i.e. dragging some data) and drop the data onto AutoCAD editor to let AutoCAD do something with the dropped data, usually, it is to create entity or entities with the data at the location of dropping.</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>If one drags something on the UI and drop it onto the same UI, the code logic of doing it is like:</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>1. Set the drop target control's <i>AllowDrop</i> property to True;</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>2. Handle <i>MouseMove</i> event of the dragging target control;</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>3. In the <i>MouseMov</i>e event handler, test if mouse' left button is pressed when mouse is moving;</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>4. If mouse' left button is pressed, it is dragging; collect data you want to be transferred for dropping and package the data into <i>IDataObject</i>; then call drop target control's <i>DoDragDrop()</i> method with the <i>IDataObject</i> passed in;</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>5. Handle drop target control's <i>DragDrop</i> event, where you can extract data transferred via <i>IDataObject</i>, and then do whatever with the data suitable to the drop target control.</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>Now that we want to change the drop target to AutoCAD application, not somewhere on the same UI as the drag target, the code logic for this kind of drag and drop process changes to:</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>1.Handle <i>MouseMove</i> event of drag target control on the UI;</span></span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>2. </span></span><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">In the <i>MouseMove</i> event handler, test if mouse' left button is pressed when mouse is moving;</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">3. </span><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">If mouse' left button is pressed, it is dragging; collect data you want to be transferred for dropping and package the data into <i>IDataObject</i>; then call <i>Application.DoDragDrop()</i>, where you not only pass <i>IDataObject</i> into it, you also need to implement the abstract class of <i>Autodesk.AutoCAD.Windows.DropTarget</i> and pass it into <i>Application.DoDragDrop(</i>);</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">4. In your custom <i>DropTarget</i> class, you override the abstract method <i>OnDrop()</i>: that is where you unpackage data from the <i>IDataObject</i> and do whatever you need to do with the data, mostly, it is to create entity or entities at the dropping location.</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">I have to point out that dragging from plugin's UI and dropping onto AutoCAD only works with modeless plugin UI.</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">This is the plugin UI, which is a WinForm modeless dialog:</span></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-WFXxWTviae0/YEpWw0--zrI/AAAAAAAABiE/FJBKQgDphpg9NtMiqlyfatXNt5aYZ1j7ACLcBGAsYHQ/s465/Plugin%2BUI.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="465" data-original-width="318" height="400" src="https://1.bp.blogspot.com/-WFXxWTviae0/YEpWw0--zrI/AAAAAAAABiE/FJBKQgDphpg9NtMiqlyfatXNt5aYZ1j7ACLcBGAsYHQ/w274-h400/Plugin%2BUI.png" width="274" /></a></div><br /><span style="color: #222222;"><span style="background-color: white;">With the form floating on top of AutoCAD, user can drag one the 2 blue labels and drop onto AutoCAD to generate a line or circle entity; or user can drag a block name from the block list box to insert a block reference at dropping location. Here is the form's code behind:</span></span><p></p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">DrawForm</span> : <span style="color: #2b91af;">Form</span>
{
<span style="color: blue;">public</span> <span style="color: #2b91af;">DrawForm</span>()
{
InitializeComponent();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> BUttonClose_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
Visible = <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> InitailizeControls()
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
<span style="color: blue;">var</span> blocks = <span style="color: #2b91af;">CadHelper</span>.GetBlockList();
<span style="color: blue;">if</span> (blocks.Count() > 0)
{
<span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> blk <span style="color: blue;">in</span> blocks)
{
BlockListBox.Items.Add(blk);
}
}
}
<span style="color: blue;">finally</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
}
ComboAngle.SelectedIndex = 0;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> DrawForm_FormClosing(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">FormClosingEventArgs</span> e)
{
e.Cancel = <span style="color: blue;">true</span>;
Visible = <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> DrawForm_Load(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
InitailizeControls();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> Linelabel_MouseMove(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">MouseEventArgs</span> e)
{
<span style="color: blue;">if</span> (e.Button== <span style="color: #2b91af;">MouseButtons</span>.Left)
{
<span style="color: #2b91af;">EntityInfo</span> entInfo = <span style="color: blue;">new</span> <span style="color: #2b91af;">LineInfo</span>
{
Length = <span style="color: #2b91af;">Convert</span>.ToDouble(TextBoxLength.Text),
Angle = <span style="color: #2b91af;">Convert</span>.ToDouble(ComboAngle.Text)
};
<span style="color: #2b91af;">EntityDropper</span>.DropEntity(
<span style="color: blue;">this</span>, <span style="color: #2b91af;">EntityType</span>.Line, entInfo, <span style="color: #2b91af;">CadHelper</span>.CreateLine);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> Circlelabel_MouseMove(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">MouseEventArgs</span> e)
{
<span style="color: blue;">if</span> (e.Button == <span style="color: #2b91af;">MouseButtons</span>.Left)
{
<span style="color: #2b91af;">EntityInfo</span> entInfo = <span style="color: blue;">new</span> <span style="color: #2b91af;">CircleInfo</span>
{
Radius=<span style="color: #2b91af;">Convert</span>.ToDouble(TextBoxRadius.Text)
};
<span style="color: #2b91af;">EntityDropper</span>.DropEntity(
<span style="color: blue;">this</span>, <span style="color: #2b91af;">EntityType</span>.Circle, entInfo, <span style="color: #2b91af;">CadHelper</span>.CreateCircle);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> BlockListBox_MouseMove(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">MouseEventArgs</span> e)
{
<span style="color: blue;">if</span> (BlockListBox.SelectedIndex < 0) <span style="color: blue;">return</span>;
<span style="color: blue;">if</span> (e.Button == <span style="color: #2b91af;">MouseButtons</span>.Left)
{
<span style="color: #2b91af;">EntityInfo</span> entInfo = <span style="color: blue;">new</span> <span style="color: #2b91af;">BlockInfo</span>
{
BlockName=BlockListBox.Text
};
<span style="color: #2b91af;">EntityDropper</span>.DropEntity(
<span style="color: blue;">this</span>, <span style="color: #2b91af;">EntityType</span>.BlockReference, entInfo, <span style="color: #2b91af;">CadHelper</span>.CreateBlockReference);
}
}
}
}</pre><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">As the code shows, the dragging begins by handling <i>MouseMove</i> event when the left mouse button is pressed, and the data used for creating entity is passed into a static method <i>DropEntity()</i> from a utility class <i>EntityDropper</i>, where <i>Application.DoDragDrop()</i> will be called to complete the drag & drop operation.</span></p><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;">First, I needed to implement custom class that is derived from abstract class <i>Autodesk.AutoCAD.Windows.DropTarget</i>:</span></p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Windows;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
<span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntityDropTarget</span> : <span style="color: #2b91af;">DropTarget</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: #2b91af;">Func</span><<span style="color: #2b91af;">Document</span>, <span style="color: #2b91af;">EntityInfo</span>, <span style="color: #2b91af;">Point3d</span>, <span style="color: #2b91af;">ObjectId</span>> _createFunction;
<span style="color: blue;">private</span> <span style="color: #2b91af;">Point3d</span> _position = <span style="color: #2b91af;">Point3d</span>.Origin;
<span style="color: blue;">public</span> <span style="color: #2b91af;">EntityDropTarget</span>(<span style="color: #2b91af;">Func</span><<span style="color: #2b91af;">Document</span>, <span style="color: #2b91af;">EntityInfo</span>, <span style="color: #2b91af;">Point3d</span>, <span style="color: #2b91af;">ObjectId</span>> entityCreation)
{
_createFunction = entityCreation;
}
<span style="color: blue;">public</span> <span style="color: #2b91af;">ObjectId</span> EntityId { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; } = <span style="color: #2b91af;">ObjectId</span>.Null;
<span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">void</span> OnDrop(<span style="color: #2b91af;">DragEventArgs</span> e)
{
<span style="color: green;">//Convert windows location of mouse into AutoCAD editor's</span>
<span style="color: green;">//WCS coordinate (Point3d)</span>
<span style="color: #2b91af;">Document</span> dwg = Autodesk.AutoCAD.ApplicationServices.
<span style="color: #2b91af;">Application</span>.DocumentManager.MdiActiveDocument;
System.Drawing.<span style="color: #2b91af;">Point</span> pt = <span style="color: blue;">new</span> System.Drawing.<span style="color: #2b91af;">Point</span>(e.X, e.Y);
_position = dwg.Editor.PointToWorld(pt);
<span style="color: green;">// Get data passed from Application.DoDragDrop()</span>
<span style="color: blue;">var</span> data = e.Data.GetData(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">EntityInfo</span>)) <span style="color: blue;">as</span> <span style="color: #2b91af;">EntityInfo</span>;
<span style="color: green;">//Create the entity</span>
EntityId = _createFunction(dwg, data, _position);
<span style="color: #2b91af;">CadApp</span>.MainWindow.Focus();
dwg.Editor.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Entity </span>{EntityId}<span style="color: #a31515;"> has been created @</span>{_position}<span style="color: #a31515;">"</span>);
}
}
}</pre><p><span style="color: #222222;"><span style="background-color: white;">As the code shows in <i>EntityDropTarget </i>class that the drag & drop process is completed in <i>OnDrop()</i> method, where the data required for creating entity is extracted from <i>IDataObject</i> and then the actual entity generating work is done. In order to decouple the drag & drop process with how entity is to be created, I used an injected function (<i>Func<Document, EntityInfo, Point3d, ObjectId></i>) to do the entity generating work. This allows me coding different way to generate entity without having to change the code for drag & drop. For example, for most user-friendly way in real production, it would be better to use a Jig to create entity, which would allow user to position the entity more accurately. But in this article, for simplicity, I just supply a simple entity creating process. Also, it is with <i>OnDrop()</i> method, the code can translate the mouse's drop location into AutoCAD's coordinate, so that the code would know where to create the entity. However, because it is quite difficult for user to drop (release let button) at an accurate location without usual AutoCAD drafting assistant (object snapping, coordinate entering), the drop location obtained from <i>OnDrop()</i> method would often be not good enough. That is why I just said, in real production, it would be ideal to use Jig for accurate entity creation.</span></span></p><p><span style="color: #222222;"><span style="background-color: white;">Here is the utility class that has a static method to actually call<i> Application.DoDragDrop()</i>:</span></span></p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
<span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">enum</span> <span style="color: #2b91af;">EntityType</span>
{
Line=0,
Circle=1,
BlockReference=2,
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntityDropper</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> DropEntity(
System.Windows.Forms.<span style="color: #2b91af;">Form</span> userUi,
<span style="color: #2b91af;">EntityType</span> entType,
<span style="color: #2b91af;">EntityInfo</span> entityInfo,
<span style="color: #2b91af;">Func</span><<span style="color: #2b91af;">Document</span>, <span style="color: #2b91af;">EntityInfo</span>, <span style="color: #2b91af;">Point3d</span>, <span style="color: #2b91af;">ObjectId</span>> entityCreation)
{
<span style="color: blue;">var</span> target = <span style="color: blue;">new</span> <span style="color: #2b91af;">EntityDropTarget</span>(entityCreation);
<span style="color: blue;">object</span> data;
<span style="color: #2b91af;">IDataObject</span> dataObject;
<span style="color: blue;">if</span> (entType== <span style="color: #2b91af;">EntityType</span>.Line)
{
data = (<span style="color: #2b91af;">LineInfo</span>)entityInfo;
}
<span style="color: blue;">else</span> <span style="color: blue;">if</span> (entType== <span style="color: #2b91af;">EntityType</span>.Circle)
{
data = (<span style="color: #2b91af;">CircleInfo</span>)entityInfo;
}
<span style="color: blue;">else</span>
{
data = (<span style="color: #2b91af;">BlockInfo</span>)entityInfo;
}
dataObject = <span style="color: blue;">new</span> <span style="color: #2b91af;">DataObject</span>();
dataObject.SetData(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">EntityInfo</span>), data);
<span style="color: #2b91af;">CadApp</span>.DoDragDrop(userUi, dataObject, <span style="color: #2b91af;">DragDropEffects</span>.Copy, target);
<span style="color: blue;">return</span> target.EntityId;
}
}
}</pre><p><span face="Arial, Tahoma, Helvetica, FreeSans, sans-serif" style="background-color: white; color: #222222;"><span>Obviously I also need a data class for transferring entity creating information between dragging and dropping via <i>IDataObject:</i></span></span></p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">abstract</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntityInfo</span>
{
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">LineInfo</span> : <span style="color: #2b91af;">EntityInfo</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">double</span> Angle { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">double</span> Length { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">CircleInfo</span> : <span style="color: #2b91af;">EntityInfo</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">double</span> Radius { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">BlockInfo</span> : <span style="color: #2b91af;">EntityInfo</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">string</span> BlockName { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
}
}</pre><p><span style="color: #222222;"><span style="background-color: white;">In order to inject an entity creating function, all the data classes (<i>LineInfo, CircleInfo and BlockInfo</i>) are derived from an abstract class <i>EntityInfo</i>, which does not have its own properties/methods and only serves as a base class, so that the injected entity creating function can have a unified method signature.</span></span></p><p>Of course I need those injectable entity creating functions:</p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
<span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">CadHelper</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">IEnumerable</span><<span style="color: blue;">string</span>> GetBlockList()
{
<span style="color: #2b91af;">IEnumerable</span><<span style="color: blue;">string</span>> names = <span style="color: blue;">null</span>;
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> tbl = (<span style="color: #2b91af;">BlockTable</span>)tran.GetObject(dwg.Database.BlockTableId, <span style="color: #2b91af;">OpenMode</span>.ForRead);
names = GetBlockNames(tbl, tran);
tran.Commit();
}
<span style="color: blue;">return</span> names;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> CreateLine(<span style="color: #2b91af;">Document</span> dwg, <span style="color: #2b91af;">EntityInfo</span> lineInfo, <span style="color: #2b91af;">Point3d</span> position)
{
<span style="color: blue;">var</span> newId = <span style="color: #2b91af;">ObjectId</span>.Null;
<span style="color: blue;">var</span> len = ((<span style="color: #2b91af;">LineInfo</span>)lineInfo).Length;
<span style="color: blue;">var</span> ang = ((<span style="color: #2b91af;">LineInfo</span>)lineInfo).Angle * <span style="color: #2b91af;">Math</span>.PI / 180.0;
<span style="color: blue;">var</span> start = position;
<span style="color: blue;">var</span> x = position.X + len * <span style="color: #2b91af;">Math</span>.Cos(ang);
<span style="color: blue;">var</span> y = position.Y + len * <span style="color: #2b91af;">Math</span>.Sin(ang);
<span style="color: blue;">var</span> end = <span style="color: blue;">new</span> <span style="color: #2b91af;">Point3d</span>(x, y, position.Z);
<span style="color: blue;">using</span> (dwg.LockDocument())
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> line = <span style="color: blue;">new</span> <span style="color: #2b91af;">Line</span>(start, end);
line.SetDatabaseDefaults();
<span style="color: blue;">var</span> space = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(
dwg.Database.CurrentSpaceId, <span style="color: #2b91af;">OpenMode</span>.ForWrite);
newId = space.AppendEntity(line);
tran.AddNewlyCreatedDBObject(line, <span style="color: blue;">true</span>);
tran.Commit();
}
}
<span style="color: blue;">return</span> newId;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> CreateCircle(<span style="color: #2b91af;">Document</span> dwg, <span style="color: #2b91af;">EntityInfo</span> circleInfo, <span style="color: #2b91af;">Point3d</span> position)
{
<span style="color: blue;">var</span> newId = <span style="color: #2b91af;">ObjectId</span>.Null;
<span style="color: blue;">using</span> (dwg.LockDocument())
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> circle = <span style="color: blue;">new</span> <span style="color: #2b91af;">Circle</span>();
circle.Center = position;
circle.Radius = ((<span style="color: #2b91af;">CircleInfo</span>)circleInfo).Radius;
circle.SetDatabaseDefaults();
<span style="color: blue;">var</span> space = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(
dwg.Database.CurrentSpaceId, <span style="color: #2b91af;">OpenMode</span>.ForWrite);
newId = space.AppendEntity(circle);
tran.AddNewlyCreatedDBObject(circle, <span style="color: blue;">true</span>);
tran.Commit();
}
}
<span style="color: blue;">return</span> newId;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> CreateBlockReference(<span style="color: #2b91af;">Document</span> dwg, <span style="color: #2b91af;">EntityInfo</span> blkInfo, <span style="color: #2b91af;">Point3d</span> position)
{
<span style="color: blue;">var</span> newId = <span style="color: #2b91af;">ObjectId</span>.Null;
<span style="color: blue;">using</span> (dwg.LockDocument())
{
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> bt = (<span style="color: #2b91af;">BlockTable</span>)tran.GetObject(
dwg.Database.BlockTableId, <span style="color: #2b91af;">OpenMode</span>.ForRead);
<span style="color: blue;">if</span> (bt.Has(((<span style="color: #2b91af;">BlockInfo</span>)blkInfo).BlockName))
{
<span style="color: blue;">var</span> blk = <span style="color: blue;">new</span> <span style="color: #2b91af;">BlockReference</span>(position, bt[((<span style="color: #2b91af;">BlockInfo</span>)blkInfo).BlockName]);
blk.SetDatabaseDefaults();
<span style="color: blue;">var</span> space = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(
dwg.Database.CurrentSpaceId, <span style="color: #2b91af;">OpenMode</span>.ForWrite);
newId = space.AppendEntity(blk);
tran.AddNewlyCreatedDBObject(blk, <span style="color: blue;">true</span>);
}
tran.Commit();
}
}
<span style="color: blue;">return</span> newId; ;
}
<span style="color: grey;">#region</span> private methods
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">IEnumerable</span><<span style="color: blue;">string</span>> GetBlockNames(<span style="color: #2b91af;">BlockTable</span> tbl, <span style="color: #2b91af;">Transaction</span> tran)
{
<span style="color: blue;">var</span> names = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: blue;">string</span>>();
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">ObjectId</span> id <span style="color: blue;">in</span> tbl)
{
<span style="color: blue;">var</span> blk = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(id, <span style="color: #2b91af;">OpenMode</span>.ForRead);
<span style="color: blue;">if</span> (!blk.IsLayout)
{
names.Add(blk.Name);
}
}
<span style="color: blue;">return</span> <span style="color: blue;">from</span> n <span style="color: blue;">in</span> names <span style="color: blue;">orderby</span> n <span style="color: blue;">ascending</span> <span style="color: blue;">select</span> n;
}
<span style="color: grey;">#endregion</span>
}
}</pre><p>As I have already mentioned, in these injectable entity creating function, I just create entities (<i>Line/Circle/BlockReference</i>) at drop location, for the reason of simplicity. In real production, I would use <i>EntityJig</i> for better usability.</p><p>Finally, here is the <i>CommandClass</i> that put everything together to work, which simply show the plugin UI as singleton modeless dialog to allow user drag something from it and drop onto AutoCAD to create entities.</p>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
[<span style="color: blue;">assembly</span>: <span style="color: #2b91af;">CommandClass</span>(<span style="color: blue;">typeof</span>(DragFromPluginUiToAcad.<span style="color: #2b91af;">MyCommands</span>))]
<span style="color: blue;">namespace</span> DragFromPluginUiToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">DrawForm</span> _floatForm = <span style="color: blue;">null</span>;
[<span style="color: #2b91af;">CommandMethod</span>(<span style="color: #a31515;">"AddEntByDrag"</span>, <span style="color: #2b91af;">CommandFlags</span>.Session)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> AddEntityFromModelessForm()
{
<span style="color: blue;">if</span> (_floatForm==<span style="color: blue;">null</span>)
{
_floatForm = <span style="color: blue;">new</span> <span style="color: #2b91af;">DrawForm</span>();
}
<span style="color: #2b91af;">CadApp</span>.ShowModelessDialog(<span style="color: #2b91af;">CadApp</span>.MainWindow.Handle, _floatForm);
}
}
}
</pre>
<p>See this video for the code in action:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyxmWkhgcXu_b7caFc8YU3XKBAI62kPN3iRN9sN5NoZ6hKtyBVBrMbo9FX8bEEdfMTz_QqacGW_M1MjrtV8HQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p><span style="font-size: medium;"><br /></span></p><p><span style="font-size: medium;">Some Thoughts</span></p><p>While using drag & drop to create entities from custom plugin UI gives us programmers another "fancy way" to deliver CAD user experience, it is more common to trigger the process with proper control on the UI, typically, user would click button for this operation, which can be used with both modal and modeless UI (if the UI is modal, the code needs to call <i>Editor.StartUserInteraction()</i> to temporarily hide the modal UI). However, if the UI is modeless, the action (of creating entities) triggered from UI should be wrapped in a custom CommandMethod and UI's event handler (for the button click) should call <i>SendStringToExecute() </i>(see <a href="https://drive-cad-with-code.blogspot.com/2021/03/modeless-formwindow-do-something-and.html" target="_blank">this article</a> of mine on this topic); but when doing drag & and drop, as shown here, the action triggered from modeless UI (i.e. dragging, and then dropping) is not wrapped in a CommandMethod and no <i>SendStringToExecute()</i> involved.</p><p>Again, the dropping part of drag & drop operation does not provide an accurate dropping location as AutoCAD native drafting function does, it may not be as attractive as it looks like at first, considering that user has to hold left button while moving the mouse, in comparison to a single click of a button, or a double-click on something (such as a block icon on Tool Palette, or the latest block inserting palette). But, since drag & drop is a traditional GUI trick, some users may like it, if we can provide it.</p><p><br /></p><p><br /></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0tag:blogger.com,1999:blog-993393844516183990.post-6034431867119299582021-03-09T11:44:00.002-08:002021-03-17T10:30:51.730-07:00Modeless Form/Window - Do Something And Update Its View - Updated<p>Using modeless from/window in AutoCAD plugin brings an unique challenge to programmers because of it being presented in ApplicationContext. One of most common issues with using modeless form/window is how to handle user interaction on the form/window, if the user interaction is meant to make changes to current drawing (MdiActiveDocument), such as user clicking a button on the form/window in order to create entities or update entities in drawing, and afterward update/refresh the modeless form/window to reflect the changes just made to the drawing.</p><p>It has been strongly recommended since the earlier stage of AutoCAD .NET API that the drawing database change action triggered from modeless form/window should be wrapped in a command and executed by calling SendStringToExecute() method, rather than directly from the form/window's user interaction event handler.</p><p>However, if the action executed by SendStringToExecute() causes drawing change and the change needs to be reflected in the modeless form/window, a proper care is needed in order for the form/window to wait until the sent command complete before the form/window update begins. </p><p><a href="https://forums.autodesk.com/t5/net/wpf-window-and-autocad-not-working-synchronously/td-p/10128980" target="_blank">A recent question on this issue</a> was posted in Autodesk's .NET discussion forum and I posted a reply by suggesting use SendStringToExecute(), and I also proposed to use CommandEnded event handler to trigger modeless UI update. I just completed a sample project, so that anyone who is interested in this topic would be able to see a more complete picture of handling this issue. </p><p>This is a simplified WinForm UI (the principle in in this article would apply to WPF UI, of course):</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-Rfhu6f84_NU/YEe_Ki7VZqI/AAAAAAAABh8/eKbymF8DCQ8z-o2PORGOVxgKKcRDPiTyACLcBGAsYHQ/s492/ModelessForm.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="332" data-original-width="492" height="270" src="https://1.bp.blogspot.com/-Rfhu6f84_NU/YEe_Ki7VZqI/AAAAAAAABh8/eKbymF8DCQ8z-o2PORGOVxgKKcRDPiTyACLcBGAsYHQ/w400-h270/ModelessForm.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>Here the form's code behind:<div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
<span style="color: blue;">namespace</span> ModelessDialogWithAction
{
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntitiesForm</span> : <span style="color: #2b91af;">Form</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: #2b91af;">DocumentCollection</span> _dwgManager = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _currentCommand = <span style="color: #a31515;">""</span>;
<span style="color: blue;">public</span> <span style="color: #2b91af;">EntitiesForm</span>()
{
InitializeComponent();
}
<span style="color: blue;">public</span> <span style="color: #2b91af;">EntitiesForm</span>(<span style="color: #2b91af;">DocumentCollection</span> dwgManager) : <span style="color: blue;">this</span>()
{
_dwgManager = dwgManager;
_dwgManager.DocumentActivated += (o, e) =>
{
<span style="color: blue;">if</span> (e.Document.UnmanagedObject != DocumentPointer)
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.WaitCursor;
RefreshEntityData(e.Document);
}
<span style="color: blue;">finally</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
}
}
};
}
<span style="color: blue;">public</span> <span style="color: #2b91af;">IntPtr</span> DocumentPointer { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">void</span> RefreshEntityData(<span style="color: #2b91af;">Document</span> dwg)
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.WaitCursor;
ListViewEntities.Items.Clear();
<span style="color: blue;">var</span> ents = <span style="color: #2b91af;">CadUtil</span>.CollectEntitiesInModelSpace(dwg);
<span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> ent <span style="color: blue;">in</span> ents)
{
<span style="color: blue;">var</span> item = <span style="color: blue;">new</span> <span style="color: #2b91af;">ListViewItem</span>(ent.EntityType);
item.SubItems.Add(ent.EntityId.ToString());
item.SubItems.Add(ent.EntityLayer);
ListViewEntities.Items.Add(item);
}
LabelCount.Text = ListViewEntities.Items.Count.ToString();
}
<span style="color: blue;">finally</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
}
DocumentPointer = dwg.UnmanagedObject;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ExecuteCommand(<span style="color: blue;">string</span> commandName)
{
_dwgManager.MdiActiveDocument.CommandEnded += MdiActiveDocument_CommandEnded;
_currentCommand = commandName;
<span style="color: #2b91af;">CadApp</span>.MainWindow.Focus();
_dwgManager.MdiActiveDocument.SendStringToExecute(
commandName + <span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>, <span style="color: blue;">true</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> MdiActiveDocument_CommandEnded(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">CommandEventArgs</span> e)
{
<span style="color: blue;">if</span> (e.GlobalCommandName.ToUpper() == _currentCommand.ToUpper())
{
_dwgManager.MdiActiveDocument.CommandEnded -= MdiActiveDocument_CommandEnded;
RefreshEntityData(
_dwgManager.MdiActiveDocument);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonAddLine_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
ExecuteCommand(<span style="color: #2b91af;">MyCommands</span>.ADD_LINE_COMMAND);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonAddCircle_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
ExecuteCommand(<span style="color: #2b91af;">MyCommands</span>.ADD_CIRCLE_COMMAND);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> EntitiesForm_FormClosing(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">FormClosingEventArgs</span> e)
{
e.Cancel = <span style="color: blue;">true</span>;
Visible = <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonClose_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
Visible = <span style="color: blue;">false</span>;
}
}
}</pre>
Here is the main class (CommandClass) that initializes/shows the modeless form, and defines 2 CommandMethods for the modeless form to call with SendStringToExecute(), noticing that I set CommandFlags.NoHistory with these 2 commands, which is not mandatory, but is meant for the 2 commands not being seen by user easily.</div><div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> Autodesk.AutoCAD.Runtime;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
[<span style="color: blue;">assembly</span>: <span style="color: #2b91af;">CommandClass</span>(<span style="color: blue;">typeof</span>(ModelessDialogWithAction.<span style="color: #2b91af;">MyCommands</span>))]
[<span style="color: blue;">assembly</span>: <span style="color: #2b91af;">ExtensionApplication</span>(<span style="color: blue;">typeof</span>(ModelessDialogWithAction.<span style="color: #2b91af;">MyCommands</span>))]
<span style="color: blue;">namespace</span> ModelessDialogWithAction
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MyCommands</span> : <span style="color: #2b91af;">IExtensionApplication</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> ADD_LINE_COMMAND = <span style="color: #a31515;">"ADDLINE"</span>;
<span style="color: blue;">public</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> ADD_CIRCLE_COMMAND = <span style="color: #a31515;">"ADDCIRCLE"</span>;
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">EntitiesForm</span> _entitiesForm = <span style="color: blue;">null</span>;
<span style="color: grey;">#region</span> IExtensionApplication implementing
<span style="color: blue;">public</span> <span style="color: blue;">void</span> Initialize()
{
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> ed = dwg.Editor;
<span style="color: blue;">try</span>
{
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Initializing custom add-in </span><span style="color: #b776fb;">\"</span>{<span style="color: blue;">this</span>.GetType().Name}<span style="color: #b776fb;">\"</span><span style="color: #a31515;">..."</span>);
_entitiesForm = <span style="color: blue;">new</span> <span style="color: #2b91af;">EntitiesForm</span>(<span style="color: #2b91af;">CadApp</span>.DocumentManager);
_entitiesForm.RefreshEntityData(<span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument);
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Intializing done.</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>);
}
<span style="color: blue;">catch</span> (System.<span style="color: #2b91af;">Exception</span> ex)
{
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Initializing error:</span><span style="color: #b776fb;">\n</span>{ex.Message}<span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>);
}
}
<span style="color: blue;">public</span> <span style="color: blue;">void</span> Terminate()
{
}
<span style="color: grey;">#endregion</span>
[<span style="color: #2b91af;">CommandMethod</span>(<span style="color: #a31515;">"ShowForm"</span>, <span style="color: #2b91af;">CommandFlags</span>.Session)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> ShowForm()
{
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">if</span> (_entitiesForm.DocumentPointer!=dwg.UnmanagedObject)
{
_entitiesForm.RefreshEntityData(dwg);
}
<span style="color: #2b91af;">CadApp</span>.ShowModelessDialog(_entitiesForm);
}
[<span style="color: #2b91af;">CommandMethod</span>(ADD_LINE_COMMAND, <span style="color: #2b91af;">CommandFlags</span>.NoHistory)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> AddLineCommand()
{
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> ed = dwg.Editor;
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">CadUtil</span>.AddLine(dwg);
}
<span style="color: blue;">catch</span> (System.<span style="color: #2b91af;">Exception</span> ex)
{
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Error:</span><span style="color: #b776fb;">\n</span>{ex.Message}<span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>);
}
}
[<span style="color: #2b91af;">CommandMethod</span>(ADD_CIRCLE_COMMAND, <span style="color: #2b91af;">CommandFlags</span>.NoHistory)]
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> AddCircleCommand()
{
<span style="color: blue;">var</span> dwg = <span style="color: #2b91af;">CadApp</span>.DocumentManager.MdiActiveDocument;
<span style="color: blue;">var</span> ed = dwg.Editor;
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">CadUtil</span>.AddCircle(dwg);
}
<span style="color: blue;">catch</span> (System.<span style="color: #2b91af;">Exception</span> ex)
{
ed.WriteMessage(<span style="color: #a31515;">$"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Error:</span><span style="color: #b776fb;">\n</span>{ex.Message}<span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>);
}
}
}
}</pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Finally, here is a helper class that does the drawing/database changes, which can be executed from UI/CommandMethod:</span></span></pre>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Linq;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.DatabaseServices;
<span style="color: blue;">using</span> Autodesk.AutoCAD.EditorInput;
<span style="color: blue;">using</span> Autodesk.AutoCAD.Geometry;
<span style="color: blue;">namespace</span> ModelessDialogWithAction
{
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntityInfo</span>
{
<span style="color: blue;">public</span> <span style="color: #2b91af;">ObjectId</span> EntityId { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">string</span> EntityType { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
<span style="color: blue;">public</span> <span style="color: blue;">string</span> EntityLayer { <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }
}
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">CadUtil</span>
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">IEnumerable</span><<span style="color: #2b91af;">EntityInfo</span>> CollectEntitiesInModelSpace(<span style="color: #2b91af;">Document</span> dwg)
{
<span style="color: blue;">var</span> lst = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">EntityInfo</span>>();
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = dwg.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> model = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(
<span style="color: #2b91af;">SymbolUtilityServices</span>.GetBlockModelSpaceId(dwg.Database), <span style="color: #2b91af;">OpenMode</span>.ForRead);
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">ObjectId</span> id <span style="color: blue;">in</span> model)
{
<span style="color: blue;">var</span> ent = (<span style="color: #2b91af;">Entity</span>)tran.GetObject(id, <span style="color: #2b91af;">OpenMode</span>.ForRead);
lst.Add(<span style="color: blue;">new</span> <span style="color: #2b91af;">EntityInfo</span>
{
EntityId = id,
EntityType = id.ObjectClass.DxfName,
EntityLayer = ent.Layer
});
}
tran.Commit();
}
<span style="color: blue;">return</span> <span style="color: blue;">from</span> e <span style="color: blue;">in</span> lst
<span style="color: blue;">orderby</span>
e.EntityType <span style="color: blue;">ascending</span>,
e.EntityLayer <span style="color: blue;">ascending</span>,
e.EntityId.ToString() <span style="color: blue;">ascending</span>
<span style="color: blue;">select</span> e;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> AddLine(<span style="color: #2b91af;">Document</span> dwg)
{
<span style="color: blue;">var</span> res = dwg.Editor.GetPoint(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Select line's Start Point:"</span>);
<span style="color: blue;">if</span> (res.Status== <span style="color: #2b91af;">PromptStatus</span>.OK)
{
<span style="color: blue;">var</span> start = res.Value;
<span style="color: blue;">var</span> opt = <span style="color: blue;">new</span> <span style="color: #2b91af;">PromptPointOptions</span>(
<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Select line's End Point:"</span>);
opt.UseBasePoint = <span style="color: blue;">true</span>;
opt.BasePoint = start;
opt.UseDashedLine = <span style="color: blue;">true</span>;
res = dwg.Editor.GetPoint(opt);
<span style="color: blue;">if</span> (res.Status== <span style="color: #2b91af;">PromptStatus</span>.OK)
{
<span style="color: blue;">var</span> end = res.Value;
<span style="color: blue;">return</span> CreateLine(dwg.Database, start, end);
}
}
dwg.Editor.WriteMessage(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">*Cancel*"</span>);
<span style="color: blue;">return</span> <span style="color: #2b91af;">ObjectId</span>.Null;
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> AddCircle(<span style="color: #2b91af;">Document</span> dwg)
{
<span style="color: blue;">var</span> res = dwg.Editor.GetPoint(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Select circle's Center Point:"</span>);
<span style="color: blue;">if</span> (res.Status == <span style="color: #2b91af;">PromptStatus</span>.OK)
{
<span style="color: blue;">var</span> center = res.Value;
<span style="color: blue;">var</span> opt = <span style="color: blue;">new</span> <span style="color: #2b91af;">PromptDoubleOptions</span>(
<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">Enter circle's Radius:"</span>);
opt.AllowNegative = <span style="color: blue;">false</span>;
opt.AllowNone = <span style="color: blue;">false</span>;
opt.DefaultValue = 300.0;
<span style="color: blue;">var</span> dRes = dwg.Editor.GetDouble(opt);
<span style="color: blue;">if</span> (dRes.Status == <span style="color: #2b91af;">PromptStatus</span>.OK)
{
<span style="color: blue;">var</span> radius = dRes.Value;
<span style="color: blue;">return</span> CreateCircle(dwg.Database, center, radius);
}
}
dwg.Editor.WriteMessage(<span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">*Cancel*"</span>);
<span style="color: blue;">return</span> <span style="color: #2b91af;">ObjectId</span>.Null;
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> CreateLine(<span style="color: #2b91af;">Database</span> db, <span style="color: #2b91af;">Point3d</span> startPt, <span style="color: #2b91af;">Point3d</span> endPt)
{
<span style="color: blue;">var</span> line = <span style="color: blue;">new</span> <span style="color: #2b91af;">Line</span>(startPt, endPt);
line.SetDatabaseDefaults(db);
<span style="color: blue;">return</span> AddEntityToModelSpace(db, line);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> CreateCircle(<span style="color: #2b91af;">Database</span> db, <span style="color: #2b91af;">Point3d</span> centerPt, <span style="color: blue;">double</span> radius)
{
<span style="color: blue;">var</span> circle = <span style="color: blue;">new</span> <span style="color: #2b91af;">Circle</span>();
circle.Center = centerPt;
circle.Radius = radius;
circle.SetDatabaseDefaults(db);
<span style="color: blue;">return</span> AddEntityToModelSpace(db, circle);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">ObjectId</span> AddEntityToModelSpace(<span style="color: #2b91af;">Database</span> db, <span style="color: #2b91af;">Entity</span> ent)
{
<span style="color: blue;">var</span> id = <span style="color: #2b91af;">ObjectId</span>.Null;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran = db.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> model = (<span style="color: #2b91af;">BlockTableRecord</span>)tran.GetObject(
<span style="color: #2b91af;">SymbolUtilityServices</span>.GetBlockModelSpaceId(db), <span style="color: #2b91af;">OpenMode</span>.ForWrite);
model.AppendEntity(ent);
tran.AddNewlyCreatedDBObject(ent, <span style="color: blue;">true</span>);
tran.Commit();
}
<span style="color: blue;">return</span> id;
}
}
}</pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">There is no DocumentLock is used in the code, in spite the adding entity action is triggered from modeless form/window, because the command executed by SendStringToExecute() does the document locking/unlock automatically. </span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Following video clip shows the modeless form/window gets updated properly by handling CommandEnded event, targeting the 2 CommandMethods. However, depending on what data is shown on the form/window, only handling the commands directly triggered by the form/window may not enough for the form/window to reflect the data changes, but this is beyond my discussion here.</span></span></pre><pre style="background: white;"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; color: black; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz-l-8k8zsanGLQ1ZJFn633roPJk8ux5xpLjNrc9Bopk9AddOEKXgAb1Ek07I5o2oiailm9x5FvIGWChu7DfQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><span style="color: red; font-size: medium; white-space: normal;"><b>Update</b></span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">A comment of this article asks how to let AutoCAD zoom to an entity that is selected in a DataGridView (or any control that contains selectable items, for that matter) on the modeless form/window.</span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Because the action of user interaction on the modeless form/window (selecting an item in a container control) that results in AutoCAD zooming to certain entity only change the editor's current view, not the drawing database, and there is no feedback requiring the modeless form/window to refresh/update, we can directly execute zooming code from the modeless form/window without having to go the route of using <i>SendStringToExecute()</i>. </span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">First, I added a static method in the <i>CadUtil</i> class for zooming to an entity operation:</span></span></pre>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> ZoomToEntity(<span style="color: #2b91af;">ObjectId</span> entId)
{
GetZoomWindow(entId, <span style="color: blue;">out</span> <span style="color: blue;">double</span>[] corner1, <span style="color: blue;">out</span> <span style="color: blue;">double</span>[] corner2);
<span style="color: blue;">dynamic</span> comApp = <span style="color: #2b91af;">CadApp</span>.AcadApplication;
comApp.ZoomWindow(corner1, corner2);
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> GetZoomWindow(<span style="color: #2b91af;">ObjectId</span> entId,
<span style="color: blue;">out</span> <span style="color: blue;">double</span>[] corner1, <span style="color: blue;">out</span> <span style="color: blue;">double</span>[] corner2)
{
<span style="color: blue;">var</span> ext = GetEntityGeoExtents(entId);
<span style="color: blue;">var</span> h = ext.MaxPoint.Y - ext.MinPoint.Y;
<span style="color: blue;">var</span> w = ext.MaxPoint.X - ext.MinPoint.X;
<span style="color: blue;">var</span> len = <span style="color: #2b91af;">Math</span>.Max(h, w)/4.0;
<span style="color: blue;">var</span> pt1 = <span style="color: blue;">new</span> <span style="color: #2b91af;">Point3d</span>(ext.MinPoint.X - len, ext.MinPoint.Y - len, ext.MinPoint.Z);
<span style="color: blue;">var</span> pt2 = <span style="color: blue;">new</span> <span style="color: #2b91af;">Point3d</span>(ext.MaxPoint.X + len, ext.MaxPoint.Y + len, ext.MaxPoint.Z);
corner1 = pt1.ToArray();
corner2 = pt2.ToArray();
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">Extents3d</span> GetEntityGeoExtents(<span style="color: #2b91af;">ObjectId</span> entId)
{
<span style="color: #2b91af;">Extents3d</span> ext;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> tran=entId.Database.TransactionManager.StartTransaction())
{
<span style="color: blue;">var</span> ent = (<span style="color: #2b91af;">Entity</span>)tran.GetObject(entId, <span style="color: #2b91af;">OpenMode</span>.ForRead);
ext = ent.GeometricExtents;
tran.Commit();
}
<span style="color: blue;">return</span> ext;
}</pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">In order to react to user's action of selecting an item in a container control on the form/window, I updated the form by adding a check box:</span></span></pre><pre style="background: white; color: black;"><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-IyqVs_5J0XY/YFI27ui7G9I/AAAAAAAABio/tho0z9s8bcE8nlMDp0ikMKIaMLiwi4__gCLcBGAsYHQ/s488/ModelessForm_Updated.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="325" data-original-width="488" height="266" src="https://1.bp.blogspot.com/-IyqVs_5J0XY/YFI27ui7G9I/AAAAAAAABio/tho0z9s8bcE8nlMDp0ikMKIaMLiwi4__gCLcBGAsYHQ/w400-h266/ModelessForm_Updated.png" width="400" /></a></div><br /></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The updated form's code behind:</span></span></pre>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">using</span> Autodesk.AutoCAD.ApplicationServices;
<span style="color: blue;">using</span> <span style="color: #2b91af;">CadApp</span> = Autodesk.AutoCAD.ApplicationServices.<span style="color: #2b91af;">Application</span>;
<span style="color: blue;">namespace</span> ModelessDialogWithAction
{
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EntitiesForm</span> : <span style="color: #2b91af;">Form</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: #2b91af;">DocumentCollection</span> _dwgManager = <span style="color: blue;">null</span>;
<span style="color: blue;">private</span> <span style="color: blue;">string</span> _currentCommand = <span style="color: #a31515;">""</span>;
<span style="color: blue;">public</span> <span style="color: #2b91af;">EntitiesForm</span>()
{
InitializeComponent();
}
<span style="color: blue;">public</span> <span style="color: #2b91af;">EntitiesForm</span>(<span style="color: #2b91af;">DocumentCollection</span> dwgManager) : <span style="color: blue;">this</span>()
{
_dwgManager = dwgManager;
_dwgManager.DocumentActivated += (o, e) =>
{
<span style="color: blue;">if</span> (e.Document.UnmanagedObject != DocumentPointer)
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.WaitCursor;
RefreshEntityData(e.Document);
}
<span style="color: blue;">finally</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
}
}
};
}
<span style="color: blue;">public</span> <span style="color: #2b91af;">IntPtr</span> DocumentPointer { <span style="color: blue;">private</span> <span style="color: blue;">set</span>; <span style="color: blue;">get</span>; }</pre><pre style="background: white; font-family: Consolas; font-size: 13px;"> <span style="color: red;"><span>public</span> Autodesk.AutoCAD.DatabaseServices.<span>ObjectId</span> SelectedEntity { <span>private</span> <span>set</span>; <span>get</span>; } =
Autodesk.AutoCAD.DatabaseServices.<span>ObjectId</span>.Null;</span><span style="color: black;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> RefreshEntityData(<span style="color: #2b91af;">Document</span> dwg)
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.WaitCursor;
ListViewEntities.Items.Clear();
<span style="color: blue;">var</span> ents = <span style="color: #2b91af;">CadUtil</span>.CollectEntitiesInModelSpace(dwg);
<span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> ent <span style="color: blue;">in</span> ents)
{
<span style="color: blue;">var</span> item = <span style="color: blue;">new</span> <span style="color: #2b91af;">ListViewItem</span>(ent.EntityType);
item.SubItems.Add(ent.EntityId.ToString());
item.SubItems.Add(ent.EntityLayer);
</span><span style="color: red;">item.Tag = ent.EntityId;
item.Selected = <span>false</span>;</span><span style="color: black;">
ListViewEntities.Items.Add(item);
}
</span><span style="color: red;">SelectedEntity = Autodesk.AutoCAD.DatabaseServices.<span>ObjectId</span>.Null;</span>
LabelCount.Text = ListViewEntities.Items.Count.ToString();
}
<span style="color: blue;">finally</span>
{
<span style="color: #2b91af;">Cursor</span>.Current = <span style="color: #2b91af;">Cursors</span>.Default;
}
DocumentPointer = dwg.UnmanagedObject;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ExecuteCommand(<span style="color: blue;">string</span> commandName)
{
_dwgManager.MdiActiveDocument.CommandEnded += MdiActiveDocument_CommandEnded;
_currentCommand = commandName;
<span style="color: #2b91af;">CadApp</span>.MainWindow.Focus();
_dwgManager.MdiActiveDocument.SendStringToExecute(
commandName + <span style="color: #a31515;">"</span><span style="color: #b776fb;">\n</span><span style="color: #a31515;">"</span>, <span style="color: blue;">true</span>, <span style="color: blue;">false</span>, <span style="color: blue;">false</span>);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> MdiActiveDocument_CommandEnded(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">CommandEventArgs</span> e)
{
<span style="color: blue;">if</span> (e.GlobalCommandName.ToUpper() == _currentCommand.ToUpper())
{
_dwgManager.MdiActiveDocument.CommandEnded -= MdiActiveDocument_CommandEnded;
RefreshEntityData(
_dwgManager.MdiActiveDocument);
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonAddLine_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
ExecuteCommand(<span style="color: #2b91af;">MyCommands</span>.ADD_LINE_COMMAND);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonAddCircle_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
ExecuteCommand(<span style="color: #2b91af;">MyCommands</span>.ADD_CIRCLE_COMMAND);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> EntitiesForm_FormClosing(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">FormClosingEventArgs</span> e)
{
e.Cancel = <span style="color: blue;">true</span>;
Visible = <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ButtonClose_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
Visible = <span style="color: blue;">false</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ListViewEntities_SelectedIndexChanged(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
<span style="color: blue;">if</span> (!CheckBoxZoom.Checked) <span style="color: blue;">return</span>;
<span style="color: blue;">if</span> (ListViewEntities.SelectedItems.Count == 0) <span style="color: blue;">return</span>;
SelectedEntity = (Autodesk.AutoCAD.DatabaseServices.<span style="color: #2b91af;">ObjectId</span>)
ListViewEntities.SelectedItems[0].Tag;
<span style="color: green;">// Directly execute the zooming code without using </span>
<span style="color: green;">// SendStringToExecute()</span>
<span style="color: red;"><span>CadUtil</span>.ZoomToEntity(SelectedEntity);</span><span style="color: black;">
}
}
}</span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The lines in red is modified code. As shown in <i>ListViewEntities_SelectIndexChange</i>d event handler, I simply to have the entity zooming code directly executed from the modeless form without using <i>SendStringToExecute() </i>and handling <i>CommandEnded</i> event for UI update, because it is not needed in this case. Following video clip shows the code in action:</span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='276' src='https://www.blogger.com/video.g?token=AD6v5dymEYv6GVVq1dXJHrpp1f4FRTGaj2EBWAKlvp2zLK1B5GtZW9ySbyVC4H-dME-1udfseZC9FHpx0Nb06dsDxw' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br /><span style="white-space: normal;"><br /></span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;"><br /></span></span></pre><pre style="background: white; color: black;"><span style="font-family: Times New Roman;"><span style="white-space: normal;"><br /></span></span></pre></div>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com7tag:blogger.com,1999:blog-993393844516183990.post-77928759384578226932021-02-06T18:19:00.004-08:002021-03-10T14:39:17.168-08:00Drag & Drop In AutoCAD (2) - Drag From External Application And Drop OnTo AutoCAD<p>This is the second of this series on Drag & Drop operation in AutoCAD. See previous ones here:</p><p><span> <a href="https://drive-cad-with-code.blogspot.com/2021/02/drag-drop-in-autocad-1-scenarios.html" target="_blank">Drag & Drop In AutoCAD (1) - The Scenarios</a></span><br /></p><p><span>In this post I look into the scenario of dragging from external application and dropping onto a running AutoCAD session.</span></p><p><span>There are 2 types of external applications user could be dragging something from:</span></p><p><span>1. Applications that we, as programmers, do not have much control on how the dragging is handled. For example, we cannot control what happens when dragging document content from a Word document into AutoCAD, or we do not have control when the document content is drooped onto AutoCAD editor. The behaviours of drag & drop from both applications (Word and AutoCAD) are designed/built in them and exposed as API for us to intercept into the drag & drop process to manipulate it. So, I'll leave this case out of the discussion here. </span></p><p><span>2. Applications we developed, thus, we have the control at the dragging end of the drag & drop operation. That is, we can choose to use UI components that support drag operation (i.e. having MouseMove event and support DoDragDrop() method) to let drag & drop operation begins at dragging end (our application side). However, at the drop end, AutoCAD runs as a different application, thus, when the drag & drop operation is initialized from our application, we do not have control on how AutoCAD receive the data being dragged and dropped onto it: AutoCAD will do what is was designed to do. There are some dropping behaviours of AutoCAD are known to most AutoCAD users:</span></p><p></p><ul style="text-align: left;"><li><span>Insert block</span></li><li><span>Open drawing</span></li><li><span>Insert Text/MText entity</span></li><li><span>Insert OLE object</span></li></ul><div>There could be other behaviours that I am not aware of, but inserting block, opening drawing and inserting MText are the most encountered dropping task when AutoCAD being at the dropping end of drag & drop operation.</div><div><br /></div><div>Here I built a WinForm desktop application to demonstrate how file name and text string can be dragged from the application, dropped onto AutoCAD and what happens in AutoCAD.</div><div><br /></div><div>The WinForm application's UI includes a Listbox that can be filled with file names, and a Textbox where user can input text string. There are different options (represented by Radiobuttons) for dragging content in Listbox and/or Textbox that would result in different dropping effect at AutoCAD end.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-JifLPnSt05s/YB2H8hSflaI/AAAAAAAABgY/dkiP67VvdJcut-9lsq_Q6CfvSYhetjb-ACLcBGAsYHQ/s620/DragFromExeToAcad_Form.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="345" data-original-width="620" height="356" src="https://1.bp.blogspot.com/-JifLPnSt05s/YB2H8hSflaI/AAAAAAAABgY/dkiP67VvdJcut-9lsq_Q6CfvSYhetjb-ACLcBGAsYHQ/w640-h356/DragFromExeToAcad_Form.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><div>Here is the code behind the form:</div>
<div>
<pre style="background: white; color: black; font-family: Consolas; font-size: 13px;"><span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Collections.Generic;
<span style="color: blue;">using</span> System.Windows.Forms;
<span style="color: blue;">using</span> System.IO;
<span style="color: blue;">namespace</span> DragFromExeToAcad
{
<span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">MainForm</span> : <span style="color: #2b91af;">Form</span>
{
<span style="color: blue;">private</span> <span style="color: blue;">readonly</span> <span style="color: blue;">string</span> _tempTextFile = <span style="color: #a31515;">""</span>;
<span style="color: blue;">public</span> <span style="color: #2b91af;">MainForm</span>()
{
InitializeComponent();
<span style="color: blue;">string</span> folder =
<span style="color: #2b91af;">Path</span>.Combine(<span style="color: #2b91af;">Environment</span>.GetFolderPath(
<span style="color: #2b91af;">Environment</span>.<span style="color: #2b91af;">SpecialFolder</span>.LocalApplicationData), <span style="color: #a31515;">"Temp"</span>);
_tempTextFile = folder + <span style="color: #a31515;">"</span><span style="color: #b776fb;">\\</span><span style="color: #a31515;">Dragged_Text.txt"</span>;
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> SelectFiles()
{
<span style="color: #2b91af;">IEnumerable</span><<span style="color: blue;">string</span>> files = <span style="color: blue;">null</span>;
<span style="color: blue;">using</span> (<span style="color: blue;">var</span> dlg = <span style="color: blue;">new</span> <span style="color: #2b91af;">OpenFileDialog</span>()
{
Title=<span style="color: #a31515;">"Select Files"</span>,
Multiselect=<span style="color: blue;">true</span>
})
{
<span style="color: blue;">if</span> (dlg.ShowDialog()== <span style="color: #2b91af;">DialogResult</span>.OK)
{
files = dlg.FileNames;
}
}
<span style="color: blue;">if</span> (files!=<span style="color: blue;">null</span>)
{
DragFileListBox.Items.Clear();
<span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> f <span style="color: blue;">in</span> files)
{
DragFileListBox.Items.Add(f);
}
}
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> SaveAsTempFile(<span style="color: blue;">string</span> textString)
{
<span style="color: blue;">if</span> (!<span style="color: blue;">string</span>.IsNullOrEmpty(_tempTextFile))
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">File</span>.Delete(_tempTextFile);
}
<span style="color: blue;">catch</span> { }
}
<span style="color: #2b91af;">File</span>.WriteAllText(_tempTextFile, textString);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> ExitButton_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
<span style="color: blue;">this</span>.Close();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> SelectFileButton_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)
{
SelectFiles();
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> DragTextBox_MouseMove(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">MouseEventArgs</span> e)
{
<span style="color: blue;">if</span> (DragTextBox.Text.Trim().Length == 0) <span style="color: blue;">return</span>;
<span style="color: blue;">if</span> (e.Button != <span style="color: #2b91af;">MouseButtons</span>.Left) <span style="color: blue;">return</span>;
<span style="color: blue;">string</span> txt = DragTextBox.SelectionLength == 0 ?
DragTextBox.Text.Trim() : DragTextBox.SelectedText.Trim();
<span style="color: #2b91af;">DataObject</span> data;
<span style="color: blue;">if</span> (TextFileRadioButton.Checked)
{
<span style="color: green;">// drag the text string as a temporary *.txt file</span>
SaveAsTempFile(txt);
data = <span style="color: blue;">new</span> <span style="color: #2b91af;">DataObject</span>();
data.SetFileDropList(
<span style="color: blue;">new</span> System.Collections.Specialized.<span style="color: #2b91af;">StringCollection</span> { _tempTextFile });
}
<span style="color: blue;">else</span>
{
data = <span style="color: blue;">new</span> <span style="color: #2b91af;">DataObject</span>(txt);
}
DragTextBox.DoDragDrop(data, <span style="color: #2b91af;">DragDropEffects</span>.All);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> DragFileListBox_MouseMove(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">MouseEventArgs</span> e)
{
<span style="color: blue;">if</span> (DragFileListBox.SelectedItems.Count == 0) <span style="color: blue;">return</span>;
<span style="color: blue;">if</span> (e.Button != <span style="color: #2b91af;">MouseButtons</span>.Left) <span style="color: blue;">return</span>;
<span style="color: blue;">var</span> data = <span style="color: blue;">new</span> <span style="color: #2b91af;">DataObject</span>();
<span style="color: blue;">var</span> files = <span style="color: blue;">new</span> System.Collections.Specialized.<span style="color: #2b91af;">StringCollection</span>();
<span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> item <span style="color: blue;">in</span> DragFileListBox.SelectedItems)
{
files.Add(item.ToString());
}
data.SetFileDropList(files);
<span style="color: green;">// start drag & drop operation</span>
DragFileListBox.DoDragDrop(data, <span style="color: #2b91af;">DragDropEffects</span>.All);
}
<span style="color: blue;">private</span> <span style="color: blue;">void</span> MainForm_FormClosed(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">FormClosedEventArgs</span> e)
{
<span style="color: blue;">if</span> (!<span style="color: blue;">string</span>.IsNullOrEmpty(_tempTextFile))
{
<span style="color: blue;">try</span>
{
<span style="color: #2b91af;">File</span>.Delete(_tempTextFile);
}
<span style="color: blue;">catch</span> { }
}
}
}
}</pre></div><p><span>The mostly used drag & and drop operation from external application onto AutoCAD would be dragging file name, or dragging text string. Here are some explanations about the code and dropping behaviours of AutoCAD.</span></p><p><span>1. Dragging file name</span></p><p>When dragging file name (single, or multiple), the file name should be stuffed into DataObject with method <i>SetDropFileList(StringCollection)</i>, instead of one of the overloaded SetData() methods.</p><p>The argument of DragDropEffects used in <i>DoDragDrop()</i> method not only paly the role of showing a different mouse cursor image, but would also result in AutoCAD doing different things at dropping end. For example, when a file name, which is not a plain text/ASCII file, is dropped onto AutoCAD editor, DragDropEffects.Move makes AutoCAD insert a DWG file as block, or as OLE entity, if applicable; however, DragDropEffects.Copy makes AutoCAD open DWG file, and ignore other types of file. Also, regardless DragDropEffects, dropping DWG file onto anywhere of AutoCAD other than editor, AutoCAD would open the drawing file or files. I recommend to use DragDropEffects.All in general to that AutoCAD would do whatever it is designed to do (or not to do) at dropping end and only try other DragDropEffects specific for your special need, if any.</p><p>It is worth being pointed out that when dragging a plain text file (typically as *.txt file), AutoCAD will insert the content of the text file as MText.</p><p>2. Dragging text string</p><p>When text string is filed into <i>DataObject</i> by calling <i>DataObject.SetData(string)</i>, or passed to <i>DataObject</i> via its Constructor (<i>new DataObject(string))</i>, AutoCAD receives it and create an MText entity. However, the MText is not created at the location where it is dropped (when mouse' left button releases), instead, it is created at the upper-left corner of current view of AutoCAD editor.</p><p>The trick to fix this issue (i.e. let AutoCAD create the MText at where the text string is dropped) is to save the text string into a temporary plain text file, and add this temporary file name to the<i> DataObject</i> by its <i>SetDropFileList(StringCollection)</i> method. That is why you see the extra code that create text file and clean it up when the UI is closed.</p><p>Here is the video clip showing drag & drop operation from an external application to AutoCAD;</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxYx9HGWScg261nqmzYuWS3M_fGLoinWmpW8fmFNzQZKzPAEjLACKj9naLv20OnpaHcUnJKRTMpG2xdKceYCA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p>The next article I will discuss on dragging from AutoCAD and dropping onto modeless form/window of a plugin application in AutoCAD.</p><p><br /></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p><p><span><br /></span></p>Norman Yuanhttp://www.blogger.com/profile/09350392399498834066noreply@blogger.com0