Удаление слоя

Понадобилось мне тут удалить слой из файла. На слое, скорее всего, есть объекты. Че будем делать? Правильно, писать код :)

Общая логика вроде бы проста - снимаем блокировку и заморозку слоя, проходим по коллекции блоков, потом по составу каждого блока (если он не является внешней ссылкой), проверяем слой объекта, при необходимости удаляем объект, в конце - удаление слоя. Что еще... А, если объект - блок, то если он не лежит на удаляемом слое, пройтись по его атрибутам и удалить те, которые лежат на удаляемом слое. Вроде все? Ах, да! Работа выполняется только в текущем документе.

Я попробовал набросать код, благо он несложный:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
(vl-load-com)

(defun _kpblc-layer-erase-vla (name / _kpblc-conv-vla-to-list adoc clayer layer_del) ;|
*    Удаление слоя по имени.
*    Параметры вызова:
  name    имя слоя для удаления
|;

  (defun _kpblc-conv-vla-to-list (value / res)
    (cond ((listp value) (mapcar (function _kpblc-conv-vla-to-list) value))
          ((= (type value) 'variant) (_kpblc-conv-vla-to-list (vlax-variant-value value)))
          ((= (type value) 'safearray)
           (if (>= (vlax-safearray-get-u-bound value 1) 0)
             (_kpblc-conv-vla-to-list (vlax-safearray->list value))
             ) ;_ end of if
           )
          ((and (member (type value) (list 'ename 'str 'vla-object))
                (= (type (_kpblc-conv-ent-to-vla value)) 'vla-object)
                (vlax-property-available-p (_kpblc-conv-ent-to-vla value) 'count)
                ) ;_ end of and
           (vlax-for sub (_kpblc-conv-ent-to-vla value) (setq res (cons sub res)))
           )
          (t value)
          ) ;_ end of cond
    ) ;_ end of defun
  (if (and name
           (/= name "0")
           (not (wcmatch name "*|*"))
           (setq adoc (vla-get-activedocument (vlax-get-acad-object)))
           (= (type
                (setq layer_del (vl-catch-all-apply (function (lambda () (vla-item (vla-get-layers adoc) name)))))
                ) ;_ end of type
              'vla-object
              ) ;_ end of =
           ) ;_ end of and
    (progn (vla-startundomark adoc)
           (setq name (strcase name))
           (if (= (strcase (getvar "clayer")) name)
             (setvar "clayer" "0")
             ) ;_ end of if
           (vla-put-lock layer_del :vlax-false)
           (vla-put-freeze layer_del :vlax-false)
           (vlax-for blk_def (vla-get-blocks adoc)
             (if (equal (vla-get-isxref blk_def) :vlax-false)
               (progn
                 (vlax-for ent blk_def
                   (if (= (strcase (vla-get-layer ent)) name)
                     (vla-erase ent)
                     (if (wcmatch (vla-get-objectname ent) "*BlockRef*")
                       (foreach att (apply (function append)
                                           (mapcar (function (lambda (x)
                                                               (if (vlax-method-applicable-p ent x)
                                                                 (_kpblc-conv-vla-to-list (vlax-invoke-method ent x))
                                                                 ) ;_ end of if
                                                               ) ;_ end of lambda
                                                             ) ;_ end of function
                                                   '("getattributes" "getconstantattributes")
                                                   ) ;_ end of mapcar
                                           ) ;_ end of apply
                         (if (= (strcase (vla-get-layer att)) name)
                           (vla-erase att)
                           ) ;_ end of if
                         ) ;_ end of foreach
                       ) ;_ end of if
                     ) ;_ end of if
                   ) ;_ end of vlax-for
                 ) ;_ end of progn
               ) ;_ end of if
             ) ;_ end of vlax-for
           (vla-delete layer_del)
           (vla-endundomark adoc)
           (princ)
           ) ;_ end of progn
    ) ;_ end of if
  ) ;_ end of defun

... и время на обработку всего-то 3-метрового файла составило в некомпилированном варианте почти 4 секунды. Подозреваю, что использование ename-представлений сильно ситуацию не поменяет (но могу и ошибаться). И при этом вызов стандартной _.-laydel срабатывал практически моментально!

У _.-laydel есть другой недостаток: она выводит отчет в ком.строку. При этом попытки погасить вывод, меняя nomutt, cmdecho и menuecho никакого эффекта (по крайней мере у меня) не оказали. Ну что ж, попробуем на C# что-нибудь нарисовать. Нуачо, забавно же! Заодно и потренируюсь ;)

Как результат (логику не менял):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using System;

namespace NetToLisp
{
  public class LayerErase
  {
    [LispFunction("_kpblc-layer-erase")]
    public static bool LispEraseLayer(ResultBuffer arguments)
    {
      Array args = arguments.AsArray();
      bool res = false;
      if (args.Length == 1)
      {
        string sLayerName = ((TypedValue)(args.GetValue(0))).Value.ToString();
        if (sLayerName != "0")
        {
          Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
          Database db = doc.Database;        

          using (Transaction tr = db.TransactionManager.StartTransaction())
          {
            LayerTable layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
            if (layerTable.Has(sLayerName))
            {
              ObjectId idLayerDelete = layerTable[sLayerName];
              ObjectId idCurrentLayer = db.Clayer;

              if (idCurrentLayer == idLayerDelete)
              {
                db.Clayer = layerTable["0"];
              }

              LayerTableRecord layer = (LayerTableRecord)tr.GetObject(idLayerDelete, OpenMode.ForRead);
              BlockTable blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

              layer.IsLocked = false;
              layer.IsFrozen = false;

              foreach (ObjectId btrId in blockTable)
              {
                BlockTableRecord blockTableRecord = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
                if (!blockTableRecord.IsFromExternalReference)
                {
                  foreach (ObjectId entId in blockTableRecord)
                  {
                    Entity ent = (Entity)tr.GetObject(entId, OpenMode.ForRead);
                    if (ent.Layer.ToUpper() == sLayerName.ToUpper())
                    {
                      ent.UpgradeOpen();
                      ent.Erase();
                    }
                    else
                    {
                      BlockReference blkRef = ent as BlockReference;
                      if (blkRef != null)
                      {
                        foreach (ObjectId attId in blkRef.AttributeCollection)
                        {
                          AttributeReference att = (AttributeReference)tr.GetObject(attId, OpenMode.ForRead);
                          if (att.Layer.ToUpper() == sLayerName.ToUpper())
                          {
                            att.UpgradeOpen();
                            att.Erase();
                          }
                        }
                      }
                    }
                  }
                }
              }
              layer.UpgradeOpen();
              layer.Erase();

              res = true;
              tr.Commit();
            }
          }
        }
      }
      return res;
    }
  }
}

Скорость - весьма впечатляет! Блин, действительно, что ли, начать на C# играться? :)

Размещено в .NET, Код LISP, Функции LISP · Метки: , ,



Комментарии

Есть 11 коммент. к “Удаление слоя”
  1. Елена пишет:

    Код на C# тоже как лисп загружать? Че-то не работает. Пишет нет такой команды - kpblc-layer-erase

  2. Кулик Алексей aka kpblc пишет:

    Код надо компилировать в dll, а потом уже загружать через netload.

  3. doctorraz пишет:

    Только начинаю разбираться с C#
    Если не затруднит дай пожалуйста пример вызова, не знаю, как получить входной параметр типа ResultBuffer

  4. Кулик Алексей aka kpblc пишет:

    Вызов из-под лиспа (при условии, что dll скомпилирована и загружена в ACAD):
    [cc lang="cadlisp"](_kpblc-layer-erase "ИмяСлояДляУдаления")[/cc]

    Или вопрос был в том, как в C# обработать переданные параметры?

    Другой вопрос, что Дима Загорулькин показал мне другой, более правильный вариант C#-кода (который, я надеюсь, он как-нибудь где-нибудь опубликует).

  5. doctorraz пишет:

    Спасибо!
    Думал, что то не так делаю...
    крашится автокад 2021, надо по шагам отладку запускать.
    --------------
    Конечная цель не удаление а laymrg
    Разбираюсь

  6. Кулик Алексей aka kpblc пишет:

    ИМХО технология примерно такая же - пройтись по всем примитивам, проверить старый слой, установить новый. Потом попробовать удалить запись о старом слое в таблице слоев

  7. doctorraz пишет:

    Вот и взял твой код за образец (списать)
    Лисп попробовал весьма шустро работает!!!
    Net то же домучу.
    Кстати, а чего ты не проверяешь в начале слой на purge, а сразу идешь перебирать объекты?
    Мож на слое ничего и нету, и его можно сразу удалять

  8. Кулик Алексей aka kpblc пишет:

    Можно, конечно, но 2 года назад не сообразил ;)

  9. doctorraz пишет:

    поменял
    LayerTableRecord layer = (LayerTableRecord)tr.GetObject(idLayerDelete, OpenMode.ForWrite); //было ForRead

    layer.IsFrozen = false; //иначе на этой строчке фаталил автокад 2021
    ------------
    действительно работает очень шустро.
    Надо в конце еще реген добавить

  10. Кулик Алексей aka kpblc пишет:

    Дима дал разрешение на публикацию, чем я и воспользуюсь :)


    using Autodesk.AutoCAD.ApplicationServices;
    using Autodesk.AutoCAD.DatabaseServices;
    using Autodesk.AutoCAD.Runtime;
    // Комментарии и разбор - Загорулькин Дмитрий https://www.youtube.com/channel/UCZqgdyZEplDlXagGv6OlmdA
    namespace NetToLisp
    {
    public class LayerErase
    {
    [LispFunction("_kpblc-layer-erase")]
    public static bool LispEraseLayer(ResultBuffer arguments)
    {
    //Array args = arguments.AsArray();
    TypedValue[] args = arguments.AsArray();
    bool res = false;
    if (args.Length == 1 && args[0].TypeCode == (int)LispDataType.Text)
    {
    //string sLayerName = ((TypedValue)(args.GetValue(0))).Value.ToString();
    string sLayerName = args[0].Value.ToString();

    // Тут ещё добавил проверку на пустоту строки на всякий случай - не знаю,
    // как поведёт себя layerTable.Has, если ему сунуть пустую строку... Может и зря паникую
    if (!string.IsNullOrEmpty(sLayerName) && sLayerName != "0")
    {
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;

    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
    LayerTable layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
    if (layerTable.Has(sLayerName))
    {
    ObjectId idLayerDelete = layerTable[sLayerName];
    ObjectId idCurrentLayer = db.Clayer;

    if (idCurrentLayer == idLayerDelete)
    {
    //db.Clayer = layerTable["0"];
    db.Clayer = db.LayerZero;
    }

    // Здесь слой открывать не нужно, сделаем это перед удалением
    //LayerTableRecord layer = (LayerTableRecord)tr.GetObject(idLayerDelete, OpenMode.ForRead);
    BlockTable blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

    //layer.IsLocked = false; // < - Вот тут, по идее, должно упасть. Объект layer открыт на чтение.
    //layer.IsFrozen = false; // <- Вот тут, по идее, должно упасть. Объект layer открыт на чтение.
    // Вообще, в .NET API об этом можно не париться. Достаточно использовать перегрузку метода открытия объекта:
    // tr.GetObject(id, OpenMode.ForWrite, false, true) <- последняя true - это разрешить оперировать объектами на блокированных слоях
    // а то что слой выключен или заморожен, вроде как, .NETу фиолетово (хотя, могу ошибаться - лучше перепроверить)

    foreach (ObjectId btrId in blockTable)
    {
    BlockTableRecord blockTableRecord = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
    if (!blockTableRecord.IsFromExternalReference)
    {
    foreach (ObjectId entId in blockTableRecord)
    {
    //Entity ent = (Entity)tr.GetObject(entId, OpenMode.ForRead);
    // Использовать преобразование через as безопаснее. Если объект не того типа - то будет просто null.
    // А если использовать преобразование типа через скобочки спереди (Entity), то если объект вдруг окажется не Entity,
    // то будет исключение и сбой. В этом случае такое маловероятно, но в других - вполне возможно
    Entity ent = tr.GetObject(entId, OpenMode.ForRead, false, true) as Entity;

    //if (ent.Layer.ToUpper() == sLayerName.ToUpper())
    if (ent.LayerId.Equals(idLayerDelete))
    {
    // У транзакций есть один косяк - не любят этот метод
    // Я не ловил с этим пролем, но вроде как они есть
    // Исправлять не буду, если интересно - почитай: https://adn-cis.org/v-autocad-2018.1-metod-upgradeopen-privodit-k-fatalnoj-oshibke-vnutri-tranzakczii.html
    ent.UpgradeOpen();
    ent.Erase();
    }
    //else

    // Можно одним выражением "убить двух зайцев" - и проверить что это блок и сохранить его в переменной
    else if (ent is BlockReference blkRef)
    {
    //BlockReference blkRef = ent as BlockReference;
    //if (blkRef != null)
    //{
    // Что-то мне кажется это неправильно - удалять атрибуты блока, оставляя при этом сам блок...
    // Может, лучше их на другой слой кидать?
    foreach (ObjectId attId in blkRef.AttributeCollection)
    {
    //AttributeReference att = (AttributeReference)tr.GetObject(attId, OpenMode.ForRead);
    AttributeReference att = tr.GetObject(attId, OpenMode.ForRead, false, true) as AttributeReference;
    //if (att.Layer.ToUpper() == sLayerName.ToUpper())
    // Добавил проверку на null на всякий случай
    if (att != null && att.LayerId.Equals(idLayerDelete))
    {
    att.UpgradeOpen();
    att.Erase();
    }
    }
    //}
    }
    }
    }
    }

    LayerTableRecord layer = (LayerTableRecord)tr.GetObject(idLayerDelete, OpenMode.ForWrite);
    //layer.UpgradeOpen();
    layer.Erase();

    res = true;
    }
    tr.Commit();
    }
    }
    }
    return res;
    }
    }
    }

  11. Кулик Алексей aka kpblc пишет:

    И я очень сильно подозреваю, что вариант Димы будет как минимум а) быстрее и б) устойчивее.

Поделитесь своим мнением


Я не робот.