IS there a way to add CommandFlags to a helper function? - c#

Currently trying to build a command which iterates through a BlockTable recursively, creating new drawings for each component BlockTableRecord. To do so, I need to lock each new document to properly edit it, which I'm trying to do via the Document.LockDocument() function. Since this command uses a recursive helper function, however, it throws a "DocumentLock is a type which is not valid in the given context" error which I believe is caused by the function lacking the "CommandFlags.Session" flag. Is there any way to attach this flag to a helper function? I've included my function's code below, thanks.
public List<string> BTRRecursor(BlockTableRecord bTR, string filepath)
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
var filepaths = new List<string>();
foreach (ObjectId internalObj in bTR)
{
Transaction trans = db.TransactionManager.StartTransaction();
try
{
BlockTableRecord internalBTR = trans.GetObject(internalObj, OpenMode.ForRead) as BlockTableRecord;
if (internalBTR.Name.Contains("_MR_"))
{
string strTemplatePath = "acad.dwt";
Document newDoc = Application.DocumentManager.Add(strTemplatePath);
using (DocumentLock lock = newDoc.LockDocument()) {
BlockTable nDBT = trans.GetObject(newDoc.Database.BlockTableId, OpenMode.ForWrite) as BlockTable;
nDBT.Add(internalBTR);
//toDWG
//Create new file
//Open its BTR for write
//Set its BTR to internalBTR
filepaths.Append("DWG Filepath");
}
}
else if (internalBTR.Name.Contains("_NMR_"))
{
BTRRecursor(internalBTR, Directory.CreateDirectory(filepath + #"\" + internalBTR.Name).FullName);
}
}
finally
{
trans.Dispose();
}
}
return filepaths;
}

I posted an example in response to your post in the Autodesk forum. Here's another one in a more functional style.
static IEnumerable<BlockTableRecord> GetNestedBlockTableRecords(BlockTableRecord source, Transaction tr)
{
foreach (var btr in source.Cast<ObjectId>()
.Where(id => id.ObjectClass.DxfName == "INSERT")
.Select(id => (BlockReference)tr.GetObject(id, OpenMode.ForRead))
.Select(br => (BlockTableRecord)tr.GetObject(br.BlockTableRecord, OpenMode.ForRead)))
{
yield return btr;
foreach (var item in GetNestedBlockTableRecords(btr, tr))
{
yield return item;
}
}
}

Related

MongoDB Change Stream

I try to code a MongoDB change stream. But if I do some changes, my code doesn't detect the changes. If the code detects the changes I have to get the operation type - according to my understanding. Moreover, how can I insert the changes in the other database? Is it so "easy", that I code a new database connection and update/insert/delete the collection/database?
What did I Code?
`
lang-cs
string mongo_db_client = "mongodb://localhost:27017";
string m_database = "myDataBase";
string m_collection ="myCollection";
MongoClient dbClient = new MongoClient(mongo_db_client);
var database = dbClient.GetDatabase(m_database);
var collection = database.GetCollection<BsonDocument>(m_collection);
var cursor = collection.Watch();
var the_operation_type = null;
if(cursor != null)
{
using(cursor)
{
var curr = cursor.Current;
if (curr != null)
{
foreach (var change in curr)
{
try
{
the_operation_type= change.OperationType;
}
catch(NullReferenceException e)
{
Log(e.ToString());
}
}
}
}
}
`

Revit API SizeTableManager.RemoveSizeTable() not working?

I'm trying to remove all Lookup Tables that start with a specific prefix inside all families.
The method "sizeTableManager.RemoveSizeTable(tableToRemove)" returns true as if it succeeded but when I go edit the families in the project and bring up the Lookup Tables list they are still there.
The transaction seems to be committing with no errors too, which is even more puzzling...
Any ideas as to what I'm doing wrong?
This is my code so far:
string existingPrefix = "ExistingPrefix_";
Document doc = this.ActiveUIDocument.Document;
FilteredElementCollector collector = new FilteredElementCollector(doc);
ICollection<Element> elements = collector.OfClass(typeof(Family)).ToElements();
foreach (var element in elements)
{
using (Transaction t = new Transaction(doc, "flush old lookup tables"))
{
t.Start();
FamilySizeTableManager sizeTableManager = FamilySizeTableManager.GetFamilySizeTableManager(doc, element.Id);
if(sizeTableManager != null)
{
foreach (var tableToRemove in sizeTableManager.GetAllSizeTableNames())
{
if(tableToRemove.StartsWith(existingPrefix))
{
bool result = sizeTableManager.RemoveSizeTable(tableToRemove);
if(result)
{
// TaskDialog.Show("Success", "Removed " + tableToRemove + " from " + element.Name);
var test = "test";
}
else
{
TaskDialog.Show("Warning", "Unable to remove " + tableToRemove + " from " + element.Name);
}
}
}
}
var commitResult = t.Commit();
}
}
Thanks in advance!
Well, turns out that the RemoveSizeTable() method was indeed working, but that I had to regenerate the document for the changes to take effect:
using (Transaction t = new Transaction(doc, "regenerate document"))
{
t.Start();
doc.Regenerate();
t.Commit();
}
Discovered this after noticing that saving the document caused the changes to take effect.

How to convert from large csv file into json without using split (Out Of Memory issue) C#

I am trying to parse a 300MB csv file and save it on mongodb. In order to do that I will need to convert this csv file into a list of BsonDocument which include key value pairs which create a document. each row in the csv file is a new BsonDocument.
Every couple of minutes of parallel testing, I am getting OOM exception on the split operation.
I've read this article which is very interesting. but I couldn't find any practical solution which I can implement on those huge files.
I was looking into different csv helpers, but couldn't find anything which solve this issue.
Any help will be much appreciated.
You should be able to read it line by line like this:
public static void Main()
{
using (StreamReader sr = new StreamReader(path))
{
string[] headers = null;
string[] curLine;
while ((curLine = sr.ReadLine().Split(',')) != null)
{
if (firstLine == null)
{
headers = curLine;
}
else
{
processLine(headers, curLine);
}
}
}
}
public static void processLine(string[] headers, string[] line)
{
for (int i = 0; i < headers.Length)
{
string header = headers[i];
string line = line[i];
//Now you have individual header/line pairs that you can put into mongodb
}
}
I've never used mongodb and I don't know the structure of your csv or your mongo, so I won't be able to help much there. Hopefully you can get it from here though. If not, edit your post with some more details about how you need to structure your mongodb and hopefully somebody will post a more helpful answer.
Thank you #dbc That worked!
#ashbygeek, I needed to add this to your code,
while (!sr.EndOfStream && (curLine = sr.ReadLine().Split('\t')) != null)
{
//do process
}
So I am uploading my code which I get my big CSV file from Azure blob, and insert in Batch to mongoDB instead of each document.
I also created my own primary key hash, and index, in order to identify duplicates documents, and if I found one, I'll start insert them one by one in order to identify the duplicate.
I hope it will help for someone in the future.
using (TextFieldParser parser = new TextFieldParser(blockBlob2.OpenRead()))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters("\t");
bool headerWritten = false;
List<BsonDocument> listToInsert = new List<BsonDocument>();
int chunkSize = 50;
int counter = 0;
var headers = new string[0];
while (!parser.EndOfData)
{
//Processing row
var fields = parser.ReadFields();
if (!headerWritten)
{
headers = fields;
headerWritten = true;
continue;
}
listToInsert.Add(new BsonDocument(headers.Zip(fields, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v)));
counter++;
if (counter != chunkSize) continue;
AdditionalInformation(listToInsert, dataCollectionQueueMessage);
CalculateHashForPrimaryKey(listToInsert);
await InsertDataIntoDB(listToInsert, dataCollectionQueueMessage);
counter = 0;
listToInsert.Clear();
}
if (listToInsert.Count > 0)
{
AdditionalInformation(listToInsert, dataCollectionQueueMessage);
CalculateHashForPrimaryKey(listToInsert);
await InsertDataIntoDB(listToInsert, dataCollectionQueueMessage);
}
}
private async Task InsertDataIntoDB(List<BsonDocument>listToInsert, DataCollectionQueueMessage dataCollectionQueueMessage)
{
const string connectionString = "mongodb://127.0.0.1/localdb";
var client = new MongoClient(connectionString);
_database = client.GetDatabase("localdb");
var collection = _database.GetCollection<BsonDocument>(dataCollectionQueueMessage.CollectionTypeEnum.ToString());
await collection.Indexes.CreateOneAsync(new BsonDocument("HashMultipleKey", 1), new CreateIndexOptions() { Unique = true, Sparse = true, });
try
{
await collection.InsertManyAsync(listToInsert);
}
catch (Exception ex)
{
ApplicationInsights.Instance.TrackException(ex);
await InsertSingleDocuments(listToInsert, collection, dataCollectionQueueMessage);
}
}
private async Task InsertSingleDocuments(List<BsonDocument> dataCollectionDict, IMongoCollection<BsonDocument> collection
,DataCollectionQueueMessage dataCollectionQueueMessage)
{
ApplicationInsights.Instance.TrackEvent("About to start insert individual documents and to find the duplicate one");
foreach (var data in dataCollectionDict)
{
try
{
await collection.InsertOneAsync(data);
}
catch (Exception ex)
{
ApplicationInsights.Instance.TrackException(ex,new Dictionary<string, string>() {
{
"Error Message","Duplicate document was detected, therefore ignoring this document and continuing to insert the next docuemnt"
}, {
"FilePath",dataCollectionQueueMessage.FilePath
}}
);
}
}
}

AutoCad NET use EntLast with asynchronous command

As I discovered in a previous question:
AutoCad Command Rejected "Undo" when using Application.Invoke()
it appears sending commands such c:wd_insym2 (ACad Electrical command) cannot be called synchronously as it calls additional commands such as Undo, causing it to fail.
However, I need to store the EntityID of the entity I have just created with the command, using either the Lisp (entlast) or Autodesk.AutoCad.Internal.Utils.EntLast(). Obviously if I send my command asynchronously this will not give me the correct result.
Maxence suggested using the doc.CommandEnded handler, however I cannot imagine how this will fit in my program flow, as I need to execute each command individually and then store the new EntityID in a .NET variable.
Is there ANY way for me to either send such commands synchronously without running into reentrancy issues, or alternatively send commands asynchronously and wait for them to execute before continuing?
Have you tried Editor.CommandAsync (AutoCAD 2015 and +):
[CommandMethod("CMD1")]
public async void Command1()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
await ed.CommandAsync("_CMD2");
ed.WriteMessage("Last entity handle: {0}", Utils.EntLast().Handle);
}
[CommandMethod("CMD2")]
public void Command2()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var line = new Line(new Point3d(), new Point3d(10, 20, 30));
var currentSpace = (BlockTableRecord) tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
currentSpace.AppendEntity(line);
tr.AddNewlyCreatedDBObject(line, true);
tr.Commit();
}
}
If you want to do this in an older version of AutoCAD, it will be more complicated:
List<ObjectId> ids;
[CommandMethod("CMD1")]
public void Cmd1()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
ids = new List<ObjectId>();
doc.CommandEnded += Doc_CommandEnded;
doc.SendStringToExecute("_CMD2 0 ", false, false, true);
}
private void Doc_CommandEnded(object sender, CommandEventArgs e)
{
if (e.GlobalCommandName != "CMD2") return;
ids.Add(Utils.EntLast());
var doc = (Document) sender;
if (ids.Count < 10)
{
double angle = ids.Count * Math.PI / 10;
doc.SendStringToExecute("_CMD2 " + Converter.AngleToString(angle) + "\n", false, false, true);
}
else
{
doc.CommandEnded -= Doc_CommandEnded;
doc.Editor.WriteMessage("\nHandles: {0}", string.Join(", ", ids.Select(id => id.Handle.ToString())));
}
}
[CommandMethod("CMD2")]
public void Cmd2()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
PromptDoubleResult pdr = doc.Editor.GetAngle("\nAngle: ");
if (pdr.Status == PromptStatus.Cancel) return;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var line = new Line(new Point3d(), new Point3d(Math.Cos(pdr.Value), Math.Sin(pdr.Value), 0));
var currentSpace = (BlockTableRecord) tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
currentSpace.AppendEntity(line);
tr.AddNewlyCreatedDBObject(line, true);
tr.Commit();
}
}

Programmatically Import Block Into AutoCAD (C#)

I'm writing a plugin for AutoCAD and want to import all the blocks it will use at the beginning to make sure that they are available when needed. To do that, I use this method
public static void ImportBlocks(string[] filesToTryToImport, string filter = "")
{
foreach (string blockToImport in filesToTryToImport)
{
if (blockToImport.Contains(filter))
{
Database sourceDb = new Database(false, true); //Temporary database to hold data for block we want to import
try
{
sourceDb.ReadDwgFile(blockToImport, System.IO.FileShare.Read, true, ""); //Read the DWG into a side database
ObjectIdCollection blockIds = new ObjectIdCollection(); // Create a variable to store the list of block identifiers
Autodesk.AutoCAD.DatabaseServices.TransactionManager tm = sourceDb.TransactionManager;
using (Transaction myT = tm.StartTransaction())
{
// Open the block table
BlockTable bt = (BlockTable)tm.GetObject(sourceDb.BlockTableId, OpenMode.ForRead, false);
// Check each block in the block table
foreach (ObjectId btrId in bt)
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(btrId, OpenMode.ForRead, false);
// Only add named & non-layout blocks to the copy list
if (!btr.IsAnonymous && !btr.IsLayout)
{
blockIds.Add(btrId);
}
btr.Dispose();
}
}
// Copy blocks from source to destination database
IdMapping mapping = new IdMapping();
sourceDb.WblockCloneObjects(blockIds, _database.BlockTableId, mapping, DuplicateRecordCloning.Replace, false);
_editor.WriteMessage("\nCopied " + blockIds.Count.ToString() + " block definitions from " + blockToImport + " to the current drawing.");
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
_editor.WriteMessage("\nError during copy: " + ex.Message);
}
finally
{
sourceDb.Dispose();
}
}
}
}
That method appears to work because it successfully executes. However when I go to insert a block in the drawing via AutoCAD's interface it doesn't show up as an option and when I try to insert it programmatically it throws a FileNotFound exception meaning it didn't work. What's wrong with this method? Thanks in advance!
EDIT: Here is a less complicated method with a test method
public static void ImportSingleBlock(string fileToTryToImport)
{
using (Transaction tr = _database.TransactionManager.StartTransaction())
{
Database sourceDb = new Database(false, true); //Temporary database to hold data for block we want to import
try
{
sourceDb.ReadDwgFile(fileToTryToImport, System.IO.FileShare.Read, true, ""); //Read the DWG into a side database
_database.Insert(fileToTryToImport, sourceDb, false);
_editor.WriteMessage("\nSUCCESS: " + fileToTryToImport);
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
_editor.WriteMessage("\nERROR: " + fileToTryToImport);
}
finally
{
sourceDb.Dispose();
}
tr.Commit();
}
}
[CommandMethod("TESTSINGLEBLOCKIMPORTING")]
public void TestSingleBlockImporting()
{
OpenFileDialog ofd = new OpenFileDialog();
DialogResult result = ofd.ShowDialog();
if (result == DialogResult.Cancel) //Ending method on cancel
{
return;
}
string fileToTryToImport = ofd.FileName;
using (Transaction tr = _database.TransactionManager.StartTransaction())
{
EntityMethods.ImportSingleBlock(fileToTryToImport);
tr.Commit();
}
}
This file is the block I'm trying to import. Hope this inspires someone cause I am desperately lost right now.
Your code is correct and should work. In fact I did tried it and works fine. You're probably missing to Commit() a outer transaction (where you call this ImportBlocs() method). Check:
using (Transaction trans = _database.TransactionManager.StartTransaction())
{
ImportBlocks(... parameters here ...);
trans.Commit(); // remember to call this commit, if omitted, Abort() is assumed
}
I had the same issue, very similar code. Issue was that
_database.Insert(fileToTryToImport, sourceDb, false);
should be
_database.Insert(blockName, sourceDb, false);
You can see that the first parameter has to be "blockName", and not the file path.

Categories