Особенности vla-функций и их применения, часть 3
В предыдущих частях (здесь и здесь) велся разговор о графических примитивах. Здесь немного продолжим о них, но основной упор сделаем на неграфических элементах dwg-файла (стилях, слоях, описаниях блоков и т.п.).
В части 2 был показан вариант внесения изменений в свойство Normal (группа 210) примитива. А что будет, если заменить свойство слоя (Layer; DXF-группа 8)? Особенно для несуществующего слоя?
Если подобное выполнять через entmod, то слой будет создан. Обычно создается слой по аналогии с текущими настройками слоя "0", но, говорят, "бывают варианты". А вот vla-операции подобных вольностей не позволяют, и придется сначала слой создать, и только потом его назначать:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (defun lispru-ent-modify-ename (/ ent layer) (setq layer "Layer_Ent" ent (entmakex '((0 . "LINE") (10 0. 0. 0.) (11 10. 100. 0.) ) ) ;_ end of entmakex ) ;_ end of setq (entmod (entmod (subst (cons 8 layer) (assoc 8 (entget ent)) (entget ent) ) ;_ end of subst ) ;_ end of entmod ) ;_ end of entmod ) ;_ end of defun |
Выполним код в пустом файле и проверим: действительно, отрезок создан, и получил слой Layer_Ent. А теперь попробуем аналогичное сделать для vla-функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (defun lispru-ent-modify-vla1 (/ ent layer) (vl-load-com) (setq layer "Layer_Vla" ent (vla-addline (vla-get-modelspace (vla-get-activedocument (vlax-get-acad-object)) ) ;_ end of vla-get-ModelSpace (vlax-3d-point '(100. 0. 0.)) (vlax-3d-point '(110. 100. 0.)) ) ;_ end of vla-addline ) ;_ end of setq (vla-put-layer ent layer) ) ;_ end of defun |
Попытка выполнить такой код выдаст ошибку:
1 2 | Command: (lispru-ent-modify-vla1) ; error: Automation Error. Key not found |
Поэтому код надо переделывать. Перед тем, как вносить изменения, попробуем немного "потеоритизировать". Как было сказано, "Суха, мой друг, теория всегда". Поэтому будем теоретизировать и ставить эксперименты одновременно. Поскольку споткнулись мы на ActiveX-обработке, с ней и будем воевать.
Создадим слой с именем "1":
1 2 | _$ (setq layer (vla-add (vla-get-layers (vla-get-ActiveDocument (vlax-get-acad-object))) "1")) #<VLA-OBJECT IAcadLayer 0bd43c04> |
Как известно, каждый элемент файла dwg имеет уникальную метку (handle, или "хендл"). Посмотрим хендл только что созданного слоя:
1 2 | _$ (vla-get-handle layer) "1F5" |
Это нам понадобится немного позже.
А вот что будет, если повторно выполнить "создание" слоя? То есть
1 2 | _$ (setq layer1 (vla-add (vla-get-layers (vla-get-ActiveDocument (vlax-get-acad-object))) "1")) #<VLA-OBJECT IAcadLayer 0bd43d94> |
Ух ты, а слой-то повторно создан, что ли? Да нет, не создан он. Просто получен новый указатель на него. Это будет видно, если мы уже для layer1 попробуем получить хендл:
1 2 | _$ (vla-get-handle layer1) "1F5" |
Казалось бы, все становится просто и понятно: надо или нет, все равно создаем новые слои, т.к. для случая их отсутствия слой будет создан; а для случая их существования - получен указатель на него.
Но лично я не рекомендовал бы такое делать. Честно говоря, такой код мне не кажется красивым. Для подобных вариантов я предпочитаю делать по-другому: попытаться получить сначала указатель на существующий слой, и, если его нет, то слой создавать.
Вариантов получения указателя на слой для vla-варианта несколько:
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 | ;;; Во-первых, через vl-catch-* (defun lispru-get-or-create-layer-by-error-catch (doc name / res) ;| * Получение или создание указателя на элемент коллекции через * отлов ошибок * Параметры вызова: doc указатель на обрабатываемый документ. nil -> текущий name имя создаваемого слоя * Возвращает vla-указатель на созданный или существующий слой |; (if (not doc) (setq doc (vla-get-activedocument (vlax-get-acad-object))) ) ;_ end of if (if (vl-catch-all-error-p (setq res (vl-catch-all-apply (function (lambda () (vla-item (vla-get-layers doc) name) ) ;_ end of lambda ) ;_ end of function ) ;_ end of vl-catch-all-apply ) ;_ end of setq ) ;_ end of vl-catch-all-error-p (setq res (vla-add (vla-get-layers doc) name)) ) ;_ end of if res ) ;_ end of defun |
Достаточно простое и очевидное решение, но в некоторых случаях неприемлемое. Проблема возникает там, где ее совсем не ждешь.
Представим себе, что в vl-catch-* "обернуто" выполнение некоей функции, которая сама вызывает vl-catch-*, а та уже, в свою очередь - вызывает lispru-get-or-create-layer-by-error-catch. То есть vl-catch-* вложены друг в друга.
В некоторых версиях AutoCAD и/или вертикальных приложениях уровень подобных вложений не может превышать 4-5. Не знаю, с чем это связано, но столкнуться с этим в условиях полноценной эксплуатации кода весьма неприятно. Возможно, сейчас эта проблема уже решена, не тестировал. Мне оказалось проще выполнять немного другой код:
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 | (defun lispru-get-or-create-layer-by-list (doc name /) ;| * Получение или создание указателя на элемент коллекции через * список слоев * Параметры вызова: doc указатель на обрабатываемый документ. nil -> текущий name имя создаваемого слоя * Возвращает vla-указатель на созданный или существующий слой |; (if (not doc) (setq doc (vla-get-activedocument (vlax-get-acad-object))) ) ;_ end of if (if (member (strcase name) (mapcar (function (lambda (x) (strcase x) ) ;_ end of lambda ) ;_ end of function ((lambda (/ lst) (vlax-for item (vla-get-layers doc) (setq lst (cons (vla-get-name item) lst)) ) ;_ end of vlax-for lst ) ;_ end of lambda ) ) ;_ end of mapcar ) ;_ end of member (vla-item (vla-get-layers doc) name) (vla-add (vla-get-layers doc) name) ) ;_ end of if ) ;_ end of defun |
Памятуя о времени выполнения, проведем тестирование:
1 2 3 4 5 | _$ (benchmark '((lispru-get-or-create-layer-by-error-catch nil "Layer1") (lispru-get-or-create-layer-by-list nil "Layer2"))) Benchmarking ...............Elapsed milliseconds / relative speed for 4096 iteration(s): (LISPRU-GET-OR-CREATE-LAYER-BY-ERROR...).....2031 / 2.54 <fastest> (LISPRU-GET-OR-CREATE-LAYER-BY-LIST ...).....5156 / 1 <slowest> |
Как видим, в "пляжных" условиях (файл пустой, функция тестируется без вложенности и т.п.) выполнение через error-catch намного быстрее (по крайней мере у меня на компьютере). Но, вспоминая об ошибке вложений, я бы не рисковал. Если хотите - проведите несколько опытов. Может, Вам повезет больше чем мне.