Пользователям современных
Автор статьи не является программистом по образованию и в тему программирования под LISP погрузился относительно недавно, поэтому приведенный далее код не является оптимальным и эталонным, но при этом может помочь таким же новичкам начать освоение программирования под API. Также в ходе использования утилиты было решено множество задач:
- описание пользовательского графического интерфейса на языке DCL и демонстрация приема загрузки DCL из LISP;
- создание реакторов, которые позволяют отслеживать изменения объекта, что в свою очередь позволило синхронизировать изменение ассоциированных объектов nanoCAD BIM Строительство и примитивов Платформы nanoCAD;
- отображение в пользовательском диалоге списка атрибутов, доступных
из COM-интерфейса объектов чертежа; - использование методов
COM-интерфейса объектов nanoCAD BIM Строительство для обновления графики, записи и создания атрибутов.
Эта статья является продолжением ранее опубликованных материалов и информации о проведенных мероприятиях:
- вебинара, посвященного теме программирования LISP под nanoCAD BIM Конструкции. Здесь можно найти различные ссылки на учебные материалы по LISP, а также советы по организации работы в средах программирования LISP;
- статьи, посвященной связыванию параметров объектов nanoCAD BIM Конструкции. В ней представлены существующие способы синхронизации атрибутов объектов, а также приведен сравнительный анализ этих способов.
Описание
По ссылке вы можете посмотреть
Учитывая, что описание синхронизации объектов и элементов пользовательского диалога должно соответствовать типам выбираемых объектов (примитивы Платформы nanoCAD и объекты nanoCAD BIM Строительство (конфигурация «Конструкции»)), первое, что нужно сделать, — определить соответствующий тип объектов. Ниже приводится функция проверки наличия
(defun BIMneBIM (vlaobj)
(not
(vl-catch-all-error-p
(vl-catch-all-apply '(lambda (x) (vlax-get-property x "Element"))
(list vlaobj)
)
)
)
)
Функция возвращает Т, если выбран объект nanoCAD BIM Конструкции, и nil, если объект не относится к категории объектов nanoCAD BIM Конструкции.
Эта функция используется в диалоге выбора объектов, так как хотя бы один объект, выбранный пользователем, должен быть объектом nanoCAD BIM Строительство. Соответствующее условие проверяется в диалоге выбора.
(defun MAX_ASSOCIATE (/ vlaobj1 vlaobj2 obj1 ojb2)
(vl-load-com)
(setq obj1 (car (entsel "\nВыберите первый объект")))
(setq vlaobj1 (vlax-ename->vla-object obj1))
(if (= nil (BIMneBIM vlaobj1))
(progn
(while (= nil (BIMneBIM vlaobj2))
(setq obj2 (car (entsel "\nВыберите второй объект, он должен быть объектом BIM Конструкции")))
(setq vlaobj2 (vlax-ename->vla-object obj2))
)
)
(progn
(setq obj2 (car (entsel "\nВыберите второй объект")))
(setq vlaobj2 (vlax-ename->vla-object obj2))
)
)
(Dialog vlaobj1 vlaobj2)
)
Далее выбранные объекты передаются для формирования окна пользовательского диалога, который описан на DCL. Объекты необходимы для формирования списков атрибутов, которые отображаются в диалоговом окне, а также для определения поведения этого окна в зависимости от типа выбранных объектов.
Для создания пользовательского диалога необходимо описать окно интерфейса. Описание выполнено с помощью DCL. Есть пользовательский прием, который позволяет упростить работу с файлом интерфейса: файл
(defun fun_create-dcl-file (file / handle)
;получение дескриптора
(setq handle (open file "w") ;создание переменной handle с номером дескриптора файла
) ;_ end of setq
(foreach item
'(;построчная запись в файл нижележащих стрингов
"ParamSpis: dialog {label=\"Список параметров объекта\";" ":row {"
":boxed_column {label=\"Список параметров первого объекта\";"
":spacer {height=3;}"
":button { label=\"Заголовок/Имя\"; key=\"k1\"; fixed_width=true; width=14; alignment=left; }"
":list_box {label=\"Список параметров 1-го выбранного объекта\"; key=\"b1\";"
"list=\"\"; width = 50; height = 50; aligment=\"right\"; fixed_width=true; fixed_width_font=True;}"
"}// column"
":boxed_column { label=\"Направление передачи и пересчет значения\";"
":spacer {height=25;}"
":button {label=\"Направление передачи\"; key=\"k3\"; height=3; width=20;}"
":text {label=\"\"; fixed_width=true; width=6; height = 4; alignment=left; value=\"----->\"; key=\"k4\"; }"
":text {label = \"Перерасчет значения\"; key=k5;}"
":text {label = \"Синтаксис должен соответствовать Мастеру функций\"; key=k52;}"
":edit_box { label=\"\"; key=\"k6\"; width=60; value = \"\";}"
":spacer {height=25;}" "}"
":boxed_column { label=\"Список параметров второго объекта\";"
":spacer {height=3;}"
":button { label=\"Заголовок/Имя\"; key=\"k2\"; fixed_width=true; width=14; alignment=left;}"
":list_box {label=\"Список параметров 2-го выбранного объекта\"; key=\"b2\";"
"list=\"\"; width = 50; height = 50; aligment=\"left\"; fixed_width=true; fixed_width_font=True;}"
"}// column" "}// row" ":spacer {height=1;}" "ok_button;" "}"
)
(write-line item handle)
) ;_ end of foreach
(close handle)
file
)
Функция создания файла интерфейса вызывается внутри диалога.
(defun Dialog (obj1 obj2 / dcl_id listik2 listik1 str1 str2 bool bool1 bool2 zero)
;создание списка параметров объектов obj1 и obj2
(setq zero (list
(cons "obj1"
(if (= nil (BIMneBIM obj1))
(progn
(setq Obj1BimNot "Obj1NeBim")
(ParamListNeBIMS obj1)
)
(progn
(setq Obj1BimNot "Obj1Bim")
(paramlist obj1)
)
)
)
(cons "obj2"
(if (= nil (BIMneBIM obj2))
(progn
(setq Obj2BimNot "Obj2NeBim")
(ParamListNeBIMS obj2)
)
(progn
(setq Obj2BimNot "Obj2Bim")
(paramlist obj2)
)
)
)
)
)
;проверка наличия файла временного диалога dcl
(setq file (strcat (vl-string-right-trim "\\" (getenv "tmp")) "\\spiskop.dcl"))
(if (= nil (findfile file)) (fun_create-dcl-file file)) ; если файл не найден, он создается.
;загрузка диалога
(if (< (setq dcl_id (load_dialog file)) 0) (exit))
(if (not (new_dialog "ParamSpis" dcl_id)) (exit))
;первоначальная загрузка в List_box-ы списков параметров с именами
(Podpersis "b1" "Имя" "obj1" zero)
(Podpersis "b2" "Имя" "obj2" zero)
(setq bool2 0)
(setq listik1 (cdr (assoc "Имя" (car (cdr (assoc "obj1" zero))))))
(setq listik2 (cdr (assoc "Имя" (car (cdr (assoc "obj2" zero))))))
(cond
((and (= "Obj2Bim" Obj2BimNot) (= "Obj1Bim" Obj1BimNot)) ; Обработка диалога в зависимости от того, чем являлись исходные объекты. Объекты nanoCAD BIM Конструкции или все прочие объекты.
(progn ;обработка диалога так, если бы оба объекта были MS-ные или BIM-овские
(action_tile "k3"
"(setq bool2 (Strelka bool2 str1 str2))"
)
;Действия, которые передаются в пользовательский диалог при выборе соответствующих значений в интерфейсе
(action_tile "b1"
"
(setq str1 $Value)
(if(= bool2 0)(set_tile \"k6\" (Nameparam listik1 str1)))
"
)
(action_tile "b2"
"
(setq str2 $Value)
(if(= bool2 1)(set_tile \"k6\" (Nameparam listik2 str2)))
"
)
(action_tile "k1"
"(setq bool (Perspis bool \"b1\" zero \"obj1\"))"
)
(action_tile "k2"
"(setq bool1 (Perspis bool1 \"b2\" zero \"obj2\"))"
)
) ;progn
) ;1-е условие
((and (= Obj1BimNot "Obj1NeBim") (= Obj2BimNot "Obj2Bim"))
(progn ;обработка диалога для случая, когда первый выбранный объект является объектом Платформы
(mode_tile "k3" 1)
(mode_tile "k1" 1)
(set_tile "k4" "----->")
(setq bool2 1)
(action_tile "b1"
"
(setq str1 $Value)
(if(= bool2 1)(set_tile \"k6\" (Nameparam listik1 str1)))
"
)
(action_tile "b2"
"
(setq str2 $Value)
"
)
(action_tile "k2"
"(setq bool1 (Perspis bool1 \"b2\" zero \"obj2\"))"
)
) ;progn
) ;2-е условие
(T
(progn ;обработка диалога для случая, когда второй выбранный объект является объектом Платформы
(mode_tile "k3" 1)
(mode_tile "k2" 1)
(set_tile "k4" "<-----")
(setq bool2 0)
(action_tile "b1"
"
(setq str1 $Value)
"
)
(action_tile "b2"
"
(setq str2 $Value)
(if(= bool2 0)(set_tile \"k6\" (Nameparam listik2 str2)))
"
)
(action_tile "k1"
"(setq bool (Perspis bool \"b1\" zero \"obj1\"))"
)
) ;progn
) ;крайнее условие
) ;cond
;Окончание диалога. И чтение из text_box имени/формулы параметра, для которого будет выполняться вычисление при последующей его передаче в объект BIM Конструкции / Model Studio CS
(action_tile "accept"
"(setq formula (get_tile \"k6\"))
(done_dialog)"
)
(start_dialog)
(unload_dialog dcl_id)
;вырезка имени из строки списка параметров по номеру выбранной строки в интерфейсе
(setq str1 (Nameparam listik1 str1))
(setq str2 (Nameparam listik2 str2))
;передача вырезанных имен и списков параметров
(if (and (= "Obj2Bim" Obj2BimNot) (= "Obj1Bim" Obj1BimNot))
(progn ;оба объекта BIM Конструкций
(if (= bool2 1)
(progn
(sinhr
str2
str1
(cdr (assoc "obj2" zero))
(cdr (assoc "obj1" zero))
obj2
obj1
formula
)
)
(progn
(sinhr
str1
str2
(cdr (assoc "obj1" zero))
(cdr (assoc "obj2" zero))
obj1
obj2
formula
)
)
)
)
(progn ;один из объектов не BIM Конструкций
(if (and (= Obj1BimNot "Obj1NeBim") (= Obj2BimNot "Obj2Bim"))
(progn ;первый объект не BIM Конструкций
(sinhr2 str1 str2 (cdr (assoc "obj2" zero)) obj1 obj2 formula)
)
(progn ;второй объект не BIM Конструкций
(sinhr2 str2 str1 (cdr (assoc "obj1" zero)) obj2 obj1 formula)
)
)
)
)
)
В диалоговом окне используются другие вспомогательные функции — они описаны
Список параметров для объектов nanoCAD BIM Строительство и примитивов Платформы nanoCAD формируется по-разному:
- у объектов nanoCAD BIM Строительство (конфигурация «Конструкции») параметры выгружаются из списка параметров главного узла свойств object.Element.Parameters. Поскольку почти у всех объектов nanoCAD BIM Строительство такая структура хранения атрибутов, достаточно описать одну функцию выгрузки для объектов nanoCAD BIM Конструкции;
- у объектов Платформы nanoCAD в COM нет отдельного свойства со списком параметров. Однако в LISP существует перечень функций, которые позволяют получить свойства
из COM-интерфейса . Есть пользовательский прием, который предоставляет возможность получить список свойств объекта с помощью циклического перебора функций, запрашивающих свойстваиз COM-интерфейса объекта (www.caduser.ru/forum/post262232.html#p262232).
Рассмотрим код, который запрашивает список свойств объектов nanoCAD BIM Строительство (конфигурация «Конструкции»).
;Функция, которая через COM достает список параметров объектов BIM Конструкции. Список формируется в виде двумерного листа.
;Где первый лист - отформатированный список "ИмяПараметра=Значение", и он необходим для вывода в диалог.
;А второй лист - аналог словаря с ключом по имени параметра.
;Возвращаемое значение функции имеет вид ((List форматированный список)(ИмяПараметра(Остальные поля параметров)))(Имя параметра.(list значение заголовок комментарий)).
(defun ParamList (obj / VlObj Elm Param_List Count_Param_List List_Param_List vla_par
ParName ParValue FrmtParamList1 FrmtParamList2 i
)
(setq Elm (vlax-get-property obj "Element"))
(setq Param_List (vlax-get-property Elm "Parameters"))
(setq Count_Param_List (vlax-invoke-method Param_list "Count"))
(setq List_Param_List (list))
(setq i 0)
(while (< i Count_Param_List)
(setq vla_par (vlax-invoke-method Param_List "item" i))
(setq ParName (vlax-get-property vla_par "Name"))
(setq ParValue (vlax-get-property vla_par "Value"))
(setq Zagolovok (vlax-get-property vla_par "Comment"))
(setq Comment (vlax-get-property vla_par "ValueComment"))
(setq List_Param_List (cons (list ParName (list ParValue Zagolovok Comment))
List_Param_List
)
)
(setq FrmtParamList1 (cons (strcat ParName " = " ParValue) FrmtParamList1))
(setq FrmtParamList2 (cons (strcat Zagolovok " = " ParValue) FrmtParamList2))
(setq i (1+ i))
)
(setq List_Param (list
(list (cons "Имя" (acad_strlsort FrmtParamList1))
(cons "Заголовок" (acad_strlsort FrmtParamList2))
)
List_Param_List
)
)
)
Таким образом, возвращаемый список параметров хранит пары значений: заголовок и значение, имя и значение. Это необходимо для переключения варианта отображения в окне пользовательского диалога (рис. 1).
Рассмотрим код функции ParamListNeBIMS, которая запрашивает список свойств объектов Платформы nanoCAD.
(defun ParamListNeBIMS (vlaobj / rezlist) ;код функции взят отсюда: https://www.caduser.ru/forum/post262232.html#p262232
(setq vlaobj (list vlaobj) ;Создание на основе VLAObj списка, состоящего из одного элемента. Это необходимо, чтобы потом отработала функция vl-catch-all-apply
rezlist nil ;очистка переменной
)
(mapcar
(lambda (x / prop); объявление лямбда-функции, у которой в качестве аргумента приходит почищенный atoms-family
(if ;Проверка через два условия наличия свойства и его значения. 1-е условие проверяет через функцию vlax-property-available-p, что свойство есть у объекта. 2-е условие проверяет возможность чтения значения проверяемого свойства.
(and
(vlax-property-available-p ;Проверяет, имеет ли объект данное свойство. (vlax-property-available-p <объект> <свойство> [<модификация>]) Возвращаемое значение - T или nil
(car vlaobj) ;получение vla-объекта
(vl-string-subst "" "VLA-GET-" x) ;из ранее обработанного списка atoms family, из которого удалили все элементы списка кроме тех, что начинаются на vla-get, берется элемент списка в виде строки с вычитанием подстроки vla-get
)
(not ;проверка отсутствия ошибки при попытке получения значения свойства
(vl-catch-all-error-p ;Проверяет, является ли объект, возвращенный функцией vl-catch-all-apply, объектом типа VL-CATCH-ALL-APPLY-ERROR. (vl-catch-all-error-p <аргумент>) Тип аргумента - любой. Возвращаемое значение: T, если <аргумент> относится к объектам типа VL-CATCH-ALL-APPLY-ERROR, и nil, если <аргумент> имеет другой тип.
(setq prop (vl-catch-all-apply ;Передает для выполнения указанной функции список в качестве аргументов и дает возможность обработать прерывание, связанное с ошибкой. (vl-catch-all-apply '<функция> <список>)
;Возвращаемое значение - результат применения функции <функция> к списку <список>
;В данном случае выполняется попытка получения значения свойства VLA-GET-... у объекта vlaobj, который передается как список. Вместо ... подразумеваются перебираемые варианты - например VLA-GET-COLOR, VLA-GET-NAME и т.п.
(read x)
vlaobj
)
)
)
)
)
(setq rezlist (cons (strcat (vl-string-subst "" "VLA-GET-" x) " = " (Preobr x prop)) rezlist) ;добавление в rezlist имени успешно отработавшего VLA-GET-... и свойства, которое по нему получено
)
)
)
(vl-remove-if-not ;Удаляет из списка все элементы, возвращающие nil при проверке тест-функцией. (vl-remove-if-not '<тест-функция> <список>)
'(lambda (x) (vl-string-search "VLA-GET-" x)) ;поиск элементов списка atoms-family, которые начинаются на "VLA-GET-"
(atoms-family 1) ;возвратит список зарезервированных символов
)
)
(setq rezlist (list (list (cons "Имя" (acad_strlsort rezlist)) (cons "Заголовок" (acad_strlsort rezlist))) rezlist))
)
Источник: www.caduser.ru/forum/post262232.html#p262.
Функция ParamListNeBIMS проверяет наличие
(defun Preobr (x prop)
(cond
((= (type (car vlaobj)) (type prop))
(strcat "Vla-объект: " (ObjName prop))
)
((numberp prop)
(setq prop (rtos prop 2 3))
)
((= prop :vlax-false)
"Ложь"
)
((= prop :vlax-true)
"Истина"
)
((= (type prop) (type (vlax-make-variant nil)))
(vl-princ-to-string (vlax-safearray->list (vlax-variant-value prop)))
)
((= prop nil)
"Пусто"
)
(T
(vl-princ-to-string prop)
)
)
)
;Функция получения имени vla-объекта даже в том случае, когда у объекта нет свойства objectname.
(defun ObjName (prop)
(setq stroka (vl-princ-to-string prop))
(setq nachalovich (+ 2 (vl-string-search " " stroka)))
(setq NachPoisVtorProb (+ 1 (vl-string-search " " stroka)))
(setq kolvoSimvl (- (+ 1 (vl-string-search " " stroka NachPoisVtorProb))
nachalovich
)
)
(substr stroka nachalovich kolvoSimvl)
)
А теперь рассмотрим подфункции dialog, которые вызывают интерфейс и описывают взаимодействие пользователя с ним. Код функции Strelka отвечает за изменение направления передачи параметра.
(defun Strelka (bool2 st1 st2)
(if (= bool2 0)
(progn
(set_tile "k4" "<-----")
(set_tile "k6" (Nameparam listik2 st2))
(setq bool2 1)
)
(progn
(set_tile "k4" "----->")
(set_tile "k6" (Nameparam listik1 st1))
(setq bool2 0)
)
)
)
Эту функцию активирует нажатие кнопки Направление в интерфейсе программы. В соответствии с изменением направления передачи синхронизируемого атрибута в диалоговом окне меняется направление стрелки и имя параметра в поле пересчета значения. В этом поле указано имя параметра, значение которого передается от ведущего объекта к ведомому (рис. 2, 3). В случае, если один из объектов является примитивом Платформы nanoCAD, кнопка направления становится неактивной, а значение параметра передается от примитива Платформы к объекту nanoCAD BIM Строительство (рис. 4).
В описанной выше функции Strelka используется подфункция Nameparam, которая возвращает имя параметра, выбранное пользователем в списке параметров объекта. Возвращаемое значение устанавливается в поле пересчета значения.
(defun Nameparam (listik str)
(If (/= str nil)
(progn
(substr (nth (atoi str) listik) 1 (vl-string-search " = " (nth (atoi str) listik))
)
)
(progn "")
)
)
Функция Perspis выполняется при нажатии кнопки смены отображения Заголовок/Имя (см. рис. 1).
;функция, загружающая в диалоговые окна списки параметров по имени/заголовку в зависимости от значения bool
(defun Perspis (bool Key_obj lister objer)
(if (= bool 0)
( rong
(Podpersis Key_obj “Имя” objer lister)
(setq bool 1)
)
( rong
(Podpersis Key_obj “Заголовок” objer lister)
(setq bool 0)
)
)
)
(Окончание следует)