Как обновить вхождения блока
Понадобилось мне тут обработать достаточно насыщенный файл dwg, в котором приличное количество блоков с несколькими уровнями вложенности. В процессе обработки приходилось еще и менять стили в текстах, которые были внутри блоков. Да и про атрибуты тоже забывать нельзя. Регенерацию в силу некоторых причин применить было невозможно (и прежде всего оттого, что занимало это дело немеряно времени. Да и тот факт, что в некоторых случаях у меня это дело не срабатывало, тоже сыграл свою роль...).
Казалось бы, решение очевидно: брать описание блока, менять его, потом брать вхождение блока и для него vla-update или entupd.
Здесь я расскажу о том, как я пытался сделать решение и чего из этого получилось.
Ну, чтобы не забивать эфир голословными рассуждениями, попробуем сначала сделать блок, внутрь него поместим один атрибут:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | (vl-load-com) (defun test1 (/ adoc blk_name blk_def) (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))) ) ;_ end of vla-StartUndoMark (setq blk_name "blk1" blk_def (vla-add (vla-get-blocks adoc) (vlax-3d-point '(0. 0. 0.)) blk_name ) ;_ end of vla-add ) ;_ end of setq (vla-addattribute blk_def 3.5 acattributemodenormal "Prompt" (vlax-3d-point '(0. 0. 0.)) "Tag" "Value" ) ;_ end of vla-AddAttribute (vla-endundomark adoc) (princ) ) ;_ end of defun |
Войдя в AutoCAD, выполним (test1) и после этого вставим несколько раз блок blk1:
Вроде бы все хорошо. Теперь напишем функцию, которая в описание блока добавит, например, окружность:
1 2 3 4 5 6 | (defun test2 (name / adoc) (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object)))) (vla-addcircle (vla-item (vla-get-blocks adoc) name) (vlax-3d-point '(0. 0. 0.)) 20.) (vla-endundomark adoc) (princ) ) ;_ end of defun |
Вполне возможно, что после выполнения в окне AutoCAD (а не из консоли VLIDE!) команды (test2 "blk1"), Вы получите полностью неизменную картинку (по крайней мере в AutoCAD 2012 я именно такое и получил). И чего делать?
Правильно, находить все вхождения нужного блока и выполнять для них vla-update или entupd. Вот этим сейчас и займемся.
В ActiveX лично я не нашел функции наподобие "vla-updateallreferences"
Обратимся к описанию блока. Его можно получить штатными tblobjname и tblsearch. Если планируется работа только с текущим документом, этого будет достаточно. Если же нет, придется получать описание блока через ActiveX.
Допустим, что нас интересует только текущий документ. Получаем указатель на описание блока:
1 2 | _$ (tblobjname "block" "blk1") <Entity name: 7ef03528> |
И сразу же подсовываем его под entget:
1 2 | _$ (entget (tblobjname "block" "blk1")) ((-1 . <Entity name: 7ef03528>) (0 . "BLOCK") (330 . <Entity name: 7ef03520>) (5 . "21D") (100 . "AcDbEntity") (67 . 0) (8 . "0") (100 . "AcDbBlockBegin") (70 . 2) (10 0.0 0.0 0.0) (-2 . <Entity name: 7ef03538>) (2 . "blk1") (1 . "")) |
Вот интересно, что хранится в группе 330? DXF Reference на это отвечает, что в этой группе хранится программный ("мягкий") указатель на объект-владелец. Нда? Попробуем его посмотреть:
1 2 | _$ (entget (cdr (assoc 330 (entget (tblobjname "block" "blk1"))))) ((-1 . <Entity name: 7ef03520>) (0 . "BLOCK_RECORD") (330 . <Entity name: 7ef01c08>) (5 . "21C") (100 . "AcDbSymbolTableRecord") (100 . "AcDbBlockTableRecord") (2 . "blk1") (360 . <Entity name: 7ef03528>) (340 . <Entity name: 0>) (102 . "{BLKREFS") (331 . <Entity name: 7ef03540>) (331 . <Entity name: 7ef03558>) (331 . <Entity name: 7ef03570>) (331 . <Entity name: 7ef03588>) (102 . "}") (70 . 0) (280 . 1) (281 . 0)) |
Уже из тупого упрямства не полезу в справку, меня и без этого заинтересовали повторяющиеся 331 группы. Хотя бы тем, что они лежат сразу после группы '(102 . "{BLKREFS"). Ну и их количество точно совпадает с количеством вхождений блоков. BLKREFS = Block References! Опаньки, так это ж я и искал!
Теперь пишем функцию обновления вхождений блоков (пока забудем про слои и Ко):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (defun _lispru-update-references1 (block-name) (if (setq blk_def (tblobjname "block" block-name)) (foreach ref (mapcar (function cdr) (vl-remove-if-not (function (lambda (x) (= (car x) 331) ) ;_ end of lambda ) ;_ end of function (member '(102 . "{BLKREFS") (entget (cdr (assoc 330 (entget blk_def))))) ) ;_ end of vl-remove-if-not ) ;_ end of mapcar (entupd ref) ) ;_ end of foreach ) ;_ end of if ) ;_ end of defun |
Напишем небольшую "обертку" - фактически добавим метки начала и окончания. Ну так, чтобы не мучаться:
1 2 3 4 5 6 | (defun c:update (/ adoc) (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object)))) (_lispru-update-references1 "blk1") (vla-endundomark adoc) (princ) ) ;_ end of defun |
Теперь усложним задачку: нарисуем пару отрезков и объединим их и одно вхождение блока blk1 в блок blk2 (вхождение блока blk2 на рисунке выделено красным прямоугольником):
Теперь добавим в описание blk1 еще одну окружность, но за компанию зададим ей принудительно цвет. Например, желтый:
1 2 3 4 5 6 7 8 9 | (defun test3 (/ adoc blk_ref) (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object)))) (vla-put-color (vla-addcircle (vla-item (vla-get-blocks adoc) "blk1") (vlax-3d-point '(0. 0. 0.)) 10) 2 ) ;_ end of vla-put-color (vla-endundomark adoc) (princ) ) ;_ end of defun |
И последовательно выполняем (test3) и нашу команду update. И получаем весьма интересный результат:
То есть обновились вхождения блоков, которые находятся в текущем пространстве. Вложенные не обработаны. Беда... При этом суть прикола состоит в том, что entupd не обработает блоки, входящие в другие блоки - можете проверить.
А если еще и в описании блока удалить единственный атрибут, то вид файла станет совсем не соответствовать действительности. И картину не исправить ни _.attsync, ни _.battman, ни _.regen, ни _.regenall. Получается, что для полноценного обновления придется:
- Получить имена всех вхождений блоков во все пространства
- Отфильтровать блоки по интересующему имени
- Пройтись по другим блокам на предмет вхождения в них нашего "обновляемого" блока
- Если обновляемый блок присутствует, то получается, что надо обновить вхождение нашего блока в описание этого блока.
- По ходу дела не забыть обновить описания атрибутов для каждого вхождения
- Повторить все эти шаги для "владельцев" вхождений
Если я ничего не упустил, то можно и попробовать начать рисовать код. Уже понятно, что получается рекурсия чистой воды. В качестве параметра примем единственный параметр. Этим параметром (ну так, для универсальности) может быть строковое имя блока, ename- или vla-указатель на вхождение блока, ename- или vla-указатель на описание блока. Для динамических блоков принимаем, что передаваться будет "чистое" имя блока, хранимое в DXF-группе 2 (или получаемое через vla-get-name), а не "полноценное истинное" имя (до которого можно добраться, например, через vla-get-effectivename).
А, нет, все же упустил: надо исключать из обработки внешние ссылки и не забыть обработать блоки на заблокированных и замороженных слоях.
Все, теперь можно будет и голову ломать. Только вот не знаю - стоит ли?
Здравствуйте Алексей! Может я чего не до конца понял в Ваше статье, но у меня вот что получилось.
Итак, на Ваших же примерах, замнил функцию _lispru-update-references1 на _lispru-update-references2:
2
3
4
5
6
7
(if (setq nbr_block_ref (ssget "_X" (list (cons 0 "INSERT"))))
(foreach item (vl-remove-if 'listp (mapcar 'cadr (ssnamex nbr_block_ref)))
(vla-Update (vlax-ename->vla-object item))
)
)
); end _lispru-update-references2
и вхождения блока blk2 обновились как надо (т. е. в них обновились blk1)
Добавив предыдущий пост обнаружил, что не отобразился лисп код для функции _lispru-update-references2, поэтому добавлю здесь
(defun _lispru-update-references2 (block-name / nbr_block_ref)
(if (setq nbr_block_ref (ssget "_X" (list (cons 0 "INSERT"))))
(foreach item (vl-remove-if 'listp (mapcar 'cadr (ssnamex nbr_block_ref)))
(vla-Update (vlax-ename->vla-object item))
)
)
); end _lispru-update-references2
Все дело в том, что подобное не срабатывает, если в блоке есть текстовые примитивы, которым меняется стиль. Или уровень вложенности больше 1.
Я долго ломал голову, как с минимальными потерями выйти из этой ситуации, но простого и изящного решения я не нашел. Пока приходится идти "в лоб"
Алексей, а можно еще вопрос? Можно ли программно (только средствами ActiveX) добавить в блок другой блок, т.е. на примере Вашего же кода:
(defun test1_(/ adoc blk_name blk_def1 blk_def2)
(vla-startundomark
(setq adoc (vla-get-activedocument (vlax-get-acad-object)))
) ;_ end of vla-StartUndoMark
(setq blk_name "blk1"
blk_def1 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
) ;_ end of setq
; --- СОЗДАЛИ ПУСТОЙ БЛОК С ИМЕНЕМ blk1 ---
(vla-addattribute
blk_def1
3.5
acattributemodenormal
"Prompt"
(vlax-3d-point '(0. 0. 0.))
"Tag"
"Value"
) ;_ end of vla-AddAttribute
; --- ДОБАВИЛИ В БЛОК blk1 АТРИБУТ ---
(vla-AddText blk_def1 "Текст"
(vlax-3d-point '(0. -5. 0.))
2
)
; --- ДОБАВИЛИ В БЛОК blk1 ТЕКСТ ---
(setq blk_name "blk2"
blk_def2 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
)
; --- СОЗДАЛИ ПУСТОЙ БЛОК С ИМЕНЕМ blk2 ---
(vla-addcircle blk_def2 (vlax-3d-point '(0. 0. 0.)) 20.)
; --- ДОБАВИЛИ В БЛОК blk2 ОКРУЖНОСТЬ ---
(...) - КАК ДОБАВИТЬ В БЛОК blk2 БЛОК blk1 ??? (ничего типа vla-addblock я не нашел)
(vla-endundomark adoc)
(princ)
)
Надо использовать метод vla-insertblock, и в качестве первого параметра указывать описание "владельца".
Кулик Алексей aka kpblc пишет:
Все дело в том, что подобное не срабатывает, если в блоке есть текстовые примитивы, которым меняется стиль. Или уровень вложенности больше 1.
Я долго ломал голову, как с минимальными потерями выйти из этой ситуации, но простого и изящного решения я не нашел. Пока приходится идти “в лоб”
Алексей и все-таки у меня получается и с текстовыми примитивами и с вложенностью больше 1.
(defun test1_(/ adoc blk_name blk_def1 blk_def2 blk_def3 blk_def4)
(vla-startundomark
(setq adoc (vla-get-activedocument (vlax-get-acad-object)))
) ;_ end of vla-StartUndoMark
(setq blk_name "blk1"
blk_def1 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
) ;_ end of setq
(vla-addattribute
blk_def1
3.5
acattributemodenormal
"Prompt"
(vlax-3d-point '(0. 0. 0.))
"Tag"
"Value"
) ;_ end of vla-AddAttribute
(vla-AddText blk_def1 "Òåêñò"
(vlax-3d-point '(0. -5. 0.))
2
)
(setq blk_name "blk2"
blk_def2 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
)
(vla-addcircle blk_def2 (vlax-3d-point '(0. 0. 0.)) 15.)
(vla-InsertBlock blk_def2 (vlax-3d-point '(0. 0. 0.)) "blk1" 1 1 1 0)
(setq blk_name "blk3"
blk_def3 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
)
(vla-AddLine blk_def3 (vlax-3d-point '(-10. -10. 0.)) (vlax-3d-point '(10. 10. 0.)))
(vla-InsertBlock blk_def3 (vlax-3d-point '(0. 0. 0.)) "blk2" 1 1 1 0)
(setq blk_name "blk4"
blk_def4 (vla-add (vla-get-blocks adoc)
(vlax-3d-point '(0. 0. 0.))
blk_name
) ;_ end of vla-add
)
(vla-AddPolyline blk_def4 (vlax-safearray-fill (vlax-make-safearray vlax-vbDouble '(0 . 8)) (append '(-15. 15. 0.) '(10. 10. 0.) '(10. -10. 0.))))
(vla-InsertBlock blk_def4 (vlax-3d-point '(0. 0. 0.)) "blk3" 1 1 1 0)
; - blk1 находиться в blk4 на 3 уровне вложенности
(vla-endundomark adoc)
(princ)
); end test1_
(defun test3_(/ adoc blk_ref)
(vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))))
(vla-put-color
(vla-addcircle (vla-item (vla-get-blocks adoc) "blk1") (vlax-3d-point '(0. 0. 0.)) 5)
1
) ;_ end of vla-put-color
(vla-put-StyleName (vla-item (vla-item (vla-get-blocks adoc) "blk1") 1) "Standard")
; меняю стиль на Standard (по умолчанию был GOST)
(vla-endundomark adoc)
(princ)
); end test3_
(defun _lispru-update-references2 (block-name / nbr_block_ref)
(if (setq nbr_block_ref (ssget "_X" (list (cons 0 "INSERT"))))
(foreach item (vl-remove-if 'listp (mapcar 'cadr (ssnamex nbr_block_ref)))
(vla-Update (vlax-ename->vla-object item))
)
)
); end _lispru-update-references2
(defun c:update (/ adoc)
(vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))))
(_lispru-update-references2 "blk1")
(vla-endundomark adoc)
(princ)
) ;_ end of defun
Правда у меня ACAD2008 локализованный.
TararykovDG у меня не работает ваш код. Скажите зачем в предпоследней программе (defun _lispru-update-references2 (block-name / nbr_block_ref) используете ввод имени блока block-name?
Кулик Алексей aka kpblc если у Вас есть решение, поделитесь пожалуйста знаниями)
Пока задача повторно не вставала, поэтому и не занимался этой проблемой Сорри.
Сейчас нет никакой возможности выделить время на этот вопрос. Если будут новости - естественно, сообщу.
Господа, а что если действовать по следующему сценарию:
1 - переопределить весь сомн блоков как хочется
2 - последовательно стирать каждое вхождение (insert) любого блока, считав\запомнив параметры этого вхождения. и тут же вставлять только что стертый блок с сохраненными ранее параметрами и в соответствии с текущими\исправленным определением
...
это сильно неправильная идея?
Вопрос в паре моментов: скорость выполнения и не слопаются ли все хендлы. Их-то количество достаточно велико, но не бесконечно.