After creating a frustum-shaped jig “manually”, refactoring the code while introducing the idea of an “Entity Jig Framework and then updating the framework and providing a number of usage examples, today’s post looks at a slightly more complex use-case: defining a jig to create a square (in X & Y) box by selecting opposing corners.
It may sound simple, but it was actually harder that it sounds – especially when supporting the use of an arbitrary User Coordinate System. It also makes use of a “phase” type that we previously hadn’t needed, as we require point input for our second corner.
Here’s the C# code implementing our BOJ command, which reuses some code previously borrowed from Philippe Leefsma (and we’ll see some other handy code snippets from Philippe, later in the week):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
using System;
using JigFrameworks;
namespace EntityJigs
{
public class Commands
{
[CommandMethod("BOJ")]
public void BoxJig()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// Let's get the initial corner of the box
var ppr = ed.GetPoint("\nSpecify first corner: ");
if (ppr.Status == PromptStatus.OK)
{
// In order for the visual style to be respected,
// we'll add the to-be-jigged solid to the database
var tr = doc.TransactionManager.StartTransaction();
using (tr)
{
var btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId, OpenMode.ForWrite
);
var sol = new Solid3d();
btr.AppendEntity(sol);
tr.AddNewlyCreatedDBObject(sol, true);
// Create our jig object passing in the selected point
var jf =
new EntityJigFramework(
ed.CurrentUserCoordinateSystem, sol, ppr.Value,
new List<Phase>()
{
// Two phases, the second of which has a custom
// offset for the base point
new PointPhase("\nSpecify opposite corner: "),
new SolidDistancePhase(
"\nSpecify height: ",
1e-05,
(vals, pt) =>
{
// Get the diagonal line between the corners
var pt2 = (Point3d)vals[0].Value;
var diag = pt2 - pt;
var dlen = diag.Length;
// Use Pythagoras' theorem to get the side
// length
var side = Math.Sqrt(dlen * dlen / 2);
var halfSide = side / 2;
// Start by getting the displacement from
// the start point, adjusting for the fact
// we're jigging from a corner, not the center
var mat =
Matrix3d.Displacement(
pt.GetAsVector() +
new Vector3d(
halfSide,
halfSide,
(double)vals[1].Value / 2
)
);
// Calculate the angle between the diagonal
// and the X axis
var ang =
ComputeAngle(
pt,
pt2,
Vector3d.XAxis,
Matrix3d.Identity
);
// Add a rotation component to the
// transformation, adjusted by 45 degrees
// to jig the box diagonally
mat =
mat.PostMultiplyBy(
Matrix3d.Rotation(
ang + (-45 * Math.PI / 180),
Vector3d.ZAxis,
new Point3d(-halfSide, -halfSide, 0)
)
);
return
new Vector3d(halfSide, halfSide, 0).
TransformBy(mat);
}
)
},
(e, vals, pt, ucs) =>
{
// Our entity update function
// Get the diagonal line between the corners
var pt2 = (Point3d)vals[0].Value;
var diag = pt2 - pt;
var dlen = diag.Length;
// Use Pythagoras' theorem to get the side
// length
var side = Math.Sqrt(dlen * dlen / 2);
var halfSide = side / 2;
// Create our box with square sides and
// the chosen height
var s = (Solid3d)e;
s.CreateBox(side, side, (double)vals[1].Value);
// Start by getting the displacement from
// the start point, adjusting for the fact
// we're jigging from a corner, not the center
// (need to adjust for the current UCS)
var mat =
Matrix3d.Displacement(
pt.GetAsVector() +
new Vector3d(
halfSide,
halfSide,
(double)vals[1].Value / 2
)
).PreMultiplyBy(ucs);
// Calculate the angle between the diagonal
// and the X axis
var ang =
ComputeAngle(
pt,
pt2,
Vector3d.XAxis,
Matrix3d.Identity
);
// Add a rotation component to the
// transformation, adjusted by 45 degrees
// to jig the box diagonally
mat =
mat.PostMultiplyBy(
Matrix3d.Rotation(
ang + (-45 * Math.PI / 180),
Vector3d.ZAxis,
new Point3d(-halfSide, -halfSide, 0)
)
);
// Transform our solid
s.TransformBy(mat);
return true;
}
);
jf.RunTillComplete(ed, tr);
}
}
}
// Custom ArcTangent method, as the Math.Atan
// doesn't handle specific cases
public static double Atan(double y, double x)
{
if (x > 0)
return Math.Atan(y / x);
else if (x < 0)
return Math.Atan(y / x) - Math.PI;
else // x == 0
{
if (y > 0)
return Math.PI;
else if (y < 0)
return -Math.PI;
else // if (y == 0) theta is undefined
return 0.0;
}
}
// Computes Angle between a provided vector and that
// defined by the vector between two points
public static double ComputeAngle(
Point3d startPoint, Point3d endPoint,
Vector3d xdir, Matrix3d ucs
)
{
var v =
new Vector3d(
(endPoint.X - startPoint.X) / 2,
(endPoint.Y - startPoint.Y) / 2,
(endPoint.Z - startPoint.Z) / 2
);
var cos = v.DotProduct(xdir);
var sin =
v.DotProduct(
Vector3d.ZAxis.TransformBy(ucs).CrossProduct(xdir)
);
return Atan(sin, cos);
}
}
}
Here’s the command in action:
Unless anyone has further suggestions for the EntityJigFramework – or requests for additional usage examples – then I think I’ll move on from this topic, for now.
Later this week we’ll see some code that purges unused, complex DGN linetypes from a DWG and I’ll hopefully also show some fun I’ve been having with my ZX Spectrum.