Bonus Plugin
Refactorised code
Edit: the the matrix calculation in the BillboardAttributesOverrule.ViewportDraw() method have been re-written so that it works whatever the block insertion plane.
Edit 2: corrected code so that it works whatever the attribute text justificaion.
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
namespace BonusPluginCs
{
//public class ExtensionApplication : IExtensionApplication
//{
// public void Initialize()
// {
// Commands.ActivateOverrule();
// }
// public void Terminate() { }
//}
public class Commands
{
private static BillboardAttributesOverrule _overrule;
// Registered Application Id for Xdata
private const string regAppName = "ADSK_ATTRIBUTE_ZERO_OVERRULE";
[CommandMethod("BillboardAttributes")]
public static void ImplementOverrule()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// Select a block reference
PromptEntityResult res = GetBlkRef(ed);
if (res.Status != PromptStatus.OK)
return;
// Create and register our overrule and turn overruling on.
ActivateOverrule();
// Create new xdata containing the overrule filter name
using (ResultBuffer resBuf = new ResultBuffer(
new TypedValue((int)DxfCode.ExtendedDataRegAppName, regAppName),
new TypedValue((int)DxfCode.ExtendedDataAsciiString, "Dummy text")))
{
// Add the xdata
AddXdata(res.ObjectId, resBuf, db);
}
}
[CommandMethod("DontBillboardAttributes")]
public static void RemoveXdata()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// Select a block reference
PromptEntityResult res = GetBlkRef(ed);
if (res.Status != PromptStatus.OK)
return;
// Create new xdata containing the overrule filter name
using (ResultBuffer resBuf = new ResultBuffer(
new TypedValue((int)DxfCode.ExtendedDataRegAppName, regAppName)))
{
// Add the xdata
// Because we leave this blank except for the regappid,
// it erases any Xdata we previously added.
AddXdata(res.ObjectId, resBuf, db);
}
}
[CommandMethod("ActivateBillboardOverrule")]
public static void ActivateOverrule()
{
// We only want to create our overrule instance once,
// so we check if it already exists before we create it
// (i.e. this may be the 2nd time we've run the command)
if (_overrule == null)
{
// Instantiate our overrule class
_overrule = new BillboardAttributesOverrule();
// Register the overrule
Overrule.AddOverrule
(RXClass.GetClass(typeof(AttributeReference)),
_overrule, false);
}
// Specify which Attributes will be overruled
_overrule.SetXDataFilter(regAppName);
// Make sure overruling is turned on so our overrule works
Overrule.Overruling = true;
}
private static PromptEntityResult GetBlkRef(Editor ed)
{
PromptEntityOptions opts = new PromptEntityOptions("\nSelect a block reference: ");
opts.SetRejectMessage("\nMust be block reference...");
opts.AddAllowedClass(typeof(BlockReference), true);
return ed.GetEntity(opts);
}
private static void AddXdata(ObjectId id, ResultBuffer resBuf, Database db)
{
using (Transaction trans = db.TransactionManager.StartTransaction())
{
// First create our RegAppId (if it doesn't already exist)
RegAppTable appTbl =
(RegAppTable)trans.GetObject(db.RegAppTableId, OpenMode.ForRead);
if (!appTbl.Has(regAppName))
{
RegAppTableRecord appTblRec = new RegAppTableRecord();
appTblRec.Name = regAppName;
appTbl.UpgradeOpen();
appTbl.Add(appTblRec);
trans.AddNewlyCreatedDBObject(appTblRec, true);
}
// Open the BlockReference for read.
// We know its a BlockReference because we set a filter in
// our PromptEntityOptions.
BlockReference blkRef =
(BlockReference)trans.GetObject(id, OpenMode.ForRead);
// Record the ObjectIds of all AttributeReferences
// attached to the BlockReference.
AttributeCollection attRefColl = blkRef.AttributeCollection;
// Iterate through ObjectIds of all AttributeReferences
// attached to the BlockReference, opening each
// AttributeReference and adding xdata to it.
foreach (ObjectId objId in attRefColl)
{
AttributeReference attRef =
(AttributeReference)trans.GetObject(objId, OpenMode.ForWrite);
// Add the xdata
attRef.XData = resBuf;
}
trans.Commit();
}
}
}
// Our custom overrule class derived from TransformOverrule
public class BillboardAttributesOverrule : DrawableOverrule
{
// Returning False from WorldDraw tells AutoCAD this entity has
// viewport dependent graphics.
public override bool WorldDraw(Drawable drawable, WorldDraw wd)
{
return false;
}
// Called for each viewport so entity can draw itself differently
// depending on the view.
public override void ViewportDraw(Drawable drawable, ViewportDraw vd)
{
// Cast drawable to type AttributeReference (we know it's an
// AttributeReference because that's the only class we register our
// overrule for).
AttributeReference attRef = (AttributeReference)drawable;
// First calculate the transformation matrix to rotate from the
// BlockReference's current orientation to the view.
Point3d org = attRef.Justify == AttachmentPoint.BaseLeft ||
attRef.Justify == AttachmentPoint.BaseAlign ||
attRef.Justify == AttachmentPoint.BaseFit ?
attRef.Position : attRef.AlignmentPoint;
Matrix3d viewMat =
Matrix3d.PlaneToWorld(new Plane(org, vd.Viewport.ViewDirection)) *
Matrix3d.WorldToPlane(new Plane(org, attRef.Normal)) *
Matrix3d.Rotation(-attRef.Rotation, attRef.Normal, org);
// Apply the transformation
vd.Geometry.PushModelTransform(viewMat);
// Draw the 'per viewport geometry
base.ViewportDraw(drawable, vd);
// Remove the transformation - we don't want any other objects
// to draw themselves in the view plane.
vd.Geometry.PopModelTransform();
}
// This function tells AutoCAD to dynamically update the
// AttributeReference during view transitions (e.g. 3DORBIT).
// Comment it out to improve graphic update performance.
public override int SetAttributes(Drawable drawable, DrawableTraits traits)
{
return base.SetAttributes(drawable, traits) | (int)DrawableAttributes.ViewDependentViewportDraw;
}
}
}