We introduced before how the Entity Jigger wizard of AutoCAD .NET Addin Wizard (AcadNetAddinWizard) can help us generate various EntityJig derivatives such as Move Jig, Rotate Jig, Scale Jig, and even Mirror Jig quickly, reliably and professionally.
We also demonstrated jigging a block (INSERT/BlockReference) along with its associated attributes, which can have any alignment options both horizontal and vertical, by a few factors such as position, rotation and scale factor honoring UCS, Annotative Blocks and supporting XData copying over, a while back.
AutoCAD .NET: Drag Blocks & Attributes with EntityJig (a Better INSERT Command)
As JeffH pointed out in the comments of the post, the BlockAttributeJig does not support dynamic blocks with some alignment parameter. Through some investigations, we found it’s pretty much doable and finally worked out the DynamicBlockAlignmentJig. Here is how it behaves beautifully in AutoCAD.
Here is the complete and succinct code.
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using MgdAcApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace AcadNetAddinWizard_Namespace
{
public class DynamicBlockAlignmentJig : EntityJig
{
#region Fields
private int mCurJigFactorNumber = 1;
private bool mIsDynamicAlignment = false;
private double mAlignmentAngle = 0.0;
private ObjectId mObjectIdToAlignWith = ObjectId.Null;
private double mAngleOffset = 0.0;
private Point3d mPosition = new Point3d(0,0,0); // Factor #1
private double mRotation = 0.0; // Factor #2
private double mScaleFactor = 1.0; // Factor #3
#endregion
#region Constructors
public DynamicBlockAlignmentJig(BlockReference ent)
: base(ent)
{
mAngleOffset = ent.Rotation;
if (ent.IsDynamicBlock)
mIsDynamicAlignment = true;
}
#endregion
#region Properties
protected static Editor CurEditor
{
get
{
return MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
}
}
protected static Matrix3d UCS
{
get
{
return CurEditor.CurrentUserCoordinateSystem;
}
}
protected new BlockReference Entity
{
get
{
return (BlockReference)base.Entity;
}
}
#endregion
#region Overrides
protected override bool Update()
{
switch (mCurJigFactorNumber)
{
case 1:
Entity.Position = mPosition.TransformBy(UCS);
if (mIsDynamicAlignment && !mObjectIdToAlignWith.IsNull)
{
if( mObjectIdToAlignWith.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Curve))))
{
Curve curve = (Curve)Entity.Database.TransactionManager.TopTransaction.GetObject(mObjectIdToAlignWith, OpenMode.ForRead);
Vector3d dir = curve.GetFirstDerivative(curve.GetClosestPointTo(Entity.Position, false)).TransformBy(UCS.Inverse());
mAlignmentAngle = Vector3d.XAxis.GetAngleTo(dir, Vector3d.ZAxis);
Entity.Rotation = mAlignmentAngle + mAngleOffset;
}
else
mObjectIdToAlignWith = ObjectId.Null;
}
break;
case 2:
Entity.Rotation = mRotation + mAngleOffset; ;
break;
case 3:
Entity.ScaleFactors = new Scale3d(mScaleFactor);
break;
default:
break;
}
return true;
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
switch (mCurJigFactorNumber)
{
case 1:
JigPromptPointOptions prOptions1 = new JigPromptPointOptions("\nBlock insertion point:");
PromptPointResult prResult1 = prompts.AcquirePoint(prOptions1);
if (prResult1.Status == PromptStatus.Cancel)
return SamplerStatus.Cancel;
Point3d tempPt = prResult1.Value.TransformBy(UCS.Inverse());
if (tempPt.IsEqualTo(mPosition))
{
return SamplerStatus.NoChange;
}
else
{
mPosition = tempPt;
mObjectIdToAlignWith = pickObjId;
return SamplerStatus.OK;
}
case 2:
JigPromptAngleOptions prOptions2 = new JigPromptAngleOptions("\nBlock rotation angle:");
prOptions2.BasePoint = mPosition.TransformBy(UCS);
prOptions2.UseBasePoint = true;
PromptDoubleResult prResult2 = prompts.AcquireAngle(prOptions2);
if (prResult2.Status == PromptStatus.Cancel)
return SamplerStatus.Cancel;
if (prResult2.Value.Equals(mRotation))
{
return SamplerStatus.NoChange;
}
else
{
mRotation = prResult2.Value;
return SamplerStatus.OK;
}
case 3:
JigPromptDistanceOptions prOptions3 = new JigPromptDistanceOptions("\nBlock scale factor:");
prOptions3.BasePoint = mPosition.TransformBy(UCS);
prOptions3.UseBasePoint = true;
PromptDoubleResult prResult3 = prompts.AcquireDistance(prOptions3);
if (prResult3.Status == PromptStatus.Cancel)
return SamplerStatus.Cancel;
if (prResult3.Value.Equals(mScaleFactor))
{
return SamplerStatus.NoChange;
}
else
{
mScaleFactor = prResult3.Value;
return SamplerStatus.OK;
}
default:
break;
}
return SamplerStatus.OK;
}
#endregion
#region Point Monitor
private static ObjectId pickObjId = ObjectId.Null;
private static void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
{
Point3d computedPt = e.Context.ComputedPoint;
FullSubentityPath[] pickedEnts = e.Context.GetPickedEntities();
if (pickedEnts != null && pickedEnts.Length > 0)
{
pickObjId = pickedEnts[0].GetObjectIds()[0];
}
else
pickObjId = ObjectId.Null;
}
#endregion
#region Method to Call
public static bool Jig(BlockReference ent)
{
try
{
DynamicBlockAlignmentJig jigger = new DynamicBlockAlignmentJig(ent);
PromptResult pr;
do
{
pr = CurEditor.Drag(jigger);
if (jigger.mCurJigFactorNumber == 1 && jigger.mIsDynamicAlignment && !jigger.mObjectIdToAlignWith.IsNull)
jigger.mCurJigFactorNumber++;
} while (pr.Status != PromptStatus.Cancel && pr.Status != PromptStatus.Error && jigger.mCurJigFactorNumber++ <= 3);
return pr.Status == PromptStatus.OK;
}
catch
{
return false;
}
}
#endregion
#region Test Command
[CommandMethod("DynamicBlockAlignmentJig")]
public static void BlockAttributeJig_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
try
{
PromptResult pr = CurEditor.GetString("\nName of the block to jig:");
if (pr.Status == PromptStatus.OK)
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
if (!bt.Has(pr.StringResult))
{
CurEditor.WriteMessage("\nThe block <{0}> does not exist.", pr.StringResult);
return;
}
BlockTableRecord btr = tr.GetObject(bt[pr.StringResult], OpenMode.ForRead) as BlockTableRecord;
using (BlockReference ent = new BlockReference(new Point3d(0, 0, 0), btr.ObjectId))
{
ent.TransformBy(UCS);
BlockTableRecord modelspace = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
modelspace.AppendEntity(ent);
tr.AddNewlyCreatedDBObject(ent, true);
CurEditor.TurnForcedPickOn();
CurEditor.PointMonitor += Editor_PointMonitor;
if (DynamicBlockAlignmentJig.Jig(ent))
tr.Commit();
CurEditor.PointMonitor -= Editor_PointMonitor;
CurEditor.TurnForcedPickOff();
}
}
}
}
catch (Exception ex)
{
CurEditor.WriteMessage(ex.Message);
}
}
#endregion
}
}
It looks simple enough. However, there are quite some tips and tricks here and there. Here are a few.
• The DynamicBlockAlignmentJig is derived from EntityJig as can be imagined.
• We exclude as many as possible factors from the jig to focus on dynamic blocks and their alignment parameters if any.
• The DynamicBlockAlignmentJig has to make some assumptions due to some limitations of the current AutoCAD .NET API.
• It assumes all dynamic blocks will have such an alignment parameter defined, whose position is coincident with the insertion point of the block and whose direction is along the tangent of any curve like entities.
• It’s figured that each dynamic block has an extension dictionary with the name ACAD_ENHANCEDBLOCK which in turn owns an AcDbBlockAlignmentParameter object defining the alignment parameter inside the dynamic block.
• However, we don’t have a way to retrieve those information with the current AutoCAD .NET API.
• The DynamicBlockAlignmentJig still supports regular blocks properly as demonstrated in the video.
Enjoy it!
YOU DA MAN!!!
Posted by: JeffH | 11/19/2012 at 12:07 PM
Hi Spiderinnet1,
Again I do not have a good fundamental understanding of jigs and need to brush back up on vectors but make entity rotate back to 0 if no entity under the cursor and to rotate 180 degrees if mouse is coming other direction would you do something like this?
I know there is a better way but this was I came up quickly just to see how it acted
case 1:
Entity.Position = mPosition.TransformBy(UCS);
if (mIsDynamicAlignment)
{
if (!mObjectIdToAlignWith.IsNull)
{
if (mObjectIdToAlignWith.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Curve))))
{
Curve curve = (Curve)Entity.Database.TransactionManager.TopTransaction.GetObject(mObjectIdToAlignWith, OpenMode.ForRead);
Point3d pnt = curve.GetClosestPointTo(Entity.Position, false);
Vector3d dir = curve.GetFirstDerivative(pnt).TransformBy(UCS.Inverse());
if (Entity.Position.X < pnt.X || Entity.Position.Y < pnt.Y)
{
dir = dir.Negate();
}
mAlignmentAngle = Vector3d.XAxis.GetAngleTo(dir, Vector3d.ZAxis);
Entity.Rotation = mAlignmentAngle + mAngleOffset;
}
else
{
mObjectIdToAlignWith = ObjectId.Null;
}
}
else
{
Entity.Rotation = 0.0;
}
}
break;
Posted by: JeffH | 11/20/2012 at 04:39 AM
Also looks like prompt for rotation gets skipped as mCurJigFactorNumber gets incremented twice on first go through
do
{
pr = CurEditor.Drag(jigger);
if (jigger.mCurJigFactorNumber == 1 && jigger.mIsDynamicAlignment && !jigger.mObjectIdToAlignWith.IsNull)
jigger.mCurJigFactorNumber++;
} while (pr.Status != PromptStatus.Cancel && pr.Status != PromptStatus.Error && jigger.mCurJigFactorNumber++ <= 3);
Posted by: JeffH | 11/20/2012 at 08:02 AM
JeffH, u can check the angle between the dir from the picked pt to the computed pt in the point monitor or the closest pt on the curve and the curve tangent dir to know if the mouse cursor is outside or inside the loop. Then u can add 180 degree to the dir or keep it as is.
If u want the ent rotate after aligned to the curve, u can comment out the curjignumber increment code as u appended.
Posted by: Spiderinnet1 | 11/21/2012 at 02:04 AM