Unlocking layers for certain AutoCAD commands using .NET

This question came up during last week's accelerator, and is part of the reason I spent creating the last post: is it possible to selectively unlock certain layers for the duration of commands that have been specified by the user? Let's take an example: you have layers that should remain locked, apart from when using the MOVE command (COPY and ERASE should not work).

The approach I took was to maintain a dictionary mapping command names to lists of layers to unlock. When a command is launched – which we can tell using the Document.CommandWillStart event – we check whether it has layers associated with it. If so, we unlock them for the duration of the command and lock them again on command completion.

I knew we'd want a map per document, so I went and used the approach from this post. Something I think people will find useful: rather than maintaining separate code to check for documents being created and closed, I used the "doc data" class itself to attach the command-tracking event handlers to documents when they're opened, and its "dispose" method to remove them when closed. This makes the code much simpler, I'm sure you'll agree.

Here's the C# code that implements the UFC command, as well as having the LL and UL commands (that use the same, slightly modified extension method) we saw last time.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Collections;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

[assembly: PerDocumentClass(typeof(LayerManipulation.UnlockCommands))]

 

namespace LayerManipulation

{

  public class UnlockCommands : System.IDisposable

  {

    public const string RecordName = "TtifLayerUnlockData";

 

    // Map commands to layers to unlock and relock

 

    public Dictionary<string, ObjectIdCollection> CommandMap =

      new Dictionary<string, ObjectIdCollection>();

 

    private Document _doc = null;

    private bool _disposed = false;

 

    // Constructor

 

    public UnlockCommands(Document doc)

    {

      _doc = doc;

 

      doc.UserData.Add(RecordName, this);

 

      // Add our command handlers

 

      doc.CommandWillStart += OnCommandWillStart;

      doc.CommandEnded += OnCommandEnded;

      doc.CommandCancelled += OnCommandEnded;

      doc.CommandFailed += OnCommandEnded;

    }

 

    void OnCommandWillStart(object sender, CommandEventArgs e)

    {

      var doc = (Document)sender;

      var uc = doc.UserData[UnlockCommands.RecordName] as UnlockCommands;

 

      // If the command ending is in our list, unlock the layer(s)

 

      if (uc != null && uc.CommandMap.ContainsKey(e.GlobalCommandName))

      {

        doc.LockOrUnlockLayers(false, uc.CommandMap[e.GlobalCommandName], false);

      }

    }

 

    void OnCommandEnded(object sender, CommandEventArgs e)

    {

      var doc = (Document)sender;

      var uc = doc.UserData[UnlockCommands.RecordName] as UnlockCommands;

 

      // If the command ending is in our list, relock the layer(s)

 

      if (uc != null< /span> && uc.CommandMap.ContainsKey(e.GlobalCommandName))

      {

        doc.LockOrUnlockLayers(true, uc.CommandMap[e.GlobalCommandName], false);

      }

    }

 

    public void Dispose()

    {

      Dispose(true);

      GC.SuppressFinalize(this);          

    }

 

    protected virtual void Dispose(bool disposing)

    {

      if (_disposed)

        return;

 

      // Remove our command handlers

 

      _doc.CommandWillStart -= OnCommandWillStart;

      _doc.CommandEnded -= OnCommandEnded;

      _doc.CommandCancelled -= OnCommandEnded;

      _doc.CommandFailed += OnCommandEnded;

 

      _disposed = true;

    }

  }

 

  public static class Extensions

  {

    public static void LockOrUnlockLayers(

      this Document doc, bool dolock,

      ObjectIdCollection layers = null, bool ignoreCurrent = true,

      bool lockZero = false

    )

    {

      var db = doc.Database;

      var ed = doc.Editor;

 

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

      {

        var layerIds =

          (layers != null ? (IEnumerable)layers :

            (IEnumerable)tr.GetObject(db.LayerTableId, OpenMode.ForRead));

 

        foreach (ObjectId ltrId in layerIds)

        {

          // Don't try to lock/unlock either the current layer or layer 0

          // (depending on whether lockZero == true for the latter)

 

          if (

            (!ignoreCurrent || ltrId != db.Clayer) &&

            (lockZero || ltrId != db.LayerZero)

          )

          {

            // Open the layer for write and lock/unlock it

 

            var ltr = (LayerTableRecord)tr.GetObject(ltrId, OpenMode.ForWrite);

            ltr.IsLocked = dolock;

            ltr.IsOff = ltr.IsOff; // This is needed to force a graphics update

          }

        }

        tr.Commit();

      }

 

      // These two calls will result in the layer's geometry fading/unfading

      // appropriately

 

      ed.ApplyCurDwgLayerTableChanges();

      ed.Regen();

    }

 

    public static ObjectIdCollection SelectLayers(this Document doc)

    {

      var db = doc.Database;

      var ed = doc.Editor;

 

      // A list of the layers' names
& IDs contained

      // in the current database, sorted by layer name

 

      var ld = new SortedList<string, ObjectId>();

 

      // A list of the selected layers' IDs

 

      var lids = new ObjectIdCollection();

 

      // Start by populating the list of names/IDs

      // from the LayerTable

 

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

      {

        var lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);

        foreach (ObjectId lid in lt)

        {

          var ltr = (LayerTableRecord)tr.GetObject(lid, OpenMode.ForRead);

          ld.Add(ltr.Name, lid);

        }

      }

 

      // Display a numbered list of the available layers

 

      ed.WriteMessage("\nLayers available:");

 

      ed.SelectNamedLayers(ld, lids);

 

      return lids;

    }

 

    private static void SelectNamedLayers(

      this Editor ed,

      SortedList<string, ObjectId> ld,

      ObjectIdCollection lids

    )

    {

      int i = 1;

      foreach (KeyValuePair<string, ObjectId> kv in ld)

      {

        ed.WriteMessage("\n{0} - {1}", i++, kv.Key);

      }

 

      // We will ask the user to select from the list

 

      var pio = new PromptIntegerOptions("\nEnter number of layer to add: ");

      pio.LowerLimit = 1;

      pio.UpperLimit = ld.Count;

      pio.AllowNone = true;

 

      // And will do so in a loop, waiting for Escape or Enter to terminate

 

      PromptIntegerResult pir;

      do

      {

        // Select one from the list

 

        pir = ed.GetInteger(pio);

 

        if (pir.Status == PromptStatus.OK)

        {

          // Get the layer's name

 

          string ln = ld.Keys[pir.Value - 1];

 

          // And then its ID

 

          ObjectId lid;

          ld.TryGetValue(ln, out lid);

 

          // Add the layer'd ID to the list, if it's not already on it

 

          if (lids.Contains(lid))

          {

            ed.WriteMessage("\nLayer \"{0}\" has already been selected.", ln);

          }

          else

          {

           
; lids.Add(lid);

            ed.WriteMessage("\nAdded \"{0}\" to selected layers.", ln);

          }

        }

      } while (pir.Status == PromptStatus.OK);

    }

  }

 

  public class Commands

  {

    [CommandMethod("LL")]

    public void LockLayers()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

 

      doc.LockOrUnlockLayers(true);

    }

 

    [CommandMethod("UL")]

    public void UnlockLayers()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

 

      doc.LockOrUnlockLayers(false);

    }

 

    [CommandMethod("UFC")]

    public void UnlockForCommands()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

      var ed = doc.Editor;

 

      // Store the selected commands in a list

 

      var cmds = new List<string>();

 

      PromptResult pr;

      bool cmdEntered;

      do

      {

        pr = ed.GetString("\nEnter name of command for which to unlock layers");

        cmdEntered =

          pr.Status == PromptStatus.OK && !String.IsNullOrEmpty(pr.StringResult);

        if (cmdEntered)

          cmds.Add(pr.StringResult.ToUpper());

      }

      while (cmdEntered);

 

      if (pr.Status == PromptStatus.Cancel)

        return;

 

      var ids = doc.SelectLayers();

 

      var uc = doc.UserData[UnlockCommands.RecordName] as UnlockCommands;

      foreach (string cmd in cmds)

      {

        if (uc.CommandMap.ContainsKey(cmd))

        {

          var existing = uc.CommandMap[cmd];

          foreach (ObjectId id in ids)

          {

            if (!existing.Contains(id))

            {

              existing.Add(id);

            }

          }

        }

        else

        {

          uc.CommandMap.Add(cmd, ids);

        }

      }

 

      if (cmds.Count > 0 && ids.Count > 0)

  &nb
sp;   {

        ed.WriteMessage(

          "\n{0} command{1} will now unlock {2} layers.",

          cmds.Count, cmds.Count == 1 ? "" : "s", ids.Count

        );

      }

    }

  }

}

 

Here's the UFC command in action:

Unlock for commands

You can see that when we MOVE the layers unlock – relocking again, afterwards – but the same doesn't happen for COPY.

Leave a Reply

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