Hello AutoCAD C# Masters,
I have this code down here that opens a drawing, change some layers and close, but after it's done and the drawing closes, there is a file named DocName.dwl in the same folder as the dwg that I can't delete without closing autocad.
Is there a place where I can release the lock and I'm not doing?
[CommandMethod("Test", CommandFlags.UsePickSet | CommandFlags.Redraw | CommandFlags.Session | CommandFlags.Modal)]
public void Test()
{
var DocList = AskUserToSelectDocs();
foreach (string FileName in DocList.Files)
{
Application.DocumentManager.Open(FileName, false);
Document zcDoc = Application.DocumentManager.MdiActiveDocument;
Database zcDB = zcDoc.Database;
using (DocumentLock acLckDoc = zcDoc.LockDocument())
{
using (Transaction ZcTran = zcDoc.TransactionManager.StartTransaction())
{
BlockTable zcBLT = (BlockTable)ZcTran.GetObject(zcDB.BlockTableId, OpenMode.ForWrite);
BlockTableRecord zcBLTR = (BlockTableRecord)ZcTran.GetObject(zcBLT[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
zcBLTR.UpgradeOpen();
var editor = zcDoc.Editor;
var SelectionSet = editor.SelectAll().Value;
foreach (ObjectId Objeto in SelectionSet.GetObjectIds())
{
Entity ent = ZcTran.GetObject(Objeto, OpenMode.ForWrite) as Entity;
if (ent is Viewport)
{
var VP = ent as Viewport;
VP.Layer = "Defpoints";
}
}
editor.Regen();
ZcTran.Commit();
}
}
zcDB.SaveAs(zcDB.Filename, zcDB.OriginalFileVersion);
zcDoc.CloseAndSave(zcDoc.Name);
zcDoc.Dispose();
}
}
EDIT: Now the full code
Thanks!
For something like this, there is no need to open the document. I would suggest just loading the database into memory, manipulating it, then saving. All of this will be outside the 'DocumentManager'. Great for performance, especially as the number of files to process increases.
It does not matter which document is currently open, as long as it is not one of the ones getting processed (you can check for that).
Try this:
public static void Test()
{
var DocList = AskUserToSelectDocs();
//save working db reference
Database originalDB = HostApplicationServices.WorkingDatabase;
foreach (string FileName in DocList.Files)
{
//a little trick here:
//construct the database in memory, and read in the target file.
//now your database is your working database, not the active doc!!
using (Database database = new Database(false, true))
{
database.ReadDwgFile(FileName, System.IO.FileShare.ReadWrite, true, string.Empty);
HostApplicationServices.WorkingDatabase = database;//important!
using (Transaction transaction = database.TransactionManager.StartTransaction())
{
//do your stuff
}
//reset WorkingDB to original db
HostApplicationServices.WorkingDatabase = originalDB;
database.SaveAs(FileName, DwgVersion.Current);
}
}
}
Related
I am working with Csv file and datagridview in a C# project for a inventory app, I try to update a row to CSV file!
i need to update if user edit a row current word with a new word but my problem here is i need save the current word and new word and get total in pseudo code example:
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if(row in column is modified)
update specific row with comma to current file and load it...
}
Csv file is look like,
Current:
1;2;;4;5
Update:
1;2,A;;4;5 changed device A total: 1 time...
Next row modified :
1;A;;4,B,C;5 changed device B and C total change : 2 time...
With a database it's easy to update data but i don't have sql server installed so this option has not for me i think..
My goal is for tracking device out/in so if you have a solution please share it.
Short of using an SQL server, maybe something like this could help? LiteDB You'd have your LiteDB to host your data, and export it CSV whenever you need. Working with CSV files usually means you'll re-write the whole file every time there is an update to make... Which is slow and cumbersome. I recommend you use CSV to transport data from Point A to Point B, but not to maintain data.
Also, if you really want to stick to CSV, have a look at the Microsoft Ace OLEDB driver, previously known as JET driver. I use it to query CSV files, but I have never used it to update... so your mileage may vary.
Short of using an actual DataBase or a database driver, you'll have to use a StreamReader along with a StreamWriter. Read the file with the StreamReader, write the new file with the StreamWriter. In your StreanReader. This implies you'll have code in your StreamReader to find the correct Line(s) to update.
Here's the class I created and am using to interact with LiteDB. It's not all that robust, but it did exactly what I needed it to do at the time. I had to make changes to a slew of products hosted on my platform, and I used this to keep track of the progress.
using System;
using LiteDB;
namespace FixProductsProperty
{
public enum ListAction
{
Add = 0,
Remove,
Update,
Disable,
Enable
}
class DbInteractions
{
public static readonly string dbFilename = "MyDatabaseName.db";
public static readonly string dbItemsTableName = "MyTableName";
public void ToDataBase(ListAction incomingAction, TrackingDbEntry dbEntry = null)
{
if (dbEntry == null)
{
Exception ex = new Exception("dbEntry can not be null");
throw ex;
}
// Open database (or create if not exits)
using (var db = new LiteDatabase(dbFilename))
{
var backupListInDB = db.GetCollection<TrackingDbEntry>(dbItemsTableName);
//ovverride action if needed
if (incomingAction == ListAction.Add)
{
var tempone = backupListInDB.FindOne(p => p.ProductID == dbEntry.ProductID);
if (backupListInDB.FindOne(p => p.ProductID == dbEntry.ProductID) != null)
{
//the record already exists
incomingAction = ListAction.Update;
//IOException ex = new IOException("Err: Duplicate. " + dbEntry.ProductID + " is already in the database.");
//throw ex;
}
else
{
//the record does not already exist
incomingAction = ListAction.Add;
}
}
switch (incomingAction)
{
case ListAction.Add:
backupListInDB.Insert(dbEntry);
break;
case ListAction.Remove:
//backupListInDB.Delete(p => p.FileOrFolderPath == backupItem.FileOrFolderPath);
if (dbEntry.ProductID != 0)
{
backupListInDB.Delete(dbEntry.ProductID);
}
break;
case ListAction.Update:
if (dbEntry.ProductID != 0)
{
backupListInDB.Update(dbEntry.ProductID, dbEntry);
}
break;
case ListAction.Disable:
break;
case ListAction.Enable:
break;
default:
break;
}
backupListInDB.EnsureIndex(p => p.ProductID);
// Use Linq to query documents
//var results = backupListInDB.Find(x => x.Name.StartsWith("Jo"));
}
}
}
}
I use it like this:
DbInteractions yeah = new DbInteractions();
yeah.ToDataBase(ListAction.Add, new TrackingDbEntry { ProductID = dataBoundItem.ProductID, StoreID = dataBoundItem.StoreID, ChangeStatus = true });
Sorry... my variable naming convention sometimes blows...
I am writing a C#.NET program that interacts with AutoCAD through the AutoCAD .NET API. The program loops through DWG files in a directory and checks every text entity on the "testLayer" layer to see if it matches "testText". I got this to work by opening up every file and making a Selectionfilter to get all of the entities on the "testLayer" layer.
Application.DocumentManager.Open(curfile.FullName, false);
....
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
using (Transaction acTrans = doc.TransactionManager.StartTransaction())
{
ObjectIdCollection ents = new ObjectIdCollection();
// Set up filter and filter on layer name
TypedValue[] tvs = new TypedValue[1] { new TypedValue((int)DxfCode.LayerName, "testLayer")};
SelectionFilter sf = new SelectionFilter(tvs);
PromptSelectionResult psr = ed.SelectAll(sf);
if (psr.Status == PromptStatus.OK)
{
// Get the object ids for all of the entities for the filtered layer
ents = new ObjectIdCollection(psr.Value.GetObjectIds());
foreach (ObjectId objid in ents)
{
DBText dbText = acTrans.GetObject(objid, OpenMode.ForRead) as DBText;
if (dbText.TextString.Contains("testText")
{
return dbText.TextString;
}
}
return "";
}
else
{
return "";
}
}
}
But now I am converting my program to side-load the underlying databases because it was taking too long for the program to open and close every .DWG file. The problem is that now I am using
db.ReadDwgFile(currentDWG, FileOpenMode.OpenForReadAndAllShare, true, string.Empty);
to read files without actually opening them so I can't use
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor
and
ed.SelectAll(sf) for the selection filter strategy I was using earlier because the document isn't actually open. So how can I can get all of the text entities on each layer named "testLayer" without actually opening the DWG file?
In a 'side database', to mimic SelectAll, you have to iterate through all entities in all the layouts and check the entity layer.
EDIT: In a 'side database', to mimic SelectAll, you have to iterate through all entities in all the layouts and check the entity type and layer.
private IEnumerable<ObjectId> GetTextEntitiesOnLayer(Database db, string layerName)
{
using (var tr = db.TransactionManager.StartOpenCloseTransaction())
{
var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
foreach (ObjectId btrId in blockTable)
{
var btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
var textClass = RXObject.GetClass(typeof(DBText));
if (btr.IsLayout)
{
foreach (ObjectId id in btr)
{
if (id.ObjectClass == textClass)
{
var text = (DBText)tr.GetObject(id, OpenMode.ForRead);
if (text.Layer.Equals(layerName, System.StringComparison.CurrentCultureIgnoreCase))
{
yield return id;
}
}
}
}
}
}
}
This is a simple example for the problem i'm having in saving lists into my database.
[TestMethod]
public void InsertResultsIntoDatabase()
{
using (var context = new ResultContext())
{
DatabaseTestResults dbTestResults = new DatabaseTestResults();
dbTestResults.ZipFileName = "report-nominal - Copy.zip";
dbTestResults.testList.Add(1);
context.DbTestResults.Add(dbTestResults);
context.SaveChanges();
}
in this point the debugger will show that context contains the testList and the zipFileName correctly.
using (var context = new ResultContext())
{
var query = context.DbTestResults.Find("report-nominal -
Copy.zip");
}
when trying to get the information from the database it's saved only zipFileName and the list is empty.
How do I save lists into database?
Try adding context.Entry(dbTestResults).State = EntityState.Added before SaveChanges()
Like this:
using (var context = new ResultContext())
{
DatabaseTestResults dbTestResults = new DatabaseTestResults();
dbTestResults.ZipFileName = "report-nominal - Copy.zip";
context.DbTestResults.Add(dbTestResults);
context.Entry(dbTestResults).State = EntityState.Added
context.SaveChanges();
}
I am using Autocad 2012 with the API provided. I am developing in c#.
What I am trying to do is select a certain layer, and "detect" all rectangles / squares in that layer. Ultimateley, I would like to be able to draw inside of all of those rectangles that I have "detected" (using their coordinates).
So far, I am using the LayerTable class along with GetObjects to associate layers with objects, like so:
LayerTable layers;
layers = acTrans.GetObject(acCurDb.LayerTableId, OpenMode.ForRead) as LayerTable;
String layerNames = "";
foreach (ObjectId layer in layers)
{
LayerTableRecord layerTableRec;
layerTableRec = acTrans.GetObject(layer, OpenMode.ForRead) as LayerTableRecord;
layerNames += layerTableRec.Name+"\n";
}
I can't seem to figure out where to go from here though. How to select just one layer, and then detect shapes inside of it. Can someone point me in the correct direction, in terms of what classes / methods to look into? Thanks.
Ultimately, you need to take another look at the AutoCAD object model. The BlockTableRecord "ModelSpace" is what will contain all* of the AutoCAD entities that have layer assignments. Once you have the BlockTableRecord open for read, you can filter down to entities matching whatever layers you're interested in. LINQ can come in handy here.
You don't actually care about the layer's objectID in this instance, just the name. You only really open up the LayerTableRecord when you want to change a layer. If you'll be changing entity properties, you really need to familiarize yourself with the Transaction class. There's also a faster alternative to using 'As' in AutoCAD by leveraging RXObject.GetClass().
*Entities can also live in other BlockTableRecords (any additional layouts for example) but for now you'll likely be fine with just modelspace.
Here's a little snippet to get you started:
var acDoc = Application.DocumentManager.MdiActiveDocument;
var acDb = acDoc.Database;
using (var tr = database.TransactionManager.StartTransaction())
{
try
{
var entClass = RXObject.GetClass(typeof(Entity));
var modelSpaceId = SymbolUtilityServices.GetBlockModelSpaceId(acDb);
var modelSpace = (BlockTableRecord)tr.GetObject(modelSpaceId, OpenMode.ForRead);
foreach (ObjectId id in modelSpace)
{
if (!id.ObjectClass.IsDerivedFrom(entClass)) // For entity this is a little redundant, but it works well with derived classes
continue;
var ent = (Entity)tr.GetObject(id, OpenMode.ForRead)
// Check for the entity's layer
// You'll need to upgrade the entity to OpenMode.ForWrite if you want to change anything
}
tr.Commit();
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
acDoc.Editor.WriteMessage(ex.Message);
}
}
var acDoc = Application.DocumentManager.MdiActiveDocument;
var acDb = acDoc.Database;
using (var tr = database.TransactionManager.StartTransaction())
{
try
{
var entClass = RXObject.GetClass(typeof(Entity));
var modelSpaceId = SymbolUtilityServices.GetBlockModelSpaceId(acDb);
var modelSpace = (BlockTableRecord)tr.GetObject(modelSpaceId, OpenMode.ForRead);
foreach (ObjectId id in modelSpace)
{
Entity acEnt = (Entity)tr.GetObject(id, OpenMode.ForRead);
string layerName = acEnt.Name;
}
tr.Commit();
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
acDoc.Editor.WriteMessage(ex.Message);
}
}
I want to display entities on a drawing area as a preview for the user, then if the user accepts the program, add the the entities to the database or make some modification.
I'm used to use transaction and commit the transaction the entities appear if i can make the entities appear before commit the transaction
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
int i = poly2TxtSetting.currentFormat.IndexFormat.startWith;
List<ObjectId> ListTextId = new List<ObjectId>();
List<ObjectId> ListPointId = new List<ObjectId>();
foreach (var po in Points)
{
i += poly2TxtSetting.currentFormat.IndexFormat.step;
DBText dtext = new DBText();
dtext.TextString = i.tostring();
dtext.Position = po;
dtext.SetDatabaseDefaults();
DBPoint point = new DBPoint(po);
btr.AppendEntity(dtext);
tr.AddNewlyCreatedDBObject(dtext, true);
btr.AppendEntity(point);
tr.AddNewlyCreatedDBObject(point, true);
}
tr.Commit();
}
If you want to display your model in AutoCAD model space, you have two options.
1) Insert it into database.
2) Add it into Transient Manager.
I think you need is 2nd option.
Search for Transient Graphics.
Check below Code that will help you.
Solid3d solid=new Solid(0);
solid.CreateSphere(10);
TransientManager.CurrentTransientManager.AddTransient(solid, TransientDrawingMode.Main, 128, new IntegerCollection());
This will display sphere on origin with radius=10;
You can wait for the graphics flush:
tr.TransactionManager.QueueForGraphicsFlush();
then prompt for input so the user has time to see the update:
PromptKeywordOptions pko = new PromptKeywordOptions("\nKeep Changes?");
pko.AllowNone = true;
pko.Keywords.Add("Y");
pko.Keywords.Add("N");
pko.Keywords.Default = "Y";
PromptResult pkr = ed.GetKeywords(pko);
if (pkr.StringResult == "Y") {
tr.Commit();
} else {
tr.Abort();
}
This link provides an example application using this technique.