您的位置:首页 > 其它

Updating a specific attribute inside a folder of AutoCAD drawings using .NET

2007-08-16 11:27 477 查看

In the last post we looked at some code to search the current drawing for a particular attribute and update its value. In this post - as promised - we're going to look at how to extend this application to work on a folder of drawings, updating those that contain the attribute and saving them to a new filename.

Rather than implement a fancy, graphical user interface, I've stuck with my approach of using the command-line for input and output. If you wish to implement your own UI, please do - it's really easy using .NET. I get the occasional request to do this myself, but I prefer to keep my posts focused - there are other posts on this blog that focus specifically on UI-related issues.

I ended up changing the code somewhat - I had been printing the results of the batching process from within the UpdateAttributesInDatabase() function. As I get closer to extracting the functionality to a RealDWG application, I've started reducing the dependencies on the Editor (as this will not be available later on). The WriteMessage() calls are now in the commands themselves, instead of in the processing functions.

In addition to the previous UA (UpdateAttribute) command, I've added a new one called UAIF (UpdateAttributeInFiles). This one queries for some additional data (such as the path to the folder to process), and then uses some handy .NET Framework functionality to iterate through the drawings stored in a particular location. Rather than overwrite the originals, the updated files get saved to a new name (with "-updated" added to the filename). It is left as an exercise to put these in a separate folder or to save back to the original location (I'd rather not get blamed for overwriting valuable data :-).

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.IO;
using System;

namespace AttributeUpdater
{
public class Commands
{
[CommandMethod("UAIF")]
public void UpdateAttributeInFiles()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;

// Have the user choose the block and attribute
// names, and the new attribute value

PromptResult pr =
ed.GetString(
"/nEnter folder containing DWGs to process: "
);
if (pr.Status != PromptStatus.OK)
return;
string pathName = pr.StringResult;

pr =
ed.GetString(
"/nEnter name of block to search for: "
);
if (pr.Status != PromptStatus.OK)
return;
string blockName = pr.StringResult.ToUpper();

pr =
ed.GetString(
"/nEnter tag of attribute to update: "
);
if (pr.Status != PromptStatus.OK)
return;
string attbName = pr.StringResult.ToUpper();

pr =
ed.GetString(
"/nEnter new value for attribute: "
);
if (pr.Status != PromptStatus.OK)
return;
string attbValue = pr.StringResult;

string[] fileNames =
Directory.GetFiles(pathName,"*.dwg");

// We'll use some counters to keep track
// of how the processing is going

int processed = 0, saved = 0, problem = 0;

foreach (string fileName in fileNames)
{
if (fileName.EndsWith(
".dwg",
StringComparison.CurrentCultureIgnoreCase
)
)
{
string outputName =
fileName.Substring(
0,
fileName.Length - 4) +
"_updated.dwg";
Database db = new Database(false, false);
using (db)
{
try
{
ed.WriteMessage(
"/n/nProcessing file: " + fileName
);

db.ReadDwgFile(
fileName,
FileShare.ReadWrite,
false,
""
);
int attributesChanged =
UpdateAttributesInDatabase(
db,
blockName,
attbName,
attbValue
);

// Display the results

ed.WriteMessage(
"/nUpdated {0} instance{1} of " +
"attribute {2}.",
attributesChanged,
attributesChanged == 1 ? "" : "s",
attbName
);

// Only save if we changed something

if (attributesChanged > 0)
{
ed.WriteMessage(
"/nSaving to file: {0}", outputName
);

db.SaveAs(
outputName,
DwgVersion.Current
);
saved++;
}
processed++;
}
catch (System.Exception ex)
{
ed.WriteMessage(
"/nProblem processing file: {0} - /"{1}/"",
fileName,
ex.Message
);
problem++;
}
}
}
}
ed.WriteMessage(
"/n/nSuccessfully processed {0} files, of which {1} had " +
"attributes to update and an additional {2} had errors " +
"during reading/processing.",
processed,
saved,
problem
);
}

[CommandMethod("UA")]
public void UpdateAttribute()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;

// Have the user choose the block and attribute
// names, and the new attribute value

PromptResult pr =
ed.GetString(
"/nEnter name of block to search for: "
);
if (pr.Status != PromptStatus.OK)
return;
string blockName = pr.StringResult.ToUpper();

pr =
ed.GetString(
"/nEnter tag of attribute to update: "
);
if (pr.Status != PromptStatus.OK)
return;
string attbName = pr.StringResult.ToUpper();

pr =
ed.GetString(
"/nEnter new value for attribute: "
);
if (pr.Status != PromptStatus.OK)
return;
string attbValue = pr.StringResult;

ed.WriteMessage(
"/nProcessing file: " + db.Filename
);

int count =
UpdateAttributesInDatabase(
db,
blockName,
attbName,
attbValue
);

ed.Regen();

// Display the results

ed.WriteMessage(
"/nUpdated {0} instance{1} of " +
"attribute {2}.",
count,
count == 1 ? "" : "s",
attbName
);
}

private int UpdateAttributesInDatabase(
Database db,
string blockName,
string attbName,
string attbValue
)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;

// Get the IDs of the spaces we want to process
// and simply call a function to process each

ObjectId msId, psId;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
msId =
bt[BlockTableRecord.ModelSpace];
psId =
bt[BlockTableRecord.PaperSpace];

// Not needed, but quicker than aborting
tr.Commit();
}
int msCount =
UpdateAttributesInBlock(
msId,
blockName,
attbName,
attbValue
);
int psCount =
UpdateAttributesInBlock(
psId,
blockName,
attbName,
attbValue
);
return msCount + psCount;
}

private int UpdateAttributesInBlock(
ObjectId btrId,
string blockName,
string attbName,
string attbValue
)
{
// Will return the number of attributes modified

int changedCount = 0;
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;

Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
btrId,
OpenMode.ForRead
);

// Test each entity in the container...

foreach (ObjectId entId in btr)
{
Entity ent =
tr.GetObject(entId, OpenMode.ForRead)
as Entity;

if (ent != null)
{
BlockReference br = ent as BlockReference;
if (br != null)
{
BlockTableRecord bd =
(BlockTableRecord)tr.GetObject(
br.BlockTableRecord,
OpenMode.ForRead
);

// ... to see whether it's a block with
// the name we're after

if (bd.Name.ToUpper() == blockName)
{

// Check each of the attributes...

foreach (
ObjectId arId in br.AttributeCollection
)
{
DBObject obj =
tr.GetObject(
arId,
OpenMode.ForRead
);
AttributeReference ar =
obj as AttributeReference;
if (ar != null)
{
// ... to see whether it has
// the tag we're after

if (ar.Tag.ToUpper() == attbName)
{
// If so, update the value
// and increment the counter

ar.UpgradeOpen();
ar.TextString = attbValue;
ar.DowngradeOpen();
changedCount++;
}
}
}
}

// Recurse for nested blocks
changedCount +=
UpdateAttributesInBlock(
br.BlockTableRecord,
blockName,
attbName,
attbValue
);
}
}
}
tr.Commit();
}
return changedCount;
}
}
}

Here's what happens when I run it on my temp folder (which is full of crud, and that's putting it politely):

Command: UAIF
Enter folder containing DWGs to process: c:/temp
Enter name of block to search for: TEST
Enter tag of attribute to update: ONE
Enter new value for attribute: 1234

Processing file: c:/temp/-old_recover.dwg
Problem processing file: c:/temp/-old_recover.dwg - "eBadDwgHeader"

Processing file: c:/temp/4076612-2.DWG
Updated 0 instances of attribute ONE.

Processing file: c:/temp/attributes.dwg
Updated 5 instances of attribute ONE.
Saving to file: c:/temp/attributes_updated.dwg

[Deleted a lot of uninteresting reports]

Successfully processed 42 files, of which 1 had attributes to update and an
additional 3 had errors during reading/processing.

You'll see that some DWGs have failed to load: hopefully because they're in need of recovery rather than the workings of this code. The above technique should handle this gracefully, allowing you to go back and fix the problematic ones.

Update:

Norman Yuan pointed out a mistake in this code - I've been using the TransactionManager from the current document, rather than from the db. This isn't dramatically bad, but would have become more of a problem as we move to RealDWG. What I should really have done sooner is remove the doc and ed variables from the UpdateAttributesIn...() functions, so that I would not have been in a position to use them by mistake. I now pass in the Database as a parameter, which allows us to use it to get the appropriate TransactionManager. One other benefit this code enables is the choice of constructing the database without an associated document:

Database db = new Database(false, true);

This makes the code work more efficiently (as well as being cleaner).

Here are the updated functions - not the entire code:

private int UpdateAttributesInDatabase(
Database db,
string blockName,
string attbName,
string attbValue
)
{
// Get the IDs of the spaces we want to process
// and simply call a function to process each

ObjectId msId, psId;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
msId =
bt[BlockTableRecord.ModelSpace];
psId =
bt[BlockTableRecord.PaperSpace];

// Not needed, but quicker than aborting
tr.Commit();
}
int msCount =
UpdateAttributesInBlock(
db,
msId,
blockName,
attbName,
attbValue
);
int psCount =
UpdateAttributesInBlock(
db,
psId,
blockName,
attbName,
attbValue
);
return msCount + psCount;
}

private int UpdateAttributesInBlock(
Database db,
ObjectId btrId,
string blockName,
string attbName,
string attbValue
)
{
// Will return the number of attributes modified

int changedCount = 0;

Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
btrId,
OpenMode.ForRead
);

// Test each entity in the container...

foreach (ObjectId entId in btr)
{
Entity ent =
tr.GetObject(entId, OpenMode.ForRead)
as Entity;

if (ent != null)
{
BlockReference br = ent as BlockReference;
if (br != null)
{
BlockTableRecord bd =
(BlockTableRecord)tr.GetObject(
br.BlockTableRecord,
OpenMode.ForRead
);

// ... to see whether it's a block with
// the name we're after

if (bd.Name.ToUpper() == blockName)
{

// Check each of the attributes...

foreach (
ObjectId arId in br.AttributeCollection
)
{
DBObject obj =
tr.GetObject(
arId,
OpenMode.ForRead
);
AttributeReference ar =
obj as AttributeReference;
if (ar != null)
{
// ... to see whether it has
// the tag we're after

if (ar.Tag.ToUpper() == attbName)
{
// If so, update the value
// and increment the counter

ar.UpgradeOpen();
ar.TextString = attbValue;
ar.DowngradeOpen();
changedCount++;
}
}
}
}

// Recurse for nested blocks
changedCount +=
UpdateAttributesInBlock(
db,
br.BlockTableRecord,
blockName,
attbName,
attbValue
);
}
}
}
tr.Commit();
}
return changedCount;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐