.NET下防止AutoCAD块被炸开
<Preventing an AutoCAD block from being exploded using .NET>
In response to these recent posts, I received a comment from Nick:
By any chance would it be possible to provide an example to prevent a user from using the EXPLODE command for a given block name?
I delved into the ADN knowledgebase and came across this helpful ObjectARX DevNote, which I used to create a .NET module to address the above question.
Here's the C# code, which should contain enough comments to make it self-explanatory:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace ExplosionPrevention
{
public class Commands
{
private Document _doc;
private Database _db;
private ObjectIdCollection _blkDefs =
new ObjectIdCollection();
private ObjectIdCollection _blkRefs =
new ObjectIdCollection();
private ObjectIdCollection _blkConts =
new ObjectIdCollection();
private bool _handlers = false;
private bool _exploding = false;
[CommandMethod("STOPEX")]
public void StopBlockFromExploding()
{
_doc =
Application.DocumentManager.MdiActiveDocument;
_db = _doc.Database;
if (!_handlers)
{
AddEventHandlers();
_handlers = true;
}
// Get the name of the block to protect
PromptStringOptions pso =
new PromptStringOptions(
"\nEnter block name: "
);
pso.AllowSpaces = false;
PromptResult pr =
_doc.Editor.GetString(pso);
if (pr.Status != PromptStatus.OK)
return;
Transaction tr =
_db.TransactionManager.StartTransaction();
using (tr)
{
// Make sure the block definition exists
BlockTable bt =
(BlockTable)
tr.GetObject(
_db.BlockTableId,
OpenMode.ForRead
);
if (bt.Has(pr.StringResult))
{
// Collect information about the block...
// 1. the block definition
ObjectId blkId =
bt[pr.StringResult];
_blkDefs.Add(blkId);
BlockTableRecord btr =
(BlockTableRecord)
tr.GetObject(
blkId,
OpenMode.ForRead
);
// 2. the block's contents
foreach (ObjectId id in btr)
_blkConts.Add(id);
// 3. the block's references
ObjectIdCollection blkRefs =
btr.GetBlockReferenceIds(true, true);
foreach (ObjectId id in blkRefs)
_blkRefs.Add(id);
}
tr.Commit();
}
}
private void AddEventHandlers()
{
// When a block reference is added, we need to
// check whether it's for a block we care about
// and add it to the list, if so
_db.ObjectAppended +=
delegate(object sender, ObjectEventArgs e)
{
BlockReference br =
e.DBObject as BlockReference;
if (br != null)
{
if (_blkDefs.Contains(br.BlockTableRecord))
_blkRefs.Add(br.ObjectId);
}
};
// Conversely we need to remove block references
// that as they're erased
_db.ObjectErased +=
delegate(object sender, ObjectErasedEventArgs e)
{
// This is called during as part of the cloning
// process, so let's check that's not happening
if (!_exploding)
{
BlockReference br =
e.DBObject as BlockReference;
if (br != null)
{
// If we're erasing, remove this block
// reference from the list, otherwise if
// we're unerasing we will want to add it
// back in
if (e.Erased)
{
if (_blkRefs.Contains(br.ObjectId))
_blkRefs.Remove(br.ObjectId);
}
else
{
if (_blkDefs.Contains(br.BlockTableRecord))
_blkRefs.Add(br.ObjectId);
}
}
}
};
// This is where we fool AutoCAD into thinking the
// block contents have already been cloned
_db.BeginDeepClone +=
delegate(object sender, IdMappingEventArgs e)
{
// Only for the explode context
if (e.IdMapping.DeepCloneContext !=
DeepCloneType.Explode)
return;
// We add IDs to the map to stop the
// block contents from being cloned
foreach (ObjectId id in _blkConts)
e.IdMapping.Add(
new IdPair(id, id, true, true, true)
);
};
// And this is where we remove the mapping entries
_db.BeginDeepCloneTranslation +=
delegate(object sender, IdMappingEventArgs e)
{
// Only for the explode context
if (e.IdMapping.DeepCloneContext !=
DeepCloneType.Explode)
return;
// Set the flag for our CommandEnded handler
_exploding = true;
// Remove the entries we added on BeginDeepClone
foreach (ObjectId id in _blkConts)
e.IdMapping.Delete(id);
};
// As the command ends we unerase the block references
_doc.CommandEnded +=
delegate(object sender, CommandEventArgs e)
{
if (e.GlobalCommandName == "EXPLODE" && _exploding)
{
// By this point the block contents should not have
// been cloned, but the blocks have been erased
Transaction tr =
_db.TransactionManager.StartTransaction();
using (tr)
{
// So we need to unerase each of the erased
// block references
foreach (ObjectId id in _blkRefs)
{
DBObject obj =
tr.GetObject(
id,
OpenMode.ForRead,
true
);
// Only unerase it if it's needed
if (obj.IsErased)
{
obj.UpgradeOpen();
obj.Erase(false);
}
}
tr.Commit();
}
_exploding = false;
}
};
}
}
}
The STOPEX command takes a block name and then gathers (and stores) information about a block: its ObjectId, the IDs of its contents and its various block references. I've added some logic to handle creation of new block references (e.g. via INSERT), and erasure of ones that are no longer needed. I haven't put anything in to deal with redefinition of blocks (if the contents of blocks change then explosion may not be prevented properly), but this is left as an exercise for the reader
Let's define and insert a series of three blocks: LINES, ARCS and CIRCLES (no prizes for guessing which is which :-):
Now we run the STOPEX command on the LINES and CIRCLES blocks:
Command: STOPEX
Enter block name: circles
Command: STOPEX
Enter block name: lines
Command: EXPLODE
Select objects: all
9 found
Select objects:
Command: Specify opposite corner:
Selecting the "exploded" blocks, we see that only the ARCS blocks have actually been exploded: