Все, что описывается ниже, разработано для устойчиво работающей сети, не требующей обязательной синхронизации серверного и локального репозиториев.
Допустим, на каком-то каталоге на сервере (\\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. Конечно, велик соблазн сделать все сразу в нем, но все же ограничим его функционал. Итак, что же он должен сделать?
- Определить, что грузить - lsp или fas/vlx
- Загрузить соответствующие модули
- При необходимости вызвать какие-то функции
- И на этом успокоиться
Неплохо было бы заодно еще и команду загрузки определить, чтобы можно было в любой момент ее повторно вызвать. Иногда полезная штука ;)
Принимаем волевое решение: если имя профиля содержит слово "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.
Попробуем подвести некие итоги:
- Разработан lsp-загрузчик, который загружает все lsp-, fas- и (или) vlx-модули, в зависимости от названия профиля AutoCAD и имени пользователя. Загрузчик срабатывает самостоятельно и не требует участия пользователя.
- В acaddoc.lsp или mnl-файл внесена строчка, обеспечивающая загрузку предыдущего пункта.
Продолжение следует здесь.
Исходный код последнего варианта загрузчика можно забрать здесь.