Работа с неактивным документом
Подавляющее большинство лисп-функций, показываемых на форумах и сайтах, работают с текущим документом. Как правило, этого достаточно. Но что делать, если надо обрабатывать несколько документов? Здесь я хотел бы рассмотреть некоторые вопросы, связанные именно с обработкой неактивного документа.
В текущем (активном) документе программисту доступны все возможности lisp'a - это и командные методы, и интерактивное формирование наборов примитивов, и ename-методы, и activex. Но стоит только заикнуться об обработке неактивного документа, как перечень возможных средств моментально сужается до состояния "ActiveX и практически больше ничего".
Прежде чем приступать к дальнейшим рассуждениям, напомню, что AutoCAD должен быть переведен в многодокументный режим (системная переменная SDI равна 0).
Первое желание - написать scr-файл (возможно, программно) по алгоритму: открыть файл, загрузить лисп, выполнить лисп, сохранить файл, открыть следующий и т.д.; и запустить его на выполнение. Чем плох подобный подход? Ну, хотя бы тем, что обрабатываемые файлы могут быть очень тяжелыми, времени на их открытие и регенерацию может уходить немеряно. Внутри файлов могут встретиться, например, прокси-объекты. Или будет запрос на поиск shx-файла (о том, как от этого запроса избавиться, поговорим позже). Или еще чего-то... Короче, придется сидеть рядом с компьютером и ждать, пока он не закончит выполнение скрипта. Не, это скучно.
Второе движение - выполнить программное открытие нужного dwg-файла, обработать его и закрыть. Все хорошо, да только вот проблема - такой подход практически никогда не работает. Открытие файла (хоть через команду _.open с установленной filedia = 0, хоть через vla-open) почти гарантированно приводит к активации только что открытого файла. Почему "почти"? Потому, что в некоторых условиях такого не происходит. Насколько я помню, этого не происходило в AutoCAD 2005 (чистом AutoCAD, а не входящем в состав, например, ADT). Но требуется-то разработать функцию, которая будет гарантированно корректно работать в любых условиях! Поэтому такой вариант тоже не годится.
Третий вариант - залезьть достаточно глубоко в дебри AutoCAD и открывать файл dwg без активизации. На нем я и хотел бы остановиться.
Скажу честно - идея не совсем моя, я только дорабатывал ее. Лично я считаю авторами и реализаторами идеи двух человек - Н.Н.Полещука и Fatty (он же fixo, он же Олег jr.). Суть метода состоит в том, чтобы обратиться к интерфейсу IAxDbDocument (или ObjectDBX) и с его помощью уже и обрабатывать dwg.
ObjectDBX появился не так уж и давно, и его регистрация в начале его существования отличалась от той, что используется в версиях последнего времени.
Поскольку вызов ObjectDBX напрямую зависит от версии AutoCAD, сначала определим версию:
1 2 3 4 5 6 7 | (defun _lispru-acad-version () ;| * Возвращает номер сборки AutoCAD'a. Для 2005 вернет 16.1, для 2006 - 16.2 * и т.д. |; (atof (getvar "acadver")) ) ;_ end of defun |
После получения версии запущенного AutoCAD можно уже и пытаться вызвать ObjectDBX:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | (defun _lispru-odbx (/) ;| * функция возвращает интерфейс IAxDbDocument (для работы с файлами DWG без * их открытия). Если интерфейс не поддерживается, возвращает nil. Проверено * на ACAD 2002, 2004, 2005, 2006, 2007, 2008, 2010 * Автор - Fatty aka Олег jr. Моего только адаптация под общую систему и * переименование * Параметры вызова: * нет * Примеры вызова: (_lispru-odbx) |; (cond ((< (_lispru-acad-version) 15.06) (alert "ObjectDBX method not applicable\nin this AutoCAD version" ) ;_ end of KPBLC-MSG-ALERT nil ) ((= (fix (_lispru-acad-version)) 15) (if (not (vl-registry-read "HKEY_CLASSES_ROOT\\ObjectDBX.AxDbDocument\\CLSID" ) ;_ end of vl-registry-read ) ;_ end of not (startapp "regsvr32.exe" (strcat "/s "" (findfile "axdb15.dll") """) ) ;_ end of startapp ) ;_ end of if (vla-getinterfaceobject (vlax-get-acad-object) "ObjectDBX.AxDbDocument" ) ;_ end of vla-getinterfaceobject ) (t (vla-getinterfaceobject (vlax-get-acad-object) (strcat "ObjectDBX.AxDbDocument." (itoa (fix (_lispru-acad-version)))) ) ;_ end of vla-getinterfaceobject ) ) ;_ end of cond ) ;_ end of defun |
Ну хорошо, сам интерфейс получен. А дальше-то что? А дальше вот что:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | _$ (setq odbx (_lispru-odbx)) #<VLA-OBJECT IAxDbDocument 000000003bcdea60> _$ (vlax-dump-Object odbx t) ; IAxDbDocument: IAxDbDocument Interface ; Property values: ; Application (RO) = Ошибка ; Blocks (RO) = #<VLA-OBJECT IAcadBlocks 000000003bd3d6e8> ; Database (RO) = #<VLA-OBJECT IAcadDatabase 000000003bb04488> ; Dictionaries (RO) = #<VLA-OBJECT IAcadDictionaries 000000003bd3d928> ; DimStyles (RO) = #<VLA-OBJECT IAcadDimStyles 000000003bd3d9b8> ; ElevationModelSpace = 0.0 ; ElevationPaperSpace = 0.0 ; FileDependencies (RO) = #<VLA-OBJECT IAcadFileDependencies 000000003bdf6878> ; Groups (RO) = #<VLA-OBJECT IAcadGroups 000000003bd3da48> ; Layers (RO) = #<VLA-OBJECT IAcadLayers 000000003bd3dad8> ; Layouts (RO) = #<VLA-OBJECT IAcadLayouts 000000003bd3db68> ; Limits = (0.0 0.0 12.0 9.0) ; Linetypes (RO) = #<VLA-OBJECT IAcadLineTypes 000000003bd3dbf8> ; Materials (RO) = #<VLA-OBJECT IAcadMaterials 000000003bd3dc88> ; ModelSpace (RO) = #<VLA-OBJECT IAcadModelSpace 000000003bb13408> ; Name = "" ; PaperSpace (RO) = #<VLA-OBJECT IAcadPaperSpace 000000003bb134a8> ; PlotConfigurations (RO) = #<VLA-OBJECT IAcadPlotConfigurations 000000003bd3dd18> ; Preferences (RO) = #<VLA-OBJECT IAcadDatabasePreferences 000000003bdf6838> ; RegisteredApplications (RO) = #<VLA-OBJECT IAcadRegisteredApplications 000000003bd3dda8> ; SectionManager (RO) = Ошибка ; SummaryInfo (RO) = #<VLA-OBJECT IAcadSummaryInfo 000000003bce2f18> ; TextStyles (RO) = #<VLA-OBJECT IAcadTextStyles 000000003bd3de38> ; UserCoordinateSystems (RO) = #<VLA-OBJECT IAcadUCSs 000000003bd3dec8> ; Viewports (RO) = #<VLA-OBJECT IAcadViewports 000000003bd3df58> ; Views (RO) = #<VLA-OBJECT IAcadViews 000000003bd3dfe8> ; Methods supported: ; CopyObjects (3) ; DxfIn (2) ; DxfOut (3) ; HandleToObject (1) ; ObjectIdToObject (1) ; ObjectIdToObject32 (1) ; Open (2) ; Save () ; SaveAs (2) T _$ |
На данный момент наиболее интересны методы Open и SaveAs (а не Save, как это ни парадоксально). Пришло время поговорить о том, как работает ObjectDBX, какие у него ограничения и как их обходить.
Вопрос с открытием файла можно даже не поднимать:
1 | (vla-open odbx filename) |
А вот с обработкой и сохранением уже не все так очевидно.
- Не допускается применение интерактивных и командных методов. Только vla и ename (да и то, последнее только в случае крайней необходимости).
- ObjectDBX при открытии файла блокирует его. Доступ в файлу возможен только при условии монопольного к нему доступа. Если кто-то этот файл уже открыл (например, сосед), то ObjectDBX этот файл не то что не обработает, а даже не откроет.
- Несмотря на заявленную поддержку метода Save, этот метод не работает, приходится использовать SaveAs.
- Сохранение файла возможно только в текущей версии, без вариантов.
- Файл можно закрыть и без сохранения изменений. Для этого достаточно выполнить (vlax-release-object) к указателю на интерфейс.
- Для файла, открытого через ObjectDBX, невозможно добраться до состава внешних ссылок. С внешними ссылками внутри ObjectDBX вообще надо быть очень осторожными, постоянно проверяя тип ссылки - вставленная она или наложенная (бывают случаи, когда перечисляются все, независимо от уровня вложенности).
- С получением путей вставленных растров тоже могут быть проблемы.
- В файле, открытом через ObjectDBX, невозможно выполнить проверку целостности: vla-auditinfo работает только в текущем документе.
Несмотря на эти ограничения (кстати, возможно, я перечислил не все!), механизм достаточно удобен, особенно при аккуратном с ним обращении: можно получить список описаний блоков стороннего документа, а потом через CopyObjects скопировать их в текущий. Или наоборот. Можно привести сотни файлов в стандартный вид, не особо беспокоясь о потерях времени: при открытии файла до 95% времени тратится на его регенерацию. Можно... В общем, сделать можно много.
Осталось только одно: запомнить, что сохраняется файл только в текущей версии и только через метод SaveAs:
1 | (vla-saveas odbx FileName) |
Естественно, что, имея один указатель на ObjectDBX, можно открывать далеко не один и не два файла (хотя лично я такого не делаю. Я предпочту мириться с потерями памяти, но не с потерями работы: в версии 2005 и 2006, по-моему, использование одного указателя иногда приводило к странным зависаниям. То есть сейчас ситуация у меня такова: создал указатель - открыл файл - обработал файл - сохранил файл - "отрелизил" указатель - обнулил указатель и все сначала). В конце всеобщей обработки надо не забыть выполнить vlax-release-object, ну и, для гарантии, обнулить переменную:
1 | (setq odbx nil) |
Примеры использования, я думаю, будут. Но позже.
Ссылки для скачивания:
_lispru-acad-version.lsp
_lispru-odbx.lsp
Здесь описаны некоторые обнаруженные ограничения ObjectDBX.
Здравствуйте Алексей, подскажите пожалуйста, а возможно ли используя интерфейс IAxDbDocument (или ObjectDBX) при работе с неактивным документом, создать в этом документе какой-либо примитив. Если возможно, то подскажите как. У меня лично не получилось.
(vla-AddCircle odbx (vlax-3D-point ‘(0 0 0)) 100)
; Ошибка: ActiveX Server возвратил ошибку: неизвестное имя: AddCircle
Если посмотреть приведенный вами список доступных методов и свойств, полученных следующим образом:
(vlax-dump-Object odbx t),
То там есть всего лишь 8 доступных методов, и ни один из них не позволяет создать примитив в неактивном документе.
Все верно. Сравните:
(vla-AddCircle odbx (vlax-3D-point '(0 0 0)) 100)
и
(vla-AddCircle (vla-get-ModelSpace odbx) (vlax-3D-point '(0 0 0)) 100)
Примитив не может быть создан в документе. Создаваемый примитив располагается в каком-то пространстве - модели, листа, блока...
Кстати о примерах. Вот, не далее как сегодня нарисовалось: https://docs.google.com/Doc?docid=0AXNsAeY1XnTrZGY3cGJtcTlfNjNodG12ZHRkOQ&hl=ru
Сегодня пришлось еще несколько времени потратить на использование ObjectDBX
Несложно выяснить, что у ObjectDBX есть два интересных метода - DxfIn и DxfOut. С первым пока не воевал, а вот второй оказался, как бы сказать... В общем, это далеко не то же, что и SaveAs для активного документа
DxfOut действительно создает dxf-файл, но вот формат этого dxf не выбрать и не назначить. Сохраняется только в "нативной" версии. Так что для нестандартных ситуаций, похоже, придется изобретать нестандартные решения...
Алексей, большое спасибо! Наконец и мне пригодилось!
Да не за что Я тут тоже с такой задачкой столкнулся - мама не горюй. Если появится гарантированно работающее решение, поделюсь
Хотелось бы сделать небольшое дополнение.
Касается это возможности "открытия через ObjectDBX заблокированного файла". На данный момент я через vl-file-systime проверяю доступность файла на открытие (функция возвращает nil, если файл кем-то открыт). Если файл недоступен, то я его копирую в %temp% и открываю уже оттуда.
Не очень изящно, зато работает. Пока, по крайней мере...
Приветствую, Алексей.
Не подскажешь как правильно вызвать vla-ZoomExtents после создания объекта в неактивном документе
(vla-ZoomExtents (vlax-get-acad-object)) - применился только в текущем открытом файле
(vla-zoomextents (vla-get-application (vla-get-activedocument(vlax-get-acad-object)))) - применился только в текущем открытом файле
(vla-ZoomExtents (vla-get-ModelSpace odbx)) ; = ActiveX Server returned the error: unknown name: ZoomExtents[cc lang="lisp"]
По-моему, в неактивном документе это не сделать. Точно так же, как и не выполнить в неактивном документе vla-purgeall.
А зачем зуммировать неактивный документ?
Проектировщики захотели автоматически обработывать dwg файлы, полученные из Ревита, для передачи смежникам, работающих в Автокаде.
1.перекрасить все слои в нейтрально серый цвет
2.переключиться в лист (layout)
3.отключить все слои для каждого экрана (vplayer/freeze)
4.вписать в чертеж в границы экрана (почему то приходя из Ревита чертеж в границы экрана не вписан)
Lee Mac тоже пишет "... ZoomExtents method will only operate on the active drawing ..."
https://www.theswamp.org/index.php?topic=47698.msg527066#msg527066 Reply #1
похоже решать при помощи скрипта как в Reply #4
>> Перекрасить все слои...
Не проблема
>> переключиться в лист (layout)
Переключиться или активировать, чтобы он при открытии был на экране? И если переключиться, то на фига? Через vla-get-block и так можно добраться до описания блока листа и работать с ним как с обычным блоком
>> отключить все слои для каждого экрана (vplayer/freeze)
Не уверен, что подобное прокатит, но тем не менее: пройтись по всем примитивам описания блока листа, найти ВЭ, получить на них указатели и дальше с ними химичить.
>> вписать в чертеж в границы экрана (почему то приходя из Ревита чертеж в границы экрана не вписан)
В чертеж или в ВЭ? Разница немного принципиальная. Если второе (и ВЭ уже существует) - то надо смотреть его масштаб, менять координаты точки, на которую он указывает и т.д.
Поскольку передо мной подобных задач никогда не ставилось, готовых решений у меня нет
Алексей, спасибо за ответы!
С перекраской слоев я уже справился
С темой работы с неактивным документом столкнулся впервые.
Мне показалось, что это более правильный подход (чем через скрипты) если есть необходимость обработки множества чертежей.
Постановку задачи буду еще уточнять...
Алексей, вот хотелось бы уточнить, можно ли в неактивном документе использовать vl-cmdf? И если можно, то какая структура написания команды будет? Если я правильно понимаю, то (vl-cmdf "_НазваниеКоманды") - такая структура вызова команды будет работать только в текущем чертеже?
Все правильно понимаете, vl-cmdf будет работать только в активном документе. Не думаю, что сработает даже конструкция типа vla-SendCommand
На всякий случай проверила vla-SendCommand - не работает.
Добрый день.
Нужно получить доступ не к файлу .dwg, а к файлу .dxf.
Попытки сделать что-то вроде
(vla-open ObjectDBX "...\\123.dxf")
выдают ошибку.
Хотел попробовать метод
DxfIn (2)
Но LISP не понимает синтаксис (vla-DxfIn ...
Информации о вызове данного метода в Autodesk AutoCAD 2015 : ActiveX Reference Guide я не нашёл.
На других сайтах нашёл синтаксис только для C++.
В общем - я в тупике. Как достать и обработать информацию из .dxf ?
Евгений, ну если не работает метод "в лоб", пойдем в обход:
2
3
4
5
(vlax-invoke-method odbx 'dxfin "d:\\drawing1.dxf")
(vlax-dump-object (vla-get-modelspace odbx))
;;; ...
(_kpblc-odbx-close odbx)
Добрый день, может тут мне подскажут. Есть много файлов. Нужно в них поотключать определенный слой. Как это можно используя или не используя данную фичу. Нашел на англоязычных сайтах кое какое решение от Lee Mac https://www.cadtutor.net/forum/topic/30428-turn-off-layers-on-a-drawing/ но на моем 2020м автокаде не запускается
Ну так и в чем проблема? Открыть документ через ObjectDBX, проверить наличие слоя, поменять включенность, сохранить документ.