Удаление слоя
Понадобилось мне тут удалить слой из файла. На слое, скорее всего, есть объекты. Че будем делать? Правильно, писать код
Общая логика вроде бы проста - снимаем блокировку и заморозку слоя, проходим по коллекции блоков, потом по составу каждого блока (если он не является внешней ссылкой), проверяем слой объекта, при необходимости удаляем объект, в конце - удаление слоя. Что еще... А, если объект - блок, то если он не лежит на удаляемом слое, пройтись по его атрибутам и удалить те, которые лежат на удаляемом слое. Вроде все? Ах, да! Работа выполняется только в текущем документе.
Я попробовал набросать код, благо он несложный:
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# играться?
Код на C# тоже как лисп загружать? Че-то не работает. Пишет нет такой команды - kpblc-layer-erase
Код надо компилировать в dll, а потом уже загружать через netload.
Только начинаю разбираться с C#
Если не затруднит дай пожалуйста пример вызова, не знаю, как получить входной параметр типа ResultBuffer
Вызов из-под лиспа (при условии, что dll скомпилирована и загружена в ACAD):
[cc lang="cadlisp"](_kpblc-layer-erase "ИмяСлояДляУдаления")[/cc]
Или вопрос был в том, как в C# обработать переданные параметры?
Другой вопрос, что Дима Загорулькин показал мне другой, более правильный вариант C#-кода (который, я надеюсь, он как-нибудь где-нибудь опубликует).
Спасибо!
Думал, что то не так делаю...
крашится автокад 2021, надо по шагам отладку запускать.
--------------
Конечная цель не удаление а laymrg
Разбираюсь
ИМХО технология примерно такая же - пройтись по всем примитивам, проверить старый слой, установить новый. Потом попробовать удалить запись о старом слое в таблице слоев
Вот и взял твой код за образец (списать)
Лисп попробовал весьма шустро работает!!!
Net то же домучу.
Кстати, а чего ты не проверяешь в начале слой на purge, а сразу идешь перебирать объекты?
Мож на слое ничего и нету, и его можно сразу удалять
Можно, конечно, но 2 года назад не сообразил
поменял
LayerTableRecord layer = (LayerTableRecord)tr.GetObject(idLayerDelete, OpenMode.ForWrite); //было ForRead
layer.IsFrozen = false; //иначе на этой строчке фаталил автокад 2021
------------
действительно работает очень шустро.
Надо в конце еще реген добавить
Дима дал разрешение на публикацию, чем я и воспользуюсь
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;
}
}
}
И я очень сильно подозреваю, что вариант Димы будет как минимум а) быстрее и б) устойчивее.