To make entity selections, we need to collect some points to make a fence, window, polygon, or so.
We can specify the UseBasePoint, BasePoint, and UseDashedLine properties of the PromptPointOptions for the Editor.GetPoint() method to connect the current cursor position with the previous point input, as we did to the Fence Selection before, but it’s far from user friendly, especially for selection options like window and polygon. Even for the simple fence option, only the last two point picks can be connected through the hard coded dashed line by the GetPoint() call.
We created a nice little coder, Point Collector, to collect points visually to form window, fence, and polygon (irregular or regular) previously. Let us extend it to support circular area pick as well in this post.
#region Namespaces
using System;
using System.Text;
using System.Linq;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Windows;
using MgdAcApplication = Autodesk.AutoCAD.ApplicationServices.Application;
using MgdAcDocument = Autodesk.AutoCAD.ApplicationServices.Document;
using AcWindowsNS = Autodesk.AutoCAD.Windows;
#endregion
using Autodesk.AutoCAD.GraphicsInterface;
namespace AcadNetAddinWizard_Namespace
{
public class PointCollector : IDisposable
{
public enum Shape
{
Window,
Fence,
Polygon,
RegularPolygon,
Circle,
}
private Shape mShape;
private Autodesk.AutoCAD.DatabaseServices.Polyline mTempPline;
private Point3d m1stPoint;
private double mDist;
private int mSegmentCount = 36;
public Point3dCollection CollectedPoints { get; set; }
public PointCollector(Shape shape)
{
mShape = shape;
CollectedPoints = new Point3dCollection();
}
private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
{
if (mTempPline != null)
TransientManager.CurrentTransientManager.EraseTransient(mTempPline, new IntegerCollection());
Point3d compPt = e.Context.ComputedPoint.TransformBy(MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.CurrentUserCoordinateSystem.Inverse());
if (mShape == Shape.Window)
{
BuildupWindowVertices(m1stPoint, compPt);
}
else if (mShape == Shape.Fence)
{
BuildupFenceVertices(compPt);
}
else if (mShape == Shape.Polygon)
{
BuildupPolygonVertices(compPt);
}
else if (mShape == Shape.RegularPolygon)
{
BuildupRegularPolygonVertices(compPt);
}
else if (mShape == Shape.Circle)
{
BuildupRegularPolygonVertices(compPt);
}
TransientManager.CurrentTransientManager.AddTransient(mTempPline, TransientDrawingMode.Main, 0, new IntegerCollection());
}
public void Collect()
{
if (mShape == Shape.Window)
{
CollectWindowPoints();
}
else if (mShape == Shape.Fence)
{
CollectFencePoints();
}
else if (mShape == Shape.Polygon)
{
CollectPolygonPoints();
}
else if (mShape == Shape.RegularPolygon)
{
PromptIntegerResult prPntRes;
PromptIntegerOptions prPntOpt = new PromptIntegerOptions("");
prPntOpt.AllowNone = true;
prPntOpt.AllowArbitraryInput = true;
prPntOpt.AllowNegative = false;
prPntOpt.AllowZero = false;
prPntOpt.DefaultValue = 5;
prPntOpt.LowerLimit = 3;
prPntOpt.Message = "\nRegular polygon side";
prPntOpt.UpperLimit = 36;
prPntOpt.UseDefaultValue = true;
prPntRes = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.GetInteger(prPntOpt);
if (prPntRes.Status == PromptStatus.OK)
{
mSegmentCount = prPntRes.Value;
}
else
{
throw new System.Exception("Regular polygon side input failed!");
}
CollectRegularPolygonPoints();
}
else if (mShape == Shape.Circle)
{
CollectRegularPolygonPoints();
}
}
private void BuildupRegularPolygonVertices(Point3d tempPt)
{
if (mTempPline != null && !mTempPline.IsDisposed)
{
mTempPline.Dispose();
mTempPline = null;
}
mTempPline = new Autodesk.AutoCAD.DatabaseServices.Polyline();
mTempPline.SetDatabaseDefaults();
mTempPline.Closed = true;
mTempPline.ColorIndex = 7;
mDist = m1stPoint.DistanceTo(tempPt);
double angle = m1stPoint.GetVectorTo(tempPt).AngleOnPlane(new Plane(Point3d.Origin, Vector3d.ZAxis));
CollectedPoints.Clear();
for (int i = 0; i < mSegmentCount; i++)
{
Point3d pt = m1stPoint.Add(new Vector3d(mDist * (Math.Cos(angle + Math.PI * 2 * i / mSegmentCount)),
mDist * (Math.Sin(angle + Math.PI * 2 * i / mSegmentCount)),
m1stPoint.Z));
CollectedPoints.Add(pt);
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(pt.X, pt.Y), 0, 1, 1);
}
mTempPline.TransformBy(MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.CurrentUserCoordinateSystem);
}
private void CollectRegularPolygonPoints()
{
PromptPointResult prPntRes1;
PromptPointOptions prPntOpt = new PromptPointOptions("\nCenter");
prPntOpt.AllowNone = true;
prPntRes1 = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.GetPoint(prPntOpt);
if (prPntRes1.Status == PromptStatus.OK)
{
m1stPoint = prPntRes1.Value;
}
else
{
throw new System.Exception("Center picking failed!");
}
MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.PointMonitor += Editor_PointMonitor;
PromptDistanceOptions prPntOpt2 = new PromptDistanceOptions("");
prPntOpt2.AllowArbitraryInput = true;
prPntOpt2.AllowNegative = false;
prPntOpt2.AllowNone = true;
prPntOpt2.AllowZero = false;
prPntOpt2.BasePoint = m1stPoint;
prPntOpt2.DefaultValue = 10.0;
prPntOpt2.Message = "\nRadius";
prPntOpt2.Only2d = true;
prPntOpt2.UseBasePoint = true;
prPntOpt2.UseDashedLine = true;
prPntOpt2.UseDefaultValue = true;
PromptDoubleResult prPntRes2 = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.GetDistance(prPntOpt2);
if (prPntRes2.Status != PromptStatus.OK)
throw new System.Exception("Radius input failed!");
mDist = prPntRes2.Value;
MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.PointMonitor -= Editor_PointMonitor;
}
...
...
public void Dispose()
{
MgdAcApplication.DocumentManager.MdiActiveDocument.Editor.PointMonitor -= Editor_PointMonitor;
if( mTempPline != null )
TransientManager.CurrentTransientManager.EraseTransient(mTempPline, new IntegerCollection());
if (mTempPline != null && !mTempPline.IsDisposed)
mTempPline.Dispose();
CollectedPoints.Dispose();
}
}
}
Please note that the code related to window pick, fence pick, and polygon pick (irregular or regular) has been removed to save some space. Please refer to earlier posts for details about it.
Here is the test code and command.
[CommandMethod("PickCirclePoints", CommandFlags.Modal)]
public void PickCirclePoints_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
using (PointCollector ptCol = new PointCollector(PointCollector.Shape.Circle))
{
ptCol.Collect();
int i = 0;
foreach (Point3d pt in ptCol.CollectedPoints)
{
ed.WriteMessage(string.Format("\nCollected point #{0}: {1}", ++i, pt.ToString()));
}
//Do anything else to the collected points
//...
}
}
catch (System.Exception ex)
{
ed.WriteMessage(Environment.NewLine + ex.Message);
}
}
Here is a screenshot to show how it behaves in AutoCAD.
As can be seen, the command runs well in a UCS, indicating our Point Collector honors UCS perfectly. As mentioned before, if some code/command works fine with UCS we can be sure that it works fine with WCS too as WCS is nothing but a special UCS, but vise is not versa!
So, to repeat one more time, it is a good practice to always test code and command in UCS instead of WCS because nothing reasonable enough should prevent users from doing the same thing in some UCS, which is supposed to provide them great flexibility and convenience instead of confusion or trouble.
By the way, the points returned by the CollectedPoints collection of the PointCollector are in UCS. We can convert them into WCS with a little additional effort for sure when necessary. We have demonstrated how to do so many times before.
Apart from this nice feature, a lot of other good stuffs are also in such a succinct but cool coder. Here are a few more.
• The PointCollector is disposable and when its Dispose() method is called things will be cleaned up properly in a single place.
• We’d better use using to create the PointCollector to better utilize the above feature as we always do to the AutoCAD .NET Transaction creations.
• Though the EntityJig or DrawJig can be used to jig the graphics, as we did many times before, we use the Transient Graphics here to draw temporarily since it’s obvious that either the EntityJig or the DrawJig is over killed for this case.
• To draw some transient graphics onto the AutoCAD drawing area is pretty simple, through calling the TransientManager.CurrentTransientManager.AddTransient() method which needs a Drawable object and a few other parameters to specify the drawing conditions.
• Please don’t forget to remove the Transient Graphics (through calling the method TransientManager.CurrentTransientManager.EraseTransient) in the next draw or when all are done. Otherwise AutoCAD may crash.
• An Editor.PointMonitor event is planted into the system at right time to monitor the cursor positions so as to draw the temporary (transient) graphics and removed right after.
• The PointMonitorEventArgs.Context.ComputedPoint is used instead of the PointMonitorEventArgs.Context.RawPoint to honor object snapping and similar.
• Though the Editor.GetPoint() call returns a point in UCS, the PointMonitorEventArgs.Context.ComputedPoint is in WCS. That is why we have to do some transformations to make them consistent with each other so that the Transient Graphics can appear good in both WCS and UCS.
• The AutoCAD .NET APIs regarding removing and updating Polyline vertices are pretty tricky and we choose not to use them at all here.
• We only call the Polyline.AddVertexAt() method to build up the polyline segments. It both makes our life a bit easier and avoids the database or the transient graphics system being messed up.
• The PointCollector currently supports Window, Fence, Polygon (Irregular or Regular), and Circular shapes.
• The PointCollector can be extended easily to include more features, e.g. supporting line types, supporting fill-out patterns, supporting arc segments, etc. Some of these may be demonstrated in future.
• Last but not least, the PointCollector can be easily extended to support other shapes. You name it and we may cover it in future.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides project wizards in C#, VB.NET and CLI/Managed C++, and various item wizards such as Event Handlers, Command/LispFunction Definers, and Entity/Draw Jiggers in both C# and VB.NET, to help program AutoCAD addins.
Posted by: |