Using a jig to rotate an AutoCAD entity via .NET
An interesting request came in via a previous post followed up by a similar question came in via another post. The original problem was to rotate a rectangular polyline entity around its centre, and be able to continue rotating it afterwards. A few things were interesting to me:
- Rectangles are simply polylines between four points, so have no inherent concept of either a centre point or a rotation angle
- The obvious answer (to me, at least) being to calculate the centre and store the rotation as XData on the polyline
- To modify an entity graphically it makes sense to use a jig
- Most examples show how to use jigs to create new entities, not modify existing ones
So with that in mind, I created the below C# code to solve this problem:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace RotatingRectangles
{
public class Commands
{
// Define some constants we'll use to
// store our XData
// AppName is our RDS (TTIF, for
// "Through The InterFace") plus an indicator
// what it's for (ROTation)
const string kRegAppName = "TTIF_ROT";
const int kAppCode = 1001;
const int kRotCode = 1040;
class RotateJig : EntityJig
{
// Declare some internal state
double m_baseAngle, m_deltaAngle;
Point3d m_rotationPoint;
Matrix3d m_ucs;
// Constructor sets the state and clones
// the entity passed in
// (adequate for simple entities)
public RotateJig(
Entity ent,
Point3d rotationPoint,
double baseAngle,
Matrix3d ucs)
: base(ent.Clone() as Entity)
{
m_rotationPoint = rotationPoint;
m_baseAngle = baseAngle;
m_ucs = ucs;
}
protected override SamplerStatus Sampler(
JigPrompts jp
)
{
// We acquire a single angular value
JigPromptAngleOptions jo =
new JigPromptAngleOptions(
"\nAngle of rotation: "
);
jo.BasePoint = m_rotationPoint;
jo.UseBasePoint = true;
PromptDoubleResult pdr =
jp.AcquireAngle(jo);
if (pdr.Status == PromptStatus.OK)
{
// Check if it has changed or not
// (reduces flicker)
if (m_deltaAngle == pdr.Value)
{
return SamplerStatus.NoChange;
}
else
{
// Set the change in angle to
// the new value
m_deltaAngle = pdr.Value;
return SamplerStatus.OK;
}
}
return SamplerStatus.Cancel;
}
protected override bool Update()
{
// We rotate the polyline by the change
// minus the base angle
Matrix3d trans =
Matrix3d.Rotation(
m_deltaAngle - m_baseAngle,
m_ucs.CoordinateSystem3d.Zaxis,
m_rotationPoint);
Entity.TransformBy(trans);
// The base becomes the previous delta
// and the delta gets set to zero
m_baseAngle = m_deltaAngle;
m_deltaAngle = 0.0;
return true;
}
public Entity GetEntity()
{
return Entity;
}
public double GetRotation()
{
// The overall rotation is the
// base plus the delta
return m_baseAngle + m_deltaAngle;
}
}
[CommandMethod("ROT")]
public void RotateEntity()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// First we prompt for the entity to rotate
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect entity to rotate: "
);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status == PromptStatus.OK)
{
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
DBObject obj =
tr.GetObject(per.ObjectId, OpenMode.ForRead);
Entity ent = obj as Entity;
// Use the origin as the default center
Point3d rotationPoint = Point3d.Origin;
// If the entity is a polyline,
// assume it is rectangular and then
// set the rotation point as its center
Polyline pl = obj as Polyline;
if (pl != null)
{
LineSegment3d ps0 =
pl.GetLineSegmentAt(0);
LineSegment3d ps1 =
pl.GetLineSegmentAt(1);
Vector3d vec =
((ps0.EndPoint - ps0.StartPoint) / 2.0) +
((ps1.EndPoint - ps1.StartPoint) / 2.0);
rotationPoint = pl.StartPoint + vec;
}
// Get the base rotation angle stored with the
// entity, if there was one (default is 0.0)
double baseAngle = GetStoredRotation(obj);
if (ent != null)
{
// Get the current UCS, to pass to the Jig
Matrix3d ucs =
ed.CurrentUserCoordinateSystem;
// Create our jig object
RotateJig jig =
new RotateJig(
ent,
rotationPoint,
baseAngle,
ucs
);
PromptResult res = ed.Drag(jig);
if (res.Status == PromptStatus.OK)
{
// Get the overall rotation angle
// and dispose of the temp clone
double newAngle = jig.GetRotation();
jig.GetEntity().Dispose();
// Rotate the original entity
Matrix3d trans =
Matrix3d.Rotation(
newAngle - baseAngle,
ucs.CoordinateSystem3d.Zaxis,
rotationPoint);
ent.UpgradeOpen();
ent.TransformBy(trans);
// Store the new rotation as XData
SetStoredRotation(ent, newAngle);
}
}
tr.Commit();
}
}
}
// Helper function to create a RegApp
static void AddRegAppTableRecord(string regAppName)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
RegAppTable rat =
(RegAppTable)tr.GetObject(
db.RegAppTableId,
OpenMode.ForRead,
false
);
if (!rat.Has(regAppName))
{
rat.UpgradeOpen();
RegAppTableRecord ratr =
new RegAppTableRecord();
ratr.Name = regAppName;
rat.Add(ratr);
tr.AddNewlyCreatedDBObject(ratr, true);
}
tr.Commit();
}
}
// Store our rotation angle as XData
private void SetStoredRotation(
DBObject obj, double rotation)
{
AddRegAppTableRecord(kRegAppName);
ResultBuffer rb = obj.XData;
if (rb == null)
{
rb =
new ResultBuffer(
new TypedValue(kAppCode, kRegAppName),
new TypedValue(kRotCode, rotation)
);
}
else
{
// We can simply add our values - no need
// to remove the previous ones, the new ones
// are the ones that get stored
rb.Add(new TypedValue(kAppCode, kRegAppName));
rb.Add(new TypedValue(kRotCode, rotation));
}
obj.XData = rb;
rb.Dispose();
}
// Retrieve the existing rotation angle from XData
private double GetStoredRotation(DBObject obj)
{
double ret = 0.0;
ResultBuffer rb = obj.XData;
if (rb != null)
{
// If we find our group code, it means that on
// the next iteration, we'll get our rotation
bool bReadyForRot = false;
foreach (TypedValue tv in rb)
{
if (bReadyForRot)
{
if (tv.TypeCode == kRotCode)
ret = (double)tv.Value;
bReadyForRot = false;
}
if (tv.TypeCode == kAppCode)
bReadyForRot = true;
}
rb.Dispose();
}
return ret;
}
}
}
To try this out, we can use the RECTANG command, to create a horizontal rectangle, and then use our custom ROT command to rotate it:
Calling the ROT command subsequently works fine, as it "remembers" the angle it was rotated at. If other tools are used to rotate the rectangle, all bets are off. One alternative would be to determine the "rotation" by performing slightly deeper analysis on the rectangle: determining the longer side and getting its angle should do it. It would also be trivial to implement a command to set the rotation to one input by the user (whether by selecting two points or entering it).
March 28, 2008 in AutoCAD, AutoCAD .NET, Jigs | Permalink | Comments (6) | TrackBack
Creating a multileader in AutoCAD using a jig from .NET
I'm now back from a fantastic break in Italy and am trying hard to catch back up. Next week I'm off again to San Diego (work, this time), which may cause further interruptions in blog postings.
This question came through from Genésio from Brazil:
I wish jig a leader with an bubble in the and of the leader, at the same time. Can you help me. Perhaps post the solution in your blog (through the interface).
It took me a while - frustratingly long, in fact, and probably this is not exactly what Genésio is after - but here's what I managed to come up with. The "bubble" is framed MText, but it should be modifiable to use a classic bubble block, instead. I drew heavily on this previous post for the jig code.
The positioning of the text took some work, but I'm reasonably happy with the results. If anyone has tweaks to suggest, please post a comment.
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace DimensionLibrary
{
public class DimensionCmds
{
class MLeaderJig : EntityJig
{
Point3dCollection m_pts;
Point3d m_tempPoint;
string m_contents;
int m_leaderIndex;
int m_leaderLineIndex;
public MLeaderJig(string contents)
: base(new MLeader())
{
// Store the string passed in
m_contents = contents;
// Create a point collection to store our vertices
m_pts = new Point3dCollection();
// Create mleader and set defaults
MLeader ml = Entity as MLeader;
ml.SetDatabaseDefaults();
// Set up the MText contents
ml.ContentType = ContentType.MTextContent;
MText mt = new MText();
mt.SetDatabaseDefaults();
mt.Contents = m_contents;
ml.MText = mt;
ml.TextAlignmentType =
TextAlignmentType.LeftAlignment;
ml.TextAttachmentType =
TextAttachmentType.AttachmentMiddle;
// Set the frame and landing properties
ml.EnableDogleg = true;
ml.EnableFrameText = true;
ml.EnableLanding = true;
// Reduce the standard landing gap
ml.LandingGap = 0.05;
// Add a leader, but not a leader line (for now)
m_leaderIndex = ml.AddLeader();
m_leaderLineIndex = -1;
}
protected override SamplerStatus Sampler(
JigPrompts prompts
)
{
JigPromptPointOptions opts =
new JigPromptPointOptions();
// Not all options accept null response
opts.UserInputControls =
(UserInputControls.Accept3dCoordinates |
UserInputControls.NoNegativeResponseAccepted
);
// Get the first point
if (m_pts.Count == 0)
{
opts.UserInputControls |=
UserInputControls.NullResponseAccepted;
opts.Message =
"\nStart point of multileader: ";
opts.UseBasePoint = false;
}
// And the second
else if (m_pts.Count == 1)
{
opts.BasePoint = m_pts[m_pts.Count - 1];
opts.UseBasePoint = true;
opts.Message =
"\nSpecify multileader vertex: ";
}
// And subsequent points
else if (m_pts.Count > 1)
{
opts.UserInputControls |=
UserInputControls.NullResponseAccepted;
opts.BasePoint = m_pts[m_pts.Count - 1];
opts.UseBasePoint = true;
opts.SetMessageAndKeywords(
"\nSpecify multileader vertex or [End]: ",
"End"
);
}
else // Should never happen
return SamplerStatus.Cancel;
PromptPointResult res =
prompts.AcquirePoint(opts);
if (res.Status == PromptStatus.Keyword)
{
if (res.StringResult == "End")
{
return SamplerStatus.Cancel;
}
}
if (m_tempPoint == res.Value)
{
return SamplerStatus.NoChange;
}
else if (res.Status == PromptStatus.OK)
{
m_tempPoint = res.Value;
return SamplerStatus.OK;
}
return SamplerStatus.Cancel;
}
protected override bool Update()
{
try
{
if (m_pts.Count > 0)
{
// Set the last vertex to the new value
MLeader ml = Entity as MLeader;
ml.SetLastVertex(
m_leaderLineIndex,
m_tempPoint
);
// Adjust the text location
Vector3d dogvec =
ml.GetDogleg(m_leaderIndex);
double doglen =
ml.DoglegLength;
double landgap =
ml.LandingGap;
ml.TextLocation =
m_tempPoint +
((doglen + landgap) * dogvec);
}
}
catch (System.Exception ex)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
doc.Editor.WriteMessage(
"\nException: " + ex.Message
);
return false;
}
return true;
}
public void AddVertex()
{
MLeader ml = Entity as MLeader;
// For the first point...
if (m_pts.Count == 0)
{
// Add a leader line
m_leaderLineIndex =
ml.AddLeaderLine(m_leaderIndex);
// And a start vertex
ml.AddFirstVertex(
m_leaderLineIndex,
m_tempPoint
);
// Add a second vertex that will be set
// within the jig
ml.AddLastVertex(
m_leaderLineIndex,
new Point3d(0, 0, 0)
);
}
else
{
// For subsequent points,
// just add a vertex
ml.AddLastVertex(
m_leaderLineIndex,
m_tempPoint
);
}
// Reset the attachment point, otherwise
// it seems to get forgotten
ml.TextAttachmentType =
TextAttachmentType.AttachmentMiddle;
// Add the latest point to our history
m_pts.Add(m_tempPoint);
}
public void RemoveLastVertex()
{
// We don't need to actually remove
// the vertex, just reset it
MLeader ml = Entity as MLeader;
if (m_pts.Count >= 1)
{
Vector3d dogvec =
ml.GetDogleg(m_leaderIndex);
double doglen =
ml.DoglegLength;
double landgap =
ml.LandingGap;
ml.TextLocation =
m_pts[m_pts.Count - 1] +
((doglen + landgap) * dogvec);
}
}
public Entity GetEntity()
{
return Entity;
}
}
[CommandMethod("MYML")]
public void MyMLeaderJig()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// Get the text outside of the jig
PromptStringOptions pso =
new PromptStringOptions(
"\nEnter text: "
);
pso.AllowSpaces = true;
PromptResult pr =
ed.GetString(pso);
if (pr.Status == PromptStatus.OK)
{
// Create MleaderJig
MLeaderJig jig =
new MLeaderJig(pr.StringResult);
// Loop to set vertices
bool bSuccess = true, bComplete = false;
while (bSuccess && !bComplete)
{
PromptResult dragres = ed.Drag(jig);
bSuccess =
(dragres.Status == PromptStatus.OK);
if (bSuccess)
jig.AddVertex();
bComplete =
(dragres.Status == PromptStatus.None);
if (bComplete)
jig.RemoveLastVertex();
}
if (bComplete)
{
// Append entity
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead,
false
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite,
false
);
btr.AppendEntity(jig.GetEntity());
tr.AddNewlyCreatedDBObject(
jig.GetEntity(),
true
);
tr.Commit();
}
}
}
}
}
}
Here's what you get when you run the MYML command:
September 21, 2007 in AutoCAD, AutoCAD .NET, Dimensions, Jigs | Permalink | Comments (5) | TrackBack
Using a jig from .NET to multiply insert AutoCAD blocks - Part 2
In the last post we looked at a jig that can be used to add block references to an AutoCAD drawing. This post extends that code to support annotative block definitions (available from AutoCAD 2008) and blocks with attributes. Thanks once again to Holger Steiner for the jig class and to Roland Feletic for posting the code to support annotative blocks.
A comment on the previous post asked about having attributes visible during the jig process: unfortunately that's not currently possible, as the existing managed AttributeCollection implementation wraps the version of the AcDbBlockReference::appendAttribute() ObjectARX function that requires the block reference to already have been added to the drawing. So for now you will need to live with the fact that the attributes don't display during the jig, but do display as soon as the block has been added to the drawing.
Below is the modified C# code, with line numbers. And here's the source file for download.
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.EditorInput;
4 using Autodesk.AutoCAD.Runtime;
5 using Autodesk.AutoCAD.Geometry;
6 using Autodesk.AutoCAD.Internal;
7
8 namespace BlockJigTest
9 {
10 class BlockJig : EntityJig
11 {
12 Point3d mCenterPt, mActualPoint;
13
14 public BlockJig(BlockReference br)
15 : base(br)
16 {
17 mCenterPt = br.Position;
18 }
19
20 protected override SamplerStatus Sampler(JigPrompts prompts)
21 {
22 JigPromptPointOptions jigOpts =
23 new JigPromptPointOptions();
24 jigOpts.UserInputControls =
25 (UserInputControls.Accept3dCoordinates
26 | UserInputControls.NoZeroResponseAccepted
27 | UserInputControls.NoNegativeResponseAccepted);
28
29 jigOpts.Message =
30 "\nEnter insert point: ";
31
32 PromptPointResult dres =
33 prompts.AcquirePoint(jigOpts);
34
35 if (mActualPoint == dres.Value)
36 {
37 return SamplerStatus.NoChange;
38 }
39 else
40 {
41 mActualPoint = dres.Value;
42 }
43 return SamplerStatus.OK;
44 }
45
46 protected override bool Update()
47 {
48 mCenterPt = mActualPoint;
49 try
50 {
51 ((BlockReference)Entity).Position = mCenterPt;
52 }
53 catch (System.Exception)
54 {
55 return false;
56 }
57 return true;
58 }
59
60 public Entity GetEntity()
61 {
62 return Entity;
63 }
64 }
65
66 public class Commands
67 {
68 [CommandMethod("BJIG")]
69 public void CreateBlockWithJig()
70 {
71 Document doc =
72 Application.DocumentManager.MdiActiveDocument;
73 Database db = doc.Database;
74 Editor ed = doc.Editor;
75
76 // First let's get the name of the block
77 PromptStringOptions opts =
78 new PromptStringOptions("\nEnter block name: ");
79 PromptResult pr = ed.GetString(opts);
80 if (pr.Status == PromptStatus.OK)
81 {
82 Transaction tr =
83 doc.TransactionManager.StartTransaction();
84 using (tr)
85 {
86 // Then open the block table and check the
87 // block definition exists
88 BlockTable bt =
89 (BlockTable)tr.GetObject(
90 db.BlockTableId,
91 OpenMode.ForRead
92 );
93 if (!bt.Has(pr.StringResult))
94 {
95 ed.WriteMessage("\nBlock not found.");
96 }
97 else
98 {
99 ObjectId bdId = bt[pr.StringResult];
100
101 // We loop until the jig is cancelled
102 while (pr.Status == PromptStatus.OK)
103 {
104 // Create the block reference and
105 // add it to the jig
106 Point3d pt = new Point3d(0, 0, 0);
107 BlockReference br =
108 new BlockReference(pt, bdId);
109
110 BlockJig entJig = new BlockJig(br);
111
112 // Perform the jig operation
113 pr = ed.Drag(entJig);
114 if (pr.Status == PromptStatus.OK)
115 {
116 // If all is OK, let's go and add the
117 // entity to the modelspace
118 BlockTableRecord ms =
119 (BlockTableRecord)tr.GetObject(
120 bt[BlockTableRecord.ModelSpace],
121 OpenMode.ForWrite
122 );
123 ms.AppendEntity(
124 entJig.GetEntity()
125 );
126 tr.AddNewlyCreatedDBObject(
127 entJig.GetEntity(),
128 true
129 );
130
131 // Start attrib/annot-scale support code
132 BlockTableRecord bd =
133 (BlockTableRecord)tr.GetObject(
134 bdId,
135 OpenMode.ForRead
136 );
137 if (bd.Annotative == AnnotativeStates.True)
138 {
139 ObjectContextManager ocm =
140 db.ObjectContextManager;
141 ObjectContextCollection occ =
142 ocm.GetContextCollection(
143 "ACDB_ANNOTATIONSCALES"
144 );
145 ObjectContexts.AddContext(
146 br,
147 occ.CurrentContext
148 );
149 }
150
151 // Add the attributes
152 foreach (ObjectId attId in bd)
153 {
154 Entity ent =
155 (Entity)tr.GetObject(
156 attId,
157 OpenMode.ForRead
158 );
159 if (ent is AttributeDefinition)
160 {
161 AttributeDefinition ad =
162 (AttributeDefinition)ent;
163 AttributeReference ar =
164 new AttributeReference();
165 ar.SetAttributeFromBlock(
166 ad,
167 br.BlockTransform
168 );
169 br.AttributeCollection.AppendAttribute(ar);
170 tr.AddNewlyCreatedDBObject(ar, true);
171 }
172 }
173 // End attrib/annot-scale su

Atom
