Many thanks to Holger Rasch who worked out how to fix the code in this previous post. It really doesn’t matter that 3 years have passed, Holger – I have no doubt people will greatly appreciate the fact the code can now run without causing the annoying display issues it produced previously.
Holger made a few adjustments to the implementation to make sure the persistent hatch loop gets added and removed in the right places. Here’s the updated C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Colors;
using System;
namespace HatchJig
{
class JigUtils
{
// 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 current direction
// (vector from last vertex to current vertex)
// and the last pline segment
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
);
double cos = v.DotProduct(xdir);
double sin =
v.DotProduct(
Vector3d.ZAxis.TransformBy(ucs).CrossProduct(xdir)
);
return Atan(sin, cos);
}
}
public class HatchJig : DrawJig
{
Point3d _tempPoint;
bool _isArcSeg = false;
bool _isUndoing = false;
Matrix3d _ucs;
Plane _plane;
Polyline _pline = null;
Hatch _hat = null;
public HatchJig(
Matrix3d ucs, Plane plane, Polyline pl, Hatch hat
)
{
_ucs = ucs;
_plane = plane;
_pline = pl;
_hat = hat;
AddDummyVertex();
}
protected override bool WorldDraw(
Autodesk.AutoCAD.GraphicsInterface.WorldDraw wd
)
{
// Update the dummy vertex to be our 3D point
// projected onto our plane
if (_isArcSeg)
{
var lastVertex =
_pline.GetPoint3dAt(_pline.NumberOfVertices - 2);
Vector3d refDir;
if (_pline.NumberOfVertices < 3)
refDir = new Vector3d(1.0, 1.0, 0.0);
else
{
// Check bulge to see if last segment was an arc or a line
if (_pline.GetBulgeAt(_pline.NumberOfVertices - 3) != 0)
{
var arcSegment =
_pline.GetArcSegmentAt(_pline.NumberOfVertices - 3);
var tangent = arcSegment.GetTangent(lastVertex);
// Reference direction is the invert of the arc tangent
// at last vertex
refDir = tangent.Direction.MultiplyBy(-1.0);
}
else
{
var pt =
_pline.GetPoint3dAt(_pline.NumberOfVertices - 3);
refDir =
new Vector3d(
lastVertex.X - pt.X,
lastVertex.Y - pt.Y,
lastVertex.Z - pt.Z
);
}
}
double angle =
JigUtils.ComputeAngle(
lastVertex, _tempPoint, refDir, _ucs
);
// Bulge is defined as tan of one fourth of included angle
// Need to double the angle since it represents the included
// angle of the arc
// So formula is: bulge = Tan(angle * 2 * 0.25)
double bulge = Math.Tan(angle * 0.5);
_pline.SetBulgeAt(_pline.NumberOfVertices - 2, bulge);
}
else
{
// Line mode. Need to remove last bulge if there was one
if (_pline.NumberOfVertices > 1)
_pline.SetBulgeAt(_pline.NumberOfVertices - 2, 0);
}
_pline.SetPointAt(
_pline.NumberOfVertices - 1, _tempPoint.Convert2d(_plane)
);
// Only when we have enough vertices
if (_pline.NumberOfVertices >2)
{
_pline.Closed = true;
AppendTheLoop();
}
if (!wd.RegenAbort)
{
wd.Geometry.Draw(_pline);
if (_pline.NumberOfVertices > 2)
{
_hat.EvaluateHatch(true);
if (!wd.RegenAbort)
wd.Geometry.Draw(_hat);
// Take it out, we will create a new one later
_hat.RemoveLoopAt(0);
}
}
return true;
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
var jigOpts = new JigPromptPointOptions();
jigOpts.UserInputControls =
(UserInputControls.NullResponseAccepted |
UserInputControls.NoNegativeResponseAccepted |
UserInputControls.GovernedByOrthoMode);
_isUndoing = false;
if (_pline.NumberOfVertices == 1)
{
// For the first vertex, just ask for the point
jigOpts.Message = "\nSpecify start point: ";
}
else if (_pline.NumberOfVertices > 1)
{
string msgAndKwds =
(_isArcSeg ?
"\nSpecify endpoint of arc or [Line/Undo]: " :
"\nSpecify next point or [Arc/Undo]: "
);
string kwds = (_isArcSeg ? "Line Undo" : "Arc Undo");
jigOpts.SetMessageAndKeywords(msgAndKwds, kwds);
}
else
return SamplerStatus.Cancel; // Should never happen
// Get the point itself
var res = prompts.AcquirePoint(jigOpts);
if (res.Status == PromptStatus.Keyword)
{
if (res.StringResult.ToUpper() == "ARC")
_isArcSeg = true;
else if (res.StringResult.ToUpper() == "LINE")
_isArcSeg = false;
else if (res.StringResult.ToUpper() == "UNDO")
_isUndoing = true;
return SamplerStatus.OK;
}
else if (res.Status == PromptStatus.OK)
{
// Check if it has changed or not (reduces flicker)
if (_tempPoint == res.Value)
return SamplerStatus.NoChange;
else
{
_tempPoint = res.Value;
return SamplerStatus.OK;
}
}
return SamplerStatus.Cancel;
}
public bool IsUndoing
{
get
{
return _isUndoing;
}
}
public void AddDummyVertex()
{
// Create a new dummy vertex... can have any initial value
_pline.AddVertexAt(
_pline.NumberOfVertices, new Point2d(0, 0), 0, 0, 0
);
}
public bool RemoveLastVertex()
{
// If there's a single vertex, don't attempt to remove the
// vertex: would cause a degenerate geometry error
// Returning false will tell the calling function to
// abort the transaction
if (_pline.NumberOfVertices == 1)
return false;
// Let's remove our dummy vertex
if (_pline.NumberOfVertices > 0)
_pline.RemoveVertexAt(_pline.NumberOfVertices - 1);
// And then check the type of the last segment
if (_pline.NumberOfVertices >= 2)
{
double blg = _pline.GetBulgeAt(_pline.NumberOfVertices - 2);
_isArcSeg = (blg != 0);
// We have to sync the hatch with the polyline
AppendTheLoop();
}
return true;
}
private void AppendTheLoop()
{
var ids = new ObjectIdCollection();
ids.Add(_pline.ObjectId);
_hat.Associative = true;
_hat.AppendLoop(HatchLoopTypes.Default, ids);
}
[CommandMethod("HATJIG")]
public static void RunHatchJig()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// Create a transaction, as we're jigging
// db-resident objects
using (var tr = db.TransactionManager.StartTransaction())
{
var btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId, OpenMode.ForWrite
);
var normal =
Vector3d.ZAxis.TransformBy(
ed.CurrentUserCoordinateSystem
);
// We will pass a plane to our jig, to help
// with UCS transformations
var plane = new Plane(Point3d.Origin, normal);
// We also pass a db-resident polyline
var pl = new Polyline();
pl.Normal = normal;
btr.AppendEntity(pl);
tr.AddNewlyCreatedDBObject(pl, true);
// And a db-resident hatch
var hat = new Hatch();
// Use a non-solid hatch pattern, to aid jigging
hat.SetHatchPattern(
HatchPatternType.PreDefined,
"ANGLE"
);
// But let's make it transparent, for fun
// Alpha value is Truncate(255 * (100-n)/100)
hat.ColorIndex = 1;
hat.Transparency = new Transparency(127);
// Add the hatch to the modelspace & transaction
var hatId = btr.AppendEntity(hat);
tr.AddNewlyCreatedDBObject(hat, true);
// And finally pass everything to the jig
var jig =
new HatchJig(
ed.CurrentUserCoordinateSystem, plane, pl, hat
);
while (true)
{
var res = ed.Drag(jig);
switch (res.Status)
{
// New point was added, keep going
case PromptStatus.OK:
jig.AddDummyVertex();
break;
// Keyword was entered
case PromptStatus.Keyword:
if (jig.IsUndoing)
jig.RemoveLastVertex();
break;
// The jig completed successfully
case PromptStatus.None:
// You can remove this next line if you want
// the vertex being jigged to be included
if (jig.RemoveLastVertex())
{
tr.Commit();
}
return;
// User cancelled the command
default:
// No need to erase the polyline & hatch, as
// the transaction will simply be aborted
return;
}
}
}
}
}
}
Here’s the code in action, with none of the former visual artifacts:
Thanks again, Holger! :-)
Update:
After a comment from Robbo, I went ahead and made the generated hatch associative to its boundary by adding a single line of code (“hat.Associative = true;”) once the hatch has been made db-resident. You can, of course, comment out this line to revert to the previous behaviour.
Update 2:
Well that’s strange. It turns out Holger’s code was already setting boundary associativity… not sure how I missed that. Anyway, I have addressed an issue pointed out in the comments (thanks, aks!) where if you hit the spacebar immediately after starting the jig, an exception gets thrown. This has now been fixed in the above code.