In the last post we looked at how to add a new annotative scale to an AutoCAD drawing.
In this post we'll look at what's needed to make an object annotative - providing it's of a type that supports annotation scaling, of course. Once again, this post is based on functionality introduced in AutoCAD 2008.
I hit my head against the problem for a while, having tried my best to convert the technique shown in the AnnotationScaling ObjectARX sample (which uses a protocol extension to access objects stored in an annotative entity's extension dictionary) to .NET. I finally ended up asking our Engineering team: Ravi Pothineni came back with some code that uses an internal assembly (but one that ships with AutoCAD) to do this. Apparently this will become standard functionality in a future release of AutoCAD - it'll just be available directly from DBObject, rather than being in a separate "helper" - but in the meantime you will have to use slightly more cumbersome code, such as that shown below, and add a reference to AcMgdInternal.dll for it to work. As indicated by the name, this functionality is unsupported and to be used at your own risk.
Here's the C# code - the "ADS" command is basically the one shown previously (renamed from "AS") and the "ATS" function is the new command that makes an object annotative, attaching annotation scales to it:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Internal;
namespace AnnotationScaling
{
public class Commands
{
[CommandMethod("ADS")]
static public void addScale()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
try
{
ObjectContextManager ocm =
db.ObjectContextManager;
if (ocm != null)
{
// Now get the Annotation Scaling context collection
// (named ACDB_ANNOTATIONSCALES_COLLECTION)
ObjectContextCollection occ =
ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
if (occ != null)
{
// Create a brand new scale context
AnnotationScale asc = new AnnotationScale();
asc.Name = "MyScale 1:28";
asc.PaperUnits = 1;
asc.DrawingUnits = 28;
// Add it to the drawing's context collection
occ.AddContext(asc);
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
[CommandMethod("ATS")]
static public void attachScale()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ObjectContextManager ocm =
db.ObjectContextManager;
ObjectContextCollection occ =
ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
PromptEntityOptions opts =
new PromptEntityOptions("\nSelect entity: ");
opts.SetRejectMessage(
"\nEntity must support annotation scaling."
);
opts.AddAllowedClass(typeof(DBText), false);
opts.AddAllowedClass(typeof(MText), false);
opts.AddAllowedClass(typeof(Dimension), false);
opts.AddAllowedClass(typeof(Leader), false);
opts.AddAllowedClass(typeof(Hatch), false);
PromptEntityResult per = ed.GetEntity(opts);
if (per.ObjectId != ObjectId.Null)
{
DBObject obj =
tr.GetObject(per.ObjectId, OpenMode.ForRead);
if (obj != null)
{
obj.UpgradeOpen();
obj.Annotative = AnnotativeStates.True;
ObjectContexts.AddContext(obj, occ.GetContext("1:1"));
ObjectContexts.AddContext(obj, occ.GetContext("1:2"));
ObjectContexts.AddContext(obj, occ.GetContext("1:10"));
ObjectContext oc = occ.GetContext("MyScale 1:28");
if (oc != null)
{
ObjectContexts.AddContext(obj, oc);
}
}
}
tr.Commit();
}
}
}
}
You'll notice that if you run the ADS command before ATS, you'll also get the newly-added annotation scale in the selected object's list:
Update:
From AutoCAD 2009 onwards, it's now possible to modify annotation scales on an object directly without relying on unsupported funcitonality in acmgdinternal.dll.
Here's the modified C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace AnnotationScaling
{
public class Commands
{
[CommandMethod("ADS")]
static public void addScale()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
try
{
ObjectContextManager ocm =
db.ObjectContextManager;
if (ocm != null)
{
// Now get the Annotation Scaling context collection
// (named ACDB_ANNOTATIONSCALES_COLLECTION)
ObjectContextCollection occ =
ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
if (occ != null)
{
// Create a brand new scale context
AnnotationScale asc = new AnnotationScale();
asc.Name = "MyScale 1:28";
asc.PaperUnits = 1;
asc.DrawingUnits = 28;
// Add it to the drawing's context collection
occ.AddContext(asc);
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
[CommandMethod("ATS")]
static public void attachScale()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ObjectContextManager ocm =
db.ObjectContextManager;
ObjectContextCollection occ =
ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
PromptEntityOptions opts =
new PromptEntityOptions("\nSelect entity: ");
opts.SetRejectMessage(
"\nEntity must support annotation scaling."
);
opts.AddAllowedClass(typeof(DBText), false);
opts.AddAllowedClass(typeof(MText), false);
opts.AddAllowedClass(typeof(Dimension), false);
opts.AddAllowedClass(typeof(Leader), false);
opts.AddAllowedClass(typeof(Table), false);
opts.AddAllowedClass(typeof(Hatch), false);
PromptEntityResult per = ed.GetEntity(opts);
if (per.ObjectId != ObjectId.Null)
{
DBObject obj =
tr.GetObject(per.ObjectId, OpenMode.ForRead);
if (obj != null)
{
obj.UpgradeOpen();
obj.Annotative = AnnotativeStates.True;
obj.AddContext(occ.GetContext("1:1"));
obj.AddContext(occ.GetContext("1:2"));
obj.AddContext(occ.GetContext("1:10"));
ObjectContext oc = occ.GetContext("MyScale 1:28");
if (oc != null)
{
obj.AddContext(oc);
}
}
}
tr.Commit();
}
}
}
}


Subscribe via RSS
Kean, interesting!
I see a new type of context: object context. Can you describe it?
Posted by: Nikolay Poleshchuk | April 28, 2007 at 06:57 AM
Hi Nikolay,
All I have is what's in the ObjectARX Reference, describing the AcDbObjectContext class:
>>>
Abstract base class for representing a particular context which may affect the properties and/or behavior of one or more types of objects.Classes that are derived from this base class are used to represent a particular type of context such as annotation scale.
<<<
Regards,
Kean
Posted by: Kean | April 29, 2007 at 05:03 AM
Hi Kean, thank you for this code.
Now I tried your code but it didn't work for BlockReferences, you get an "eIvalidInput" error when you try it. It would be nice if you could tell us how to do it for BlockReferences.
Also if you want to see the result for dimensions you have to do something more. I don't know if it is the right way, but I inserted the line "obj.UpgradeOpen()" after adding the context.
Posted by: Roland Feletic | May 02, 2007 at 06:47 PM
Hi Roland,
OK - my mistake. I should not allow selection of BlockReferences in the above code (I'll remove it). Here's what's said in the online help:
>>>
Annotative block definitions create annotative block references. Annotative block references and attributes initially support the current annotation scale at the time they are inserted. You should insert annotative block references with a unit factor of 1.
You cannot change the Annotative property of individual block references.
<<<
Regards,
Kean
Posted by: Kean | May 04, 2007 at 02:26 AM
I removed the lines allowing selection of BlockReference and AttributeReference objects from the above code.
About the dimension issue - it seems to work fine for me (I just used DIM VERT to create a dimension, used ATS to make it annotative, and I see the glyph when I hover over it and I get the appropriate annotation properties in the properties palette).
Any hints on what do I need to do to reproduce the problem?
Regards,
Kean
Posted by: Kean | May 04, 2007 at 02:40 AM
Kean, I think that table object does not support annotative scale too.
Posted by: Nikolay Poleshchuk | May 04, 2007 at 04:41 AM
Hi Kean,
thank you again. If I want to use a block with different scales, what do I have to do then?
I will have a look at the dimension problem on monday when i'm back to work.
Posted by: Roland Feletic | May 04, 2007 at 05:09 PM
Hi Roland,
You need to create annotative block definitions. This is from the online help:
>>>
To create an annotative block definition
1. Click Draw menu -> Block -> Make.... or At the command prompt, enter block.
2. In the Block Definition dialog box, enter a block name in the Name box.
3. Under Objects, select Convert to Block.
4. Click Select Objects.
5. Under Behavior, select Annotative.
6. Use your pointing device to select objects to be included in block definition. Press ENTER to complete object selection.
7. In the Block Definition dialog box, under Base Point, specify the block insertion point.
8. Click OK.
<<<
Then when you insert the block, it will pivk up the current annotation scale:
>>>
Annotative block definitions create annotative block references. Annotative block references and attributes initially support the current annotation scale at the time they are inserted. You should insert annotative block references with a unit factor of 1.
<<<
Regards,
Kean
Posted by: Kean | May 07, 2007 at 08:33 AM
Hi Nikolay,
I think you're right... it was listed as annotative in a version of the docs I looked at, but I don't see it as being annotative. I'll remove the option to select a table from the code.
Thanks,
Kean
Posted by: Kean | May 07, 2007 at 08:36 AM
Hi Kean,
I know how to make an annotative block ;-) I'm testing spago since the alpha release.
The problem i have is to insert an external drawing as a block with .NET (and VBA, also in VBA it inserts an annotative DWG as non annotative).
1. How do I now if the drawing is annotative or not?
2. How do I insert the drawing as a block and make it annotative?
3. And if i insert the annotative block, is it possible to insert it for more annotative scales (eg. inserting the block for annotation scale "1:100" and "1:50")?
Now the problem with the dimensions. It doesn' matter how I do it, I have to do it like this to see the annotation-scales for a dimension.
if (oc != null)
{
ObjectContexts.AddContext(obj, oc);
}
obj.UpgradeOpen();
Regards
Roland
Posted by: Roland Feletic | May 07, 2007 at 09:40 AM
Hi Roland,
I'm afraid I'm no mind reader - your previous descriptions were missing a lot of information. Even now I have no idea how you are inserting the external DWG programmatically.
Also, this is going off-topic, in that it's only tangentially related to the blog post. I'd suggest submitting the question - with code and a DWG - to my team via the ADN website or posting it on the Discussion Groups.
As for the dimension problem - on my system I don't need to make that change to my code - the object is already open for write, and it works just fine. Have you modified other parts of the code?
Regards,
Kean
Posted by: Kean | May 07, 2007 at 10:06 AM
Hi Kean,
thank you for your response, I will post my annotative block problems in the discussion group.
As for the dimension problem I didn't change your code (just deleted the lines for adding 1:2 and 1:10), but it is no problem, it works when I add the line like in my previous post.
Regards
Roland
Posted by: Roland Feletic | May 07, 2007 at 06:17 PM
Hi Kean,
I tested a little bit with inserting an annotative block into the drawing and it didn't work as the help said. It didn't insert the block at the current annotative scale, just the attributes where inserted at the current annotation-scale.
Therefore i tried to add the context and it did work.
Here is the code for inserting an annotative block which is working for me if someone want to know.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Internal;
[assembly: CommandClass(typeof(RSNNAcadApp.Test.InsertBlock))]
namespace RSNNAcadApp.Test
{
public class InsertBlock
{
//Inserts a blockreference at Point 0,0,0
[CommandMethod("InsertTest")]
static public void InsertBlockTest()
{
ObjectId tmpBlockId;
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
Transaction tr = doc.TransactionManager.StartTransaction();
try
{
using (tr)
{
//Get Blockname and Id
PromptStringOptions BlockNameOption = new PromptStringOptions("Blockname:");
BlockNameOption.AllowSpaces = false;
PromptResult BlockNameResult = ed.GetString(BlockNameOption);
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead, false);
if (BlockNameResult.Status == PromptStatus.Cancel)
return;
else if (BlockNameResult.Status == PromptStatus.OK)
{
string tmpBlockName = BlockNameResult.StringResult;
tmpBlockId = bt[tmpBlockName];
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
Point3d ObjPunkt = Point3d.Origin;
BlockReference BlockRef = new BlockReference(ObjPunkt, tmpBlockId);
ObjectId BlObj = btr.AppendEntity(BlockRef);
tr.AddNewlyCreatedDBObject(BlockRef, true);
BlockTableRecord btAttRec = (BlockTableRecord)tr.GetObject(tmpBlockId, OpenMode.ForRead);
ed.WriteMessage(string.Format("Block {0} annotative!", btAttRec.Annotative == AnnotativeStates.True ? "is" : "is not"));
//Attach Current Annotation-Scale.
//If you don't add the content the block and the following attribute will not inserted correct.
ObjectContextManager ocm = db.ObjectContextManager;
ObjectContextCollection occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
DBObject obj = tr.GetObject(BlObj, OpenMode.ForRead);
if (obj != null)
{
//ObjectContexts.AddContext(obj, occ.GetContext("1:1"));
ObjectContexts.AddContext(obj, occ.CurrentContext);
}
//Add the attributes
foreach (ObjectId idAtt in btAttRec)
{
Entity ent = (Entity)tr.GetObject(idAtt, OpenMode.ForRead);
if (ent is AttributeDefinition)
{
AttributeDefinition attDef = (AttributeDefinition)ent;
AttributeReference attRef = new AttributeReference();
attRef.SetAttributeFromBlock(attDef, BlockRef.BlockTransform);
ObjectId AttObj = BlockRef.AttributeCollection.AppendAttribute(attRef);
tr.AddNewlyCreatedDBObject(attRef, true);
/* //You do not need to insert the CurrentContext to AttributeReference because it is inserted with it.
DBObject aobj = tr.GetObject(AttObj, OpenMode.ForRead);
if (aobj != null)
{
//ObjectContexts.AddContext(aobj, occ.GetContext("1:1"));
ObjectContexts.AddContext(aobj, occ.CurrentContext);
}
*/
}
}
}
tr.Commit();
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
finally
{
tr.Dispose();
}
}
}
}
Regards
Roland
Posted by: Roland Feletic | May 09, 2007 at 01:44 PM
Hi Roland,
OK, *now* I see what you wanted to do... :-)
The help refers to the INSERT command, whereas you wanted to programmatically insert an annotative block. Yes - I can see why you'd have to do what you've done.
Thanks for sharing the code!
Kean
Posted by: Kean | May 10, 2007 at 02:50 PM
I know it is an old post, but I want to delete all annotation-scales which are not used. Is it possible to see if an AnnotationScale is used or not by any objects? The following code deletes all scales, it doesn't matter if they are used or not except the current annoscale and 1:1.
[CommandMethod("ASDELETE")]
public void DeleteAll()
{
try
{
ObjectContextManager ocm =
db.ObjectContextManager;
if (ocm != null)
{
// Now get the Annotation Scaling context collection
// (named ACDB_ANNOTATIONSCALES_COLLECTION)
ObjectContextCollection occ =
ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
if (occ != null)
{
foreach (ObjectContext oc in occ)
{
if (oc is AnnotationScale)
{
try
{
AnnotationScale scale = (AnnotationScale)oc;
if (scale.Name != "1:1")
if (oc.Name != occ.CurrentContext.Name)
{
ed.WriteMessage(string.Format("\n{0} wird gelöscht", scale.Name));
occ.RemoveContext(scale.Name);
}
}
catch (System.Exception e)
{
ed.WriteMessage(e.ToString());
}
}
}
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
Posted by: Roland Feletic | June 05, 2007 at 06:03 PM
Hi Roland,
I don't know how you'd do this: for something like a style you'd get the object ID and use the "purge" function on the database to check the references to it, but I don't know whether this would work for Annotation Scales (assuming you could get the object ID of the dictionary entry).
Regards,
Kean
Posted by: Kean | June 06, 2007 at 01:43 PM
Thank you, Kean,
I will have a look at it. But I also hope that in the next release it is possible to purge all the unused annotation-scales with AutoCAD.
Regards
Roland
Posted by: Roland Feletic | June 07, 2007 at 11:07 AM
Hi Kean
I try to add annotations to MTEXT objects with the COM interface and have no luck so far. I know they get stored somewhere inside AcadDictionary. If I parse an entity object with added annotation scales I get the same amount of entries of ObjectName "AcDbMTextObjectContextData" things. But there are no interfaces to read them out or to create such things. Any ideas?
The code I used to get the dictionary items looks as below:
AcadEntity entity = ...
if ( entity.HasExtensionDictionary )
{
AcadDictionary dict = entity.GetExtensionDictionary( );
DumpAcadDictionary( dict, "" );
}
private void DumpAcadDictionary( AcadDictionary dict, string offset )
{
for ( int i = 0; i < dict.Count; i++ )
{
AcadObject obj = dict.Item( i );
Console.WriteLine( "{0}Item: {1} {2}", offset, i, obj.ObjectName );
if ( obj.ObjectName == "AcDbDictionary" )
{
DumpAcadDictionary( ( AcadDictionary ) obj, offset + " " );
}
if ( obj.HasExtensionDictionary )
{
AcadDictionary subDict = obj.GetExtensionDictionary( );
DumpAcadDictionary( subDict, offset + " " );
}
}
if ( dict.HasExtensionDictionary )
{
AcadDictionary subDict = dict.GetExtensionDictionary( );
DumpAcadDictionary( subDict, offset + " " );
}
}
Posted by: Armin Müller | June 14, 2007 at 06:25 PM
Hi Armin,
I don't think you can rely on adding Annotation Scales manually into the extension dictionary: I'd suggest using the technique I've shown with the managed interface, instead. You mention COM, but seem to be coding in C#, so that shouldn't be a big issue (unless I'm missing something).
Regards,
Kean
Posted by: Kean | June 15, 2007 at 09:49 AM
Newbie question: How do you compile the above code for use in AutoCAD?
Posted by: David Dixon | June 21, 2007 at 04:53 PM
Hi David,
I'd suggest checking out this introductory post.
Regards,
Kean
Posted by: Kean | June 21, 2007 at 05:35 PM
Hi Kean,
i was asked to search for 5 examples of elevation, 3D animation and step by step procedure of gear making.. All of this were made using autocad.. do you have any of these? this is my project and i have seen already 3 samples of elevation and 3D animation but i still need 2 more each and the step by step gear making using autocad.. it's hard to find one and my time is limited only in searching.. can you help me?
Posted by: zyra joy | November 07, 2007 at 10:27 PM
Hi Zyra,
Are you looking for code samples or content samples? If content, you might try the Content Search tool on the Autodesk Labs website.
If content then I'd suggest posting something to the appropriate discussion group, depending on the technology you're interested in.
Regards,
Kean
Posted by: Kean | November 08, 2007 at 08:58 AM
Hi there Kean,
I got Visual Studio .NET 2008 that has a C# compiler.
I loaded your above code (for "Making AutoCAD objects annotative using .NET"), I referenced the ACDBMGD.DLL and ACMGD.DLL from AutoCAD 2008, and the .Internal in "using Autodesk.AutoCAD.Internal;" statement is missing. As such I was not able to compile the code. Can you please tell me what to do in order to compile your code?
Thanks in advance for any help.
Have a nice day and happy 4th of July.
Regards,
Stefan
Posted by: Stefan | July 03, 2008 at 05:31 PM
There should also be an assembly in the AutoCAD folder called acmgdinternal.dll. Be warned: the APIs exposed by this assembly are not officially supported.
Kean
Posted by: Kean | July 03, 2008 at 05:34 PM