Анализ кода
С помощью выражений princ, находящихся в тексте главной функции, на экран выводится результат, если программа была выполнена успешно, или предупреждающее сообщение, если возникла непредвиденная ситуация. Например, как будет показано на втором занятии, если пользователь нажмет клавишу ENTER вместо указания точки на экране, выполнение функции gp:getPointInput прервется, а в главную функцию будет передано значение nil. В результате функция princ выдает сообщение о том, что введено недостаточно информации для построения границы.
Вызов функции princ ближе к концу служит для вывода подсказки. После загрузки приложения в командной строке выдается подсказка, в которой говорится какое имя команды надо ввести для построения парковой дорожки. Последняя функция princ, не имеющая строковых аргументов, служит для мягкого выхода из программы (то есть без возвращения главной функцией результата). При отсутствии последнего выражения princ подсказка выдавалась бы дважды.
Автоматическое дописывание слов
ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
(if (equal ObjectCreationStyle "COMMAND")
(progn
(setq firstCenterPt(polar rowCenterPt (Degrees->Radians 45) distanceOnPath))
(gp:Create_activeX_Circle)
)
)
Сейчас можно не тратить время на изучение содержимого этого кода. Он используется только в качестве примера, иллюстрируя способы обращения с длинными именами переменных и функций.
VLISP может автоматически дописывать длинные слова по их начальным буквам.
Для использования функции дописывания слов в Visual LISP
Переместите курсор в конец файла gpdraw.lsp и введите следующий код:
ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
(if (equal Ob
Нажмите CTRL + ПРОБЕЛ.
VLISP находит ближайшее соответствие для двух введенных последними букв и автоматически дописывает слово, позволяя не тратить время на ввод оставшихся 17 букв.
Допишите строку до конца:
(if (equal ObjectCreationStyle "COMMAND")
Добавьте следующие строки:
(progn
(setq firstCenterPt(p
Нажмите CTRL + ПРОБЕЛ.
VLISP подставляет самое последнее слово, начинающееся на «p», которым в данном случае оказывается progn. Однако нам нужно слово polar. Если продолжать нажимать CTRL + ПРОБЕЛ, VLISP начнет циклически перебирать все подходящие слова, использованные в коде. И, в конце концов, будет найдено слово polar.
Удалите только что введенный код. Он был нужен только в качестве примера.
Функция дописывания слов доступна также и через меню VLISP «Поиск».
Дальнейшие действия
(if UserClick ; User clicked Ok
;; Build the resulting data
(progn
(setq Result (list
(cons 42 tileRad)
(cons 43 TileSpace)
(cons 3 objectCreateMethod)
(cons 4 plineStyle)
)
)
)
)
Добавление функций отклика для реакторов
Для добавления функций отклика для реакторов в программу
Скопируйте файл gpreact.lsp из папки Tutorial\VisualLISP\Lesson6 в рабочую папку MyPath.
Откройте проект GPath (если он еще не открыт) и нажмите кнопку "Свойства проекта" в окне проекта парковой дорожки.
Добавьте к проекту файл gpreact.lsp.
Файл gpreact.lsp можно расположить в любом месте между файлами utils.lsp (который должен оставаться первым) и gpmain.lsp (который должен оставаться последним). Переместите файлы, если нужно, и нажмите «OK».
Откройте файл gpreact.lsp, дважды щелкнув мышью на его имени в окне проекта gpath.
іПросмотрите записанные в файле комментарии, для того чтобы понять, какие операции в нем выполняется. Следует обратить внимание, что функции отклика являются фиктивными. Единственное, что они пока делают — это вывод предупреждающих сообщений.
Последняя функция в файле является настолько важной, что заслуживает отдельного рассмотрения.
Дописывание словом из списка
(setq myEnt (ssname mySelectionSet ssIndex))
Часто бывает трудно отследить все функции набора объектов: ssname, ssget, sslength и т.д. Здесь может пригодиться средство VLISP «Дописать словом из списка».
Для использования функции дописывания словом из списка в Visual LISP
Переместите курсор в конец файла gpdraw.lsp и введите на пустой строке следующее:
(setq myEnt (ent
Нажмите CTRL + SHIFT + ПРОБЕЛ.
VLISP выводит список всех символов AutoLISP, начинающихся с ent.
С помощью клавиш курсора (стрелок «вверх» и «вниз») можно перемещаться по списку. Выберите ENTGET и нажмите ENTER.
VLISP заменяет введенный фрагмент слова ent на ENTGET.
Удалите код.
Доработка функций отклика для объектных реакторов
Для доработки функций отклика объектных реакторов
В файле gpreact.lsp измените функцию gp:outline-erased так, чтобы она выглядела следующим образом:
(defun gp:outline-erased (outlinePoly reactor parameterList)
(setq *reactorsToRemove*
(cons reactor *reactorsToRemove*))
(princ)
) ;_ end of defun
Здесь выполняется только одна операция. Реактор, назначенный полилинии, сохраняется в списке реакторов для удаления. (Обратите внимание: хотя реакторы и прикрепляются к объектам, они полностью самостоятельны, поэтому их отношения с объектами рисунка необходимо поддерживать так же, как и для обычных объектов AutoCAD).
Внесите следующие изменения в функцию gp:outline-changed:
(defun gp:outline-changed (outlinePoly reactor parameterList)
(if *lostAssociativity*
(setq *reactorsToRemove*
(cons reactor *reactorsToRemove*))
(setq *polytochange* outlinePoly
*reactorsToChange* (cons reactor *reactorsToChange*))
)
(princ)
)
Существуют две категории функций, способных изменить полилинию-границу. К первой категории относятся команды, разрывающие ассоциативность дорожки и составляющих ее плиток. Проверка этой ситуации производится в функции gp:command?will?start; на основании результата проверки устанавливается глобальная переменная *lostAssociativity*. В данном случае плитки должны быть удалены, после чего пользователь свободен в дальнейшем редактировании дорожки. Ко второй категории относится режим работы с ручками команды РАСТЯНУТЬ, где ассоциативность сохраняется и необходимо выровнять полилинию-границу после перетаскивания вершины в новое положение.
Переменная *polyToChange* хранит указатель на саму полилинию. Она используется функцией gp:command-ended для пересчета полилинии-границы.
Функции отклика
Описание функции gp:outline-changed
Удалить плитки.
Определить, как изменилась граница.
Исправить границу.
Перерисовать новые плитки.
Конец функции
Но при этом возникает одна сложность. О перетаскивании вершины полилинии AutoCAD сообщает приложению с помощью события :vlr?modified. Однако, пусть пользователь только начал перетаскивать одну из вершин полилинии. Если немедленно вызвать функцию gp:outline-changed, действие пользователя будет прервано на середине. Где именно будет располагаться вершина — еще неизвестно, так как пользователь пока не указал ее новое положение. И, наконец, AutoCAD не позволяет изменять полилинию в то время, когда пользователь перетаскивает какую-либо из ее вершин. Полилиния открыта в AutoCAD для редактирования и будет оставаться в этом состоянии, пока пользователь не закончит перемещение.
В связи с этим подход к программе следует изменить. Приведем обновленный алгоритм:
Когда пользователь начинает перемещение вершины полилинии,
Вызвать функцию gp:outline-changed
Описание функции gp:outline-changed
Описать глобальную переменную, сохраняющую указатель на полилинию,
изменяемую пользователем.
Конец функции
После завершения команды
Вызвать функцию gp:command-ended
Описание функции gp:command-ended
Удалить плитки
Получить информацию о предыдущем расположении вершин полилинии
Получить информацию о новом расположении вершин полилинии
Переопределить полилинию (выпрямить ее)
Перерисовать плитки
Конец функции
О завершении изменения контура дорожки AutoCAD сообщает приложению с помощью события :vlr?commandEnded, если был назначен реактор редактора.
Использование глобальной переменной для идентификации измененной пользователем полилинии необходимо, так как между функциями gp:outline-changed и gp:command-ended нет связи.
В нашем приложении обе функции вызываются и выполняются независимо друг от друга. Глобальная переменная хранит важную информацию, определенную одной функцией и доступную для другой.
Теперь надо подумать о том, что должно происходить, если пользователь удалит границу дорожки. В конечном счете, должны удалиться все плитки. Этот случай описывается следующим алгоритмом:
Когда пользователь начинает удалять границу,
Вызвать функцию gp:outline-erased
Описание функции gp:outline-erased
Описать глобальную переменную, сохраняющую указатель на реактор,
назначенный удаляемой полилинии
Конец функции
После завершения удаления
Вызвать функцию gp:command-ended
Описание функции gp:command-ended
Удалить плитки, принадлежавшие только что удаленной полилинии
Конец функции
Геометрические величины
Шаг плитки равен сумме диаметра плитки и расстояния между плитками. Размеры задаются пользователем.
Шаг между рядами вычисляется по несколько более сложной тригонометрической формуле: промежуток между рядами
Шаг между рядами = (Диаметр плитки + Расстояние между плитками) * (синус угла
60 градусов)
Хранение данных с реактором
Описание функции C:GPath
Выполнить все, что делала функция ранее для построения парковой дорожки
(ничего не меняя)
Назначить полилинии реактор объекта, используя следующие параметры:
Указатель на только что построенную полилинию,
Список данных, которые нужно запомнить в реакторе,
Список отслеживаемых событий, происходящих с полилинией,
а также вызываемые функции отклика LISP.
Конец задания объектного реактора
Прикрепить к графическому редактору реактор, используя
следующие параметры:
Все данные, которые необходимо сохранить с реактором (в данном случае нет)
Список отслеживаемых событий редактора,
а также вызываемые функции отклика LISP.
Конец задания реактора редактора
Конец функции
Хранение информации с реакторами
Так как необходимо выполнить пересчет полилинии, к переменной gp_pathData добавляются четыре дополнительных ключевых значения:
;;; StartingPoint ;
;;; (12 . BottomStartingPoint) 15------------------------14 ;
;;; (15 . TopStartingPoint) | | ;
;;; EndingPoint 10 ----pathAngle---> 11 ;
;;; (13 . BottomEndingPoint) | | ;
;;; (14 . TopEndingPoint) 12------------------------13 ;
;;; ;
Пронумерованные точки необходимы для пересчета границы дорожки, когда пользователь перетаскивает одну из угловых ручек в новое положение. Эта информация уже имеется в функции gp:drawOutline (файл gpdraw.lsp). Однако посмотрим на значение, возвращаемое функцией. Пока возвращается только указатель на объект полилинии. Поэтому необходимо выполнить три дополнительных действия:
Сгруппировать точки периметра в подходящем формате.
Изменить функцию так, чтобы она возвращала списки точек периметра и указатель на полилинию.
Изменить функцию C:GPath для обработки нового формата значений, возвращаемых функцией gp:drawOutline.
Группировка списков точек периметра является простой задачей. Посмотрим на код функции gp:drawOutline. Локальной переменной p1 соответствует код 12, p2 - 13, p3 - 14 и p4 - 15. Для того чтобы объединить эту информацию, используется следующий вызов функции:
(setq polyPoints(list
(cons 12 p1)
(cons 13 p2)
(cons 14 p3)
(cons 15 p4)
))
Изменить функцию так, чтобы она возвращала списки точек периметра и указатель на полилинию, также просто. Для этого в последнем выражении функции gp:drawOutline нужно собрать в один список две величины.
(list pline polyPoints)
Для добавления в программу кода для сохранения точек периметра полилинии
Измените функцию gp:drawOutline. Изменения показаны полужирным шрифтом (не пропустите описание локальной переменной polyPoints в выражении defun):
(defun gp:drawOutline (BoundaryData / PathAngle
Width HalfWidth StartPt PathLength
angm90 angp90 p1 p2
p3 p4 poly2Dpoints
poly3Dpoints plineStyle pline
polyPoints
)
;; extract the values from the list BoundaryData.
(setq PathAngle (cdr (assoc 50 BoundaryData))
Width (cdr (assoc 40 BoundaryData))
HalfWidth (/ Width 2.00)
StartPt (cdr (assoc 10 BoundaryData))
PathLength (cdr (assoc 41 BoundaryData))
angp90 (+ PathAngle (Degrees->Radians 90))
angm90 (- PathAngle (Degrees->Radians 90))
p1 (polar StartPt angm90 HalfWidth)
p2 (polar p1 PathAngle PathLength)
p3 (polar p2 angp90 Width)
p4 (polar p3 (+ PathAngle
(Degrees->Radians 180)) PathLength)
poly2Dpoints (apply 'append
(mapcar '3dPoint->2dPoint (list p1 p2 p3 p4))
)
poly3Dpoints (mapcar 'float (append p1 p2 p3 p4))
;; get the polyline style.
plineStyle (strcase (cdr (assoc 4 BoundaryData)))
;; Add polyline to the model space using ActiveX automation.
pline (if (= plineStyle "LIGHT")
;; create a lightweight polyline.
(vla-addLightweightPolyline
*ModelSpace* ; Global Definition for Model Space
(gp:list->variantArray poly2Dpoints)
;data conversion
) ;_ end of vla-addLightweightPolyline
;; or create a regular polyline.
(vla-addPolyline
*ModelSpace*
(gp:list->variantArray poly3Dpoints)
;data conversion
) ;_ end of vla-addPolyline
) ;_ end of if
polyPoints (список
(cons 12 p1)
(cons 13 p2)
(cons 14 p3)
(cons 15 p4)
)
) ;_ end of setq
(vla-put-closed pline T)
(list pline polyPoints)
) ;_ end of defun
Измените функцию C:GPath (файл gpmain.lsp). Найдите строку кода, которая сейчас выглядит так:
(setq PolylineName (gp:drawOutline gp_PathData))
Измените ее следующим образом:
(setq PolylineList (gp:drawOutline gp_PathData)
PolylineName (car PolylineList)
gp_pathData (append gp_pathData (cadr PolylineList))
) ;_ end of setq
Переменная gp_PathData теперь содержит всю необходимую для функции реактора информацию.
Добавьте PolylineList в раздел описаний локальных переменных функции C:GPath.
Инициализация значений по умолчанию для диалогового окна
Теперь установим начальные значения для радиуса плиток и расстояния между ними. Функция set_tile присваивает значение элементу управления диалогового окна. В текстовом поле используются строковые переменные (а не числа), поэтому для преобразования значения размера плиток в строки десятичного формата с точностью до двух знаков используется функция rtos (преобразование вещественнных чисел в строки). Преобразование осуществляется с помощью следующего кода:
(if (and dialogLoaded dialogShow)
(progn
;; Set the initial state of the tiles
(set_tile "gp_trad" (rtos tileRad 2 2))
(set_tile "gp_spac" (rtos tileSpace 2 2))
Использование командной строки AutoCAD
Использование методов ActiveX в функциях отклика реакторов
Перерисовать плитки (с помощью ActiveX)
Следует обратить внимание на выражение в скобках: с помощью ActiveX. Почему именно так? Почему приложение не может использовать метод, хранящийся в ассоциативном списке?
Ответ заключается в том, что функция command не может быть использована для создания объектов внутри функции отклика реактора. Это связано с внутренними ограничениями AutoCAD. Поэтому необходимо использовать для перерисовки плиток ActiveX. Подробнее об этом будет рассказано позже на данном занятии.
Использование нескольких реакторов
Реактор, назначенный объекту, является реактором объекта. Каждому объекту на рисунке может соответствовать свой реактор. Определенное событие редактирования (такое, например, как команда СТЕРЕТЬ) может вызвать несколько функций отклика, в зависимости от количества выбранных объектов, к которым прикреплены реакторы. Реакторы редактора, с другой стороны, по своей природе всегда задаются по одному. Поэтому приложение всегда назначает только один реактор события :vlr-commandEnded.
Последовательность событий для обоих типов изменений (изменение положения вершины и стирание полилинии) заканчивается действиями, которые должны выполняться внутри функции gp:command-ended. Определим набор действий, выполняемых для каждого случая. Этот случай описывается следующим алгоритмом:
Описание команды gp:command-ended (2-я версия)
Извлечь указатель полилинии (из глобальной переменной)
Условие:
Если полилиния была изменена, то:
Удалить плитки
Получить информацию о предыдущем расположении вершин полилинии
Получить информацию о новом расположении вершин полилинии
Переопределить полилинию (выпрямить ее)
Перерисовать плитки
Конец условного выражения
Если полилиния была стерта, то:
Удалить плитки
Конец условного выражения
Конец условного выражения.
Конец функции
Использование реакторов
Использование реакторов
Данное занятие знакомит с тем, что такое реакторы и как они назначаются событиям и объектам рисунка. Реакторы позволяют сообщать приложению об определенных событиях в AutoCAD® . Например, если пользователь перемещает объект, к которому прикреплен реактор, приложение получает уведомление о том, что объект был перемещен. При этом можно запрограммировать дополнительные операции — например, перемещение других объектов, связанных с перемещенным или обновление текстовой метки, которая записывает информацию об изменениях объекта или свойства рисунка. Фактически, это равноценно установке пейджера для приложения и настройке AutoCAD на отправку сообщений на этот пейджер в случае возникновения какого-либо события.
Темы:
Общие сведения о реакторах
Разработка реакторов для парковой дорожки
Проверка работы реакторов
Итоги занятия 6
Использование служебных функций
Служебные функции необходимо тщательно документировать. В комментариях следует также указывать, каким образом можно усовершенствовать функцию в будущем, если позволит время.
Темы:
Перевод градусов в радианы
Преобразование 3М точек в 2М
Использование углов и задание точек
Здесь пригодится функция Degrees->Radians. В следующем фрагменте кода выполняется расчет двух перпендикулярных векторов; при этом переменная PathAngle передается в качестве аргумента функции Degrees->Radians:
(setq angp90 (+ PathAngle (Degrees->Radians 90))
angm90 (- PathAngle (Degrees->Radians 90)))
Имеющиеся данные позволяют найти с помощью функции polar четыре угловые точки дорожки.
(setq p1 (polar StartPt angm90 HalfWidth)
p2 (polar p1 PathAngle PathLength)
p3 (polar p2 angp90 Width)
p4 (polar p3 (+ PathAngle (Degrees->Radians 180))
Функция polar возвращает 3М точку, расположенную под заданным углом и на заданном расстоянии от исходной точки. Например, точку p2 функция polar находит, откладывая от точки p1 расстояние, равное PathLength, вдоль вектора, повернутого на угол PathAngle против часовой стрелки относительно оси X.
Итоги учебного пособия
в отличие от совместно используемых
Постановка задачи.
Значения фиктивных функций.
Именование внутренних функций приложений, в отличие от совместно используемых функций.
Проверка кода с помощью VLISP.
Загрузка и выполнение программ в VLISP.
Занятие окончено. Сохраните программу, чтобы не потерять последние изменения в ней.
Локальные и глобальные переменные.
Задание и удаление точек останова.
Пошаговое выполнение программы.
Контроль и динамическое изменение значений переменных в процессе выполнения программы.
Сброс значений локальных переменных в nil после завершения выполнения функции, в которой они определены.
Программисты, планирующие разрабатывать приложения на AutoLISP, смогут убедиться, что инструменты, описанные на этом занятии, незаменимы в ежедневной работе.
Служебные функции пользователя, которые можно использовать в различных приложениях.
Механизм создания объектов из программы пользователя.
Работа с функциями ActiveX.
Работа с ассоциативными списками.
Реализация функций для построения границ парковой дорожки.
Если на данном этапе что-то осталось непонятным, перед переходом к четвертому занятию рекомендуется повторно проработать предыдущие занятия. В этом случае, чтобы начать выполнение упражнений с нужного этапа, следует скопировать код из папки Lesson2. При возникновении затруднений правильный код можно скопировать из папки Tutorial\VisualLISP\Lesson3.
Разбиение кода на модули, хранящиеся в четырех отдельных файлах.
Организация модулей кода в проект VLISP.
Описание диалогового окна с помощью языка DCL (Dialog Control Language).
Создание кода AutoLISP для обработки вводимых в диалоговом окне данных.
Доработка кода с целью предоставления пользователю возможности выбора типа линий границ.
Теперь программа умеет строить границу дорожки. На следующем занятии мы разместим на дорожке плитки. В ходе выполнения упражнений мы познакомимся еще с несколькими инструментами разработки VLISP.
Проверка соответствия скобок в коде.
Автоматическое дописывание имен функций.
Получение справочной информации о функциях.
Занятие было завершено созданием функции для заполнения дорожки плитками. Теперь программа удовлетворяет требованиям, сформированным в самом начале учебного пособия.
На этом этапе читатели уже имеют достаточно знаний для того, чтобы приступить к самостоятельной разработке программ на VLISP. Однако, для желающих в учебном пособии предлагается еще два занятия для ознакомления с функциями реакторов и другими возможностями среды VLISP.
чем шире возможности программы, тем более серьезными могут быть сбои.
Кроме того, следует помнить, что назначение реакторов, описанное здесь, действует только в пределах одного рабочего сеанса. Реакторы, назначенные парковой дорожке, не будут работать при последующем открытии рисунка. О добавлении постоянных реакторов см. раздел “Transient versus Persistent Reactors” руководства AutoLISP Developer’s Guide, а о связанных с ними функциях - в документе AutoLISP Reference.
Изменение других вызовов функции gp:Calculate-and-Draw-Tiles
Вспомним занятие 4, где говорилось, что при каждом изменении фиктивной фунции необходимо ответить на следующие вопросы:
Был ли изменен вызов (обращение к) функции? Необходимо ли функции то же число аргументов, что и раньше?
Изменился ли результат, возвращаемый функцией?
На те же вопросы необходимо отвечать при каждом существенном изменении рабочей функции в процессе создания, улучшения и обновления приложений. В данном случае необходимо найти все другие функции, вызывающие gp:Calculate-and-Draw-Tiles. В VLISP имеется средство, помогающее это сделать.
Для нахождения всех вызовов функции gp:Calculate-and-Draw-Tiles в проекте
В текстовом редакторе VLISP дважды щелкните мышью на слове gp:Calculate-and-Draw-Tiles в файле gpdraw.lsp.
Выберите "Поиск"
"Найти" в меню VLISP.Так как имя функции было выделено заранее, оно появляется в качестве строки для поиска.
Установите переключатель «Поиск» в положение «Проект».
При выборе этой опции диалоговое окно «Поиск» разворачивается, и в нем можно выбрать проект для поиска.
Укажите имя проекта и нажмите кнопку «Найти».
VLISP выводит результат в отдельном окне.
Посмотрим на результаты поиска и определим, откуда еще вызывается функция gp:Calculate-and-Draw-Tiles. У нее должно быть только одно местоположение: файл gpmain.lsp.
Дважды щелкните мышью в окне результатов поиска на строке кода, вызывающего функцию gp:Calculate-and-Draw-Tiles.
VLISP активизирует окно текстового редактора и переходит к строке gpmain.lsp. Пока код выглядит следующим образом:
(setq tilelist (gp:Calculate-and-Draw-Tiles gp_PathData))
Замените строку кода на следующую:
(setq tilelist (gp:Calculate-and-Draw-Tiles gp_PathData nil))
Почему nil? Еще раз посмотрим на алгоритм:
If ObjectCreationStyle is nil, assign it from the BoundaryData.
При передаче параметра nil функции gp:Calculate-and-Draw-Tiles проверяется пользовательская настройка стиля создания плиток (заданная в диалоговом окне и хранящаяся в переменной gp_PathData). Однако при последующих вызовах функции реактором, обслуживающим окончание выполнения команды, будет использоваться метод ActiveX.
Отлично! Теперь реакторы выполняют свои основные функции. При желании можно скопировать файлы gpmain.lsp и gpdraw.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку и проверить окончательный отлаженный код.
Однако почивать на лаврах пока рано. Необходимо еще выполнить много работы с функцией gp:Command-ended:
(setq NewReactorData
(gp:RedefinePolyBorder CurrentPoints reactorData)
) ;_ end of setq
Изучение функции gp:getPointInput
Внутри программы происходит следующее:
VLISP ожидает указания первой точки.
После указания первой точки программа сохраняет ее значение (список из трех координат X, Y и Z) в переменной StartPt.
В первой функции if проверяется, было или не было введено правильное значение. После задания начальной точки управление передается следующей функции getpoint.
После указания конечной точки значения ее координат сохраняются в переменной Endpt.
Результат этого выражения проверяется в следующем выражении if, после чего управление передается функции getdist.
Функция getdist позволяет как указывать точку на экране, так и вводить численное значение. Результат выполнения функции getdist хранится в переменной HalfWidth.
После этого программа встречает значение T, находящееся внутри функции. Так как далее не следует никаких других функций, данная функция завершается и возвращает значение T. Именно оно (Т) и выводится в окне консоли.
Данные необходимо каким-то образом передать из одной функции в другую. Для этого можно, например, создать список значений, полученных функцией gp:getPointInput, как описано ниже:
(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
(if (setq StartPt (getpoint "\nStart point of path: "))
(if (setq EndPt (getpoint StartPt "\nEndpoint of path: "))
(if (setq HalfWidth (getdist EndPt "\nhalf-width of path: "))
(list StartPt EndPt HalfWidth)
)
)
)
)
Скопируйте этот вариант gp:getPointInput в окно консоли и нажмите ENTER. Теперь можно воспользоваться еще одной служебной функцией окна консоли.
Для выполнения gp:getPointInput с помощью протокола окна консоли
Нажмите TAB.
Консоль переходит в режим протокола, и в ней происходит циклический перебор ранее введенных команд. Для перебора команд в обратном порядке служит комбинация SHIFT+TAB.
Когда в окне консоли появится подсказка (gp:getPointInput), нажмите ENTER для повторного выполнения функции.
Ответьте на запросы, как и в предыдущий раз.
Функция возвращает список, содержащий в себе два вложенных списка и одно вещественное число (с плавающей точкой). Возвращаемые значения выглядят приблизительно следующим образом:
((4.46207 4.62318 0.0) (7.66688 4.62318 0.0) 0.509124)
Эти значения соответствуют переменным StartPt,EndPt и HalfWidth.
Изучение функций файла gppoly.lsp
Примечание В данном разделе учебного пособия построения парковой дорожки описан наиболее сложный материал. Начинающим программистам рекомендуется перейти к разделу Сборка приложения, расположенному ниже в данной главе.
Организация функций внутри файла gppoly.lsp аналогична другим исходным файлам AutoLISP. Функция самого высокого уровня, зачастую основная, или функция C: (в данном случае gp:Redefine-PolyBorder), расположена в конце файла. Функции, вызываемые из главной, описаны в исходном файле выше. Такой порядок сложился исторически, так как в прежние времена он использовался некоторыми средами программирования. В VLISP соблюдать такой порядок личное дело каждого программиста, так как требований располагать функции в какой-либо определенной последовательности не существует.
Перед тем, как перейти к подробностям, вернемся немного назад и посмотрим, что нужно сделать, чтобы пересчитать и построить границу дорожки. На следующем рисунке показан пример парковой дорожки и отмечены ключевые точки ассоциативного списка, хранящегося с реактором:
В данном примере ключевая точка 12 представляет собой нижний левый угол, 13 — нижний правый, и т.д. Если пользователь перемещает правую верхнюю точку (14), программе необходимо пересчитать две точки: нижнюю правую (13) и верхнюю левую (15).
Изучение кода
(defun gp:calculate-Draw-TileRow (startPoint TileRadius
TileSpace pathWidth pathAngle offsetFromCenter
ObjectCreationStyle / HalfWidth TileDiameter
ObjectCreationFunction angp90 angm90
firstCenterPt TileCenterPt TileList)
(setq HalfWidth (- (/ pathWidth 2.00) TileRadius)
Tilespacing (+ (* TileRadius 2.0) TileSpace)
TileDiameter (* TileRadius 2.0)
angp90 (+ PathAngle (Degrees->Radians 90))
angm90 (- PathAngle (Degrees->Radians 90))
firstCenterPt (polar startPoint angp90 offsetFromCenter)
tileCenterPt firstCenterPt
ObjectCreationStyle(strcase ObjectCreationStyle)
ObjectCreationFunction
(cond
((equal ObjectCreationStyle "ACTIVEX")
gp:Create_activeX_Circle
)
((equal ObjectCreationStyle "ENTMAKE")
gp:Create_entmake_Circle
)
((equal ObjectCreationStyle "COMMAND")
gp:Create_command_Circle
)
(T
(alert (strcat "ObjectCreationStyle in function
gp:calculate?Draw?TileRow"
"\nis invalid. Contact developer for assistance."
"\n ObjectCreationStyle set to ACTIVEX"
)
)
setq ObjectCreationStyle "ACTIVEX")
)
)
)
;; Draw the circles to the left of the center.
(while (< (distance startPoint tileCenterPt) HalfWidth)
;; Add each tile to the list to return.
(setq tileList
(cons
(ObjectCreationFunction tileCenterPt TileRadius)
tileList
)
)
;; Calculate the center point for the next tile.
(setq tileCenterPt
(polar tileCenterPt angp90 TileSpacing)
)
);_ end of while
;; Draw the circles to the right of the center.
(setq tileCenterPt
(polar firstCenterPt angm90 TileSpacing))
(while (< (distance startPoint tileCenterPt) HalfWidth)
;; Add each tile to the list to return.
(setq tileList
(cons
(ObjectCreationFunction tileCenterPt TileRadius)
tileList
)
)
;; Calculate the center point for the next tile.
(setq tileCenterPt (polar tileCenterPt angm90 TileSpacing))
);_ end of while
;; Return the list of tiles.
tileList
) ;_ end of defun
Этот AutoLISP- код реализует описанный выше словесный алгоритм и имеет следующие дополнения:
(setq ObjectCreationFunction
(cond
((equal ObjectCreationStyle "ACTIVEX")
gp:Create_activeX_Circle
)
((equal ObjectCreationStyle "ENTMAKE")
gp:Create_entmake_Circle
)
((equal ObjectCreationStyle "COMMAND")
gp:Create_command_Circle
)
(T
(alert
(strcat
"ObjectCreationStyle in function gp:calculate-Draw-TileRow"
"\nis invalid. Contact the developer for assistance."
"\n ObjectCreationStyle set to ACTIVEX"
) ;_ end of strcat
) ;_ end of alert
(setq ObjectCreationStyle "ACTIVEX")
)
) ;_ end of cond
) ;_ end of setq
Вспомним, что плитки (круги) можно строить одним из трех способов: с помощью функций ActiveX, функции entmake и функции command. Переменной ObjectCreationFunction присваивается одна из этих трех функций, в зависимости от параметра ObjectCreationStyle (передаваемого из C:GPath через gp:Calculate-and-Draw-Tiles). Ниже приводятся три функции, как они должны выглядеть в файле gpdraw.lsp:
(defun gp:Create_activeX_Circle (center radius)
(vla-addCircle *ModelSpace*
(vlax-3d-point center) ; convert to ActiveX-compatible 3D point
радиус
)
) ;_ end of defun
(defun gp:Create_entmake_Circle(center radius)
(entmake
(list (cons 0 "CIRCLE") (cons 10 center) (cons 40 radius))
)
(vlax-ename->vla-object (entlast))
)
(defun gp:Create_command_Circle(center radius)
(command "_CIRCLE" center radius)
(vlax-ename->vla-object (entlast))
)
Первая функция строит круг с помощью функции ActiveX и возвращает объект ActiveX.
Вторая функция строит круг с помощью функции entmake. Она возвращает имя объекта, преобразованное в объект ActiveX.
Третья функция строит круг с помощью функции command. Она также возвращает имя объекта, преобразованное в объект ActiveX.
Книги по LISP и AutoLISP
AutoLISP in Plain English: A Practical Guide for Non-Programmers, George O. Head, Ventana Press, ISBN: 1566041406.
LISP, 3rd Edition, Patrick Henry Winston and Berthold Klaus Paul Horn, Addison-Wesley Publishing Company, ISBN 0-201-08319-1.
ANSI Common Lisp, Paul Graham, Prentice Hall, ISBN 0-13-370875-6.
Common LISP, The Language, Second Edition, Guy L. Steele, Jr., Digital Press, ISBN 1-55558-041-6.
Код пошаговой отладки
Шаг с заходом в выделенное выражение.
Переход к концу выделенного выражения (т.е. шаг с обходом).
Переход к концу текущей функции, внутри которой произошел останов (т.е. шаг с выходом).
Перед тем, как сделать выбор, следует еще раз проверить статус выделенного выражения и положение курсора. В данном случае выделенное выражение включает в себя вложенную в setq функцию getdist, а курсор располагается в самом начале выделенного блока.
Для пошагового выполнения кода от точки останова
Нажмите кнопку "Шаг с обходом".
После этого управление передается AutoCAD и запрашивается ширина дорожки.
Ответьте на запрос.
После того, как ширина задана, управление снова передается в VLISP. Следует обратить внимание на положение курсора на индикаторе.
VLISP вычисляет выделенное выражение целиком и останавливается в конце выражения.
Снова нажмите кнопку «Шаг с обходом». VLISP переходит к началу следующего блока кода и выделяет блок целиком.
Теперь нажмите кнопку "Шаг с заходом" (не нажимайте кнопку "Шаг с обходом").
Примечание Если в ходе выполнения упражнения был сделан неправильный выбор или пропущены какие-либо шаги, его можно легко начать сначала. Для этого сначала следует нажать кнопку «Сброс» на панели «Отладка». Выполнение кода VLISP прекращается, и система VLISP возвращается на верхний уровень. Теперь можно начать все с пункта 1.
Выделенной становится первая функция cons, а VLISP останавливается непосредственно перед функцией (обратите внимание на индикатор положения курсора).
Комментарии в тексте программы
Для того, чтобы код был понятен самому разработчику при редактировании программы и добавлении в нее новых возможностей месяцы спустя. Человеческой памяти свойственно быстро забывать подробности реализации алгоритмов, и даже самая очевидная последовательность функций через некоторое время может восприниматься как непонятный набор скобок.
Для того, чтобы код был понятен другим людям, несущим ответственность за дальнейшее сопровождение программы. Читать код, написанный другим человеком, утомительно и сложно — особенно если в нем практически не содержится комментариев.
В VLISP включены средства, помогающие комментировать код. Следует обратить внимание на то, что некоторые комментарии в примерах начинаются с трех точек с запятой (;;;), некоторые с двух (;;), а некоторые только с одной (;). Об обработке различных комментариев с помощью VLISP см. в разделе “Applying Visual LISP Comment Styles” руководства AutoLISP Developer’s Guide.
В целях экономии места в оставшихся исходных файлах примеров кода данного учебного пособия комментариев не содержится. Предполагается, что разработчик уже имеет хорошую привычку вставлять в текст программы подробные комментарии, и делает это без посторонней подсказки.
Компоновка кода
Для объединения кода функции gp:getDialogInput
Откройте файл gp-io.lsp в окне текстового редактора VLISP.
Удалите имеющийся код функции gp:getDialogInput (выражение defun gp:getDialogInput и все, что идет за ним).
В качестве первой строки функции gp:getDialogInput введите следующее выражение defun:
(defun gp:getDialogInput (pathWidth / dcl_id objectCreateMethod
plineStyle tilerad tilespace result UserClick
dialogLoaded dialogShow)
Функция ожидает задания одного аргумента (pathwidth) и имеет ряд локальных переменных.
Вслед за кодом, добавленным в пункте 3, введите код примеров из следующих разделов данного занятия:
Задание значений для диалогового окна
Загрузка файла диалогового окна
Загрузка нужного диалогового окна в память
Инициализация значений по умолчанию для диалогового окна
Назначение действий элементам окна
Примечание Из раздела выберите только первый пример кода Назначение действий элементам окна. Не вводите следующие за ним фрагменты в пояснениях. Эти фрагменты просто повторяют участки исходного примера.
Запуск диалогового окна
Выгрузка диалогового окна
Дальнейшие действия
После последней строки кода добавьте следующее:
)
)
Result;
) ;_ end of defun
Отформатируйте введенный код, выбрав "Сервис"
"Форматировать код в редакторе" из меню VLISP.
Контроль значений переменных программы
Для контроля значений переменных
Выберите «Отладка»
«Добавить контрольное значение» из меню VLISP. Откроется диалоговое окно "Добавление контрольного значения".Введите имя переменной для контроля ее значения. В данном случае введем переменную gp_PathData, только что описанную в окне консоли. Открывается диалоговое окно «Контрольное значение»:
VLISP выводит значение переменной в окне «Контрольное значение» на отдельной строке (см. основное окно на рисунке). Таким образом, длинные списки в окне целиком не умещаются. Окно контрольных значений можно растянуть, но можно поступить и иначе.
Дважды щелкните мышью на имени переменной в окне «Контрольное значение». Открывается диалоговое окно «Изучение»:
В диалоговом окне «Изучение» показаны тип данных изучаемой переменной (в данном случае LIST—список) и ее значение. Каждый элемент списка выводится в этом диалоговом окне на отдельной строке.
Дважды щелкните мышью на строке с кодом 11. Открывается еще одно диалоговое окно «Изучение»:
Просмотрев нужные переменные, закройте все диалоговые окна «Изучение», однако окно «Контрольное значение» оставьте открытым.
Контроль значений переменных в ходе пошагового выполнения программы
Если окно контрольных значений не отображается на экране, для его вывода достаточно нажать на панели кнопку "Окно контрольных значений".
Если в окне контрольных значений все еще содержится переменная gp_PathData, следует нажать кнопку «Очистить окно», расположенную в верхней части окна.
Для добавления переменной в окно контрольных значений
Дважды щелкните мышью на любом вхождении StartPt в окне текстового редактора VLISP. Это имя переменной, изменения значения которой необходимо проследить.
Нажмите кнопку «Добавить контрольное значение» в окне контрольных значений, или щелкните правой кнопкой мыши и выберите «Добавить контрольное значение» из контекстного меню.
Повторите ту же процедуру для переменных EndPt и HalfWidth. Окно контрольных значений должно выглядеть приблизительно так:
Использование точек останова в сочетании с контролем значений при отладке неверно работающей программы позволяет проверить, принимают ли переменные нужные значения в процессе выполнения программы.
Кроме того, пользователь может изменить значение переменной и проследить, как это повлияет на выполнение программы. Пусть, например, переменная halfwidth должна принимать только целые значения. Однако в результате неточного ввода точек ее значение может оказаться равным 1.94818. Изменяя значение переменной вручную, можно выяснить, как это скажется на поведении программы.
Для изменения значения переменной в ходе выполнения программы
В ответ на подсказку в окне консоли введите следующее:
(setq halfwidth 2.0)
Следует обратить внимание на то, что значение в окне «Контрольное значение» изменилось. Но можно ли при этом быть уверенным, что именно это новое значение используется при создании ассоциативного списка (вложенный список с кодом 40)? Для проверки этого добавим еще одно выражение в окно контрольных значений.
Выберите «Отладка» « Результат последнего вычисления» из меню VLISP.
В окно «Контрольное значение» добавляется переменная с именем *Last-Value*. *Last-Value* это глобальная переменная, в которой VLISP автоматически сохраняет значение последнего вычисленного выражения.
Выполняйте программу пошагово с помощью кнопок "Шаг с заходом" и "Шаг с обходом" до тех пор, пока не будет вычислено выражение, ответственное за построение вложенного списка для ширины. Это выражение представляет собой следующее:
(cons 40 (* HalfWidth 2.0))
Если значение переменной HalfWidth было установлено равным 2, то в результате вычислений выражение должно возвращать в окне контрольных значений (40 .
Корректировка
(princ "\nThe gp:drawOutline function returned <")
(princ PolylineName)
(princ ">")
(Alert "Congratulations - your program is complete!")
Так как функция gp:drawOutline теперь готова и полностью работоспособна, этот фрагмент, использовавшийся в качестве программной «заглушки», больше не нужен.
Логика построения
Плитки образуют периодический узор. Подумаем, как бы мы выкладывали плитки на настоящей дорожке. Вероятно, мы начали бы с одного ее конца и последовательно укладывали ряды плиток до тех пор, пока позволяет место.
Словесное описание алгоритма выглядит следующим образом:
В начальной точке дорожки
Определить смещение начального ряда плиток относительно центральной линии дорожки (центр средней плитки ряда либо
совпадает с центральной линии дорожки, либо смещен относительно этой линии на половину «шага плитки»).
Пока заполненное расстояние на дорожке меньше длины, которую нужно
заполнить,
Построить ряд плиток.
Сместить начальную точку для следующего ряда (увеличить на один «шаг плитки»).
Добавить расстояние, занимаемое новым рядом плиток, к уже
заполненному пространству.
Изменить смещение ряда относительно центральной линии дорожки (если оно было равным 0,
сместить на половину «шага плитки», и наоборот).
Вернуться в начало цикла.
Начало работы в Visual LISP
Чтобы увидеть, как ведет себя Visual LISP, когда находится в режиме ожидания передачи управления из AutoCAD
В меню "Сервис" AutoCAD выберите "Приложения".
Выберите файл gardenpath.vlx из папки Tutorial\VisualLISP и нажмите кнопку «Загрузить».
Нажать кнопку «Закрыть».
В командной строке AutoCAD введите vlisp для запуска Visual LISP.
Переключитесь обратно в окно AutoCAD (для этого можно либо выбрать значок AutoCAD на панели задач, либо требуемое количество раз нажать ALT + TAB и выбрать AutoCAD) и в командной строке AutoCAD введите gpath.
Перед тем, как отвечать на подсказки команды gpath, переключитесь в окно VLISP.
В окне VLISP указатель мыши заменяется специальным курсором VLISP, обозначающим, что ввод команд или текста в окне VLISP запрещен. Этот курсор напоминает, что для возобновления работы с VLISP необходимо завершить выполняющуюся в AutoCAD программу AutoLISP. Программу необходимо завершать каждый раз при появлении курсора VLISP
Вернитесь в окно AutoCAD и ответьте на все запросы команды gpath.
Теперь можно приступить к созданию приложения для построения парковой дорожки.
Для начала разработки приложения в Visual LISP
В меню "Файл" среды VLISP выберите "Создать файл"
.В текстовом окне редактора, озаглавленном «Без имени-0», введите следующий текст (комментарии можно опустить):
;;; Function C:GPath is the main program function and defines the
;;; команда GPATH (команда среды AutoCAD).
(defun C:GPath ()
;; Запрос на ввод: путь к местоположению и
;; direction, then for path parameters. Continue only if you have
;; valid input.
(if (gp:getPointInput) ;
(if (gp:getDialogInput)
(progn
;; At this point, you have valid input from the user.
;; Draw the outline, storing the resulting polyline
;; "pointer" in the variable called PolylineName.
(setq PolylineName (gp:drawOutline))
(princ "\nThe gp:drawOutline function returned <")
(princ PolylineName)
(princ ">")
(Alert "Congratulations - your program is complete!")
)
(princ "\nFunction cancelled.")
)
(princ "\nIncomplete information to draw a boundary.")
)
(princ) ; exit quietly
)
;;; Display a message to let the user know the command name.
(princ "\nType gpath to draw a garden path.")
(princ)
Выберите "Файл" "Сохранить как" и сохраните код в новом файле <AutoCAD папка>\Tutorial\VisualLISP\MyPath\gpmain.lsp.
Проверьте свою работу.
Написание функции command-ended
(defun gp:command-ended (reactor command-list
/ objReactor
reactorToChange reactorData
coordinateValues currentPoints
newReactorData newPts
tileList
)
(cond
;; CONDITION 1 - POLYLINE ERASED (Erase command)
;; If one or more polyline borders are being erased (indicated
;; by the presence of *reactorsToRemove*), erase the tiles
;; within the border, then remove the reactor.
(*reactorsToRemove*
(foreach objReactor *reactorsToRemove*
(gp:erase-tiles objReactor)
)
(setq *reactorsToRemove* nil)
)
;; CONDITION 2 - LOST ASSOCIATIVITY (Move, Rotate, etc.)
;; If associativity has been lost (undo, move, etc.), then
;; erase the tiles within each border
;;
((and *lostassociativity* *reactorsToChange*)
(foreach reactorToChange *reactorsToChange*
(gp:erase-tiles reactorToChange)
)
(setq *reactorsToChange* nil)
)
;; CONDITION 3 - GRIP_STRETCH
;; In this case, the associativity of the tiles to the path is
;; kept, but the path and the tiles will need to be
;; recalculated and redrawn. A GRIP_STRETCH can only be
;; performed on a single POLYLINE at a time.
((and (not *lostassociativity*)
*polytochange*
*reactorsToChange*
(member "GRIP_STRETCH" command-list)
;; for a GRIP_STRETCH, there will be only one reactor in
;; the global *reactorsToChange*.
(setq reactorData
(vlr-data (setq reactorToChange
(car *reactorsToChange*)
)
)
)
)
;; First, erase the tiles within the polyline border.
(gp:erase-tiles reactorToChange)
;; Next, get the current coordinate values of the polyline
;; vertices.
(setq coordinateValues
(vlax-safearray->list
(vlax-variant-value
(vla-get-coordinates *polyToChange*)
)
)
)
;; If the outline is a lightweight polyline, you have
;; 2d points, so use utility function xyList->ListOfPoints
;; to convert the coordinate data into lists of
;; ((x y) (x y) ...) points. Otherwise, use the
;; xyzList->ListOfPoints function that deals
;; with 3d points, and converts the coordinate data into
;; список ((x y z) (x y z) ... ) точек.
(setq CurrentPoints
(if (= (vla-get-ObjectName *polytochange*) "AcDbPolyline")
(xyList->ListOfPoints coordinateValues)
(xyzList->ListOfPoints coordinateValues)
)
)
;; Send this new information to RedefinePolyBorder -- this
;; will return the new Polyline Border
(setq NewReactorData
(gp:RedefinePolyBorder CurrentPoints reactorData)
)
;; Get all the border Points and ...
(setq newpts (list (cdr (assoc 12 NewReactorData))
(cdr (assoc 13 NewReactorData))
(cdr (assoc 14 NewReactorData))
(cdr (assoc 15 NewReactorData))
)
)
;; ...update the outline of the polyline with the new points
;; calculated above. If dealing with a lightweight polyline,
;; convert these points to 2D (since all the points in
;; newpts are 3D), otherwise leave them alone.
(if (= (cdr (assoc 4 NewReactorData)) "LIGHT")
(setq newpts (mapcar '(lambda (point)
(3dPoint->2dPoint Point)
)
newpts
)
)
)
;; Now update the polyline with the correct points.
(vla-put-coordinates
*polytochange*
;; For description of the list->variantArray see utils.lsp.
(gp:list->variantArray (apply 'append newpts))
)
;; Now use the current definition of the NewReactorData,
;; which is really the same as the garden path data
;; structure. The only exception is that the field (100)
;; containing the list of tiles is nil. This is OK since
;; gp:Calculate-and-Draw-Tiles does not require this field
;; to draw the tiles. In fact this function creates the tiles
;; and returns a list of drawn tiles.
(setq tileList (gp:Calculate-and-Draw-Tiles
;; path data list without correct tile list
NewReactorData
;; Object creation function
;; Within a reactor this *MUST* be ActiveX
"ActiveX"
)
)
;; Now that you have received all the tiles drawn, rebuild
;; the data structure with the correct tileList value and
;; reset the data property in the reactor.
;; Update the tiles associated with the polyline border.
(setq NewReactorData
(subst (cons 100 tileList)
(assoc 100 NewReactorData)
NewReactorData
)
)
;; By now you have the new data associated with the polyline.
;; All there is left to do is associate it with the reactor
;; using vlr-data-set.
(vlr-data-set (car *reactorsToChange*) NewReactorData)
;; Remove all references to the temporary
;; variables *polytochange* and *reactorsToChange*.
(setq *polytochange* nil
*reactorsToChange* nil
)
)
)
;; Delete any items in the *Safe-to-Delete* global if you can!!!
(Gp:Safe-Delete (car command-list))
(princ)
)
Назначение действий элементам окна
;; Assign actions (the functions to be invoked) to dialog buttons
(action_tile
"gp_lw"
"(setq plineStyle \"Light\")"
)
(action_tile
"gp_hw"
"(setq plineStyle \"Pline\")"
)
(action_tile
"gp_actx"
"(setq objectCreateMethod \"ActiveX\")"
)
(action_tile
"gp_emake"
"(setq objectCreateMethod \"Entmake\")"
)
(action_tile
"gp_cmd"
"(setq objectCreateMethod \"Command\")"
)
(action_tile "cancel" "(done_dialog) (setq UserClick nil)")
(action_tile
"accept"
(strcat "(progn (setq tileRad (atof (get_tile \"gp_trad\")))"
"(setq tileSpace (atof (get_tile \"gp_spac\")))"
"(done_dialog) (setq UserClick T))"
)
)
Обратим внимание на кавычки вокруг кода AutoLISP. Функция AutoLISP action_tile сообщает элементу окна строку в кавычках, которую элемент должен будет вернуть при его выборе. Строка (все, что находится внутри кавычек) бездействует до тех пор, пока пользователь не выберет элемент окна. Именно тогда элемент окна передает строку в AutoCAD, где строка преобразуется в рабочий код AutoLISP и выполняется.
Например, рассмотрим следующее выражение action_tile, которое связано с положением переключателя для компактной полилинии:
(action_tile
"gp_lw"
"(setq plineStyle \"Light\")"
)
Код присваивает положению переключателя строку «(setq plineStyle \"Light\")». Когда пользователь переводит переключатель в данное положение, строка передается в AutoCAD и преобразуется непосредственно в следующее выражение AutoLISP:
(setq plineStyle "Light")
Рассмотрим еще один фрагмент кода. Следующее выражение action_tile присвоено кнопке «OK»:
(action_tile
"accept"
(strcat "(progn (setq tileRad (atof (get_tile \"gp_trad\")))"
"(setq tileSpace (atof (get_tile \"gp_spac\")))"
"(done_dialog) (setq UserClick T))"
)
При нажатии кнопки "OK" длинная строка, назначенная кнопке, передается в AutoCAD и преобразуется в следующий код на AutoLISP:
(progn
(setq tileRad (atof (get_tile "gp_trad")))
(setq tileSpace (atof (get_tile "gp_spac")))
(done_dialog)
(setq UserClick T)
)
Код выполняет несколько действий. Он извлекает текущие значения элементов диалогового окна с ключевыми значениями gp_trad (радиус плитки) и gp_spac(расстояние между плитками). Затем с помощью функции atof строка чисел преобразуется в вещественное число. Функция done_dialog прерывает работу диалогового окна, и переменной UserClick присваивается значение T (истина).
Кнопкам диалогового окна назначены действия. Следующий этап заставить их работать.
Назначение реакторов
Необходимо учесть еще одно обстоятельство. Для пересчета контура полилинии (с целью возвращения ему прямоугольной формы после внесения в него изменений пользователем) необходимо знать, как располагалась вершина до перемещения. Установить эту информацию после того, как полилиния была изменена, нельзя. Известно только новое расположение точки. Как можно решить проблему? Можно сохранить эту информацию в глобальной переменной, но здесь возникает одно большое неудобство. Пользователи могут нарисовать множество различных парковых дорожек и для каждой из них потребуется своя глобальная переменная. Это может внести путаницу.
Объединение данных в ассоциативные списки
((10 4.46207 4.62318 0.0) (11 7.66688 4.62318 0.0) (40 . 1.018248))
В этом списке ключевыми кодами являются числа 10, 11 и 40. Эти ключевые коды служат уникальным индексом списка. С помощью данного механизма AutoCAD возвращает в AutoLISP информацию об объектах, полученную при выполнении программы. Код 10 обозначает начальную точку, код 11 обычно используется для конечной точки.
В чем преимущество использования ассоциативного списка? В отличие от обычного списка, порядок значений в нем не играет никакой роли. Еще раз рассмотрим первый список:
((4.46207 4.62318 0.0) (7.66688 4.62318 0.0) 0.509124)
Присмотримся к возвращенным значениям. Непонятно, какой из вложенных списков соответствует начальной, а какой — конечной точке. Более того, внесение изменений в такую функцию может нежелательным образом повлиять на другие функции, использующие возвращаемые ею данные.
В ассоциативном списке порядок следования значений не важен. Даже при изменении порядка элементы ассоциативного списка всегда можно идентифицировать. Например, код 11 обозначает конечную точку вне зависимости от того, где она располагается в общем списке.
((11 7.66688 4.62318 0.0) ; order of list
(40 . 1.018248) ; has been
(10 4.46207 4.62318 0.0)) ; modified
Темы:
Применение ассоциативных списков
Сохранение результата функции gp:getPointInput в переменной ї
Объяснение функции gp:RedefinePolyBorder
Функция gp:RedefinePolyBorder
Извлечь предыдущие точки углов полилинии
(ключевые значения 12, 13, 14 и 15).
Найти перемещенную угловую точку, сравнив предыдущие точки
углов полилинии с текущими угловыми точками.
Несовпадающая точка и есть перемещенная точка.
Задать новые угловые точки, пересчитав две точки,
соседние с перемещенной.
Обновить новые угловые точки в данных реактора (они будут
сохранены с реактором для измененной полилинии).
Обновить другую информацию в данных реактора. Необходимо пересчитать начальную
и конечную точки, ширину и длину дорожки.
Обновление фиктивной функции
Изменилось ли выражение defun? Необходимо ли функции то же число аргументов, что и раньше?
Изменился ли результат, возвращаемый функцией?
В случае функции gp:getDialogInput ответ на оба вопроса положителен. Функция теперь принимает параметр ширину дорожки (для определения значений по умолчанию для размера плиток и расстояния между ними). Вместо символа T, который возвращала раньше фиктивная функция, теперь результатом выполнения функции gp:getDialogInput является ассоциативный список, состоящий из четырех значений.
Оба изменения влияют как на код, вызывающий функцию, так и на код, обрабатывающий полученные функцией результаты. Замените предыдущую версию функции C:GPath в файле gpmain.lsp следующим кодом:
(defun C:GPath (/ gp_PathData gp_dialogResults)
;; Запрос на ввод: путь к местоположению и
;; direction, then for path parameters. Continue only if you
;; have valid input. Store the data in gp_PathData.
(if (setq gp_PathData (gp:getPointInput))
(if (setq gp_dialogResults (gp:getDialogInput (cdr(assoc 40
gp_PathData))))
(progn
;; Now take the results of gp:getPointInput and append this
;; to the added information supplied by gp:getDialogInput.
(setq gp_PathData (append gp_PathData gp_DialogResults))
;; At this point, you have all the input from the user.
;; Draw the outline, storing the resulting polyline
;; "pointer" in the variable called PolylineName.
(setq PolylineName (gp:drawOutline gp_PathData))
) ;_ end of progn
(princ "\nFunction cancelled.")
) ;_ end of if
(princ "\nIncomplete information to draw a boundary.")
) ;_ end of if
(princ) ; exit quietly
) ;_ end of defun
Обратите внимание на строки в измененной версии функции C:GPath, выделенные полужирным шрифтом. Для того чтобы программа работала правильно, внесено два основных изменения:
При вызове функции gp:getDialogInput ей передается ширина дорожки. Для этого из ассоциативного списка gp_PathData извлекается значение, которому присвоен код 40.
Ассоциативный список, возвращаемый функцией gp:getPointInput, записывается в переменную gp_dialogResults. Значение этой переменной, если оно имеется, добавляется к списку значений ассоциативного списка, уже хранящихся в gp_PathData.
Дополнительные изменения вызваны удалением теперь уже ненужных элементов фиктивной функции. Наиболее простой способ произвести их скопировать код из электронного учебного пособия и вставить его в файл.
Обновление функции C:GPath
Для создания реактора в C:GPath
Заменим содержимое файла gpmain.lsp обновленной версией кода. Для этого можно скопировать файл из папки <AutoCAD >\Tutorial\VisualLISP\Lesson6:
(defun C:GPath (/
gp_PathData
gp_dialogResults
PolylineName
tileList
)
(setvar "OSMODE" 0) ;; Отключают объектную привязку
;|
;; Lesson 6 - добавление фиктивного реактора команды в AutoCAD
;; However, it would be undesirable to react to every
;; drawing of a circle should the COMMAND tile creation
;; method be chosen by the user. So, disable the
;; *commandReactor* in case it exists.
|;
(if *commandReactor*
(progn
(setq *commandReactor* nil)
(vlr-remove-all :VLR-Command-Reactor)
)
)
;; Запрос на ввод: путь к местоположению и
;; direction, then for path parameters. Continue only if you
;; have valid input. Store the data in gp_PathData.
(if (setq gp_PathData (gp:getPointInput))
(if (setq gp_dialogResults
(gp:getDialogInput
(cdr (assoc 40 gp_PathData))
) ;_ end of gp:getDialogInput
) ;_ end of setq
(progn
;; Now take the results of gp:getPointInput and append this to
;; добавленная информация передается с помощью функции gp:getDialogInput
(setq gp_PathData (append gp_PathData gp_DialogResults))
;; На этом этапе все данные, указанные пользователем, введены
;; Draw the outline, storing the resulting polyline "pointer"
;; в переменную, именуемую PolylineName
(setq PolylineName (gp:drawOutline gp_PathData))
;; Next, it is time to draw the tiles within the boundary.
;; The gp_tileList contains a list of the object pointers for
;; the tiles. By counting up the number of points (using the
;; length function), we can print out the results of how many
;; tiles were drawn.
(princ "\nThe path required ")
(princ
(длина
(setq tileList (gp:Calculate-and-Draw-Tiles gp_PathData))
) ;_ end of length
) ;_ end of princ
(princ " tiles.")
;; Добавляют список указателей к плиткам (возвращаемый
;; gp:Calculate-and-Draw-Tiles) to gp_PathData. This will
;; be stored in the reactor data for the reactor attached
;; to the boundary polyline. With this data, the polyline
;; "knows" what tiles (circles) belong to it.
(setq gp_PathData
(append (list (cons 100 tileList))
; all the tiles
gp_PathData
) ;_ end of append
) ;_ end of setq
;; Прежде чем назначить данные реактора объекту, рассмотрим
;; функцию vlr-object-reactor
;; vlr-object-reactor has the following arguments:
;; (vlr-object-reactor owner’s data callbacks)
;; The callbacks Argument is a list comprised
;; '(event_name . функция_отклика)
;;
;; For this exercise we will use all arguments
;; связанные с реактором объекта vlr-object-reactor
;; These reactor functions will execute only if
;; полилиния будет изменена или удалена в PolylineName
(vlr-object-reactor
;; The first argument for vlr-object-reactor is
;; the "Owner’s List" argument. This is where to
;; place the object to be associated with the
;; reactor. In this case, it is the vlaObject
;; stored in PolylineName.
(list PolylineName)
;; The second argument contains the data for the path
gp_PathData
;; The third argument is the list of specific reactor
;; типов реакторов, которые требуется использовать
'
(
;; реактор, вызываемый при изменении объекта
(:vlr-modified . gp:outline-changed)
;; реактор, вызываемый при удалении объекта
(:vlr-erased . gp:outline-erased)
)
) ;_ end of vlr-object-reactor
;; Next, register a command reactor to adjust the polyline
;; по завершении команды изменения
(if (not *commandReactor*)
(setq *commandReactor*
(VLR-Command-Reactor
nil ; No data is associated with the command reactor
'(
(:vlr-commandWillStart . gp:command-will-start)
(:vlr-commandEnded . gp:command-ended)
)
) ;_ end of vlr-command-reactor
)
)
;; The following code removes all reactors when the drawing is
;; closed. This is extremely important!!!!!!!!!
;; Без этого уведомления может произойти аварийное завершение работы AutoCAD!
(if (not *DrawingReactor*)
(setq *DrawingReactor*
(VLR-DWG-Reactor
nil ; No data is associated with the drawing reactor
'((:vlr-beginClose . gp:clean-all-reactors)
)
) ;_ end of vlr-DWG-reactor
)
)
) ;_ end of progn
(princ "\nFunction cancelled.")
) ;_ end of if
(princ "\nIncomplete information to draw a boundary.")
) ;_ end of if
(princ) ; exit quietly
) ;_ end of defun
;;; Display a message to let the user know the command name.
(princ "\nType GPATH to draw a garden path.")
(princ)
Просмотрите изменения в коде и комментарии к каждому выражению. Все внесенные изменения по сравнению с предыдущим вариантом в данном учебном пособии выделяются полужирным шрифтом.
Обновление функции gp:Calculate-and-Draw-Tiles
(setq tileList (gp:Calculate-and-Draw-Tiles
;; path data list without correct tile list.
NewReactorData
;; Object creation function.
;; Within a reactor this *MUST* be ActiveX.
"ActiveX"
)
)
Функции gp:Calculate-and-Draw-Tiles передается два параметра: NewReactorData (список в форме исходного ассоциативного списка gp_PathData) и строка "ActiveX" (для задания стиля создания объекта). Посмотрим на текущее описание функции gp:Calculate-and-Draw-Tiles (функция описана в файле gpdraw.lsp). Здесь приведена часть функции, которая объявляет параметры и локальные переменные:
(defun gp:Calculate-and-Draw-Tiles (BoundaryData /
PathLength TileSpace
TileRadius SpaceFilled
SpaceToFill RowSpacing
offsetFromCenter rowStartPoint
pathWidth pathAngle
ObjectCreationStyle TileList)
Заметим, что сейчас задан только один параметр, а переменная ObjectCreationStyle описана как локальная. Посмотрим, как присваивается значение переменной ObjectCreationStyle (далее в описании функции):
(setq ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData))))
Переменная ObjectCreationStyle определена внутри функции и извлекает значение переменной BoundaryData (ассоциативный список). Теперь нужно переопределить это значение.
Для добавления в функцию gp:Calculate-and-Draw-Tiles аргумента стиля создания объектов
Добавьте переменную ObjectCreationStyle к аргументам функции.
Удалите ObjectCreationStyle из списка локальных переменных.
Выражение defun для функции должно выглядеть следующим образом:
(defun gp:Calculate-and-Draw-Tiles (BoundaryData
ObjectCreationStyle
/ PathLength TileSpace
TileRadius SpaceFilled
SpaceToFile RowSpacing
offsetFromCenter rowStartPoint
pathWidth pathAngle
TileList) ; remove ObjectCreationStyle from locals
Если переменная объявлена одновременно и параметром (до косой черты), и локальной переменной (после косой черты), VLISP сообщит об этом. Например, если описать ObjectCreationStyle одновременно и как параметр, и как переменную, а затем запустить средство синтаксической проверки VLISP для функции gp:Calculate-and-Draw-Tiles, в окне «Сообщения сборки» появится следующее сообщение:
; *** ВНИМАНИЕ: один и тот же символ найден до и после / в списке аргументов: OBJECTCREATIONSTYLE
Измените первое выражение setq в gp:Calculate-and-Draw-Tiles так, чтобы оно выглядело следующим образом (изменения обозначены полужирным шрифтом):
(setq
PathLength (cdr (assoc 41 BoundaryData))
TileSpace (cdr (assoc 43 BoundaryData))
TileRadius (cdr (assoc 42 BoundaryData))
SpaceToFill (- PathLength TileRadius)
RowSpacing (* (+ TileSpace (* TileRadius 2.0))
(sin (Degrees->Radians 60))
)
SpaceFilled RowSpacing
offsetFromCenter 0.0
offsetDistance /(+(* TileRadius 2.0)TileSpace)2.0)
rowStartPoint cdr (assoc 10 BoundaryData))
pathWidth cdr (assoc 40 BoundaryData))
pathAngle cdr (assoc 50 BoundaryData))
) ;_ end of setq
(if (not ObjectCreationStyle)
(setq ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData))))
)
Ранее стоявшее в тексте выражение, которое инициализировало переменную ObjectCreationStyle, теперь удалено. Программа проверяет, имеет ли параметр ObjectCreationStyle какое-либо значение. Если значение не задано (т.е. ObjectCreationStyle равно nil), функция присваивает параметру значение из переменной BoundaryData.
Теперь необходимо сделать еще ряд изменений в функции gp:Calculate-and-Draw-Tiles.
Обработка нелинейных последовательностей реакторов
;; To start, select the polyline and some of the circles by using a
;; crossing selection box. The items in the selection set--
;; the chosen circles and the polyline--are now shown with grips on.
;; To initiate the sequence, click on one of the polyline grips:
(GP:COMMAND-WILL-START #<VLR-Command-reactor> (GRIP_STRETCH))
;; Now change the command to a move by right-clicking and choosing
;; MOVE from the pop-up menu. Notice that the command-ended
;; reactor fires in order to close out the GRIP_STRETCH command
;; without having fired an object reactor event:
(GP:COMMAND-ENDED #<VLR-Command-reactor> (GRIP_STRETCH))
(GP:COMMAND-WILL-START #<VLR-Command-reactor> (GRIP_MOVE))
;; Now drag the outline (and the selected circles) to a new location.
(GP:OUTLINE-CHANGED #<VLA-OBJECT IAcadLWPolyline 028f3188>
#<VLR-Object-reactor> nil)
(GP:COMMAND-ENDED #<VLR-Command-reactor> (GRIP_MOVE))
Как видно из показанного примера, нельзя быть уверенным, что функции отклика для объектных реакторов вызываются во всех случаях.
Данная последовательность имеет некоторые отличия от обычной. Даже во время последнего отклика на окончание команды круги, являющиеся выбранными, не удаляются.
Эти круги все еще открыты программой AutoCAD. Попытка удалить их с помощью функции отклика на окончание команды может привести к сбою в AutoCAD. Для того чтобы избежать этого, можно воспользоваться другой глобальной переменной, хранящей список указателей на объекты-плитки до тех пор, пока последние не будут удалены.
Для обработки нелинейной реакторной последовательности
Добавьте в файл gpreact.lsp следующую функцию:
(defun gp:erase-tiles (reactor / reactorData tiles tile)
(if (setq reactorData (vlr-data reactor))
(progn
;; Tiles in the path are stored as data in the reactor.
(setq tiles (cdr (assoc 100 reactorData)))
;; Erase all the existing tiles in the path.
(foreach tile tiles
(if (and (null (member tile *Safe-to-Delete*))
(not (vlax-erased-p tile))
)
(progn
(vla-put-visible tile 0)
(setq *Safe-to-Delete* (cons tile *Safe-to-Delete*))
)
)
)
(vlr-data-set reactor nil)
)
)
)
Эта функция будет использоваться на первом этапе удаления плиток. Обратите внимание, что что плитки в действительности не стираются: они становятся невидимыми и добавляются в глобальную переменную *Safe-to-Delete*.
Добавьте в файл gpreact.lsp следующую функцию:
(defun Gp:Safe-Delete (activeCommand)
(if (not (equal
(strcase (substr activeCommand 1 5))
"GRIP_"
)
)
(progn
(if *Safe-to-Delete*
(foreach Item *Safe-to-Delete*
(if (not (vlax-erased-p Item))
(vla-erase item)
)
)
)
(setq *Safe-to-Delete* nil)
)
)
)
Функция может быть вызвана, если неактивны команды перемещения или растягивания объектов с помощью ручек.
Обработка нескольких типов объектов
Для того чтобы найти положение границы измененной дорожки после перемещения одной из вершин полилинии, необходимо выполнить некоторые вычисления. Вычисления значительно упрощаются, если данные о полилинии имеют постоянный формат.
Файл utils.lsp занятия 7 содержит функции для выполнения необходимых преобразований формата: функция xyzList->ListOfPoints извлекает и форматирует списки 3М точек в список списков, а функция xyList->ListOfPoints делает то же самое со списками 2М точек.
Для добавления кода для преобразования данных полилинии в постоянных формат
Если файл utils.lsp открыт в окне текстового редактора VLISP, закройте его.
Скопируйте версию файла utils.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку.
Помимо двух функций преобразования формата данных о полилиниях, файл utils.lsp содержит служебные функции, необходимые для поддержания пользовательских изменений в парковой дорожке.
Откройте файл utils.lsp в текстовом редакторе VLISP и просмотрите новый код.e.
Общее планирование реакторного процесса
Примечание В процессе разработки и отладки приложений с реакторами всегда существует потенциальная опасность, что AutoCAD® останется в нестабильном состоянии. Это может быть вызвано различными причинами например, невозможности удалить реакторы удаленных объектов. Поэтому рекомендуется перед началом 7 занятия сохранить все открытые файлы, закрыть VLISP, выйти из AutoCAD и перезапустить оба приложения.
Начнем с того, что загрузим проект в том виде, каким он стал к концу шестого занятия.
В приложении парковой дорожки осталось выполнить две основных задачи:
Написать функции отклика для реактора объекта.
Написать функции отклика для реактора редактора.
Необходимо также решить, каким образом программа будет обращаться с глобальными переменными. Часто требуется, чтобы глобальные переменные сохраняли свои значения в течении всего сеанса AutoCAD. Однако в случае с реакторами это не так. Чтобы понять почему, представим себе, что пользователь построил с помощью нашего приложения несколько парковых дорожек в одном рисунке. После этого пользователь начал стирать их, сначала по одной, затем по две и т.д., до тех пор пока все дорожки, кроме одной, не оказались стертыми.
На занятии 5 была введена глобальная переменная *reactorsToRemove*, ответственная за хранение указателей на реакторы для удаляемых полилиний. Объявление переменной *reactorsToRemove* в функции gp:outline-erased означает, что полилиния сейчас будет удалена. В действительности же полилиния удаляется только после вызова функции gp:command-ended.
При первом стирании полилинии все работает правильно. Указатель на реактор хранится в gp:outline-erased. При вызове функции gp:command-ended удаляются все плитки, связанные с полилинией, к которой был прикреплен реактор. После этого пользователь решает удалить две дорожки. В результате приложение получает два вызова функции gp:outline-erased, по одному на каждую стираемую полилинию. Здесь возникают две потенциальные проблемы:
При выполнении функции setq для переменной *reactorsToRemove* необходимо добавить к этой переменной указатель на реактор, не стирая уже имеющихся в ней значений. Это означает, что переменная *reactorsToRemove* должна иметь структуру списка, в который можно добавлять указатели на реакторы. Таким образом, можно будет хранить несколько указателей на реакторы по количеству дорожек, удаляемых пользователем с помощью одной команды.
При каждом вызове функции gp:command-will-start, обозначающей начало новой последовательности команд, необходимо сбрасывать значение переменной *reactorsToRemove* в nil. Это необходимо, чтобы глобальная переменная не хранила указатели на реакторы после предыдущих команд "Стереть".
Невыполнение этих требований (корректная структура данных в виде списка и сброс значений глобальной переменной) может привести к непредсказуемому поведению программы. В случае реакторов непредвиденные результаты могут стать причиной неустранимой ошибки в сеансе AutoCAD.
Ниже приведена цепь событий при стирании пользователем двух парковых дорожек одной командой. Следует обратить внимание на глобальные переменные:
Вызвать команду стереть. Запускается функция gp:command-will-start. Переменной *reactorsToRemove* присваивается значение nil.
Выбрать две полилинии. Приложение еще не получило сигнала.
Нажать ENTER для стирания двух выбранных полилиний.
Приложение получает отклик на gp:outline-erased для одной из полилиний. Указатель на реактор добавляется в пустую глобальную переменную *reactorsToRemove*.
Приложение получает отклик на gp:outline-erased для второй полилинии.
Добавьте ее указатель на реактор к глобальной переменной *reactorsToRemove*, которая уже содержит один указатель на реактор.
AutoCAD удалит полилинии.
Запускается функция отклика gp:command-ended. Все плитки, связанные с указателями на реакторы, хранящимися в *reactorsToRemove*, удаляются.
Кроме глобальной переменной *reactorsToRemove* приложение также содержит глобальную переменную *polyToChange* которая хранит указатели на изменяемые полилинии. Две дополнительные глобальные переменные для приложения будут представлены позже на этом занятии.
Темы:
Реакция на вызываемые пользователем команды
Хранение информации с реакторами
Общие сведения о реакторах
Примечание Код программы в двух последних занятиях существенно сложнее, чем в занятиях 1-5, и требует от читателя значительно большего уровня подготовки. Здесь дается большой объем информации, при этом не все объясняется так же подробно, как ранее. Вполне вероятно, что новички могут не понять описанный здесь материал с первого раза. Поэтому его следует рассматривать как первое знакомство с более мощными, но и более сложными возможностями Visual LISP®.
Темы:
Типы реакторов
Обзор учебного пособия
Занятия 4 и 5 предназначены для пользователей среднего уровня и выходят за рамки базовых концепций AutoLISP. Занятия 6 и 7 посвящены достаточно сложным задачам программирования и предназначены для опытных разработчиков AutoLISP.
Если AutoCAD устанавливался в полном варианте, то файлы исходного кода находятся в следующей папке:
<Папка AutoCAD>\Tutorial\VisualLISP\
Если продукт AutoCAD установлен без примеров, можно повторно запустить установку, выбрать вариант "Выборочная", а затем указать элемент "Учебное пособие".
Перед началом выполнения учебного пособия рекомендуется создать рабочую папку и скопировать в нее все файлы исходного кода AutoCAD. Это позволит при возникновении трудностей в ходе изучения учебного пособия восстановить файлы с правильным кодом. В тексте учебного пособия рабочая папка обозначается следующим образом:
<Папка AutoCAD>\Tutorial\VisualLISP\MyPath
Вместо этой строки в каждом конкретном случае следует вводить путь, по которому реально находится рабочая папка.
Окончательная компоновка
Для обновления кода
Замените старый код функции gp:drawOutline следующим фрагментом:
;;;---------------------------------------------------------------
;;; Функция: gp:drawOutline
;;;---------------------------------------------------------------
;;; Описание: С помощью этой функции можно создать границу парковой
;;; дорожки.
;;;---------------------------------------------------------------
;;; Примечание: Проверка на наличие ошибок и коррекция не выполняются на
;;; BoundaryData parameter. The sequence of items within this
;;; parameter does not matter, but it is assumed that all sublists
;;; are present and contain valid data.
;;; --------------------------------------------------------------
(defun gp:drawOutline (BoundaryData / VLADataPts PathAngle
Width HalfWidth StartPt PathLength
angm90 angp90 p1 p2
p3 p4 polypoints pline
)
;; extract the values from the list BoundaryData
(setq PathAngle (cdr (assoc 50 BoundaryData))
Width (cdr (assoc 40 BoundaryData))
HalfWidth (/ Width 2.00)
StartPt (cdr (assoc 10 BoundaryData))
PathLength (cdr (assoc 41 BoundaryData))
angp90 (+ PathAngle (Degrees->Radians 90))
angm90 (- PathAngle (Degrees->Radians 90))
p1 (polar StartPt angm90 HalfWidth)
p2 (polar p1 PathAngle PathLength)
p3 (polar p2 angp90 Width)
p4 (polar p3 (+ PathAngle (Degrees->Radians 180)) PathLength)
polypoints (apply 'append
(mapcar '3dPoint->2dPoint (list p1 p2 p3 p4))
)
)
;; ***** data conversion *****
;; Notice, polypoints is in AutoLISP format, consisting of a list
;; of the 4 corner points for the garden path.
;; The variable needs to be converted to a form of input parameter
;; acceptable to ActiveX calls.
(setq VLADataPts (gp:list->variantArray polypoints))
;; Add polyline to the model space using ActiveX automation.
(setq pline (vla-addLightweightPolyline
*ModelSpace* ; Global Definition for Model Space
VLADataPts
) ;_ end of vla-addLightweightPolyline
) ;_ end of setq
(vla-put-closed pline T)
;; Return the ActiveX object name for the outline polyline
;; The return value should look something like this:
;; #<VLA-OBJECT IAcadLWPolyline 02351a34>
плиния (pline)
) ;_ end of defun
Обратите внимание на то, что gp:drawOutline теперь возвращает переменную pline, а не символ с апострофом 'SomeEname, как было в фиктивной версии функции.
Отформатируем введенный код. Для этого выделите его и нажмите кнопку «Форматирование выделенного фрагмента» на панели инструментов VLISP.
Добавим код для загрузки ActiveX и опишем глобальную переменную, хранящую указатель на пространство модели, как это было описано выше. Для этого установите курсор в верхней части окна текстового редактора и добавьте перед первым выражением defun следующий фрагмент:
;;;--------------------------------------------------------------
;;; Сначала необходимо загрузить функции ActiveX. If ActiveX support
;;; already exists in document (can occur when Bonus tools have been
;;; загружен в AutoCAD), ничего не произойдет. Otherwise, ActiveX
;;; support is loaded.
;;;---------------------------------------------------------------
(vl-load-com)
;;; In Lesson 4, the following comment and code is moved to utils.lsp
;;;---------------------------------------------------------------
;;; For ActiveX functions, we need to define a global variable that
;;; "points" to the Model Space portion of the active drawing. This
;;; variable, named *ModelSpace* will be created at load time.
;;;---------------------------------------------------------------
(setq *ModelSpace*
(vla-get-ModelSpace
(vla-get-ActiveDocument (vlax-get-acad-object))
) ;_ end of vla-get-ModelSpace
) ;_ end of setq
Обратите внимание, что этот фрагмент кода должен находиться в тексте раньше, чем все функции, объявленные defun. Это обеспечивает автоматическое выполнение данного кода VLISP при загрузке файла.
Найдите следующую строку в функции C:GPath:
(setq PolylineName (gp:drawOutline))
Измените ее следующим образом:
(setq PolylineName (gp:drawOutline gp_PathData))
Теперь функция gp: drawOutline ожидает передачи параметра списка, содержащего данные для построения полилинии. Выполненное изменение удовлетворяет этому требованию.
Добавьте в конец файла gpmain.lsp функцию Преобразование списка точек в массив типа variantgp:list->variantArray, описанную в разделе .
Попробуйте загрузить и выполнить измененную программу. По окончании выполнения программы управление передается из AutoCAD в VLISP, поэтому для просмотра результатов необходимо снова переключиться в окно AutoCAD. Если программа работает правильно, она должна построить границы парковой дорожки. При возникновении ошибок следует отладить код и запустить программу заново.
Окончательный код программы
Функция gp:drawOutline изменена таким образом, что помимо указателей на полилинию возвращаются и точки периметра полилинии. Эта информация была добавлена к переменной gp_PathData. Переменная хранится с объектным реактором, назначенным каждой парковой дорожке.
Обновлена функция реактора в файле gpreact.lsp.
Добавлены функции xyzList->ListOfPoints, xyList->ListOfPoints и другие утилиты в файл utils.lsp.
Обновлена функция gp:Calculate-and-Draw-Tiles; ObjectCreationStyle преобразована в ней из локальной переменной в параметр.
Изменен вызов функции gp:Calculate-and-Draw-Tiles из функции C:GPath (файл gpmain.lsp).
К проекту добавлен файл gppoly.lsp, а затем рассмотрены функции, входящие в него.
Теперь можно проверить готовое приложение. Для этого надо сохранить результаты работы, загрузить исходные файлы проекта, выполнить функцию Gpath и попробовать растянуть и передвинуть границу построенной парковой дорожки. Следует помнить: если что-то не работает и программу не удается отладить, необходимо загрузить готовый код из папки Tutorial\VisualLISP\Lesson7.
Описание диалогового окна с помощью DCL
Диалоговое окно должно содержать следующие элементы:
Два переключателя.
Один переключатель управляет типом полилинии, образующей границу; другой задает метод создания плиток (ActiveX, entmake или command). Каждый из переключателей в определенный момент времени может быть установлен только в одно из положений.
Текстовые поля для задания радиуса плиток и расстояния между ними.
Стандартные кнопки «OK» и «Cancel».
Компоненты диалогового окна называются его элементами. Полное описание диалогового окна в файле DCL вначале может казаться трудной задачей. Поэтому нужно составить общую схему того, что необходимо включить в диалоговое окно, а затем разбить ее на несколько частей и описать каждую часть отдельно.
Для описания диалогового окна
Откройте новый файл в текстовом редакторе VLISP.
В новом файле введите следующее выражение:
label = "Garden Path Tile Specifications";
Это выражение DCL описывает заголовок диалогового окна.
Опишите с помощью следующего кода положения переключателя, используемые для задания типа полилинии:
: boxed_radio_column { // определяет положение кнопки-переключателя
label = "Outline Polyline Type";
: radio_button { // определяет компактную кнопку-переключатель
label = "&Lightweight";
key = "gp_lw";
value = "1";
}
: radio_button { // определяет кнопку-переключатель полилинии старого формата
label = "&Old-style";
key = "gp_hw";
}
}
DCL-директива boxed_radio_column описывает границу группы переключателя и позволяет указать его имя. Внутри границы отдельные положения переключателя задаются директивами radio_button. Для каждого положения переключателя необходимо задать имя и ключ. Ключ - это имя, по которому идентифицируется положение переключателя внутри AutoLISP .
Обратите внимание, что положению переключателя «lightweight» (компактная полилиния) присвоено значение 1. Значение 1 (это строка, а не целое число) указывает, что данное положение должно быть активным по умолчанию. Другими словами, при появлении диалогового окна на экране в переключателе устанавливается выбранным именно этот вариант. Следует также запомнить, что в DCL-файлах символами комментариев являются две косых черты, а не точка с запятой, как в программах AutoLISP.
Опишите с помощью следующего кода переключатель, используемый для задания метода создания объектов:
: boxed_radio_column { // определяет положение кнопки-переключателя
label = "Tile Creation Method";
: radio_button { // определяет кнопку-переключатель ActiveX
label = "&ActiveX Automation";
key = "gp_actx";
value = "1";
}
: radio_button { // определяет кнопку-переключатель (функции entmake)
label = "&Entmake";
key = "gp_emake";
}
: radio_button { // определяет кнопку-переключатель (команды)
label = "&Command";
key = "gp_cmd";
}
}
Для описания текстовых полей, в которых будет задаваться размер плиток и расстояния между ними, добавьте следующий код:
: edit_box { // определяет окно редактирования "Радиус плитки"
label = "&Radius of tile";
key = "gp_trad";
edit_width = 6;
}
: edit_box { // определяет окно редактирования "Расстояние между плитками"
label = "S&pacing between tiles";
key = "gp_spac";
edit_width = 6;
}
Обратим внимание, что текстовым полям не присваивается никакого исходного значения. Значение по умолчанию для каждого текстового поля задаются из AutoLISP-программы.
Добавьте следующий код для кнопок «OK» и «Cancel»:
: row { // определяет кнопки "OK"/"Отмена"
: spacer { width = 1; }
: button { // определяет кнопку "OK"
label = "OK";
is_default = true;
key = "accept";
width = 8;
fixed_width = true;
}
: button { // определяет кнопку "Отмена"
label = "Cancel";
is_cancel = true;
key = "cancel";
width = 8;
fixed_width = true;
}
: spacer { width = 1;}
}
Обе кнопки описаны внутри одного ряда, поэтому они устанавливаются по одной горизонтальной линии.
Установите курсор в верхней части окна текстового редактора и вставьте первой строкой следующее выражение:
gp_mainDialog : dialog {
Директива dialog требует закрывающей фигурной скобки. Поэтому поместите курсор в конец файла и вставьте закрывающую фигурную скобку последней строкой DCL-кода:
}
Описание функции gp:FindMovedPoint
Наилучший способ познакомиться с работой функции — это выполнить ее пошагово и посмотреть значения, которыми она оперирует. Для этого надо задать точку останова в первом выражении (setq result . . .) и проследить, как изменяются при пошаговом выполнении функции следующие переменные:
KeyListToLookFor;
PresentPoints;
KeyedList;
Result;
KeyListStatus;
MissingKey;
MovedPoint.
Функции mapcar и lambda будут рассмотрены в следующем разделе. Для того чтобы понять, что происходит внутри функций, следует ознакомиться с комментариями к коду.
Описание функции gp:FindPointInList
Функция mapcar применяет выражение ко всем элементам списка. Например, если имеется список целых чисел 1, 2, 3 и 4, функцию mapcar можно использовать для прибавления единицы (выражение 1+) ко всем элементам списка:
_$ (mapcar '1+ '(1 2 3 4))
(2 3 4 5)
Исходное назначение функции mapcar заключается в том, что она последовательно применяет функцию, указанную в первом параметре к элементам второго параметра списка. Результатом выполнения операции mapcar является преобразованный список. В действительности функция mapcar может использоваться не только для этого, но на данном этапе такого определения достаточно.
В приведенном примере для каждого значения в списке '(1 2 3 4) была выполнена функция 1+. По сути дела, функция mapcar выполнила несколько операций, а затем собрала полученные значения в один список:
(1+ 1) -> 2
(1+ 2) -> 3
(1+ 3) -> 4
(1+ 4) -> 5
Приведем еще один пример использования mapcar, на этот раз для выполнения функции null с целью выявления в списке неверных значений:
_$ (mapcar 'null (list 1 (= 3 "3") nil "Steve"))
(nil T T nil)
В данном фрагменте кода выполняется следующее:
(null 1) -> nil
(null (= 3 "3") -> T
(null nil) -> T
(null "Steve") -> nil
Внутри функции mapcar можно использовать большинство из функций AutoLISP.
Допускается также применять пользовательские функции. Например, возьмем пользовательскую функцию с именем equals2:
_$ (defun equals2(num)(= num 2))
EQUALS2
_$ (mapcar 'equals2 '(1 2 3 4))
(nil T nil nil)
Однако, функцию equals2 можно и не описывать отдельно. В таких случаях может пригодиться функция lambda. Она служит для того, чтобы создавать функции «на ходу»; другими словами lambda описывает анонимные функции. Иногда lambda определяется как анонимная функция. Например, вместо описания функции equals2можно использовать выражение lambda для выполнения той же операции:
_$ (mapcar '(lambda (num) (= num 2)) '(1 2 3 4))
(nil T nil nil)
В данном фрагменте кода выполняется следующее:
(= 1 2) -> nil
(= 2 2) -> T
(= 3 2) -> nil
(= 4 2) -> nil
После этих пояснений работа функции gp:FindPointInList становится понятной. Снова обратимся к комментариям внутри исходного кода.
Описание функции gp:recalcPolyCorners
В изображенном на рисунке случае угловая точка с кодом 14 была перемещена пользователем. Это означает, что необходимо пересчитать угловые точки 13 и 15.
Точку 15 необходимо передвинуть вдоль текущего вектора, определенного точками 12 и 15, до тех пор, пока она не выровняется с новой точкой 14. Векторы от 12 к 15 и от 14 к 15 должны быть перпендикулярны друг другу. То же самое необходимо сделать для вычисления нового положения точки 13.
Посмотрите еще раз на программный код функции. Теперь он должен стать более понятным.
Описание функций gp:pointEqual, gp:rtos2 и gp:zeroSmallNum
В некоторых случаях при выводе информации, связанной с AutoCAD, можно встретить такие малые значения, как, например, 1.0e-017. Это число почти равно нулю, однако при сравнении его с нулем даже такое малое несовпадение играет роль.
В приложении построения парковой дорожки при сравнении чисел необходимо учитывать все сказанное выше. Функции gp:pointEqual, gp:rtos2 и gp:zeroSmallNum как раз и служат для правильной обработки различий, возникающих из-за округления, при сравнении списков.
На этом обзор функций файла gppoly.lsp завершается.
Панель инструментов «Отладка»
Панель «Отладка» располагается левее всех. Большинство кнопок панели активизируются только после запуска программы в отладочном режиме (т.е. если заданы одна или несколько точек останова).
Для удобства использования панель «Отладка» можно открепить из ее исходного положения. Для этого нужно установить указатель мыши на две вертикальные линии в левой части панели и перетащить панель. Точно так же можно открепить и установить в удобное для работы место любую другую панель VLISP.
Панель «Отладка» разделена на три основные группы, в каждой из которых имеется по три кнопки. При выполнении программы в режиме отладки панель выглядит следующим образом:
Первые три кнопки позволяют выполнять код программы по шагам.
Следующие три кнопки определяют поведение VLISP после обнаружения точки останова или ошибки.
Следующие три кнопки служат для задания или удаления точек останова, добавления переменных для контроля их значений и перехода к месту последнего останова в исходном коде.
Последняя кнопка на панели «Отладка» является индикатором положения курсора. Она не выполняет никаких функций, а только наглядно показывает положение курсора при пошаговой отладке кода. При выполнении программы в обычном режиме (не в режиме отладки) на кнопке ничего не изображается.
Для задания точки останова
В окне редактора VLISP, содержащем файл gpmain.lsp, установите указатель мыши перед открывающей скобкой функции setq в следующей строке кода внутри функции gp:getPointInput:
(setq HalfWidth (getdist EndPt "\nhalf-width of path: "))
Щелкните один раз мышью. Курсор устанавливается в указанную позицию, как видно на следующей иллюстрации:
Установив курсор в нужное положение, нажмите кнопку "Точка останова Вкл/Откл" на панели инструментов "Отладка".
Кнопка «Точка останова Вкл/Откл» действует как переключатель между состояниями «Вкл» и «Откл». Если в положении курсора нет точки останова, то она ставится, если уже имеется удаляется.
Чтобы загрузить файл, нажмите кнопку "Загрузка активного окна редактирования" на панели "Инструменты".
Запустите функцию (C:GPath) из окна консоли VLISP.
VLISP выполняет программу обычным образом вплоть до точки останова. В данном случае программа запрашивает ввести первые две точки дорожки (начальную и конечную).
Укажите в ответ на запросы начальную и конечную точки.
После этого VLISP приостанавливает выполнение программы и переключается в окно текстового редактора. Строка, на которой произошел останов, выделяется.
Необходимо обратить внимание на следующее:Необходимо обратить внимание на следующее:
Курсор располагается прямо в точке останова. Это может оказаться не очевидным, поэтому VLISP предлагает еще одну подсказку.
курсор, и точки останова На панели «Отладка» индикатор положения курсора принимает вид красного курсора перед парой скобок. Это означает, что работа отладчика VLISP была приостановлена перед выражением.
Передача параметров функциям
(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0)))
При вызове функции необходимо передать ей число. Число внутри функции Degrees->Radians объявляется параметром, называемым numberOfDegrees. Например: Например:
_$ (degrees->radians 90)
1.5708
В данном случае параметру numberOfDegrees присваивается число 90.
Можно также передать функции аргумент, являющийся переменной. Например, пусть имеется переменная aDegreeValue. Следующие функции присваивают aDegreeValue значение 90 и передают его функции Degrees->Radians:
_$ (setq aDegreeValue 90)
90
_$ (degrees->radians aDegreeValue)
1.5708
Переопределение полилинии-границы
Для копирования кода, переопределяющего полилинии-границы
Скопируйте файл gppoly.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку.
В окне проекта нажмите кнопку «Свойства проекта».
Добавьте к проекту файл gppoly.lsp.
Нажмите «OK» для добавления файла в проект.
В окне проекта дважды щелкните мышью на имени gppoly.lsp, чтобы открыть файл.
Темы:
Изучение функций файла gppoly.lsp
Объяснение функции gp:RedefinePolyBorder
Описание функции gp:FindMovedPoint
Описание функции gp:FindPointInList
Описание функции gp:recalcPolyCorners
Описание функций gp:pointEqual, gp:rtos2 и gp:zeroSmallNum
Переработка кода программы
Примечание При наборе текста программы в файле gpmain.lsp вручную (а не копированием из имеющегося готового файла) можно сэкономить время, исключив все комментарии (строки, начинающиеся с точки запятой). Однако это не означает, что следует привыкать писать код без комментариев и в дальнейшем!
;;;--------------------------------------------------------------;
;;; Функция: gp:getPointInput ;
;;;--------------------------------------------------------------;
;;; Описание: С помощью этой функции пользователю предлагается выбрать три точки с запятой (;)
;;; points in a drawing, which will determine the ;
;;; path location, direction, and size. ;
;;;--------------------------------------------------------------;
;;; If the user responds to the get functions with valid data, ;
;;; use startPt and endPt to determine the position, length, ;
;;; and angle at which the path is drawn. ;
;;;--------------------------------------------------------------;
;;; Эта функция возвращает список, который состоит из следующих элементов: ;
;;; (10 . Starting Point) ;; List of 3 reals (a point) denoting ;
;;; ;; starting point of garden path. ;
;;; (11 . Ending Point) ;; List of 3 reals (a point) denoting ;
;;; ;; ending point of garden path. ;
;;; (40 . Width) ;; Real number denoting boundary ;
;;; ;; ширина. ;
;;; (41 . Length) ;; Real number denoting boundary ;
;;; ;; длина. ;
;;; (50 . Path Angle) ;; Real number denoting the angle ;
;;; ;; of the path, in radians. ;
;;;--------------------------------------------------------------;
(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
(if (setq StartPt (getpoint "\nStart point of path: "))
(if (setq EndPt (getpoint StartPt "\nEndpoint of path: "))
(if (setq HalfWidth (getdist EndPt "\nhalf-width of path: "))
;; if you' ve made it this far, build the association list
;; as documented above. This will be the return value
;; from the function.
(list
(cons 10 StartPt)
(cons 11 EndPt)
(cons 40 (* HalfWidth 2.0))
(cons 50 (angle StartPt EndPt))
(cons 41 (distance StartPt EndPt))
) ) ) ) )
Теперь необходимо обновить основную функцию C:GPath в файле gpmain.lsp. Она должна выглядеть следующим образом:
(defun C:GPath (/ gp_PathData)
;; Запрос на ввод: путь к местоположению и
;; direction, then for path parameters. Continue only if you
;; have valid input. Store the data in gp_PathData.
(if (setq gp_PathData (gp:getPointInput))
(if (gp:getDialogInput)
(progn
;; At this point, you have valid input from the user.
;; Draw the outline, storing the resulting polyline
;; pointer in the variable called PolylineName.
(setq PolylineName (gp:drawOutline))
(princ "\nThe gp:drawOutline function returned <")
(princ PolylineName)
(princ ">")
(Alert "Congratulations - your program is complete!")
) ;_ end of progn
(princ "\nFunction cancelled.")
) ;_ end of if
(princ "\nIncomplete information to draw a boundary.")
) ;_ end of if
(princ) ; exit quietly
) ;_ end of defun
При копировании и вставке кода перед описанием функции C:GPath: следует добавить следующий заголовок:
;;;**************************************************************;
;;; Функция: C:GPath - основная функция для построения парковой дорожки ;
;;;--------------------------------------------------------------;
;;; Описание: Это основная функция для построения парковой дорожки. It is a ;
;;; C: функция означает, что она преобразуется в ;
;;; команда AutoCAD именуемая GPATH. This function ;
;;; determines the overall flow of the garden path ;
;;; program. ;
;;;**************************************************************;
;;; В переменной gp_PathData содержится ассоциативный список форм: ;
;;; (10 . Starting Point) - List of 3 reals (a point) denoting ;
;;; starting point of the garden path. ;
;;; (11 . Ending Point) - List of 3 reals (a point) denoting ;
;;; endpoint of the garden path. ;
;;; (40 . Width) - Real number denoting boundary ;
;;; ширина. ;
;;; (41 . Length) - Real number denoting boundary ;
;;; длина. ;
;;; (50 . Path Angle) - Real number denoting the angle of ;
;;; the path, in radians. ;
;;; (42 . Tile Size) - Real number denoting the size ;
;;; (radius) of the garden path tiles. ;
;;; (43 . Tile Offset) - Spacing of tiles, border to border. ;
;;; ( 3 . Object Creation Style) ;
;;; - Object creation style indicates how ;
;;; the tiles are to be drawn. Элемент ;
;;; expected value is a string and one ;
;;; one of three values (string case ;
;;; не важен): ;
;;; "ActiveX" ;
;;; "Entmake" ;
;;; "Command" ;
;;; ( 4 . Polyline Border Style) ;
;;; - Polyline border style determines ;
;;; the polyline type to be used for ;
;;; path boundary. The expected value ;
;;; one of the following (string case is;
;;; не важна): ;
;;; "Pline" ;
;;; "Light" ;
;;;**************************************************************;
Для проверки переработанного кода
Сохраните обновленный файл.
Воспользуйтесь функцией «Проверить текст в редакторе» для проверки текста на наличие синтаксических ошибок.
Отформатируйте текст программы для повышения читаемости кода.
Загрузите код, чтобы переопределить предыдущие версии функций.
Для выполнения программы в ответ на подсказку в окне консоли введите (c:gpath).
Если при выполнении программы возникли ошибки, следует попытаться их исправить и запустить программу снова. Если успешного выполнения программы не удается добиться даже после нескольких попыток, можно скопировать правильный код из папки Tutorial\VisualLISP\Lesson2.
Перевод градусов в радианы
(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0)))
Функция называется Degrees->Radians и служит для перевода углов, выраженных в градусах, в радианы.
Для чего необходимо переводить угловые величины в радианы? На заднем плане AutoCAD® при работе с углами оперирует радианами, тогда как большинство людей привыкли измерять углы в градусах. Благодаря этой функции пользователь может вводить значения углов в градусах, а AutoLISP® сам преобразует их в радианы.
Для проверки работы служебной функции
В ответ на подсказку в окне консоли VLISP введите следующее:
(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0)))
В ответ на подсказку в окне консоли VLISP введите следующее:
(degrees->radians 180)
Функция возвращает число 3,14159. Таким образом, 180 градусов преобразуются в 3,14159 радиан.
Для того, чтобы использовать функцию в программе, следует просто скопировать ее из окна консоли в файл gpmain.lsp. Функцию можно вставить в любое место файла; но, естественно, не в код какой-либо уже имеющейся функции.
Для форматирования вставленного текста выделите текст, а затем нажмите кнопку "Форматирование выделенного фрагмента". VLISP выполнит форматирование кода с соответствующими отступами.
Теперь добавим комментарии, описывающие функцию. После документирования код функции должен выглядеть примерно так:
;;;--------------------------------------------------------------;
;;; Функция: Degrees->Radians;
;;;--------------------------------------------------------------;
;;; Описание: Данная функция преобразует значение, представляющее точку с запятой (;)
;;; angular measurement in degrees, into its radian ;
;;; equivalent. There is no error checking on the ;
;;; numberOfDegrees parameter -- it is always ;
;;; expected to be a valid number. ;
;;;--------------------------------------------------------------;
(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0))
)
Подробное изучение поведения реакторов
Нарисуйте десять парковых дорожек, затем проверьте следующие сочетания «команда/объект», последовательно выбирая полилинии:
Стереть/Полилиния границы (дорожка 1)
Стереть/Круг внутри полилинии (дорожка 2)
Стереть/Две полилинии (дорожки 3 и 4)
Перенести/Полилиния границы (дорожка 5)
Перенести/Круг внутри полилинии (дорожка 6)
Перенести/Две полилинии и несколько кругов (дорожки 7 и 8)
Переместить вершину (с помощью ручек)/Полилиния границы (дорожка 9)
Растянуть/Полилиния границы (дорожка 10)
Это упражнение позволит лучше понять, что происходит при работе приложения. А наличие записей поможет разобраться с поведением реакторов на занятии 7.
Получение справочной информации о функции
Для получения справки о функции
Введите на новой строке следующее:
(vla-add
Нажмите CTRL + SHIFT + ПРОБЕЛ.
Найдите в списке vla-addLightweightPolyline.
Дважды щелкните мышью на vla-addLightweightPolyline.
VLISP выводит для выбранной функции диалоговое окно «Инспектор символов».
В диалоговом окне «Инспектор символов» нажмите кнопку «Справка». (Для функций ActiveX® открывается электронный документ Справка по ActiveX и VBA).
Удалите изменения, внесенные в файл gpdraw.lsp. Они были нужны только в качестве примера. Также закройте диалоговые окна «Инспектор символов» и «Справочный список».
Получение указателя на пространство модели
(vla-get-ModelSpace (vla-get-ActiveDocument
(vlax?get?Acad?Object)))
Функция vlax-get-Acad-Object работает внутри программы и возвращает указатель на AutoCAD. Указатель передается в функцию vla?get?ActiveDocument, которая затем возвращает его на текущий активный рисунок (документ) AutoCAD. Этот указатель служит аргументом функции vla-get-ModelSpace, результатом которой является указатель на пространство модели текущего рисунка.
Естественно, каждый раз набирать это выражение не очень удобно. Например, посмотрим, насколько усложняется код для построения полилинии с помощью ActiveX, если в него включить целиком выражение для указателя на пространство модели:
(setq pline (vla-addLightweightPolyline
(vla-get-ModelSpace
(vla-get-ActiveDocument
(vlax-get-Acad-Object)
)
)
VLADataPts)
)
(vla-put-closed pline T)
Функция становится менее понятной. И дело не только в этом. Теперь при создании каждого объекта необходимо будет повторять одну и ту же последовательность вложенных функций. Это — один из тех немногих случаев, когда уместно использовать глобальные переменные. Приложение может строить множество объектов в пространстве модели (вспомним о плитках, которыми должна быть вымощена дорожка), поэтому объявим глобальную переменную для сохранения указателя на пространство модели:
(setq *ModelSpace* (vla-get-ModelSpace (vla-get-ActiveDocument
(vlax-get-Acad-Object))))
Переменную *ModelSpace* можно использовать в вызове любой ActiveX-функции, предназначенной для создания объектов. Единственное, на что следует обратить внимание это то, что переменная *ModelSpace* должна быть определена до начала создания графических объектов. Поэтому функция setq, присваивающая значение данной переменной, должна выполняться в процессе загрузки, сразу же после функции vl-load-com. Вызовы этих функций следует поместить в тексте программы до самого первого объявления defun. В результате они будут выполняться на самом раннем этапе, в процессе загрузки файла.
Постановка задачи
Для построения дорожки программа на AutoLISP должна:
Нарисовать прямоугольную границу по начальной точке, конечной точке и ширине. Граница может быть расположена на плоскости произвольным образом и обладать произвольными размерами.
Запросить у пользователя размер плитки и расстояние между плитками. Плитки представляют собой круги; они должны заполнять дорожку, не перекрываясь и не пересекая границу.
Расположить плитки на дорожке в шахматном порядке.
Чтобы посмотреть, как программа должна работать, можно запустить готовую версию приложения, поставляемую с AutoCAD.
Для запуска имеющегося примера
В меню "Сервис" AutoCAD выберите "Приложения".
Выберите файл gardenpath.vlx из папки Tutorial\VisualLISP и нажмите кнопку «Загрузить».
Нажать кнопку «Закрыть».
В командной строке введите gpath.
В ответ на первые два запроса укажите начальную и конечную точки в области рисования AutoCAD.
В ответ на запрос «Half Width of Path» (половина ширины дорожки) введите 50 (если работа ведется в британской системе единиц, следует ввести число 2).
В диалоговом окне «Garden Path Tile Specifications» нажмите «OK».
Постановка задачи и начало программирования
Постановка задачи и начало программирования
На первом занятии определяется, какие задачи должно выполнять приложение. С помощью среды разработки Visual LISP® (VLISP) создается LISP-файл и начинается разработка кода программы для поддержки приложения на языке AutoLISP®. В данном разделе будут описаны средства VLISP, облегчающие разработку приложений.
Темы:
Постановка задачи
Начало работы в Visual LISP
Знакомство с форматированием кода в Visual LISP
Анализ кода
Создание фиктивных функций в программе
Проверка кода с помощью Visual LISP
Выполнение программы в Visual LISP
Итоги занятия 1
Построение границы дорожки
Построение границы дорожки
В этом занятии возможности программы будут расширены, чтобы создать рисунок в AutoCAD, а именно границу парковой дорожки в виде полилинии. Для построения границы необходимо предварительно написать несколько служебных функций, которые в дальнейшем могут быть применены и в других приложениях. Здесь также рассмотрено создание функций, ожидающих передачи данных извне, а также объяснено, почему использование аргументов является важным принципом программирования. К концу занятия можно будет создать объект в AutoCAD параметрически (т.е. динамически на основе уникальных параметров, заданных пользователем).
Темы:
Использование служебных функций
Создание объектов в AutoCAD
Создание функции построения границы дорожки
Итоги занятия 3
Построение плиток в ряду
Установить переменные StartPoint, angp90, angm90 и т.п.
Установить переменную FirstCenterPoint равной StartPoint + сдвиг
(который может быть равным 0.0).
Установить начальное значение TileCenterPt равным FirstCenterPoint.
(Пояснение: Начните с построения кругов в направлении angp90)
Пока расстояние от StartPoint до TileCenterPt меньше, чем HalfWidth:
Построить круг (добавив его в список кругов).
Установить TileCenterPt на расстоянии шага плитки
в направлении angm90.
Конец цикла
Установить TileCenterPoint равным FirstCenterPoint + шаг плитки в направлении angm90.
Пока расстояние от StartPoint до TileCenterPt меньше, чем HalfWidth:
Построить круг (добавив его в список кругов).
Установить TileCenterPt на расстоянии шага плитки
в направлении angm90.
Конец цикла
Вернуть список кругов.
Построение рядов плиток
(defun gp:Calculate-and-Draw-Tiles (BoundaryData / PathLength
TileSpace TileRadius SpaceFilled SpaceToFill
RowSpacing offsetFromCenter
rowStartPoint pathWidth pathAngle
ObjectCreationStyle TileList)
(setq PathLength (cdr (assoc 41 BoundaryData))
TileSpace (cdr (assoc 43 BoundaryData))
TileRadius (cdr (assoc 42 BoundaryData))
SpaceToFill (- PathLength TileRadius)
RowSpacing (* (+ TileSpace (* TileRadius 2.0))
(sin (Degrees->Radians 60))
) ;_ end of *
SpaceFilled RowSpacing
offsetFromCenter 0.0
offsetDistance (/ (+ (* TileRadius 2.0) TileSpace) 2.0)
rowStartPoint (cdr (assoc 10 BoundaryData))
pathWidth (cdr (assoc 40 BoundaryData))
pathAngle (cdr (assoc 50 BoundaryData))
ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
) ;_ end of setq
;; Compensate for the first call to gp:calculate-Draw-tile Row
;; in the loop below.
(setq rowStartPoint
(polar rowStartPoint
(+ pathAngle pi)
(/ TileRadius 2.0)
) ;_ end of polar
) ;_ end of setq
;; Draw each row of tiles.
(while (<= SpaceFilled SpaceToFill)
;; Get the list of tiles created, adding them to our list.
(setq tileList (append tileList
(gp:calculate-Draw-TileRow
(setq rowStartPoint
(polar rowStartPoint
pathAngle
RowSpacing
) ;_ end of polar
) ;_ end of setq
TileRadius
TileSpace
pathWidth
pathAngle
offsetFromCenter
ObjectCreationStyle
) ;_ end of gp:calculate-Draw-TileRow
) ;_ end of append
;; Calculate the distance along the path for the next row.
SpaceFilled (+ SpaceFilled RowSpacing)
;; Alternate between a zero and a positive offset
;; (causes alternate rows to be indented).
offsetFromCenter
(if (= offsetFromCenter 0.0)
offsetDistance
0.0
) ;_ end of if
) ;_ end of setq
);_ end of while
;; Return the list of tiles created.
tileList
) ;_ end of defun
Некоторые участки кода требуют дополнительных пояснений.
Следующий фрагмент располагается непосредственно перед началом цикла while:
;; Compensate for the very first start point!!
(setq rowStartPoint(polar rowStartPoint
(+ pathAngle pi)(/ TileRadius 2.0)))
Здесь необходимо пояснить следующее:
Ей присваивается точка, выбранная пользователем в качестве начала дорожки.
Самый первый аргумент, передаваемый функции gp:calculate-Draw-TileRow, выглядит так:
(setq rowStartPoint(polar rowStartPoint pathAngle RowSpacing))
Иначе говоря, в момент вызова функции gp:calculate-Draw-TileRow точка rowStartPoint переносится на расстояние RowSpacing от текущего положения rowStartPoint.
Аргумент rowStartPoint используется в функции gp:calculate-Draw-TileRow в качестве начальной точки для центров кругов в ряде.
Для того чтобы компенсировать начальный сдвиг вперед rowStartPoint при рисовании первого ряда (т.е. при первом выполнении цикла while), необходимо слегка передвинуть rowStartPoint в обратном направлении. Благодаря этому удается избежать образования слишком большого пустого пространства между границей дорожки и первым рядом. Точку достаточно сдвинуть на расстояние, равное половине радиуса плитки (TileRadius). Этого можно достичь, используя функцию polar для перемещения точки rowStartPoint вдоль вектора, повернутого на 180 градусов относительно угла PathAngle. При этом точка временно оказывается за пределами границы дорожки.
Следующий фрагмент (слегка измененный для упрощения чтения) также может вызвать затруднения:
(setq tileList (append tileList
(gp:calculate-Draw-TileRow
(setq rowStartPoint
(polar rowStartPoint pathAngle RowSpacing)
) ;_ end of setq
TileRadius TileSpace pathWidth pathAngle
offsetFromCenter ObjectCreationStyle
)))
По сути, он представляет собой вызов функции gp:calculate-Draw-TileRow, вложенный в функцию append, которая, в свою очередь, вложена в функцию setq.
Функция gp:calculate-Draw- TileRow возвращает идентификаторы объектов для построенных плиток. Такими идентификаторами обозначается каждая плитка на рисунке. Плитки строятся ряд за рядом, поэтому функция возвращает идентификаторы объектов сразу для целого ряда. Функция append добавляет их в список tileList.
Ближе к концу функции можно обнаружить следующий фрагмент кода:
(setq offsetFromCenter
(if (= offsetFromCenter 0.0)
offsetDistance
0.0
)
)
Этот фрагмент отвечает за рисование рядов плиток в шахматном порядке. Здесь происходит либо совмещение центра средней плитки с центральной линией дорожки, либо сдвиг плиток относительно этой линии. Алгоритм выглядит следующим образом:
Установить сдвиг равным следующему:
Если текущий сдвиг равен 0, установить его равным расстоянию сдвига;
Иначе, установить сдвиг равным 0.
Предварительный просмотр диалогового окна
Для предварительного просмотра диалогового окна, описанного с помощью DCL
Выберите «Сервис»
«Инструменты интерфейса» «Просмотр DCL из редактора» меню VLISP.Нажмите «OK» в ответ на запрос ввести имя диалогового окна.
В данном случае в DCL-файле описывается только одно диалоговое окно. Однако, для более сложных приложений DCL-файлы могут содержать описания нескольких диалоговых окон. В этом случае пришлось бы выбирать, какое из них нужно просмотреть.
Если диалоговое окно выглядит правильно, нажмите любую кнопку для выхода из него.
Для вывода диалогового окна VLISP передает управление AutoCAD. При нахождении ошибок AutoCAD выдает одно или несколько соответствующих сообщений.
Если программой AutoCAD были обнаружены ошибки в DCL и их не удается исправить, следует скопировать файл gpdialog.dcl из папки Tutorial\VisualLISP\Lesson4 в папку Support.
Преобразование 3М точек в 2М
Для преобразования 3М точек в 2М
В ответ на подсказку в окне консоли введите следующее:
(defun 3dPoint->2dPoint (3dpt)(list (car 3dpt) (cadr 3dpt))) Выполним функцию.
Для этого в ответ на подсказку в окне консоли введите:
(3dpoint->2dpoint (list 10 20 0))
Функция работает; однако, для создания приложения, строящего парковую дорожку, следует учитывать еще одно обстоятельство. Для функций LISP чаще всего безразлично, является ли аргумент целым или вещественным числом. Но совсем по другому обстоит дело с функциями ActiveX, которые будут использованы позднее на этом занятии. Функции ActiveX работают с вещественными числами. Нашу функцию можно легко модифицировать так, чтобы она всегда возвращала вещественные числа, а не целые.
В ответ на подсказку в окне консоли введите следующее:
(defun 3dPoint->2dPoint (3dpt)(list (float(car 3dpt))
(float(cadr 3dpt))))
Снова запустите функцию:
(3dpoint->2dpoint (list 10 20 0))
Следует обратить внимание на то, что возвращаемые значения теперь являются вещественными числами (т.е. записываются с десятичной точкой).
Снова проверим работу функции, на этот раз с помощью функции getpoint. В ответ на подсказку в окне консоли введите следующее:
(setq myPoint(getpoint))
Укажите точку в области рисования AutoCAD.
Функция getpoint возвращает 3М точку.
В ответ на подсказку в окне консоли введите следующее:
(3dPoint->2Dpoint myPoint)
На этот раз возвращается 2М точка.
Теперь добавим функцию в файл gpmain.lsp (точно так же, как это было сделано с функцией Degrees->Radians).
Новый код должен выглядеть следующим образом:
;;;--------------------------------------------------------------;
;;; Функция: 3dPoint->2dPoint ;
;;;--------------------------------------------------------------;
;;; Описание: Эта функция обрабатывает один параметр, представляющий точку с запятой (;)
;;; 3D point (list of three integers or reals), and ;
;;; converts it into a 2D point (list of two reals).;
;;; There is no error checking on the 3D point ;
;;; parameter -- it is assumed to be a valid point. ;
;;;--------------------------------------------------------------;
;;; Действие: Добавьте функцию проверки параметров, чтобы точка с запятой (;)
;;; function won’t crash a program if it is passed a ;
;;; null value, or some other kind of data type than a ;
;;; 3D point. ;
;;;--------------------------------------------------------------;
(defun 3dPoint->2dPoint (3dpt)
(list (float(car 3dpt)) (float(cadr 3dpt)))
)
Обратите внимание, что заголовок содержит указания о дальнейшей доработке данной функции. Для того чтобы приобрести дополнительный опыт, можно подумать о том как усовершенствовать функцию, чтобы защитить функцию от аварийного завершения из-за неверного ввода данных.
Совет: используйте функции numberp и listp…
(listp '(1 1 0)) => T
(numberp 3.4) => T
Преобразование списка точек в массив типа variant
(defun gp:list->variantArray (ptsList / arraySpace sArray)
; allocate space for an array of 2d points stored as doubles
(setq arraySpace (vlax-make-safearray
vlax-vbdouble ; element type
(cons 0
(- (length ptsList) 1)
) ; array dimension
)
)
(setq sArray (vlax-safearray-fill arraySpace ptsList))
; return array variant
(vlax-make-variant sArray)
)
Внутри функции gp:list->variantArray выполняются следующие действия:
Функция vlax-make-safearray вызывается для выделения памяти под массив чисел типа double (vlax-vbdouble). Функция vlax?make?safearray также требует задания минимального и максимального индекса элементов массива. В gp:list->variantArray в запросе к vlax?make?safearray начальный индекс принимается равным 0, а максимальный индекс устанавливается на единицу меньше числа передаваемых в него элементов (ptsList).
Функция vlax-safearray-fill заполняет массив элементами списка точек.
Функция vlax-make-variant преобразует массив в тип данных variant. Так как это последняя функция в gp:list->variantArray, возвращаемое ей значение передается наверх в вызывающую процедуру.
Ниже приведен пример вызова функции, которая использует gp:list->variantArray для преобразования списка в массив типа variant:
; data conversion from list to variant
(setq VLADataPts (gp:list->variantArray polypoints))
Применение ассоциативных списков
10 обозначает 3М координаты начальной точки дорожки.
11 обозначает 3М координаты конечной точки дорожки.
40 обозначает ширину (но не половину ширины) дорожки.
41 обозначает длину дорожки от начала до конца.
50 обозначает угол поворота дорожки.
Ниже приведен обновленный вариант функции gp:getPointInput. Здесь используется функция AutoLISP cons, которая создает кодированные вложенные списки, объединяемые затем в общий ассоциативный список. Теперь скопируйте этот вариант функции в окно консоли, нажмите ENTER и запустите функцию (gp:getPointInput) снова.
(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
(if (setq StartPt (getpoint "\nStart point of path: "))
(if (setq EndPt (getpoint StartPt "\nEndpoint of path: "))
(if (setq HalfWidth (getdist EndPt "\nhalf-width of path: "))
;; if you've made it this far, build the association list
;; as documented above. This will be the return value
;; from the function.
(list
(cons 10 StartPt)
(cons 11 EndPt)
(cons 40 (* HalfWidth 2.0))
(cons 50 (angle StartPt EndPt))
(cons 41 (distance StartPt EndPt))
)
)
)
)
)
Следует обратить внимание на то, что при построении списка программа умножает половину ширины, указанную пользователем, на 2, преобразуя ее таким образом в полную ширину.
Полученный результат в окне консоли выглядит примерно так:
_$ (gp:getPointInput)
((10 2.16098 1.60116 0.0) (11 12.7126 7.11963 0.0) (40 . 0.592604) (50 . 0.481876) (41 . 11.9076))
_$
Применение локальных переменных в программе
(defun gp:getPointInput()
(alert
"Function gp:getPointInput will get user drawing input"
)
;; For now, return T, as if the function worked correctly.
T
)
В том виде, как она есть на данный момент, функция не выполняет практически никаких действий. Нам нужно расширить ее, добавив функции ввода пользовательских данных (начальной и конечной точек, а также ширины дорожки).
При создании программ на AutoLISP желательно мысленно воссоздавать работу AutoCAD. Исходя из этих соображений лучше написать программу так, чтобы она запрашивала не ширину дорожки, а половину ширины, так как в последнем случае появляется возможность задать ее указанием точки на рисунке относительно осевой линии.
После завершения функции gp:getPointInput ее переменные, как и присвоенные им значения, становятся не нужны. Поэтому будем хранить значения, введенные пользователем, в локальных переменных. Функция должна выглядеть следующим образом:
(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
(if (setq StartPt (getpoint "\nStart point of path: "))
(if (setq EndPt (getpoint StartPt "\nEndpoint of path: "))
(if (setq HalfWidth (getdist EndPt "\nhalf-width of path: "))
T
)
)
)
)
Локальные переменные объявляются после косой черты в выражении defun, с которого начинается описание функции. Сначала, при первом вызове getpoint, запрашивается начальная точка. Затем относительно нее задается конечная точка. При этом из начальной точки исходит динамическая, растягивающаяся при перемещении курсора линия (резиновая нить). Аналогично этому, при задании половины ширины резиновая нить исходит из конечной точки.
Для проверки работы gp:getPointInput
Введите в окне консоли VLISP код функции gp:getPointInput.
Поместив курсор в окне консоли за последней скобкой блока кода (или на следующей строке), нажмите ENTER для замены предыдущей загруженной версии функции gp:getPointInput.
Вызовите функцию из окна консоли. Для этого в ответ на подсказку введите (gp:getPointInput).
Укажите точки, запрашиваемые программой, и задайте половину ширины.
Продолжение знакомства со средствами редактирования Visual LISP
Темы:
Сопоставление скобок
Автоматическое дописывание слов
Дописывание словом из списка
Получение справочной информации о функции
Проекты Visual LISP
Для создания проекта VLISP
Выберите «Проект»
«Новый проект» из меню VLISP.Сохраните файл в рабочей папке Lesson4 под именем gpath.prj.
После этого VLISP открывает диалоговое окно «Свойства проекта».
Нажмите кнопку «Выбрать/Очистить все» в левой части диалогового окна «Свойства проекта».
Нажмите кнопку со стрелкой, указывающей вправо. Все выделенные файлы добавляются в проект.
В окне «Свойства проекта» в списке слева выводятся имена всех LISP-файлов, расположенных в той же папке, что и файл проекта, но не включенных в этот проект. В списке справа перечисляются имена файлов, входящих в проект. При добавлении к проекту имена выбранных файлов перемещаются из левого списка в правый.
В списке, расположенном в правой части диалогового окна, выделите gpmain и нажмите кнопку «Конец». При этом файл перемещается в конец списка.
VLISP загружает файлы проекта в порядке, в котором они перечислены. Так как подсказка, информирующая пользователей об имени команды построения дорожки, расположена в файле gpmain.lsp, этот файл необходимо переместить в конец списка. Если этот файл будет загружен последним, пользователь увидит подсказку на экране. Файл utils.lsp должен загружаться первым, так как он содержит код инициализации для приложения. Поэтому выделите utils в списке и нажмите кнопку «Начало».
Нажать «OK».
В главном окне VLISP появляется маленькое окно управления проектом. В этом окне перечисляются файлы, входящие в проект. Для того, чтобы открыть файл в текстовом редакторе (если он еще не открыт) и сделать его текущим в проекте, нужно дважды щелкнуть мышью на его имени.
Проверка кода с помощью Visual LISP
Для синтаксической проверки кода
Убедитесь, что окно текстового редактора, содержащее gpmain.lsp, является активным. Для активизации окна следует щелкнуть на его заголовке.
В меню среды VLISP выберите "Сервис"
"Проверить текст в редакторе".Результаты синтаксической проверки выводятся в окне «Сообщения сборки». Если ошибок не обнаружено, окно содержит приблизительно следующее сообщение:
[CHECKING TEXT GPMAIN.LSP загружается...]
......
; Проверка завершена.
При возникновении затруднений см. главу "Developing Programs with Visual LISP" ("Разработка программ с помощью редактора Visual LISP") электронного документа AutoLISP Developer’s Guide (Руководство разработчика AutoLISP). Сначала следует попытаться установить причину ошибки самостоятельно. Если на поиск ошибки уходит слишком много времени, для продолжения работы над учебным пособием можно воспользоваться файлом примера gpmain.lsp, находящимся в папке lesson1.
Для использования файла примера gpmain.lsp (при необходимости)
Закройте окно текстового редактора, содержащее файл gpmain.lsp с неработающим кодом.
Из меню VLISP выберите «Файл»
«Открыть файл» и откройте файл gpmain.lsp, расположенный в папке \Tutorial\VisualLISP\lesson1.Выберите «Файл»
«Сохранить как» и сохраните файл в рабочей папке \Tutorial\VisualLISP\MyPath под именем gpmain.lsp, заменив им неверную копию.
Проверка работы кода программы
Для проверки работы кода
Закройте все активные окна в VLISP, включая окна проекта.
Скопируйте полностью содержимое папки Tutorial\VisualLISP\Lesson5 в папку MyPath.
Откройте файл проекта gpath5.prj с помощью меню VLISP «Проект»
«Открыть проект».Загрузите исходные файлы проекта.
Активизируйте (переключитесь в) окно AutoCAD® и введите в командной строке gpath для запуска программы.
Постройте с помощью gpath три дорожки, каждый раз выбирая разные методы создания объектов. Вы заметили разницу в скорости построения дорожки в зависимости от выбранного метода?
Проверка работы реакторов
Для проверки работы кода реакторов
Загрузите все исходные файлы проекта. Для этого нажмите кнопку "Загрузка исходных файлов" в окне проекта.
Вызовите и выполните функцию C:GPath.
Программа строит парковую дорожку так же, как на занятии 5. На первый взгляд ничего необычного не происходит.
После того, как дорожка нарисована, попробуйте выполнить следующие действия:
Переместите вершину полилинии. Для этого выберите полилинию и перетащите одну из вершин за ручку.
Растяните полилинию.
Передвиньте полилинию.
Сотрите полилинию.
Посмотрите на появляющиеся сообщения. Они разъясняют, что происходит при выполнении операции внутри приложения.
Если приложение работает неверно и при этом нет времени на отладку кода, можно воспользоваться кодом, имеющимся в папке Tutorial\VisualLISP\Lesson6. В этой папке следует выбрать проект Gpath6.
Примечание После проверки работы реакторов может случиться так, что переключение из AutoCAD в VLISP перестанет осуществляться с помощью сочетания клавиш ALT + TAB или щелчка мыши в окне VLISP. Если это произойдет, то для возврата в VLISP следует просто ввести в командной строке AutoCAD vlisp.
Темы:
Подробное изучение поведения реакторов
Работа с ассоциативным списком
(gp:drawOutline gp_PathData)
Это достаточно просто. Однако в тексте функции необходимо указать, каким образом информация из ассоциативного списка должна обрабатываться. Выяснить структуру списка можно с помощью средства VLISP «Изучить».
Для анализа ассоциативного списка с помощью средства VLISP «Изучить»
Загрузите код из окна редактора.
В ответ на подсказку в окне консоли введите следующее выражение:
(setq BoundaryData (gp:getPointInput))
VLISP будет записывать информацию в переменную BoundaryData.
Ответьте на запросы о начальной и конечной точках и полуширине.
Выберите имя переменной BoundaryData в окне консоли, дважды щелкнув на нем мышью.
Выберите "Вид"
"Изучить" из меню VLISP.VLISP выводит следующее окно:
В окне «Изучение» выводится каждый вложенный список переменной BoundaryData.
В ответ на подсказку в окне консоли VLISP введите следующее:
(assoc 50 BoundaryData)
Функция assoc возвращает элемент ассоциативного списка, обозначенный определенным кодом. В данном примере указан код 50, соответствующий углу наклона парковой дорожки (см. раздел Применение ассоциативных списков).
В ответ на подсказку в окне консоли VLISP введите следующее:
(cdr(assoc 50 BoundaryData))
Функция cdr возвращает второй и все следующие за ним элементы списка. В данном примере cdr находит значение угла, которое является вторым и последним элементом значения, возвращаемого функцией assoc.
После этих объяснений при понимании следующего фрагмента кода затруднения возникать не должны:
(setq PathAngle (cdr (assoc 50 BoundaryData))
Width (cdr (assoc 40 BoundaryData))
HalfWidth (/ Width 2.00)
StartPt (cdr (assoc 10 BoundaryData))
PathLength (cdr (assoc 41 BoundaryData))
Работа в среде Visual LISP
В данном пособии рассматриваются:
новые возможности Визуальная среда разработки Visual LISP® (VLISP). Она позволяет редактировать и отлаживать код, а также предоставляет пользователю ряд других специальных средств разработки приложений AutoLISP.
описание Функции ActiveX® и реакторов, а также некоторых других расширений языка AutoLISP, имеющихся в VLISP
Имеются два возможных способа выполнения упражнений данного учебного пособия:
Приложение можно выполнить по частям путем интерпретации LISP-файлов, загружаемых в один документ.
Можно скомпилировать программный код в VLX-приложение (исполняемый файл с расширением .vlx). VLX обладает независимым пространством имен и может взаимодействовать с документом, из которого загружается приложение.
Расширение возможностей реакторов
Для расширения возможностей функции отклика gp:command-will-start
Откройте файл gpreact.lsp.
Измените функцию gp:command-will-start, добавив к вызову функции setq две переменные, следующим образом:
;; Reset all four reactor globals to nil.
(setq *lostAssociativity* nil
*polyToChange* nil
*reactorsToChange* nil
*reactorsToRemove* nil)
Замените оставшийся код функции gp:command-will-start, до последнего вызова функции princ, следующим кодом:
(if (member (setq currentCommandName (car command-list))
'( "U" "UNDO" "STRETCH" "MOVE"
"ROTATE" "SCALE" "BREAK" "GRIP_MOVE"
"GRIP_ROTATE" "GRIP_SCALE" "GRIP_MIRROR")
) ;_ end of member
(progn
(setq *lostAssociativity* T)
(princ "\nПРИМЕЧАНИЕ: ")
(princ currentCommandName)
(princ " command will break a path's associativity .")
) ;_ end of progn
) ;_ end of if
Здесь производится проверка, не запустил ли пользователь команду, разрушающую ассоциативность между плитками и границей дорожки. Если это произошло, программа устанавливает глобальную переменную *lostAssociativity* и выдает пользователю предупреждение.
Поэкспериментировав с приложением построения парковой дорожки, можно выявить дополнительные команды редактирования, способные внести изменения в нарисованную дорожку и привести к потере ассоциативности. Эти команды также следует занести в список, чтобы предупредить пользователя о последствиях их применения. Функция вызывается, когда пользователь запустил команду, но еще не выбрал объекты для изменения. В этот момент пользователь еще может прервать выполнение команды.
Темы:
Доработка функций отклика для объектных реакторов
Разработка функции отклика gp:command-ended
Обработка нескольких типов объектов
Использование методов ActiveX в функциях отклика реакторов
Обработка нелинейных последовательностей реакторов
Написание функции command-ended
Обновление функции gp:Calculate-and-Draw-Tiles
Изменение других вызовов функции gp:Calculate-and-Draw-Tiles
Разбиение программы на модули
В учебном пособии файлы модулей будут организованы следующим образом:
Организация файлов учебного пособия
Имя файла
Содержимое
Для разбиения файла gpmain.lsp на отдельные файлы
Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующие функции:
gp:getPointInput
gp:getDialogInput
Сохраните новый файл в рабочей папке под именем gp-io.lsp.
Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующие функции:
Degrees->Radians
3Dpoint->2Dpoint
gp:list->variantArray
В начале этого файла вставьте строки кода, загружающего ActiveX (vl-load-com) и присваивающего значение глобальной переменной *ModelSpace*.
Сохраните файл под именем utils.lsp.
Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующую функцию:
gp:drawOutline
Сохраните файл под именем gpdraw.lsp.
После вырезания кода сохраните и проверьте файл gpmain.lsp. В нем должна остаться только основная функция C:GPath.
Главное окно VLISP начинает переполняться. Каждое из окон внутри VLISP может быть свернуто, оставаясь, тем не менее, доступным. Для того чтобы развернуть окно, можно либо нажать кнопку "Выбрать окно" на панели инструментов и выбрать нужное окно из списка, либо в меню VLISP нажать "Окно" и выбрать нужное окно.
Различия между локальными и глобальными переменными
Локальные переменные хранят свои значения, только пока выполняется функция, в которой они были определены. После завершения выполнения функции память автоматически освобождается от значений локальных переменных. Это называется автоматической очисткой памяти от мусора и является общим свойством большинства сред разработки на языке LISP, в том числе и VLISP. Локальные переменные используют память более эффективно, чем глобальные.
Другим большим преимуществом локальных переменных является то, что они упрощают отладку и усовершенствование приложений. Так, например, никогда нельзя быть точно уверенным, когда и какая именно функция изменила значение глобальной переменной. Проследить же изменение локальной переменной значительно проще. Таким образом, локальные переменные позволяют сократить количество побочных эффектов (выражающихся в том, что одна часть программы влияет на ее другую часть).
Из-за перечисленных выше преимуществ мы практически везде в данном пособии будем использовать локальные переменные.
Примечание Возможно, что пользователи, уже работавшие с AutoLISP, привыкли использовать глобальные переменные для проверки работы программы в ходе ее создания. Теперь это уже не нужно, так как в VLISP имеются другие мощные средства отладки.
Темы:
Применение локальных переменных в программе
Изучение функции gp:getPointInput
Разработка функции отклика gp:command-ended
Действие функции gp:command-ended словесно описывается следующим алгоритмом:
Определить состояние полилинии.
УСЛОВИЕ 1 - ПОЛИЛИНИИЯ СТЕРТА (Командой СТЕРЕТЬ)
Удалить плитки.
УСЛОВИЕ 2 - ПОТЕРЯНА АССОЦИАТИВНОСТЬ (ПЕРЕНЕСТИ, ПОВЕРНУТЬ и т.д.)
Удалить плитки.
УСЛОВИЕ 3 - РАСТЯГИВАНИЕ РУЧКАМИ - ПЕРЕРИСОВКА КОНТУРА И ПЛИТОК
Удалить плитки.
Извлечь текущие данные о границе полилинии.
Если это компактная полилиния,
обрабатывать данные о границе как 2М
Иначе
обрабатывать данные о границе как 3М
Конец Если
Переопределить границу (передать ей параметры текущего
и старого состояния границы).
Получить новую информацию о границе и записать ее в формате,
используемом для задания полилинии.
Регенерировать полилинию.
Перерисовать плитки (с помощью ActiveX).
Поместить переработанную информацию обратно в реактор
с именем *reactorsToChange*.
Конец функции
Алгоритм достаточно понятен, однако в нем скрыто несколько важных деталей, на которых следует заострить внимание.
Разработка реакторов для парковой дорожки
Темы:
Выбор событий для назначения реакторов парковой дорожке
Функции отклика
Использование нескольких реакторов
Назначение реакторов
Хранение данных с реактором
Обновление функции C:GPath
Добавление функций отклика для реакторов
Устранение последствий работы реакторов
Реакция на вызываемые пользователем команды
Поведение программы при вызове пользователем команд ОТМЕНИТЬ и ПОВТОРИТЬ.
Поведение программы при вызове пользователем команды ОЙ после удаления объектов, связанных с реакторами.
Чтобы не превращать сложное в слишком сложное, в данном учебном пособии не делается попыток предусмотреть все возможные случаи. Поэтому на этом занятии рассматривается абсолютный минимум возможностей.
Для команд редактирования, которые не будут учтены на занятии, приведем только некоторые общие соображения:
При растягивании полилинии границы дорожки (с помощью команды РАСТЯНУТЬ) может произойти несколько вещей. Полилиния может быть растянута в любом направлении, а не только вдоль большой и маленькой сторон; в итоге она может приобрести весьма произвольную форму. Кроме того, необходимо учесть, сколько вершин было перемещено. Так, результат растягивания при перемещении одной вершины будет отличаться от того, что происходит, если переместить сразу две вершины. В любом случае необходимо стереть старые плитки, а затем, определив изменения границы, пересчитать новые положения плиток.
Если пользователь перемещает полилинию границы, необходимо стереть все имеющиеся плитки, а затем построить их в новом месте. Это достаточно простая операция, так как полилиния не изменила ни размера, ни формы.
Если пользователь масштабирует полилинию границы, необходимо решить, масштабировать ли вместе с ней плитки, чтобы не изменилось их количество, или, оставив прежний размер, изменить количество плиток.
Сборка приложения
Примечание Рекомендуется использовать для сборки приложения только отлаженный код. Перед этим следует убедиться, что работа исходных файлов приложения была проверена и получены удовлетворительные результаты.
Темы:
Запуск Мастера сборки приложений
с выходом из функции gp:getPointInput и переход к C:Gpmain
Для выхода из функции gp:getPointInput и перехода к c:gpath
Нажмите кнопку "Шаг с выходом".
VLISP переходит к самому концу функции gp:getPointInput и останавливается непосредственно перед выходом.
Нажмите кнопку "Шаг с заходом".
Управление передается функции c:gpmain, из которой функция gp:getPointInput была вызвана перед этим.
Проверим значения переменных в окне контрольных значений. Переменные endpt и StartPt являются локальными для функции gp:getPointInput и получают значение nil. VLISP автоматически освобождает память, занимаемую этими переменными. Третья локальная переменная HalfWidth также должна была бы принять значение nil, однако в ходе отладки ее значение было глобально заменено из окна консоли, поэтому в окне «Контрольное значение» для этой переменной остается значение 2.0. Переменная *Last-Value* выводит ассоциативный список, созданный функцией gp:getPointInput.
Наш первый сеанс отладки завершен. При этом не следует забывать, что программа все еще находится в состоянии останова.
Для завершения занятия
Нажмите кнопку «Продолжить» на панели «Отладка». Ответьте на запросы. Это приводит к завершению работы программы.
Выберите «Отладка»
«Удалить все точки останова» из меню VLISP. Ответьте «да» на запрос подтверждения. Все точки останова удаляются из текста программы.Следует помнить, что для удаления отдельной точки останова следует поместить курсор в ее позицию в тексте программы и нажать кнопку "Точка останова Вкл/Откл".
Сохранение DCL-файла
Сохраните файл в папке Support программы AutoCAD.
Для сохранения DCL-файла
Выберите «Файл»
«Сохранить как» из меню VLISP.Из раскрывающегося списка «Тип файла» диалогового окна «Сохранить как» выберите «Исходные DCL-файлы».
Измените текущий путь на папка <AutoCAD>\Support.
Введите имя файла gpdialog.dcl.
Нажать «Сохранить».
Обратите внимание, как после сохранения файла VLISP выделяет его элементы цветом. VLISP распознает директивы языка DCL и старается таким образом облегчить работу с ними для пользователя.
Сохранение результата функции gp:getPointInput в переменной ї
(setq gp_PathData (gp:getPointInput))
Для просмотра содержимого только что описанной переменной введите в окне консоли:
_$ gp_PathData
VLISP возвращает данные в следующем виде:
((10 2.17742 1.15771 0.0) (11 13.2057 7.00466 0.0) (40 . 1.12747) (50 . 0.487498) (41 . 12.4824))
Сопоставление скобок
Для сопоставления открывающей и соответствующей ей закрывающей скобок
Поместите курсор перед открывающей скобкой функции setq.
Нажмите CTRL + SHIFT + ]. Можно также дважды щелкнуть мышью на нужной скобке.
VLISP находит закрывающую скобку, которая соответствует выбранной открывающей скобке, и выделяет код между ними. Это не только обеспечивает соответствие скобок, но и позволяет избежать ошибок при копировании выделенного текста. Это, например, могло пригодиться при модификации функции в конце занятия 4.
Для чего еще можно использовать сопоставление скобок? С его помощью можно скопировать фрагмент кода в окно консоли VLISP для проверки его работы. Или, пусть, например, требуется заменить 50 строк имеющегося кода на 3 строки более удачного кода. Старый код можно быстро выделить, используя функцию сопоставления скобок, а затем удалить одним нажатием клавиши. Пользователю значительно удобнее возложить на VLISP поиск блоков кода программы, нежели самому проверять каждую закрывающую скобку.
Для сопоставления скобок в обратном направлении также используется специальное сочетание клавиш. В этом случае, установив курсор после закрывающей скобки, необходимо дважды щелкнуть мышью или нажать CTRL+SHIFT+[. VLISP находит соответствующую открывающую скобку и выделяет все заключенное в скобки выражение.
Обе команды можно также вызвать, выбрав в меню VLISP "Правка"
"Сопоставление скобок".
Создание диалогового окна
До настоящего времени наша функция gpath воспринимала ввод данных только из командной строки. В месте, где должно вызываться диалоговое окно, была вставлена фиктивная функция (gp:getDialogInput). Теперь настало время создать это диалоговое окно.
Процесс создания диалогового окна состоит из двух этапов:
Разработка требований к внешнему виду диалогового окна и необходимому набору элементов управления.
Написание кода, управляющего поведением диалогового окна.
Описание и внешний вид диалогового окна задаются в файле с расширением .dcl. Язык управления диалоговыми окнами (DCL) описан в документе AutoLISP Developer’s Guide в главе 11, "Designing Dialog Boxes", 12, "Managing Dialog Boxes" и в главе 13 - "Programmable Dialog Box Reference").
Код программы, включающий функции инициализации рабочих параметров и реакции на действия пользователя, будет добавлен в функцию gp:getDialogInput.
Темы:
Описание диалогового окна с помощью DCL
Сохранение DCL-файла
Предварительный просмотр диалогового окна
Создание фиктивных функций в программе
gp:getPointInput
gp:getUserInput
gp:drawOutline
Теперь необходимо создать три описания фиктивных функций. Фиктивные функции пока будут заменять полные функции, к разработке которых мы еще вернемся. Их наличие позволяет проверять работу программы до того, как в нее будут добавлены все элементы, необходимые для выполнения приложения.
Для описания фиктивной функции в приложении
Поместите курсор в окне редактора в самое начало кода программы и нажмите пару раз ENTER для ввода пустых строк.
Введите перед главной функцией следующее:
;;; Function gp:getPointInput will get path location and size
(defun gp:getPointInput()
(alert
"Function gp:getPointInput will get user drawing input"
)
;; For now, return T, as if the function worked correctly.
T
)
;;; Function gp:getDialogInput will get path parameters
(defun gp:getDialogInput ()
(alert
"Function gp:getDialogInput will get user choices via a dialog"
)
;; For now, return T, as if the function worked correctly.
T
)
;;; Function gp:drawOutline will draw the path boundary
(defun gp:drawOutline ()
(alert
(strcat "This function will draw the outline of the polyline "
"\nand return a polyline entity name/pointer."
)
)
;; For now, simply return a quoted symbol. Eventually, this
;; function will return an entity name or pointer.
'SomeEname
)
Каждая функция содержит в конце строку, состоящую из одного символа T. Она используется в качестве возвращаемого значения. Каждая функция AutoLISP должна возвращать в вызвавшую ее функцию какое-либо значение. Буква T используется в AutoLISP для значения «true» (истина); именно оно будет передано в вызывающую функцию.
Файл gpmain. lsp организован таким образом, что для перехода к следующему этапу выполнения программы каждая вызываемая функция должна возвращать значение, отличное от nil (что обозначает «нет значения»).
По умолчанию функции AutoLISP всегда возвращают значение последнего вычисленного внутри нее выражения. В фиктивных функциях единственным выражением является обращение к функции alert. Однако функция alert всегда возвращает значение nil. Таким образом, будучи вызванной последней в gp:getPointInput, она возвратит nil и переход через if к функции gp:getDialogInput станет невозможным.
По аналогичной причине функция gp:DrawOutline возвращает в качестве фиктивного значение ('SomeEname). Конструкции LISP, предваренные апострофом, не вычисляются. Для более подробного изучения языка LISP в конце данного учебного пособия приведен список рекомендуемых книг.
Создание функции построения границы дорожки
;;;--------------------------------------------------------------;
;;; Функция: gp:drawOutline ;
;;;--------------------------------------------------------------;
;;; Описание: С помощью этой функции можно создать границу ;
;;; garden path. ;
;;;--------------------------------------------------------------;
(defun gp:drawOutline ()
(alert
(strcat "This function will draw the outline of the polyline "
"\nand return a polyline entity name/pointer."
)
)
;; For now, simply return a quoted symbol. Eventually, this
;; function will return an entity name or pointer.
'SomeEname
)
В том виде, как она есть, функция не выполняет практически никаких действий. Однако, ассоциативный список, хранящийся в переменной gp_PathData, позволяет рассчитать все характерные точки, необходимые для построения контура дорожки. Теперь определим, каким образом информация из этой переменной будет передаваться в функцию gp:drawOutline.
Следует помнить, что переменная gp_PathData является локальной и определена внутри функции C:GPath. В AutoLISP локальные переменные, описанные в одной функции, доступны всем функциям, вызываемым из нее (подробнее см. раздел Различия между локальными и глобальными переменными). Функция gp:drawOutline вызывается из C:GPath. Поэтому переменную gp-PathData можно использовать и для функции gp:drawOutline; однако это не является наилучшим решением.
Почему так? Если одна и та же переменная используется только двумя функциями, описанными в одном файле (как в приведенном примере), установить, где была описана и для чего используется переменная, нетрудно. Однако если функции определены в различных файлах (как часто случается), необходимо будет выполнить поиск в двух файлах, чтобы выяснить, что представляет собой переменная gp_PathData.
Темы:
Передача параметров функциям
Работа с ассоциативным списком
Использование углов и задание точек
Вызов функций ActiveX из функции gp:drawOutline
Загрузка среды ActiveX
Получение указателя на пространство модели
Создание массива вершин полилинии
Преобразование списка точек в массив типа variant
Окончательная компоновка
Создание массива вершин полилинии
Для получения информации о функции
Нажмите кнопку "Справка" на панели инструментов VLISP.
В диалоговом окне «Введите имя пункта» введите vla-addLightweightpolyline и нажмите «OK». Справочная система не учитывает регистр символов, поэтому можно не заботиться о расстановке заглавных букв.
В справочной системе говорится, что для AddLightWeightPolyline вершины полилинии должны быть представлены в виде массива типа variant (универсальный тип), хранящего числа типа double. В справочной системе по этому поводу говорится следующее:
Массив 2М координат (в МСК), задающий вершины полилинии. Для построения компактной полилинии нужно не меньше двух точек (т.е. четырех элементов). Размер массива должен быть кратным 2.
В ActiveX тип variant используется для хранения произвольных типов данных. Типом variant могут быть представлены строки, целые числа и массивы. Данные в типе variant хранятся вместе с идентифицирующей их информацией.
Итак, у нас имеется четыре точки, каждая в формате (x, y, z). Задача заключается в том, чтобы преобразовать их в список следующего вида:
(x1 y1 x2 y2 x3 y3 x4 y4)
Функция append объединяет несколько списков в один. Для создания списка из четырех точек в формате функции ActiveX используется следующее выражение:
(setq polypoints (append (3dPoint->2dPoint p1)
(3dPoint->2dPoint p2)
(3dPoint->2dPoint p3)
(3dPoint->2dPoint p4)))
Писать одну и ту же функцию 3dPoint->2dPoint четыре раза не очень удобно. Сократить выражение можно с помощью функций mapcar и apply. Функция mapcar выполняет операции над отдельными элементами одного или более списков, а функция apply передает список аргументов заданной функции. В итоге код должен выглядеть следующим образом:
(setq polypoints (apply 'append (mapcar '3dPoint->2dPoint
(list p1 p2 p3 p4))))
Таким образом, перед вызовом функции mapcar список точек примет вид:
((x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4))
После выполнения mapcar получается список точек следующего вида:
((x1 y1) (x2 y2) (x3 y3) (x4 y4))
И, наконец, после применения функции append список, полученный из mapcar, приобретает следующий вид:
(x1 y1 x2 y2 x3 y3 x4 y4)
Создание объектов с помощью entmake
Создание объектов с помощью функций ActiveX
Функции ActiveX работают быстрее.
Имена функций ActiveX обозначают действие, которые они выполняют, что обеспечивает более удобное чтение, обновление и исправление программы.
Пример функции ActiveX приводится ниже в тексте этого же занятия.
Создание объектов в AutoCAD
функции ActiveX
Функция entmake
Функция command
На этом занятии объясняется построение объектов с помощью ActiveX® . На 5 занятии будут использоваться функция entmake и альтернативы команды AutoCAD.
Темы:
Создание объектов с помощью функций ActiveX
Создание объектов с помощью entmake
Использование командной строки AutoCAD
Создание плиток
Создание плиток
К концу этого занятия программа должна отвечать основным требованиям, сформулированным на занятии 1. В нее будут добавлены функции для заполнения дорожки плитками. Пользователю будет предоставлена возможность выбора между тремя способами создания плиток. Кроме того, будут рассмотрены использование сочетания клавиш для ускорения ввода и новые инструменты редактирования.
Темы:
Продолжение знакомства со средствами редактирования Visual LISP
Создание плиток на парковой дорожке
Проверка работы кода программы
Итоги занятия 5
Создание плиток на парковой дорожке
Темы:
Логика построения
Геометрические величины
Построение рядов плиток
Построение плиток в ряду
Изучение кода
Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Целью урока является выполнение двух основных задач: создание проекта Visual LISP® и добавление в приложение диалогового интерфейса. Для закрепления понятия об отдельных модулях в процессе обучения будут освоены методы разбиения единого рабочего файла AutoLISP® (gpmain.lsp) на несколько мелких файлов.
Начиная с этого занятия, в ходе изложения материала последовательность уже знакомых пользователю операций описывается менее подробно. Для экономии места фрагменты кода сопровождаются минимумом комментариев.
Темы:
Разбиение программы на модули
Проекты Visual LISP
Создание диалогового окна
Взаимодействие программы на AutoLISP с диалоговым окном
Выбор типа линий для границы дорожки
Корректировка
Выполнение приложения
Итоги занятия 4
Средства отладки Visual LISP
Средства отладки Visual LISP
Это занятие посвящено технологии использования средств отладки Visual LISP®, позволяющих ускорить разработку программ на AutoLISP®. Кроме того, здесь объясняется разница между локальными и глобальными переменными и особенностями их применения. По окончании данного занятия программа построения дорожки становится более интерактивной, так как в нее добавляется возможность запроса информации у пользователя. Результаты вычислений сохраняются в виде списка. Ознакомившиеся с данной главой начинают понимать преимущества списков при программировании на AutoLISP. Ведь именно благодаря возможностям обработки списков язык LISP получил свое имя (LISt Processing).
Темы:
Различия между локальными и глобальными переменными
Объединение данных в ассоциативные списки
Контроль значений переменных программы
Переработка кода программы
Комментарии в тексте программы
Точки останова и несколько контрольных значений
Итоги занятия 2