just trying to find an efficient way of retrieving blockreferences from a drawing's database so I can explode them. Our shop's CNC mill isn't capable of reading blocks, only geometric entities. Our current export function creates a new drawing for each selected block, recreates it there, and then tries to explode it so the mill can properly read its files - right now we have to explode each new drawing manually, but doing so does achieve the results we want.
Tyler
The following code should explode all first generation block references in model space. Note: It does not explode sub entity block references.
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.Runtime;
public static class Commands
{
[CommandMethod("ExplodeAll")]
public static void ExplodeAll()
{
try
{
Database database = Application.DocumentManager.MdiActiveDocument.Database;
using Transaction transaction = database.TransactionManager.StartTransaction();
using BlockTable blockTable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;
ObjectId modelId = blockTable[BlockTableRecord.ModelSpace];
using BlockTableRecord model = transaction.GetObject(modelId, OpenMode.ForRead) as BlockTableRecord;
RXClass rxBlockReference = RXClass.GetClass(typeof(BlockReference));
foreach (ObjectId id in model)
{
if (id.IsNull || id.IsErased || id.IsEffectivelyErased || !id.IsValid)
continue;
if (id.ObjectClass == rxBlockReference)
{
using BlockReference reference = transaction.GetObject(id, OpenMode.ForRead) as BlockReference;
reference.ExplodeToOwnerSpace();
reference.Erase();
}
}
transaction.Commit();
}
catch { }
}
}
Related
I am new to autocad scripting and am trying to create an application which allows the user to select blocks and show the associated attributes of the block. I have found an alternative solution for now but would like to try an understand why my initial method was not working.
What I initially tried to do is create a selection set from the user input, get the objectid and try to create a blocktablerecord from this information. However every time I tried it returned Null.
here is the code I was using:
public void getattrib()
{
Database acCurDb;
acCurDb = Application.DocumentManager.MdiActiveDocument.Database;
Document acDoc = Application.DocumentManager.MdiActiveDocument;
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
// Open the Block table for read
BlockTable acBlkTbl;
acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;
PromptSelectionResult acSSPrompt = acDoc.Editor.GetSelection();
if (acSSPrompt.Status == PromptStatus.OK)
{
SelectionSet acSSet = acSSPrompt.Value;
ObjectId anothertest = acSSet[0].ObjectId;
BlockTableRecord br = acTrans.GetObject(anothertest, OpenMode.ForRead) as BlockTableRecord;
If You select the block, it's BlockReference not BlockTableRecord.
BlockTableRecord is a block definition.
If You want BlockTableRecord you can read it's ObjectId as property BlockTableRecord of selected BlockReference
Method One
_AcDb.Line oLine = new _AcDb.Line(ptStart, ptEnd);
AddToModelSpace("PLOT", oLine);
Where AddToModelSpace is:
public static void AddToModelSpace(string strLayer, _AcDb.Entity oEntity)
{
_AcAp.Document acDoc = _AcAp.Application.DocumentManager.MdiActiveDocument;
_AcDb.Database acCurDb = acDoc.Database;
_AcEd.Editor ed = acDoc.Editor;
using (_AcDb.BlockTable bt = acCurDb.BlockTableId.GetObject(_AcDb.OpenMode.ForRead) as _AcDb.BlockTable)
using (_AcDb.BlockTableRecord ms = bt[_AcDb.BlockTableRecord.ModelSpace].GetObject(_AcDb.OpenMode.ForWrite) as _AcDb.BlockTableRecord)
ms.AppendEntity(oEntity);
oEntity.Layer = strLayer;
oEntity.Dispose();
}
Method Two
// Get the current document and database
_AcAp.Document docActive = _AcAp.Application.DocumentManager.MdiActiveDocument;
_AcDb.Database docDB = docActive.Database;
// Start a transaction
using (_AcDb.Transaction acTrans = docDB.TransactionManager.StartTransaction())
{
// Open the Block table for read
_AcDb.BlockTable acBlkTbl;
acBlkTbl = acTrans.GetObject(docDB.BlockTableId,
_AcDb.OpenMode.ForRead) as _AcDb.BlockTable;
// Open the Block table record Model space for write
_AcDb.BlockTableRecord acBlkTblRec;
acBlkTblRec = acTrans.GetObject(acBlkTbl[_AcDb.BlockTableRecord.ModelSpace],
_AcDb.OpenMode.ForWrite) as _AcDb.BlockTableRecord;
// Create line
using (_AcDb.Line acLine = new _AcDb.Line(ptStart, ptEnd))
{
// Add the new object to the block table record and the transaction
acBlkTblRec.AppendEntity(acLine);
acTrans.AddNewlyCreatedDBObject(acLine, true);
}
// Save the new object to the database
acTrans.Commit();
}
I have used AddToModelSpace in my project so I hope it is fine!
Method Two is the way Autodesk recommends in the developer's documentation (you can read this section).
In Method One, you use the ObjectId.GetObject() method to open the BlockTable and the model space 'BlockTableRecord'. This method uses the top transaction to open object which means that there's an active transaction you should use to add the newly created entity. You can get it with Database.TransactionManager.TopTransaction. If you don't want to use a transaction at all, you have to use the "for advanced use only" ObjectId.Open() method.
A Method Three should be using some extension methods to be called from within a transaction. Here's a simplified (non error checking) extract of the ones I use.
static class ExtensionMethods
{
public static T GetObject<T>(
this ObjectId id,
OpenMode mode = OpenMode.ForRead,
bool openErased = false,
bool forceOpenOnLockedLayer = false)
where T : DBObject
{
return (T)id.GetObject(mode, openErased, forceOpenOnLockedLayer);
}
public static BlockTableRecord GetModelSpace(this Database db, OpenMode mode = OpenMode.ForRead)
{
return SymbolUtilityServices.GetBlockModelSpaceId(db).GetObject<BlockTableRecord>(mode);
}
public static ObjectId Add (this BlockTableRecord owner, Entity entity)
{
var tr = owner.Database.TransactionManager.TopTransaction;
var id = owner.AppendEntity(entity);
tr.AddNewlyCreatedDBObject(entity, true);
return id;
}
}
Using example:
using (var tr = db.TransactionManager.StartTransaction())
{
var line = new Line(startPt, endPt) { Layer = layerName };
db.GetModelSpace(OpenMode.ForWrite).Add(line);
tr.Commit();
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have a raster image jpg attached as Xref in the dwg and I want to dettach the file. How can I delete it ?
I don't believe there is anything special about it being an Xref necessarily. I took the code below from an article titled, "Detaching an AutoCAD external reference using .NET".
From the description:
There’s nothing very surprising about the code, although it does show
how to loop selection based on a core property of the selected object
(or even one connected to it, as is the case, here), rather than just
trusting AutoCAD to do so based on the object’s type. We also have to
pass the ObjectId of the defining BlockTableRecord to the
Database.DetachXref() method, which is certainly worth being aware of.
Code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
namespace XrefApplication
{
public class Commands
{
[CommandMethod("DX")]
static public void DetachXref()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Select an external reference
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTableRecord btr = null;
ObjectId xrefId = ObjectId.Null;
// We'll loop, as we need to open the block and
// underlying definition to check the block is
// an external reference during selection
do
{
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect an external reference:"
);
peo.SetRejectMessage("\nMust be a block reference.");
peo.AddAllowedClass(typeof(BlockReference), true);
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
// Open the block reference
BlockReference br =
(BlockReference)tr.GetObject(
per.ObjectId,
OpenMode.ForRead
);
// And the underlying block table record
btr =
(BlockTableRecord)tr.GetObject(
br.BlockTableRecord,
OpenMode.ForRead
);
// If it's an xref, store its ObjectId
if (btr.IsFromExternalReference)
{
xrefId = br.BlockTableRecord;
}
else
{
// Otherwise print a message and loop
ed.WriteMessage(
"\nMust be an external reference."
);
}
}
while (!btr.IsFromExternalReference);
// If we have a valid ObjectID for the xref, detach it
if (xrefId != ObjectId.Null)
{
db.DetachXref(xrefId);
ed.WriteMessage("\nExternal reference detached.");
}
// We commit the transaction simply for performance
// reasons, as the detach is independent
tr.Commit();
}
}
}
}
From MyCadSite.com you can read more about Xrefs outside of the C# API. In the interface, it all appears to attach the same way. When looking at this code on Typepad, you can see that the references are all treated the same way in the database as well.
From the author:
The db.GetHostDwgXrefGraph() method returns the Xref hierarchy for the
current drawing as an XrefGraph object. Here’s a simple code snippet
to demonstrate its use – in this case to print the Xref structure of
the current drawing to the command line.
Code:
[CommandMethod("XrefGraph")]
public static void XrefGraph()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
using (Transaction Tx = db.TransactionManager.StartTransaction())
{
ed.WriteMessage("\n---Resolving the XRefs------------------");
db.ResolveXrefs(true, false);
XrefGraph xg = db.GetHostDwgXrefGraph(true);
ed.WriteMessage("\n---XRef's Graph-------------------------");
ed.WriteMessage("\nCURRENT DRAWING");
GraphNode root = xg.RootNode;
printChildren(root, "|-------", ed, Tx);
ed.WriteMessage("\n----------------------------------------\n");
}
}
// Recursively prints out information about the XRef's hierarchy
private static void printChildren(GraphNode i_root, string i_indent,
Editor i_ed, Transaction i_Tx)
{
for (int o = 0; o < i_root.NumOut; o++)
{
XrefGraphNode child = i_root.Out(o) as XrefGraphNode;
if (child.XrefStatus == XrefStatus.Resolved)
{
BlockTableRecord bl =
i_Tx.GetObject(child.BlockTableRecordId, OpenMode.ForRead)
as BlockTableRecord;
i_ed.WriteMessage("\n" + i_indent + child.Database.Filename);
// Name of the Xref (found name)
// You can find the original path too:
//if (bl.IsFromExternalReference == true)
// i_ed.WriteMessage("\n" + i_indent + "Xref path name: "
// + bl.PathName);
printChildren(child, "| " + i_indent, i_ed, i_Tx);
}
}
}
I have found another solution for the problem:
private void DetachRasterImage(Transaction transaction, Database database, List<string> jpgPaths)
{
Dictionary<ObjectId, RasterImageDef> imageDefinitions = new Dictionary<ObjectId, RasterImageDef>();
DBDictionary imageDictionary = (DBDictionary)transaction.GetObject(RasterImageDef.GetImageDictionary(database), OpenMode.ForWrite);
foreach (DBDictionaryEntry entry in imageDictionary)
{
ObjectId id = (ObjectId)entry.Value;
RasterImageDef rasterImageDef = transaction.GetObject(id, OpenMode.ForWrite) as RasterImageDef;
if (jpgPaths.Contains(rasterImageDef.LocateActivePath()))
imageDefinitions.Add(id, rasterImageDef);
}
foreach (KeyValuePair<ObjectId, RasterImageDef> item in imageDefinitions)
{
imageDictionary.Remove(item.Key);
item.Value.Erase();
}
}
I have developed an external WPF application to generate drawings in c#. I have been able to draw, dimension, add blocks and every thing else required by the application using Autodesk.AutoCAD.Interop, however I can't seem to populate The title block, or generate a parts list.
All the examples I've seen are based on the mechanism that requires the application to run as a plugin inside AutoCAD. The truth is, inserting a line used is one or two lines of code using ModelSpace.InsertLine, now, it's at least 8 lines of code!
Is there a way to achieve this functionality using the Autodesk.AutoCAD.Interop? Or is there a way to combine using the interop with a plugin that can be called from the external exe?
Any pointers on this will be appreciated.
Thanks.
EDIT
To illustrate:
// before - Draw Line with Autodesk.AutoCAD.Interop
private static AcadLine DrawLine(double[] startPoint, double[] endPoint)
{
AcadLine line = ThisDrawing.ModelSpace.AddLine(startPoint, endPoint);
return line;
}
// Now - Draw line with Autodesk.AutoCAD.Runtime
[CommandMethod("DrawLine")]
public static Line DrawLine(Coordinate start, Coordinate end)
{
// Get the current document and database
// Get the current document and database
Document acDoc = Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
// Start a transaction
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
// Open the Block table for read
BlockTable acBlkTbl;
acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;
// Open the Block table record Model space for write
BlockTableRecord acBlkTblRec;
acBlkTblRec = acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
// Create a line that starts at 5,5 and ends at 12,3
Line acLine = new Line(start.Point3d, end.Point3d);
acLine.SetDatabaseDefaults();
// Add the new object to the block table record and the transaction
acBlkTblRec.AppendEntity(acLine);
acTrans.AddNewlyCreatedDBObject(acLine, true);
// Save the new object to the database
acTrans.Commit();
return acLine;
}
}
Yes, you can absolutely combine the two approaches.
Write an in-process DLL that does the work in AutoCAD. Make the commands you wish to call available to the command line by flagging your public methods with [CommandMethod("MethodName")].
Get AutoCAD started or connected via interop calls.
Using the interop AcadApplication, netload your DLL, and then call your work functions from the command line.
*Bonus * You can pass interop parameters to internal commands much easier this way too.
Here's an example of how you could build a commandmethod in-process and then call it via COM:
[CommandMethod("EditBlockAtt")]
public void EditBlockAtt()
{
var acDb = HostApplicationServices.WorkingDatabase;
var acEd = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
var blockNamePrompt = acEd.GetString(Environment.NewLine + "Enter block name: ");
if (blockNamePrompt.Status != PromptStatus.OK) return;
var blockName = blockNamePrompt.StringResult;
var attNamePrompt = acEd.GetString(Environment.NewLine + "Enter attribute name: ");
if (attNamePrompt.Status != PromptStatus.OK) return;
var attName = attNamePrompt.StringResult;
var acPo = new PromptStringOptions(Environment.NewLine + "Enter new attribute value: "){ AllowSpaces = true };
var newValuePrompt = acEd.GetString(acPo);
if (newValuePrompt.Status != PromptStatus.OK) return;
var newValue = newValuePrompt.StringResult;
using (var acTrans = acDb.TransactionManager.StartTransaction())
{
var acBlockTable = acTrans.GetObject(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
if (acBlockTable == null) return;
var acBlockTableRecord = acTrans.GetObject(acBlockTable[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
if (acBlockTableRecord == null) return;
foreach (var blkId in acBlockTableRecord)
{
var acBlock = acTrans.GetObject(blkId, OpenMode.ForRead) as BlockReference;
if (acBlock == null) continue;
if (!acBlock.Name.Equals(blockName, StringComparison.CurrentCultureIgnoreCase)) continue;
foreach (ObjectId attId in acBlock.AttributeCollection)
{
var acAtt = acTrans.GetObject(attId, OpenMode.ForRead) as AttributeReference;
if (acAtt == null) continue;
if (!acAtt.Tag.Equals(attName, StringComparison.CurrentCultureIgnoreCase)) continue;
acAtt.UpgradeOpen();
acAtt.TextString = newValue;
}
}
acTrans.Commit();
}
}
Then from an interop AcadApplication, netload the dll and call the method from the commandline in this format:
(Command "EditBlockAtt" "BlockName" "AttributeName" "NewValue")
However if you want to go pure Interop, this may get you what you need given you have an AcadDocument object at runtime:
foreach (AcadEntity ent in acadDoc.ModelSpace)
{
var block = ent as AcadBlockReference;
if (block == null) continue;
{
if (!block.Name.Equals("BlockName", StringComparison.CurrentCultureIgnoreCase)) continue;
var atts = block.GetAttributes() as object[];
if (atts == null) continue;
foreach (var attribute in atts.OfType<AcadAttributeReference>()
.Where(attribute => attribute.TagString.Equals("AttributeName",
StringComparison.CurrentCultureIgnoreCase)))
{
attribute.TextString = "New Value";
}
}
}
Also note this is using the AutoCAD 2012 Interop libraries. YMMV.
I have some trouble to select the targeted acadObject. I get the input via selectionset.SelectonScreen method.
Here i can get more number of object from modelspace based on my filter condition.But i need only one object from the user.
Here i mentioned my code below:
AcadSelectionSet selset= null;
selset=currDoc.selectionset.add("Selset");
short[] ftype=new short[1];
object[] fdata=new object[1];
ftype[0]=2;//for select the blockreference
fdata[0]=blkname;
selset.selectOnScreen ftype,fdata; // Here i can select any no. of blocks according to filter value but i need only one block reference.
Please help me to solve this problem.
That's possible using other Autocad .NET libraries (instead of Interop library). But fortunately, one does not exclude the other.
You will need to reference the libraries containing the following namespaces:
using Autodesk.Autocad.ApplicationServices
using Autodesk.Autocad.EditorInput
using Autodesk.Autocad.DatabaseServices
(you get those downloading the Object Arx libraries for free from Autodesk):
You will need to access the Editor from an autocad Document.
By the code you've shown, you're probably working with AcadDocument documents.
So, to transform an AcadDocument into a Document, do this:
//These are extension methods and must be in a static class
//Will only work if Doc is saved at least once (has full name) - if document is new, the name will be
public static Document GetAsAppServicesDoc(this IAcadDocument Doc)
{
return Application.DocumentManager.OfType<Document>().First(D => D.Name == Doc.FullOrNewName());
}
public static string FullOrNewName(this IAcadDocument Doc)
{
if (Doc.FullName == "")
return Doc.Name;
else
return Doc.FullName;
}
Once you've got a Document, get the Editor, and the GetSelection(Options, Filter)
The Options contains a property SingleOnly and a SinglePickInSpace. Setting that to true does what you want. (Try both to see wich works better)
//Seleciton options, with single selection
PromptSelectionOptions Options = new PromptSelectionOptions();
Options.SingleOnly = true;
Options.SinglePickInSpace = true;
//This is the filter for blockreferences
SelectionFilter Filter = new SelectionFilter(new TypedValue[] { new TypedValue(0, "INSERT") });
//calls the user selection
PromptSelectionResult Selection = Document.Editor.GetSelection(Options, Filter);
if (Selection.Status == PromptStatus.OK)
{
using (Transaction Trans = Document.Database.TransactionManager.StartTransaction())
{
//This line returns the selected items
AcadBlockReference SelectedRef = (AcadBlockReference)(Trans.GetObject(Selection.Value.OfType<SelectedObject>().First().ObjectId, OpenMode.ForRead).AcadObject);
}
}
this is a direct quote from autocad developer help
http://docs.autodesk.com/ACD/2013/ENU/index.html?url=files/GUID-CBECEDCF-3B4E-4DF3-99A0-47103D10DADD.htm,topicNumber=d30e724932
There is tons of documentation the AutoCAD .NET API.
you will need to have
[assembly: CommandClass(typeof(namespace.class))]
above your namespace if you want to be able to invoke this command from the command line after you NetLoad the .dll, if it is a classLibrary.
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
[CommandMethod("SelectObjectsOnscreen")]
public static void SelectObjectsOnscreen()
{
// Get the current document and database
Document acDoc = Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
// Start a transaction
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
// Request for objects to be selected in the drawing area
PromptSelectionResult acSSPrompt = acDoc.Editor.GetSelection();
// If the prompt status is OK, objects were selected
if (acSSPrompt.Status == PromptStatus.OK)
{
SelectionSet acSSet = acSSPrompt.Value;
// Step through the objects in the selection set
foreach (SelectedObject acSSObj in acSSet)
{
// Check to make sure a valid SelectedObject object was returned
if (acSSObj != null)
{
// Open the selected object for write
Entity acEnt = acTrans.GetObject(acSSObj.ObjectId,
OpenMode.ForWrite) as Entity;
if (acEnt != null)
{
// Change the object's color to Green
acEnt.ColorIndex = 3;
}
}
}
// Save the new object to the database
acTrans.Commit();
}
// Dispose of the transaction
}
}