VetCAD

Загрузка приложений с минимумом усилий

   0 оценок

размещено: 25 Января 2016
обновлено: 26 Января 2016

Все, что описывается ниже, разработано для устойчиво работающей сети, не требующей обязательной синхронизации серверного и локального репозиториев.

Допустим, на каком-то каталоге на сервере (\\server\adds) располагаем один-единственный lsp (например, "firmloader.lsp"), который будет выполнять общую загрузку приложений. Как он это делает - вопрос шестнадцатый. Нам необходимо обеспечить загрузку этого firmloader.lsp в любых условиях и для любых пользователей.

Первое, что приходит в голову - это изменить acaddoc.lsp, вбив в самом конце строку

(load "\\\\server\\adds\\firmloader.lsp") ;; Обратите внимание на сдвоенные слеши

Второй вариант - внести аналогичную строку в acad.mnl (он ведь тоже загружается в каждый документ - хотим мы того или нет)

Третий вариант. Если используется собственное меню, подгружаемое как частичное, то рядом со своим файлом меню (к примеру, firmpart.cuix) кладем текстовый файл firmpart.mnl (обратите внимание: название файла такое же, как и у файла меню; а расширение - mnl).И указанную строку вносим уже в этот mnl. 

Если используется корпоративное меню (к примеру, firmcorporate.cuix), то рядом с cuix кладем текстовый файл firmcorporate.mnl. В остальном все то же, что и в третьем варианте.

Бывает, что сервера "дохнут", или их меняют. Поэтому использовать абсолютные пути лично я не стал бы. Намного проще обеспечить подключение сетевого каталога \\server\adds как сетевого диска (к примеру, s:). Тогда наша строка поменяется на

(load "s:\\firmloader.lsp")

Классно? Ага, почти. Autodesk в последних версиях AutoCAD вопросам безопасности кода уделила самое пристальное внимание. И попытка выполнения такой строчки, если s: у нас не внесен в список доверенных каталогов, выведет на экран соответствующее предупреждение. Не станем нервировать пользователей и вместо одной строки нарисуем уже чуть побольше:

(apply
  (function
    (lambda (/ err sysvar)
      ;; Сначала устанавливаем новые значения переменных, отвечающих за безопасность
      ;; и при этом сохраняем их старые значения
      (setq sysvar (vl-remove nil
                              (mapcar
                                (function
                                  (lambda (x / tmp)
                                    (if (setq tmp (getvar (car x)))
                                      (progn
                                        (setvar (car x) (cdr x))
                                        (cons (car x) tmp)
                                        ) ;_ end of progn
                                      ) ;_ end of if
                                    ) ;_ end of lambda
                                  ) ;_ end of function
                                ;; При необходимости список можно будет дополнять
                                '
                                 (("secureload" . 0)
                                  ("sysmon" . 0)
                                  )
                                ) ;_ end of mapcar
                              ) ;_ end of vl-remove
            ) ;_ end of setq
      ;; Теперь выполняем загрузку нашего firmloader.lsp, обернув "на всякий случай"
      ;; его в обработку ошибок
      (if (vl-catch-all-error-p
            (setq err (vl-catch-all-apply (function (lambda () (load "s:\\firmloader.lsp")))))
            ) ;_ end of vl-catch-all-error-p
        (alert (strcat "Невозможно загрузить основной код!\n"
                       (vl-catch-all-error-p err)
                       ) ;_ end of strcat
               ) ;_ end of alert
        ) ;_ end of if
      ;; И восстанавливаем старые значения системных переменных
      (foreach item sysvar
        (setvar (car item) (cdr item))
        ) ;_ end of foreach
      ) ;_ end of lambda
    ) ;_ end of function
  '()
  ) ;_ end of apply

Дальше предполагается, что приложения и необходимые настроечные файлы все-таки как-то структурированы. К примеру:

Каталог Пояснение
s:\arx arx-приложения. Структурированы по подкаталогам типа s:\arx\2012x32, s:\arx\2012x64 и т.п.
s:\net .NET-модули. Тоже структурированы по подкаталогам типа s:\net\2012, s:\net\2016
s:\lsp lsp-дополнения. Могут лежать как по подкаталогам, так и "просто так". Загружаются в строго определенных случаях. Предполагаем, что эти лиспы не использут диалоги dcl. А если используют, то написаны достаточно грамотно, чтобы dcl-файлы опрашивать не из предопределенного места.
s:\fas fas-модули. Если тут ничего нет, то выполняется загрузка из s:\lsp
s:\settings Файлы настроек, данных и т.п.

Определим те самые "строго определенные случаи", когда необходимо загружать lsp, игнорируя fas/vlx. Допустим, у нас есть несколько пользователей, с которыми можно договориться на предмет тестирования. Или у нас самих есть необходимость загрузки lsp. Можно проверять значения переменных окружения UserName, ComputerName, а можно проверять имя профиля AutoCAD: если имя профиля содержит, к примеру, слово "test", то загружаем именно lsp, в противном случае - fas / vlx

Теперь приступим собственно к разработке firmloader.lsp. Конечно, велик соблазн сделать все сразу в нем, но все же ограничим его функционал. Итак, что же он должен сделать?

  1. Определить, что грузить - lsp или fas/vlx
  2. Загрузить соответствующие модули
  3. При необходимости вызвать какие-то функции
  4. И на этом успокоиться

Неплохо было бы заодно еще и команду загрузки определить, чтобы можно было в любой момент ее повторно вызвать. Иногда полезная штука ;)

Принимаем волевое решение: если имя профиля содержит слово "test", или имя пользователя одно из "GoodEngineer1", "GoodEngineer2", "CoolTester" - то грузим lsp. Если нет - грузим fas / vlx. И только если fas / vlx не обнаружены, то грузим lsp:

(vl-load-com)

(defun c:firmloader (/)
  ;; Определяем, что грузить
  (if (or (wcmatch (strcase (getvar "cprofile")) "*TEST*") ; в имени профиля содержится "test"
          (wcmatch (strcase (getenv "username"))
                   "GOODENGINEER1,GOODENGINEER2,COOLTESTER" ; имя пользователя одно из этих
                   ) ;_ end of wcmatch
          (not (vl-directory-files "s:\\fas" "*.vlx" 1)) ; Не найдены файлы vlx
          (not (vl-directory-files "s:\\fas" "*.fas" 1)) ; Не найдены файлы fas
          ) ;_ end of or
    (progn
     ;; Будем грузить все из \\lsp
     )
    (progn
     ;; Будем грузить только из \\fas
     )
    ) ;_ end of if
  ) ;_ end of defun

Теперь перед нами встает очень интересная задача - найти и загрузить из каталога s:\lsp все lsp-файлы независимо от их полного пути. Воспользуемся имеющимися решениями (например, от LeeMac):

(vl-load-com)

(defun c:firmloader (/ lm:directoryfiles)

  (defun lm:directoryfiles (dir typ sub)
                           ;|
 dir - [str] Root directory for which to return filenames
 typ - [str] Optional filetype filter (DOS pattern)
 sub - [bol] If T, subdirectories of the root directory are included
 Returns: [lst] List of files matching the filetype criteria, else nil if none are found
|;
    (setq dir (vl-string-right-trim "\\" (vl-string-translate "/" "\\" dir)))
    (append (mapcar '(lambda (x) (strcat dir "\\" x)) (vl-directory-files dir typ 1))
            (if sub
              (apply 'append
                     (mapcar
                       '(lambda (x)
                          (if (not (wcmatch x "`.,`.`."))
                            (lm:directoryfiles (strcat dir "\\" x) typ sub)
                            ) ;_ end of if
                          ) ;_ end of lambda
                       (vl-directory-files dir nil -1)
                       ) ;_ end of mapcar
                     ) ;_ end of apply
              ) ;_ end of if
            ) ;_ end of append
    ) ;_ end of defun

  ;; Определяем, что грузить
  (if (or (wcmatch (strcase (getvar "cprofile")) "*TEST*") ; в имени профиля содержится "test"
          (wcmatch (strcase (getenv "username"))
                   "GOODENGINEER1,GOODENGINEER2,COOLTESTER" ; имя пользователя одно из этих
                   ) ;_ end of wcmatch
          (not (vl-directory-files "s:\\fas" "*.vlx" 1)) ; Не найдены файлы vlx
          (not (vl-directory-files "s:\\fas" "*.fas" 1)) ; Не найдены файлы fas
          ) ;_ end of or
    (progn
      ;; Будем грузить все из \\lsp
      (foreach file (lm:directoryfiles "s:\\lsp" "*.lsp" t)
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    (progn
      ;; Будем грузить только из \\fas
      (foreach file
                    ;; Получаем все файлы в каталоге s:\\fas и убираем те, у которых
                    ;; расширение не fas и не vlx
                    (vl-remove-if-not
                      (function
                        (lambda (x)
                          (wcmatch (strcase (vl-filename-extension x)) ".FAS,.VLX")
                          ) ;_ end of lambda
                        ) ;_ end of function
                      (lm:directoryfiles "s:\\fas" "*.*" t)
                      ) ;_ end of vl-remove-if-not
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    ) ;_ end of if
  ) ;_ end of defun

Теперь у нас загружены все коды, можно и с ними играться.

Например, у нас в каталоге s:\lsp есть такой замечательный лисп, который дополняет пути поддержки (firm-set-support). А есть еще один лисп, который устанавливает значения системных переменных (firm-set-sysvar). А есть еще один, который выполняет настройку текстового стиля (firm-set-textstyle). А есть еще код, который подгружает arx соответствующей версии и разрядности. А есть еще лисп, который обновляет net-модули. А есть еще... В общем, много чего есть.

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

Можно прямо внутри нашего загрузчика (который c:firmloader) оформить вызов этих функций. Достаточно простое и эффективное решение. Вот только одно: имя функции может быть изменено - по неосторожности, случайно. Или лисп может быть изменен и не загружен (всякое может случиться). Соответственно если у нас не определена функция, к примеру, firm-set-support, то при попытке ее вызова мы получим ошибку. Надо бы предусмотреть защиту от такого действия. Что мы и сделаем.

(vl-load-com)

(defun c:firmloader (/ lm:directoryfiles err)

  (defun lm:directoryfiles (dir typ sub)
                           ;|
 dir - [str] Root directory for which to return filenames
 typ - [str] Optional filetype filter (DOS pattern)
 sub - [bol] If T, subdirectories of the root directory are included
 Returns: [lst] List of files matching the filetype criteria, else nil if none are found
|;
    (setq dir (vl-string-right-trim "\\" (vl-string-translate "/" "\\" dir)))
    (append (mapcar '(lambda (x) (strcat dir "\\" x)) (vl-directory-files dir typ 1))
            (if sub
              (apply 'append
                     (mapcar
                       '(lambda (x)
                          (if (not (wcmatch x "`.,`.`."))
                            (lm:directoryfiles (strcat dir "\\" x) typ sub)
                            ) ;_ end of if
                          ) ;_ end of lambda
                       (vl-directory-files dir nil -1)
                       ) ;_ end of mapcar
                     ) ;_ end of apply
              ) ;_ end of if
            ) ;_ end of append
    ) ;_ end of defun

  ;; Определяем, что грузить
  (if (or (wcmatch (strcase (getvar "cprofile")) "*TEST*") ; в имени профиля содержится "test"
          (wcmatch (strcase (getenv "username"))
                   "GOODENGINEER1,GOODENGINEER2,COOLTESTER" ; имя пользователя одно из этих
                   ) ;_ end of wcmatch
          (not (vl-directory-files "s:\\fas" "*.vlx" 1)) ; Не найдены файлы vlx
          (not (vl-directory-files "s:\\fas" "*.fas" 1)) ; Не найдены файлы fas
          ) ;_ end of or
    (progn
      ;; Будем грузить все из \\lsp
      (foreach file (lm:directoryfiles "s:\\lsp" "*.lsp" t)
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    (progn
      ;; Будем грузить только из \\fas
      (foreach file
                    ;; Получаем все файлы в каталоге s:\\fas и убираем те, у которых
                    ;; расширение не fas и не vlx
                    (vl-remove-if-not
                      (function
                        (lambda (x)
                          (wcmatch (strcase (vl-filename-extension x)) ".FAS,.VLX")
                          ) ;_ end of lambda
                        ) ;_ end of function
                      (lm:directoryfiles "s:\\fas" "*.*" t)
                      ) ;_ end of vl-remove-if-not
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    ) ;_ end of if

  (foreach item '(;; Теперь перечисляем вызываемые нами функции в том порядке и с теми аргументами,
                  ;; в котором они должны выполняться. Вызов оформляем без скобок, внутри строк
                  "firm-set-support"
                  "firm-set-sysvar"
                  "firm-set-textstyle"
                  ;; В качестве примера вызова функции с каким-то параметром:
                  ;; "firm-do-something param1 param2"
                  )
    (if   ; 5. Если п.4 породил ошибку - неважно какую...
      (vl-catch-all-error-p
        (setq err ; 4. Оборачиваем п.3 в обработку ошибки
               (vl-catch-all-apply
                 (function
                   (lambda ()
                     ;; 3. Вычисляем п.2:
                     (eval
                       ;; 2. "Читаем" п.1:
                       (read
                         ;; 1. Добавляем скобки в начало и в конец:
                         (strcat "(" item ")")
                         ) ;_ end of read
                       ) ;_ end of eval
                     ) ;_ end of lambda
                   ) ;_ end of function
                 ) ;_ end of vl-catch-all-apply
              ) ;_ end of setq
        ) ;_ end of vl-catch-all-error-p
          ; 5.1 ... то выводим соответствующее предупреждение
       (princ (strcat "\nError call " item " : " (vl-catch-all-error-message err)))
       ) ;_ end of if
    ) ;_ end of foreach

  ) ;_ end of defun

Посмотрите внимательно на последние строки - начиная с foreach (строка 65 последнего кода). Я постарался в комментариях указать все, что необходимо.

Все? Почти. Скорее всего, коды, которые будут вызываться, будут вносить определенные изменения в текущий файл. На случай нормальной отмены действий добавим метки начала и конца отмены, а также - для полного шику - тихий выход из команды, чтобы в ком.строке ничего лишнего не выводилось:

(vl-load-com)

(defun c:firmloader (/ lm:directoryfiles err)

  (defun lm:directoryfiles (dir typ sub)
                           ;|
 dir - [str] Root directory for which to return filenames
 typ - [str] Optional filetype filter (DOS pattern)
 sub - [bol] If T, subdirectories of the root directory are included
 Returns: [lst] List of files matching the filetype criteria, else nil if none are found
|;
    (setq dir (vl-string-right-trim "\\" (vl-string-translate "/" "\\" dir)))
    (append (mapcar '(lambda (x) (strcat dir "\\" x)) (vl-directory-files dir typ 1))
            (if sub
              (apply 'append
                     (mapcar
                       '(lambda (x)
                          (if (not (wcmatch x "`.,`.`."))
                            (lm:directoryfiles (strcat dir "\\" x) typ sub)
                            ) ;_ end of if
                          ) ;_ end of lambda
                       (vl-directory-files dir nil -1)
                       ) ;_ end of mapcar
                     ) ;_ end of apply
              ) ;_ end of if
            ) ;_ end of append
    ) ;_ end of defun

;;; Метка начала отмены
  (vla-startundomark (vla-get-activedocument (vlax-get-acad-object)))

  ;; Определяем, что грузить
  (if (or (wcmatch (strcase (getvar "cprofile")) "*TEST*") ; в имени профиля содержится "test"
          (wcmatch (strcase (getenv "username"))
                   "GOODENGINEER1,GOODENGINEER2,COOLTESTER" ; имя пользователя одно из этих
                   ) ;_ end of wcmatch
          (not (vl-directory-files "s:\\fas" "*.vlx" 1)) ; Не найдены файлы vlx
          (not (vl-directory-files "s:\\fas" "*.fas" 1)) ; Не найдены файлы fas
          ) ;_ end of or
    (progn
      ;; Будем грузить все из \\lsp
      (foreach file (lm:directoryfiles "s:\\lsp" "*.lsp" t)
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    (progn
      ;; Будем грузить только из \\fas
      (foreach file
                    ;; Получаем все файлы в каталоге s:\\fas и убираем те, у которых
                    ;; расширение не fas и не vlx
                    (vl-remove-if-not
                      (function
                        (lambda (x)
                          (wcmatch (strcase (vl-filename-extension x)) ".FAS,.VLX")
                          ) ;_ end of lambda
                        ) ;_ end of function
                      (lm:directoryfiles "s:\\fas" "*.*" t)
                      ) ;_ end of vl-remove-if-not
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    ) ;_ end of if

  (foreach item '(;; Теперь перечисляем вызываемые нами функции в том порядке и с теми аргументами,
                  ;; в котором они должны выполняться. Вызов оформляем без скобок, внутри строк
                  "firm-set-support"
                  "firm-set-sysvar"
                  "firm-set-textstyle"
                  ;; В качестве примера вызова функции с каким-то параметром:
                  ;; "firm-do-something param1 param2"
                  )
    (if   ; 5. Если п.4 породил ошибку - неважно какую...
      (vl-catch-all-error-p
        (setq err ; 4. Оборачиваем п.3 в обработку ошибки
               (vl-catch-all-apply
                 (function
                   (lambda ()
                     ;; 3. Вычисляем п.2:
                     (eval
                       ;; 2. "Читаем" п.1:
                       (read
                         ;; 1. Добавляем скобки в начало и в конец:
                         (strcat "(" item ")")
                         ) ;_ end of read
                       ) ;_ end of eval
                     ) ;_ end of lambda
                   ) ;_ end of function
                 ) ;_ end of vl-catch-all-apply
              ) ;_ end of setq
        ) ;_ end of vl-catch-all-error-p
          ; 5.1 ... то выводим соответствующее предупреждение
       (princ (strcat "\nError call " item " : " (vl-catch-all-error-message err)))
       ) ;_ end of if
    ) ;_ end of foreach
;;; Метка конца отмены
  (vla-endundomark (vla-get-activedocument (vlax-get-acad-object)))
;;; Тихий выход
  (princ)
  ) ;_ end of defun

Теперь все, что выполнялось между строками 30..100, будет отменяться (при необходимости) по одному нажатию Ctrl+Z.

Осталось добавить только самовызов нашего загрузчика: в самом конце добавляем (c:firmloader) - и вуаля, загрузчик готов к использованию!

(vl-load-com)

(defun c:firmloader (/ lm:directoryfiles err)

  (defun lm:directoryfiles (dir typ sub)
                           ;|
 dir - [str] Root directory for which to return filenames
 typ - [str] Optional filetype filter (DOS pattern)
 sub - [bol] If T, subdirectories of the root directory are included
 Returns: [lst] List of files matching the filetype criteria, else nil if none are found
|;
    (setq dir (vl-string-right-trim "\\" (vl-string-translate "/" "\\" dir)))
    (append (mapcar '(lambda (x) (strcat dir "\\" x)) (vl-directory-files dir typ 1))
            (if sub
              (apply 'append
                     (mapcar
                       '(lambda (x)
                          (if (not (wcmatch x "`.,`.`."))
                            (lm:directoryfiles (strcat dir "\\" x) typ sub)
                            ) ;_ end of if
                          ) ;_ end of lambda
                       (vl-directory-files dir nil -1)
                       ) ;_ end of mapcar
                     ) ;_ end of apply
              ) ;_ end of if
            ) ;_ end of append
    ) ;_ end of defun

  (vla-startundomark (vla-get-activedocument (vlax-get-acad-object)))

  ;; Определяем, что грузить
  (if (or (wcmatch (strcase (getvar "cprofile")) "*TEST*") ; в имени профиля содержится "test"
          (wcmatch (strcase (getenv "username"))
                   "GOODENGINEER1,GOODENGINEER2,COOLTESTER" ; имя пользователя одно из этих
                   ) ;_ end of wcmatch
          (not (vl-directory-files "s:\\fas" "*.vlx" 1)) ; Не найдены файлы vlx
          (not (vl-directory-files "s:\\fas" "*.fas" 1)) ; Не найдены файлы fas
          ) ;_ end of or
    (progn
      ;; Будем грузить все из \\lsp
      (foreach file (lm:directoryfiles "s:\\lsp" "*.lsp" t)
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    (progn
      ;; Будем грузить только из \\fas
      (foreach file
                    ;; Получаем все файлы в каталоге s:\\fas и убираем те, у которых
                    ;; расширение не fas и не vlx
                    (vl-remove-if-not
                      (function
                        (lambda (x)
                          (wcmatch (strcase (vl-filename-extension x)) ".FAS,.VLX")
                          ) ;_ end of lambda
                        ) ;_ end of function
                      (lm:directoryfiles "s:\\fas" "*.*" t)
                      ) ;_ end of vl-remove-if-not
        ;; Выполняем загрузку. В случае ошибки выводим соответствующее предупреждение
        ;; в ком.строке
        (load file (strcat "\nError loading file " file))
        ) ;_ end of foreach
      ) ;_ end of progn
    ) ;_ end of if

  (foreach item '(;; Теперь перечисляем вызываемые нами функции в том порядке и с теми аргументами,
                  ;; в котором они должны выполняться. Вызов оформляем без скобок, внутри строк
                  "firm-set-support"
                  "firm-set-sysvar"
                  "firm-set-textstyle"
                  ;; В качестве примера вызова функции с каким-то параметром:
                  ;; "firm-do-something param1 param2"
                  )
    (if   ; 5. Если п.4 породил ошибку - неважно какую...
      (vl-catch-all-error-p
        (setq err ; 4. Оборачиваем п.3 в обработку ошибки
               (vl-catch-all-apply
                 (function
                   (lambda ()
                     ;; 3. Вычисляем п.2:
                     (eval
                       ;; 2. "Читаем" п.1:
                       (read
                         ;; 1. Добавляем скобки в начало и в конец:
                         (strcat "(" item ")")
                         ) ;_ end of read
                       ) ;_ end of eval
                     ) ;_ end of lambda
                   ) ;_ end of function
                 ) ;_ end of vl-catch-all-apply
              ) ;_ end of setq
        ) ;_ end of vl-catch-all-error-p
          ; 5.1 ... то выводим соответствующее предупреждение
       (princ (strcat "\nError call " item " : " (vl-catch-all-error-message err)))
       ) ;_ end of if
    ) ;_ end of foreach
  (vla-endundomark (vla-get-activedocument (vlax-get-acad-object)))
  (princ)
  ) ;_ end of defun

(c:firmloader)

Это всего лишь один из очень и очень многих путей загрузки приложений в AutoCAD.

Попробуем подвести некие итоги:

  1. Разработан lsp-загрузчик, который загружает все lsp-, fas- и (или) vlx-модули, в зависимости от названия профиля AutoCAD и имени пользователя. Загрузчик срабатывает самостоятельно и не требует участия пользователя.
  2. В acaddoc.lsp или mnl-файл внесена строчка, обеспечивающая загрузку предыдущего пункта.

Продолжение следует здесь.

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