To make entity selections, we need to collect some points first to make a fence, window, polygon, and so on. A while back, we presented a nice little coder, PointCollector, which can help visual friendly pick AutoCAD entities from a shape or area of fence, window, crossing window, polygon, crossing polygon, regular polygon, crossing regular polygon, circle or crossing circle.
The first version of the PointCollector does not support the background color and the transparency for the selection area, which feature has been provided in AutoCAD for quite some time. In a previous post, we rolled out a nicer and still concise enough version of the point collector, PointCollector2. In this post, let’s continue to enhance the newer and better Point Collector for it to support the Regular Polygon pick with either crossing or normal mode.
Here is a video first to show how the (Crossing) Regular Polygon pick of our PointCollector2 behaves in AutoCAD.
As can be seen, the behavior of the (Crossing) Regular Polygon selection of our PointCollector2 is nice and cool, which is not available in the AutoCAD Select command. It also runs well in UCS, as always, indicating the Point Collector 2 with all selection modes honors UCS perfectly.
Here is the latest PointCollector2.
#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 PointCollector2 : IDisposable
{
#region Types/Consts/Members
public enum Shape
{
Window,
Box,
RegularPolygon,
}
private const int constWindowColorIndex = 172;
private const int constCrossingWindowColorIndex = 110;
private const byte constWindowTransparency = 50;
private const short constBoundaryColorIndex = 7;
private int mSegmentCount = 72;
private Shape mShape;
private Point3d m1stPoint;
private double mDist;
private Autodesk.AutoCAD.DatabaseServices.Hatch mTempHatch;
private short mHatchColorIndex = constCrossingWindowColorIndex;
private byte mHatchTransparency = constWindowTransparency;
private TransientDrawingMode mHatchDrawMode;
private Autodesk.AutoCAD.DatabaseServices.Polyline mTempPline;
private TransientDrawingMode mBoundaryDrawMode;
#endregion
#region Constructors
public PointCollector2(Shape shape, bool isCrossing)
{
mShape = shape;
IsCrossing = isCrossing;
mTempPline = new Autodesk.AutoCAD.DatabaseServices.Polyline();
mTempPline.SetDatabaseDefaults();
mTempPline.ColorIndex = constBoundaryColorIndex;
mTempHatch = new Hatch();
mTempHatch.SetDatabaseDefaults();
mTempHatch.ColorIndex = mHatchColorIndex;
mTempHatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
mTempHatch.Transparency = new Autodesk.AutoCAD.Colors.Transparency(mHatchTransparency);
mHatchDrawMode = TransientDrawingMode.Main;
if (IsCrossing)
{
mHatchColorIndex = constCrossingWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.Highlight;
}
else
{
mHatchColorIndex = constWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.DirectTopmost;
}
CollectedPoints = new Point3dCollection();
CollectionStatus = PromptStatus.OK;
}
#endregion
#region Properties
public Point3dCollection CollectedPoints { get; private set; }
public PromptStatus CollectionStatus { get; private set; }
public bool IsCrossing { get; private set; }
private Editor CurEditor
{
get
{
return MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
}
}
#endregion
#region Point Monitor
private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
{
CleanupGraphics();
mTempPline = new Autodesk.AutoCAD.DatabaseServices.Polyline();
mTempPline.SetDatabaseDefaults();
mTempPline.ColorIndex = constBoundaryColorIndex;
mTempHatch = new Hatch();
mTempHatch.SetDatabaseDefaults();
mTempHatch.ColorIndex = mHatchColorIndex;
mTempHatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
mTempHatch.Transparency = new Autodesk.AutoCAD.Colors.Transparency(mHatchTransparency);
Point3d compPt = e.Context.ComputedPoint.TransformBy(CurEditor.CurrentUserCoordinateSystem.Inverse());
if (mShape == Shape.Window)
{
BuildupWindowVertices(m1stPoint, compPt);
}
else if (mShape == Shape.Box)
{
BuildupWindowVertices(m1stPoint, compPt);
IsCrossing = compPt.X <= m1stPoint.X;
}
else if (mShape == Shape.RegularPolygon)
{
BuildupRegularPolygonVertices(compPt);
}
mTempPline.TransformBy(CurEditor.CurrentUserCoordinateSystem);
mTempHatch.Normal = mTempPline.Normal;
HatchLoop loop = new HatchLoop(HatchLoopTypes.Default);
for (int i = 0; i < mTempPline.NumberOfVertices; i++)
{
loop.Curves.Add(mTempPline.GetLineSegment2dAt(i));
}
mTempHatch.AppendLoop(loop);
mTempHatch.EvaluateHatch(true);
if (IsCrossing)
{
mHatchColorIndex = constCrossingWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.Highlight;
}
else
{
mHatchColorIndex = constWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.DirectTopmost;
}
TransientManager.CurrentTransientManager.AddTransient(mTempHatch, mHatchDrawMode, 0, new IntegerCollection());
TransientManager.CurrentTransientManager.AddTransient(mTempPline, mBoundaryDrawMode, 0, new IntegerCollection());
}
#endregion
#region Point Collectors & Shape Builders
#region Window & Box
private void CollectWindowPoints()
{
PromptPointResult prPntRes1 = CurEditor.GetPoint("\nSpecify first corner");
if (prPntRes1.Status != PromptStatus.OK)
{
CollectionStatus = prPntRes1.Status;
return;
}
m1stPoint = prPntRes1.Value;
CollectedPoints.Add(m1stPoint);
BuildupWindowVertices(m1stPoint, m1stPoint);
CurEditor.PointMonitor += Editor_PointMonitor;
PromptPointResult prPntRes2 = CurEditor.GetPoint("\nSpecify opposite corner");
if (prPntRes2.Status != PromptStatus.OK)
{
CollectionStatus = prPntRes2.Status;
CurEditor.PointMonitor -= Editor_PointMonitor;
return;
}
CollectedPoints.Add(prPntRes2.Value);
CurEditor.PointMonitor -= Editor_PointMonitor;
}
private void BuildupWindowVertices(Point3d corner1, Point3d corner2)
{
mTempPline.Closed = true;
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(corner1.X, corner1.Y), 0, 1, 1);
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(corner2.X, corner1.Y), 0, 1, 1);
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(corner2.X, corner2.Y), 0, 1, 1);
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(corner1.X, corner2.Y), 0, 1, 1);
}
#endregion
#region Regular Polygon & Circle
private void CollectRegularPolygonPoints()
{
PromptPointResult prPntRes1;
PromptPointOptions prPntOpt = new PromptPointOptions("\nCenter");
prPntRes1 = CurEditor.GetPoint(prPntOpt);
if (prPntRes1.Status != PromptStatus.OK)
{
CollectionStatus = prPntRes1.Status;
return;
}
m1stPoint = prPntRes1.Value;
CurEditor.PointMonitor += Editor_PointMonitor;
PromptDistanceOptions prPntOpt2 = new PromptDistanceOptions("\nRadius");
prPntOpt2.AllowArbitraryInput = true;
prPntOpt2.AllowNegative = false;
prPntOpt2.AllowNone = true;
prPntOpt2.AllowZero = false;
prPntOpt2.BasePoint = m1stPoint;
prPntOpt2.DefaultValue = 10.0;
prPntOpt2.Only2d = true;
prPntOpt2.UseBasePoint = true;
prPntOpt2.UseDashedLine = true;
prPntOpt2.UseDefaultValue = true;
PromptDoubleResult prPntRes2 = CurEditor.GetDistance(prPntOpt2);
if (prPntRes2.Status != PromptStatus.OK)
{
CollectionStatus = prPntRes2.Status;
return;
}
CurEditor.PointMonitor -= Editor_PointMonitor;
}
private int GetRegularPolygonSideCount()
{
PromptIntegerResult prIntRes;
PromptIntegerOptions prIntOpt = new PromptIntegerOptions("\nRegular polygon side");
prIntOpt.AllowNone = true;
prIntOpt.AllowArbitraryInput = true;
prIntOpt.AllowNegative = false;
prIntOpt.AllowZero = false;
prIntOpt.DefaultValue = 5;
prIntOpt.LowerLimit = 3;
prIntOpt.UpperLimit = 36;
prIntOpt.UseDefaultValue = true;
prIntRes = CurEditor.GetInteger(prIntOpt);
if (prIntRes.Status != PromptStatus.OK)
{
CollectionStatus = prIntRes.Status;
return -1;
}
return mSegmentCount = prIntRes.Value;
}
private void BuildupRegularPolygonVertices(Point3d tempPt)
{
mTempPline.Closed = true;
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);
}
}
#endregion
#endregion
#region Cleanup
public void Dispose()
{
CurEditor.PointMonitor -= Editor_PointMonitor;
CleanupGraphics();
CollectedPoints.Dispose();
}
private void CleanupGraphics()
{
if (mTempPline != null && !mTempPline.IsDisposed)
{
TransientManager.CurrentTransientManager.EraseTransient(mTempPline, new IntegerCollection());
mTempPline.Dispose();
mTempPline = null;
}
if (mTempHatch != null && !mTempHatch.IsDisposed)
{
TransientManager.CurrentTransientManager.EraseTransient(mTempHatch, new IntegerCollection());
mTempHatch.Dispose();
mTempHatch = null;
}
}
#endregion
#region Collect/Select Methods
public Point3dCollection Collect()
{
if (mShape == Shape.Window || mShape == Shape.Box)
{
CollectWindowPoints();
}
else if (mShape == Shape.RegularPolygon)
{
mSegmentCount = GetRegularPolygonSideCount();
CollectRegularPolygonPoints();
}
return CollectedPoints;
}
public SelectionSet Select()
{
Editor ed = CurEditor;
this.Collect();
ed.PointMonitor -= Editor_PointMonitor;
this.CleanupGraphics();
if (this.CollectionStatus == PromptStatus.OK)
{
PromptSelectionResult prSelRes = null;
if (mShape == Shape.Window || mShape == Shape.Box)
{
if (IsCrossing)
prSelRes = ed.SelectCrossingWindow(CollectedPoints[0], CollectedPoints[1]);
else
prSelRes = ed.SelectWindow(CollectedPoints[0], CollectedPoints[1]);
}
else if (mShape == Shape.RegularPolygon)
{
if (IsCrossing)
prSelRes = ed.SelectCrossingPolygon(CollectedPoints);
else
prSelRes = ed.SelectWindowPolygon(CollectedPoints);
}
if (prSelRes != null && prSelRes.Status == PromptStatus.OK)
return prSelRes.Value;
}
return null;
}
#endregion
}
}
Here is some test code and command.
[CommandMethod("SelectCrossingRegularPolygon2", CommandFlags.Modal | CommandFlags.Redraw)]
public static void SelectCrossingRegularPolygon2_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
using (PointCollector2 pc = new PointCollector2(PointCollector2.Shape.RegularPolygon, true))
{
using (SelectionSet ss = pc.Select())
if (ss != null && ss.Count > 0)
ed.SetImpliedSelection(ss.GetObjectIds().ToArray());
}
}
catch (System.Exception ex)
{
ed.WriteMessage(Environment.NewLine + ex.ToString());
}
}
[CommandMethod("SelectRegularPolygon2", CommandFlags.Modal | CommandFlags.Redraw)]
public static void SelectRegularPolygon2_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
using (PointCollector2 pc = new PointCollector2(PointCollector2.Shape.RegularPolygon, false))
{
using (SelectionSet ss = pc.Select())
if (ss != null && ss.Count > 0)
ed.SetImpliedSelection(ss.GetObjectIds().ToArray());
}
}
catch (System.Exception ex)
{
ed.WriteMessage(Environment.NewLine + ex.ToString());
}
}
As can be seen, a single Select() method has been provided to make all the point collection and entity selection must easier. The collected points are still accessible for any purposes such as some further calculation work and some further operations before the PointCollector2 is disposed. By the way, the points returned by the CollectedPoints collection of the PointCollector2 are in UCS. Apart from these nice features, a lot of other good stuffs are in such a succinct but cool coder. Here are a few more.
• The PointCollector2 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 PointCollector2 to better utilize the above feature as we always do to the AutoCAD .NET Transaction creations.
• The Transient Graphics here is used to draw the rectangle and the filled region temporarily.
• The boundary is still drawn as a polyline, the same as before. In the version 2 of the Point Collector, an extra hatch is drawn inside the boundary to highlight the selection area.
• It is necessary to remove the Transient Graphics (through calling the method TransientManager.CurrentTransientManager.EraseTransient) before the next draw.
• An Editor.PointMonitor event is planted into the system at right time to monitor the cursor positions so as to draw temporary (transient) graphics for different shape or area.
• The PointMonitorEventArgs.Context.ComputedPoint is used instead of the PointMonitorEventArgs.Context.RawPoint to honor object snapping, ortho-mode and so on.
• The consistency issue has been addressed that the Editor.GetPoint() call returns a point in UCS but the PointMonitorEventArgs.Context.ComputedPoint is in WCS.
• The APIs of removing and updating Polyline vertices are pretty tricky so we don’t use them at all. Instead, we only call the Polyline.AddVertexAt() method to both make our life a bit easier and avoid the database or the transient graphics system being messed up.
• The regular polygon pick supports both the crossing and regular mode, as the Window/Box pick we presented earlier.
• A single Select() method is provided now to return a SelectionSet directly and the CollectedPoints are still available in the life of the PointCollector2.
• Last but not least, the PointCollector2 can be easily extended to support other shapes, e.g. irregular Polygon and Circle. We will demonstrate it in future. Please stay tuned.
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: |