Все, что описывается ниже, разработано для устойчиво работающей сети, не требующей обязательной синхронизации серверного и локального репозиториев.
Допустим, на каком-то каталоге на сервере (\\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-файл внесена строчка, обеспечивающая загрузку предыдущего пункта.
Продолжение следует здесь.
Исходный код последнего варианта загрузчика можно забрать здесь.

