Parseur en python

Moi je veux bien expliquer ce qui fait quoi si ça vous aide. Le soucis si je résume c’est que le fichier correspond à des ordres pour une autre machine à qui il faut “parler” différemment. Or la machine de mon pote est un peu plus récente et a besoin de moins d’ordres :

  • Sur la machine de départ il faut lui dire "tu vas faire un trou" (G100=faire un trou) avec tel outil (le T) donc place-toi au bon endroit (G0 avec le X et le Y) et bosse jusqu'à telle profondeur (G80 avec le Z). La machine mon pote condense toutes ces infos en une ligne parce qu'elle a juste besoin de savoir "tu vas faire un trou" (G100) en X, Y, Z avec T. Elle n'a pas besoin des G0, G80...
  • Sur la machine de départ, si une action similaire à la précédente doit être entreprise, il suffit de renseigner la variable qui change. Exemple, après ton premier trou en X50 Y20, tu vas faire le même (même forêt=T, même profondeur=Z) mais cette fois-ci en X100 Y20. Sur le fichier de départ, on voit juste un nouveau X sans remettre le G, le T, le Z ou le Y (logique puisque ceux-ci ne changent pas). Sur la machine de mon pote il faut remettre toute la ligne avec la variable qui s'est modifiée mais aussi celles qui sont restées les mêmes (c'est une nouvelle action, il faut toutes les infos).
  • Il y a des actions spéciales qui demandent l'écriture d'une double ligne. La première, G172 revient essentiellement à donner le point d'origine, càd "tu commences ici". Mais ça ne dit pas ce qu'il y a à faire. Le second G détermine ce qu'il y a à faire et jusqu'où. Par exemple : scie de ici (G172 X,Y,Z) jusque là (G180 X,Y,Z). Dans l'ancien fichier, la machine avait besoin de plein d'étapes intermédiaires (d'où les séries de X) tandis que la machine de mon pote a juste besoin de la ligne avec le point d'origine et la ligne avec le point d'arrivée. S'il le faut, je peux spécifier quelles sont ces 3 actions spéciales et comment elles fonctionnent 
En soit la traduction n'est pas compliquée, elle peut être effectuée à l'oeil dès qu'on comprend ce que les codes représentent. Le plus dur pour moi est d'arriver à déterminer les éléments objectifs du fichier qui permettent de dire au programme : "quand tu as ce type d'action, tu as besoin d'aller chercher ce X là, ce Y là, ce T là et ce Z là".

Tu ne te rends pas compte à quel point ces infos clarifient le problème ^^

Je n’ai pas le temps aujourd’hui, mais je vais essayer d’y remettre un coup de patte demain.

A mon avis, il faut faire une premiere passe pour stocker les valeurs des G/X/Y/Z et leur sequencement, puis une seconde passe sur ce séquencement pour appliquer tes règles métiers.

J’ai malheureusement pas de temps pour t’aider, mais c’est sur la bonne voie :slight_smile:

[quote=“Histrion, post:42, topic: 55095”][/quote]
Cool j’ai enfin fait un truc utile  :thumbs-up2:

Je vais reformuler ce que tu as dit, et tu me corriges là où je dis des bêtises.

Tout pilotage de l’ancienne machine peut être découpé en trois phases :

  • une préparation (type G0 pour un déplacement ou G172 même si j'ai pas trop compris pourquoi c'est différent) qui a besoin d'une position X Y Z et de l'outil T
  • une action (type G >= 100, comme un perçage, un sciage) qui utilise l'outil T et des coordonnées X Y Z de la préparation
  • une évolution de l'action (type G80 pour la profondeur du perçage) qui fait évoluer une ou plusieurs des coordonnées
Quand tu lis le fichier au format de l'ancienne machine, pour écrire un fichier pour la nouvelle machine tu veux donc :
  • regrouper toute la phase de préparation en une seule ligne G172 avec X Y Z et T qui initialise ce qui va suivre
  • écrire autant de lignes G > 100 qu'il y a d'actions qui ne nécessitent pas une nouvelle initialisation au préalable (par exemple quand tu scies le long de X, puis que tu fais un angle droit pour scier le long de Y, tu n'as pas besoin de refaire un G172)
Si j'ai compris jusque là, je suis à deux doigts de pouvoir t'aider à écrire ton programme. Il me reste à savoir :
  • Quels G font qu'on a besoin d'un G172 après quand on change X
  • Même question pour Y
  • Même question pour Z
  • (Autrement dit, pour remplacer les trois points ci-dessus, est-ce que tu peux faire une liste de toutes les actions G avec ce qu'elles font en français par rapport à X Y Z : genre scier si j'ai bien compris c'est que dans le plan X Y, percer c'est dans la direction Z etc.)
Et accessoirement :
  • Pourquoi G0 et G172 c'est différent (tu parles d'action "spéciales" : lesquelles et en quoi elles sont spéciales ?)
  • C'est quoi les S et les F ?

[quote=“Histrion, post:45, topic: 55095”][/quote]

Le plus dur n’a jamais été d’écrire un programme en python, C, Basic, whatever mais plutôt de comprendre parfaitement comment le dis programme dois fonctionner. Et pour décrire un programme, rien de tel que du bon français, une fois cette étape faite, y’a plus qu’à !

@ZGoblin

Même réponse qu’à Ben et phili_b, ce que tu dis est vrai dans pas mal de cas mais ça n’en fait pas une règle générale. La rétro-ingénierie utilise le processus inverse. Le cerveau bosse à base de déductions (les spécifications pilotent le code et/ou la structure des données), mais également à base d’induction (j’ai du code ou des données structurées, je me fabrique un modèle mental de ce que les spécs devaient/devraient être).

Il ne faut pas sous-estimer les capacités qu’on a à parfois faire plus vite à base d’inductions (correction d’un modèle pas à pas, “trial and error”), qu’à base de déductions.

Edit :

Ca diverge du sujet, mais c’est typiquement l’approche “agile”. On part du principe qu’au lieu de spécifier complètement (modèle en V), il vaut mieux spécifier de manière assez floue, et se laisser des itérations pour s’adapter. Autrement dit, alterner les phases de déduction et d’induction au sein d’un même processus créatif.

[quote=« Histrion, post:47, topic: 55095 »][/quote]

Oui, mais bon, vous galériez jusqu’à ce que vous sortiez une description de ce que le programme doit faire :slight_smile:
Il faut pas tout spécifier, je suis d’accord, mais y’a un minimum.
Si tu sais meme pas ce que tu veux faire, tu vas itérer un paquet de fois avec d’aboutir, si jamais tu aboutis …

Et dans l’approche agile, la première étape, et certainement la plus importante, reste la définition des taches, ce qui demande quand même de savoir ce qu’on veut faire.

Sauf qu’ici on ne cherche pas à écrire un pilote de la machine, càd la couche métier, mais un outil qui transforme une structure de commandes en une autre structure de commandes, càd la couche persistance. Sur ce genre de problématiques de transformation l’approche empirique a du sens. Bon en pratique ici la structure de données est très fortement couplée à la nature du métier, parce que c’est probablement des machines commandées à bas niveau (je ne suis pas électronicien mais je ne serais pas surpris que le tout soit lu directement par une puce programmable qui traite ça avec du VHDL).

Pour comparaison, ce serait un schéma XML à transformer dans un autre schéma XML mais avec un arbre équivalent, on ne se poserait même pas la question de savoir ce que les champs peuvent bien vouloir dire, on ferait un XSLT et baste.

[quote=“Histrion, post:45, topic: 55095”][/quote]
J’espère que ça répond à tes questions ?

C’est très clair. J’imagine que les G40/G80/G90 c’est un peu comme G0 ? En gros G < 100, on s’en moque ça n’existe plus ?

[quote=“Histrion, post:51, topic: 55095”][/quote]
Voilà, c’est exactement ça… la nouvelle machine n’interprète aucun G en dessous de 100 il me semble.

Bon, c’est pas commenté, mais je pense que tu vas trouver que ça approche du résultat souhaité :
 

# -- coding:Latin-1 --
import sys

“”"
    * La lecture d’un outil T declenche la creation d’une nouvelle serie d’operations
    * La liste des G > 100 qui suivent le T sur la même ligne décrivent les operations à mener
    * Jusqu’au T suivant, les X, Y et Z indiquent le chemin à parcourir
"""

class OperationSeq:
    tool = ‘’
    operation = ‘’
    prefixWithG172 = False
    xIndices = []
    yIndices = []
    zIndices = []
    coordKinds = []
    coordValues = []
    def removeUselessCoords(self):
        # TODO : Ici on peut raffiner la liste de X/Y/Z, par exemple s’il y a successivement deux mouvements
        # qui vont dans la même direction
        doNothing = True
    def printMe(self):
        if self.prefixWithG172:
            printNewFormat(‘172’, self.coordValues[self.xIndices[0]], self.coordValues[self.yIndices[0]], self.coordValues[self.zIndices[0]], self.tool)
        lastX = lastY = lastZ = -1
        for i in range(0,len(self.coordKinds)):
            if self.coordKinds[i] == ‘X’:
                lastX = i
            elif self.coordKinds[i] == ‘Y’:
                lastY = i
            elif self.coordKinds[i] == ‘Z’:
                lastZ = i
            if lastX >= 0 and lastY >= 0 and lastZ >= 0:
                printNewFormat(self.operation, self.coordValues[lastX], self.coordValues[lastY], self.coordValues[lastZ], self.tool)

    
def printNewFormat(g, x, y, z, t):
    print(“G{0}\tX{1}\tY{2}\tZ{3}\tT{4}”.format(g, x, y, z, t))

def readInputFile(filename, allOperationSequences):
    operationSeq = None
    f = open(filename, ‘r’)
    for line in f:
        readOperations = False
        fields = line.split(’\t’)
        for field in fields:
            if field.startswith(‘T’, 0, 1):
                readOperations = True
                if operationSeq is not None:
                    allOperationSequences.append(operationSeq)
                operationSeq = OperationSeq()
                operationSeq.tool = field[1:]
            if operationSeq is not None and readOperations and field.startswith(‘G’, 0, 1):
                if field == ‘G172’:
                    operationSeq.prefixWithG172 = True
                else:
                    operationSeq.operation = field[1:]
            if operationSeq is not None and field.startswith(‘X’, 0, 1) or field.startswith(‘Y’, 0, 1) or field.startswith(‘Z’, 0, 1):
                if field.startswith(‘X’, 0, 1):
                    operationSeq.xIndices.append(len(operationSeq.coordKinds))
                if field.startswith(‘Y’, 0, 1):
                    operationSeq.yIndices.append(len(operationSeq.coordKinds))
                if field.startswith(‘Z’, 0, 1):
                    operationSeq.zIndices.append(len(operationSeq.coordKinds))
                operationSeq.coordKinds.append(field[0:1])
                operationSeq.coordValues.append(field[1:])
    if operationSeq is not None:
        allOperationSequences.append(operationSeq)
    f.close()

def main(args):
    allOperationSequences = []
    readInputFile(‘De atdedit.txt’, allOperationSequences)
    for operationSeq in allOperationSequences:
        operationSeq.removeUselessCoords()
        operationSeq.printMe()

if name == “main”:
   main(sys.argv[1:])

Il reste à coder la partie TODO. Mais déjà est-ce que la sortie est ok pour toi en l'état ?

[quote=« Histrion, post:53, topic: 55095 »][/quote]
Alors euuuuh… ok…

Bon déjà là je me dis que j’ai quand même été vachement présomptueux de penser que je pourrais apprendre assez vite pour faire ça :open_mouth: Je m’imaginais pas que ça prendrait une forme aussi compliquée. Du coup je mesure encore plus la chance d’avoir quelqu’un qui produit du code pour moi  :thumbs-up2:

Alors je vais essayer de décoder ce que je peux là dedans pour comprendre ce qu’il s’y passe (j’arrive à voir des éléments qui se trouvaient dans le précédent code mais il y a plein de nouvelles choses). Il se peut que ça prenne 2-3 jours parce que je pars en weekend mais wow quoi, là je suis un peu sans voix… 

Ne prends pas trop peur. Ca n’est pas si compliqué que ça en a l’air, et j’ai laissé des trucs dont finalement je ne me suis pas servi (ou que je pouvais simplifier : par ex. finalement on peut se passer des listes d’indices de X, Y, Z).

La partie qui va te dérouter le plus c’est la notion de « class ». Mais pas de panique, c’est grosso modo juste pour ranger des variables et des fonctions.

Je ne peux que t’encourager à découvrir Python via un vrai cours plutôt qu’en lisant directement du code. Je suis conscient que le côté scolaire peut être un peu rebutant, mais tu y gagnera le fait d’avoir abordé certains principes dans le bon ordre.

Par exemple le site du Zéro est une très bonne référence de cours / tutoriaux pour apprendre un langage (perso j’avais appris PHP avec eux :wink: ) : http://www.siteduzero.com/informatique/tutoriels/apprenez-a-programmer-en-python

En tout cas Histrion, c’est cool d’avoir pris le temps de produire ce code !

[quote=« Ben, post:56, topic: 55095 »][/quote]
Mais oui c’est super cool… et c’est pour ça que je suis sans voix : à la fois content et gêné. Super content évidemment parce qu’il m’aurait fallu un bout de temps avant d’arriver à faire ça mais gêné parce que là j’ai beaucoup l’impression d’abuser :-/

Et pour le site du zéro, je suis effectivement passé par là et j’ai imprimé leur cours justement :wink: Encore un énorme merci Histrion et comme je le disais, je vais prendre le temps de lire ton code dès que je serai rentré de weekend.

Franchement pas de souci. Je ne peux évidemment pas y passer trop de temps mais coder c’est vraiment un plaisir par rapport à la paperasse administrative que je me tape dans ma mission actuelle. Un quart d’heure par-ci, une demi-heure par là, ça me permet de faire un peu le vide dans la tête :wink:

3615-MyLife :
Pour vous donner un exemple, il y a peu j’ai rédigé une procédure. Je dis « il faut faire A puis B ». Ensuite je l’envoie pour réalisation à une autre équipe, et quand j’appelle pour demander comment ça se passe :
« j’ai un problème, B marche pas
- et A ça a bien marché ? pas d’erreurs ?
- ah j’ai pas fait A, ça avait l’air pas bon.
- … »

Bon alors je suis de retour et je me suis attelé à la tâche :smiley: Je dois avouer que c’est quand même bien compliqué à comprendre pour moi donc je vais essayer de décoder pas à pas. Si je comprends bien tu as découpé le boulot en plusieurs fonctions :

  • La fonction "main" est celle qui organise l'intervention de toutes les autres fonctions. En premier lieu la fonction "readInputFile" qui ouvre le fichier de départ et découpe les blocs de données et les place dans les listes de X,Y,Z,G et T. J'ai un peu de mal à voir comment marche le "operationSeq" dans cette fonction (à quoi il sert, comment il passe à une autre valeur que "none")
  • La fonction "removeUselessCoords" où on spécifiera les règles des lignes qu'il ne faut pas imprimer dans le fichier final c'est ça ?
  • La fonction "print Me" qui détermine quel X,Y,Z utiliser dans les piles pour écrire la ligne
  • La fonction "printNewFormat" qui écrit la ligne dans le fichier final avec les variables dans le bon ordre
Jusque là est-ce que je me gourre pas trop ?

Je viens de lire le code et je comprends que ça doit être super difficile à comprendre pour quelqu’un qui débute.

Pour les fonctions c’est quasiment ça, pour l’instant removeUselessCoords est vide et est mise là pour un usage futur. PrintMe est faite pour écrire plusieurs lignes dans le fichier grâce à printNewFormat.

La chose importante qui te manquerait, c’est la notion de class. Le mieux est de te renseigner dessus dans un cours mais pour résumer très vite, une class est comme une petite “usine” qui sait créer des objet. Dans le code, en dessous de la class “OperationSeq” tu verras une description de ces objet, ils sont composés grosso modo de tableaux de X, de Y, de Z et une fonction printMe qui lui permet d’écrire dans le fichier en se basant sur ses tableaux de X, Y et Z.

readInputFile va lire le fichier, quand il tombe sur T, il utilise la classe OperationSeq pour créer un nouvel objet, on appelle ça créer une instance de la classe OperationSeq, dans le code c’est la ligne “operationSeq = OperationSeq()”.
De plus, juste au dessus on voit que si on avait déjà créé un objet, un nouveau T indique qu’on a fini avec celui-ci et on le stocke dans une liste allOperationSequences.

Si on lit autre chose qu’un T, a l’intérieur de l’objet on stocke le dernier G, on stocke si il y a un G172, et on stocke les X, Y et Z dans des liste.

Une fois la boucle déroulée, on a une liste de plusieurs objets, une par “T”. Ces objets contiennent eux même des listes de X, Y, Z, le dernier G et si on a trouvé un G172.

On parcourt cette liste en faisant faire à chaque objet son printMe pour écrire des lignes dans le fichier.
Le printMe va d’abord trouver écrire le G172 avec le premier X, Y et Z.
Ensuite il cherche son premier X, Y et Z et va écrire une ligne ces valeurs et le dernier G.
Après ça, à chaque changement de X, Y ou Z il va écrire une ligne avec la coordonnée qui a changé, les dernières autres coordonnées et le dernier G.