Урок 8. Пример AutoLISP программы: Сумма длин отрезков.

В этом уроке мы рассмотрим пример AutoLISP программы, которая будет считать сумму длин выбираемых отрезков.

Вначале нарисуем в AutoCAD два отрезка: один длиной 300, другой 200. Чтобы нам было, чего выбирать, и легче было тестировать программу.

Затем запустим Visual LISP, создадим новый файл и откроем консоль Visual LISP.

Для того, чтобы выбрать объект в AutoLISP используется функция entsel.

Функция entsel предлагает пользователю указать объект, выдавая запрос, текст которого задан в качестве аргумента.

( entsel [<запрос>])

Аргумент <запрос> — текст запроса.

Давайте наберем функцию.

(entsel «Выберите отрезок:»: «)

Дважды щелкнем после закрывающейся скобки (функция выделится) и нажмем кнопку «Загрузить выделенный фрагмент». См. Рис. 1.

Пример AutoLISP

Рис. 1.  Функция entsel

На запрос: «Выберите отрезок:» укажите один из ранее нарисованных отрезков. См. Рис. 2.

Пример AutoLISP

Рис. 2.    Выбор отрезка.

Функция entsel вернет нам имя объекта и координаты точки, которые мы указали, выбирая отрезок. См. Рис. 3.

Пример AutoLISP

Рис. 3. Функция entsel. Имя объекта и координаты точки.

Чтобы, оставить только имя объекта, используем функцию car, которая извлекает первый элемент из списка.

В начале запроса поставим \n, чтобы он отображался в отдельной строке.

Выделяем всю строку и нажмем кнопку «Загрузить выделенный фрагмент».

На запрос, «Выберите отрезок:» укажите один из ранее нарисованных отрезков.

На консоли Visual LISP появится результат выполнения всей строки — это имя выбранного объекта.

См. Рис. 4.

Пример AutoLISP

Рис. 4.   Функция car. Имя выбранного объекта.

Давайте, при помощи функции присвоения setq, запомним это имя в переменной name_obj.

(setq name_obj (car (entsel «\n Выберите отрезок: «)))

Для того, чтобы получить список с характеристиками примитива используем функцию entget. Аргументом этой функции является имя примитива, данные которого мы хотим получить. См. Рис. 5.

Пример AutoLISP

Рис. 5.   Функция entget. Список с характеристиками примитива.

Выделяем вторую строку и нажмем кнопку «Загрузить выделенный фрагмент».

На консоли Visual LISP появится результат выполнения : список с характеристиками примитива.

Весь список выглядит так:

 ((-1 . < Имя объекта: 7ee90e38>) (0 . «LINE») (330 . < Имя объекта: 7ee8ecf8>) (5 . «32F») (100 . «AcDbEntity») (67 . 0) (410 . «Model») (8 . «0») (100 . «AcDbLine») (10 4905.76 5404.8 0.0) (11 5125.47 5609.07 0.0) (210 0.0 0.0 1.0))

Давайте, при помощи функции присвоения setq, запомним этот список в переменной list_obj.

(setq list_obj (entget name_obj))

Из этого списка нам нужны координаты точек выбранного отрезка. Для того, чтобы их от туда извлечь, используем функцию assoc.

Функция assoc применяется к сложному списку, каждый элемент которого начинается с DXF-кода. Именно по этому коду функция assoc и извлекает элемент из списка.

(assoc <код> <список>)

<код>  — DXF-код.

<список> — список с данными примитива.

Для того чтобы получить элемент списка, который содержит координаты первой точки, добавим строку:

(assoc 10 list_obj)

10 – это  DXF-код координат первой точки.

Выделяем третью строку и нажмем кнопку «Загрузить выделенный фрагмент».

В консоли появится результат выполнения. См. Рис. 6.

Пример AutoLISP

Рис. 6.  Функция assoc.  Элемент списка с DXF-кодом 10.

Из списка будет извлечен элемент с DXF-кодом 10.

Для того, чтобы оставить только координаты точки, добавляем функцию cdr.

(cdr(assoc 10 list_obj))

возвращает список без первого элемента.

При помощи функции присвоения setq, запомним значения координат в переменной р1.

(setq p1 (cdr(assoc 10 list_obj)))

Выделяем третью строку и нажмем кнопку «Загрузить выделенный фрагмент».

В консоли появится результат выполнения. См. Рис. 7.

Пример AutoLISP

Рис. 7.   Функция cdr.

В переменной р1 теперь хранятся координаты начальной точки отрезка.

Аналогичным образом извлекаем из списка list_obj координаты конечной точки отрезка (DXF-код конечной точки отрезка равен 11), и сохраняем их в переменной р2.

(setq p2 (cdr(assoc 11 list_obj)))

Длину отрезка определяем при помощи функции distance.

Функция  (distance <точка1> < точка2>) вычисляет расстояние между двумя точками, где

<точка1> < точка2> — координаты точек.

Давайте добавим строку:

(setq dl_otr (distance р1 р2))

в которой мы вычисляем длину выбранного отрезка, и сохраняем ее в переменной dl_otr

Выделяем последние две строчки и нажимаем кнопку «Загрузить выделенный фрагмент».

В консоли появится результат выполнения. Координаты второй точки и длина выбранного отрезка. См. Рис. 8.

Пример AutoLISP

Рис. 8.   Функция distance.

Для того, чтобы программа после указания первого отрезка запросила следующий мы организует цикл при помощи функции while.

Функция while выполняет операцию цикла по многократно проверяемому условию.

(while <условие>

      <выражения>

)

Если <условие> верно, то оно возвращает Т (True), если не верно то nil.

Пока <условие> принимает значение Т, функция while выполняет <выражения>, и она прекратит свою работу, когда на некотором шаге аргумент <условие> получит значение nil.

Чтобы наш цикл был бесконечным, мы вместо условия поставим значение Т.

(while Т

      <выражения>

)

В этом случаи наш цикл будет бесконечно запрашивать новый отрезок, пока мы не прервем его, нажав на <Esc>

Для того, чтобы сосчитать сумму длин выбранных отрезков, введем переменную sum_dl.

Перед началом цикла присвоим sum_dl начальное значение 0.0.

(setq sum_dl 0.0)

Внутри цикла добавим выражение:

(setq sum_dl (+ sum_dl dl_otr))

На первом шаге к sum_dl =0.0 прибавится длина первого отрезка dl_otr. Сумма снова присваивается переменной sum_dl.

На втором шаге к sum_dl (которая равна длине первого отрезка) прибавится длина второго выбранного отрезка dl_otr. Сумма снова присваивается переменной sum_dl.

И так далее с каждым новым шагом мы будем прибавлять длину выбранного отрезка к сумме отрезков выбранных ранее.

Чтобы напечатать результат в командной строке добавим выражение:

(princ sum_dl)

В результате наша программа будет выглядеть так. См. Рис. 9.

Пример AutoLISP

Рис. 9.   Функция while. Создания цикла.

Выделяем весь текст и нажимаем кнопку «Загрузить выделенный фрагмент».

На запрос: «Выберите отрезок:» укажите один из ранее нарисованных отрезков.

В командной строке появится длина выбранного отрезка, ниже снова появится запрос: «Выберите отрезок:»  См. Рис. 10.

Пример AutoLISP

Рис. 10.    Длина выбранного отрезка.

Укажите второй отрезок, и в командной строке появится сумма длин двух отрезков.

Ниже снова появится запрос: «Выберите отрезок:»  См. Рис. 11.

Пример AutoLISP

Рис. 11.   Сумма длин двух отрезков.

Чтобы прервать выполнения программы нажмите <Esc>.

Хотя мы и получили требуемый результат, написание программы еще не окончено.

Давайте проверим, что будет, если при выборе отрезка мы промахнемся и щелкнем мимо.

Снова выделяем весь текст и нажимаем кнопку «Загрузить выделенный фрагмент».

На запрос: «Выберите отрезок:», щелкнете в пустом месте не выбирая отрезок.  См. Рис. 12.

Пример AutoLISP

Рис. 12.    Ошибка: неверный тип аргумента.

Программа прервет свою работу.

В консоли Visual LISP появится сообщение: ошибка: неверный тип аргумента: lentityp nil.

Очевидно, что после того, как мы промахнулись при выборе отрезка, функция entsel вернуло не имя выбранного отрезка, а значение nil. Это и вызвало ошибку.

Чтобы программа не реагировала на промахи пользователя, нужно добавить в нашу программу еще несколько строк.

Функция if выполняет условную операцию типа if-then-else (если-то-иначе)

(if <условие> <выражение1> [<выражение2>])

Если <условие> верно выполняется <выражение1>

Если <условие> не верно выполняется <выражение2>

Давайте напишем <условие> для нашего случая.

Следующее условие (= name_obj nil) будет читаться так: если name_obj рано nil.

Но нам нужно выполнять наши выражения наоборот, когда name_obj не рано nil. А это соответствует следующему условию:

(not (= name_obj nil))

Если <условие> верно и нам нужно выполнить не одно <выражение>, а несколько, то нужно использовать функцию progn.

Функция prong объединяет несколько выражений в одно. Добавим ее к функции if и в общем случаи наш кусок кода будет выглядеть так:

(if (not (= name_obj nil))

    (progn

      <наши выражения>

    ) ;end progn

  ) ; end if

Читается: Если name_obj не равно nil выполнить <наши выражения>

Добавим эти строки в нашу программу и она примет следующий вид. См. Рис. 13

Пример AutoLISP

Рис. 13.   Функции if и progn

Выделяем весь текст и нажимаем кнопку «Загрузить выделенный фрагмент».

Протестируете программу. Теперь даже, если вы щелкаете на пустом месте, программа AutoLISP не прекращает свою работу, а продолжает выдавать запрос: «Выберите отрезок:»

Но пользователь может случайно выбрать другой примитив (не отрезок), например окружность. По всей видимости, программа снова выдаст ошибку. Чтобы этого не произошло, следующим выражением прочитаем и запомним тип выбранного примитива:

(setq tip_obj (cdr (assoc 0 list_obj)))

0 – это  DXF-код типа.

Затем, поставим условия, что выполнять выражения нужно, если выбранный объект имеет тип отрезок («LINE»).

Добавляем выше сказанное в программу, и она приобретает следующий вид. См. Рис. 14.

Пример AutoLISP

Рис. 14.  Проверка типа «LINE«.

Добавленные строки помечены синими полосками.  Теперь наша программа не будет выдавать ошибку, если пользователь случайно выделит другой примитив.

Для того, чтобы пользователь видел какие отрезки он уже выбрал, а какие нет, давайте выделим выбранные отрезки.

Для этого сначала, создадим пустой набор и сохраним его в переменной line_set:

(setq line_set (ssadd))

Функция ssadd добавляет примитив в набор.

(ssadd [<примитив> [<набор>]]) , где

<примитив> — имя примитива.

<набор> — имя набора.

Для того, чтобы выделить примитивы, входящие в набор используется функция sssetfirst:

(sssetfirst <набор1> [<набор2>])

Функция sssetfirst включает ручки у примитивов, входящих в <набор1>.

А у примитивов входящих в <набор2> включает ручки и подсвечивает их пунктиром.

Добавим в нашу программу, следующую строчку:

(sssetfirst nil (ssadd name_obj line_set))

Она добавляет примитив name_obj в набор line_set, а затем включает ручки и подсвечивает пунктиром примитивы, входящие в этот набор.

Теперь наша программа будет иметь следующий вид. См. Рис.15.

Пример AutoLISP

Рис. 15.  Функции ssadd и sssetfirst.

Выделяем весь текст и нажимаем кнопку «Загрузить выделенный фрагмент».

На запрос: «Выберите отрезок:» укажите один из ранее нарисованных отрезков.

Отрезок будет выделен. См. Рис. 16.

Пример AutoLISP

Рис. 16.   Выделенный отрезок.

Причем, если пользователь укажет другой примитив, он выделен не будет и сумма отрезков не изменится.

Теперь давайте сделаем результат, который выводится у нас в командной строке более информативным.

Для этого, сначала преобразуем вещественное число, которое хранится в переменной sum_dl в строку:

(rtos sum_dl).

Затем при помощи функции сцепления строк strcat, прицепим пояснительную надпись.

Строка печати примет следующий вид:

(princ (strcat «\nОбщая длина: — « (rtos sum_dl)))

Кроме этого давайте сосчитаем количество выбранных отрезков. Для этого вначале перед циклом введем переменную n и присвоим ей целочисленное значения 0.

Далее внутри цикла помещаем строку

(setq n (+ n 1))

 которая будет считать количество выбранных отрезков.

Затем, преобразуем целое число в строку (itoa n), добавим пояснительную надпись и вставим в строку печати. См. Рис.17.

Пример AutoLISP

Рис. 17.    Добавление количества выбранных отрезков.

Поле чего она примет следующий вид:

(princ (strcat «\nОтрезков — « (itoa n) «\ nОбщая длина: — « (rtos sum_dl))).

Теперь наша программа выглядит так. См. Рис.18.

Пример AutoLISP

Рис. 18. Функции rtos, itoa и strcat. Изменения строки печати.

Выделяем весь текст и нажимаем кнопку «Загрузить выделенный фрагмент».

На запрос: «Выберите отрезок:» укажите сначала один, а затем и второй отрезок.

В командной строке будет написано количество выбранных отрезков и их общая длина. См. Рис. 19.

Пример AutoLISP

Рис. 19.  Количество выбранных отрезков и их общая длина.

Чтобы прервать выполнения программы нажмите <Esc>.

В заключении преобразуем нашу программу в пользовательскую функцию.

Добавим строку, в которой придумаем имя функции и перечислим все временные переменные:

(defun c:SumDl (/ line_set sum_Dl n name_obj list_obj tip_obj p1 p2 dl_otr)

      <наша программа>

) ; end_defun

Окончательный вариант программы см. Рис. 20.

Пример AutoLISP

Рис. 20.  Программа сумма длин отрезков.

Код программы:

(defun c:SumDl (/ line_set sum_Dl n name_obj list_obj tip_obj p1 p2 dl_otr)
   (setq line_set (ssadd)) ; создаем пустой список
   (setq sum_dl 0.0)         ; Сумма длин = 0
   (setq n 0)                      ; Кол-во выбранных отрезков
   (while T (setq name_obj (car (entsel "\Выберите отрезок: ")))
       (if (not (= name_obj nil))
          (progn
             (setq list_obj (entget name_obj))
             (setq tip_obj (cdr (assoc 0 list_obj)))
             (if (= tip_obj "LINE")
                (progn
                    (sssetfirst nil (ssadd name_obj line_set))
                    (setq p1 (cdr (assoc 10 list_obj)))
                    (setq p2 (cdr (assoc 11 list_obj)))
                    (setq dl_otr (distance p1 p2))
                    (setq sum_dl (+ sum_dl dl_otr))
                    (setq n (+ n 1))
                 );end progn
              ); end if
           );end progn
        ); end if
        (princ (strcat "\nОтрезков - " (itoa n) "\nОбщая длина: - " (rtos sum_dl)))
    ); end while
); end_defun

 Скачать программу Sum_dl.lsp Скачать программу Sum_dl.lsp (Размер файла: 464 bytes)

Теперь, чтобы запустить программу, используем кнопку «Загрузить активное окно редактора». Переходим в AutoCAD, в командной строке набираем SumDl и нажимаем <Enter>.

После того, как программа написана и протестирована нужно:

  • Если это ещё не сделано, добавить все локальные переменные в список временных переменных функции defun.
  • Сохранить свою LISP-программу.
  • Закрыть редактор Visual LISP и AutoCAD, чтобы очистить все значения переменных.
  • Загрузить AutoCAD снова.
  • Также можно добавить нашу программу в список автоматической загрузки и создать для нее кнопку запуска. Как это сделать рассмотрено в уроке: Простой запуск LISP программ.

Надеюсь, что этот пример AutoLISP программы у Вас получился, и Вы найдете ему применение.

В следующем уроке мы рассмотрим возможные вариации этой программы: Примеры LISP программ: Сумма длин отрезков.

Пишите в комментариях:

Трудно ли было выполнить этот урок?

Где у вас возникли трудности?

Была ли для Вас полезной информация, данная в этом уроке?

На какие вопросы программирования, Вы хотели бы, увидит ответы в следующих уроках?

Я с удовольствием отвечу на ваши комментарии.

Если вы хотите получать новости с моего сайта. Оформляйте подписку.

До новых встреч.

 «Автор: Михаил Орлов»

Google

Также на эту тему Вы можете почитать:

21 комментарии на “Урок 8. Пример AutoLISP программы: Сумма длин отрезков.

  1. Александр 02.04.2015 14:44

    Спасибо за урок!
    Но мне немного непонятно. после набора:

    (setq name_obj (car (entsel "\nВыберите об")))
    (setq list_obj (entget name_obj))
    (setq p1 (cdr (assoc 10 list_obj)))
    (setq p2 (cdr (assoc 11 list_obj)))

    Я загрузил данный фрагмент в автокад, выбрал отрезок, в консоле лиспа отобразились координаты точек. Но эти координаты никак не совпадают с координатами точек на отрезках если посмотреть в строке состояния «координаты курсора». То есть вычисленые в лиспе точки не лежат на отрезке. Что я не так понял?

    • Михаил Орлов 02.04.2015 16:17

      Несомненно, они должны совпадать.
      Я не знаю, как у Вас в строке состояния настроено отображение координат (там несколько настроек).
      Выделите ваш отрезок и нажмите Ctrl+1 (удерживая клавишу «Ctrl» нажмите «1»). Откроются свойства выбранного отрезка. На вкладке «Геометрия» есть координаты начальной и конечной точек. Сравните с этими координатами.

      • Александр 06.04.2015 08:52

        Да, если мы создадим новый (чистый) чертеж, нарисуем линию, загрузим данный выше фрагмент, то полученые точки, и точки в свойствах линни совпадают. Но когда я откриваю чертеж с начерчеными раньше примитивами или в тут же в новом чертеже нарисовать несколько линий, полилиний, кругов, прямоугольников и т.д. (короче загруженый примитивами чертеж), то проведя операцию опрелеления координат любой линии, точки не совпадаю с теми, что в свойствах. Проверено на двух комп. с автокадами 2008 и 2015.
        Да и определение координат (начальная точка, конечная) применимо к «Линии», с «Полилинией» получается, что под DXF-кодом «10» скрывается одновременно две точки начало и конец, я так понимаю тут нежун другой код определения вершин.

        • Михаил Орлов 06.04.2015 12:25

          Пришлите мне ваш файл, где координаты линий определяются не верно на почту acadprog@gmail.com Выделите эти линии каким нибудь цветом. Я проверю у себя.
          Программа написана только для объектов «Отрезок» (LINE). Для полилиниий нужен другой код, которой в принципе можно добавить в эту же программу.

        • Михаил Орлов 06.04.2015 14:52

          В вашем файле действительно эти координаты расходятся.
          Я стал строить отрезки по координатам, которые определяет AutoLisp, и обнаружил закономерность,
          что все координаты имеют одинаковое смещение, относительно координат в Автокаде.
          Это меня натолкнуло на мысль, что в вашем файле установлена Пользовательская Система Координат (ПСК).
          Я перевел чертеж в Мировую Систему Координат (МСК) и все встало на свои места.
          Наберите в командной строке ПСК (_UCS) и выберите или введите опцию [мир] (world)
          AutoLISP всегда определяет координаты в МСК. А Автокад в той системе координат, которая является текущей. Поэтому бывают случаи, когда они не совпадают.

          • Александр 06.04.2015 15:44

            Да, спасибо что подсказали. Я тоже думал что тут какое то смещение (морока с ПСК МСК), поскольку саму длину отрезка программа определяет верно. А возможно кодом в программе назначить привязку (отсчет) к ПСК?

  2. Михаил Орлов 07.04.2015 10:46

    Смещение точки начало координат на 1000 единиц по Х и по Y.

    (command "._ucs" "_n" '(1000 1000 0))
    • Даниил 24.05.2015 06:56

      Здравствуйте, Михаил! Такой вопрос, насколько трудна в исполнении в LISP, программа для построения шестерни? И с чего начать, могли бы вы подсказать или дать направление?

  3. Михаил Орлов 25.05.2015 20:42

    Если Вы знаете как нарисовать шестерню стандартным способом Автокад, то Lisp перевести не очень сложно. В июне будет урок по созданию шестерни.

  4. Владимир 15.10.2015 06:15

    А как модифицировать эту программу, чтобы она выбирала не по одному примитиву, а сразу несколько — выделением прямоугольником?

    • Владимир 15.10.2015 06:17

      Упс, кажется это в следующем уроке…

  5. Светлана 17.03.2016 13:40

    Спасибо за урок. Всё очень понятно. Понравилось, что даете описание всего того, что используем в коде. Получилось всё с первого раза. Буду с помощью Вашего материал изучать ЛИСП дальше.

    • Михаил Орлов 18.03.2016 17:29

      Рад, что Вас все получилось. Удачи в дальнейшем изучении.

  6. Светлана 17.03.2016 13:45

    НО заметила следующее: если выбираем отрезок,выбранный ранее (чисто случайно нажала на него), то отрезок учитывается в вычислениях (в количествах и в длине). Можно ли поставить еще одну проверку «если отрезок уже вошел в подсчет.»

    • Михаил Орлов 18.03.2016 17:36

      Да, нужно добавить еще проверку:

      (if (= tip_obj "LINE")
        ; проверяем есть ли имя в наборе.
        (if (= (ssmemb name_obj line_set) nil)
          (progn
            (sssetfirst nil (ssadd name_obj line_set))
            (setq p1 (cdr (assoc 10 list_obj)))
            (setq p2 (cdr (assoc 11 list_obj)))
            (setq dl_otr (distance p1 p2))
      
            (setq sum_dl (+ sum_dl dl_otr))
            (setq n (+ n 1))
            (princ (strcat "\nОтрезков - " (itoa n) "  Общая длина: - " (rtos sum_dl)))
          );end progn
          (princ " Этот отрезок уже выбран")
        ); end if
        (princ " Выбран не отрезок")
      ); end if
  7. Наталья 20.10.2016 12:31

    Если в строке (if (= tip_obj «LINE») слово LINE написать маленькими буквами, то программа работать не будет. Это так, или у меня что-то неправильно работает ?

    • Михаил Орлов 20.10.2016 17:52

      «LINE» должно быть написано большими буквами. Иначе работать не будет.

  8. Андрей 24.04.2020 13:50

    Изучаю Ваши уроки. Дошёл до функции distance и в консоле Visual LISP вместо сообщения изображенного на рис 6 получаю — (3709.14 2003.13 0.0)
    ; ошибка: неверный тип аргумента: 2D/3D-точка: nil
    _$
    Не могу понять в чем проблема. Подскажите, что на так?

    • Михаил Орлов 22.07.2020 07:56

      Координаты какой-то точки не определены. Выполните еще раз две предыдущие строки перед distance.

  9. Равшан 29.12.2020 07:01

    Здраствуйте Михаил можно ли с помошю Липс изменить параметры динамического блока?

Оставить комментарий

Ваш mail не будет опубликован.

Вы можете использовать HTML теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>