puda-drivers 0.0.6__py3-none-any.whl → 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- puda_drivers/core/__init__.py +2 -1
- puda_drivers/core/position.py +378 -0
- puda_drivers/core/serialcontroller.py +33 -18
- puda_drivers/labware/__init__.py +9 -0
- puda_drivers/labware/labware.py +157 -0
- puda_drivers/labware/opentrons_96_tiprack_300ul.json +1020 -0
- puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +141 -0
- puda_drivers/labware/trash_bin.json +41 -0
- puda_drivers/machines/__init__.py +3 -0
- puda_drivers/machines/first.py +242 -0
- puda_drivers/move/__init__.py +3 -0
- puda_drivers/move/deck.py +47 -0
- puda_drivers/move/gcode.py +143 -134
- puda_drivers/transfer/liquid/sartorius/sartorius.py +88 -79
- {puda_drivers-0.0.6.dist-info → puda_drivers-0.0.8.dist-info}/METADATA +2 -2
- puda_drivers-0.0.8.dist-info/RECORD +27 -0
- puda_drivers-0.0.6.dist-info/RECORD +0 -18
- {puda_drivers-0.0.6.dist-info → puda_drivers-0.0.8.dist-info}/WHEEL +0 -0
- {puda_drivers-0.0.6.dist-info → puda_drivers-0.0.8.dist-info}/licenses/LICENSE +0 -0
puda_drivers/move/gcode.py
CHANGED
|
@@ -7,12 +7,14 @@ absolute coordinates, with relative moves converted to absolute internally.
|
|
|
7
7
|
Supports homing and position synchronization.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import time
|
|
10
11
|
import re
|
|
11
12
|
import logging
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
from typing import Optional, Dict, Tuple, Union
|
|
14
15
|
|
|
15
16
|
from puda_drivers.core.serialcontroller import SerialController
|
|
17
|
+
from puda_drivers.core.position import Position
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
@dataclass
|
|
@@ -90,12 +92,7 @@ class GCodeController(SerialController):
|
|
|
90
92
|
)
|
|
91
93
|
|
|
92
94
|
# Tracks internal position state
|
|
93
|
-
self._current_position
|
|
94
|
-
"X": 0.0,
|
|
95
|
-
"Y": 0.0,
|
|
96
|
-
"Z": 0.0,
|
|
97
|
-
"A": 0.0,
|
|
98
|
-
}
|
|
95
|
+
self._current_position = Position(x=0.0, y=0.0, z=0.0, a=0.0)
|
|
99
96
|
self._feed: int = feed
|
|
100
97
|
self._z_feed: int = z_feed
|
|
101
98
|
|
|
@@ -143,7 +140,7 @@ class GCodeController(SerialController):
|
|
|
143
140
|
self._feed = new_feed
|
|
144
141
|
self._logger.debug("Feed rate set to: %s mm/min.", self._feed)
|
|
145
142
|
|
|
146
|
-
def _build_command(self, command: str) -> str:
|
|
143
|
+
def _build_command(self, command: str, value: Optional[str] = None) -> str:
|
|
147
144
|
"""
|
|
148
145
|
Build a G-code command with terminator.
|
|
149
146
|
|
|
@@ -155,14 +152,14 @@ class GCodeController(SerialController):
|
|
|
155
152
|
"""
|
|
156
153
|
return f"{command}{self.PROTOCOL_TERMINATOR}"
|
|
157
154
|
|
|
158
|
-
def
|
|
155
|
+
def _wait_for_move(self) -> None:
|
|
159
156
|
"""
|
|
160
157
|
Wait for the current move to complete (M400 command).
|
|
161
158
|
|
|
162
159
|
This sends the M400 command which waits for all moves in the queue to complete
|
|
163
160
|
before continuing. This ensures that position updates are accurate.
|
|
164
161
|
"""
|
|
165
|
-
self.execute(
|
|
162
|
+
self.execute("M400")
|
|
166
163
|
|
|
167
164
|
def _validate_axis(self, axis: str) -> str:
|
|
168
165
|
"""
|
|
@@ -191,51 +188,45 @@ class GCodeController(SerialController):
|
|
|
191
188
|
|
|
192
189
|
def _validate_move_positions(
|
|
193
190
|
self,
|
|
194
|
-
|
|
195
|
-
y: Optional[float] = None,
|
|
196
|
-
z: Optional[float] = None,
|
|
197
|
-
a: Optional[float] = None,
|
|
191
|
+
position: Position,
|
|
198
192
|
) -> None:
|
|
199
193
|
"""
|
|
200
194
|
Validate that move positions are within axis limits.
|
|
201
195
|
|
|
202
|
-
Only validates axes that are
|
|
196
|
+
Only validates axes that are present in the position. Raises ValueError
|
|
203
197
|
if any position is outside the configured limits.
|
|
204
198
|
|
|
205
199
|
Args:
|
|
206
|
-
|
|
207
|
-
y: Target Y position (optional)
|
|
208
|
-
z: Target Z position (optional)
|
|
209
|
-
a: Target A position (optional)
|
|
200
|
+
position: Position object to validate
|
|
210
201
|
|
|
211
202
|
Raises:
|
|
212
203
|
ValueError: If any position is outside the axis limits
|
|
213
204
|
"""
|
|
214
|
-
if x
|
|
205
|
+
if position.has_axis("x"):
|
|
215
206
|
if "X" in self._axis_limits:
|
|
216
207
|
try:
|
|
217
|
-
self._axis_limits["X"].validate(x)
|
|
208
|
+
self._axis_limits["X"].validate(position.x)
|
|
218
209
|
except ValueError as e:
|
|
219
210
|
self._logger.error("Move validation failed for X axis: %s", e)
|
|
220
211
|
raise
|
|
221
|
-
if y
|
|
212
|
+
if position.has_axis("y"):
|
|
222
213
|
if "Y" in self._axis_limits:
|
|
223
214
|
try:
|
|
224
|
-
self._axis_limits["Y"].validate(y)
|
|
215
|
+
self._axis_limits["Y"].validate(position.y)
|
|
225
216
|
except ValueError as e:
|
|
226
217
|
self._logger.error("Move validation failed for Y axis: %s", e)
|
|
227
218
|
raise
|
|
228
|
-
if z
|
|
219
|
+
if position.has_axis("z"):
|
|
229
220
|
if "Z" in self._axis_limits:
|
|
230
221
|
try:
|
|
231
|
-
self._axis_limits["Z"].validate(z)
|
|
222
|
+
self._axis_limits["Z"].validate(position.z)
|
|
232
223
|
except ValueError as e:
|
|
233
224
|
self._logger.error("Move validation failed for Z axis: %s", e)
|
|
234
225
|
raise
|
|
235
|
-
if a
|
|
226
|
+
if position.has_axis("a"):
|
|
236
227
|
if "A" in self._axis_limits:
|
|
237
228
|
try:
|
|
238
|
-
self._axis_limits["A"].validate(a)
|
|
229
|
+
self._axis_limits["A"].validate(position.a)
|
|
239
230
|
except ValueError as e:
|
|
240
231
|
self._logger.error("Move validation failed for A axis: %s", e)
|
|
241
232
|
raise
|
|
@@ -305,15 +296,14 @@ class GCodeController(SerialController):
|
|
|
305
296
|
home_target = "All"
|
|
306
297
|
|
|
307
298
|
self._logger.info("[%s] homing axis/axes: %s **", cmd, home_target)
|
|
308
|
-
self.execute(
|
|
309
|
-
self._logger.info("Homing of %s completed
|
|
299
|
+
self.execute(cmd)
|
|
300
|
+
self._logger.info("Homing of %s completed.\n", home_target)
|
|
310
301
|
|
|
311
302
|
# Update internal position (optimistic zeroing)
|
|
312
303
|
if axis:
|
|
313
|
-
self._current_position[axis] = 0.0
|
|
304
|
+
self._current_position[axis.lower()] = 0.0
|
|
314
305
|
else:
|
|
315
|
-
|
|
316
|
-
self._current_position[key] = 0.0
|
|
306
|
+
self._current_position = Position(x=0.0, y=0.0, z=0.0, a=0.0)
|
|
317
307
|
|
|
318
308
|
self._logger.debug(
|
|
319
309
|
"Internal position updated (optimistically zeroed) to %s",
|
|
@@ -322,99 +312,118 @@ class GCodeController(SerialController):
|
|
|
322
312
|
|
|
323
313
|
def move_absolute(
|
|
324
314
|
self,
|
|
325
|
-
|
|
326
|
-
y: Optional[float] = None,
|
|
327
|
-
z: Optional[float] = None,
|
|
328
|
-
a: Optional[float] = None,
|
|
315
|
+
position: Position,
|
|
329
316
|
feed: Optional[int] = None,
|
|
330
|
-
) ->
|
|
317
|
+
) -> Position:
|
|
331
318
|
"""
|
|
332
319
|
Move to an absolute position (G90 + G1 command).
|
|
333
320
|
|
|
334
321
|
Args:
|
|
335
|
-
|
|
336
|
-
y: Target Y position (optional)
|
|
337
|
-
z: Target Z position (optional)
|
|
338
|
-
a: Target A position (optional)
|
|
322
|
+
position: Target Position object. Missing axes will use current position values.
|
|
339
323
|
feed: Feed rate for this move (optional, uses current feed if not specified)
|
|
340
324
|
|
|
341
325
|
Raises:
|
|
342
326
|
ValueError: If any position is outside the axis limits
|
|
343
|
-
"""
|
|
344
|
-
# Validate positions before executing move
|
|
345
|
-
self._validate_move_positions(x=x, y=y, z=z, a=a)
|
|
346
327
|
|
|
328
|
+
Examples:
|
|
329
|
+
>>> pos = Position(x=10, y=20, z=30)
|
|
330
|
+
>>> controller.move_absolute(position=pos)
|
|
331
|
+
>>> # Partial position (only x and y specified)
|
|
332
|
+
>>> pos = Position(x=10, y=20)
|
|
333
|
+
>>> controller.move_absolute(position=pos) # z and a use current values
|
|
334
|
+
"""
|
|
347
335
|
# Fill in missing axes with current positions
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
336
|
+
final_x = position.x if position.has_axis("x") else self._current_position.x
|
|
337
|
+
final_y = position.y if position.has_axis("y") else self._current_position.y
|
|
338
|
+
final_z = position.z if position.has_axis("z") else self._current_position.z
|
|
339
|
+
final_a = position.a if position.has_axis("a") else self._current_position.a
|
|
340
|
+
|
|
341
|
+
# Create final position for validation and execution
|
|
342
|
+
final_position = Position(x=final_x, y=final_y, z=final_z, a=final_a)
|
|
343
|
+
|
|
344
|
+
# Validate positions before executing move
|
|
345
|
+
self._validate_move_positions(position=final_position)
|
|
352
346
|
|
|
353
347
|
feed_rate = feed if feed is not None else self._feed
|
|
354
348
|
self._logger.info(
|
|
355
349
|
"Preparing absolute move to X:%s, Y:%s, Z:%s, A:%s at F:%s",
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
350
|
+
final_x,
|
|
351
|
+
final_y,
|
|
352
|
+
final_z,
|
|
353
|
+
final_a,
|
|
360
354
|
feed_rate,
|
|
361
355
|
)
|
|
362
356
|
|
|
363
357
|
return self._execute_move(
|
|
364
|
-
position=
|
|
358
|
+
position=final_position,
|
|
365
359
|
feed=feed_rate
|
|
366
360
|
)
|
|
367
361
|
|
|
368
362
|
def move_relative(
|
|
369
363
|
self,
|
|
370
|
-
|
|
371
|
-
y: Optional[float] = None,
|
|
372
|
-
z: Optional[float] = None,
|
|
373
|
-
a: Optional[float] = None,
|
|
364
|
+
position: Position,
|
|
374
365
|
feed: Optional[int] = None,
|
|
375
|
-
) ->
|
|
366
|
+
) -> Position:
|
|
376
367
|
"""
|
|
377
368
|
Move relative to the current position (converted to absolute move internally).
|
|
378
369
|
|
|
379
370
|
Args:
|
|
380
|
-
|
|
381
|
-
y: Relative Y movement (optional)
|
|
382
|
-
z: Relative Z movement (optional)
|
|
383
|
-
a: Relative A movement (optional)
|
|
371
|
+
position: Relative Position object. Only axes specified in position will move.
|
|
384
372
|
feed: Feed rate for this move (optional, uses current feed if not specified)
|
|
385
373
|
|
|
386
374
|
Raises:
|
|
387
375
|
ValueError: If any resulting absolute position is outside the axis limits
|
|
388
|
-
|
|
376
|
+
|
|
377
|
+
Examples:
|
|
378
|
+
>>> # Move relative in x and y only
|
|
379
|
+
>>> pos = Position(x=10, y=20)
|
|
380
|
+
>>> controller.move_relative(position=pos)
|
|
381
|
+
>>> # Move relative in all axes
|
|
382
|
+
>>> pos = Position(x=10, y=20, z=5, a=0)
|
|
383
|
+
>>> controller.move_relative(position=pos)
|
|
384
|
+
"""
|
|
385
|
+
# Convert relative movements to absolute positions
|
|
386
|
+
# Only move axes that are specified in the position
|
|
387
|
+
abs_x = self._current_position.x
|
|
388
|
+
abs_y = self._current_position.y
|
|
389
|
+
abs_z = self._current_position.z
|
|
390
|
+
abs_a = self._current_position.a
|
|
391
|
+
|
|
392
|
+
if position.has_axis("x"):
|
|
393
|
+
abs_x = self._current_position.x + position.x
|
|
394
|
+
if position.has_axis("y"):
|
|
395
|
+
abs_y = self._current_position.y + position.y
|
|
396
|
+
if position.has_axis("z"):
|
|
397
|
+
abs_z = self._current_position.z + position.z
|
|
398
|
+
if position.has_axis("a"):
|
|
399
|
+
abs_a = self._current_position.a + position.a
|
|
400
|
+
|
|
401
|
+
# Create absolute position for validation and execution
|
|
402
|
+
abs_position = Position(x=abs_x, y=abs_y, z=abs_z, a=abs_a)
|
|
403
|
+
|
|
404
|
+
# Validate absolute positions before executing move
|
|
405
|
+
self._validate_move_positions(position=abs_position)
|
|
406
|
+
|
|
389
407
|
feed_rate = feed if feed is not None else self._feed
|
|
390
408
|
self._logger.info(
|
|
391
409
|
"Preparing relative move by dX:%s, dY:%s, dZ:%s, dA:%s at F:%s",
|
|
392
|
-
x,
|
|
393
|
-
y,
|
|
394
|
-
z,
|
|
395
|
-
a,
|
|
410
|
+
position.x if position.has_axis("x") else 0,
|
|
411
|
+
position.y if position.has_axis("y") else 0,
|
|
412
|
+
position.z if position.has_axis("z") else 0,
|
|
413
|
+
position.a if position.has_axis("a") else 0,
|
|
396
414
|
feed_rate,
|
|
397
415
|
)
|
|
398
416
|
|
|
399
|
-
# Convert relative movements to absolute positions, filling in missing axes with current position
|
|
400
|
-
abs_x = (self._current_position["X"] + x) if x is not None else self._current_position["X"]
|
|
401
|
-
abs_y = (self._current_position["Y"] + y) if y is not None else self._current_position["Y"]
|
|
402
|
-
abs_z = (self._current_position["Z"] + z) if z is not None else self._current_position["Z"]
|
|
403
|
-
abs_a = (self._current_position["A"] + a) if a is not None else self._current_position["A"]
|
|
404
|
-
|
|
405
|
-
# Validate absolute positions before executing move
|
|
406
|
-
self._validate_move_positions(x=abs_x, y=abs_y, z=abs_z, a=abs_a)
|
|
407
|
-
|
|
408
417
|
return self._execute_move(
|
|
409
|
-
position=
|
|
418
|
+
position=abs_position,
|
|
410
419
|
feed=feed_rate
|
|
411
420
|
)
|
|
412
421
|
|
|
413
422
|
def _execute_move(
|
|
414
423
|
self,
|
|
415
|
-
position:
|
|
424
|
+
position: Position,
|
|
416
425
|
feed: int,
|
|
417
|
-
) ->
|
|
426
|
+
) -> Position:
|
|
418
427
|
"""
|
|
419
428
|
Internal helper for executing G1 move commands with safe movement pattern.
|
|
420
429
|
All coordinates are treated as absolute positions.
|
|
@@ -425,15 +434,15 @@ class GCodeController(SerialController):
|
|
|
425
434
|
3. Finally move Z and A back to original position (or target if specified)
|
|
426
435
|
|
|
427
436
|
Args:
|
|
428
|
-
position:
|
|
437
|
+
position: Position with absolute positions for X, Y, Z, A axes
|
|
429
438
|
feed: Feed rate for the move
|
|
430
439
|
"""
|
|
431
440
|
# Check if any movement is needed
|
|
432
|
-
needs_x_move = abs(position
|
|
433
|
-
needs_y_move = abs(position
|
|
434
|
-
needs_z_move = abs(position
|
|
435
|
-
needs_a_move = abs(position
|
|
436
|
-
|
|
441
|
+
needs_x_move = abs(position.x - self._current_position.x) > self.TOLERANCE
|
|
442
|
+
needs_y_move = abs(position.y - self._current_position.y) > self.TOLERANCE
|
|
443
|
+
needs_z_move = abs(position.z - self._current_position.z) > self.TOLERANCE
|
|
444
|
+
needs_a_move = abs(position.a - self._current_position.a) > self.TOLERANCE
|
|
445
|
+
|
|
437
446
|
if not (needs_x_move or needs_y_move or needs_z_move or needs_a_move):
|
|
438
447
|
self._logger.warning(
|
|
439
448
|
"Move command issued without any axis movement. Skipping transmission."
|
|
@@ -447,73 +456,70 @@ class GCodeController(SerialController):
|
|
|
447
456
|
raise ValueError("Move command issued with both Z and A movement. This is not supported.")
|
|
448
457
|
|
|
449
458
|
# Step 0: Ensure absolute mode is active
|
|
450
|
-
self.execute(
|
|
451
|
-
needs_xy_move = needs_x_move or needs_y_move
|
|
459
|
+
self.execute("G90")
|
|
452
460
|
|
|
453
461
|
# Step 1: Move Z and A to SAFE_MOVE_HEIGHT if XY movement is needed
|
|
454
|
-
if
|
|
455
|
-
self._logger.
|
|
462
|
+
if needs_x_move or needs_y_move:
|
|
463
|
+
self._logger.debug(
|
|
456
464
|
"Safe move: Raising Z and A to safe height (%s) before XY movement", self.SAFE_MOVE_HEIGHT
|
|
457
465
|
)
|
|
458
466
|
move_cmd = f"G1 Z-5 A-5 F{self._z_feed}"
|
|
459
|
-
self.execute(
|
|
460
|
-
self.
|
|
461
|
-
self._current_position
|
|
462
|
-
self._current_position
|
|
467
|
+
self.execute(move_cmd)
|
|
468
|
+
self._wait_for_move()
|
|
469
|
+
self._current_position.z = self.SAFE_MOVE_HEIGHT
|
|
470
|
+
self._current_position.a = self.SAFE_MOVE_HEIGHT
|
|
463
471
|
self._logger.debug("Z and A moved to safe height (%s)", self.SAFE_MOVE_HEIGHT)
|
|
464
472
|
|
|
473
|
+
# update needs_z_move and needs_a_move based on the current position
|
|
474
|
+
needs_z_move = abs(position.z - self._current_position.z) > self.TOLERANCE
|
|
475
|
+
needs_a_move = abs(position.a - self._current_position.a) > self.TOLERANCE
|
|
476
|
+
|
|
465
477
|
# Step 2: Move X, Y to target
|
|
466
|
-
if
|
|
478
|
+
if needs_x_move or needs_y_move:
|
|
467
479
|
move_cmd = "G1"
|
|
468
480
|
if needs_x_move:
|
|
469
|
-
move_cmd += f" X{position
|
|
481
|
+
move_cmd += f" X{position.x}"
|
|
470
482
|
if needs_y_move:
|
|
471
|
-
move_cmd += f" Y{position
|
|
483
|
+
move_cmd += f" Y{position.y}"
|
|
472
484
|
move_cmd += f" F{feed}"
|
|
473
485
|
|
|
474
|
-
self._logger.
|
|
475
|
-
self.execute(
|
|
476
|
-
self.
|
|
486
|
+
self._logger.debug("Executing XY move command: %s", move_cmd)
|
|
487
|
+
self.execute(move_cmd)
|
|
488
|
+
self._wait_for_move()
|
|
477
489
|
|
|
478
490
|
# Update position for moved axes
|
|
479
491
|
if needs_x_move:
|
|
480
|
-
self._current_position
|
|
492
|
+
self._current_position.x = position.x
|
|
481
493
|
if needs_y_move:
|
|
482
|
-
self._current_position
|
|
494
|
+
self._current_position.y = position.y
|
|
483
495
|
|
|
484
496
|
# Step 3: Move Z and A back to original position (or target if specified)
|
|
485
497
|
if needs_z_move:
|
|
486
|
-
move_cmd = f"G1 Z{position
|
|
487
|
-
self.execute(
|
|
488
|
-
self.
|
|
498
|
+
move_cmd = f"G1 Z{position.z} F{self._z_feed}"
|
|
499
|
+
self.execute(move_cmd)
|
|
500
|
+
self._wait_for_move()
|
|
501
|
+
self._current_position.z = position.z
|
|
489
502
|
elif needs_a_move:
|
|
490
|
-
move_cmd = f"G1 A{position
|
|
491
|
-
self.execute(
|
|
492
|
-
self.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
self._logger.info(
|
|
496
|
-
"Move complete. Final position: %s", self._current_position
|
|
497
|
-
)
|
|
503
|
+
move_cmd = f"G1 A{position.a} F{self._z_feed}"
|
|
504
|
+
self.execute(move_cmd)
|
|
505
|
+
self._wait_for_move()
|
|
506
|
+
self._current_position.a = position.a
|
|
498
507
|
self._logger.debug("New internal position: %s", self._current_position)
|
|
499
|
-
|
|
500
|
-
# Step 4: Post-move position synchronization check
|
|
501
|
-
self.sync_position()
|
|
502
508
|
|
|
503
509
|
return self._current_position
|
|
504
510
|
|
|
505
|
-
def query_position(self) ->
|
|
511
|
+
def query_position(self) -> Position:
|
|
506
512
|
"""
|
|
507
513
|
Query the current machine position (M114 command).
|
|
508
514
|
|
|
509
515
|
Returns:
|
|
510
|
-
|
|
516
|
+
Position containing X, Y, Z, and A positions
|
|
511
517
|
|
|
512
518
|
Note:
|
|
513
|
-
Returns an empty
|
|
519
|
+
Returns an empty Position if the query fails or no positions are found.
|
|
514
520
|
"""
|
|
515
521
|
self._logger.info("Querying current machine position (M114).")
|
|
516
|
-
res: str = self.execute(
|
|
522
|
+
res: str = self.execute("M114")
|
|
517
523
|
|
|
518
524
|
# Extract position values using regex
|
|
519
525
|
pattern = re.compile(r"([XYZA]):(-?\d+\.\d+)")
|
|
@@ -523,7 +529,7 @@ class GCodeController(SerialController):
|
|
|
523
529
|
|
|
524
530
|
for axis, value_str in matches:
|
|
525
531
|
try:
|
|
526
|
-
position_data[axis] = float(value_str)
|
|
532
|
+
position_data[axis.lower()] = float(value_str)
|
|
527
533
|
except ValueError:
|
|
528
534
|
self._logger.error(
|
|
529
535
|
"Failed to convert position value '%s' for axis %s to float.",
|
|
@@ -532,9 +538,11 @@ class GCodeController(SerialController):
|
|
|
532
538
|
)
|
|
533
539
|
continue
|
|
534
540
|
|
|
535
|
-
|
|
541
|
+
position = Position.from_dict(position_data)
|
|
542
|
+
self._logger.info("Query position complete. Retrieved positions: %s", position)
|
|
543
|
+
return position
|
|
536
544
|
|
|
537
|
-
def
|
|
545
|
+
def _sync_position(self) -> Tuple[bool, Position]:
|
|
538
546
|
"""
|
|
539
547
|
Synchronize internal position with actual machine position.
|
|
540
548
|
|
|
@@ -543,7 +551,7 @@ class GCodeController(SerialController):
|
|
|
543
551
|
it by moving to the internal position.
|
|
544
552
|
|
|
545
553
|
Returns:
|
|
546
|
-
Tuple of (adjustment_occurred: bool, final_position:
|
|
554
|
+
Tuple of (adjustment_occurred: bool, final_position: Position)
|
|
547
555
|
where adjustment_occurred is True if a correction move was made.
|
|
548
556
|
|
|
549
557
|
Note:
|
|
@@ -554,29 +562,30 @@ class GCodeController(SerialController):
|
|
|
554
562
|
# Query the actual machine position
|
|
555
563
|
queried_position = self.query_position()
|
|
556
564
|
|
|
557
|
-
if not queried_position:
|
|
565
|
+
if not queried_position.get_axes():
|
|
558
566
|
self._logger.error("Query position failed. Cannot synchronize.")
|
|
559
567
|
raise ValueError("Query position failed. Cannot synchronize.")
|
|
560
568
|
|
|
561
569
|
# Compare internal vs. queried position
|
|
562
|
-
axis_keys = ["
|
|
570
|
+
axis_keys = ["x", "y", "z", "a"]
|
|
563
571
|
adjustment_needed = False
|
|
564
572
|
|
|
565
573
|
for axis in axis_keys:
|
|
574
|
+
axis_upper = axis.upper()
|
|
566
575
|
if (
|
|
567
|
-
|
|
568
|
-
and axis
|
|
576
|
+
self._current_position.has_axis(axis)
|
|
577
|
+
and queried_position.has_axis(axis)
|
|
569
578
|
and abs(self._current_position[axis] - queried_position[axis])
|
|
570
579
|
> self.TOLERANCE
|
|
571
580
|
):
|
|
572
581
|
self._logger.warning(
|
|
573
582
|
"Position mismatch found on %s axis: Internal=%.3f, Queried=%.3f",
|
|
574
|
-
|
|
583
|
+
axis_upper,
|
|
575
584
|
self._current_position[axis],
|
|
576
585
|
queried_position[axis],
|
|
577
586
|
)
|
|
578
587
|
adjustment_needed = True
|
|
579
|
-
elif axis
|
|
588
|
+
elif queried_position.has_axis(axis):
|
|
580
589
|
# Update internal position with queried position if it differs slightly
|
|
581
590
|
self._current_position[axis] = queried_position[axis]
|
|
582
591
|
|
|
@@ -588,11 +597,11 @@ class GCodeController(SerialController):
|
|
|
588
597
|
)
|
|
589
598
|
|
|
590
599
|
try:
|
|
591
|
-
self.move_absolute(
|
|
600
|
+
self.move_absolute(position=self._current_position.copy())
|
|
592
601
|
self._logger.info("Synchronization move successfully completed.")
|
|
593
602
|
|
|
594
603
|
# Recursive call to verify position after move
|
|
595
|
-
return self.
|
|
604
|
+
return self._sync_position()
|
|
596
605
|
except (ValueError, RuntimeError, OSError) as e:
|
|
597
606
|
self._logger.error("Synchronization move failed: %s", e)
|
|
598
607
|
adjustment_needed = False
|
|
@@ -614,14 +623,14 @@ class GCodeController(SerialController):
|
|
|
614
623
|
Machine information string from the device
|
|
615
624
|
"""
|
|
616
625
|
self._logger.info("Querying machine information (M115).")
|
|
617
|
-
return self.execute(
|
|
626
|
+
return self.execute("M115")
|
|
618
627
|
|
|
619
|
-
def get_internal_position(self) ->
|
|
628
|
+
def get_internal_position(self) -> Position:
|
|
620
629
|
"""
|
|
621
630
|
Get the internally tracked position.
|
|
622
631
|
|
|
623
632
|
Returns:
|
|
624
|
-
|
|
633
|
+
Position containing the current internal position for all axes
|
|
625
634
|
"""
|
|
626
635
|
self._logger.debug("Returning internal position: %s", self._current_position)
|
|
627
|
-
return self._current_position.copy()
|
|
636
|
+
return self._current_position.copy()
|