.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Accessing a Block in the background, editing an attribute, and then saving that change

7 REPLIES 7
Reply
Message 1 of 8
Poncho_Slim
303 Views, 7 Replies

Accessing a Block in the background, editing an attribute, and then saving that change

I have worked on a project where a windows form opens, and a user can select blocks that are logged in a SQL database, make some edits on the blocks, and then post those changes. These blocks are spread across multiple drawings.

I seem to have been successful at opening a drawing in the background via its database,  finding the correct Block via its handle, access al attributes and even change the attribute.textstring to my test string ("HELLO WORLD"). When I commit the changes, I don't receive an exception, but when I opened the drawing file in AutoCAD, the change has not been made. This has me conclude that I have to save the drawing after I make the change. So, then I add the db.SaveAs line at the end, but this is causing issues.

I think I am running into an issue at his line: db.SaveAs(FilePath,DwgVersion.Current);
I get FilerError.

How can I save the db to cement my changes to the attributes to the block.

        public static Database OpenDrawingFile(string filePath)
        {
            Document doc = null;
            Database database = new Database();

            try
            {
                DocumentCollection acDocs = Application.DocumentManager;

                // Check if the document is already open
                foreach (Document d in acDocs)
                {
                    if (d.Name.Equals(filePath, StringComparison.OrdinalIgnoreCase))
                    {
                        database = d.Database;
                        break;
                    }
                }

                // Open the document if not already open
                if (doc == null)
                {
                    Debug.Print($"Opened:{filePath}");
                    database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, true, null);
                }
            }
            catch (System.Exception ex)
            {
                // Handle exceptions
                Console.WriteLine(ex.Message);
            }

            return database;
        }

        public static ObjectId GetBlockIdByHandle(Database db, string blockHandle)
        {
            ObjectId blockId = ObjectId.Null;

            try
            {
                Transaction tr = db.TransactionManager.StartTransaction();

                // Open the block table
                BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                // Open the block table record for the model space
                BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;

                // Iterate through the entities in the model space
                foreach (ObjectId objId in btr)
                {

                    if (objId.ObjectClass.DxfName == "INSERT")
                    {
                        BlockReference blockRef = tr.GetObject(objId, OpenMode.ForRead) as BlockReference;

                        // Check if the block reference has the specified handle
                        if (blockRef != null && blockRef.Handle.ToString() == blockHandle)
                        {
                            Debug.Print($"Handle Found:{blockRef.Handle}");
                            blockId = blockRef.ObjectId;
                            break;
                        }
                    }
                }

                tr.Commit();
            }
            catch (System.Exception ex)
            {
                // Handle exceptions
                Console.WriteLine(ex.Message);
            }

            return blockId;
        }

        public static void ModifyBlock(Database db, ObjectId blockId, DB_Delta Delta, string FilePath)
        {
            try
            {
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    DBObject obj = tr.GetObject(blockId, OpenMode.ForWrite);
                    if (obj is BlockReference blockRef)
                    {
                        BlockTableRecord btr = (BlockTableRecord)tr.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead);
                        Debug.Print($"{btr.Name}");

                        foreach (ObjectId attId in blockRef.AttributeCollection)
                        {
                            AttributeReference attRef = (AttributeReference)attId.GetObject(OpenMode.ForWrite);
                            Debug.Print($"{attRef.Tag}:{attRef.TextString}");

                            if(attRef.Tag == "REASON")
                            {
                                Debug.Print($"Changing {attRef.TextString} to \"HELLO WORLD\"");
                                attRef.TextString = "HELLO WORLD";
                            }
                        }
                    }
                    tr.Commit();
                }
                db.SaveAs(FilePath,DwgVersion.Current);
            }
            catch (Autodesk.AutoCAD.Runtime.Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (System.Exception ex)
            {
                // Handle exceptions
                Console.WriteLine(ex.Message);
            }
        }

 

7 REPLIES 7
Message 2 of 8
daniel_cadext
in reply to: Poncho_Slim

Just an observation, check the new Database parameters, I think it should be Database(false, true)

Also just after ReadDwgFile, I think you should call close input

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 3 of 8
Ed.Jobe
in reply to: Poncho_Slim

A Filer error would be a System.IO exception if you want to catch it. But it indicates that the system can't write to the file you specified or it can't find it. Did you pass a valid path? If the file already exists, then you can handle that in the error handler by overwriting or appending a prefix/suffix or prompting the user for a valid name. However, using the same path should be the same as the SAVE command. This thread might give you some suggestions on handling a filer error. This will also enable you to get more specific info on what exactly the Filer error is.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 4 of 8
ntclmain
in reply to: Poncho_Slim

@Poncho_Slim 

https://adndevblog.typepad.com/autocad/2012/04/batch-process-in-memory-1.html

You cannot use SaveAs method with the same filename as original file name.

*

Assuming that:  You want to edit side database from A.dwg in F folder

You must:

+ Save as  side database of A.dwg to B.dwg in the F Folder 

+ Dispose side database

+ Delete the A.dwg

+ Rename B.dwg to A.dwg

----------------------------------

P/S:

Does anyone get into troubles with google search recently?
I've found out that my Google search doesn't return any results related Forums.Autodesk.com (??!?!?@)

Message 5 of 8
ActivistInvestor
in reply to: ntclmain


@ntclmain wrote:

 

P/S:

Does anyone get into troubles with google search recently?
I've found out that my Google search doesn't return any results related Forums.Autodesk.com (??!?!?@)


You're right. It appears that this resource has been blocked (perhaps to prevent scraping by ChatGPT, et. al.). 

 

 

Untitled.png

 

 

Message 6 of 8
norman.yuan
in reply to: ntclmain


@ntclmain wrote:

@Poncho_Slim 

https://adndevblog.typepad.com/autocad/2012/04/batch-process-in-memory-1.html

You cannot use SaveAs method with the same filename as original file name.

*

Assuming that:  You want to edit side database from A.dwg in F folder

You must:

+ Save as  side database of A.dwg to B.dwg in the F Folder 

+ Dispose side database

+ Delete the A.dwg

+ Rename B.dwg to A.dwg

----------------------------------

...


Well, it is not true by saying "You cannot use SaveAs method", unless one use very, very old version to AutoCAD. In earlier version of .NET API, there is only 2 SaveAs() with different arguments. But a third overload version SaveAs(string, bool, DwgVersion, SecurotyParameter) was added later (I cannot recall from which version of AutoCAD, but it is very earlier version, maybe Acad2012/13, or event earlier). This version of SaveAs() does about the same as you (and the ADN blog article you referred to) described: it save the side database with a temp file name; once the side db is disposed, rename the original file as *.bak file; rename the temp file back to the original file name.

 

So, for the OP, simply call his SaveAs overload, the Filer error would be gone.

 

Also, since the side database read data from a DWG file, it would be better to use the constructor with proper parameters, as @daniel_cadext suggested:

 

var db = new Database(false, true);

 

Norman Yuan

Drive CAD With Code

EESignature

Message 7 of 8

First, a "Filer error" is not a 'System.IO exception', it is an Autodesk.AutoCAD.Runtime.Exception (thrown using ErrorStatus.FilerError), it is the exception raised when you try to save to the same DWG file that was read using ReadDwgFile(), if you don't call CloseInput(true) first, or dispose the database (which you obviously can't do before calling SaveAs()).

 

Below is some code that I dug up (can't even recall how long ago it was that I wrote it, but it's at least 10 years back and haven't tested it on recent versions), that makes "in-place" saving of in-memory Databases a bit easier. It adds a SaveToOriginalFile()  extension method to the Database class that takes care of the messy stuff that has to be done, as mentioned in other responses, and also adds an  option to retain the original DWG file as a backup.

 

 

 

 

using AcRx = Autodesk.AutoCAD.Runtime;
using System;
using System.IO;

namespace Autodesk.AutoCAD.DatabaseServices
{
   public static class DatabaseIOExtensions
   { 
      /// <summary>
      /// Database.SaveToOriginalFile() extension method
      /// 
      /// Saves a Database to its current filename by first
      /// saving it to a temporary file, erasing (or optionally
      /// renaming) the original DWG file, then replacing the
      /// original DWG file with the temporary file, and then
      /// disposing the Database.
      /// 
      /// When the call to this method returns, the Database 
      /// it was invoked on is no-longer usable.
      /// 
      /// The Database which this method is invoked on must
      /// have an existing filename (e.g., represents a DWG
      /// file that was opened by a call to ReadDwgFile()).
      /// 
      /// Optionally, the original DWG file can be retained as
      /// a backup by specifying a file extension for the backup
      /// (which must NOT be "dwg"). If a file with the backup 
      /// filespec exists, it is replaced.
      /// 
      /// DWG files saved using this method will retain their
      /// original thumbnail if one exists.
      /// 
      /// In order to use this extension method, the Database
      /// must have a filename.
      /// 
      /// The primary purpose of retaining a backup of the original
      /// drawing file is for diagnostic purposes, allowing the
      /// developer to do a before/after comparison of drawing files 
      /// that were modified by code. A backup file can also be used
      /// as 'evidence', to identify the cause of erroneous output.
      ///
      ///   Example:
      ///  
      ///     using(Database db = new Database(false, true))
      ///     {
      ///        db.ReadDwgFile("MyFile.dwg");
      ///
      ///        /// manipulate database here...
      ///       
      ///        db.SaveToOriginalFile();
      ///     }
      /// 
      /// </summary>
      /// <param name="db">The target Database. The database must
      /// not be open in the drawing editor.</param>
      /// <param name="version">The DWG version to save the file as.
      /// If not provided, the version of the original DWG file is used.</param>
      /// <param name="backupExtension">Optional extension to backup
      /// existing DWG file to. Must not be ".dwg". If not provided, 
      /// the original DWG file is overwritten amd not retained</param>

      public static void SaveToOriginalFile(this Database db, DwgVersion version = DwgVersion.Unknown, string backupExtension = "")
      {
         if(db == null || db.IsDisposed)
            throw new ArgumentNullException(nameof(db));
         if(!db.AutoDelete)
            throw new AcRx.Exception(AcRx.ErrorStatus.WrongDatabase);
         string filename = db.Filename;
         if(string.IsNullOrEmpty(filename))
            throw new AcRx.Exception(AcRx.ErrorStatus.NoFileName);
         string path = ExceptExtension(db.Filename);
         string tempName = GetTempFileName(path, ".$$$");
         string bakFile = null;
         if(!string.IsNullOrWhiteSpace(backupExtension) && backupExtension.Trim() != ".")
         {
            if(backupExtension.ToUpper().EndsWith("DWG"))
               throw new ArgumentException("cannot use .dwg as backup extension");
            bakFile = ChangeExtension(filename, backupExtension);
         }
         if(version == DwgVersion.Unknown)
            version = db.OriginalFileVersion;
         db.RetainOriginalThumbnailBitmap = true;
         db.SaveAs(tempName, version);
         db.CloseInput(true);
         if(!IsWriteEnabled(filename))
            throw new AcRx.Exception(AcRx.ErrorStatus.FileAccessErr);
         if(bakFile != null)
         {
            if(File.Exists(bakFile))
               File.Delete(bakFile);
            File.Move(filename, bakFile);
         }
         else
         {
            File.Delete(filename);
         }
         File.Move(tempName, filename);
      }

      static string GetTempFileName(string filespec, string ext)
      {
         int i = 1;
         string result = filespec + GetExtension(ext);
         while(File.Exists(result))
            result = string.Format("{0}{1}{2}", filespec, i++, ext);
         return result;
      }

      static string ExceptExtension(string filespec)
      {
         return Path.Combine(
            Path.GetDirectoryName(filespec),
            Path.GetFileNameWithoutExtension(filespec));
      }

      static string ChangeExtension(string filespec, string extension)
      {
         return ExceptExtension(filespec) + GetExtension(extension);
      }

      static string GetExtension(string ext)
      {
         return ext.StartsWith(".") ? ext : "." + ext;
      }

      static bool IsWriteEnabled(string filePath)
      {
         try
         {
            using(new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
            {
               return true;
            }
         }
         catch(System.Exception ex)
         {
            return false;
         }
      }

   }
}

 

 

 

 

Message 8 of 8
Ed.Jobe
in reply to: ActivistInvestor

Thanks for the clarification on the error source.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Forma Design Contest


AutoCAD Beta