Simply Moira  

HexaBlock

Bibliothèque de formes

Documentation interne du moteur


Ce document constitue la documentation interne du composant HEXABLOCK.
Ce document fait partie des sources du projet et est géré en configuration Pour le mettre à jour, utiliser kompozer, disponible sur Linux


o Table of contents :
 

o   Partie 1 : Prévisualisation des éléments créés
o   Partie 2 : Nouvelles fonctions de construction
o   Partie 3 : Bibliothèques de formes
o   Partie 4 : Evolution des associations

o See also :

o   Last news
o   Last last News : Voir les nouvelles formes
o   Précisions sur las associations par lignes
o   Frequently asked questions
o   Maintenance


o Partie 1 : prévisualisation des éléments créés

Pour que SALOME puisse prévisualiser le "document" en cours de modivication, il est nécessaire de créer une copie de l'original. L'évolution majeure du moteur  consiste à implémenter une fonction de copie. 

Ce qui revient à ajouter dans la classe Document la méthode suivante :

Document* copyDocument ();

La valeur rendue est le document copié.
Les éléments copiés sont les éléments intrinsèques du document : vertices, edges, quadrangles, hexaèdres, lois. Qui sont aussi ceux sauvegardés dans un texte XML. On ne stocke pas les éléments intermédiaires de construction (grilles, vecteurs, etc...).

Cette rège pourra être remise en cause si l'application interactive a besoin d'autres informations.

La copie se déroule ainsi :
Les associations sont copiées lors de la copie de chaque élément.


Retour au début

o  Partie 2 : Nouvelles fonctions de construction

o 2.1 Création d'Hexaèdres

Il s'agit de construire un hexaèdre à partir de quadrangles déjà créés dans le modèle de blocs. Cet hexaèdre est produit à partir des 4 cas suivants :

Les points d'entrée sont naturellement :

Hexa* addHexa2Quads (Quad* q1, Quad* q2);
Hexa* addHexa3Quads (Quad* q1, Quad* q2, Quad* q3);
Hexa* addHexa4Quads (Quad* q1, Quad* q2, Quad* q3, Quad* q4);
Hexa* addHexa5Quads (Quad* q1, Quad* q2, Quad* q3, Quad* q4, Quad* q5);

Chacun des cas amène une discussion. La démarche est la suivante : 

Remarques :
o    Rappel  :  formalisation des Hexaèdres/Quadrangles/Vertex


Pour rappel :
          //      z=0   z=1   y=0   y=1   x=0  x=1
enum EnumHQuad   {Q_A,  Q_B,  Q_C,  Q_D,  Q_E, Q_F,  HQ_MAXI};

La face A est opposée à B, C est opposée à D, E à F. La position des faces  A, C, E normales à Oz, Oy, Ox est purement théorique. Elle améliore la visualisation. Les orientations sont en fait  interchangeables. On peut intervertir les couples (C,D) et (E,F), on peut intervertir A et B, C et D, E et F, l'essentiel est d'avoir une cohérence  avec les autres dénominations d'edges (ac, af, ad ...) ou de sommets (ace, acf, adf ...)  et les conventions de départ.

L'arête située la face X et la face Y est notée xy. Un sommet situé entre les 3 faces X, Y Z est noté xyz :


       6=bde  +----bd-----+ bdf=7
             /|          /|
           be |   B    bf |
           /  |        /  |
    4=bce +----bc-----+...|...bcf=5
          |  de     D |   df
          | E |       | F |           z
         ce   |     cf  |           ^
  2=ade...|...+----ad-|---+ adf=3     |   y
          |  /        |  /            |  /
          | ae    A   | af            | /
          |/          |/              |/
    0=ace +----ac-----+ acf=1         +-----> x
  


o    La classe AnaQuads

Cette classe analyse les relations topologiques existant entre plusieurs quandrangles. Un objet de ce type est créé au début de chaque fonction addHexaXQuads.

Le constructeur est :

AnaQuads (Quad* q1, Quad* q2, Quad* q3=NULL, Quad* q4=NULL, Quad* q5=NULL);

Les champs affectés lors de la construction de l'objet :

MawQuads Constante égale à 5 5
int    nbr_quads;Nombre de quadrangles
int    nbr_aretes;Nombre total d'arêtes
Quad*  tab_quads  [i];Le ième quadrangle passé en argument
int    inter_nbre [i];Nombre d'intersections du ième quadrangle
int    inter_edge [i][j];Nro d'arête de i intersectant le jème quadrangle
int    inter_quad [i][j];Nro de quadrangle présent sur la jème arete du iéme quad

o    La classe Cramer

Cette classe résout un système linéaire de n équations  à n inconnues. Quand n est "petit", n<10. Peut être réutilisée sur un autre projet.
Implémentation : HexCramer.hxx.
Test unitaire : test_cramer dans test_hexa1.cxx


int test_cramer (int nbargs, cpchar tabargs[])
{
   double matrice [] = { 2, 7, 1,    0, 3, 0,    1, 9, 5 };
   double second_membre  [] = { 1, 1, 1 };
   Hex::Real3  solution;
   Hex::Cramer system(3);   // On dimensionne le système de Cramer à la construction

   int ier = system.resoudre (matrice, second_membre, solution);

   cout << " Solution  = (" << solution[0] << ", " << solution[1] << ", " << solution[2]
        << "), ier = " << ier << endl;
                                 // Pour vérifier :
   Hex::Real3 produit;
   system.multiply (solution, produit);
   cout << " Produit = (" << produit[0] << ", " << produit[1] << ", " << produit[2]
        << ")"  << endl;
   return HOK;
}




o    Création d'un hexaèdre avec deux quadrangles :  

En diedre

Vis a vis
addHexa2Quads avec 2 quadrangles  non reliés.
Fonction : addHexaQuadsAB
addHexa2Quads avec 2 quadrangles   reliés.
Fonction : addHexaQuadsAC

addHexaQuadAB :

Les huits sommets sont présents, il manque quatre arêtes verticales ce, cf, df, de.
La difficuté consiste à faire correspondre deux à deux les quatre sommets de chaque quadrangle sans que les arêtes ne se croisent. La situation est analogue à mergeQuads ou joinQuads, sauf que dans ces cas, on passe en argument quatre vertex pour déterminer dans ambiguité les sommets correspondants deux à deux. On a choisi ici d'automatiser l'association :
Une fois la combinaison optimale établie :
Pour créer le i-ème quadrangle vertical :

addHexaQuadAC :

Deux faces présentes A et C, une arête commune ac. Il manque deux sommets bde et bdf à partir des quels on construit les arêtes manquantes bd, be, de, bf, df. 
On doit s'assurer de ma planéité des quadrangles créés.

La solution n'est pas unique. Il existe une infinité de sommets pouvant définir un hexaèdre  conforme. On se contente ici d'en choisir une et de démontrer que l'objet créé est conforme. 

Le point bde (resp bdf) est construit en sommant les vecteurs ac et ce (resp af et cf), ce qui revient à définir un parallélogramme. On assure ainsi sa présence dans le plan E (resp. F). Les faces créées E et F sont donc planaires.

Pour démontrer la planéité de la face B :

  1. (ae , af)  coplanaires (donnée)
  2. be = ae (construction d'un parallèlogramme)
  3. df = af  (idem)
  4. (1), (2), (3) => (be, bf) coplanaires

On procéderait de la même manière pour établir que la face D est plane.

La fonction s'écrit ainsi :

o    Création d'un hexaèdre avec trois quadrangles :  


En diedre

Trièdre
addHexa2Quads avec 3 quadrangles disposés en U.
Fonction : addHexaQuadsACD
addHexa2Quads avec  quadrangles disposés  en trièdre
Fonction : addHexaQuadsACE


addHexaQuadACD :

Tous les sommets sont présents. Il reste à créer :

La difficulté est d'identifier clairement les arêtes et les sommets à partir des quadrangles existant : 

addHexaQuadACE :

Il est nécessaire de créer le sommet v_bdf. Il est l'intersection des plans (B, D, F). Chaque plan est défini par trois points présents sur les autres quadrangles :

Soit un plan P défini par un point A et un vecteur normal N. Un point M appartient à P si le produit scalaire N.AM est nul

On obtient le système d'équations

  1. (bdf,bce) . norm_b = 0
  2. (bdf,ade) . norm_d = 0
  3. (bdf,acf) . norm_f = 0
Equivalent à : 
  1.    norme_b[0]*X + norme_b[1]*Y + norme_b[2]*Z = prod_scalaire (v_bce, norm_b)
  2.    norme_d[0]*X + norme_d[1]*Y + norme_d[2]*Z = prod_scalaire (v_ade, norm_d)
  3.    norme_f[0]*X + norme_f[1]*Y + norme_f[2]*Z = prod_scalaire (v_acf, norm_f)

Ce système de 3 équations à trois inconnues est résolu par la méthode de Cramer au moyen d'une classe du même nom défini dans le nouveau fichier HexCramer.hxx. Notons qu'un test unitaire de la classe Cramer existe dans le fichier test_quads.cxx

Une fois le système résolu ; s'il n'est pas régulier on retourne un vecteur nul, la suite est aisée :

o    Création d'un hexaèdre avec quatre quadrangles :  

En tunnel

En but
addHexa2Quads avec 4 quadrangles  disposés en tunnel.
Fonction : addHexaQuadsABCD
addHexa2Quads avec 4 quadrangles disposés en fauteuil.
Fonction : addHexaQuadsACDE

addHexaQuadABCD :

Le rôle des quadrangles passés en arguments est symétrique On nommera q_a le premier trouvé, q_c le premier qui intersecte q_a, q_c le second et q_d celui qui n'intersecte pas q_a.

Tous les sommets et arêtes sont présents. Il suffit de créer les deux quadrangles E et F:

Les problèmes de détermination des variables se traiotent comme dans addHexaQuadABC

addHexaQuadACDE :

Le rôle de q_a et qc est symétrique. Ils ont chacun trois arêtes communes avec les autres. q_e et q_f n'en ont que 2.

Tous les sommets sont présents. Il suffit de créer l'arête e_bf et les deux quasrangles B et F:

Les edges se déterminent aisément grâce aux indices d'interection fournis par AnaQuads. Il suffiit de prendre l'opposé :et à la propriété de la classe Quad : l'indice d'une arête opposée à l'arête i vaut i+2.

   int   nc_ac = strquads.inter_edge[pos_c][pos_a]; // Nro dans  q_c de e_ac
   int   nc_ce = strquads.inter_edge[pos_c][pos_e]; // Nro dans  q_c de e_ce
   int   nd_ad = strquads.inter_edge[pos_d][pos_a]; // Nro dans  q_d de e_ad
   int   nd_de = strquads.inter_edge[pos_d][pos_e]; // Nro dans  q_d de e_de
   int   ne_ae = strquads.inter_edge[pos_e][pos_a]; // Nro dans  q_e de e_ae

   Edge* e_af  = q_a->getEdge ((na_ac + 3) MODULO QUAD4);
   Edge* e_bc  = q_c->getEdge ((nc_ac + 2) MODULO QUAD4);
   Edge* e_cf  = q_c->getEdge ((nc_ce + 2) MODULO QUAD4);
   Edge* e_bd  = q_d->getEdge ((nd_ad + 2) MODULO QUAD4);
   Edge* e_df  = q_d->getEdge ((nd_de + 2) MODULO QUAD4);
   Edge* e_be  = q_e->getEdge ((ne_ae + 2) MODULO QUAD4);

   Vertex* v_bcf = e_cf->opposedVertex (e_cf->commonVertex (e_af));
   Vertex* v_bdf = e_df->opposedVertex (e_df->commonVertex (e_af));

o    Création d'un hexaèdre avec cinq quadrangles : 

Il n'y a pas besoin de dissocier plusieurs cas, le travail est réalisé directement dans la méthode addHexa5Quads().

En but

Ce cas est assez simple : c'est un hexaèdre auquel il manque la face que l'on convient de nommer B.

Retour au début

o 2.2 Génération d'Hexaèdres par révolution

Le pont d'entrée dans la classe Document est :

Elements* revolutionQuads (Quads& start, Vertex* center, Vector* axis, vector<double>& angles);

Avec :



En diedre

Trièdre
Avant la revolution : on a coloré les quadrangles à déplacer, ainsi que l'axe de rotation.Résultat de  la revolution.

Retour au début

o 2.3 Substitution d'Hexaèdres

Il s'agit de remplacer un hexaèdre ou des hexaèdres par une autre série d'hexaaaèdres. Pour pouvoir effectuer ce remplacement, cette autre série d'hexaèdres doit vérifier des conditions de collage de faces.



En diedre

Trièdre
Exemple : remplacement des 3 hexaèdres jaunes centraux par 3 fois 5 nouveaux hexaèdres qui sont construits par extrusion des 5 quadrangles marrons (le motif).Résultat du remplacement. 


La signature de la fonction est la suivante :
Elements* Document::revolutionQuads (Hexas& pattern, Vertex* p1, Vertex* c1,
                                     Vertex* p2, Vertex* c2, Vertex* p3, Vertex* c3);

Avec :

Conditions : 

Pour désigner les faces d'un hexaèdres, on utilise les  notations habituelles définies plus haut.

Conventions et orientations :


  (A suivre ...)


Retour au début


o  Partie 3 : Bibliothèque de formes

Cette biblothèque est constituée de quatre nouvelles formes :

Ces formes sont implémentées (et traduites ....) de la façon suivante :

Elements* makeSphere (Vertex* center, Vector* vx, Vector* vz,
                      double radius, 
double radhole,
                      Vertex* plorig, 

                      int nrad, int nang, int nhaut);
Elements* makePartSphere (Vertex* center, Vector* vx, Vector* vz,
                          double  radius,
double radhole,
                          Vertex* plorig, double angle,
                          int nrad, int nang, int nhaut);
Elements* makeRind (Vertex* center, Vector* vx, Vector* vz,
                    double  radius, double radint,
double radhole,
                    Vertex* plorig, 
                    int nrad, int nang, int nhaut);
Elements* makePartRind (Vertex* center, Vector* vx, Vector* vz,
                       
double  radius, double radint, double radhole,
                        Vertex* plorig, double angle,
                        int nrad, int nang, int nhaut);

Remarques :

o 3.1  Aspect visuel (exemples)


Conditions

Demi Sphere trouee

Huitième de sphère
Demi-Sphère trouée (makeSphere ())Huitième de sphère trouée (makePartSphere ())

Ecorce trouée    

Huitième d'ecorce       

Demi écorce trouée (makeRind ())Huitième d'écorce trouée (makePartRind ())

o 3.1  Implémentation

Toutes ces fonctions publiques appellent une seule fonction  de la classe Elements :

int makeRind (EnumGrid type, Vertex* center, Vector* vx, Vector* vz,
             
double  radius, double radint, double radhole,
              Vertex* plorig, double angle,
               int nrad, int nang, int nhaut);

Une fonction controlRind() vérifie la cohérence des arguments passés en entrée et calcule les deux extrema de l'angle d'élévation phi (voir chapitre suivant).
Une boucle à triple indice sur (nrad, nang, nhaut) calculme les coordonnées des sommets par appel de la fonction getCylPoint (nx,ny,nz, px,py,pz).
Ce calcul se fait sur une sphère de centre O et d'axe Oz, dont les hexaedres sont comptés à partir de l'axe Ox de façon à remplir le secteur angulaire theta.. Les coordonnées sont ensuite replacées dans les coordonnes utilisateur grâce à la fonction transfoVertices(). Edges, quadrangles et hexaèdres sont ensuites assemblés grâce à  la fonction fillGrid().

o 3.2 Détermination de l'angle d'élévation (phi)  

L'angle d'azimuth theta (longitude) est défini explicitement par l'utilisateur. L'angle d'élévation phi est délimité par la position du  plan horizontal et par le diamètre du trou.  Soit :

Sphere

A partir de ces éléments de base, on  définit :
L'angle d'élévation  phi  est encadré par les valeurs suivantes :

Retour au début


o  Partie 4 : Evolution des associations

Cette tâche est composée de 3 évolutions, au sujet de l'association, qui sont décrites ci-après.

o 4.1 Nouveaux objets  

Il s'agissait d'ajouter de nouveaux objets intermédiaires de travail, comme le vecteur ou le cylindre :

Il était p aussi associer  les objets intermédiaires aux objets géométriques venant du composant GEOM de SALOME :

o 4.2 Associations prédéfinies  

La deuxième évolution consiste à améliorer les associations des modèles de blocs prédéfinis en calculant automatiquement des associations implicites, les fonctions concernées sont les suivantes:

Pour chaque arête (sphérique) concernée, on associera un arc de cercle.

Il est inutile d'associer laes faces, SMESH est capable de mailler si les arêtes sont associées.

Implémentation 

Rappelons que l'objet Shape de HexaBlock est constitué :

Deux opérations sont nécessaires  :

  1.  créer un cercle au moyen de GEOM,
  2. découper ce cercle en arcs de cercle qui seront associés aux arêtes concernées.

Opération 1 : Le cercle à créer est en fait un arc de cercle orienté de 360 degrés.  Il est en effet nécessaire de définir une origine. GEOM permet de générer finir un tel cercle, à partir duquel HexaBlock définira des arcs au moyen des paramètres debut et fin des shapes :

Cette action est assurée par la fonction geom_create_circle qui génère la brep du cercle à partir de son centre, de son rayon, de son vecteur normal, du  vecteur Ox qui permet de définir son origine.


void geom_create_circle (double* centre, double rayon, Vector* normale,
                         double* ox, string& brep)
{
   gp_Pnt gp_center (centre [dir_x], centre [dir_y], centre [dir_z]);
   gp_Vec gp_ox     (ox     [dir_x], ox     [dir_y], ox     [dir_z]);
   gp_Vec gp_norm   (normale->getDx(), normale->getDy(), normale->getDz());

   gp_Ax2  gp_axes (gp_center, gp_norm, gp_ox);
   gp_Circ gp_circ (gp_axes,   rayon);

   TopoDS_Edge    geom_circ = BRepBuilderAPI_MakeEdge(gp_circ).Edge();
   ostringstream  stream_shape;
   BRepTools::Write(geom_circ, stream_shape);

   brep = stream_shape.str();
}

GEOM permet de définir un tel cercle (calsse gp_Circ)  à partir d'un systemes d'axes 2D (classe gp_Ax2 )  et d'un rayon. Ce cercle esr ensuite transformé en forme TopoDS_Shape qui est l'objet GEOM que l'on maniplule habituellement losque l'on procède aux associations. Plus précisément en TopoDS_Edge  au moyen de BRepBuilderAPI_MakeEdge. Enfin l'objet est  transformé en chaine de caractères de format Brep via ostringstream.

Opération 2 : On dispose d'une b-rep  décrivant un arc de cercle de 360 degrés. Il est aisé de définir proportionnellement pour chacun des edges constituant la ligne associée à la portion de cercle les paramètres début et fin par rapport aux angles de départ et d'arrivée de chaque arc. Cette opération fut d'ailleurs réalisée lors de la préservarion des associations lors d'un "cut". On réutilisera donc la fonction Elements::cutAssociation(...) moyennant quelques aménagements.

o 4.3 Propagation des associations  


La troisième évolution consiste à améliorer la propagation automatique des associations, les fonctions concernées sont les suivantes:

Ces trois évolutions seront implémentées dans le moteur. Seule le première évolution (création de sphères ou cercles) sera implémentée dans la partie interactive.



o    Implémentation :


En ce qui concerne les associations propagées par les transformations, la fonction de base de l'implémentation est transfo_brep.
Cette fonction reprend la matrice du modèle de blocs et l'applique à la shape décrite par la brep.

// ====================================================== transfo_brep
void transfo_brep (string& brep, Matrix* matrice, string& trep)
{
   BRep_Builder  builder;
   TopoDS_Shape  shape_orig;
   ostringstream stream_shape;
   gp_Trsf       transfo;

   double             a11,a12,a13,a14, a21,a22,a23,a24, a31,a32,a33,a34;
   matrice->getCoeff (a11,a12,a13,a14, a21,a22,a23,a24, a31,a32,a33,a34);
   transfo.SetValues (a11,a12,a13,a14, a21,a22,a23,a24, a31,a32,a33,a34,
                      Epsil2, Epsil2);

   istringstream stream_brep (brep);
   BRepTools::Read           (shape_orig, stream_brep, builder);
  
   BRepBuilderAPI_Transform brep_transfo (shape_orig, transfo, Standard_True);
   TopoDS_Shape result = brep_transfo.Shape();

   BRepTools::Write (result, stream_shape);
   trep = stream_shape.str();
}


Pour la fonction de prismQuads, on utilise une variante simplifiée :

// ====================================================== translate_brep
void translate_brep (string& brep, double dir[], string& trep)
{
   gp_Trsf       transfo;
   BRep_Builder  builder;
   TopoDS_Shape  orig;
   ostringstream stream_shape;

   gp_Vec      vecteur       (dir [dir_x], dir [dir_y], dir [dir_z]);
   transfo.SetTranslation    (vecteur);
   istringstream stream_brep (brep);
   BRepTools::Read           (orig, stream_brep, builder);
  
   TopLoc_Location  loc_orig   = orig.Location();
   gp_Trsf          trans_orig = loc_orig.Transformation();
   TopLoc_Location  loc_result (transfo * trans_orig);
   TopoDS_Shape     result = orig.Located (loc_result);

   BRepTools::Write (result, stream_shape);
   trep = stream_shape.str();
}


Pour la fonction de prismQuads, on utilise une variante simplifiée :



o    Propagation des associations pour les transformations :


L'exemple donné est makeScale

# ======================================================= make_grid
def make_grid (doc) :
    ori  = doc.addVertex ( 0, 0, 0)
    vz   = doc.addVector ( 0, 0, 1)
    vx   = doc.addVector ( 1 ,0, 0)

    dr = 1
    da = 360
    dl = 1
    nr = 1
    na = 6
    nl = 1
    grid = doc.makeCylindrical (ori, vx,vz, dr,da,dl, nr,na,nl, False)
    return grid

# ======================================================= test_scale
def test_scale () :

    doc  = hexablock.addDocument()
    grid = make_grid (doc)

    dest   = doc.addVertex (15, 0, 0)
    grid2  = doc.makeScale (grid, dest, 0.5)
    return doc


transfo

ss


o    Propagation des associations pour la fonction prismQuads

Pour la fonction prismQuads :



# ========================================================== test_prism
def test_prism () :
    doc  = hexablock.addDocument()
    nr = 1
    na = 6
    nl = 1
    grid = make_grid (doc, nr, na, nl)

    liste = [ ]
    for nx in range (nr) :
        for ny in range (na) :
            cell = grid.getQuadIJ (nx, ny, nl)
            liste.append (cell);

    axis = doc.addVector (1, 1, 1);
    bloc = doc.prismQuads  (liste, axis, 3)
    return doc







Prisme






o    Propagation des associations pour la fonction revolutionQuads


Pour la fonction revolutionQuads



# ========================================================== test_revolution
def test_revolution () :
    doc  = hexablock.addDocument()
    nr = 1
    na = 6
    nl = 1
    grid = make_grid (doc, nr, na, nl)

    liste = [ ]
    for nx in range (nr) :
        for ny in range (na) :
            cell = grid.getQuadIJ (nx, ny, nl)
            print " ... cell = ", cell
            liste.append (cell);
ss

    center = doc.addVertex (0, -10, 0);
    axis   = doc.addVector (1, 0, 0);
    angles = [5, 10, 15, 20, 30, 20, 15, 10, 5 ]
ss
    bloc = doc.revolutionQuads  (liste, center, axis, angles);
    return doc




Macaroni





Retour au début