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é !

Etiquettes:

Commentaires

Objet : AutoLISP : filtre de sélection pour blocs dynamiques Publié par Maxence le ven, 13/06/2014 - 11:34 Bonjour, Je vous remercie du partage de votre connaissance qui m'a été très utile. Bonjour, J'ai utilisé il y a quelques mois votre routine dans un de mes programmes LISP maison, mais je m'aperçois que régulièrement la liste retournée est incomplète. Je pense qu'il manque sans doute une petite ligne de commande qui "réinitialise" et recharge sa base de donnée. En effet il me suffit sortir de mon dessin et de le ré-ouvrir et la liste obtenue est bonne. Le problème, c'est que je ne me rend pas toujours compte que la liste est incomplète car je ne sort pas systématiquement de mon dessin. Pourriez-vous m'indiquer ce qu'il faut que je rajoute, sans doute une commande VLA que je maîtrise pas du tout. Je suis comme on dit de la vielle école, je pratique le LISP depuis 1985 mais les nouveautés sont difficiles à intégrées. Bien cordialement.

La fonction tblnext doit être réinitialisée via l'argument rewind, c'est pour ça que j'utilise (null block). Il faut veiller à ce que la variable block soit bien déclarée comme variable locale dans le deuxième argument de la fonction defun.

Bonjour, J'attendais votre réponse par le biais d'une alerte mail qui n'est jamais arrivée, mais je viens à l'instant de découvrir votre réponse sur le site. Je vous remercie donc un peu tard... J'ai bien vérifié le "(null block)", je suis bien conforme à votre programmation. Donc il y a bien un problème au final, et je ne sais pas comment le résoudre. Je dois sélectionner des occurances d'un bloc dynamique, et donc pour aller vite, je filtre la sélection en couplant avec votre programme. Et donc c'est là que le bug intervient. J'essaye de comprendre à quel moment ce la plante, je reviendrais vers vous si vraiment je n'y arrive pas. Tous mes remerciements et bonne soirée.

Ajouter un commentaire