tube aligner
This commit is contained in:
@@ -4,15 +4,6 @@ GCode pour piloter la L2544 Laser Engraving Machine
|
||||
GRBLController:
|
||||
Commande uniquement les mouvements (X, Y)
|
||||
Le mode absolue est retenu
|
||||
|
||||
GridScanner
|
||||
Balayage complet de la grille d'éprouvettes en mode serpentin
|
||||
|
||||
Usage:
|
||||
grbl = GRBLController()
|
||||
scan = GridScanner(grbl, xbase=100, ybase=100, duration=5)
|
||||
scan.start()
|
||||
|
||||
Created on 25 mars 2026
|
||||
|
||||
@author: denis@miraceti.net
|
||||
@@ -170,6 +161,7 @@ class GRBLController:
|
||||
if "MPos" in status:
|
||||
mpos = status.split("MPos:")[1].split("|")[0]
|
||||
x, y, *_ = mpos.split(",")
|
||||
self._state(state='Mpos', msg=f"pos >>> ({x}, {y})")
|
||||
return float(x), float(y)
|
||||
return None, None
|
||||
|
||||
@@ -188,13 +180,20 @@ class GRBLController:
|
||||
break
|
||||
self.wait_for(0.1)
|
||||
|
||||
def send_command(self, cmd):
|
||||
self.send(cmd)
|
||||
self.wait_idle()
|
||||
|
||||
def move_to(self, x, y, feed=1000):
|
||||
x, y = self._clamp(x, y)
|
||||
#cmd = f"G0 X{x:.2f} Y{y:.2f} F{feed}" # feed is not updated in G0 mode
|
||||
cmd = f"G53 G1 X{x:.2f} Y{y:.2f} F{feed}"
|
||||
self.send(cmd)
|
||||
self.wait_idle()
|
||||
|
||||
self.send_command(cmd)
|
||||
|
||||
def move_relative(self, dx=0, dy=0, feed=1000):
|
||||
x, y = self.get_mpos() # Position actuelle
|
||||
self.move_to(x + dx, y + dy, feed=feed)
|
||||
|
||||
def move_relative_(self, dx=0, dy=0, feed=1000):
|
||||
self.send("G91") # Mode relatif
|
||||
cmd = f"G0 X{dx} Y{dy} F{feed}"
|
||||
@@ -202,10 +201,6 @@ class GRBLController:
|
||||
self.send("G90") # Retour en mode absolu
|
||||
self.wait_idle()
|
||||
|
||||
def move_relative(self, dx=0, dy=0, feed=1000):
|
||||
x, y = self.get_mpos() # Position actuelle
|
||||
self.move_to(x + dx, y + dy)
|
||||
|
||||
def go_origin(self, feed=1000):
|
||||
self.move_to(0, 0, feed=feed)
|
||||
self.wait_for(2.0)
|
||||
@@ -230,131 +225,3 @@ class GRBLController:
|
||||
|
||||
def close(self):
|
||||
self.ser.close()
|
||||
|
||||
|
||||
class GridScanner:
|
||||
|
||||
def __init__(self, grbl, process=None, **config):
|
||||
'''
|
||||
xbase # Position X de départ (col 0) en mm
|
||||
ybase # Position Y de départ (row 0) en mm
|
||||
cols # Nombre de colonnes
|
||||
rows # Nombre de lignes
|
||||
dx # Pas entre colonnes en mm
|
||||
dy # Pas entre lignes en mm
|
||||
duration # Durée de filmage par éprouvette en secondes
|
||||
feed # Vitesse de déplacement entre éprouvettes (mm/min)
|
||||
'''
|
||||
self.grbl = grbl
|
||||
self.process = process
|
||||
|
||||
self.position = config.get('position', 'HG')
|
||||
self.xbase = config.get('xbase', 50)
|
||||
self.ybase = config.get('ybase', 50)
|
||||
self.cols = config.get('cols', 6)
|
||||
self.rows = config.get('rows', 4)
|
||||
self.dx = config.get('dx', 20)
|
||||
self.dy = config.get('dy', 19)
|
||||
self.feed = config.get('feed', 1000)
|
||||
self.duration = config.get('duration', 120) # secondes
|
||||
self.xnext = config.get('xnext', 50)
|
||||
self.ynext = config.get('ynext', 50)
|
||||
|
||||
row_to_char = config.get('row_to_char', 'D,C,B,A')
|
||||
self.row_to_char = row_to_char.split(',')
|
||||
self.stop_playing = None
|
||||
|
||||
|
||||
def halt(self):
|
||||
self.process.tag.record = False
|
||||
return self.stop_playing.set()
|
||||
|
||||
def _capture(self, uuid: str, duration: float, stop_running: Optional[threading.Event]) -> None:
|
||||
"""
|
||||
Déclenche la caméra ArduCam et attend la fin de l'acquisition.
|
||||
"""
|
||||
print(f"# démarrer l'enregistrement {uuid}")
|
||||
self.process.cam.on_well_change()
|
||||
|
||||
self.process.tag.uuid = uuid
|
||||
self.process.tag.record = True
|
||||
|
||||
start = time.monotonic()
|
||||
while not stop_running.is_set():
|
||||
if time.monotonic() - start > duration:
|
||||
break
|
||||
self.grbl.wait_for(1.0)
|
||||
|
||||
print("# arrêter l'enregistrement")
|
||||
self.process.tag.record = False
|
||||
self.process.tag.uuid = None
|
||||
|
||||
def start(self, xnext=None, ynext=None, position=None):
|
||||
"""
|
||||
Balayage complet de la grille d'éprouvettes en mode serpentin.
|
||||
|
||||
Parcours :
|
||||
- Lignes paires (0, 2) : gauche → droite (col 0 → col 5)
|
||||
- Lignes impaires (1, 3) : droite → gauche (col 5 → col 0)
|
||||
|
||||
Le déplacement entre éprouvettes se fait en mode absolu via move_to().
|
||||
La caméra filme pendant `` secondes sur chaque position.
|
||||
|
||||
Grille : 6 colonnes × 4 lignes = 24 éprouvettes
|
||||
- x = XBASE + col * PAS_X
|
||||
- y = YBASE + row * PAS_Y
|
||||
"""
|
||||
try:
|
||||
if xnext is None:
|
||||
xnext = self.xnext
|
||||
if ynext is None:
|
||||
ynext = self.ynext
|
||||
if position is None:
|
||||
position = self.position
|
||||
|
||||
max_cells = self.cols * self.rows
|
||||
cell = 0
|
||||
|
||||
logger.info("Début du scan serpentin : %d éprouvettes, %d s/éprouvette, durée totale estimée : %d min",
|
||||
max_cells,
|
||||
self.duration,
|
||||
(max_cells * self.duration) // 60,
|
||||
)
|
||||
|
||||
self.stop_playing = threading.Event()
|
||||
for row in range(self.rows):
|
||||
if self.stop_playing.is_set():
|
||||
break
|
||||
|
||||
# Ordre des colonnes selon la parité de la ligne (serpentin)
|
||||
if row % 2 == 0:
|
||||
# Ligne paire : gauche → droite
|
||||
cols = range(self.cols)
|
||||
else:
|
||||
# Ligne impaire : droite → gauche
|
||||
cols = range(self.cols - 1, -1, -1)
|
||||
|
||||
for col in cols:
|
||||
if self.stop_playing.is_set():
|
||||
break
|
||||
# Calcul de la position absolue en mm
|
||||
x = self.xbase + col * self.dx
|
||||
y = self.ybase + row * self.dy
|
||||
cell += 1
|
||||
|
||||
logger.info(
|
||||
"[%02d/%02d] row=%d col=%d → X=%.1f mm Y=%.1f mm",
|
||||
cell, max_cells, row, col, x, y,
|
||||
)
|
||||
|
||||
self.grbl.move_to(x, y, feed=self.feed)
|
||||
|
||||
uuid = f'{self.process.tag.session}-{position}-{self.row_to_char[row]}{col+1}'
|
||||
self._capture(uuid, self.duration, self.stop_playing)
|
||||
|
||||
# Retour à nexr après le scan
|
||||
logger.info("Scan terminé — retour à l'origine (X=%.1f Y=%.1f)", xnext, ynext)
|
||||
self.grbl.move_to(xnext, ynext, feed=self.feed*2)
|
||||
except Exception as e:
|
||||
logger.error(f"scan error: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user