A colleague set me a fun little geometry-related challenge a couple of days ago: to write C# and F# applications to make AutoCAD draw lines between a number of points spaced evenly around the circumference of a circle.
Here’s the first C# version I wrote, which makes use of a function to collect the various points before indexing into the collection from a nested loop:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
namespace CircleOfLines
{
public class Commands
{
public static Point3dCollection pointsOnCircle(
Point3d center, double radius, int n
)
{
double alpha = Math.PI * 2 / n;
Point3dCollection pts = new Point3dCollection();
for (int i = 0; i < n; i++)
{
double theta = alpha * i;
Point3d pt =
new Point3d(
center.X + Math.Cos(theta) * radius,
center.Y + Math.Sin(theta) * radius,
center.Z
);
pts.Add(pt);
}
return pts;
}
[CommandMethod("CSCIR")]
public void CreateLinesBetweenPoints()
{
Database db =
HostApplicationServices.WorkingDatabase;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite
);
int n = 19;
Point3dCollection pts =
pointsOnCircle(new Point3d(0, 0, 0), 5.0, n);
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
Line ln = new Line(pts[i], pts[j]);
btr.AppendEntity(ln);
tr.AddNewlyCreatedDBObject(ln, true);
}
}
tr.Commit();
}
}
}
}
I also wrote a “flattened” version in C# using a more linear approach (avoiding the function definition/call), although ultimately I believe the prior version to be more realistic:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
namespace CircleOfLines
{
public class Commands2
{
[CommandMethod("CSCIR2")]
public void CreateLinesBetweenPoints2()
{
Database db =
HostApplicationServices.WorkingDatabase;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite
);
int n = 19;
Point3d center = new Point3d(0, 0, 0);
double radius = 5.0;
double alpha = Math.PI * 2 / n;
for (int i = 0; i < n; i++)
{
double theta = alpha * i;
Point3d pt1 =
new Point3d(
center.X + Math.Cos(theta) * radius,
center.Y + Math.Sin(theta) * radius,
center.Z
);
for (int j = i + 1; j < n; j++)
{
double theta2 = alpha * j;
Point3d pt2 =
new Point3d(
center.X + Math.Cos(theta2) * radius,
center.Y + Math.Sin(theta2) * radius,
center.Z
);
Line ln = new Line(pt1, pt2);
btr.AppendEntity(ln);
tr.AddNewlyCreatedDBObject(ln, true);
}
}
tr.Commit();
}
}
}
}
And finally I provided a really “functional” version (in the sense that it uses Functional Programming techniques – all three versions are functional, of course ;-) in F#. This avoids any use of direct iteration, preferring to use data pipelining and recursion, although the recursion gets changed to iteration in the generated IL by the F# compiler’s tail call optimization.
module CircleOfLines
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.Runtime
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.Geometry
open System
let rec circPoints acc (cen : Point3d) rad alpha n =
if n <= 0 then
acc
else
let theta = alpha * (float)n
let pt =
new Point3d
(cen.X + Math.Cos(theta) * rad,
cen.Y + Math.Sin(theta) * rad,
cen.Z)
circPoints (pt :: acc) cen rad alpha (n-1)
let pointsOnCircle cen rad n =
let alpha = Math.PI * 2.0 / (float)n
circPoints [] cen rad alpha n
let rec fromAtoBs acc pt1 pts =
match pts with
| [] -> acc
| pt2::t ->
let ln = new Line(pt1,pt2)
fromAtoBs (ln :: acc) pt1 t
let linesFromPoint start endpts = fromAtoBs [] start endpts
let rec fromAsToBs acc pts =
match pts with
| [] -> acc
| h :: t ->
fromAsToBs (linesFromPoint h t @ acc) t
let linesBetweenPoints pts = fromAsToBs [] pts
[<CommandMethod("FSCIR")>]
let createLinesBetweenPoints() =
let db = HostApplicationServices.WorkingDatabase
use tr = db.TransactionManager.StartTransaction()
let bt =
tr.GetObject
(db.BlockTableId,OpenMode.ForRead)
:?> BlockTable
let ms =
tr.GetObject
(bt.[BlockTableRecord.ModelSpace],
OpenMode.ForWrite)
:?> BlockTableRecord
let addToDatabase ent =
ms.AppendEntity(ent) |> ignore
tr.AddNewlyCreatedDBObject(ent, true)
pointsOnCircle (new Point3d(0.0, 0.0, 0.0)) 5.0 19
|> linesBetweenPoints
|> List.map addToDatabase
|> ignore
tr.Commit()
All versions do the same thing and actually have similar numbers of lines of code (although that would probably be different if I were formatting to a broader page width than fits on my blog, of course).
Update
Thanks for MichaelGG for the comment helping refine the F# implementation. Here’s the updated code which is, indeed, a good few lines shorter:
module CircleOfLines
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.Runtime
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.Geometry
open System
let pointsOnCircle (cen: Point3d) rad n =
let alpha = Math.PI * 2.0 / (float)n
let calcPoint n =
let theta = alpha * (float)(n + 1)
Point3d(cen.X + Math.Cos theta * rad,
cen.Y + Math.Sin theta * rad,
cen.Z)
List.init n calcPoint
let linesFromPoint start endpts =
endpts |> List.map(fun x -> new Line(start, x)) |> List.rev
let rec fromAsToBs acc pts =
match pts with
| [] -> acc
| h :: t ->
fromAsToBs (linesFromPoint h t @ acc) t
let linesBetweenPoints pts = fromAsToBs [] pts
[<CommandMethod("FSCIR")>]
let createLinesBetweenPoints() =
let db = HostApplicationServices.WorkingDatabase
use tr = db.TransactionManager.StartTransaction()
let bt =
tr.GetObject
(db.BlockTableId,OpenMode.ForRead)
:?> BlockTable
let ms =
tr.GetObject
(bt.[BlockTableRecord.ModelSpace],
OpenMode.ForWrite)
:?> BlockTableRecord
let addToDatabase ent =
ms.AppendEntity(ent) |> ignore
tr.AddNewlyCreatedDBObject(ent, true)
pointsOnCircle (new Point3d(0.0, 0.0, 0.0)) 5.0 19
|> linesBetweenPoints
|> List.map addToDatabase
|> ignore
tr.Commit()
Oh, and thanks to Jason Davies I now know I’ve been plotting a complete graph. Which is even easier to plot using Wolfram Alpha than it is using C# or F# inside AutoCAD. ;-)