Modifying the contents of an AutoCAD xref using .NET

An interesting query came into my inbox, last week. A member of one of our internal product teams was looking to programmatically modify the contents of an external reference file. She was using the code in this helpful DevBlog post, but was running into issues. She was using WblockCloneObjects() to copy a block definition across from a separate drawing into a particular xref, but found some strange behaviour.

In this post I'm going to show the steps we ended up following to make this work. We're going to implement a slightly different scenario, where we modify an external reference to load a linetype and apply that to all the circles in its modelspace.

The application is split into two separate commands: our CRX command (for CReate Xref… yes, I know this name's a bit confusing) will create an external drawing file – consisting of a circle with a polyline boundary around it – and reference it in the current drawing. There's nothing very special about this command: we just create an external Database, save it to disk, and then run the standard ATTACH command via ed.Command().

The second command is called CHX (for CHange Xref) and this is the more interesting one: it attempts to edit any external reference found in modelspace, manipulating each one to have all its circles given the "dashed" linetype. If that linetype isn't loaded it'll go ahead and load it.

Here are the two commands in action:

Modify external reference

The main "trick" we had to resolve when using WblockCloneObjects() was to set the WorkingDatabase to be that of the xref. This step wasn't needed for loading linetypes, but I've left the code commented out for setting the WorkingDatabase appropriately: you may well need to uncomment it for your own more complex operations.

The other important step was to check whether the xref's underlying file is accessible for write: if the drawing is open in the editor or is read-only on disk (unlikely in our scenario, as we're creating it) then the XrefFileLock.LockFile() operation will throw an exception (but also keep a file lock in memory which will throw another exception when eventually finalized… not good). So we need to preempt the exception, avoiding the conditions under which it would be thrown.

We do this using this approach to check whether the file is in use, extended to also detect whether the file is read-only on disk. It can be extended with additional file access exceptions, if the two that I've included don't end up covering the various scenarios.

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;

 

namespace XrefManipulation

{

  public class Commands

  {

    const string xloc = @"c:\temp\xref.dwg";

 

    [CommandMethod("CRX")]

    public void CreateXref()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var ed = doc.Editor;

 

      // Create our Database with the default dictionaries and

      // symbol tables

 

      using (var db = new Database(true, true))

      {

        using (var tr = db.TransactionManager.StartTransaction())

        {

          // We'll add entities to its modelspace

 

          var bt =

            (BlockTable)tr.GetObject(

              db.BlockTableId, OpenMode.ForRead

            );

          var ms =

            (BlockTableRecord)tr.GetObject(

              bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite

            );

 

          // Create a Circle inside a Polyline boundary

 

          var c = new Circle(Point3d.Origin, Vector3d.ZAxis, 1.0);

 

          ms.AppendEntity(c);

          tr.AddNewlyCreatedDBObject(c, true);

 

          var p = new Polyline(4);

          p.AddVertexAt(0, new Point2d(-1, -1), 0, 0, 0);

          p.AddVertexAt(1, new Point2d(-1, 1), 0, 0, 0);

          p.AddVertexAt(2, new Point2d(1, 1), 0, 0, 0);

          p.AddVertexAt(3, new Point2d(1, -1), 0, 0, 0);

          p.Closed = true;

 

          ms.AppendEntity(p);

          tr.AddNewlyCreatedDBObject(p, true);

 

          tr.Commit();

        }

 

        // We're going to save our file in the specified location,

        // after erasing the file if it already exists

 

        if (File.Exists(xloc))

        {

          try

          {

            File.Delete(xloc);

          }

          catch (System.Exception ex)

          {

            if (

              ex is IOException || ex is UnauthorizedAccessException

            )

            {

              ed.WriteMessage(

                "\nUnable to erase existing reference file. " +

                "It may be open in the editor or read-only."

              );

              return;

            }

            throw;

          }

        }

        db.SaveAs(xloc, DwgVersion.Current);

      }

 

      // The simplest way to attach the xref is via a command

 

      ed.Command("_.-ATTACH", xloc, "_A", "25,12.5,0", 5, 5, 0);

    }

 

    [CommandMethod("CHX")]

    public void ChangeXref()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Get the database associated with each xref in the

      // drawing and change all of its circles to be dashed

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

        var ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace], OpenMode.ForRead

          );

 

        // Loop through the contents of the modelspace

 

        foreach (var id in ms)

        {

          // We only care about BlockReferences

 

          var br =

            tr.GetObject(id, OpenMode.ForRead) as BlockReference;

          if (br != null)

          {

            // Check whether the associated BlockTableRecord is

            // an external reference

 

            var bd =

              (BlockTableRecord)tr.GetObject(

                br.BlockTableRecord, OpenMode.ForRead

              );

            if (bd.IsFromExternalReference)

            {

              // If so, get its Database and call the function

              // to change the linetype of its Circles

 

              var xdb = bd.GetXrefDatabase(false);

              if (xdb != null)

              {

                // Lock the xref database

 

                if (

                  IsFileLockedOrReadOnly(new FileInfo(xdb.Filename))

                )

                {

                  ed.WriteMessage(

                    "\nUnable to modify the external reference. " +

                    "It may be open in the editor or read-only."

                  );

                }

                else

                {

                  using (

                    var xf = XrefFileLock.LockFile(xdb.XrefBlockId)

                  )

                  {

                    // Make sure the original symbols are loaded

 

                    xdb.RestoreOriginalXrefSymbols();

 

                    // Depending on the operation you're performing,

                    // you may need to set the WorkingDatabase to

                    // be that of the Xref

 

              &#
160;     //HostApplicationServices.WorkingDatabase = xdb;

 

                    ChangeEntityLinetype(

                      xdb, typeof(Circle), "DASHED"

                    );

 

                    // And then set things back, afterwards

 

                    //HostApplicationServices.WorkingDatabase = db;

 

                    xdb.RestoreForwardingXrefSymbols();

                  }

                }

              }

            }

          }

        }

        tr.Commit();

      }

    }

 

    internal virtual bool IsFileLockedOrReadOnly(FileInfo fi)

    {

      FileStream fs = null;

      try

      {

        fs =

          fi.Open(

            FileMode.Open, FileAccess.ReadWrite, FileShare.None

          );

      }

      catch (System.Exception ex)

      {

        if (ex is IOException || ex is UnauthorizedAccessException)

        {

          return true;

        }

        throw;

      }

      finally

      {

        if (fs != null)

          fs.Close();

      }

 

      // File is accessible

 

      return false;

    }

 

    // Change all the entities of the specified type in a Database

    // to the specified linetype

 

    private void ChangeEntityLinetype(

      Database db, System.Type t, string ltname

    )

    {

      using (

        var tr = db.TransactionManager.StartTransaction()

      )

      {

        // Check whether the specified linetype is already in the

        // specified Database

 

        var lt =

          (LinetypeTable)tr.GetObject(

            db.LinetypeTableId, OpenMode.ForRead

          );

        if (!lt.Has(ltname))

        {

          // If not, load it from acad.lin

 

          string ltpath =

            HostApplicationServices.Current.FindFile(

              "acad.lin", db, FindFileHint.Default

            );

          db.LoadLineTypeFile(ltname, ltpath);

        }

 

        // Now go through and look for entities of the specified

        // class, changing each of their linetypes

 

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

        var ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForRead

          );

 

        // Loop through the modelspace

 

        foreach (var id in ms)

        {

          // Get each entity

 

          var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;

          if (ent != null)

          {

            // Check its type against the one specified

 

            var et = ent.GetType();

            if (et == t || et.IsSubclassOf(t))

            {

              // In the case of a match, change the linetype

 

              ent.UpgradeOpen();

              ent.Linetype = ltname;

            }

          }

        }

        tr.Commit();

      }

    }

  }

}

This technique is really useful: I can imagine lots of scenarios where in-place xref modification could be used to develop a valuable application feature. If you agree and have a particular scenario you think is worth sharing – or perhaps would like to see some sample code developed for – please post a comment!

5 responses to “Modifying the contents of an AutoCAD xref using .NET”

  1. Hi Kean,

    Can I know How to use XREF using Send command ,I m using only Interop here

    doc.SendCommand("XATTACH" + " " + Fname + " " + " " + "0,0,0" +" "); but its not working can u help me out.

    1. Hi Dilip,

      This is not a forum for support. Please post your questions to the AutoCAD .NET Discussion Group:

      forums.autodesk.com/

      Regards,

      Kean

      1. Hi Kean,
        I asked in forum as u said,we can use this method, AcadModelSpace/PaperSpace.AttachExternalReference() .

        regards,
        Dilip.

  2. Daniel Travaglia Dos Santos Avatar
    Daniel Travaglia Dos Santos

    HOW TO COPY A XREF PACK FOR CURRENT SPACE? HAVE SOME EXAMPLE? (DECULPE MY ENGLISH)

  3. Daniel Travaglia Dos Santos Avatar
    Daniel Travaglia Dos Santos

    HOW TO COPY A XREF "BLOCKREFERENCE" FOR CURRENT SPACE? HAVE SOME EXAMPLE? (DECULPE MY ENGLISH)

Leave a Reply to Dilip S Cancel reply

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