Get the position of a template file (requires a reference to Autodesk.AutoCAD.Interop) |
Collect the relevant layers |
For each layer, select all ModelSpace objects |
Create a new empty database and read the template file into it |
WblockClone the selected objects into the new database |
Saveas the database and dispose it implicitly; no need for an open transaction in the last four steps |
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Runtime
type acApp = Autodesk.AutoCAD.ApplicationServices.Application
[<CommandMethod "WBL">]
let copyObjectsOnLayers() =
// Get the current document and database
let doc = acApp.DocumentManager.MdiActiveDocument
let ed = doc.Editor
let db = doc.Database
// Get the path to an appropriate template file
let prefs = acApp.Preferences :?> Autodesk.AutoCAD.Interop.AcadPreferences
let sTemplatePath =
System.IO.Path.Combine(prefs.Files.TemplateDwgPath, "acadiso.dwt")
// Get the current document's path and name without extension
let currRoot = acApp.GetSystemVariable "DWGPREFIX" :?> string
let currName =
acApp.GetSystemVariable "DWGNAME" :?> string
|> System.IO.Path.GetFileNameWithoutExtension
let lrcoll = new System.Collections.Generic.List<string>()
// Start a transaction
use tr = db.TransactionManager.StartTransaction()
// Collect all unlocked and not xref-dependent layers
let lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead) :?> LayerTable
for lrid in lt do
let ltr = tr.GetObject(lrid, OpenMode.ForRead, false) :?> LayerTableRecord
if not ltr.IsLocked && not(ltr.Name.Contains "|") then
lrcoll.Add ltr.Name |> ignore
tr.Commit()
for lrname in lrcoll do
let res =
new SelectionFilter[|
new TypedValue(int DxfCode.LayerName, lrname)
new TypedValue(int DxfCode.LayoutName, "Model") |]
|> acApp.DocumentManager.MdiActiveDocument.Editor.SelectAll
if res.Status = PromptStatus.OK && res.Value.Count > 0 then
let objcoll = new ObjectIdCollection(res.Value.GetObjectIds())
let strFilename = //<-- build filename as you need here
System.IO.Path.Combine(
currRoot, currName + @"_" + lrname + @".dwg" )
ed.WriteMessage("\n" + strFilename)
// Create a new database to copy the objects to
use newdb = new Database(false, true)
// Read the template file into it
newdb.ReadDwgFile(sTemplatePath, System.IO.FileShare.Read, true, "")
// Get the ObjectId of the new ModelSpace BlockTableRecord
let newbtrId = SymbolUtilityServices.GetBlockModelSpaceId newdb
// Clone the objects to the new database
let idmap = new IdMapping()
db.WblockCloneObjects(
objcoll, newbtrId, idmap, DuplicateRecordCloning.Ignore, false)
newdb.SaveAs(strFilename, DwgVersion.Current)
Is that your code posted at Through The Interface (http://through-the-interface.typepad.com/through_the_interface/)
or is there another Thorsten out there who likes F#?
// Late binding Interop workaround, casts obj to binding target
let (?) (o: obj) prop : 'T =
let bf = System.Reflection.BindingFlags.GetProperty
o.GetType().InvokeMember(prop, bf, null, o, null) :?> 'T
// Get the path to an appropriate template file
let sTemplatePath =
System.IO.Path.Combine(
acApp.Preferences?Files?TemplateDwgPath, "acadiso.dwt" )
System.IO.Path.Combine( acApp.Preferences?Files?TemplateDwgPath, "acadiso.dwt" )
? in place of \\ :|
Is that a typo ??
// Get the path to an appropriate template file
let prefs = acApp.Preferences :?> Autodesk.AutoCAD.Interop.AcadPreferences
let sTemplatePath =
System.IO.Path.Combine(prefs.Files.TemplateDwgPath, "acadiso.dwt")
Hi Jeff,
see if you discover any similarities:
I tried to reduce his function to the bare minmum, that is:
using System;
using System.Collections;
using System.Collections.Generic;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
[assembly: CommandClass(typeof(WblockLayers.MyCommands))]
namespace WblockLayers
{
public class MyCommands
{
// Dictionary to Store the Layer Name and the Entites that are on the Layer
Dictionary<string, ObjectIdCollection> entFileDict = new Dictionary<string, ObjectIdCollection>();
[CommandMethod("WblockLayers")]
public void WblockLayers()
{
// Get the folder that the current drawing is in
string dwgDialog = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("DWGPREFIX") as string;
Document doc = Application.DocumentManager.MdiActiveDocument;
DocumentCollection docMgr = Application.DocumentManager;
Database db = doc.Database;
Editor ed = doc.Editor;
using (Transaction trx = db.TransactionManager.StartTransaction())
{
BlockTable bt = db.BlockTableId.GetObject(OpenMode.ForRead) as BlockTable;
BlockTableRecord btrMs = bt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForRead) as BlockTableRecord;
ObjectIdCollection entIds = null;
// For each entity in Model Space it checks if the Dictiony contains the layer name
// If it does it adds the Entities Object Id to the value
//If not it creates a new entry
foreach (ObjectId entID in btrMs)
{
Entity ent = entID.GetObject(OpenMode.ForRead) as Entity;
string lyrName = ent.Layer;
if (entFileDict.TryGetValue(lyrName, out entIds))
{
entIds.Add(entID);
}
else
{
ObjectIdCollection entObjIds = new ObjectIdCollection();
entObjIds.Add(entID);
entFileDict.Add(lyrName, entObjIds);
}
}
// Loop through the dictionary using the key values which are the layer names
// Create new drawing add the entites on that layer
// Save drawing in current drawing folder as the layer name and close drawing
foreach (string lyrName in entFileDict.Keys)
{
using (Document newDoc = docMgr.Add("Fart.dwt"))
{
using (DocumentLock newLoc = newDoc.LockDocument())
{
using (Transaction newTrx = newDoc.Database.TransactionManager.StartTransaction())
{
BlockTable newBt = newDoc.Database.BlockTableId.GetObject(OpenMode.ForRead) as BlockTable;
BlockTableRecord newBtrMs = newBt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForRead) as BlockTableRecord;
IdMapping map = new IdMapping();
ObjectIdCollection newEntIds = null;
if (entFileDict.TryGetValue(lyrName, out newEntIds))
{
db.WblockCloneObjects(newEntIds, newBtrMs.Id, map, DuplicateRecordCloning.Replace, false);
}
else
{
continue;
}
newTrx.Commit();
newDoc.Database.SaveAs(dwgDialog + lyrName + ".dwg", DwgVersion.Current);
}
}
newDoc.CloseAndDiscard();
}
}
trx.Commit();
}
}
}
}
Hi Jeff,
see if you discover any similarities:
Sorry Thorsten I do not understand the question
1. It uses a dictionary and does one loop through model space adding each entity to the dictionary
2. Tries to create new drawing for each layer from the template "Fart.dwt" ---- Did this so it would fail and use default templateThis is cool and useful behaviour, since the fallback default seems to be either the last one used or it's based on the current drawing; it's not coming from QNEW settings, those are empty here.
Not sure if this will help reduce the function a little more or is not efficient enough.Reduction: check. Efficiency: not sure, for large numbers of files the document manager is really slow.
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
using acApp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace WblockLayers
{
public class MyCommands
{
[CommandMethod("WblockLayersC")]
public static void WblockLayers()
{
Database db = acApp.DocumentManager.MdiActiveDocument.Database;
Editor ed = acApp.DocumentManager.MdiActiveDocument.Editor;
// Dictionary to store the Layer Name and the Entites that are on the Layer
Dictionary<string, ObjectIdCollection> entFileDict = new Dictionary<string, ObjectIdCollection>();
// Get the folder that the current drawing is in
string dwgDialog = acApp.GetSystemVariable("DWGPREFIX") as string;
// Get the template folder from the preferences
string sTemplatePath = get(get(acApp.Preferences, "Files"), "TemplateDwgPath") as string;
string sTemplateFile = sTemplatePath + @"\acadiso.dwt";
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = db.BlockTableId.GetObject(OpenMode.ForRead) as BlockTable;
BlockTableRecord btrMs =
bt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForRead) as BlockTableRecord;
// For each entity in Model Space it checks if the Dictiony contains the layer name
// If it does it adds the Entities Object Id to the value
//If not it creates a new entry
foreach (ObjectId entID in btrMs)
{
Entity ent = entID.GetObject(OpenMode.ForRead) as Entity;
string lyrName = ent.Layer;
if (!entFileDict.ContainsKey(lyrName))
entFileDict.Add(lyrName, new ObjectIdCollection());
entFileDict[lyrName].Add(entID);
}
tr.Commit();
}
// Loop through the dictionary using the key values which are the layer names
// Create new drawing add the entites on that layer
// Save drawing in current drawing folder as the layer name and close drawing
foreach (KeyValuePair<string,ObjectIdCollection>entry in entFileDict)
{
using (Database newDb = new Database(false, true))
{
newDb.ReadDwgFile(sTemplateFile, System.IO.FileShare.Read, true, "");
IdMapping map = new IdMapping();
db.WblockCloneObjects(
entry.Value,
SymbolUtilityServices.GetBlockModelSpaceId(newDb),
map, DuplicateRecordCloning.Replace, false);
newDb.SaveAs(dwgDialog + entry.Key + ".dwg", DwgVersion.Current);
}
}
}
static Object get(Object o, string prop)
{
return o.GetType().InvokeMember(prop, System.Reflection.BindingFlags.GetProperty, null, o, null);
}
}
}
Cheers, Thorsten
This idea is pretty snappy. I'll borrow it, may I?
System.IO.Path.Combine( acApp.Preferences?Files?TemplateDwgPath, "acadiso.dwt" )
? in place of \\ :|
Is that a typo ??
Hi Kerry,
nope. Read ? in place of .
The ?-operator is an underdocumented feature of the latest F#-compiler. Its main use is endorsed by the gurus at MS research as a replacement for the FrameworkElement.FindName method in WPF. That's why I thought I could borrow this for my little late binding exercise. The orig was:Code: [Select]// Get the path to an appropriate template file
let prefs = acApp.Preferences :?> Autodesk.AutoCAD.Interop.AcadPreferences
let sTemplatePath =
System.IO.Path.Combine(prefs.Files.TemplateDwgPath, "acadiso.dwt")
Regards, Thorsten
specifying whether or not to build an empty object
foreach (KeyValuePair<string, ObjectIdCollection> entry in entFileDict)
{
using (Database newDb = new Database(true, false))
using (Transaction newTrx = newDb.TransactionManager.StartTransaction())
{
BlockTable newBt = newDb.BlockTableId.GetObject(OpenMode.ForRead) as BlockTable;
BlockTableRecord newBtrMs = newBt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForRead) as BlockTableRecord;
IdMapping map = new IdMapping();
db.WblockCloneObjects(entry.Value, newBtrMs.Id, map, DuplicateRecordCloning.Replace, false);
newTrx.Commit();
newDb.SaveAs(dwgDialog + entry.Key + ".dwg", DwgVersion.Current);
//newDb.CloseInput(true);
}
}
This makes a huge difference from my earlier post by changing the last foreach loop.Thanks for your patience with this one.
Setting the the first argument to true in the Database constructor
From docsQuotespecifying whether or not to build an empty object
...memory leaks, or worse, will result.
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LayerTable lt = (LayerTable)db.LayerTableId.GetObject(OpenMode.ForRead);
IEnumerable<ObjectId> layerIds = lt.Cast<ObjectId>();
// get the count for collection initialisation
int layerCount = layerIds.Count();
// init our collection to predefined size to avoid overhead
// of resizing. Also, we use ObjectId as the key for easy comparison/lookup
Dictionary<ObjectId, ObjectIdCollection> layerMap = new Dictionary<ObjectId, ObjectIdCollection>(layerCount);
// construct collection for easy access. We know layers are unique, so their
// objectid's will be, which is perfect for keys
layerIds.ForEach(id => layerMap.Add(id, new ObjectIdCollection()));
// grab MS
BlockTable bt = (BlockTable)db.BlockTableId.GetObject(OpenMode.ForRead);
BlockTableRecord msBtr = (BlockTableRecord)bt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForRead);
// loop it
msBtr.Cast<ObjectId>().ForEach(entId =>
{
Entity msEnt = (Entity)entId.GetObject(OpenMode.ForRead);
layerMap[msEnt.LayerId].Add(entId);
}
);
tr.Commit();
}
A slightly different approach for the Dictionary and it's population. It's a pity that the SymbolTables don't implement standard/generic collections or their interfaces.
use tr = db.TransactionManager.StartTransaction()
let layerMap =
db.LayerTableId.GetObject OpenMode.ForRead :?> LayerTable
|> Seq.cast<ObjectId>
|> Seq.filter(fun ltid -> not (ltid.GetObject OpenMode.ForRead :?> LayerTableRecord).IsDependent)
|> Seq.map(fun ltid -> ltid, new ObjectIdCollection())
|> dict
(SymbolUtilityServices.GetBlockModelSpaceId db).GetObject OpenMode.ForRead :?> BlockTableRecord
|> Seq.cast<ObjectId>
|> Seq.iter
(fun entId ->
let msEnt = entId.GetObject OpenMode.ForRead :?> Entity
layerMap.[msEnt.LayerId].Add entId |> ignore )
tr.Commit()
Regards, Thorsten
Minor quibble: wouldn't it be advisable to check the IsDependent property of each LayerTableRecord, to avoid empty ObjectIdCollections for xref layers?
< .. >
You'll also notice I'm not using any error checking either - shame on me.
You'll also notice I'm not using any error checking either - shame on me.
let GetTemplatePath() =
let openKey key = Registry.CurrentUser.OpenSubKey(key)
let getPath sub next key = key + "\\" + (openKey(key).GetValue(sub) :?> string) + next
("Software\\Autodesk\\AutoCAD"
|> getPath "CurVer" ""
|> getPath "CurVer" "\\Profiles"
|> getPath "" "\\General"
|> openKey).GetValue("TemplatePath") :?> string
let CurProfileRegKey() =
let openKey key = Registry.CurrentUser.OpenSubKey(key)
let getPath sub key = key + "\\" + (openKey(key).GetValue(sub) :?> string)
("Software\\Autodesk\\AutoCAD" |> getPath "CurVer" |> getPath "CurVer") + "\\Profiles"
|> getPath "" |> openKey
CurProfileRegKey().OpenSubKey("General").GetValue("TemplatePath")
let sTemplatePath =
let getPath sub key =
key + "\\" + (Registry.CurrentUser.OpenSubKey(key).GetValue(sub) :?> string)
((("Software\\Autodesk\\AutoCAD" |> getPath "CurVer" |> getPath "CurVer") + "\\Profiles"
|> getPath "") + "\\General"
|> Registry.CurrentUser.OpenSubKey).GetValue("TemplatePath") :?> string + "\\acadiso.dwt"
May be no need to build so many functions.
module Reg =
let substKey<'T> sub key = (Microsoft.Win32.Registry.CurrentUser.OpenSubKey key).GetValue sub :?> 'T
let getPath sub key = key + "\\" + substKey<_> sub key
let curVer = "Software\\Autodesk\\AutoCAD" |> Reg.getPath "CurVer" |> Reg.getPath "CurVer"
let sTemplatePath =
((curVer + "\\Profiles" |> Reg.getPath "") + "\\General" |> Reg.substKey<_> "TemplatePath") + "\\acadiso.dwt"
let openKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey
let (+&) key sub = (openKey key).GetValue sub
let (+/) key sub = key + "\\" + (key +& sub :?> string)
let templatePath() =
"Software\\Autodesk\\AutoCAD"
+/ "CurVer"
+/ "CurVer"
+ "\\Profiles"
+/ ""
+ "\\General"
+& "TemplatePath"
:?> string
+ "\\acadiso.dwt"
the ugly parenthesesI'am comming from LISP, parentheses reassure me.