Updating a specific attribute inside a folder of AutoCAD drawings using .NET

In the last post we looked at some code to search the current drawing for a particular attribute and update its value. In this post - as promised - we're going to look at how to extend this application to work on a folder of drawings, updating those that contain the attribute and saving them to a new filename.

Rather than implement a fancy, graphical user interface, I've stuck with my approach of using the command-line for input and output. If you wish to implement your own UI, please do - it's really easy using .NET. I get the occasional request to do this myself, but I prefer to keep my posts focused - there are other posts on this blog that focus specifically on UI-related issues.

I ended up changing the code somewhat - I had been printing the results of the batching process from within the UpdateAttributesInDatabase() function. As I get closer to extracting the functionality to a RealDWG application, I've started reducing the dependencies on the Editor (as this will not be available later on). The WriteMessage() calls are now in the commands themselves, instead of in the processing functions.

In addition to the previous UA (UpdateAttribute) command, I've added a new one called UAIF (UpdateAttributeInFiles). This one queries for some additional data (such as the path to the folder to process), and then uses some handy .NET Framework functionality to iterate through the drawings stored in a particular location. Rather than overwrite the originals, the updated files get saved to a new name (with "-updated" added to the filename). It is left as an exercise to put these in a separate folder or to save back to the original location (I'd rather not get blamed for overwriting valuable data :-).

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;

namespace AttributeUpdater

{

  public class Commands

  {

    [CommandMethod("UAIF")]

    public void UpdateAttributeInFiles()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      // Have the user choose the block and attribute

      // names, and the new attribute value

      PromptResult pr =

        ed.GetString(

          "\nEnter folder containing DWGs to process: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string pathName = pr.StringResult;

      pr =

        ed.GetString(

          "\nEnter name of block to search for: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string blockName = pr.StringResult.ToUpper();

      pr =

        ed.GetString(

          "\nEnter tag of attribute to update: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbName = pr.StringResult.ToUpper();

      pr =

        ed.GetString(

          "\nEnter new value for attribute: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbValue = pr.StringResult;

      string[] fileNames =

        Directory.GetFiles(pathName,"*.dwg");

      // We'll use some counters to keep track

      // of how the processing is going

      int processed = 0, saved = 0, problem = 0;

      foreach (string fileName in fileNames)

      {

        if (fileName.EndsWith(

              ".dwg",

              StringComparison.CurrentCultureIgnoreCase

            )

        )

        {

          string outputName =

            fileName.Substring(

              0,

              fileName.Length - 4) +

            "_updated.dwg";

          Database db = new Database(false, false);

          using (db)

          {

            try

            {

              ed.WriteMessage(

                "\n\nProcessing file: " + fileName

              );

              db.ReadDwgFile(

                fileName,

                FileShare.ReadWrite,

                false,

                ""

              );

              int attributesChanged =

                UpdateAttributesInDatabase(

                  db,

                  blockName,

                  attbName,

                  attbValue

                );

              // Display the results

              ed.WriteMessage(

                "\nUpdated {0} instance{1} of " +

                "attribute {2}.",

                attributesChanged,

                attributesChanged == 1 ? "" : "s",

                attbName

              );

              // Only save if we changed something

              if (attributesChanged > 0)

              {

                ed.WriteMessage(

                  "\nSaving to file: {0}", outputName

                );

                db.SaveAs(

                  outputName,

                  DwgVersion.Current

                );

                saved++;

              }

              processed++;

            }

            catch (System.Exception ex)

            {

              ed.WriteMessage(

                "\nProblem processing file: {0} - \"{1}\"",

                fileName,

                ex.Message

              );

              problem++;

            }

          }

        }

      }

      ed.WriteMessage(

        "\n\nSuccessfully processed {0} files, of which {1} had " +

        "attributes to update and an additional {2} had errors " +

        "during reading/processing.",

        processed,

        saved,

        problem

      );

    }

    [CommandMethod("UA")]

    public void UpdateAttribute()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

      // Have the user choose the block and attribute

      // names, and the new attribute value

      PromptResult pr =

        ed.GetString(

          "\nEnter name of block to search for: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string blockName = pr.StringResult.ToUpper();

      pr =

        ed.GetString(

          "\nEnter tag of attribute to update: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbName = pr.StringResult.ToUpper();

      pr =

        ed.GetString(

          "\nEnter new value for attribute: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbValue = pr.StringResult;

      ed.WriteMessage(

        "\nProcessing file: " + db.Filename

      );

      int count =

        UpdateAttributesInDatabase(

          db,

          blockName,

          attbName,

          attbValue

        );

      ed.Regen();

      // Display the results

      ed.WriteMessage(

        "\nUpdated {0} instance{1} of " +

        "attribute {2}.",

        count,

        count == 1 ? "" : "s",

        attbName

      );

    }

    private int UpdateAttributesInDatabase(

      Database db,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      // Get the IDs of the spaces we want to process

      // and simply call a function to process each

      ObjectId msId, psId;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        msId =

          bt[BlockTableRecord.ModelSpace];

        psId =

          bt[BlockTableRecord.PaperSpace];

        // Not needed, but quicker than aborting

        tr.Commit();

      }

      int msCount =

        UpdateAttributesInBlock(

          msId,

          blockName,

          attbName,

          attbValue

        );

      int psCount =

        UpdateAttributesInBlock(

          psId,

          blockName,

          attbName,

          attbValue

        );

      return msCount + psCount;

    }

    private int UpdateAttributesInBlock(

      ObjectId btrId,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Will return the number of attributes modified

      int changedCount = 0;

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            btrId,

            OpenMode.ForRead

          );

        // Test each entity in the container...

        foreach (ObjectId entId in btr)

        {

          Entity ent =

            tr.GetObject(entId, OpenMode.ForRead)

            as Entity;

          if (ent != null)

          {

            BlockReference br = ent as BlockReference;

            if (br != null)

            {

              BlockTableRecord bd =

                (BlockTableRecord)tr.GetObject(

                  br.BlockTableRecord,

                  OpenMode.ForRead

              );

              // ... to see whether it's a block with

              // the name we're after

              if (bd.Name.ToUpper() == blockName)

              {

                // Check each of the attributes...

                foreach (

                  ObjectId arId in br.AttributeCollection

                )

                {

                  DBObject obj =

                    tr.GetObject(

                      arId,

                      OpenMode.ForRead

                    );

                  AttributeReference ar =

                    obj as AttributeReference;

                  if (ar != null)

                  {

                    // ... to see whether it has

                    // the tag we're after

                    if (ar.Tag.ToUpper() == attbName)

                    {

                      // If so, update the value

                      // and increment the counter

                      ar.UpgradeOpen();

                      ar.TextString = attbValue;

                      ar.DowngradeOpen();

                      changedCount++;

                    }

                  }

                }

              }

              // Recurse for nested blocks

              changedCount +=

                UpdateAttributesInBlock(

                  br.BlockTableRecord,

                  blockName,

                  attbName,

                  attbValue

                );

            }

          }

        }

        tr.Commit();

      }

      return changedCount;

    }

  }

}

Here's what happens when I run it on my temp folder (which is full of crud, and that's putting it politely):

Command: UAIF

Enter folder containing DWGs to process: c:\temp

Enter name of block to search for: TEST

Enter tag of attribute to update: ONE

Enter new value for attribute: 1234

Processing file: c:\temp\-old_recover.dwg

Problem processing file: c:\temp\-old_recover.dwg - "eBadDwgHeader"

Processing file: c:\temp\4076612-2.DWG

Updated 0 instances of attribute ONE.

Processing file: c:\temp\attributes.dwg

Updated 5 instances of attribute ONE.

Saving to file: c:\temp\attributes_updated.dwg

[Deleted a lot of uninteresting reports]

Successfully processed 42 files, of which 1 had attributes to update and an

additional 3 had errors during reading/processing.

You'll see that some DWGs have failed to load: hopefully because they're in need of recovery rather than the workings of this code. The above technique should handle this gracefully, allowing you to go back and fix the problematic ones.

Update 1:

Norman Yuan pointed out a mistake in this code - I've been using the TransactionManager from the current document, rather than from the db. This isn't dramatically bad, but would have become more of a problem as we move to RealDWG. What I should really have done sooner is remove the doc and ed variables from the UpdateAttributesIn...() functions, so that I would not have been in a position to use them by mistake. I now pass in the Database as a parameter, which allows us to use it to get the appropriate TransactionManager. One other benefit this code enables is the choice of constructing the database without an associated document:

Database db = new Database(false, true);

This makes the code work more efficiently (as well as being cleaner).

Here are the updated functions - not the entire code:

    private int UpdateAttributesInDatabase(

      Database db,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Get the IDs of the spaces we want to process

      // and simply call a function to process each

      ObjectId msId, psId;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        msId =

          bt[BlockTableRecord.ModelSpace];

        psId =

          bt[BlockTableRecord.PaperSpace];

        // Not needed, but quicker than aborting

        tr.Commit();

      }

      int msCount =

        UpdateAttributesInBlock(

          db,

          msId,

          blockName,

          attbName,

          attbValue

        );

      int psCount =

        UpdateAttributesInBlock(

          db,

          psId,

          blockName,

          attbName,

          attbValue

        );

      return msCount + psCount;

    }

    private int UpdateAttributesInBlock(

      Database db,

      ObjectId btrId,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Will return the number of attributes modified

      int changedCount = 0;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            btrId,

            OpenMode.ForRead

          );

        // Test each entity in the container...

        foreach (ObjectId entId in btr)

        {

          Entity ent =

            tr.GetObject(entId, OpenMode.ForRead)

            as Entity;

          if (ent != null)

          {

            BlockReference br = ent as BlockReference;

            if (br != null)

            {

              BlockTableRecord bd =

                (BlockTableRecord)tr.GetObject(

                  br.BlockTableRecord,

                  OpenMode.ForRead

              );

              // ... to see whether it's a block with

              // the name we're after

              if (bd.Name.ToUpper() == blockName)

              {

                // Check each of the attributes...

                foreach (

                  ObjectId arId in br.AttributeCollection

                )

                {

                  DBObject obj =

                    tr.GetObject(

                      arId,

                      OpenMode.ForRead

                    );

                  AttributeReference ar =

                    obj as AttributeReference;

                  if (ar != null)

                  {

                    // ... to see whether it has

                    // the tag we're after

                    if (ar.Tag.ToUpper() == attbName)

                    {

                      // If so, update the value

                      // and increment the counter

                      ar.UpgradeOpen();

                      ar.TextString = attbValue;

                      ar.DowngradeOpen();

                      changedCount++;

                    }

                  }

                }

              }

              // Recurse for nested blocks

              changedCount +=

                UpdateAttributesInBlock(

                  db,

                  br.BlockTableRecord,

                  blockName,

                  attbName,

                  attbValue

                );

            }

          }

        }

        tr.Commit();

      }

      return changedCount;

    }

  }

Update 2:

The above code does not realign attributes after editing their values: if your attributes are anything other than left-justified, you will need to make a call to AdjustAlignment on the attribute reference after editing it.

There's a trick to this: you need to make sure the working database is set to the drawing you're working on, as well as passing it as an argument to the function.

You could set the working database early in the code, or insert this code to do it locally (the choice is yours):

  ar.TextString = attbValue;

  // Begin alignment code

  Database wdb = HostApplicationServices.WorkingDatabase;

  HostApplicationServices.WorkingDatabase = db;

  ar.AdjustAlignment(db);

  HostApplicationServices.WorkingDatabase = wdb;

  // End alignment code

  ar.DowngradeOpen();

I've left the line before and the line after in the above snippet, so it should be clear where the code needs inserting.

32 responses to “Updating a specific attribute inside a folder of AutoCAD drawings using .NET”

  1. Kean,

    In VBA, autocad object document has "name" and "path" property, but in Autocad.Net, I only find "name" but not "path" for document,(Application.DocumentManager.MdiActiveDocument.Name)is that true? I also searched autodesk forum, but did not find an anwser.

    If that's true, is there any other way to get the path of current activated drawing?

    Thanks.

  2. Kean,
    Your example was great, but was not what I was hoping for. I need to be able to display a userform and be able to select a group of drawing files, perhaps from different locations and execute different routines in each one of them. What has me stuck is the "Execution Context". I know this is not a personal answering service, but if you ever write something that will show how to do this, I will very thankful.

  3. Yes - this is not a complete solution for someone wanting to run an arbitrary command against a list of files/folders. This isn't necessarily hard to build, but in any case there are out-of-the-box solutions you can use for just this - take a look at ScriptPro, for instance.

    My posts are more about helping people develop code to solve their own specific development issues, not providing a generic tool to solve them all myself.

    Regards,

    Kean

  4. Limin,

    The Name property on the Document object exposes AcApDocument::fileName(), which contains both the filename and the path:

    "Returns the full path and file name of the database being used by this AcApDocument."

    Kean

  5. Kean,
    funny enough, I did something nearly identical to this not more than about a couple weeks ago. I tried to incorporate the progress meter as you illustrated a while ago, and found that the (if I recall correctly) db.SaveAs() method seems to put its progress on the progress meter, and when the save is complete, my progress meter does not return. So, i get a meter in place for the first drawing processed, and then it just reverts to the default. Any insight on a way to maintain the progress meter through the entire process?

  6. Thanks. Kean.

    And I found for a drawing not saved yet, it only returns drawing name not path name, (even at that time it has a temparary file path). but for drawing that is saved, it returns both file name and path.

  7. Kean,
    I appreciate your response, but what I was look for is for something that AutoCAD users have been able to do since ever and it is being able to customized the application to suit our requirements. I have been able to create customizations that make our daily work much efficient. With the new .NET managed classes, the potential to create more sophisticated solutions has greatly expanded. Right now all our customizations (none of them commercially available) have been developed in LISP and VBA, both of which have been great in their times, excellent resources. I work for the precast industry and most of our jobs generate lots of drawings, hence the need to process batch of drawings. If I am not able to find a way of opening a list of drawings from a user form and solve the “Execution Context” problem, my future in .NET managed classes is dead. There is no information on the topic or I have been not able to find it.

  8. Kean Walmsley Avatar

    HJohn,

    I'd suggest submitting your question - with sample code to reproduce - via the ADN website, if you're a member, or otherwise posting it to the .NET Customization Discussion Group.

    Assuming you're talking about processing a number of open documents - which this post does not - then you will need to be careful. ObjectARX and .NET are inherently document-centric, unless you work to make sure they are working in the session/application context. There are various places you could be falling over - I now see you've posted to the discussion group, but haven't provided code. Unless you provide a minimal sample showing the problem you're having, it's hard for people to determine exactly where your problem is occurring.

    Regards,

    Kean

  9. Kean Walmsley Avatar

    M Schumacher,

    Yes - I see the problem. It also occurs if your drawing requires Autodesk Shape Manager to be loaded (the message "Loading Modeler DLLs" clears the progress meter in this case).

    The only way I can see around it would be to display your own dialog - it could be located wherever you like - with a progress meter to demonstrate overall progress through the operation.

    It would clearly have been nice to have used AutoCAD's, but this isn't really an option in this case, it seems.

    Regards,

    Kean

  10. Nikolay Poleshchuk Avatar
    Nikolay Poleshchuk

    Kean, thank you for your example. It demonstrates the idea very well.
    For the future:
    You mentioned "context" many times, will you give a review of them, their limitations, peculiarities, obligatory switching between them etc.? Now I know 3 contexts: application, document and (new) object.

  11. Hi Nikolay,

    Only two of these "contexts" are related: document and session/application. This previous post covers these fairly well (I hope).

    Object contexts are a separate concept and are currently only used for the annotation scaling feature. They're unrelated to the application execution contexts mentioned above.

    Then, of course, there are context menus, which are unrelated to both these areas. 🙂

    Regards,

    Kean

  12. Kean,
    Thanks for pointing me to this great article! It really gave me what I needed. Is there any docmentation on the the BlockTable properties that are being exposed? I want to iterate thru more than 1 BlockTableRecord.PaperSpace object in each drawing file that I open and read. Is it possible to do that?
    Thanks again,
    John

  13. John,

    The documentation (such that it is) is currently included in the ObjectARX SDK (which you can download from autodesk.com/objectarx).

    You can certainly iterate through multiple layouts. Instead of just getting psId, you should be able to use foreach() on the BlockTable, taking each ObjectId and passing it to the UpdateAttributesInBlock() function. If you want to be slightly more efficient, you might want to open the block table record and check its IsLayout property, to skip the block definitions that are not layouts (this property is also true for the modelspace layout).

    Or you could just run the command on every block in the block table, and remove the recursion - that should also work.

    Cheers.

    Kean

  14. Kean,
    I appreciate your quick response and all these articles. They've been more informative and helpful than any "documentation" I've found so far (including the documentation downloaded with ObjectARX SDK). I'm assuming you're talking about in the UpdateAttributesInDatabase function. How do you type the foreach to iterate thru all ids in the blocktable and pass them into the UpdateAttributesInBlock function? That's giving me the most fits. It seems no matter how I type the variable for the foreach, it gives one error or another in the build.
    Thanks,
    John

  15. John,

    Try this (it compiles, but I haven't tested it, yet):

    private int UpdateAttributesInDatabase(
    Database db,
    string blockName,
    string attbName,
    string attbValue
    )
    {
    // Get the IDs of the spaces we want to process
    // and simply call a function to process each

    ObjectIdCollection layoutIds =
    new ObjectIdCollection();

    Transaction tr =
    db.TransactionManager.StartTransaction();
    using (tr)
    {
    BlockTable bt =
    (BlockTable)tr.GetObject(
    db.BlockTableId,
    OpenMode.ForRead
    );
    foreach (ObjectId id in bt)
    {
    BlockTableRecord btr =
    (BlockTableRecord)tr.GetObject(
    id,
    OpenMode.ForRead
    );
    if (btr.IsLayout)
    layoutIds.Add(id);
    }

    // Not needed, but quicker than aborting
    tr.Commit();
    }
    int updateCount = 0;
    foreach (ObjectId id in layoutIds)
    {
    updateCount +=
    UpdateAttributesInBlock(
    db,
    id,
    blockName,
    attbName,
    attbValue
    );
    }
    return updateCount;
    }

    Cheers,

    Kean

  16. Kean,
    You are the man!! That worked perfectly! Thanks so much for your time, help, and guidance!
    John

  17. Agence de casting pour enfants Avatar
    Agence de casting pour enfants

    Really interesting post!

    Never stop iterating and don’t fear failure. Choose well-understood conventions where they will do to the most good , shortcuts you might take will cost you more to fix later than to try to get right up-front today.

    Thanks , Zoli Juhasz

  18. Hi, Kean

    I'm luis again

    I take you code abut "Updating a specific attribute", Now when I to open file updated the attribute is not sichronized, so need to aply command "AttSync" but I can´t. how I do it in code VB, can you give me a tittle example to do it plase.

  19. Hi Luis,

    Have you tried SendStringToExecute, as in this post?

    Kean

  20. Yes kean, I tired that, but the problem is because I was opening the file by code:

    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    Dim db As New Database(False, True)
    Dim cadena As String = ruta
    Dim blk As BlockReference
    Try
    db.ReadDwgFile(cadena, System.IO.FileShare.ReadWrite, False, "")
    Catch ex As Exception
    MsgBox(ex.Message)
    Exit Sub
    End Try

    If I try use "SendStringToExecute" I need a drawing open (activedocumet), I just want to opening his database file but not open the file. I try this:

    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    Dim db As New Database(True, False)

    Dim cadena As String = RUTA
    Try
    db.ReadDwgFile(cadena, System.IO.FileShare.ReadWrite, False, "")
    Catch ex As Exception
    MsgBox(ex.Message)
    Exit Sub
    End Try
    Dim trn As Transaction = db.TransactionManager.StartTransaction
    Dim bt As BlockTable = trn.GetObject(db.BlockTableId, OpenMode.ForWrite)
    If bt.Has("PIE DE PLANO-" & ID_CLIENTE) Then
    doc.SendStringToExecute("attsync" & Chr(10) & "n" & Chr(10) & "PIE DE PLANO-597" & Chr(13), False, False, False)
    Else
    Exit Sub
    End If
    trn.Commit()
    Try
    db.SaveAs(RUTA, DwgVersion.Current)
    db.Dispose()
    db = Nothing
    Catch ex As Exception
    MsgBox("en archivo esta abierto" & ex.Message)
    ed.WriteMessage(vbCr + "\nError al Actualizar Archivo: " + db.Filename)
    End Try

    But it has an error (Buffertoosmal).How a do this (sichronize the atributtes) just open the database file:

    thank´s a lot kean. but I'm started to programing and a has so much errors.

  21. hi kean,I was tired but I'canto do this Help me please.

  22. Hi Luis,

    I'm sorry - I really don't have time to provide individual support (unless there's a problem with one of my posts).

    If you're an ADN member, please post it there - otherwise someone on the AutoCAD .NET Discussion Group may be able to help.

    Kean

  23. I'm sorry kean it wasn't my intesion. I'm started to programing and the best guide to do is your blog. Every day on the morning or on the bus in cel phone when I go to work I read an new example on you blog (you have many post).

    I hope you help if I will on problems abut someone your post.

  24. Terry W. Dotson Avatar
    Terry W. Dotson

    Many thanks for Update 2, that alignment problem plagued me for years in VBA and I'm glad to see there is a workaround on .NET.

    Keep up the good work, your a true hero to those of us learning .NET!

  25. "that alignment problem plagued me for years in VBA and I'm glad to see there is a workaround on .NET."

    Speaking of true heros, it sure was good of you to warn or caution potential buyers of your wares about that little 'gocha', so they didn't find themselves sitting on thousands of drawing files, each of which had to be opened manually to update the botched text alignment.

    Way to go, Terry!

  26. Tony -

    Please contact Terry directly if you have issues you'd like to raise with him.

    I don't want to have to start deleting comments from this blog (so far I just strip out spam), but I will if they're not respectful.

    Kean

  27. Sorry posted my problem on the wrong page before.

    When I run the above piece of code I get an eWasErased error.

    It fails at
    Dim MyDB as Database = new Database(False, False)
    MyDB.ReadDwgFile(DwgFilename, FileOpenMode.OpenForReadAndWriteNoShare, False, "")

    How ever this only happens when I load the drawing with a third party app loaded. (in this case ProSteel)

    If I run it with plain AutoCAD everything works fine.

    Can any one make any suggestions or could some explain what "eWasErased" means Please?

    Much appreciated.
    Stephan

  28. Also getting eNoInputFiler.

  29. Hi Stephan,

    If the problem doesn't exist in plain AutoCAD, and only happens when ProSteel is loaded, then you really need to raise this with the developer of ProSteel (StrucSoft?). Just as I suggested in response to your previous comment.

    Regards,

    Kean

  30. Can you explain what eNoInputFiler or eWasErased means?

  31. Without context those error codes are meaningless - I can't tell you exactly what the problem is just based on these error codes.

    As I've said before, this is something that needs to be debugged by the 3rd party provider.

    Kean

  32. Kean, you are awesome! You save my time to adjust the alignment of an attribute. It was not in middle center after changing text string. Thanks!

Leave a Reply to HJohn Cancel reply

Your email address will not be published. Required fields are marked *