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 Fence pick.
Here is a video first to show how the Fence selection of our PointCollector2 behaves in AutoCAD.
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,
Fence,
}
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;
if (mShape != Shape.Fence)
{
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);
}
else if (mShape == Shape.Fence)
{
BuildupFenceVertices(compPt);
}
mTempPline.TransformBy(CurEditor.CurrentUserCoordinateSystem);
if (mShape != Shape.Fence)
{
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);
TransientManager.CurrentTransientManager.AddTransient(mTempHatch, mHatchDrawMode, 0, new IntegerCollection());
}
if (IsCrossing)
{
mHatchColorIndex = constCrossingWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.Highlight;
}
else
{
mHatchColorIndex = constWindowColorIndex;
mBoundaryDrawMode = TransientDrawingMode.DirectTopmost;
}
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
#region Fence
private void CollectFencePoints()
{
PromptPointOptions prPntOpt = new PromptPointOptions("\nFence point (Enter to finish)");
prPntOpt.AllowNone = true;
CurEditor.PointMonitor += Editor_PointMonitor;
PromptPointResult prPntRes1;
do
{
prPntRes1 = CurEditor.GetPoint(prPntOpt);
if (prPntRes1.Status == PromptStatus.OK)
CollectedPoints.Add(prPntRes1.Value);
} while (prPntRes1.Status == PromptStatus.OK);
CurEditor.PointMonitor -= Editor_PointMonitor;
}
private void BuildupFenceVertices(Point3d tempPt)
{
mTempPline.Closed = false;
foreach (Point3d pt in CollectedPoints)
{
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(pt.X, pt.Y), 0, 1, 1);
}
mTempPline.AddVertexAt(mTempPline.NumberOfVertices, new Point2d(tempPt.X, tempPt.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();
}
else if (mShape == Shape.Fence)
{
CollectFencePoints();
}
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);
}
else if (mShape == Shape.Fence)
{
prSelRes = ed.SelectFence(CollectedPoints);
}
if (prSelRes != null && prSelRes.Status == PromptStatus.OK)
return prSelRes.Value;
}
return null;
}
#endregion
}
}
Here is some test code and command.
[CommandMethod("SelectFence2", CommandFlags.Modal | CommandFlags.Redraw)]
public static void SelectFence2_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
using (PointCollector2 pc = new PointCollector2(PointCollector2.Shape.Fence, 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());
}
}
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: |