TheSwamp
Code Red => .NET => Topic started by: Jeff H on May 29, 2011, 09:54:12 PM
-
Does anyone have a clean way of adding a entry to the Extension Dictionary of all objects added to a drawing?
Before I get too deep in this I was seeing if you guys had any suggestions and ideas.
Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.
Added the second if statement because with a empty drawing when the first entity was added a 'RegAppTableRecord' object caused it to fail
and a 'DimStyleTableRecord' would cause it to fail in AddUserName() method when casting it to a DBobject.--(Now cast to a Entity)
void Database_ObjectAppended(object sender, ObjectEventArgs e)
{
if (!cmdFlag)
return;
if (e.DBObject.ObjectId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Entity))))
appendedIds.Add(e.DBObject.ObjectId);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
namespace HpadStudentCad
{
public class Loader : IExtensionApplication
{
//// Collection to hold open documents
static Dictionary<Document, DocMgr> docs;
DocumentCollection documents;
public void Initialize()
{
documents = Application.DocumentManager;
docs = new Dictionary<Document,DocMgr>();
///// Add all open documents to collection and while adding
///// Create New DocMgr Instance
foreach (Document doc in documents)
{
docs.Add(doc, new DocMgr(doc));
}
//// Add Handlers for Drawings
documents.DocumentActivated += new DocumentCollectionEventHandler(documents_DocumentActivated);
documents.DocumentToBeDestroyed += new DocumentCollectionEventHandler(documents_DocumentToBeDestroyed);
}
//// If a document becomes active and has not been added to collection then add it.
void documents_DocumentActivated(object sender, DocumentCollectionEventArgs e)
{
if (!(docs.ContainsKey(e.Document)))
{
docs.Add(e.Document, new DocMgr(e.Document));
}
}
//// If a document is closed then remove it from the collection.
void documents_DocumentToBeDestroyed(object sender, DocumentCollectionEventArgs e)
{
if (docs.ContainsKey(e.Document))
{
docs[e.Document].DocDestroy();
docs.Remove(e.Document);
}
}
public void Terminate()
{
documents.DocumentActivated -= new DocumentCollectionEventHandler(documents_DocumentActivated);
documents.DocumentToBeDestroyed -= new DocumentCollectionEventHandler(documents_DocumentToBeDestroyed);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////HELPER CLASS FOR DOCUMENTS////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
public class DocMgr
{
private Document doc;
private Database db;
private static string userName;
private List<ObjectId> appendedIds;
private const string DICT_NAME = "HpadStudentDictionary";
private const string USER_NAME_ENTRY = "HpadStudentUserName";
private bool cmdFlag;
static DocMgr()
{
userName = Environment.UserName;
}
public DocMgr(Document document)
{
appendedIds = new List<ObjectId>();
cmdFlag = false;
doc = document;
db = doc.Database;
db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
db.ObjectModified += new ObjectEventHandler(Database_ObjectModified);
doc.CommandWillStart += new CommandEventHandler(doc_CommandWillStart);
doc.CommandEnded += new CommandEventHandler(doc_CommandEnded);
}
void doc_CommandWillStart(object sender, CommandEventArgs e)
{
cmdFlag = true;
}
void doc_CommandEnded(object sender, CommandEventArgs e)
{
if (!cmdFlag) return;
foreach (ObjectId id in appendedIds)
{
AddUserName(id);
}
cmdFlag = false;
appendedIds.Clear();
}
void Database_ObjectModified(object sender, ObjectEventArgs e)
{
}
void Database_ObjectAppended(object sender, ObjectEventArgs e)
{
if (!cmdFlag)
return;
if (e.DBObject.ObjectId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Entity))))
appendedIds.Add(e.DBObject.ObjectId);
}
private void AddUserName(ObjectId id)
{
using (Transaction trx = db.TransactionManager.StartTransaction())
{
Entity ent = trx.GetObject(id, OpenMode.ForRead) as Entity;
if (ent == null) return;
try
{
if (ent.ExtensionDictionary == ObjectId.Null)
{
ent.UpgradeOpen();
ent.CreateExtensionDictionary();
}
DBDictionary studentDic;
DBDictionary extDic = (DBDictionary)trx.GetObject(ent.ExtensionDictionary, OpenMode.ForRead);
if (!extDic.Contains(DICT_NAME))
{
extDic.UpgradeOpen();
studentDic = new DBDictionary();
Xrecord xr = new Xrecord();
xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));
extDic.SetAt(DICT_NAME, studentDic);
studentDic.SetAt(USER_NAME_ENTRY, xr);
}
}
catch (System.Exception)
{
}
trx.Commit();
}
}
public void DocDestroy()
{
doc.Database.ObjectAppended -= new ObjectEventHandler(Database_ObjectAppended);
doc.Database.ObjectModified -= new ObjectEventHandler(Database_ObjectModified);
doc.CommandEnded -= new CommandEventHandler(doc_CommandEnded);
doc.CommandWillStart -= new CommandEventHandler(doc_CommandWillStart);
}
}
}
-
Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.
Can't confirm:
--The blocks aren't empty, but the UI radio button "Convert to block" isn't honoured
--Dimensions are ok
Observations:
I think you will need to take the CommandCancelled/CommandFailed events into account.
You should be able to run the complete list through the same transaction.
type CmdClass() =
let doc = Application.DocumentManager.MdiActiveDocument
let db = doc.Database
let ed = doc.Editor
let DICT_NAME = "HpadStudentDictionary"
let USER_NAME_ENTRY = "HpadStudentUserName"
let userName = System.Environment.UserName
let mutable isRunning = false
let appendedIds = new ResizeArray<ObjectId>()
let addUserName objs =
use tr = db.TransactionManager.StartTransaction()
for oid in objs do
match tr.GetObject(oid, OpenMode.ForRead) with
| :? Entity as ent ->
if ent.ExtensionDictionary = ObjectId.Null then
ent.UpgradeOpen()
ent.CreateExtensionDictionary()
let extDic = tr.GetObject(ent.ExtensionDictionary, OpenMode.ForRead) :?> DBDictionary
if not(extDic.Contains DICT_NAME) then
extDic.UpgradeOpen()
let studentDic = new DBDictionary()
let xr = new Xrecord()
xr.Data <- new ResultBuffer(new TypedValue(int DxfCode.Text, userName))
extDic.SetAt(DICT_NAME, studentDic) |> ignore
studentDic.SetAt(USER_NAME_ENTRY, xr) |> ignore
| _ -> ()
tr.Commit()
let objectAppended =
new ObjectEventHandler(
fun _ e ->
let oid = e.DBObject.ObjectId
if oid.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof<Entity>)) &&
not(appendedIds.Contains oid) then
appendedIds.Add oid )
let commandEnded =
new CommandEventHandler(
fun _ e ->
addUserName appendedIds
appendedIds.Clear() )
let commandCancelled =
new CommandEventHandler(
fun _ e ->
appendedIds.Clear() )
let commandFailed =
new CommandEventHandler(
fun _ e ->
appendedIds.Clear() )
[<CommandMethod "Foo1">]
member __.Foo1() =
if not isRunning then
isRunning <- true
db.ObjectAppended.AddHandler objectAppended
doc.CommandEnded.AddHandler commandEnded
doc.CommandCancelled.AddHandler commandCancelled
doc.CommandFailed.AddHandler commandFailed
[<CommandMethod "Foo0">]
member __.Foo0() =
if isRunning then
isRunning <- false
db.ObjectAppended.RemoveHandler objectAppended
doc.CommandEnded.RemoveHandler commandEnded
doc.CommandCancelled.RemoveHandler commandCancelled
doc.CommandFailed.RemoveHandler commandFailed
-
Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.
Can't confirm:
--The blocks aren't empty, but the UI radio button "Convert to block" isn't honoured
--Dimensions are ok
Can confirm: Especially bad with -BLOCK, its command line version. Bombs with eWasOpenForWrite...
-
Got a little better
Stepping through adding a dimension which adds a anonymous block there are 24 objects for it add and I not quite sure what AutoCAD is doing but it is maybe doing something like building the dimension with objects for some reason then deleteing them. There a number of lines solids that were erased.
So checking for ObjectId.IsErased or I was using IsEffectivelyErased
void doc_CommandEnded(object sender, CommandEventArgs e)
{
if (!cmdFlag) return;
using (Transaction trx = db.TransactionManager.StartTransaction())
{
foreach (ObjectId id in appendedIds)
{
if (id.IsEffectivelyErased) continue;
AddUserName(id, trx);
}
trx.Commit();
}
cmdFlag = false;
appendedIds.Clear();
}
If you use the block command and build a block all that is returned are 2 objects AcdbBeginBlock and AcDbEndBlock and no entites are saved.
If you enter BE and build the block in the block editor then it saves it and works.
But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned
Changes in Red
public DocMgr(Document document)
{
appendedIds = new List<ObjectId>();
cmdFlag = false;
doc = document;
db = doc.Database;
[color=red] using (Transaction trx = db.TransactionManager.StartTransaction())
{
RegAppTable regTbl = (RegAppTable)trx.GetObject(db.RegAppTableId, OpenMode.ForWrite, false);
RegAppTableRecord regTblRec = new RegAppTableRecord();
regTblRec.Name = "HpadStudentUserName";
regTbl.Add(regTblRec);
trx.AddNewlyCreatedDBObject(regTblRec, true);
trx.Commit();
}[/color]
db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
db.ObjectModified += new ObjectEventHandler(Database_ObjectModified);
doc.CommandWillStart += new CommandEventHandler(doc_CommandWillStart);
doc.CommandEnded += new CommandEventHandler(doc_CommandEnded);
}
private void AddUserName(ObjectId id, Transaction trx)
{
[color=green] //using (Transaction trx = db.TransactionManager.StartTransaction())
//{[/color]
[color=red] try
{
Entity ent = trx.GetObject(id, OpenMode.ForRead) as Entity;
if (ent == null) return;
ent.UpgradeOpen();
ResultBuffer rb;
rb = ent.XData;
if (rb == null)
{
ent.XData = new ResultBuffer(new TypedValue(1001, "HpadStudentUserName"), new TypedValue(1000, userName));
}[/color]
[color=green] //if (ent.ExtensionDictionary == ObjectId.Null)
//{
// ent.UpgradeOpen();
// ent.CreateExtensionDictionary();
//}
//DBDictionary studentDic;
//DBDictionary extDic = (DBDictionary)trx.GetObject(ent.ExtensionDictionary, OpenMode.ForRead);
//if (!extDic.Contains(DICT_NAME))
//{
// extDic.UpgradeOpen();
// studentDic = new DBDictionary();
// Xrecord xr = new Xrecord();
// xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));
// extDic.SetAt(DICT_NAME, studentDic);
// studentDic.SetAt(USER_NAME_ENTRY, xr);
//}[/color]
}
catch (System.Exception)
{
}
//trx.Commit();
//}
}
-
You should do something in the empty catch block, like a log message or some dummy code you can set a breakpoint on when in debug mode. As it is now, if an exception is thrown you won't know anything about it.
-
Got a little better
You weren't seriously ill, were you?
But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned
Funny things, those extension dictionaries. I would have thought that listening to the combination of Database.WblockNotice and Database.BeginSave might produce an alternative solution to your original approach, but no such luck.
-
Got a little better
You weren't seriously ill, were you?
But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned
Funny things, those extension dictionaries. I would have thought that listening to the combination of Database.WblockNotice and Database.BeginSave might produce an alternative solution to your original approach, but no such luck.
No wasn't ill but thanks.
-
Thanks to Philippe Leefsma at ADN,
Looks like this took care of the issues.
All handled in ObjectAppended
public class DocMgr
{
private Document doc;
private Database db;
private static string userName;
private List<ObjectId> appendedIds;
private const string DICT_NAME = "HpadStudentDictionary";
private const string USER_NAME_ENTRY = "HpadStudentUserName";
private bool cmdFlag;
static DocMgr()
{
userName = Environment.UserName;
}
public DocMgr(Document document)
{
doc = document;
db = doc.Database;
db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
}
void Database_ObjectAppended(object sender, ObjectEventArgs e)
{
using (Transaction trx = db.TransactionManager.StartOpenCloseTransaction())
{
try
{
DBObject dbObj = e.DBObject;
if (dbObj == null)
return;
if (dbObj is DBDictionary || dbObj is Xrecord)
return;
if (dbObj.ObjectId.IsEffectivelyErased)
return;
if (dbObj.ExtensionDictionary == ObjectId.Null)
{
dbObj.UpgradeOpen();
dbObj.CreateExtensionDictionary();
}
DBDictionary studentDic;
DBDictionary extDic = (DBDictionary)trx.GetObject(dbObj.ExtensionDictionary, OpenMode.ForRead);
if (!extDic.Contains(DICT_NAME))
{
extDic.UpgradeOpen();
studentDic = new DBDictionary();
Xrecord xr = new Xrecord();
xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));
extDic.SetAt(DICT_NAME, studentDic);
studentDic.SetAt(USER_NAME_ENTRY, xr);
trx.AddNewlyCreatedDBObject(xr, true);
trx.AddNewlyCreatedDBObject(studentDic, true);
}
trx.Commit();
}
catch (System.Exception ex)
{
}
}
}
public void DocDestroy()
{
doc.Database.ObjectAppended -= new ObjectEventHandler(Database_ObjectAppended);
}
}
-
Thanks to Philippe Leefsma at ADN,
Looks like this took care of the issues.
I think the crucial differences are
1. that there were missing calls to Transaction.AddNewlyCreatedDBObject and
2. missing tests for DBDictionary and Xrecord to avoid unwanted recursion.
All handled in ObjectAppended
You aren't getting any eHadMultipleReaders exceptions when you open the DBObject directly in the ObjectAppended handler? That's why I think that it's still necessary to collect the ObjectIds and act on them when the command ends.
Retrieving nested DBDictionaries and Xrecords struck me as very similar, so I've been playing with generalizing it. It's pretty useless for timestamping only objects appended, but would allow for ObjectModified too.
type ObjectId with
member oid.IsType objType =
RXClass.GetClass objType |> oid.ObjectClass.IsDerivedFrom
type CmdClass() =
let doc = Application.DocumentManager.MdiActiveDocument
let db = doc.Database
let ed = doc.Editor
let DICT_NAME = "StudentDictionary"
let USER_NAME_ENTRY = "StudentUserName"
let userName = System.Environment.UserName
let mutable isRunning = false
let appendedIds = new ObjectIdCollection()
let addUserName (objs: ObjectIdCollection) =
use tr = db.TransactionManager.StartTransaction()
let openContainer (dictName: string) createContainer (dbDic : DBDictionary) =
if dbDic.Contains dictName then
tr.GetObject(dbDic.GetAt dictName, OpenMode.ForWrite) :?> _
else
let newContainer = createContainer()
dbDic.SetAt(dictName, newContainer) |> ignore
tr.AddNewlyCreatedDBObject(newContainer, true)
newContainer
for oid in objs do
if not oid.IsNull && not oid.IsEffectivelyErased then
try
let dbObj = tr.GetObject(oid, OpenMode.ForRead)
if dbObj.ExtensionDictionary.IsNull then
dbObj.UpgradeOpen()
dbObj.CreateExtensionDictionary()
tr.GetObject(dbObj.ExtensionDictionary, OpenMode.ForWrite) :?> DBDictionary
|> openContainer DICT_NAME (fun () -> new DBDictionary())
|> openContainer USER_NAME_ENTRY (fun () -> new DBDictionary())
|> openContainer System.Environment.UserName (fun () -> new Xrecord())
|> fun xr ->
xr.Data <-
new ResultBuffer[|
new TypedValue(
int DxfCode.Text,
System.DateTime.Now.ToString "yyyy-MM-dd HH:mm:ssK") |]
with
ex ->
ed.WriteMessage("\n{0}\n{1} ", ex.Message, ex.StackTrace)
tr.Commit()
let objectAppended =
new ObjectEventHandler(
fun _ e ->
let oid = e.DBObject.ObjectId
if not(appendedIds.Contains oid) &&
not(oid.IsType typeof<DBDictionary>) &&
not(oid.IsType typeof<Xrecord>) then appendedIds.Add oid |> ignore )
let commandEnded =
new CommandEventHandler(
fun _ _ ->
addUserName appendedIds
appendedIds.Clear() )
let commandCancelledOrFailed =
new CommandEventHandler(
fun _ _ ->
appendedIds.Clear() )
[<CommandMethod "Foo1">]
member __.Foo1() =
if not isRunning then
isRunning <- true
db.ObjectAppended.AddHandler objectAppended
doc.CommandEnded.AddHandler commandEnded
doc.CommandCancelled.AddHandler commandCancelledOrFailed
doc.CommandFailed.AddHandler commandCancelledOrFailed
[<CommandMethod "Foo0">]
member __.Foo0() =
if isRunning then
isRunning <- false
db.ObjectAppended.RemoveHandler objectAppended
doc.CommandEnded.RemoveHandler commandEnded
doc.CommandCancelled.RemoveHandler commandCancelledOrFailed
doc.CommandFailed.RemoveHandler commandCancelledOrFailed
-
I think the crucial differences are
1. that there were missing calls to Transaction.AddNewlyCreatedDBObject and
2. missing tests for DBDictionary and Xrecord to avoid unwanted recursion.
I agree
You aren't getting any eHadMultipleReaders exceptions when you open the DBObject directly in the ObjectAppended handler?
Not as long as I use
Database.TransactionManager.StartOpenCloseTransaction()