-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdisplay_gcode_3d.py
More file actions
486 lines (417 loc) · 20.9 KB
/
Copy pathdisplay_gcode_3d.py
File metadata and controls
486 lines (417 loc) · 20.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# display_gcode_3d.py
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import matplotlib
matplotlib.use('TkAgg') # Forcer l'utilisation du backend TkAgg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import re
import os
import glob
import numpy as np
from datetime import datetime
import sys
import traceback
# Journaliser l'environnement au démarrage
print(f"Débogage: sys.executable : {sys.executable}")
print(f"Débogage: PYTHONPATH : {os.environ.get('PYTHONPATH', 'Non défini')}")
print(f"Débogage: Version matplotlib : {matplotlib.__version__}")
# Vérification de l'importation de mpl_toolkits.mplot3d
try:
from mpl_toolkits.mplot3d import Axes3D
print("Débogage: Importation de mpl_toolkits.mplot3d réussie")
except ImportError as e:
print(f"Débogage: Échec de l'importation de Axes3D : {str(e)}")
messagebox.showerror("Erreur", f"Échec de l'importation de la bibliothèque 3D : {str(e)}\nVeuillez réinstaller matplotlib.")
sys.exit(1)
def get_latest_gcode_file(nc_dir="NC"):
"""Récupère le fichier G-code spécifié en argument ou le plus récent dans le dossier NC."""
print("Débogage: Début de get_latest_gcode_file")
try:
if len(sys.argv) > 1:
gcode_file = sys.argv[1]
if os.path.exists(gcode_file):
print(f"Débogage: Utilisation du fichier G-code spécifié : {gcode_file}")
return gcode_file
else:
print(f"Débogage: Fichier spécifié '{gcode_file}' non trouvé")
return None
nc_dir = os.path.join(os.path.dirname(__file__), nc_dir)
if not os.path.exists(nc_dir):
print(f"Débogage: Dossier NC '{nc_dir}' non trouvé")
return None
gcode_files = glob.glob(os.path.join(nc_dir, "*.nc"))
if not gcode_files:
print(f"Débogage: Aucun fichier .nc trouvé dans {nc_dir}")
return None
latest_file = max(gcode_files, key=os.path.getmtime)
print(f"Débogage: Fichier G-code le plus récent : {latest_file}")
return latest_file
except Exception as e:
print(f"Débogage: Erreur dans get_latest_gcode_file : {str(e)}")
return None
def parse_stock_dimensions(gcode):
"""Extrait les dimensions du stock depuis l'en-tête du G-code."""
print("Débogage: Début de parse_stock_dimensions")
try:
stock_pattern = re.compile(r'\[(\d+\.\d+), (\d+\.\d+), (\d+\.\d+)\]')
for line in gcode.split('\n'):
match = stock_pattern.search(line)
if match:
dimensions = float(match.group(1)), float(match.group(2)), float(match.group(3))
print(f"Débogage: Dimensions extraites : {dimensions}")
return dimensions
print("Débogage: Dimensions par défaut utilisées")
return 100.0, 100.0, 10.0
except Exception as e:
print(f"Débogage: Erreur dans parse_stock_dimensions : {str(e)}")
return 100.0, 100.0, 10.0
def interpolate_arc(start_x, start_y, start_z, end_x, end_y, end_z, i, j, direction='G03', num_points=50):
"""Interpole un arc (G02/G03) entre les points de départ et d'arrivée."""
print(f"Débogage: Interpolation d'arc de ({start_x}, {start_y}, {start_z}) à ({end_x}, {end_y}, {end_z})")
try:
center_x = start_x + i
center_y = start_y + j
radius = np.sqrt(i**2 + j**2)
if radius < 0.0001:
print(f"Débogage: Rayon nul ou trop petit pour I={i}, J={j}")
return [start_x, end_x], [start_y, end_y], [start_z, end_z]
# Vérification optionnelle : distance end au centre
dist_end = np.sqrt((end_x - center_x)**2 + (end_y - center_y)**2)
if abs(dist_end - radius) > 0.01:
print(f"Débogage: Avertissement - End point hors cercle (dist={dist_end:.2f}, radius={radius:.2f}) - Fallback à ligne droite")
return [start_x, end_x], [start_y, end_y], [start_z, end_z]
start_angle = np.arctan2(start_y - center_y, start_x - center_x)
end_angle = np.arctan2(end_y - center_y, end_x - center_x)
if direction == 'G03':
if end_angle <= start_angle:
end_angle += 2 * np.pi
else:
if end_angle >= start_angle:
end_angle -= 2 * np.pi
angles = np.linspace(start_angle, end_angle, num_points)
x_arc = [center_x + radius * np.cos(angle) for angle in angles]
y_arc = [center_y + radius * np.sin(angle) for angle in angles]
z_arc = np.linspace(start_z, end_z, num_points)
print("Débogage: Arc interpolé avec succès")
return x_arc, y_arc, z_arc
except Exception as e:
print(f"Débogage: Erreur dans interpolate_arc : {str(e)}")
return [start_x, end_x], [start_y, end_y], [start_z, end_z]
# Variable globale pour stocker le contenu du G-code chargé
current_gcode_content = ""
def plot_gcode_3d(gcode, canvas_widget, stock_x, stock_y, stock_z):
"""Trace le G-code en 3D dans une fenêtre Tkinter."""
global current_gcode_content
current_gcode_content = gcode # On stocke le G-code pour l'onglet texte
print("Débogage: Début de plot_gcode_3d")
try:
fig = plt.figure(figsize=(9, 7)) # Ratio 10:7 pour matcher fenêtre 1000x700 sans distorsion
try:
ax = fig.add_subplot(111, projection='3d')
print("Débogage: Axe 3D créé avec succès")
except Exception as e:
print(f"Débogage: Échec de la création de l'axe 3D : {str(e)}")
raise
x, y, z = [], [], []
colors = []
current_x, current_y, current_z = 0.0, 0.0, 0.0
mode = 'absolute' # G90 par défaut
x.append(current_x)
y.append(current_y)
z.append(current_z)
coord_pattern = r'([XYZEIJ])([-+]?\d*\.?\d+)'
for line in gcode.split('\n'):
line = line.strip()
if not line or line.startswith(';') or line.startswith('('):
continue
print(f"Débogage: Traitement de la ligne : {line}")
try:
g_match = re.search(r'G(\d+)', line)
coords = re.findall(coord_pattern, line)
coord_dict = {k: float(v) for k, v in coords}
i_match = re.search(r'I([-+]?\d*\.?\d+)', line)
j_match = re.search(r'J([-+]?\d*\.?\d+)', line)
if mode == 'absolute':
new_x = coord_dict.get('X', current_x)
new_y = coord_dict.get('Y', current_y)
new_z = coord_dict.get('Z', current_z)
else:
new_x = current_x + coord_dict.get('X', 0.0)
new_y = current_y + coord_dict.get('Y', 0.0)
new_z = current_z + coord_dict.get('Z', 0.0)
if g_match:
g_num = g_match.group(1)
g_code = f'G{g_num.zfill(2)}'
if g_code == 'G90':
mode = 'absolute'
print(f"Débogage: Mode passé en absolu")
continue
elif g_code == 'G91':
mode = 'relative'
print(f"Débogage: Mode passé en relatif")
continue
if g_code in ('G00', 'G01'):
color_choice = 'y' if g_code == 'G00' else 'r'
colors.append(color_choice)
x.append(new_x)
y.append(new_y)
z.append(new_z)
print(f"Débogage: [{g_code}] vers ({new_x:.3f}, {new_y:.3f}, {new_z:.3f}) mode={mode} COULEUR={color_choice}")
if 'X' not in coord_dict and 'Y' not in coord_dict and 'Z' in coord_dict:
direction = "plunge" if new_z < current_z else "retract"
print(f"Débogage: {direction.capitalize()} Z-only [{g_code}] en {color_choice}")
current_x, current_y, current_z = new_x, new_y, new_z
elif g_code in ('G02', 'G03'):
i = float(i_match.group(1)) if i_match else 0.0
j = float(j_match.group(1)) if j_match else 0.0
print(f"Débogage: Arc G{g_code[-2:]} I={i} J={j} vers ({new_x:.3f}, {new_y:.3f}, {new_z:.3f})")
x_arc, y_arc, z_arc = interpolate_arc(
current_x, current_y, current_z,
new_x, new_y, new_z,
i, j, direction=g_code, num_points=50
)
colors.extend(['b'] * (len(x_arc) - 1))
x.extend(x_arc[1:])
y.extend(y_arc[1:])
z.extend(z_arc[1:])
current_x, current_y, current_z = new_x, new_y, new_z
else:
print(f"Débogage: Commande G-code ignorée : {g_code}")
except Exception as e:
print(f"Débogage: Erreur lors du traitement de la ligne '{line}' : {str(e)}")
continue
if len(x) > 1 and len(colors) > 0:
x_min, x_max = min(x, default=0), max(x, default=stock_x)
y_min, y_max = min(y, default=0), max(y, default=stock_y)
z_min, z_max = min(z, default=0), max(z, default=stock_z)
x_margin, y_margin, z_margin = stock_x * 0.1, stock_y * 0.1, stock_z * 0.1
span_x = x_max - x_min
span_y = y_max - y_min
max_span = max(span_x, span_y)
ref_color = 'g'
ref_linewidth = 2
ax.plot([x_min, x_min + max_span], [y_min, y_min], [0, 0], color=ref_color, linewidth=ref_linewidth, label=f'Ref X/Y = {max_span:.1f} mm')
ax.plot([x_min, x_min], [y_min, y_min + max_span], [0, 0], color=ref_color, linewidth=ref_linewidth)
print(f"Débogage: Lignes de référence vertes ajoutées (longueur commune {max_span:.1f} mm)")
x_max = max(x_max, x_min + max_span)
y_max = max(y_max, y_min + max_span)
x_margin, y_margin = (x_max - x_min) * 0.1, (y_max - y_min) * 0.1
for i in range(len(x) - 1):
color = colors[i]
if not (np.isnan(x[i]) or np.isnan(x[i+1]) or np.isnan(y[i]) or np.isnan(y[i+1]) or np.isnan(z[i]) or np.isnan(z[i+1])):
ax.plot([x[i], x[i+1]], [y[i], y[i+1]], [z[i], z[i+1]], color + '-', linewidth=1)
ax.legend(loc='upper right')
ax.set_xlim(x_min - x_margin, x_max + x_margin)
ax.set_ylim(y_min - y_margin, y_max + y_margin)
ax.set_zlim(z_min - z_margin, z_max + z_margin)
try:
ax.set_box_aspect([2, 1, 0.5])
print(f"Débogage: Aspect ratio appliqué : [1, 1, 0.5]")
except AttributeError:
print("Débogage: set_box_aspect non supporté")
ax.dist = 10
fig.tight_layout(pad=1.0)
else:
print("Débogage: Pas de données valides pour le tracé 3D")
messagebox.showwarning("Avertissement", "Aucune donnée valide pour l'affichage 3D.")
return None
ax.set_xlabel('X (mm)')
ax.set_ylabel('Y (mm)')
ax.set_zlabel('Z (mm)')
ax.set_title('Visualisation 3D du G-code')
canvas_widget.figure = fig
canvas_widget.ax = ax
for widget in canvas_widget.winfo_children():
widget.destroy()
canvas = FigureCanvasTkAgg(fig, master=canvas_widget)
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
try:
toolbar = NavigationToolbar2Tk(canvas, canvas_widget)
toolbar.update()
toolbar.pack(side=tk.TOP, fill=tk.X)
canvas.draw()
except Exception as e:
print(f"Débogage: Erreur barre d'outils : {str(e)}")
canvas.draw()
print("Débogage: Visualisation 3D affichée avec succès")
return canvas
except Exception as e:
print(f"Débogage: Erreur dans plot_gcode_3d : {str(e)}")
print(f"Débogage: Traceback : {traceback.format_exc()}")
messagebox.showerror("Erreur", f"Échec du chargement du G-code : {str(e)}")
return None
def open_gcode_file():
global current_gcode_content
print("Débogage: Ouverture d'un fichier G-code")
file_path = filedialog.askopenfilename(
initialdir=os.path.join(os.path.dirname(__file__), "NC"),
title="Sélectionner un fichier G-code",
filetypes=[("Fichiers G-code", "*.nc"), ("Tous les fichiers", "*.*")]
)
if file_path:
try:
with open(file_path, 'r') as f:
gcode = f.read()
current_gcode_content = gcode
print(f"Débogage: Fichier G-code chargé : {file_path}")
file_label.configure(text=f"Fichier chargé : {os.path.basename(file_path)}")
stock_x, stock_y, stock_z = parse_stock_dimensions(gcode)
plot_gcode_3d(gcode, canvas_frame, stock_x, stock_y, stock_z)
refresh_text_tab() # Mise à jour de l'onglet texte
except Exception as e:
print(f"Débogage: Erreur dans open_gcode_file : {str(e)}")
messagebox.showerror("Erreur", f"Échec du chargement du G-code : {str(e)}")
file_label.configure(text="Erreur lors du chargement")
def save_image():
print("Débogage: Sauvegarde de l'image")
file_path = filedialog.asksaveasfilename(
initialdir=os.path.join(os.path.dirname(__file__), "NC"),
title="Sauvegarder l'image",
defaultextension=".png",
filetypes=[("Images PNG", "*.png"), ("Tous les fichiers", "*.*")]
)
if file_path:
try:
canvas_frame.figure.savefig(file_path)
messagebox.showinfo("Succès", f"Image sauvegardée dans\n{file_path}")
except Exception as e:
messagebox.showerror("Erreur", f"Échec de la sauvegarde de l'image : {str(e)}")
def rotate_view(axis, angle):
print(f"Débogage: Rotation autour de l'axe {axis} de {angle} degrés")
try:
ax = canvas_frame.ax
elev, azim = ax.elev, ax.azim
if axis == 'X':
ax.view_init(elev=elev + angle, azim=azim)
elif axis == 'Y':
ax.view_init(elev=elev, azim=azim + angle)
canvas_frame.figure.canvas.draw()
except Exception as e:
messagebox.showerror("Erreur", f"Échec de la rotation : {str(e)}")
def reset_view():
print("Débogage: Réinitialisation de la vue")
try:
ax = canvas_frame.ax
ax.view_init(elev=30, azim=45)
canvas_frame.figure.canvas.draw()
except Exception as e:
messagebox.showerror("Erreur", f"Échec de la réinitialisation de la vue : {str(e)}")
def show_help():
help_text = (
"Instructions pour interagir avec la visualisation 3D :\n"
"- Rotation : Clic gauche + glisser dans la zone de visualisation\n"
"- Zoom : Clic droit + glisser ou molette de la souris\n"
"- Pan : Clic central + glisser\n"
"- Boutons de rotation : Utilisez 'Tourner X+', 'Tourner X-', 'Tourner Y+', 'Tourner Y-' pour tourner manuellement\n"
"- Réinitialiser la vue : Menu Affichage > Réinitialiser la vue\n"
"- Barre d'outils : Utilisez les icônes en haut pour zoom, pan, rotation, etc.\n"
"- Couleurs : Jaune (G00 rapide), Rouge (G01 linéaire), Bleu (G02/G03 arcs)"
)
messagebox.showinfo("Aide", help_text)
def about():
messagebox.showinfo("À propos", "Visualisation 3D du G-code\nVersion 2.0 (avec onglets)\nDéveloppé pour Gcode-Generator")
def refresh_text_tab():
"""Met à jour le contenu de l'onglet texte avec le G-code actuel."""
text_widget.delete('1.0', tk.END)
text_widget.insert('1.0', current_gcode_content)
def update_visualization():
global current_gcode_content
print("Débogage: Début de update_visualization")
gcode_file = get_latest_gcode_file()
if not gcode_file:
messagebox.showerror("Erreur", "Aucun fichier G-code trouvé dans le dossier NC.")
file_label.configure(text="Aucun fichier chargé")
return
try:
with open(gcode_file, 'r') as f:
gcode = f.read()
current_gcode_content = gcode
file_label.configure(text=f"Fichier chargé : {os.path.basename(gcode_file)}")
stock_x, stock_y, stock_z = parse_stock_dimensions(gcode)
plot_gcode_3d(gcode, canvas_frame, stock_x, stock_y, stock_z)
refresh_text_tab() # Mise à jour de l'onglet texte
except Exception as e:
messagebox.showerror("Erreur", f"Échec du chargement du G-code : {str(e)}")
file_label.configure(text="Erreur lors du chargement")
def on_closing():
print("Débogage: Fermeture de l'application")
try:
plt.close('all')
root.destroy()
except Exception as e:
print(f"Débogage: Erreur lors de la fermeture : {str(e)}")
# ==================== INTERFACE GRAPHIQUE ====================
try:
root = tk.Tk()
root.title("Visualisation 3D du G-code")
root.geometry("1100x750+950+100") # Un peu plus large pour les onglets
# Barre de menus
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Fichier", menu=file_menu)
file_menu.add_command(label="Ouvrir...", command=open_gcode_file)
file_menu.add_command(label="Recharger", command=update_visualization)
file_menu.add_command(label="Sauvegarder image...", command=save_image)
file_menu.add_separator()
file_menu.add_command(label="Quitter", command=on_closing)
view_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Affichage", menu=view_menu)
view_menu.add_command(label="Réinitialiser la vue", command=reset_view)
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Aide", menu=help_menu)
help_menu.add_command(label="Instructions", command=show_help)
help_menu.add_command(label="À propos", command=about)
style = ttk.Style()
style.configure("TFrame", borderwidth=2, relief="groove")
# Notebook (onglets)
notebook = ttk.Notebook(root)
notebook.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
# Onglet Vue 3D
tab_3d = ttk.Frame(notebook)
notebook.add(tab_3d, text="Vue 3D")
canvas_frame = ttk.Frame(tab_3d)
canvas_frame.pack(fill=tk.BOTH, expand=True)
# Onglet G-code texte
tab_text = ttk.Frame(notebook)
notebook.add(tab_text, text="G-code texte")
text_frame = ttk.Frame(tab_text)
text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
text_widget = tk.Text(text_frame, wrap="none", font=("Courier", 10))
v_scroll = ttk.Scrollbar(text_frame, orient="vertical", command=text_widget.yview)
h_scroll = ttk.Scrollbar(text_frame, orient="horizontal", command=text_widget.xview)
text_widget.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)
text_widget.grid(row=0, column=0, sticky="nsew")
v_scroll.grid(row=0, column=1, sticky="ns")
h_scroll.grid(row=1, column=0, sticky="ew")
text_frame.grid_rowconfigure(0, weight=1)
text_frame.grid_columnconfigure(0, weight=1)
# Contrôles (boutons de rotation, etc.)
control_frame = ttk.Frame(root)
control_frame.grid(row=1, column=0, padx=5, pady=5, sticky="ew")
ttk.Button(control_frame, text="Tourner X+", command=lambda: rotate_view('X', 15)).grid(row=0, column=0, padx=5)
ttk.Button(control_frame, text="Tourner X-", command=lambda: rotate_view('X', -15)).grid(row=0, column=1, padx=5)
ttk.Button(control_frame, text="Tourner Y+", command=lambda: rotate_view('Y', 15)).grid(row=0, column=2, padx=5)
ttk.Button(control_frame, text="Tourner Y-", command=lambda: rotate_view('Y', -15)).grid(row=0, column=3, padx=5)
file_label = ttk.Label(root, text="Aucun fichier chargé")
file_label.grid(row=2, column=0, padx=5, pady=5)
reload_button = ttk.Button(root, text="Recharger le dernier G-code", command=update_visualization)
reload_button.grid(row=3, column=0, padx=5, pady=10)
# Configuration du grid principal
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
# Rafraîchir l'onglet texte quand on y passe (au cas où le contenu aurait changé)
def on_tab_changed(event):
if notebook.index(notebook.select()) == 1: # onglet texte
refresh_text_tab()
notebook.bind("<<NotebookTabChanged>>", on_tab_changed)
root.protocol("WM_DELETE_WINDOW", on_closing)
# Chargement initial
update_visualization()
root.mainloop()
except Exception as e:
print(f"Débogage: Erreur lors de l'initialisation de la fenêtre : {str(e)}")
print(f"Débogage: Traceback : {traceback.format_exc()}")
messagebox.showerror("Erreur", f"Échec de l'initialisation : {str(e)}")
sys.exit(1)