Как сделать отмену результата выполнения?

Разрабатываемые коды, как правило, что-то делают с файлом dwg: рисуют объекты, вносят изменения в таблицы файла и т.п. При этом могут меняться системные переменные, отрисовываться временные примитивы и т.п.
Все будет замечательно, если пользователь сделает все сразу и верно. А если нет? А если понадобится отменять выполненную команду?

Пользователю-то все просто: нажал один (подчеркиваю - один!) раз отмену, и его работа выполнена. "Команда была одна? Одна. Сколько надо отмен? По идее тоже одна",- так или примерно так рассуждает пользователь, и нельзя сказать, что он сильно неправ. При этом после выполненной отмены AutoCAD должен восстановить свое состояние. Такое поведение предсказуемо, и предсказуемость эта - одна из основ нормально работающей программы.
Рассмотрим элементарную задачу: нарисовать отрезок по 2 заранее известным точкам в указанном слое. Кстати, сразу заменить отрезку цвет и вес линии. Для большей наглядности работу будем выполнять командными методами. Опять же, для наглядности исключим всякие обработчики ошибок (подробнее здесь).
Точки: 0,0,0 - 100,10,0
Слой: имя слоя "Тест_123", цвет слоя - 1, вес линии - 0,25, тип линии - Continuous
Цвет отрезка: 2, вес линии отрезка - 0,50

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defun cmd (/ cecolor celweight)
(command "_.-layer" "_make"    "Тест_123" "_color"   1
""         "_lweight" 0.25       ""         ""
) ;_ end of command
(setq cecolor   (getvar "cecolor")
celweight (getvar "celweight")
) ;_ end of setq
(setvar "cecolor" "2")
(setvar "celweight" 50)
(command "_.line" "_none" '(0. 0. 0.) "_none" '(100. 10. 0.) "")
(setvar "cecolor" cecolor)
(setvar "celweight" celweight)
(princ)
) ;_ end of defun

Казалось бы, все просто и логично: сначала создается, активируется и настраивается слой, потом устанавливаются системные переменные и выполняется собственно рисование. Попробуем выполнить этот код в новом файле (где слой всего один с именем "0", вес и цвет линии - по слою, тип линии - только Continuous / Непрерывный).
Все получилось просто отлично, отрезок нарисован, слой для него установлен верно и так далее. А вот теперь начнем отменять выполненные действия. В AutoCAD, естественно:
[Ctrl]+[z] - отрезок исчез, это хорошо. А цвет и слой вернулись обратно? Нет.
[Ctrl]+[z] - вернулись настройки слоев.
А если бы выполнялось рисование не 1 отрезка, а сотни? А если между рисованием отрезков надо было бы опять слои менять? Представьте себе, какой это будет адский труд - отменить выполнение всего одной команды.
Подобную проблему надо решать. Надо? - Решим.
Как раз для таких случаев существует понятие меток отмены. Ставится одна метка в начале, потом вторая - в конце. Все, что между ними, AutoCAD понимает как "одно неделимое целое". И отменяет "чохом".
Существует два способа установки этих самых меток: командный и "программисткий" (через ActiveX). Оба метода достаточно подробно описаны здесь. Повторяться не хотелось бы.
С метками (как, впрочем, и со всем, чего касается программирование) следует обращаться достаточно аккуратно. Количество меток начала и конца отмены должно совпадать, иначе AutoCAD может, что называется, "развалиться" на ровном месте.
То есть коды вида

1
2
3
4
5
6
7
8
(defun cmd (/ cecolor celweight)
(command "_.undo" "_begin")
(command "_.-layer" "_make"    "Тест_123" "_color"   1
""         "_lweight" 0.25       ""         ""
) ;_ end of command
(command "_.line" "_none" '(0. 0. 0.) "_none" '(100. 10. 0.) "")
(princ)
) ;_ end of defun

или

1
2
3
4
5
6
7
8
9
10
11
(defun cmd (/ cecolor celweight)
(command "_.-layer" "_make"    "Тест_123" "_color"   1
""         "_lweight" 0.25       ""         ""
) ;_ end of command
(setq cecolor   (getvar "cecolor")
celweight (getvar "celweight")
) ;_ end of setq
(command "_.line" "_none" '(0. 0. 0.) "_none" '(100. 10. 0.) "")
(command "_.undo" "_end")
(princ)
) ;_ end of defun

неверны изначально. И подобных ситуаций надо избегать.
Лично я предпочитаю не использовать командные методы без крайней на то нужды (в силу разных причин - начиная с вопросов скорости выполнения и заканчивая проблемами переноса кода на BricsCAD, например). Поэтому дальнейшее тестирование будет идти с использованием либо entmake, entmakex, entmod и им подобных функций, либо с объектной моделью.
Итак, сделаем код, в котором будут установлены "вложенные" метки отмены (обращайте внимание на расположение vla-startundomark и vla-endundomark):

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
(defun test (/ fun adoc model)

  (defun fun ()
    (vla-startundomark adoc)
;;; Метка второго уровня, начало
    (vla-addtext
      model
      "Текст 2"
      (vlax-3d-point '(0. -5. 0.))
      2.5
      ) ;_ end of vla-addtext
    (vla-endundomark adoc) ;; Метка второго уровня, конец
    ) ;_ end of defun

  (vl-load-com)
  (vla-startundomark
    (setq adoc (vla-get-activedocument (vlax-get-acad-object)))
    ) ;_ end of vla-startundomark
;;; Метка верхнего уровня, начало
  (setq model (vla-get-modelspace adoc)
        text1 (vla-addtext
                model
                "Текст 1"
                (vlax-3d-point '(0. 0. 0.))
                2.5
                ) ;_ end of vla-addtext
        ) ;_ end of setq
  (fun)
  (vla-addtext
    model
    "Текст 3"
    (vlax-3d-point '(0. -10. 0.))
    2.5
    ) ;_ end of vla-addtext
  (vla-endundomark adoc) ;; Метка верхнего уровня, конец
  (princ)
  ) ;_ end of defun

Запустим код на выполнение. В принципе ничего сверхъестественного не происходит: в пространство модели активного документа добавляется 3 объекта текста. Но вот отмена выполнения уже не настолько очевидна.
Вызовем отмену. Удаляются объекты "Текст 3" и "Текст 2". То есть выполняется отмена вплоть до метки начала второго уровня (то есть отменяется и выполнение функции (fun)). Логично предположить, что AutoCAD находит код между последней vla-endundomark и ближайшей к ней по ходу выполнения vla-startundomark.
Если представить наш код в таком виде:

1
{Начало1 {Начало2 Конец2} Конец1}

То первая отмена будет выполняться так:

1
{Начало2 Конец2} Конец1}

И в результате мы получаем незавершенную марку Начало1. Как раз та ситуация, избегать которую мы договорились чуть выше.
Анализируя все вышесказанное, можно вывести рекомендацию: метки начала и отмены устанавливать только в функциях-"обертках" или командах, которые и будут вызывать пользователи. Установку меток отмены в функциях нижнего уровня, а также вложенных (локальных) функциях надо избегать.

Размещено в Код LISP · Метки: ,



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


Я не робот.