Как программно сменить файл основного меню?

Автор Тема: Как программно сменить файл основного меню?  (Прочитано 8147 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
В силу разных причин приходится для разных версий и профилей AutoCAD иметь разные по расположению файлы основного меню. Смену основного файла меню выполняю при старте AutoCAD, только один раз. Код выдран с мясом из рабочей копии:
Код - Auto/Visual Lisp [Выбрать]
  1. (vl-load-com)
  2.  
  3. (setq *kpblc-acad* (vlax-get-acad-object)
  4.       *kpblc-adoc* (vla-get-activedocument (vlax-get-acad-object))
  5.       ) ;_ end of setq
  6.  
  7. (defun test (/ main_menu_file path)
  8.   (setq path           (vl-string-right-trim
  9.                          "\\"
  10.                          (_kpblc-dir-create
  11.                            (strcat (vl-string-right-trim "\\" (getenv "AppData"))
  12.                                    "\\kpblc\\AutoCAD\\"
  13.                                    (_kpblc-acad-version-with-bit-and-loc)
  14.                                    "\\"
  15.                                    (_kpblc-get-profile-name)
  16.                                    ) ;_ end of strcat
  17.                            ) ;_ end of _kpblc-dir-create
  18.                          ) ;_ end of vl-string-right-trim
  19.         main_menu_file (cond ((vl-directory-files
  20.                                 (vl-filename-directory (vla-get-menufile (vla-get-files (vla-get-preferences *kpblc-acad*))))
  21.                                 (strcat (vl-filename-base (vla-get-menufile (vla-get-files (vla-get-preferences *kpblc-acad*))))
  22.                                         ".*"
  23.                                         ) ;_ end of strcat
  24.                                 ) ;_ end of vl-directory-files
  25.                               (vl-filename-base (vla-get-menufile (vla-get-files (vla-get-preferences *kpblc-acad*))))
  26.                               )
  27.                              ((setq main_menu_file
  28.                                      (findfile
  29.                                        (strcat (vl-filename-base (vla-get-menufile (vla-get-files (vla-get-preferences *kpblc-acad*))))
  30.                                                (if (< (_kpblc-acad-version) 18.)
  31.                                                  ".cui"
  32.                                                  ".cuix"
  33.                                                  ) ;_ end of if
  34.                                                ) ;_ end of strcat
  35.                                        ) ;_ end of findfile
  36.                                     ) ;_ end of setq
  37.                               (_kpblc-cmd-silence (list "_.menu" main_menu_file))
  38.                               (vl-filename-base (vla-get-menufile (vla-get-files (vla-get-preferences *kpblc-acad*))))
  39.                               )
  40.                              ) ;_ end of cond
  41.         main_menu_file (car
  42.                          (vl-remove-if-not
  43.                            (function (lambda (x)
  44.                                        (= (strcase (vl-filename-base (cdr (assoc "menufilename" x)))) (strcase main_menu_file))
  45.                                        ) ;_ end of lambda
  46.                                      ) ;_ end of function
  47.                            (mapcar (function (lambda (x)
  48.                                                (mapcar (function (lambda (pr) (cons (strcase pr t) (vlax-get-property x pr))))
  49.                                                        '("name" "menufilename")
  50.                                                        ) ;_ end of mapcar
  51.                                                ) ;_ end of lambda
  52.                                              ) ;_ end of function
  53.                                    (_kpblc-conv-vla-to-list (vla-get-menugroups *kpblc-acad*))
  54.                                    ) ;_ end of mapcar
  55.                            ) ;_ end of vl-remove-if-not
  56.                          ) ;_ end of car
  57.         ) ;_ end of setq
  58.   (if (/= (strcase
  59.             (vl-string-right-trim "\\" (vl-filename-directory (cdr (assoc "menufilename" main_menu_file))))
  60.             ) ;_ end of strcase
  61.           (strcase (vl-string-right-trim "\\" path))
  62.           ) ;_ end of /=
  63.     (progn ;; Здесь код упростил, тупо выполняю копирование файла основного меню
  64.            (foreach file (vl-directory-files
  65.                            path
  66.                            (strcat (vl-filename-base (cdr (assoc "menufilename" main_menu_file))) ".*")
  67.                            ) ;_ end of vl-directory-files
  68.              (vl-file-delete (strcat (_kpblc-dir-path-and-splash (_kpblc-get-path-local "menu")) file))
  69.              ) ;_ end of foreach
  70.            (foreach file (vl-remove-if
  71.                            (function
  72.                              (lambda (x)
  73.                                (or (wcmatch (strcase x t) "*.bak.*,*._*") (not (wcmatch (strcase x t) "*.cui*,*.mnl,*.dll")))
  74.                                ) ;_ end of lambda
  75.                              ) ;_ end of function
  76.                            (vl-directory-files
  77.                              (vl-filename-directory (cdr (assoc "menufilename" main_menu_file)))
  78.                              (strcat (vl-filename-base (cdr (assoc "menufilename" main_menu_file))) ".*")
  79.                              1
  80.                              ) ;_ end of vl-directory-files
  81.                            ) ;_ end of vl-remove-if
  82.              (vl-file-copy
  83.                (strcat (vl-string-right-trim "\\" (vl-filename-directory (cdr (assoc "menufilename" main_menu_file))))
  84.                        "\\"
  85.                        file
  86.                        ) ;_ end of strcat
  87.                (strcat path "\\" file)
  88.                ) ;_ end of vl-file-copy
  89.              ) ;_ end of foreach
  90.            ;; И теперь выполняем установку нового файла меню.
  91.            ;; Вариант 1: выполнение прекращается с фатальной ошибкой
  92.            ;|
  93.            (vla-put-menufile
  94.              (vla-get-preferences *kpblc-acad*)
  95.              (strcat path "\\" (vl-filename-base (cdr (assoc "menufilename" main_menu_file))))
  96.              ) ;_ end of vla-put-menufile
  97.              |;
  98.            ;; Вариант 2: выполнение приводит к ошибке ядра (0x000000c5, кажется)
  99.            (vl-cmdf "_.menu"
  100.                     (strcat path
  101.                             "\\"
  102.                             (vl-filename-base (cdr (assoc "menufilename" main_menu_file)))
  103.                             (if (< (atof (getvar "acadver")) 18.)
  104.                               ".cui"
  105.                               ".cuix"
  106.                               ) ;_ end of if
  107.                             ) ;_ end of strcat
  108.                     ) ;_ end of vl-cmdf
  109.            ) ;_ end of progn
  110.     ) ;_ end of if
  111.   ) ;_ end of defun
  112. (defun _kpblc-acad-version-with-bit ()
  113.   (strcat (itoa (atoi (vl-string-trim "VISUALP " (strcase (ver)))))
  114.           "x"
  115.           (if (and (getvar "platform") (wcmatch (strcase (getvar "platform")) "*X64*"))
  116.             "64"
  117.             "32"
  118.             ) ;_ end of if
  119.           ) ;_ end of strcat
  120.   ) ;_ end of defun
  121. (defun _kpblc-acad-version-with-bit-and-loc ()
  122.   (strcat (_kpblc-acad-version-with-bit)
  123.           "-"
  124.           (vl-registry-read (strcat "HKEY_LOCAL_MACHINE\\" (vlax-product-key)) "LocaleID")
  125.           ) ;_ end of strcat
  126.   ) ;_ end of defun
  127. (defun _kpblc-conv-vla-to-list (value / res)
  128.   (cond ((listp value) (mapcar (function _kpblc-conv-vla-to-list) value))
  129.         ((= (type value) 'variant) (_kpblc-conv-vla-to-list (vlax-variant-value value)))
  130.         ((= (type value) 'safearray)
  131.          (if (>= (vlax-safearray-get-u-bound value 1) 0)
  132.            (_kpblc-conv-vla-to-list (vlax-safearray->list value))
  133.            ) ;_ end of if
  134.          )
  135.         ((and (member (type value) (list 'ename 'str 'vla-object))
  136.               (= (type (_kpblc-conv-ent-to-vla value)) 'vla-object)
  137.               (vlax-property-available-p (_kpblc-conv-ent-to-vla value) 'count)
  138.               ) ;_ end of and
  139.          (vlax-for sub (_kpblc-conv-ent-to-vla value) (setq res (cons sub res)))
  140.          )
  141.         (t value)
  142.         ) ;_ end of cond
  143.   ) ;_ end of defun
  144. (defun _kpblc-conv-ent-to-ename (ent_value / _lst)
  145.   (cond ((= (type ent_value) 'vla-object) (vlax-vla-object->ename ent_value))
  146.         ((= (type ent_value) 'ename) ent_value)
  147.         ((and (= (type ent_value) 'str) (handent ent_value) (entget (handent ent_value)))
  148.          (handent ent_value)
  149.          )
  150.         ((and (= (type ent_value) 'str) (handent ent_value) (tblobjname "style" ent_value))
  151.          (tblobjname "style" ent_value)
  152.          )
  153.         ((and (= (type ent_value) 'str) (handent ent_value) (tblobjname "dimstyle" ent_value))
  154.          (tblobjname "dimstyle" ent_value)
  155.          )
  156.         ((and (= (type ent_value) 'str) (handent ent_value) (tblobjname "block" ent_value))
  157.          (tblobjname "block" ent_value)
  158.          )
  159.         ((and (= (type ent_value) 'list) (cdr (assoc -1 ent_value))) (cdr (assoc -1 ent_value)))
  160.         (t nil)
  161.         ) ;_ end of cond
  162.   ) ;_ end of defun
  163. (defun _kpblc-conv-ent-to-vla (ent_value / res)
  164.   (cond ((= (type ent_value) 'vla-object) ent_value)
  165.         ((= (type ent_value) 'ename) (vlax-ename->vla-object ent_value))
  166.         ((setq res (_kpblc-conv-ent-to-ename ent_value)) (vlax-ename->vla-object res))
  167.         ) ;_ end of cond
  168.   ) ;_ end of defun
  169. (defun _kpblc-get-profile-name ()
  170.   (vl-list->string
  171.     (vl-remove-if-not
  172.       (function
  173.         (lambda (x) (or (<= 48 x 57) (<= 65 x 90) (<= 97 x 122) (= x 32) (<= 224 x 255) (<= 192 x 223)))
  174.         ) ;_ end of function
  175.       (vl-string->list (getvar "cprofile"))
  176.       ) ;_ end of vl-remove-if
  177.     ) ;_ end of VL-LIST->STRING
  178.   ) ;_ end of defun
  179. (defun _kpblc-dir-create (path / tmp)
  180.   (cond ((vl-file-directory-p path) path)
  181.         ((setq tmp (_kpblc-dir-create (vl-filename-directory path)))
  182.          (vl-mkdir (strcat tmp
  183.                            "\\"
  184.                            (vl-filename-base path)
  185.                            (cond ((vl-filename-extension path))
  186.                                  (t "")
  187.                                  ) ;_ end of cond
  188.                            ) ;_ end of strcat
  189.                    ) ;_ end of vl-mkdir
  190.          (if (vl-file-directory-p path)
  191.            path
  192.            ) ;_ end of if
  193.          )
  194.         ) ;_ end of cond
  195.   ) ;_ end of defun
Частичные меню можно (на данный момент) игнорировать. Пути поиска устанавливаются в другом месте и гарантированно содержат новый путь к основному меню. Проблемные места выделены в функции test, в комментариях со словами "Вариант ".
Если вручную выполнить команду _,menu, то все срабатывает корректно, ошибка не появляется. Проблему необходимо решить для AutoCAD 2009 и 2013, а также вертикалок (Map3D, Civil), независимо от локализации и разрядности. Собственно вопрос: где и что я не учел? Что можно сделать, чтобы смена основного файла меню не приводила ни к фатальным ошибкам, ни (в идеале) не требовала перезапуска AutoCAD?
Спасибо.
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Если не работает (vl-cmdf), то пробуй (command) и/или (vla-sendcomand)
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
command приводит к тому же результату, что и vl-cmdf. Завтра попробую vla-sendcommand, спасибо
P.S. Вот сейчас не помню, там можно подавить вывод в ком.строку для vla-sendcommand или нет? ;)
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
P.S. Вот сейчас не помню, там можно подавить вывод в ком.строку для vla-sendcommand или нет? ;)
Нет. Но вообще то, что ты пытаешься делать я бы делать внутри AutoCAD не рискнул бы. Во всяком случае из lisp. Так как при выполнении команды menu грузятся еще связанные mnl-файлы, что может как раз и приводить к аварийному завершению.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
Судя по всему, можно как-то обойти через .NET ? :)
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Если я правильно понял по твоему коду, то файл меню ты копируешь, сохраняя его имя. Удали на всякий случай соответствующий mnr-файл.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
В обоих каталогах? Попробую, спасибо. Но это уже точно завтра )
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Судя по всему, можно как-то обойти через .NET ? :)
Желательно из внешнего приложения просто исправить реестр и скопировать файл меню.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
А еще обрати внимание на MenuGroups.Load (ну и его эквивалент в lisp) и MenuGroup.Unload
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Полагаю, что задачу можно решить иным, на мой взгляд более удобным способом: ты можешь попробовать один раз "ручками" полностью очистить acad.cui\acad.cuix файл (это занимает пару-тройку минут), разнеся его текущее содержимое по нужному тебе набору частичных меню (partial CUI\CUIX). Затем, в процессе загрузки AutoCAD, ты сможешь формировать содержимое основного файла меню программно, через динамическое подключение\отключение интересующих тебя наборов частичных файлов меню. В этом случае изменения будут применяться к текущему интерфейсу сразу же и перезагрузка акада не потребуется.

Во всяком случае с динамической (программной) загрузкой\выгрузкой частичных меню и обновлением экрана в соответствии с произведёнными изменениями у меня проблем не возникало. Правда я не пробовал полностью очищать основной файл меню - может акад не даст убрать всё - это пробовать нужно... Да и делал я это на .NET, а не на Auto\VisualLISP.

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Любопытства ради проверил - всё удаляется без проблем (проверял на AutoCAD 2009). И нужно после очистки файла основного меню не забыть удалить файлы acad.mnr и acetmain.mnr.

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
Если коротко, выяснилось следующее:
Прежде всего не забыть поменять пути поиска AutoCAD (у меня-то это в реальной версии делается, здесь просто не приводил). Дальше, потребуется копирование не только cui/cuix, то и mnl и dll (если, конечно, они существуют). Конструкция типа (vla-load FllCuiName :vlax-true) срабатывает корректно. Но в некоторых случаях сразу после ее применения необходимо выполнить (princ), чтобы AutoCAD "подхватил" изменения. После установки основного файла меню почти гарантированно потребуется установить рабочее пространство.
Ошибка выполнения возникала в строке обращения к внедокументной переменной через vl-bb-set - вот уж чего не ожидал...
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Чем не устроил вариант #9? На мой взгляд - самый простой и надёжный.

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
Дело в том, что у меня на работе жуткий зоопарк софта - ACAD2009Eng/Rus, AA2009Eng/Rus, C3D2013, Map3D2013, ACAD2013. Что тут будет через месяц - не представляю. А решение хотелось сделать один раз и навсегда. Пока что работает - посмотрю, что будет дальше. Может быть, твой вариант и стану использовать.
В любом случае - спасибо за идеи! ))
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Я свой код с обозначенным мною вариантом проверял в AutoCAD 2009-2017 Enu и AutoCAD 2013-2017 Rus.

Оффлайн Алексей КуликАвтор темы

  • Administrator
  • *****
  • Сообщений: 1096
  • Карма: 172
Добавь сюда вертикалки - и жить станет значительно интереснее :)
Все, что сказано - личное мнение.

Правила форума существуют не просто так!

Приводя в сообщении код, не забывайте про его форматирование!

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Базовый API один и тот же. :)

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
При помощи COM API можно выгрузить все загруженные в AutoCAD меню, в т.ч. и основное меню. Для этого через MenuGroups нужно выполнить итерацию по каждому объекту MenuGroup, вызывая его экземплярный метод Unload. Основное меню акада так же присутствует в этой коллекции в виде обычного элемента MenuGroup.

Однако после выгрузки всех меню, перерисовка основного окна акада не происходит автоматом. Если ты завершишь работу акада и снова запустишь, то увидишь, что менюшки полностью отсутствуют, однако конечно же перегружать акад для регенерации основного окна автокада - это не "кошерно"...

В своих расширениях я проделывал такой фокус: изменял значение системной переменной, указывающей имя текущего воркспейса, указывая ей в качестве нового значения текущее же значение этой же переменной. Этот фокус проходил на "ура" - происходила перерисовка всего экрана и программно подгруженные мною файлы частичного меню успешно отображались тут же.

Однако такой способ регенерации не пройдёт в случае выгрузки из акада сразу всех меню, т.к. в этом случае и воркспейсов текущих так же не существует, ибо они живут в контексте выгруженных менюшек...

Если кто-нить знает, как в подобной ситуации вызвать полную перерисовку основного окна приложения, то с интересом и сам почитаю. :)

НО!
Если после выгрузки всех менюшек, затем загрузить набор необходимых (через MenuGroups.Load), то воркспейсы появятся (если они определены в составе подгруженных меню) и обозначенный мною выше финт ушами (по теме регенерации) можен снова пригодиться. :)