Nouvelle étape dans la modélisation de notre PythonPart « Poteau en Béton Armé », regardons aujourd’hui comment configurer les ancrages de notre objet 3D.

Par ancrage je pense tout particulièrement à 2 types :

  1. la gestion de l’altimétrie ;
  2. le point d’insertion en vue en plan.

1) Script Interface

  • Altimétrie

Bien plus qu’un simple objet 3D positionné sans intelligence, l’API d’Allplan nous permet d’accrocher automatiquement notre PythonPart aux niveaux présents dans la structure du bâtiment.

Pour cela, nous allons compléter notre variable Hauteur ou « Height » précédemment utilisée ; pour mémoire nous avions mis en place un champ de type « Length », type générique pour saisir une longueur.

En effet, pour nous permettre de définir une référence à un niveau, nous allons d’abord mettre en place la boîte de dialogue utilisée par les éléments d’architecture standards :

A noter :

Il y a 3 façons de définir une référence à un plan :

  • saisie du bord supérieur ;
  • saisie du bord inférieur ;
  • les 2.

Nous allons choisir cette dernière hypothèse.

 <Parameter>
<Name>PlaneReferences</Name>
<Text> </Text>
<Value></Value>
<ValueType>PlaneReferences</ValueType>
<ValueDialog>PlaneReferences</ValueDialog>
</Parameter>

Puis, afin d’obtenir une meilleure expérience utilisateur, nous allons lier notre champ Height à ce PlaneReferences par l’ajout d’une nouvelle balise nommée Constraint.

De cette manière Allplan va calculer et afficher automatiquement la hauteur absolue de notre objet.

Voici la syntaxe :

 <Parameter>
<Name>ColumnHeight</Name>
<Text>Hauteur</Text>
<FontFaceCode>4</FontFaceCode>
<Value>-1</Value>
<ValueType>Length</ValueType>
<Constraint>PlaneReferences</Constraint>
</Parameter>

 <Parameter>
<Name>PlaneReferences</Name>
<Text> </Text>
<Value></Value>
<ValueType>PlaneReferences</ValueType>
<ValueDialog>PlaneReferences</ValueDialog>
<Constraint>ColumnHeight</Constraint>
</Parameter>

A noter : cette balise Constraint a été ajoutée dans les 2 champs afin de créer une liaison bidirectionnelle

Dans Allplan, j’obtiens :

Si je modifie une hauteur comme suit :

la valeur se met à jour :

Inversement, lorsque je change ma valeur dans la palette :

les champs dans la boîte de dialogue sont correctement mis à jour :

  • Point d’Insertion

Dans le premier chapitre de cet exemple, nous avons mis en place un poteau qui pouvait être soit rectangulaire, soit circulaire.

Avec les paramètres mis en place nous obtenons :

En suivant la même logique que pour l’altimétrie, nous allons reproduire le comportement natif d’Allplan avec les éléments d’architecture Poteaux :

Je vais donc créer un nouvel Expander dédié à cette fonction :

<Parameter>

<Name>AttachmentExpander</Name>

<Text>Accrochage</Text>

<ValueType>Expander</ValueType>

</Parameter>

puis j’ajoute les champs suivants :

<Parameter>
<Name>AttachmentPointButtonRow</Name>
<Text>Point d’accrochage</Text>
<ValueType>Row</ValueType>

 

<Parameter>
<Name>AttachmentPoint</Name>
<Text>Attachment point index</Text>
<Value>5</Value>
<ValueType>RefPointButton</ValueType>
</Parameter>

 

</Parameter>

A noter :

  • je place mon bouton dans un Row et je complète le texte qui s’affichera dans Allplan ;
  • avec 5 comme Value par défaut, on propose un ancrage au centre

De retour dans Allplan, j’obtiens :

Voici le fichier complet :

2) Script Principal

Je commence par compléter ma liste d’imports :

import NemAll_Python_ArchElements as ArchElements
  • Fonction create_element

Je me place ensuite dans la fonction create_element pour compléter la liste des informations récupérées :

attach_point = build_ele.AttachmentPoint.value

 

plane_ref: ArchElements.PlaneReferences = build_ele.PlaneReferences.value

Puis je crée une variable pour récupérer l’arase inférieure de mon objet :

column_bottom = plane_ref.GetAbsBottomElevation()

Cette arase ainsi que la hauteur (pour mémoire column_height) nous permettent de modéliser notre objet.

  • Classe Objects3D

Je vais compléter ma classe mère Objects3D pour intégrer les 3 valeurs d’ancrage (X, Y et Z) :

class Objects3D:

def __init__(self, object_prop, attach_point, column_bottom):
        self.object_prop = object_prop
        self.geo = None
        self.attach_point = attach_point
        self.x_offset = 0
        self.y_offset = 0
        self.z_offset = column_bottom

A noter : je stocke le point d’insertion à part et laisse le décalage en X et Y à 0 par défaut car le calcul va dépendre de la forme du poteau

Je prépare ma future fonction attachment_point pour calculer ce décalage :

def attachment_point(self):
        pass
  • Classe Cuboid

Je me place dans ma classe enfant Cuboid que je complète :

class Cuboid(Objects3D):

 

def __init__(self, object_prop, attach_point, column_bottom, column_length, column_thick, column_height):
        Objects3D.__init__(self, object_prop, attach_point, column_bottom)
        self.column_length = column_length
        self.column_thick = column_thick
        self.column_height = column_height
        self.name_dim = f »{round(column_length / 10)}x{round(column_thick / 10)} »
        self.placement_pt = self.attachment_point()

Pour bien comprendre le placement en vue en plan, il faut avoir en tête les valeurs renvoyées par notre AttachmentPoint que je rappelle ici :

Par défaut notre poteau rectangulaire est placé à partir du coin inférieur gauche, donc :

  • en X
    1. les valeurs à gauche (1, 4 et 7) sont ignorées ;
    2. les valeurs au centre (2, 5, 8) donneront un décalage égal à la moitié de la longueur ;
    3. les valeurs à droite (3, 6, 9) induisent un décalage égal à la longueur.
  • en Y
    1. les valeurs en haut (1, 2 et 3) imposent un décalage égal à la largeur ;
    2. les valeurs au centre (4, 5, 6) donneront un décalage égal à la moitié de la largeur ;
    3. les valeurs en bas (7, 8 et 9) sont ignorées.

En Python, je traduis ces données avec les 2 conditions suivantes :

def attachment_point(self):

 

if self.attach_point in {2, 5, 8}:
        self.x_offset = -self.column_length / 2
elif self.attach_point in {3, 6, 9}:
        self.x_offset = -self.column_length

 

if self.attach_point in {1, 2, 3}:
        self.y_offset = -self.column_thick
elif self.attach_point in {4, 5, 6}:
        self.y_offset = -self.column_thick / 2

 

placement_pt = Geometry.Point3D(self.x_offset, self.y_offset, self.z_offset)

 

return placement_pt

A noter : je concatène le résultat en un point 3D qui sera renvoyé par la fonction

  • Classe Cylinder

Même logique que précédemment, mais cette fois-ci nous partons d’un point central.

Nous obtenons donc :

class Cylinder(Objects3D):

 

def __init__(self, object_prop, attach_point, column_bottom, column_rad, column_height):
        Objects3D.__init__(self, object_prop, attach_point, column_bottom)
        self.column_radius = column_rad
        self.column_height = column_height
        self.name_dim = f »Ø{round(column_rad / 10)} »
        self.placement_pt = self.attachment_point()

et pour le calcul :

  • en X
    1. les valeurs à gauche (1, 4 et 7) donneront un décalage égal au rayon ;
    2. les valeurs au centre (2, 5, 8) sont ignorées ;
    3. les valeurs à droite (3, 6, 9) induisent un décalage égal au rayon.
  • en Y
    1. les valeurs en haut (1, 2 et 3) imposent un décalage égal au rayon ;
    2. les valeurs au centre (4, 5, 6) sont ignorées ;
    3. les valeurs en bas (7, 8 et 9) donneront un décalage égal au rayon.

Voici la fonction :

def attachment_point(self):

 

if self.attach_point in {1, 4, 7}:
        self.x_offset = self.column_radius
elif self.attach_point in {3, 6, 9}:
        self.x_offset = -self.column_radius

 

if self.attach_point in {1, 2, 3}:
        self.y_offset = -self.column_radius
elif self.attach_point in {7, 8, 9}:
        self.y_offset = self.column_radius

 

placement_pt = Geometry.Point3D(self.x_offset, self.y_offset, self.z_offset)

 

return placement_pt

  • Fonction create_element

De retour dans create_element, je complète l’appel de mes classes avec les nouveaux arguments :

 # Create 3D object
if choice == « rectangle »:
        my_column = Cuboid(com_prop, attach_point, column_bottom, column_length, column_thickness, column_height)
else:
        my_column = Cylinder(com_prop, attach_point, column_bottom, column_radius, column_height)

Ensuite, je vais effecteur une translation globale sur mon PythonPart via une Matrix3D :

# Reference point
placement_mat = Geometry.Matrix3D()
placement_mat.SetTranslation(Geometry.Vector3D(my_column.placement_pt))

# Create the PythonPart
model_ele_list = pyp_util.create_pythonpart(build_ele, placement_mat)

A noter : l’utilitaire create_pythonpart prend comme deuxième argument (optionnel) une matrice de placement utilisée pour la transformation de la géométrie

Notre PythonPart est correctement affecté par ma translation mais il y a un problème : les différentes poignées ne suivent pas.

Nous allons donc modifier ces dernières pour prendre en compte le décalage :

  • Classe Handle

Le principe est très simple, je vais additionner mes points d’accroche au point d’insertion :

class Handle:

 

def __init__(self, placement_pt, handle_id, handle_point, ref_point, handle_param_data, handle_move_dir, handle_info_text):
        self.handle_id = handle_id
        self.handle_point = placement_pt + handle_point
        self.ref_point = placement_pt + ref_point
        self.handle_param_data = handle_param_data
        self.handle_move_dir = handle_move_dir
        self.handle_info_text = handle_info_text

Je fais bien attention à transmettre le point d’ancrage lors de la construction des poignées pour la forme rectangulaire :

 self.handles_prop = [Handle(self.placement_pt,
                « ColumnLengthHandle »,
                Geometry.Point3D(self.column_length, 0, 0),
                Geometry.Point3D(),
                « ColumnLength« ,
                HandleDirection.X_DIR,
                « Longueur »
                ),
                Handle(self.placement_pt,
                « ColumnThickHandle »,
                Geometry.Point3D(self.column_length, self.column_thick, 0),
                Geometry.Point3D(self.column_length, 0, 0),
                « ColumnThick« ,
                HandleDirection.Y_DIR,
                « Largeur »
                ),
                Handle(self.placement_pt,
                « ColumnHeightHandle »,
                Geometry.Point3D(0, 0,  self.column_height),
                Geometry.Point3D(),
                « ColumnHeight« ,
                HandleDirection.Z_DIR,
                « Hauteur »
                )
]

mais également pour la forme circulaire :

 self.handles_prop = [Handle(self.placement_pt,
                « ColumnRadiusHandle »,
                Geometry.Point3D(self.column_radius, 0, 0),
                Geometry.Point3D(),
                « ColumnRadius« ,
                HandleDirection.X_DIR,
                « Rayon »
                ),
                Handle(self.placement_pt,
                « ColumnHeightHandle »,
                Geometry.Point3D(0, 0, self.column_height),
                Geometry.Point3D(),
                « ColumnHeight« ,
                HandleDirection.Z_DIR,
                « Hauteur »
                )
]

Voici le fichier complet :

Nous avons vu dans ce chapitre comment mettre en place notre point d’insertion ainsi que l’accrochage aux niveaux, améliorant grandement l’utilisation de notre objet PythonPart.

0 commentaires

Soumettre un commentaire

Objects3D V1.0

Nouvelle série dans l'apprentissage des PythonParts, étudions la modélisation d'un objet 3D : un poteau en béton armé.1) Script InterfaceDans cet exemple nous allons mettre en place les premiers...

Objects2D V3.0

Dernière étape pour cet exemple de PythonParts, je vous propose de mettre en place une légende pour notre objet. Cette dernière devra reprendre les informations suivantes : le nom de mon objet ; le...

Objects2D V2.0

Dans le précédent article, nous avons vu comment préparer notre code en POO, voyons aujourd'hui comment en exploiter le potentiel avec ce nouvel exemple. En effet, nous allons compléter notre...

Objects2D V1.0

Nous allons commencer à construire des objets plus complexes sur Allplan, mais avant tout, un mot sur la Programmation Orientée Objet (POO)...1) Programmation Orientée ObjetLa Programmation Orientée...

HelloWorld V3.0

Dernière étape pour notre 1er exemple de script HelloWorld, nous allons maintenant voir comment personnaliser le rendu de notre objet.1) Script InterfaceDe retour dans notre palette, je vais tout...

HelloWorld V2.0

Dans l'article précédent, nous avons appris comment générer un objet (une ligne de longueur fixe) via l'API PythonParts. Aujourd'hui je vais vous montrer la mise en place de poignées pour notre...

HelloWorld V1.0

HelloWorld est par tradition écrit pour donner un exemple d'un langage de programmation. Nous ne ferons pas exception ici avec l'écriture de notre 1er script. Le but est simple, créer une ligne de...

Structure des PythonParts

Allplan est installé, votre IDE est prêt... parfait, voyons en détail le fonctionnement des PythonParts.1) Description des fichiersPour fonctionner, un PythonPart a besoin d'au moins 2 fichiers :...

Introduction

Dans cette série d'articles, nous allons étudier l'édition de scripts en langage de programmation Python pour le logiciel Allplan.Pour vous permettre de bien suivre ces tutoriels, je vais partir sur...