This request came in over the weekend:
There is a useful object snap in AutoCAD for the mid point of 2 selected points. I would like a midpoint (average) of 3 (or more) points. Could this work in 3D as well as 2D? It’s useful when drawing building surveys, often you triangulate a point from several and there are often ‘minimal’ differences in the dimension and you just take the average.
Given the fact we’re actually talking about an arbitrary number of points that will almost certainly not belong to a single entity, object snaps are probably neither the easiest nor the most appropriate way to solve this problem.
The below C# code registers a transparent command – AVG – which can be called from within drafting or modelling commands to calculate the average of a set of points (which can be selected using object snapping, should the user so wish). The resulting point is sent to the command-line – so the command really does need to be called transparently to be useful – which obviates the need for an object snap to be implemented.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using System.Linq;
using System;
namespace AveragePoint
{
public class Commands
{
[CommandMethod("AVG", CommandFlags.Transparent)]
public void AveragePoint()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Prompt will be reused in our loop
PromptPointOptions ppo =
new PromptPointOptions("\nSelect point to average: ");
ppo.AllowNone = true;
// List of the points selected and our result object
List<Point3d> pts = new List<Point3d>();
PromptPointResult ppr;
// The selection loop
do
{
// If we have a valid point selection, add it to the list
ppr = ed.GetPoint(ppo);
if (ppr.Status == PromptStatus.OK)
pts.Add(ppr.Value);
}
while (ppr.Status == PromptStatus.OK);
// The loop will continue until a non-OK result...
// We only care if Enter was used to terminate
if (ppr.Status == PromptStatus.None)
{
// Create a string containing the point information:
// the average of the selected coordinates
string cmd =
String.Format(
"{0},{1},{2} ",
pts.Average(a => a.X),
pts.Average(a => a.Y),
pts.Average(a => a.Z)
);
// Send it to the command-line
doc.SendStringToExecute(cmd, true, false, true);
}
}
}
}
Part of the beauty of this command (if I do say so myself ;-) is in its use of LINQ to average the list of points. It calculates the average X, Y and Z coordinates using a LINQ extension method on the List<> class, placing the results in a string that gets squirted into the command-line. We could just as easily have created a Point3d from these averages, of course, had that been the requirement.
Here’s how you’d use the command. Let’s start with some basic geometry:
Let’s now launch the LINE command, and then call AVG transparently. We’ll do so one with the end-points of the line, once with the corners of the triangle and finally with those of the rectangle. We can see we end up with two lines connecting the mid-points of these shapes.
Here’s the command-line output, to see it at work:
Command: LINE
Specify first point: 'AVG
>>Select point to average:
>>Select point to average:
>>Select point to average:
Resuming LINE command.
Specify first point: 8.00080539552933,15.9545432099352,0
Specify next point or [Undo]: 'AVG
>>Select point to average:
>>Select point to average:
>>Select point to average:
>>Select point to average:
Resuming LINE command.
Specify next point or [Undo]: 15.9604077207532,14.8917902344987,0
Specify next point or [Undo]: 'AVG
>>Select point to average:
>>Select point to average:
>>Select point to average:
>>Select point to average:
>>Select point to average:
Resuming LINE command.
Specify next point or [Undo]: 26.731968038993,16.1501419209108,0
Specify next point or [Close/Undo]:
Command:
Here’s a quick test to see it working in 3D: the AVG was used to calculate the average of the peaks of three cones, returning the point for use as the start-point of a line:
As well as working in both 2D and 3D, the command appears to deal with custom user-coordinate systems without any trouble (as the average is made of the selected point – which is provided in UCS coordinates – before being sent to the command-line, which also expects UCS coordinates).
One outstanding concern I have – as it sends a point as a string to the command-line – is how it works in locales which use commas as the decimal point. I did some cursory testing, but if someone else could give it a spin and let me know how it works, that’d be great.
Although a very simple piece of code, this may well become a future Plugin of the Month: its simplicity actually makes it ideal as a project demonstrating the benefits of custom development.