Как обновить вхождения блока

Понадобилось мне тут обработать достаточно насыщенный файл 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).

А, нет, все же упустил: надо исключать из обработки внешние ссылки и не забыть обработать блоки на заблокированных и замороженных слоях.

Все, теперь можно будет и голову ломать. Только вот не знаю - стоит ли? :)

Размещено в Код LISP, Новости · Метки:



Комментарии

Есть 10 коммент. к “Как обновить вхождения блока”
  1. TararykovDG пишет:

    Здравствуйте Алексей! Может я чего не до конца понял в Ваше статье, но у меня вот что получилось.
    Итак, на Ваших же примерах, замнил функцию _lispru-update-references1 на _lispru-update-references2:

    1
    2
    3
    4
    5
    6
    7
    (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

    и вхождения блока blk2 обновились как надо (т. е. в них обновились blk1)

  2. TararykovDG пишет:

    Добавив предыдущий пост обнаружил, что не отобразился лисп код для функции _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

  3. Кулик Алексей aka kpblc пишет:

    Все дело в том, что подобное не срабатывает, если в блоке есть текстовые примитивы, которым меняется стиль. Или уровень вложенности больше 1.
    Я долго ломал голову, как с минимальными потерями выйти из этой ситуации, но простого и изящного решения я не нашел. Пока приходится идти "в лоб" :(

  4. TararykovDG пишет:

    Алексей, а можно еще вопрос? Можно ли программно (только средствами 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)
    )

  5. Кулик Алексей aka kpblc пишет:

    Надо использовать метод vla-insertblock, и в качестве первого параметра указывать описание "владельца".

  6. TararykovDG пишет:

    Кулик Алексей 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 локализованный.

  7. Дмитрий пишет:

    TararykovDG у меня не работает ваш код. Скажите зачем в предпоследней программе (defun _lispru-update-references2 (block-name / nbr_block_ref) используете ввод имени блока block-name?
    Кулик Алексей aka kpblc если у Вас есть решение, поделитесь пожалуйста знаниями)

  8. Кулик Алексей aka kpblc пишет:

    Пока задача повторно не вставала, поэтому и не занимался этой проблемой :( Сорри.
    Сейчас нет никакой возможности выделить время на этот вопрос. Если будут новости - естественно, сообщу.

  9. Петр-Алекс пишет:

    Господа, а что если действовать по следующему сценарию:
    1 - переопределить весь сомн блоков как хочется
    2 - последовательно стирать каждое вхождение (insert) любого блока, считав\запомнив параметры этого вхождения. и тут же вставлять только что стертый блок с сохраненными ранее параметрами и в соответствии с текущими\исправленным определением
    ...
    это сильно неправильная идея?

  10. Кулик Алексей aka kpblc пишет:

    Вопрос в паре моментов: скорость выполнения и не слопаются ли все хендлы. Их-то количество достаточно велико, но не бесконечно.

Поделитесь своим мнением


Я не робот.