AutoLISP : filtre de sélection pour blocs dynamiques

Retrouver les insertions d'un bloc dynamique peut se révéler plus compliqué que prévu. Si par exemple vous avez un bloc nommé Porte - Métrique (exemple de bloc dynamique fourni avec AutoCAD dans la palette d'outils, onglet Architecture), le code suivant :

(ssget "X" '((0 . "INSERT") (2 . "Porte - Métrique")))

Vous retournera un jeu de sélection contenant uniquement les insertions du bloc dynamique Porte - Métrique qui ont les propriétés par défaut (bloc inséré sans modification ou insertion réinitialisée avec l'option Réinitialiser le bloc du menu contextuel). Le problème, c'est que dès qu'on modifie une insertion de bloc dynamique, une nouvelle définition de bloc anonyme (dont le nom commence par *U) est créé. On peut s'en rendre compte en utilisant la commande LISTE sur un bloc anonyme.

Commande: LISTE
Choix des objets: 1 trouvé(s)
Choix des objets:
                  REFERENCE DE BLOC  Calque: "0"
                            Espace: Espace objet
                   Maintien = 105
      Nom du bloc: "Porte - Métrique"
      Nom anonyme: "*U4"
                en point, X=-196.9295  Y= 541.8606  Z=   0.0000
Facteur d'échelle X:    1.0000
Facteur d'échelle Y:    1.0000
Angle de rotation:      0
Facteur d'échelle Z:    1.0000
        UnitésIns: Millimètres
Conversion d'unités:    1.0000
Mettre à l'échelle uniformément: Non
Autoriser la décomposition: Oui
Taille de la porte:
                    750.0000
Epaisseur de la paroi:
                    100.0000
        Charnière: Gauche
        Ouverture: Intérieur
Angle d'ouverture: Ouverture 60º

On voit qu'il y a deux noms de bloc. Le nom "effectif" du bloc et le nom du bloc anonyme (*U4 ici). Notre filtre de sélection ne fonctionne donc pas car il opère sur le nom du bloc anonyme.

La solution à ce problème a été détaillé sur le blog de Kean WALMSLEY, Through the Interface. Dans son article, Kean explique que le lien entre la définition de bloc anonyme et la définition du bloc dynamique se fait grâce à des données étendues.

Pour les visualiser, Kean a utilisé le complément ArxDbg qui permet d'explorer le contenu d'un fichier DWG. Mais il est également possible de les afficher avec un peu de code LISP :

(entget (cdr (assoc 330 (entget (tblobjname "BLOCK" "*U4")))) '("*"))

On utilise la fonction tblobjname pour retrouver l'entité BLOCK dans la table des symboles, puis on récupère la paire pointée code 330 pour retrouver le nom de l'entité de type BLOCK_RECORD. Finalement on utilise entget pour retrouver la liste de définition en indiquant qu'on veut récupérer les données étendues pour toutes les applications. La liste de définition retournée ressemble à ceci :

((-1 . <Nom d 'entité: 7ffff705f20>)
  (0 . "BLOCK_RECORD")
  (5 . "26A")
  (102 . "{ACAD_XDICTIONARY")
  (360 . <Nom d 'entité: 7ffff705f30>)
  (102 . "}")
  (330 . <Nom d 'entité: 7ffff703810>)
  (100 . "AcDbSymbolTableRecord")
  (100 . "AcDbBlockTableRecord")
  (2 . "*U2")
  (360 . <Nom d 'entité: 7ffff705f50>)
  (340 . <Nom d 'entité: 0>)
  (102 . "{BLKREFS")
  (331 . <Nom d 'entité: 7ffff72a330>)
  (102 . "}")
  (70 . 0)
  (280 . 1)
  (281 . 0)
  (-3 ("AcDbBlockRepBTag" (1070 . 1) (1005 . "232")))
)

Dans l'élément code -3, on voit qu'il y a des données associées à l'application AcDbBlockRepBTag : des drapeaux (code DXF 1070) et un maintien (code DXF 1005).

Le maintien correspond au BLOCK_RECORD du bloc dynamique. On peut donc retrouver tous les noms des blocs anonymes associés au bloc dynamique et les combiner avec une expression OR dans notre filtre. Voici la fonction get-ano-block-handles qui retrouve tous les maintiens des blocs anonymes :

(defun get-ano-block-names
         (block-name
          /
          target-dyn-block-record-handle
          block
          block-name
          block-record-def-lst
          xdata
          dyn-block-record-handle
          block-names
         )
  (setq target-dyn-block-record-handle
   (cdr
     (assoc 5
      (get-block-record-def-lst
        block-name
      )
     )
   )
  )
  (while (setq block (tblnext "BLOCK" (null block)))
    (setq block-name (cdr (assoc 2 block)))
    (setq block-record-def-lst (get-block-record-def-lst block-name))
    (if (setq xdata (assoc -3 block-record-def-lst))
      (progn
  (setq dyn-block-record-handle (cdr (assoc 1005 (cdadr xdata))))
  (if (= target-dyn-block-record-handle dyn-block-record-handle)
    (setq   block-names
     (cons block-name block-names)
    )
  )
      )
    )
  )
  block-names
)

(defun get-block-record-def-lst
       (block-name / block-ent-name block-record-ent-name)
  (setq block-ent-name (tblobjname "BLOCK" block-name))
  (setq block-record-ent-name (cdr (assoc 330 (entget block-ent-name))))
  (entget block-record-ent-name '("AcDbBlockRepBTag"))
)

Ça nous donne une liste avec tous les noms de blocs :

("*U5" "*U4" "*U2")

On concatène ces noms avec une virgule en guise de séparateur avec une savante utilisation de mapcar et de apply :

(defun create-ss-filter (block-name /)
  (strcat
    (apply 'strcat
     (mapcar '(lambda (s) (strcat "`" s ","))
       (get-ano-block-names block-name)
     )
    )
    block-name
  )
)

Au passage on échappe l’astérisque avec le caractère ` pour éviter que ssget ne le considère comme un caractère jocker. Et on n'oublie pas de placer le nom du bloc dynamique à la fin pour sélectionner également les insertions non modifiées. Ça nous donne la chaîne suivante :

"`*U5,`*U4,`*U2,Porte - Métrique"

Et enfin on peut créer notre jeu de sélection :

(ssget "X"
       (list '(0 . "INSERT")
       (cons 2 (create-ss-filter "Porte - Métrique"))
       )
)

Quand je vous disais que c'était compliqué, je ne m'était pas trompé !

Français

Etiquettes:

Ajouter un commentaire