AutoCAD и реакторы
Известно, что в AutoCAD можно запрограммировать строго определенные действия, которые будут выполняться в ответ на какие-то действия CAD'a. Я не говорю об обычных пользовательских функциях и командах. Я говорю, например, о специфических реакциях на смену системных переменных, или клик мышкой, или выполнение штатной команды.
Такие вещи в lisp'е называются реакторами (в .NET, насколько я помню, аналогом становятся события). Каждый реактор знает свое событие и действие, которое он должен выполнять в ответ на это событие. Для создания, активизации или отключения реактора используются функции vlr-*.
:vlr-editor-reactor заменен на несколько реакторов:В общем и целом хотелось бы отметить несколько моментов:
- В реакторах не срабатывает обычный command или vl-cmdf. Если "с ножом к горлу" надо использовать командную строку (бывает такое), то следует использовать механизм vla-sendcommand для текущего документа
- Реакторы могут быть двух типов: постоянными и непостоянными. Понятно, что срабатывать и те, и другие будут только в том случае, если загружены соответствующие коды. Но вот какая штука - постоянные (persistent) реакторы прописываются в чертеже достаточно хитро.
Представим себе ситуацию: на компьютере №1 есть соответствующий код, человек работает с постоянными реакторами. На этом компьютере создан dwg и отдан в руки пользователя, который сидит за девственно чистым компьютером №2. В результате AutoCAD может запросто заявить, что файл требует восстановления и проверки. После _.audit и нормального _.-purge записи о постоянных реакторах уничтожаются (Поправка: записи не обязательно уничтожаются. Бывает, что записи можно удалить, только уничтожив все реакторы). По закону подлости файл попадает обратно на компьютер №1. Реакторы понадобится опять прописывать, регистрировать и т.д.
Учитывая, что неизвестно, с какими файлами приходится работать пользователю, подобные действия (прописывание, регистрация, активация и т.п.) для реакторов приходится все равно выполнять каждый раз. Так что особого смысла в использовании именно постоянных реакторов лично я не вижу.
Теперь попробуем создать элементарный реактор на команду:
1 2 3 4 5 6 7 8 9 | (setq vlr_react (vlr-command-reactor "Command reactor" (list '(:vlr-commandwillstart . start-command)) ) ;_ end of vlr-command-reactor ) ;_ end of setq (defun start-command (reactor execute-command) (princ (strcat "\n" (vlr-data reactor) " : control " (vl-princ-to-string execute-command))) ) ;_ end of defun |
Загрузим код к AutoCAD и выполним пару команд. Лог получается таким:
1 2 3 4 5 6 7 8 9 10 11 12 | Command: _LINE Command reactor : control (LINE) Specify first point: Specify next point or [Undo]: Specify next point or [Undo]: Command: _PLINE Command reactor : control (PLINE) Specify start point: Current line-width is 0.0000 Specify next point or [Arc/Halfwidth/Length/Undo/Width]: |
А что будет, если мы случайно (или нет - неважно) загрузим код повторно?
1 2 3 4 5 6 | Command: L LINE Command reactor : control (LINE) Command reactor : control (LINE)Specify first point: Specify next point or [Undo]: Specify next point or [Undo]: |
Обратите внимание: командный реактор сработал два раза! Получается, что командные реакторы надо каким-то образом "очищать" перед загрузкой (об этом чуть ниже).
Но здесь кроется еще один подводный камень: помимо контролируемых реакторов могут быть загружены сторонние коды, которые тоже используют командные реакторы. Что будет, если мы нарисуем два командных реактора на команду, к примеру, LINE: оба просто сигнализируют о срабатывании, но загружаются отдельными кодами.
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 | ;; Реактор 1 (setq vlr_react (vlr-command-reactor "Command reactor" (list '(:vlr-commandwillstart . start-command)) ) ;_ end of vlr-command-reactor ) ;_ end of setq (defun start-command (reactor execute-command) (cond ((= (strcase (car execute-command)) "LINE") (princ "\nCommandReactor. Command : LINE; #1") ) ) ;_ end of cond ) ;_ end of defun ;; Реактор 2 (setq vlr_react (vlr-command-reactor "Command reactor" (list '(:vlr-commandwillstart . start-command2)) ) ;_ end of vlr-command-reactor ) ;_ end of setq (defun start-command2 (reactor execute-command) (cond ((= (strcase (car execute-command)) "LINE") (princ "\nCommandReactor. Command : LINE; #2") ) ) ;_ end of cond ) ;_ end of defun |
В командной строке получим:
1 2 3 4 | Command: L LINE CommandReactor. Command : LINE; #1 CommandReactor. Command : LINE; #2Specify first point: |
Реакторы срабатывают в той последовательности, в которой они были загружены в AutoCAD.
Вернемся к "очистке" реакторов. Можно удалить все реакторы либо строго определенные.
Для удаления всех реакторов служит функция vlr-removeall, которая вызывается по принципу
1 | (vlr-remove-all [ТипРеактора]) |
Можно указать ТипРеактора (:vlr-command-reactor,:vlr-sysvar-reactor и т.п.) - и тогда код удалит все прописанные реакторы соответствующего типа. Я не уверен, коснется ли такая конструкция обработчиков событий, написанных на .NET - квалификации на создание подобных штук у меня не хватает Но лисповые конструкции точно будут удалены. Точнее, не удалены, а деактивированы.
Но! Если разрабатывается свое собственное приложение, которое, вполне возможно, будет работать совместно с дополнениями, неизвестно где и кем разработанными, то может потребоваться обрабатывать только строго определенные реакторы. Точнее, свои собственные реакторы - не затрагивая чужие. В таком случае начинаем использовать конструкцию
1 | (vlr-remove [VLR-объект]) |
Здесь VLR-объект - это (в нашем случае) значения переменной vlr_react. Допустим, у нас код написан неверно: на одну и ту же переменную "засунуты" разные реакторы. vlr_react сначала ссылается на start-command, а потом на start-command2. Если сейчас мы выполним
1 | (vlr-remove vlr_react) |
то мы деактивируем только второй реактор. До первого будет просто не достучаться:
1 2 3 4 | Command: l LINE CommandReactor. Command : LINE; #1Specify first point: Specify next point or [Undo]: Specify next point or [Undo]: |
Соответственно для безболезненного использования собственных реакторов необходимо использовать конструкцию типа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (if vlr-cmd (progn (vlr-remove vlr-cmd) (setq vlr-cmd nil) ) ;_ end of progn ) ;_ end of if (if (not vlr-cmd) (setq vlr-cmd (vlr-command-reactor "kpblc-command-reactor" '((:vlr-commandwillstart . command-start) (:vlr-commandended . command-end) (:vlr-commandcancelled . command-cancel) (:vlr-commandfailed . command-fail) ) ) ;_ end of vlr-command-reactor ) ;_ end of setq ) ;_ end of if |
Ну а после этого уже загружать соответствующие функции. Для реакторов системных переменных принципиально подход не меняется - достаточно заменить слово "command" на "sysvar"