Прогресс-бары или о длительных операциях
Иногда необходимо выполнить длительные вычисления - например, обработать несколько десятков (а то и сотен) файлов, вызвать стороннее приложение и в нем что-то сделать и т.п. Если работать через LISP, то AutoCAD (с точки зрения пользователя) "зависает".
Чтобы этого не было, процесс выполнения надо хоть как-то показывать. Некоторые тонкости этой задачки и хотелось бы рассмотреть.
Чуть ли не самый интересный и стандартный метод - показ некоего прогресс-бара. Для разработчиков на arx / .NET соответствующие классы уже давно существуют, но и лиспописатели могут кое-что сделать.
Некоторый набор уже имеющихся решений:
Тема на аркаде:
Код от VVA:
1 2 3 4 5 6 7 8 9 10 | (defun C:TEST () (setq i 0 n 5000 ) ;_ end of setq (repeat n (grtext -1 (strcat "№ " (itoa (setq i (1+ i))) " из " (itoa n))) ) ;_ end of repeat (getstring "\nPress enter to continue") (vl-cmdf "_.redrawall") ) ;_ end of defun |
Тема на dwg.ru;
Autodesk discussion
Рано или поздно вопрос переводится либо к рисованию собственного dcl-окна с изменяемым image-окном, либо использование сторонних arx-приложений (например, чертовски многофункциональный прогресс-бар от А.Ривилиса или решение от DosLib), либо использование ExpressTools-функций.
У всех этих решений лично я нашел только одно ограничение (все тестировалось в AutoCAD 2008, в 2009 и дальше я использовал только ExpressTools): если используем стандартный ProgressBar в статусной строке AutoCAD, то количество операций в нем не может превышать 32-с-чем-то-там-тысяч (ну да, там 32-битные числа, что уж тут поделаешь). А у меня несколько раз была ситуация, когда этого оказывалось маловато (и не надо говорить, что я извращенец - я и так это знаю :)).
Чуть ли не основная задача при использовании прогресс-бара - вычисление полного количества циклов, для которых надо выполнять обновление прогресс-бара. И при этом надо предусмотреть ситуацию, когда вроде бы и посчитали, но немного ошиблись - причем не в меньшую сторону (это-то не страшно: ну закрылся прогресс чуть раньше - делов-то), а в большую (при неудачном решении прогресс может "замереть").
В результате некоторых экспериментов родился такой код:
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 | (defun _lispru-progress-start (msg range) ;| * Инициализирует прогресс-бар * Параметры вызова: msg показываемое сообщение range общая длина прогресс-бара |; (cond ((and msg acet-ui-progress) (acet-ui-progress msg (min 32000 range)) ) ((and (not msg) acet-ui-progress) (acet-ui-progress (min 32000 range)) ) ((and msg progressbar) (progressbar msg (min 32000 range)) ) ((and (not msg) progressbar) (progressbar (min 32000 range)) ) (t (_lispru-progress-cmd msg range) ) ) ;_ end of cond ) ;_ end of defun (defun _lispru-progress-cmd (msg pos / lst) ;| * Выводит в ком.строку сообщение с "прогрессом" * Параметры вызова: msg строковое сообщение pos счетчик выполняемых действий |; (if msg (princ (strcat "\r" msg " : " (nth (rem pos 4) '("-" "\" "|" "/")))) (princ "\n" msg " закончено") ) ;_ end of if ) ;_ end of defun (defun _lispru-progess-continue (msg pos) ;| * Заполняет прогресс-бар * Параметры вызова: msg выводимое сообщение pos текущая позиция |; (cond (acet-ui-progress (acet-ui-progress (rem pos 32000)) ) (progressbar (progressbar (rem pos 32000)) ) (t (_lispru-progress-cmd msg pos) ) ) ;_ end of cond ) ;_ end of defun (defun _lispru-progress-end () ;| * Завершение прогресс-бара |; (cond (acet-ui-progress (acet-ui-progress) ) (progressbar (progressbar) ) (t (princ)) ) ;_ end of cond ) ;_ end of defun |
Я не стал оформлять это несколькими функциями, закинув все в один lsp-файл (исходник здесь:
_lispru-progress.lsp.
Критикуйте
Дополню. Уже не помню, по каким причинам, но код пришлось модифицировать:
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
* Инициализирует прогресс-бар
* Параметры вызова:
msg показываемое сообщение
range общая длина прогресс-бара
|;
(setq *kpblc-progress-range*
(list (cons "progress" (min 32000 range)))
range (cdr (assoc "progress" *kpblc-progress-range*))
) ;_ end of setq
(cond ((and msg progressbar) (progressbar msg range))
((and (not msg) progressbar) (progressbar range))
((and msg acet-ui-progress) (acet-ui-progress msg range))
((and (not msg) acet-ui-progress) (acet-ui-progress range))
(t (_kpblc-progress-cmd msg range))
) ;_ end of cond
) ;_ end of defun
(defun _kpblc-progress-continue (msg pos) ;|
* Заполняет прогресс-бар
* Параметры вызова:
msg выводимое сообщение
pos текущая позиция
|;
(if (and (cdr (assoc "progress" *kpblc-progress-range*))
(> 0 (cdr (assoc "progress" *kpblc-progress-range*)))
(> pos (cdr (assoc "progress" *kpblc-progress-range*)))
) ;_ end of and
(while (> pos (cdr (assoc "progress" *kpblc-progress-range*)))
(setq pos (- pos (cdr (assoc "progress" *kpblc-progress-range*))))
) ;_ end of while
) ;_ end of if
(cond (progressbar (progressbar (rem pos 32000)))
(acet-ui-progress (acet-ui-progress (rem pos 32000)))
(t (_kpblc-progress-cmd msg pos))
) ;_ end of cond
) ;_ end of defun
(defun _kpblc-progress-cmd (msg pos / lst) ;|
* Выводит в ком.строку сообщение с "прогрессом"
* Параметры вызова:
msg строковое сообщение
pos счетчик выполняемых действий
|;
(if msg
(princ (strcat "\r" msg " : " (nth (rem pos 4) '("-" "\" "|" "/"))))
(princ (strcat "\n" msg " закончено"))
) ;_ end of if
) ;_ end of defun
(defun _kpblc-progress-end ();|
* Завершение прогресс-бара
|;
(setq *kpblc-progress-range* nil)
(cond (progressbar (progressbar))
(acet-ui-progress (acet-ui-progress))
(t (princ))
) ;_ end of cond
) ;_ end of defun
И примерчик использования:
2
3
4
5
6
7
8
9
(setq len 33600
pos 0
msg "Проверка прогресс-бара"
) ;_ end of setq
(_kpblc-progress-start msg len)
(while (< pos len) (_kpblc-progress-continue msg (setq pos (1+ pos))))
(_kpblc-progress-end)
) ;_ end of defun
Все коды можно забрать здесь